Lattice2/lattice2PDPattern.py
luz.paz 0c595166f3 Misc. typo fixes
Found via `codespell -q 3 -L ang,childs,sinc,vertexes`
2019-05-21 12:55:38 +03:00

362 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 *
#* *
#***************************************************************************
__title__="Lattice PartDesign Pattern object: a partdesign pattern based on Lattice array."
__author__ = "DeepSOIC"
__url__ = ""
import FreeCAD as App
import Part
from lattice2Common import *
import lattice2BaseFeature
import lattice2Executer
from lattice2ShapeCopy import shallowCopy, transformCopy_Smart
from lattice2PopulateCopies import DereferenceArray
class FeatureUnsupportedError(RuntimeError):
pass
class NotPartDesignFeatureError(RuntimeError):
pass
class FeatureFailure(RuntimeError):
pass
class ScopeError(RuntimeError):
pass
class MultiTransformSettings(object):
selfintersections = False #if True, take care of intersections between occurrences. If False, optimize assuming occurrences do not intersect.
sign_override = +1 #+1 for keep sign, -1 for invert, +2 for force positive, -2 for force negative
def makeFeature():
'''makeFeature(): makes a PartDesignPattern object.'''
obj = activeBody().newObject("PartDesign::FeaturePython","LatticePattern")
LatticePDPattern(obj)
if FreeCAD.GuiUp:
ViewProviderLatticePDPattern(obj.ViewObject)
return obj
def getBodySequence(body, skipfirst = False):
visited = set()
result = []
curfeature = body.Tip
while True:
if curfeature in visited:
raise ValueError("Feature sequence is looped in {body}".format(body= body.Name))
if curfeature is None:
break
if not curfeature.isDerivedFrom('PartDesign::Feature'):
break
if not body.hasObject(curfeature):
break
if curfeature.isDerivedFrom('PartDesign::FeatureBase'):
#base feature for body. Do not include.
break
visited.add(curfeature)
result.insert(0, curfeature)
curfeature = curfeature.BaseFeature
if skipfirst:
result.pop(0)
return result
def feature_sign(feature, raise_if_unsupported = False):
"""feature_sign(feature, raise_if_unsupported = False): returns +1 for additive PD features, -1 for subtractive PD features, and 0 for the remaining (unsupported)"""
additive_types = [
'PartDesign::Pad',
'PartDesign::Revolution',
]
subtractive_types = [
'PartDesign::Pocket',
'PartDesign::Groove',
'PartDesign::Hole',
]
def unsupported():
if raise_if_unsupported:
raise FeatureUnsupportedError("Feature {name} is neither additive nor subtractive. Unsupported.".format(name= feature.Name))
else:
return 0
if not feature.isDerivedFrom('PartDesign::Feature'):
raise NotPartDesignFeatureError("Feature {name} is not a PartDesign feature. Unsupported.".format(name= feature.Name))
if hasattr(feature, 'AddSubType'): #part-o-magic; possibly PartDesign future
t = feature.AddSubType
if t == 'Additive':
return +1
elif t == 'Subtractive':
return -1
else:
return unsupported()
typ = feature.TypeId
if typ in additive_types:
return 1
if typ in subtractive_types:
return -1
if 'Additive' in typ:
return +1
if 'Subtractive' in typ:
return -1
if typ == 'PartDesign::Boolean':
t = feature.Type
if t == 'Fuse':
return +1
elif t == 'Cut':
return -1
else:
return unsupported()
return unsupported()
def getFeatureShapes(feature):
sign = feature_sign(feature, raise_if_unsupported= True)
if hasattr(feature, 'AddSubShape'):
sh = shallowCopy(feature.AddSubShape)
sh.Placement = feature.Placement
return [(sign, sh)]
elif feature.isDerivedFrom('PartDesign::Boolean'):
return [(sign, obj.Shape) for obj in feature.Group]
else:
raise FeatureUnsupportedError("Feature {name} is not supported.".format(name= feature.Name))
def is_supported(feature):
if hasattr(feature, 'Proxy') and hasattr(feature.Proxy, 'applyTransformed'):
return True
try:
sign = feature_sign(feature, raise_if_unsupported= True)
return True
except (FeatureUnsupportedError, NotPartDesignFeatureError):
return False
def applyFeature(baseshape, feature, transforms, mts):
if hasattr(feature, 'Proxy') and hasattr(feature.Proxy, 'applyTransformed'):
return feature.Proxy.applyTransformed(feature, baseshape, transforms, mts)
task = getFeatureShapes(feature)
for sign,featureshape in task:
actionshapes = []
for transform in transforms:
actionshapes.append(shallowCopy(featureshape, transform))
if mts.selfintersections:
pass #to fuse the shapes to baseshape one by one
else:
actionshapes = [Part.Compound(actionshapes)] #to fuse all at once, saving for computing intersections between the occurrences of the feature
for actionshape in actionshapes:
assert(sign != 0)
realsign = sign * mts.sign_override
if abs(mts.sign_override) == +2:
realsign = int(mts.sign_override / 2)
if realsign > 0:
baseshape = baseshape.fuse(actionshape)
elif realsign < 0:
baseshape = baseshape.cut(actionshape)
if baseshape.isNull():
raise FeatureFailure('applying {name} failed - returned shape is null'.format(name= feature.Name))
return baseshape
class LatticePDPattern(object):
def __init__(self,obj):
obj.addProperty('App::PropertyLinkListGlobal','FeaturesToCopy',"Lattice Pattern","Features to be copied (can be a body)")
obj.addProperty('App::PropertyLinkGlobal','PlacementsFrom',"Lattice Pattern","Reference placement (placement that marks where the original feature is)")
obj.addProperty('App::PropertyLink','PlacementsTo',"Lattice Pattern","Target placements")
obj.addProperty('App::PropertyEnumeration','Referencing',"Lattice Pattern","Reference placement mode (sets what to grab the feature by).")
obj.Referencing = ['Origin','First item', 'Last item', 'Use PlacementsFrom']
obj.addProperty('App::PropertyBool', 'IgnoreUnsupported', "Lattice Pattern", "Skip unsupported features such as fillets, instead of throwing errors")
obj.addProperty('App::PropertyBool', 'SkipFirstInBody', "Lattice Pattern", "Skip first body feature (which may be used as support for the important features).")
obj.addProperty('App::PropertyEnumeration', 'SignOverride', "Lattice Pattern", "Use it to change Pockets into Pads.")
obj.SignOverride = ['keep', 'invert', 'as additive', 'as subtractive']
obj.addProperty('App::PropertyBool', 'Selfintersections', "Lattice Pattern", "If True, take care of intersections between occurrences. If False, you get a slight speed boost.")
obj.addProperty('App::PropertyBool', 'Refine', "PartDesign", "If True, remove redundant edges after this operation.")
obj.Refine = getParamPDRefine()
obj.addProperty('App::PropertyBool', 'SingleSolid', "PartDesign", "If True, discard solids not joined with the base.")
obj.Proxy = self
def execute(self, selfobj):
if selfobj.BaseFeature is None:
baseshape = Part.Compound([])
else:
baseshape = selfobj.BaseFeature.Shape
mts = MultiTransformSettings()
mts.sign_override = {'keep': +1, 'invert': -1, 'as additive': +2 , 'as subtractive': -2}[selfobj.SignOverride]
mts.selfintersections = selfobj.Selfintersections
result = self.applyTransformed(selfobj, baseshape, None, mts)
if selfobj.SingleSolid:
# not proper implementation, but should do for majority of cases: pick the largest solid.
vmax = 0
vmax_solid = None
for s in result.Solids:
v = s.Volume
if v > vmax:
vmax = v
vmax_solid = s
if vmax_solid is None:
raise ValueError("No solids in result. Maybe the result is corrupted because of failed BOP, or all the material was removed in the end.")
result = vmax_solid
if selfobj.SingleSolid or len(result.Solids) == 1:
result = transformCopy_Smart(result.Solids[0], selfobj.Placement)
if selfobj.Refine:
result = result.removeSplitter()
selfobj.Shape = result
def applyTransformed(self, selfobj, baseshape, transforms, mts):
featurelist = []
has_bodies = False
has_features = False
for lnk in selfobj.FeaturesToCopy:
if lnk.isDerivedFrom('PartDesign::Body'):
featurelist.extend(getBodySequence(lnk, skipfirst= selfobj.SkipFirstInBody))
has_bodies = True
else:
featurelist.append(lnk)
has_features = True
#check cross-links
if selfobj.Referencing == 'Use PlacementsFrom':
body_ref = bodyOf(selfobj.PlacementsFrom)
else:
body_ref = bodyOf(selfobj)
for feature in featurelist:
if bodyOf(feature) is not body_ref:
raise ScopeError('Reference placement and the feature are not in the same body (use Shapebinder or Ghost to bring the placement in).')
placements = lattice2BaseFeature.getPlacementsList(selfobj.PlacementsTo, selfobj)
placements = DereferenceArray(selfobj, placements, selfobj.PlacementsFrom, selfobj.Referencing)
if selfobj.Referencing == 'First item' and transforms is None:
placements.pop(0) #to not repeat the feature where it was applied already
elif selfobj.Referencing == 'Last item' and transforms is None:
placements.pop() #to not repeat the feature where it was applied already
if not transforms is None:
newplacements = []
for transform in transforms:
newplacements += [transform.multiply(plm) for plm in placements]
placements = newplacements
for feature in featurelist:
try:
baseshape = applyFeature(baseshape, feature, placements, mts)
except FeatureUnsupportedError as err:
if not selfobj.IgnoreUnsupported:
raise
else:
App.Console.PrintLog('{name} is unsupported, skipped.\n'.format(name= feature.Name))
return baseshape
def __getstate__(self):
return None
def __setstate__(self,state):
return None
class ViewProviderLatticePDPattern:
"A View Provider for the Lattice PartDesign Pattern object"
def __init__(self,vobj):
vobj.Proxy = self
def getIcon(self):
return getIconPath("Lattice2_PDPattern.svg")
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):
return [self.Object.PlacementsTo]
def onDelete(self, feature, subelements): # subelements is a tuple of strings
return True
# -------------------------- /document object --------------------------------------------------
# -------------------------- Gui command --------------------------------------------------
def CreateLatticePDPattern(features, latticeObjFrom, latticeObjTo, refmode):
FreeCADGui.addModule("lattice2PDPattern")
FreeCADGui.addModule("lattice2Executer")
#fill in properties
FreeCADGui.doCommand("f = lattice2PDPattern.makeFeature()")
reprfeatures = ', '.join(['App.ActiveDocument.'+f.Name for f in features])
FreeCADGui.doCommand("f.FeaturesToCopy = [{features}]".format(features= reprfeatures))
FreeCADGui.doCommand("f.PlacementsTo = App.ActiveDocument."+latticeObjTo.Name)
if latticeObjFrom is not None:
FreeCADGui.doCommand("f.PlacementsFrom = App.ActiveDocument."+latticeObjFrom.Name)
FreeCADGui.doCommand("f.Referencing = "+repr(refmode))
#execute
FreeCADGui.doCommand("lattice2Executer.executeFeature(f)")
#hide something
FreeCADGui.doCommand("f.PlacementsTo.ViewObject.hide()")
FreeCADGui.doCommand("f.BaseFeature.ViewObject.hide()")
#finalize
FreeCADGui.doCommand("Gui.Selection.addSelection(f)")
FreeCADGui.doCommand("f = None")
def cmdPDPattern():
sel = FreeCADGui.Selection.getSelectionEx()
(lattices, shapes) = lattice2BaseFeature.splitSelection(sel)
if len(shapes) > 0 and len(lattices) == 2:
FreeCAD.ActiveDocument.openTransaction("Lattice Pattern")
latticeFrom = lattices[0]
latticeTo = lattices[1]
CreateLatticePDPattern([so.Object for so in shapes], latticeFrom.Object, latticeTo.Object,'Use PlacementsFrom')
deselect(sel)
FreeCAD.ActiveDocument.commitTransaction()
elif len(shapes) > 0 and len(lattices) == 1:
FreeCAD.ActiveDocument.openTransaction("Lattice Pattern")
latticeTo = lattices[0]
CreateLatticePDPattern([so.Object for so in shapes], None, latticeTo.Object,'First item')
deselect(sel)
FreeCAD.ActiveDocument.commitTransaction()
else:
raise SelectionError("Bad selection",
"Please select either:\n"
" one or more PartDesign features, and one or two placements/arrays \n"
"or\n"
" a template body and two placements/arrays, one from selected body and one from active body."
)
# command defined in lattice2PDPatternCommand.py