Lattice2/lattice2Mirror.py
2018-05-14 22:42:50 +03:00

318 lines
15 KiB
Python

#***************************************************************************
#* *
#* Copyright (c) 2018 - 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 *
#* *
#***************************************************************************
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)
if FreeCAD.GuiUp:
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 --------------------------------------------------