#*************************************************************************** #* * #* 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 * #* * #*************************************************************************** __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