New feature: array interpolation (experimental)
This commit is contained in:
parent
f81441c1f8
commit
c0aca13264
|
@ -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
|
||||
|
|
57
latticeInterpolatorUtil.py
Normal file
57
latticeInterpolatorUtil.py
Normal 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
204
latticeResample.py
Normal 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 --------------------------------------------------
|
||||
|
Loading…
Reference in New Issue
Block a user