New feature: array interpolation (experimental)

This commit is contained in:
DeepSOIC 2015-11-17 14:35:51 +03:00
parent f81441c1f8
commit c0aca13264
3 changed files with 263 additions and 0 deletions

View File

@ -64,6 +64,8 @@ class LatticeWorkbench (Workbench):
cmdsArrayTools = cmdsArrayTools + mod.exportedCommands
import latticeProjectArray as mod
cmdsArrayTools = cmdsArrayTools + mod.exportedCommands
import latticeResample as mod
cmdsArrayTools = cmdsArrayTools + mod.exportedCommands
import latticeApply as mod
cmdsArrayTools = cmdsArrayTools + mod.exportedCommands

View File

@ -0,0 +1,57 @@
import FreeCAD as App
import Part
class InterpolateF:
'''InterpolateF class interpolates an F(x) function from a set of points using BSpline interpolation'''
def __init__(self, XPoints = None, YPoints = None):
self.XPoints = XPoints
self.YPoints = YPoints
if XPoints is not None:
self.recompute
def recompute(self):
'''call before using value(), if changing sample values via attributes'''
#compute min-max
XPoints = self.XPoints
YPoints = self.YPoints
x_max = max(XPoints)
x_min = min(XPoints)
self._x_max = x_max
self._x_min = x_min
if x_max - x_min <= (x_max + x_min)*1e-9:
raise ValueError('X range too small')
min_x_step = x_max - x_min #initialize
for i in range(0,len(self.XPoints)-1):
step = abs( XPoints[i+1] - XPoints[i] )
if step <= (x_max + x_min)*1e-9:
raise ValueError("X points "+str(i)+"-"+str(i+1)+" are too close.")
if step < min_x_step:
min_x_step = step
y_min = min(YPoints)
y_max = max(YPoints)
# we want to make sure the smallest X step is way larger than possible
# Y step, so only X points affect knotting. This is what we are using
# _x_multiplicator for - it is the scaling applied to X coordinates of
# the interpolation points. Doing this will make u parameter of the
# spline equivalent to X coordinate.
self._x_multiplicator = 1e20*(y_max - y_min)/min_x_step
# This fixes nan outut if y span is zer length
if y_max - y_min < 1e-40:
self._x_multiplicator = 1.0
# create the spline
if not hasattr(self,"_spline"):
self._spline = Part.BSplineCurve()
spline = self._spline
points_for_spline = [App.Vector(XPoints[i]*self._x_multiplicator, YPoints[i], 0.0) for i in range(0,len(XPoints))]
spline.approximate(points_for_spline)
def value(self, x):
return self._spline.value( (x - self._x_min) / (self._x_max - self._x_min) ).y

204
latticeResample.py Normal file
View File

@ -0,0 +1,204 @@
#***************************************************************************
#* *
#* Copyright (c) 2015 - Victor Titov (DeepSOIC) *
#* <vv.titov@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
__title__="Lattice Resample object: changes the number of placements in an array, maintaining overall path. Aka interpolation."
__author__ = "DeepSOIC"
__url__ = ""
import math
import FreeCAD as App
import Part
from latticeCommon import *
import latticeBaseFeature
import latticeCompoundExplorer as LCE
import latticeInterpolatorUtil as LIU
import latticeExecuter
# -------------------------- document object --------------------------------------------------
def dotProduct(list1,list2):
sum = 0
for i in range(0,len(list1)):
sum += list1[i]*list2[i]
return sum
def makeLatticeResample(name):
'''makeLatticeResample(name): makes a LatticeResample object.'''
return latticeBaseFeature.makeLatticeFeature(name, LatticeResample, ViewProviderLatticeResample)
class LatticeResample(latticeBaseFeature.LatticeFeature):
"The Lattice Resample object"
def derivedInit(self,obj):
self.Type = "LatticeResample"
obj.addProperty("App::PropertyLink","Base","Lattice Resample","Lattice, the array of placements to be interpolated.")
obj.addProperty("App::PropertyEnumeration","TranslateMode","Lattice Resample","What to do with translation part of placements")
obj.TranslateMode = ['interpolate', 'reset']
obj.TranslateMode = 'interpolate'
obj.addProperty("App::PropertyEnumeration","OrientMode","Lattice Resample","what to do with orientation part of placements")
obj.OrientMode = ['interpolate', 'reset']
obj.OrientMode = 'interpolate'
obj.addProperty("App::PropertyFloat","NumberSamples","Lattice Resample","Number of placements to generate")
obj.NumberSamples = 51
def derivedExecute(self,obj):
# cache stuff
base = obj.Base.Shape
if not latticeBaseFeature.isObjectLattice(obj.Base):
latticeExecuter.warning(obj, "Base is not a lattice, but lattice is expected. Results may be unexpected.\n")
input = [leaf.Placement for leaf in LCE.AllLeaves(base)]
if len(input) < 2:
raise ValueError("At least 2 placements ar needed to interpolate; there are just "+str(len(input))+" in base array.")
if obj.NumberSamples < 2:
raise ValueError("Can output no less than 2 samples; "+str(obj.NumberSamples)+" was requested.")
#cache mode comparisons, for speed
posIsInterpolate = obj.TranslateMode == 'interpolate'
posIsReset = obj.TranslateMode == 'reset'
oriIsInterpolate = obj.OrientMode == 'interpolate'
oriIsReset = obj.OrientMode == 'reset'
# construct interpolation functions
# prepare lists of input samples
IArray = [float(i) for i in range(0,len(input))]
XArray = [plm.Base.x for plm in input]
YArray = [plm.Base.y for plm in input]
ZArray = [plm.Base.z for plm in input]
QArrays = [[],[],[],[]]
prevQ = [0.0]*4
for plm in input:
Q = plm.Rotation.Q
#test if quaernion has changed sign compared to previous one.
# Quaternions of opposite sign are equivalent in terms of rotation,
# but sign changes confuse interpolation, so we are detecting sign
# changes and discarding them
if dotProduct(Q,prevQ) < -ParaConfusion:
Q = [-v for v in Q]
for iQ in [0,1,2,3]:
QArrays[iQ].append( Q[iQ] )
prevQ = Q
# constuct function objects
interpolations = [] #container to receive all active interpolation function objects
if posIsInterpolate:
FX = LIU.InterpolateF(IArray,XArray)
interpolations.append(FX)
FY = LIU.InterpolateF(IArray,YArray)
interpolations.append(FY)
FZ = LIU.InterpolateF(IArray,ZArray)
interpolations.append(FZ)
if oriIsInterpolate:
FQs = []
for iQ in [0,1,2,3]:
FQs.append(LIU.InterpolateF(IArray,QArrays[iQ]))
interpolations.extend(FQs)
# recompute function objects
for F in interpolations:
F.recompute()
# initialize output containers and loop variables
outputPlms = [] #list of placements
for i_output in range(0,math.trunc(obj.NumberSamples+ParaConfusion)):
i_input = float(i_output) / (obj.NumberSamples-1) * (len(input)-1)
pos = App.Vector()
ori = App.Rotation()
if posIsInterpolate:
pos = App.Vector(FX.value(i_input), FY.value(i_input), FZ.value(i_input))
if oriIsInterpolate:
ori = App.Rotation(FQs[0].value(i_input),
FQs[1].value(i_input),
FQs[2].value(i_input),
FQs[3].value(i_input))
plm = App.Placement(pos, ori)
outputPlms.append(plm)
return outputPlms
class ViewProviderLatticeResample(latticeBaseFeature.ViewProviderLatticeFeature):
def getIcon(self):
return getIconPath('Lattice_Resample.svg')
def claimChildren(self):
return [self.Object.Base]
# -------------------------- /document object --------------------------------------------------
# -------------------------- Gui command --------------------------------------------------
def CreateLatticeResample(name):
sel = FreeCADGui.Selection.getSelectionEx()
FreeCAD.ActiveDocument.openTransaction("Create LatticeResample")
FreeCADGui.addModule("latticeResample")
FreeCADGui.addModule("latticeExecuter")
FreeCADGui.doCommand("f = latticeResample.makeLatticeResample(name='"+name+"')")
FreeCADGui.doCommand("f.Base = App.ActiveDocument."+sel[0].ObjectName)
FreeCADGui.doCommand("for child in f.ViewObject.Proxy.claimChildren():\n"+
" child.ViewObject.hide()")
FreeCADGui.doCommand("latticeExecuter.executeFeature(f)")
FreeCADGui.doCommand("f = None")
FreeCAD.ActiveDocument.commitTransaction()
class _CommandLatticeResample:
"Command to create LatticeResample feature"
def GetResources(self):
return {'Pixmap' : getIconPath("Lattice_Resample.svg"),
'MenuText': QtCore.QT_TRANSLATE_NOOP("Lattice_Resample","Resample Array"),
'Accel': "",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Lattice_Resample","Lattice Resample: interpolate placement-path using 3-rd degree b-spline interpolation.")}
def Activated(self):
if len(FreeCADGui.Selection.getSelection()) == 1 :
CreateLatticeResample(name = "Resample")
else:
mb = QtGui.QMessageBox()
mb.setIcon(mb.Icon.Warning)
mb.setText(translate("Lattice_Resample", "Please select one object, first. The object must be a lattice object (array of placements).", None))
mb.setWindowTitle(translate("Lattice_Resample","Bad selection", None))
mb.exec_()
def IsActive(self):
if FreeCAD.ActiveDocument:
return True
else:
return False
FreeCADGui.addCommand('Lattice_Resample', _CommandLatticeResample())
exportedCommands = ['Lattice_Resample']
# -------------------------- /Gui command --------------------------------------------------