From 50f4372b02cff6dc4c6202b6a3d03381311ff2e3 Mon Sep 17 00:00:00 2001 From: DeepSOIC Date: Fri, 7 Sep 2018 17:40:32 +0300 Subject: [PATCH] Resample: re-implement, + reference placement support Now uses ValueSeriesGenerator for extra flexibility --- InitGui.py | 2 +- Lattice2ArrayFeatures.py | 1 + lattice2Resample2.py | 257 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 lattice2Resample2.py diff --git a/InitGui.py b/InitGui.py index dad2c51..c61df41 100644 --- a/InitGui.py +++ b/InitGui.py @@ -63,7 +63,7 @@ class Lattice2Workbench (Workbench): + Lattice2.ArrayFeatures.JoinArrays.exportedCommands + Lattice2.ArrayFeatures.ArrayFilter.exportedCommands + Lattice2.ArrayFeatures.ProjectArray.exportedCommands - + Lattice2.ArrayFeatures.Resample.exportedCommands + + Lattice2.ArrayFeatures.Resample2.exportedCommands + Lattice2.ArrayFeatures.PopulateCopies.exportedCommands + Lattice2.ArrayFeatures.PopulateChildren.exportedCommands + Lattice2.ArrayFeatures.Mirror.exportedCommands diff --git a/Lattice2ArrayFeatures.py b/Lattice2ArrayFeatures.py index 3517ce4..131199d 100644 --- a/Lattice2ArrayFeatures.py +++ b/Lattice2ArrayFeatures.py @@ -12,4 +12,5 @@ import lattice2PopulateChildren as PopulateChildren import lattice2PopulateCopies as PopulateCopies import lattice2ProjectArray as ProjectArray import lattice2Resample as Resample +import lattice2Resample2 as Resample2 import lattice2Mirror as Mirror \ No newline at end of file diff --git a/lattice2Resample2.py b/lattice2Resample2.py new file mode 100644 index 0000000..d1c745e --- /dev/null +++ b/lattice2Resample2.py @@ -0,0 +1,257 @@ +#*************************************************************************** +#* * +#* Copyright (c) 2018 - Victor Titov (DeepSOIC) * +#* * +#* * +#* 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 FreeCAD as App +import Part + +from lattice2Common import * +import lattice2BaseFeature +import lattice2InterpolatorUtil as LIU +import lattice2Executer +from lattice2ValueSeriesGenerator import ValueSeriesGenerator + +# -------------------------- 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 lattice2BaseFeature.makeLatticeFeature(name, LatticeResample, ViewProviderLatticeResample) + +class LatticeResample(lattice2BaseFeature.LatticeFeature): + "The Lattice Resample object" + + def derivedInit(self,selfobj): + selfobj.addProperty("App::PropertyLink","Base","Lattice Resample","Lattice, the array of placements to be interpolated.") + + selfobj.addProperty("App::PropertyEnumeration","TranslateMode","Lattice Resample","What to do with translation part of placements") + selfobj.TranslateMode = ['interpolate', 'reset'] + selfobj.TranslateMode = 'interpolate' + + selfobj.addProperty("App::PropertyEnumeration","OrientMode","Lattice Resample","what to do with orientation part of placements") + selfobj.OrientMode = ['interpolate', 'reset'] + selfobj.OrientMode = 'interpolate' + + self.assureGenerator(selfobj) + selfobj.ValuesSource = "Generator" + selfobj.GeneratorMode = "SpanN" + selfobj.EndInclusive = True + selfobj.SpanStart = 0.0 + selfobj.SpanEnd = 1.0 + selfobj.Step = 1.0/51.0 + selfobj.Count = 51 + + def assureProperties(self, selfobj, creating_new = False): + super(LatticeResample, self).assureProperties(selfobj, creating_new) + + created = self.assureProperty(selfobj, + 'App::PropertyEnumeration', + 'ReferencePlacementOption', + ['external', 'origin', 'inherit', 'SpanStart', 'SpanEnd', 'at custom value', 'first placement', 'last placement'], + "Lattice Resample", + "Reference placement, corresponds to the original occurrence of the object to be populated." + ) + if created: + selfobj.ReferencePlacementOption = 'SpanStart' + self.assureProperty(selfobj, 'App::PropertyFloat', 'ReferenceValue', 0.0, "Lattice Resample", "Sets the value to use for generating ReferencePlacement. This value sets, what coordinate the object to be populated corresponds to.") + + def assureGenerator(self, selfobj): + '''Adds an instance of value series generator, if one doesn't exist yet.''' + if hasattr(self,"generator"): + return + self.generator = ValueSeriesGenerator(selfobj) + self.generator.addProperties(groupname= "Lattice Array", + groupname_gen= "Lattice Series Generator", + valuesdoc= "List of parameter values. Values should be in range 0..n-1 for interpolation, and can be outside for extrapolation.", + valuestype= "App::PropertyFloat") + + def updateReadonlyness(self, selfobj): + super(LatticeResample, self).updateReadonlyness(selfobj) + + self.assureGenerator(selfobj) + self.generator.updateReadonlyness() + + selfobj.setEditorMode('ReferenceValue', 0 if selfobj.ReferencePlacementOption == 'at custom value' else 2) + + + def recomputeReferencePlm(self, selfobj, selfplacements): #override + if selfobj.ReferencePlacementOption == 'external': + super(LatticeResample, self).recomputeReferencePlm(selfobj, selfplacements) + #the remaining options are handled in derivedExecute + + def derivedExecute(self,selfobj): + self.assureGenerator(selfobj) + + self.generator.execute() + values = [float(strv) for strv in selfobj.Values] + + input = lattice2BaseFeature.getPlacementsList(selfobj.Base) + + if len(input) < 2: + raise ValueError("At least 2 placements ar needed to interpolate; there are just "+str(len(input))+" in base array.") + + #cache mode comparisons, for speed + posIsInterpolate = selfobj.TranslateMode == 'interpolate' + posIsReset = selfobj.TranslateMode == 'reset' + + oriIsInterpolate = selfobj.OrientMode == 'interpolate' + oriIsReset = selfobj.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 + if posIsInterpolate: + FX = LIU.InterpolateF(IArray,XArray) + FY = LIU.InterpolateF(IArray,YArray) + FZ = LIU.InterpolateF(IArray,ZArray) + if oriIsInterpolate: + FQs = [] + for iQ in [0,1,2,3]: + FQs.append(LIU.InterpolateF(IArray,QArrays[iQ])) + + def plmByVal(val): + pos = App.Vector() + ori = App.Rotation() + if posIsInterpolate: + pos = App.Vector(FX.value(val), FY.value(val), FZ.value(val)) + + if oriIsInterpolate: + ori = App.Rotation(FQs[0].value(val), + FQs[1].value(val), + FQs[2].value(val), + FQs[3].value(val)) + return App.Placement(pos, ori) + + output = [plmByVal(val) for val in values] + + # update reference placement + ref = selfobj.ReferencePlacementOption + if ref == 'external': + pass + elif ref == 'origin': + self.setReferencePlm(selfobj, None) + elif ref == 'inherit': + self.setReferencePlm(selfobj, lattice2BaseFeature.getReferencePlm(selfobj.Base)) + elif ref == 'SpanStart': + self.setReferencePlm(selfobj, plmByVal(float(selfobj.SpanStart))) + elif ref == 'SpanEnd': + self.setReferencePlm(selfobj, plmByVal(float(selfobj.SpanEnd))) + elif ref == 'at custom value': + self.setReferencePlm(selfobj, plmByVal(float(selfobj.ReferenceValue))) + elif ref == 'first placement': + self.setReferencePlm(selfobj, output[0]) + elif ref == 'last placement': + self.setReferencePlm(selfobj, output[-1]) + else: + raise NotImplementedError("Reference option not implemented: " + ref) + + return output + + +class ViewProviderLatticeResample(lattice2BaseFeature.ViewProviderLatticeFeature): + + def getIcon(self): + return getIconPath('Lattice2_Resample.svg') + + def claimChildren(self): + return [screen(self.Object.Base)] + + +# -------------------------- /document object -------------------------------------------------- + +# -------------------------- Gui command -------------------------------------------------- + +def CreateLatticeResample(name): + sel = FreeCADGui.Selection.getSelectionEx() + FreeCAD.ActiveDocument.openTransaction("Create LatticeResample") + FreeCADGui.addModule("lattice2Resample2") + FreeCADGui.addModule("lattice2Executer") + FreeCADGui.doCommand("f = lattice2Resample2.makeLatticeResample(name='"+name+"')") + FreeCADGui.doCommand("f.Base = App.ActiveDocument."+sel[0].ObjectName) + FreeCADGui.doCommand("f.setExpression('SpanEnd', '{base}.NumElements - 1')".format(base= lattice2BaseFeature.source(sel[0].Object)[1].Name)) + FreeCADGui.doCommand("for child in f.ViewObject.Proxy.claimChildren():\n"+ + " child.ViewObject.hide()") + FreeCADGui.doCommand("lattice2Executer.executeFeature(f)") + FreeCADGui.doCommand("f = None") + FreeCAD.ActiveDocument.commitTransaction() + + +class _CommandLatticeResample: + "Command to create LatticeResample feature" + def GetResources(self): + return {'Pixmap' : getIconPath("Lattice2_Resample.svg"), + 'MenuText': QtCore.QT_TRANSLATE_NOOP("Lattice2_Resample","Resample Array"), + 'Accel': "", + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Lattice2_Resample","Lattice Resample: interpolate placement-path using 3-rd degree b-spline interpolation.")} + + def Activated(self): + try: + if len(FreeCADGui.Selection.getSelection()) == 1 : + CreateLatticeResample(name = "Resample") + else: + infoMessage( + "Lattice Resample command. Interpolates an array of placements, using 3-rd dergee bsplines.\n\n" + "Please select one object, first. The object must be an array of placements." + ) + except Exception as err: + msgError(err) + + def IsActive(self): + if FreeCAD.ActiveDocument: + return True + else: + return False + +if FreeCAD.GuiUp: + FreeCADGui.addCommand('Lattice2_Resample2', _CommandLatticeResample()) + +exportedCommands = ['Lattice2_Resample2'] + +# -------------------------- /Gui command -------------------------------------------------- +