#*************************************************************************** #* * #* 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 * #* * #*************************************************************************** from lattice2Common import * import lattice2CompoundExplorer as LCE import lattice2ShapeCopy as ShapeCopy import lattice2BaseFeature as LBF from lattice2GeomUtils import makeOrientationFromLocalAxes from lattice2Utils import getSelectionAsListOfLinkSub import lattice2Executer import FreeCAD as App __title__="Lattice Mirror module for FreeCAD" __author__ = "DeepSOIC" def mirrorShape(shape, pivotPlacement, flipX, flipY, flipZ): plmM = pivotPlacement.toMatrix() mirrM = App.Base.Matrix() if flipX: mirrM.A11 = -1 if flipY: mirrM.A22 = -1 if flipZ: mirrM.A33 = -1 m = plmM.multiply(mirrM.multiply(plmM.inverse())) return ShapeCopy.transformShape(shape, m) def mirrorPlacement(placement, pivotPlacement, flipX, flipY, flipZ): """mirrorPlacement(placement, pivotPlacement, flipX, flipY, flipZ): mirrors a placement. Y axis of placement is adjusted to keep the placement's CS right-handed.""" plmM = pivotPlacement.toMatrix() mirrM = App.Base.Matrix() if flipX: mirrM.A11 = -1 if flipY: mirrM.A22 = -1 if flipZ: mirrM.A33 = -1 m = plmM.multiply(mirrM.multiply(plmM.inverse())) OX = App.Vector(1,0,0) OZ = App.Vector(0,0,1) base = m.multiply(placement.Base) xdir = m.submatrix(3).multiply(placement.Rotation.multVec(OX)) zdir = m.submatrix(3).multiply(placement.Rotation.multVec(OZ)) rot = makeOrientationFromLocalAxes(zdir, xdir) return App.Placement(base, rot) def resolveSingleSublink(lnk): if lnk is None: raise ValueError("resolveSingleSublink: link is empty") obj, sub = lnk if len(sub)>1: raise ValueError("Too many subelements linked: num. Maximum: 1".format(num= len(sub))) sh = obj.Shape if len(sub) == 0 or sub[0] == '' else obj.Shape.getElement(sub[0]) shs = LCE.AllLeaves(sh) #if whole object is linked, it may be a compound containing the shape of interest. Explode it. if len(shs) != 1: raise ValueError("Linked is {num} shapes, but should be exactly one.".format(num= len(shs))) return shs[0] # -------------------------- document object -------------------------------------------------- def makeLatticeMirror(name): '''makeLatticeMirror(name): makes a LatticeMirror object.''' obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython",name) LatticeMirror(obj) ViewProviderLatticeMirror(obj.ViewObject) return obj class LatticeMirror(LBF.LatticeFeature): "The LatticeMirror object" def derivedInit(self,obj): self.Type = "LatticeMirror" obj.addProperty("App::PropertyLink","Object","Lattice Mirror","Object to mirror.") obj.addProperty("App::PropertyLinkSub","Pivot","Lattice Mirror","Object to mirror against. Can be a placement, a planar face, an edge (line), or a vertex.") obj.addProperty("App::PropertyBool","FlipX","Lattice Mirror","Sets if the object is to be flipped along X axis.") obj.addProperty("App::PropertyBool","FlipY","Lattice Mirror","Sets if the object is to be flipped along Y axis.") obj.addProperty("App::PropertyBool","FlipZ","Lattice Mirror","Sets if the object is to be flipped along Z axis.") obj.addProperty("App::PropertyPlacement","PivotPlacement","Lattice Mirror","Mirror pivot") obj.addProperty("App::PropertyEnumeration","ObjectTraversal","Lattice Mirror","Sets if base object should be treated as an array or not.") obj.ObjectTraversal = ['Use whole', 'Direct children only', 'Recursive'] obj.Proxy = self def derivedExecute(self,obj): base_is_lattice = LBF.isObjectLattice(obj.Object) pivot_is_lattice = LBF.isObjectLattice(obj.Pivot[0]) if obj.Pivot else True flipX = obj.FlipX flipY = obj.FlipY flipZ = obj.FlipZ # collect mirror pivot placements pivots = None em = 0 #editormode of PivotPlacement property. 0 = editable, 1 = read-only, 2 = hidden if obj.Pivot: em = 1 #read-only if pivot_is_lattice: pivots = LBF.getPlacementsList(obj.Pivot[0]) else: pivot_shape = resolveSingleSublink(obj.Pivot) if pivot_shape.ShapeType == 'Edge' and type(pivot_shape.Curve) is Part.Line: dir = pivot_shape.Curve.Direction base = pivot_shape.CenterOfMass if flipX != flipY: raise ValueError("Unsupported combination of flips for mirroring against line. FlipX and FlipY must either be both on or both off.") rot = makeOrientationFromLocalAxes(dir) pivots = [App.Placement(base, rot)] elif pivot_shape.ShapeType == 'Face' and type(pivot_shape.Surface) is Part.Plane: dir = pivot_shape.Surface.Axis base = pivot_shape.CenterOfMass if flipX != flipY: raise ValueError("Unsupported combination of flips for mirroring against line. FlipX and FlipY must either be both on or both off.") rot = makeOrientationFromLocalAxes(dir) pivots = [App.Placement(base, rot)] elif pivot_shape.ShapeType == 'Vertex': base = pivot_shape.Point pivots = [App.Placement(base, obj.PivotPlacement.Rotation)] em = 0 #editable else: raise TypeError("Unsupported geometry for use as mirror") if len(pivots) == 1: obj.PivotPlacement = pivots[0] else: em = 2 #hidden else: pivots = [obj.PivotPlacement] em = 0 obj.setEditorMode('PivotPlacement', em) # collect objects to be mirrored loop = False whole = obj.ObjectTraversal == 'Use whole' children = [] if base_is_lattice: children = LBF.getPlacementsList(obj.Object) else: if obj.ObjectTraversal == 'Use whole': children = [obj.Object.Shape] loop = True elif obj.ObjectTraversal == 'Direct children only': children = obj.Object.Shape.childShapes() elif obj.ObjectTraversal == 'Use whole': children = LCE.AllLeaves(obj.Object.Shape) else: raise ValueError("Traversal mode not implemented: {mode}".format(mode= obj.ObjectTraversal)) if len(pivots) != len(children) and not loop and not whole: lattice2Executer.warning(obj,"{label}: Number of children ({nch}) doesn't match the number of pivot placements ({npiv})" .format( label= obj.Label, nch= len(children), npiv= len(pivots) ) ) n = min(len(pivots), len(children)) else: n = len(pivots) # actual mirroring! result = [] for i in range(n): piv = pivots[i] ichild = i % len(children) if base_is_lattice: if whole: for plm in children: result.append(mirrorPlacement(plm, piv, flipX, flipY, flipZ)) else: result.append(mirrorPlacement(children[ichild], piv, flipX, flipY, flipZ)) else: result.append(mirrorShape(children[ichild], piv, flipX, flipY, flipZ)) # write out the result if base_is_lattice: return result else: if n == 1: result = ShapeCopy.transformCopy(result[0]) else: result = Part.Compound(result) obj.Shape = result return None class ViewProviderLatticeMirror(LBF.ViewProviderLatticeFeature): "A View Provider for the LatticeMirror object" def getIcon(self): obj = self.Object base_is_lattice = LBF.isObjectLattice(obj.Object) pivot_is_lattice = LBF.isObjectLattice(obj.Pivot[0]) if obj.Pivot else True whole = obj.ObjectTraversal == 'Use whole' key = 'Plm' if base_is_lattice else 'Sh' key += 's' if not whole and pivot_is_lattice else '' key += 'Plms' if pivot_is_lattice else 'Sh' return getIconPath("Lattice2_Mirror_{key}.svg".format(key= key)) def attach(self, vobj): self.ViewObject = vobj self.Object = vobj.Object def __getstate__(self): return None def __setstate__(self,state): return None def claimChildren(self): if self.Object.Pivot: return [screen(self.Object.Object), screen(self.Object.Pivot)] else: return [screen(self.Object.Object)] # -------------------------- /document object -------------------------------------------------- # -------------------------- Gui command -------------------------------------------------- def CreateLatticeMirror(name, extra_code = ''): FreeCAD.ActiveDocument.openTransaction("Create LatticeMirror") FreeCADGui.addModule("lattice2Mirror") FreeCADGui.addModule("lattice2Executer") FreeCADGui.addModule("lattice2Utils") FreeCADGui.doCommand("sel = lattice2Utils.getSelectionAsListOfLinkSub()") FreeCADGui.doCommand("f = lattice2Mirror.makeLatticeMirror(name = '"+name+"')") FreeCADGui.doCommand("f.Object = sel[0][0]") FreeCADGui.doCommand("if len(sel) == 2:\n" " f.Pivot = sel[1]") FreeCADGui.doCommand("f.Label = '{name} of {olabel}'.format(name= f.Name, olabel= f.Object.Label)") if extra_code: FreeCADGui.doCommand(extra_code) FreeCADGui.doCommand("lattice2Executer.executeFeature(f)") FreeCAD.ActiveDocument.commitTransaction() class CommandLatticeMirror: "Command to create LatticeMirror feature" def GetResources(self): return {'Pixmap' : getIconPath("Part_Mirror.svg"), 'MenuText': QtCore.QT_TRANSLATE_NOOP("Lattice2Mirror","Mirror"), 'Accel': "", 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Lattice2Mirror","Lattice Mirror: mirror, inversion or 180-turn of placements and shapes")} def Activated(self): try: sel = getSelectionAsListOfLinkSub() if len(sel) == 1 : #TODO: pop-up with options CreateLatticeMirror(name= "Mirror", extra_code= "f.FlipX = True" ) elif len(sel) == 2 : #TODO: pop-up with options instead of guessing lnk = sel[1] if LBF.isObjectLattice(lnk[0]): extra_code = ( "f.FlipY = True" ) else: sh = resolveSingleSublink(lnk) if sh.ShapeType == 'Face': extra_code = ( "f.FlipZ = True" ) elif sh.ShapeType == 'Edge': extra_code = ( "f.FlipX = True\n" "f.FlipY = True" ) elif sh.ShapeType == 'Vertex': extra_code = ( "f.FlipX = True\n" "f.FlipY = True\n" "f.FlipZ = True" ) CreateLatticeMirror(name = "Mirror", extra_code= extra_code) else: infoMessage("Lattice Mirror","Lattice Mirror feature. Mirrors shapes and placements. Please select object to be mirrored, first," " and then the mirror object (optional). Then invoke this tool.\n\n" "Object to be mirrored: any shape, or compound of shapes, or a placement, or an array of placements." " Note that when a placement is mirrored, its Y axis is switched, for the coordinate system to remain right-handed.\n\n" "Mirror object: either a placement, an array of placements, a vertex, a line, or a plane face. If an array of" " placements is used, the object is reflected using each placement as mirror, and the result is packed into a compound.\n\n" "You can adjust the mirroring direction in property editor by editing FlipX, FlipY, FlipZ properties." " The mirror object is used to establish the coordinate system to work in. If the mirror object is not" " specified, global coordinate system is used (and a custom one can be set up by editing PivotPlacement).") except Exception as err: msgError(err) def IsActive(self): if FreeCAD.ActiveDocument: return True else: return False if FreeCAD.GuiUp: FreeCADGui.addCommand('Lattice2Mirror', CommandLatticeMirror()) exportedCommands = ['Lattice2Mirror'] # -------------------------- /Gui command --------------------------------------------------