diff --git a/InitGui.py b/InitGui.py index c144b62..264ff37 100644 --- a/InitGui.py +++ b/InitGui.py @@ -36,29 +36,31 @@ __Communication__ = 'vv.titov@gmail.com; DeepSOIC on FreeCAD forum' class LatticeWorkbench (Workbench): MenuText = 'Lattice' def Initialize(self): - commandslist0 = [] - commandslist1 = [] - commandslist2 = [] + cmdsArrayTools = [] + cmdsCompoundTools = [] + cmdsMiscTools = [] import latticePolarArray - commandslist0 = commandslist0 + latticePolarArray.exportedCommands + cmdsArrayTools = cmdsArrayTools + latticePolarArray.exportedCommands + import latticeCompose + cmdsArrayTools = cmdsArrayTools + latticeCompose.exportedCommands import latticeDowngrade - commandslist1 = commandslist1 + latticeDowngrade.exportedCommands + cmdsCompoundTools = cmdsCompoundTools + latticeDowngrade.exportedCommands import CompoundFilter - commandslist1 = commandslist1 + CompoundFilter.exportedCommands + cmdsCompoundTools = cmdsCompoundTools + CompoundFilter.exportedCommands import FuseCompound - commandslist1 = commandslist1 + FuseCompound.exportedCommands + cmdsCompoundTools = cmdsCompoundTools + FuseCompound.exportedCommands import latticeBoundBox - commandslist2 = commandslist2 + latticeBoundBox.exportedCommands + cmdsMiscTools = cmdsMiscTools + latticeBoundBox.exportedCommands - self.appendToolbar('LatticeArrayTools', commandslist0) - self.appendToolbar('LatticeCompoundTools', commandslist1) - self.appendToolbar('LatticeMiscTools', commandslist2) + self.appendToolbar('LatticeArrayTools', cmdsArrayTools) + self.appendToolbar('LatticeCompoundTools', cmdsCompoundTools) + self.appendToolbar('LatticeMiscTools', cmdsMiscTools) #FreeCADGui.addIconPath( '' ) #FreeCADGui.addPreferencePage( '','Lattice' ) - self.appendMenu('Lattice', commandslist0) - self.appendMenu('Lattice', commandslist1) - self.appendMenu('Lattice', commandslist2) + self.appendMenu('Lattice', cmdsArrayTools) + self.appendMenu('Lattice', cmdsCompoundTools) + self.appendMenu('Lattice', cmdsMiscTools) def Activated(self): pass diff --git a/latticeBaseFeature.py b/latticeBaseFeature.py index 20d59f3..d309180 100644 --- a/latticeBaseFeature.py +++ b/latticeBaseFeature.py @@ -103,6 +103,19 @@ class ViewProviderLatticeFeature: def __setstate__(self,state): return None + + def claimChildren(self): + return [] + def onDelete(self, feature, subelements): # subelements is a tuple of strings + try: + children = self.claimChildren() + if children and len(children) > 0: + for child in children: + child.ViewObject.show() + except Exception as err: + # catch all exceptions, because we don't want to prevent deletion if something goes wrong + FreeCAD.Console.PrintError("Error in onDelete: " + err.message) + return True \ No newline at end of file diff --git a/latticeCompose.py b/latticeCompose.py new file mode 100644 index 0000000..81304ea --- /dev/null +++ b/latticeCompose.py @@ -0,0 +1,181 @@ +#*************************************************************************** +#* * +#* Copyright (c) 2015 - 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 Compose object: combine elements of two lattices" +__author__ = "DeepSOIC" +__url__ = "" + +import math + +import FreeCAD as App +import Part + +from latticeCommon import * +import latticeBaseFeature +import latticeCompoundExplorer as LCE + +# -------------------------- document object -------------------------------------------------- + +def makeCompose(name): + '''makeCompose(name): makes a Compose object.''' + return latticeBaseFeature.makeLatticeFeature(name, Compose,'Lattice_Compose.svg', ViewProviderCompose) + +class Compose(latticeBaseFeature.LatticeFeature): + "The Lattice Compose object" + + operList = ['MultiplyPlacements','AveragePlacements', 'IgnoreBasePlacements','OverrideBasePlacements'] + + def derivedInit(self,obj): + self.Type = "LatticeCompose" + + obj.addProperty("App::PropertyEnumeration","Operation","Compose","Operation to perform between pairs of shapes") + + obj.Operation = Compose.operList + + + obj.addProperty("App::PropertyLink","Base","Base Compound","Base object. Usually a compound of generic shapes.") + + obj.addProperty("App::PropertyBool","BaseLoopSequence","Base Compound","If index goes out of range, apply modulo math.") + + obj.addProperty("App::PropertyBool","BaseFlattenHierarchy","Base Compound","Unpack subcompounds, to use all shapes, not just direct children.") + + obj.addProperty("App::PropertyBool","BaseKeepPosOfFirst","Base Compound","Makes the result pass through the first element of Base.") + obj.BaseKeepPosOfFirst = True + + obj.addProperty("App::PropertyLink","Tool","Tool Compound","Tool object. Usually a lattice object.") + + obj.addProperty("App::PropertyBool","ToolFlattenHierarchy","Tool Compound","Unpack subcompounds, to use all shapes, not just direct children.") + obj.ToolFlattenHierarchy = True + + + def execute(self,obj): + # Fill in (update read-only) properties that are driven by the mode. + base = obj.Base.Shape + if base.ShapeType != 'Compound': + base = Part.makeCompound([base]) + if obj.BaseFlattenHierarchy: + baseChildren = LCE.AllLeaves(base) + else: + baseChildren = base.childShapes() + + tool = obj.Tool.Shape + if tool.ShapeType != 'Compound': + tool = Part.makeCompound([tool]) + if obj.BaseFlattenHierarchy: + toolChildren = LCE.AllLeaves(tool) + else: + toolChildren = tool.childShapes() + + iBase = 0 + isMult = obj.Operation == 'MultiplyPlacements' # cache comparisons to speed them up + isAvg = obj.Operation == 'AveragePlacements' + isIgnore = obj.Operation == 'IgnoreBasePlacements' + isOvrride = obj.Operation == 'OverrideBasePlacements' + rst = [] + bFirst = True + plmMatcher = App.Placement() #extra placement, that aligns first tool member and first base member + for toolChild in toolChildren: + sh = baseChildren[iBase].copy() + if bFirst: + bFirst = False + if obj.BaseKeepPosOfFirst: + plmMatcher = toolChild.Placement.inverse() + toolPlm = plmMatcher.multiply(toolChild.Placement) + if isMult: + sh.Placement = toolPlm.multiply(sh.Placement) + elif isAvg: + plm1 = toolPlm + plm2 = sh.Placement + transl = plm1.Base*0.5 + plm2.Base*0.5 + a1,b1,c1,d1 = plm1.Rotation.Q + a2,b2,c2,d2 = plm2.Rotation.Q + rot = App.Rotation((a1+a2,b1+b2,c1+c2,d1+d2)) #no divide-by-two, because FreeCAD will normalize the quaternion automatically + sh.Placement = App.Placement(transl,rot) + elif isIgnore: + sh.Placement = toolPlm + elif isOverride: + sh.transformShape(toolPlm.inverse.multiply(sh.Placement)) + sh.Placement = toolPlm + rst.append(sh) + + iBase += 1 + if iBase > len(baseChildren)-1: + if obj.BaseLoopSequence: + iBase = 0 + else: + break + + obj.Shape = Part.makeCompound(rst) + +class ViewProviderCompose(latticeBaseFeature.ViewProviderLatticeFeature): + + def claimChildren(self): + return [self.Object.Base, self.Object.Tool] + +# -------------------------- /document object -------------------------------------------------- + +# -------------------------- Gui command -------------------------------------------------- + +def CreateCompose(name): + sel = FreeCADGui.Selection.getSelectionEx() + FreeCAD.ActiveDocument.openTransaction("Create Compose") + FreeCADGui.addModule("latticeCompose") + FreeCADGui.doCommand("f = latticeCompose.makeCompose(name='"+name+"')") + FreeCADGui.doCommand("f.Base = App.ActiveDocument."+sel[0].ObjectName) + FreeCADGui.doCommand("f.Tool = App.ActiveDocument."+sel[1].ObjectName) + FreeCADGui.doCommand("f.Proxy.execute(f)") + FreeCADGui.doCommand("f.purgeTouched()") + FreeCADGui.doCommand("f = None") + FreeCAD.ActiveDocument.commitTransaction() + + +class _CommandCompose: + "Command to create Compose feature" + def GetResources(self): + return {'Pixmap' : getIconPath("Lattice_Compose.svg"), + 'MenuText': QtCore.QT_TRANSLATE_NOOP("Lattice_Compose","Compose arrays"), + 'Accel': "", + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Lattice_Compose","Lattice Compose: element-wise operations between compounds")} + + def Activated(self): + if len(FreeCADGui.Selection.getSelection()) == 2 : + CreateCompose(name = "Compose") + else: + mb = QtGui.QMessageBox() + mb.setIcon(mb.Icon.Warning) + mb.setText(translate("Lattice_Compose", "Please select two objects, first. The fist object is Base, second is Tool. Base can contain real shapes, as well as be a lattice object. Tool is typically a lattice object.", None)) + mb.setWindowTitle(translate("Lattice_Compose","Bad selection", None)) + mb.exec_() + + def IsActive(self): + if FreeCAD.ActiveDocument: + return True + else: + return False + +FreeCADGui.addCommand('Lattice_Compose', _CommandCompose()) + +exportedCommands = ['Lattice_Compose'] + +# -------------------------- /Gui command -------------------------------------------------- + diff --git a/latticeCompoundExplorer.py b/latticeCompoundExplorer.py index 015e744..a84ac01 100644 --- a/latticeCompoundExplorer.py +++ b/latticeCompoundExplorer.py @@ -141,4 +141,12 @@ def CalculateNumberOfLeaves(compound): cnt = 0 for ch in children: cnt += CalculateNumberOfLeaves(ch) - return cnt \ No newline at end of file + return cnt + +def AllLeaves(compound): + 'AllLeaves(compound): Traverses the compound and collects all the leaves into a single list' + output = [] + for (child, msg, it) in CompoundExplorer(compound): + if msg == it.MSG_LEAF: + output.append(child) + return output \ No newline at end of file