New feature: PartDesign Pattern!
This commit is contained in:
parent
52b80f44b6
commit
af79de8c01
|
@ -83,6 +83,12 @@ class Lattice2Workbench (Workbench):
|
|||
)
|
||||
self.appendToolbar('Lattice2CompoundFeatures', cmdsCompoundTools)
|
||||
self.appendMenu('Lattice2', cmdsCompoundTools)
|
||||
|
||||
cmdsPDTools = ([]
|
||||
+ Lattice2.PartDesignFeatures.PDPattern.exportedCommands
|
||||
)
|
||||
self.appendToolbar('Lattice2PartDesignFeatres', cmdsPDTools)
|
||||
self.appendMenu('Lattice2', cmdsPDTools)
|
||||
|
||||
cmdsGuiTools = ([]
|
||||
+ Lattice2.GuiTools.Inspect.exportedCommands
|
||||
|
|
|
@ -31,6 +31,8 @@ import Lattice2CompoundFeatures as CompoundFeatures
|
|||
|
||||
import Lattice2ArrayFeatures as ArrayFeatures
|
||||
|
||||
import Lattice2PartDesignFeatures as PartDesignFeatures
|
||||
|
||||
import Lattice2GuiTools as GuiTools
|
||||
|
||||
import lattice2_rc as resource_module
|
||||
|
|
1
Lattice2PartDesignFeatures.py
Normal file
1
Lattice2PartDesignFeatures.py
Normal file
|
@ -0,0 +1 @@
|
|||
import lattice2PDPattern as PDPattern
|
|
@ -50,7 +50,8 @@ def makeAttachablePlacement(name):
|
|||
ViewProviderAttachablePlacement(obj.ViewObject)
|
||||
else:
|
||||
obj = lattice2BaseFeature.makeLatticeFeature(name, AttachablePlacement, ViewProviderAttachablePlacement, no_disable_attacher= True)
|
||||
obj.addExtension("Part::AttachExtensionPython", None)
|
||||
if not obj.hasExtension('Part::AttachExtension'):
|
||||
obj.addExtension("Part::AttachExtensionPython", None)
|
||||
|
||||
return obj
|
||||
|
||||
|
@ -67,6 +68,10 @@ class AttachablePlacement(lattice2BaseFeature.LatticeFeature):
|
|||
obj.positionBySupport()
|
||||
|
||||
return [obj.Placement]
|
||||
|
||||
def onDocumentRestored(self, selfobj):
|
||||
#override that disables disabling of attacher
|
||||
pass
|
||||
|
||||
|
||||
class ViewProviderAttachablePlacement(lattice2BaseFeature.ViewProviderLatticeFeature):
|
||||
|
|
|
@ -57,21 +57,12 @@ def makeLatticeFeature(name, AppClass, ViewClass, no_body = False, no_disable_at
|
|||
|
||||
body = activeBody()
|
||||
if body and not no_body:
|
||||
obj = body.newObject("Part::Part2DObjectPython",name)
|
||||
obj = body.newObject("Part::Part2DObjectPython",name) #hack: body accepts any 2dobjectpython, thinking it is a sketch. Use it to get into body. This does cause some weirdness (e.g. one can Pad a placement), but that is rather minor.
|
||||
obj.AttacherType = 'Attacher::AttachEngine3D'
|
||||
if not no_disable_attacher:
|
||||
attachprops = [
|
||||
'Support',
|
||||
'MapMode',
|
||||
'MapReversed',
|
||||
'MapPathParameter',
|
||||
'AttachmentOffset',
|
||||
]
|
||||
for prop in attachprops:
|
||||
obj.setEditorMode(prop, 2) #hidden
|
||||
else:
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython",name)
|
||||
AppClass(obj)
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
if ViewClass:
|
||||
vp = ViewClass(obj.ViewObject)
|
||||
|
@ -254,6 +245,25 @@ class LatticeFeature():
|
|||
def __setstate__(self,state):
|
||||
return None
|
||||
|
||||
def disableAttacher(self, selfobj, enable= False):
|
||||
if selfobj.isDerivedFrom('Part::Part2DObject'):
|
||||
attachprops = [
|
||||
'Support',
|
||||
'MapMode',
|
||||
'MapReversed',
|
||||
'MapPathParameter',
|
||||
'AttachmentOffset',
|
||||
]
|
||||
for prop in attachprops:
|
||||
selfobj.setEditorMode(prop, 0 if enable else 2)
|
||||
if enable:
|
||||
selfobj.MapMode = selfobj.MapMode #trigger attachment, to make it update property states
|
||||
|
||||
def onDocumentRestored(self, selfobj):
|
||||
#override to have attachment!
|
||||
self.disableAttacher(selfobj)
|
||||
|
||||
|
||||
class ViewProviderLatticeFeature:
|
||||
"A View Provider for base lattice object"
|
||||
|
||||
|
|
|
@ -34,6 +34,8 @@ def translate(context, text, disambig):
|
|||
|
||||
def getParamRefine():
|
||||
return FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Part/Boolean").GetBool("RefineModel")
|
||||
def getParamPDRefine():
|
||||
return FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/PartDesign").GetBool("RefineModel")
|
||||
|
||||
def getIconPath(icon_dot_svg):
|
||||
return ":/icons/" + icon_dot_svg
|
||||
|
@ -93,3 +95,10 @@ def screen(feature):
|
|||
|
||||
def activeBody():
|
||||
return FreeCADGui.ActiveDocument.ActiveView.getActiveObject("pdbody")
|
||||
|
||||
def bodyOf(feature):
|
||||
body = feature.getParentGeoFeatureGroup()
|
||||
if body.isDerivedFrom('PartDesign::Body'):
|
||||
return body
|
||||
else:
|
||||
return None
|
||||
|
|
|
@ -30,6 +30,21 @@ __title__="Geometric utility routines for Lattice workbench for FreeCAD"
|
|||
__author__ = "DeepSOIC"
|
||||
__url__ = ""
|
||||
|
||||
def PlacementsFuzzyCompare(plm1, plm2):
|
||||
pos_eq = (plm1.Base - plm2.Base).Length < 1e-7 # 1e-7 is OCC's Precision::Confusion
|
||||
|
||||
q1 = plm1.Rotation.Q
|
||||
q2 = plm2.Rotation.Q
|
||||
# rotations are equal if q1 == q2 or q1 == -q2.
|
||||
# Invert one of Q's if their scalar product is negative, before comparison.
|
||||
if q1[0]*q2[0] + q1[1]*q2[1] + q1[2]*q2[2] + q1[3]*q2[3] < 0:
|
||||
q2 = [-v for v in q2]
|
||||
rot_eq = ( abs(q1[0]-q2[0]) +
|
||||
abs(q1[1]-q2[1]) +
|
||||
abs(q1[2]-q2[2]) +
|
||||
abs(q1[3]-q2[3]) ) < 1e-12 # 1e-12 is OCC's Precision::Angular (in radians)
|
||||
return pos_eq and rot_eq
|
||||
|
||||
|
||||
def makeOrientationFromLocalAxes(ZAx, XAx = None):
|
||||
'''
|
||||
|
|
394
lattice2PDPattern.py
Normal file
394
lattice2PDPattern.py
Normal file
|
@ -0,0 +1,394 @@
|
|||
#***************************************************************************
|
||||
#* *
|
||||
#* 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 occurences. If False, optimize assuming occurences 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',
|
||||
]
|
||||
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 occurences 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 occurences. 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."
|
||||
)
|
||||
|
||||
|
||||
class CommandLatticePDPattern:
|
||||
"Command to create Lattice PartDesign Pattern feature"
|
||||
def GetResources(self):
|
||||
return {'Pixmap' : getIconPath("Lattice2_PDPattern.svg"),
|
||||
'MenuText': "Lattice PartDesign Pattern",
|
||||
'Accel': "",
|
||||
'ToolTip': "Lattice PartDesign Pattern command. Replicates partdesign features at every placement in array."}
|
||||
|
||||
def Activated(self):
|
||||
try:
|
||||
if len(FreeCADGui.Selection.getSelection())==0:
|
||||
infoMessage("Lattice PartDesign Pattern",
|
||||
"Lattice PartDesign Pattern command. Replicates partdesign features at every placement in array.\n\n"
|
||||
"Please select features to repeat, reference placement (optional), and target placement/array. \n\n"
|
||||
"You can use features from another body. Then, reference placement is required. You can also select a body (a \"template body\"), then all features from that body will be replicated.\n\n"
|
||||
"Please observe scope restrictions. Reference placement must be in same body the original features are in; target placement/array must be in active body. You can create Lattice Arrays "
|
||||
"right in PartDesign bodies, but you can't drag them in after the fact. You can import arrays of placements from elsewhere using a Shapebinder, or Part-o-Magic Ghost.")
|
||||
return
|
||||
if activeBody() is None:
|
||||
infoMessage("Lattice PartDesign Pattern", "No active body. Please, activate a body, first.")
|
||||
cmdPDPattern()
|
||||
except Exception as err:
|
||||
msgError(err)
|
||||
|
||||
def IsActive(self):
|
||||
if FreeCAD.ActiveDocument:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
FreeCADGui.addCommand('Lattice2_PDPattern', CommandLatticePDPattern())
|
||||
|
||||
exportedCommands = ['Lattice2_PDPattern']
|
|
@ -28,10 +28,12 @@ __doc__ = "Utility methods to copy shapes"
|
|||
|
||||
import FreeCAD
|
||||
import Part
|
||||
from lattice2GeomUtils import PlacementsFuzzyCompare
|
||||
|
||||
def shallowCopy(shape, extra_placement = None):
|
||||
"""shallowCopy(shape, extra_placement = None): creates a shallow copy of a shape. The
|
||||
copy will match by isSame/isEqual/isPartner tests, but will have an independent placement."""
|
||||
copy will match by isSame/isEqual/isPartner tests, but will have an independent placement.
|
||||
Supports matrix, but the matrix should be pure placement (not be mirroring)."""
|
||||
|
||||
copiers = {
|
||||
"Vertex": lambda sh: sh.Vertexes[0],
|
||||
|
@ -59,7 +61,7 @@ def shallowCopy(shape, extra_placement = None):
|
|||
|
||||
def deepCopy(shape, extra_placement = None):
|
||||
"""deepCopy(shape, extra_placement = None): Copies all subshapes. The copy will not match by isSame/isEqual/
|
||||
isPartner tests."""
|
||||
isPartner tests. If matrix is provided, redirects the call to transformCopy."""
|
||||
|
||||
if extra_placement is not None:
|
||||
if hasattr(extra_placement, 'toMatrix'):
|
||||
|
@ -72,7 +74,7 @@ def deepCopy(shape, extra_placement = None):
|
|||
|
||||
def transformCopy(shape, extra_placement = None):
|
||||
"""transformCopy(shape, extra_placement = None): creates a deep copy shape with shape's placement applied to
|
||||
the subelements (the placement of returned shape is zero)."""
|
||||
the subelements (the placement of returned shape is zero). Supports matrices, including mirroring matrices."""
|
||||
|
||||
if extra_placement is None:
|
||||
extra_placement = FreeCAD.Placement()
|
||||
|
@ -88,6 +90,21 @@ def transformCopy(shape, extra_placement = None):
|
|||
ret.transformShape(extra_placement.multiply(splm), True)
|
||||
return ret
|
||||
|
||||
def transformCopy_Smart(shape, feature_placement):
|
||||
"""transformCopy_Smart(shape, feature_placement): gets rid of shape's internal placement
|
||||
(by applying transform to all its elements), and assigns feature_placement to the placement.
|
||||
I.e. feature_placement is the additional transform to apply. Unlike transformCopy, creates
|
||||
a shallow copy if possible. Does not support matrices."""
|
||||
|
||||
if shape.isNull():
|
||||
return shape
|
||||
if PlacementsFuzzyCompare(shape.Placement, FreeCAD.Placement()):
|
||||
sh = shallowCopy(shape)
|
||||
else:
|
||||
sh = transformCopy(shape)
|
||||
sh.Placement = feature_placement
|
||||
return sh
|
||||
|
||||
|
||||
copy_types = ["Shallow copy", "Deep copy", "Transformed deep copy"]
|
||||
copy_functions = [shallowCopy, deepCopy, transformCopy]
|
||||
|
|
Loading…
Reference in New Issue
Block a user