diff --git a/InitGui.py b/InitGui.py index 767e822..3dad2b0 100644 --- a/InitGui.py +++ b/InitGui.py @@ -1,23 +1,23 @@ #*************************************************************************** #* * -#* Copyright (c) 2015 - Victor Titov (DeepSOIC) * +#* 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 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. * +#* 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 * +#* 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 * #* * #*************************************************************************** @@ -51,6 +51,8 @@ class LatticeWorkbench (Workbench): cmdsArrayTools = cmdsArrayTools + latticePlacement.exportedCommands import latticePolarArray cmdsArrayTools = cmdsArrayTools + latticePolarArray.exportedCommands + import latticeArrayFromShape + cmdsArrayTools = cmdsArrayTools + latticeArrayFromShape.exportedCommands import latticeApply cmdsArrayTools = cmdsArrayTools + latticeApply.exportedCommands import latticeCompose diff --git a/latticeArrayFromShape.py b/latticeArrayFromShape.py new file mode 100644 index 0000000..61123d3 --- /dev/null +++ b/latticeArrayFromShape.py @@ -0,0 +1,216 @@ +#*************************************************************************** +#* * +#* 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 ArrayFromShape object: creates an array of placements from a compound." +__author__ = "DeepSOIC" +__url__ = "" + +import math + +import FreeCAD as App +import Part + +from latticeCommon import * +import latticeBaseFeature +import latticeCompoundExplorer as LCE +import latticeGeomUtils as Utils + +# -------------------------- document object -------------------------------------------------- + +def makeLatticeArrayFromShape(name): + '''makeLatticeArrayFromShape(name): makes a LatticeArrayFromShape object.''' + return latticeBaseFeature.makeLatticeFeature(name, LatticeArrayFromShape,'Lattice_ArrayFromShape.svg') + +class LatticeArrayFromShape(latticeBaseFeature.LatticeFeature): + "The Lattice ArrayFromShape object" + + def derivedInit(self,obj): + self.Type = "LatticeArrayFromShape" + + obj.addProperty("App::PropertyLink","Base","Lattice ArrayFromShape","Object to generate array of placements from. Should be a compound. If not, single placement will be created.") + + obj.addProperty("App::PropertyBool","FlattenBaseHierarchy","Lattice ArrayFromShape","Unpack subcompounds, to use all shapes, not just direct children.") + obj.FlattenBaseHierarchy = True + + obj.addProperty("App::PropertyEnumeration","TranslateMode","Lattice ArrayFromShape","Method of deriving translation part of output placements") + obj.TranslateMode = ['(none)', 'base', 'child', 'child.CenterOfMass','child.CenterOfBoundBox','child.Vertex'] + obj.TranslateMode = 'child' + + obj.addProperty("App::PropertyInteger","TranslateElementIndex","Lattice ArrayFromShape","Index of vertex used for translation calculation.") + + obj.addProperty("App::PropertyEnumeration","OrientMode","Lattice ArrayFromShape","Method of deriving orientation part of output placements") + obj.OrientMode = ['(none)', 'base', 'child', 'child.InertiaAxes','child.Edge', 'child.FaceAxis'] + obj.OrientMode = 'child' + + obj.addProperty("App::PropertyInteger","OrientElementIndex","Lattice ArrayFromShape","Index of subelement used for orientation calculation.") + + def derivedExecute(self,obj): + # cache stuff + base = obj.Base.Shape + if base.ShapeType != 'Compound': + base = Part.makeCompound([base]) + if obj.FlattenBaseHierarchy: + baseChildren = LCE.AllLeaves(base) + else: + baseChildren = base.childShapes() + + #cache mode comparisons, for speed + posIsNone = obj.TranslateMode == '(none)' + posIsBase = obj.TranslateMode == 'base' + posIsChild = obj.TranslateMode == 'child' + posIsCenterM = obj.TranslateMode == 'child.CenterOfMass' + posIsCenterBB = obj.TranslateMode == 'child.CenterOfBoundBox' + posIsVertex = obj.TranslateMode == 'child.Vertex' + + oriIsNone = obj.OrientMode == '(none)' + oriIsBase = obj.OrientMode == 'base' + oriIsChild = obj.OrientMode == 'child' + oriIsInertial = obj.OrientMode == 'child.InertiaAxes' + oriIsEdge = obj.OrientMode == 'child.Edge' + oriIsFace = obj.OrientMode == 'child.FaceAxis' + + # initialize output containers and loop variables + outputPlms = [] #list of placements + + # the essence + for child in baseChildren: + pos = App.Vector() + ori = App.Rotation() + if posIsNone: + pass + elif posIsBase: + pos = base.Placement.Base + elif posIsChild: + pos = child.Placement.Base + elif posIsCenterM: + leaves = LCE.AllLeaves(child) + totalW = 0 + weightAttrib = {"Vertex":"", + "Edge":"Length", + "Wire":"Length", + "Face":"Area", + "Shell":"Area", + "Solid":"Volume", + "CompSolid":""} + #Center of mass of a compound is a weghted average of centers + # of mass of individual objects. + for leaf in leaves: + w = 1.0 if not weightAttrib else (getattr(leaf, weightAttrib)) + if child.ShapeType == 'Vertex': + childCM = child.Point + #elif child.ShapeType == 'CompSolid': + #todo + else: + childCM = child.CenterOfMass + pos += childCM * w + totalW += w + pos = pos * (1.0/totalW) + elif posIsCenterBB: + import latticeBoundBox + bb = latticeBoundBox.getPrecisionBoundBox(child) + pos = bb.Center + elif posIsVertex: + v = child.Vertexes[obj.TranslateElementIndex - 1] + else: + raise ValueError("latticePolarArrayFromShape: translation mode not implemented: "+obj.TranslateMode) + + if oriIsNone: + pass + elif oriIsBase: + ori = base.Placement.Rotation + elif oriIsChild: + ori = child.Placement.Rotation + elif oriIsInertial: + leaves = LCE.AllLeaves(child) + if len(leaves)>1: + raise ValueError("latticePolarArrayFromShape: calculation of principal axes of compounds is not supported yet") + props = leaves[0].PrincipalProperties + XAx = props['FirstAxisOfInertia'] + ZAx = props['ThirdAxisOfInertia'] + ori = Utils.makeOrientationFromLocalAxes(ZAx, XAx) + elif oriIsEdge: + edge = child.Edges[obj.OrientElementIndex - 1] + XAx = edge.Curve.tangent(edge.Curve.FirstParameter)[0] + ori1 = Utils.makeOrientationFromLocalAxes(ZAx= XAx) + ori2 = Utils.makeOrientationFromLocalAxes(ZAx= App.Vector(1,0,0),XAx= App.Vector(0,0,1)) + ori = ori1.multiply(ori2) + elif oriIsFace: + face = child.Faces[obj.OrientElementIndex - 1] + ZAx = face.Surface.Axis + else: + raise ValueError("latticePolarArrayFromShape: rientation mode not implemented: "+obj.OrientMode) + + plm = App.Placement(pos, ori) + outputPlms.append(plm) + if (posIsNone or posIsBase) and (oriIsNone or oriIsBase): + break #output just one placement if modes are set so that placement does not depend on current child + return outputPlms + +# -------------------------- /document object -------------------------------------------------- + +# -------------------------- Gui command -------------------------------------------------- + +def CreateLatticeArrayFromShape(name): + sel = FreeCADGui.Selection.getSelectionEx() + FreeCAD.ActiveDocument.openTransaction("Create LatticeArrayFromShape") + FreeCADGui.addModule("latticeArrayFromShape") + FreeCADGui.doCommand("f = latticeArrayFromShape.makeLatticeArrayFromShape(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("f.Proxy.execute(f)") + FreeCADGui.doCommand("f.purgeTouched()") + FreeCADGui.doCommand("f = None") + FreeCAD.ActiveDocument.commitTransaction() + + +class _CommandLatticeArrayFromShape: + "Command to create LatticeArrayFromShape feature" + def GetResources(self): + return {'Pixmap' : getIconPath("Lattice_ArrayFromShape.svg"), + 'MenuText': QtCore.QT_TRANSLATE_NOOP("Lattice_ArrayFromShape","Make lattice from compound"), + 'Accel': "", + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Lattice_ArrayFromShape","Lattice ArrayFromShape: make placements array from shapes in a compound.")} + + def Activated(self): + if len(FreeCADGui.Selection.getSelection()) == 1 : + CreateLatticeArrayFromShape(name = "ArrayFromShape") + else: + mb = QtGui.QMessageBox() + mb.setIcon(mb.Icon.Warning) + mb.setText(translate("Lattice_ArrayFromShape", "Please select one object, first.", None)) + mb.setWindowTitle(translate("Lattice_ArrayFromShape","Bad selection", None)) + mb.exec_() + + def IsActive(self): + if FreeCAD.ActiveDocument: + return True + else: + return False + +FreeCADGui.addCommand('Lattice_ArrayFromShape', _CommandLatticeArrayFromShape()) + +exportedCommands = ['Lattice_ArrayFromShape'] + +# -------------------------- /Gui command -------------------------------------------------- + diff --git a/latticeGeomUtils.py b/latticeGeomUtils.py new file mode 100644 index 0000000..f22620a --- /dev/null +++ b/latticeGeomUtils.py @@ -0,0 +1,69 @@ +#*************************************************************************** +#* * +#* 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 * +#* * +#*************************************************************************** + + +import FreeCAD as App + +from latticeCommon import * + +__title__="Geometric utility routines for Lattice workbench for FreeCAD" +__author__ = "DeepSOIC" +__url__ = "" + + +def makeOrientationFromLocalAxes(ZAx, XAx = None): + ''' + makeOrientationFromLocalAxes(ZAx, XAx): constructs App.Rotation to get into + alignment with given local Z and X axes. Z axis is followed strictly; X axis + is a guide and can be not strictly perpendicular to Z axis; it will be + corrected and modified + + ''' + if XAx is None: + XAx = App.Vector(0,0,1) #Why Z? Because I prefer local X axis to be aligned so that local XZ plane is parallel to global Z axis. + #First, compute all three axes. + ZAx.normalize() #just to be sure; it's important to have the matrix normalized + YAx = ZAx.cross(XAx) # construct Y axis + if YAx.Length < ParaConfusion*10.0: + #failed, try some other X axis direction hint + XAx = App.Vector(0,0,1) + YAx = ZAx.cross(XAx) + if YAx.Length < ParaConfusion*10.0: + #failed again. Now, we can tell, that local Z axis is along global + # Z axis + XAx = App.Vector(1,0,0) + YAx = ZAx.cross(XAx) + + YAx.normalize() + XAx = YAx.cross(ZAx) # force X perpendicular + + #hacky way of constucting rotation to a local coordinate system: + # make matrix, + m = App.Matrix() + m.A = list(XAx)+[0.0]+list(YAx)+[0.0]+list(ZAx)+[0.0]+[0.0]*3+[1.0] + m.transpose() # local axes vectors are columns of matrix, but we put them in as rwos, because it is convenient, and then transpose it. + # make placement out of matrix, + tmpplm = App.Placement(m) + # and extract rotation from placement. + ori = tmpplm.Rotation + return ori