From 19cce52864f27f1dd0ad131f63abdfe9a6540719 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Mon, 24 Jan 2022 17:11:08 -0600 Subject: [PATCH] pocket black --- src/Mod/Path/PathScripts/PathPocket.py | 232 ++++++++++++++++++------- 1 file changed, 165 insertions(+), 67 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathPocket.py b/src/Mod/Path/PathScripts/PathPocket.py index 65249c6b20..f1057f95a3 100644 --- a/src/Mod/Path/PathScripts/PathPocket.py +++ b/src/Mod/Path/PathScripts/PathPocket.py @@ -30,7 +30,8 @@ from PySide import QtCore # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader -Part = LazyLoader('Part', globals(), 'Part') + +Part = LazyLoader("Part", globals(), "Part") __title__ = "Path 3D Pocket Operation" __author__ = "Yorik van Havre " @@ -51,32 +52,64 @@ def translate(context, text, disambig=None): class ObjectPocket(PathPocketBase.ObjectPocket): - '''Proxy object for Pocket operation.''' + """Proxy object for Pocket operation.""" def pocketOpFeatures(self, obj): return PathOp.FeatureNoFinalDepth def initPocketOp(self, obj): - '''initPocketOp(obj) ... setup receiver''' - if not hasattr(obj, 'HandleMultipleFeatures'): - obj.addProperty('App::PropertyEnumeration', 'HandleMultipleFeatures', 'Pocket', QtCore.QT_TRANSLATE_NOOP('PathPocket', 'Choose how to process multiple Base Geometry features.')) - obj.HandleMultipleFeatures = ['Collectively', 'Individually'] - if not hasattr(obj, 'AdaptivePocketStart'): - obj.addProperty('App::PropertyBool', 'AdaptivePocketStart', 'Pocket', QtCore.QT_TRANSLATE_NOOP('App::Property', 'Use adaptive algorithm to eliminate excessive air milling above planar pocket top.')) - if not hasattr(obj, 'AdaptivePocketFinish'): - obj.addProperty('App::PropertyBool', 'AdaptivePocketFinish', 'Pocket', QtCore.QT_TRANSLATE_NOOP('App::Property', 'Use adaptive algorithm to eliminate excessive air milling below planar pocket bottom.')) - if not hasattr(obj, 'ProcessStockArea'): - obj.addProperty('App::PropertyBool', 'ProcessStockArea', 'Pocket', QtCore.QT_TRANSLATE_NOOP('App::Property', 'Process the model and stock in an operation with no Base Geometry selected.')) + """initPocketOp(obj) ... setup receiver""" + if not hasattr(obj, "HandleMultipleFeatures"): + obj.addProperty( + "App::PropertyEnumeration", + "HandleMultipleFeatures", + "Pocket", + QtCore.QT_TRANSLATE_NOOP( + "PathPocket", + "Choose how to process multiple Base Geometry features.", + ), + ) + obj.HandleMultipleFeatures = ["Collectively", "Individually"] + if not hasattr(obj, "AdaptivePocketStart"): + obj.addProperty( + "App::PropertyBool", + "AdaptivePocketStart", + "Pocket", + QtCore.QT_TRANSLATE_NOOP( + "App::Property", + "Use adaptive algorithm to eliminate excessive air milling above planar pocket top.", + ), + ) + if not hasattr(obj, "AdaptivePocketFinish"): + obj.addProperty( + "App::PropertyBool", + "AdaptivePocketFinish", + "Pocket", + QtCore.QT_TRANSLATE_NOOP( + "App::Property", + "Use adaptive algorithm to eliminate excessive air milling below planar pocket bottom.", + ), + ) + if not hasattr(obj, "ProcessStockArea"): + obj.addProperty( + "App::PropertyBool", + "ProcessStockArea", + "Pocket", + QtCore.QT_TRANSLATE_NOOP( + "App::Property", + "Process the model and stock in an operation with no Base Geometry selected.", + ), + ) def opOnDocumentRestored(self, obj): - '''opOnDocumentRestored(obj) ... adds the properties if they doesn't exist.''' + """opOnDocumentRestored(obj) ... adds the properties if they doesn't exist.""" self.initPocketOp(obj) def pocketInvertExtraOffset(self): return False def areaOpShapes(self, obj): - '''areaOpShapes(obj) ... return shapes representing the solids to be removed.''' + """areaOpShapes(obj) ... return shapes representing the solids to be removed.""" PathLog.track() subObjTups = [] @@ -102,34 +135,51 @@ class ObjectPocket(PathPocketBase.ObjectPocket): if len(Faces) == 0: allSubsFaceType = False - if allSubsFaceType is True and obj.HandleMultipleFeatures == 'Collectively': + if ( + allSubsFaceType is True + and obj.HandleMultipleFeatures == "Collectively" + ): (fzmin, fzmax) = self.getMinMaxOfFaces(Faces) if obj.FinalDepth.Value < fzmin: - PathLog.warning(translate('PathPocket', 'Final depth set below ZMin of face(s) selected.')) + PathLog.warning( + translate( + "PathPocket", + "Final depth set below ZMin of face(s) selected.", + ) + ) - if obj.AdaptivePocketStart is True or obj.AdaptivePocketFinish is True: + if ( + obj.AdaptivePocketStart is True + or obj.AdaptivePocketFinish is True + ): pocketTup = self.calculateAdaptivePocket(obj, base, subObjTups) if pocketTup is not False: obj.removalshape = pocketTup[0] removalshapes.append(pocketTup) # (shape, isHole, detail) else: shape = Part.makeCompound(Faces) - env = PathUtils.getEnvelope(base[0].Shape, subshape=shape, depthparams=self.depthparams) + env = PathUtils.getEnvelope( + base[0].Shape, subshape=shape, depthparams=self.depthparams + ) obj.removalshape = env.cut(base[0].Shape) # obj.removalshape.tessellate(0.1) - removalshapes.append((obj.removalshape, False, '3DPocket')) # (shape, isHole, detail) + removalshapes.append( + (obj.removalshape, False, "3DPocket") + ) # (shape, isHole, detail) else: for sub in base[1]: if "Face" in sub: shape = Part.makeCompound([getattr(base[0].Shape, sub)]) else: edges = [getattr(base[0].Shape, sub) for sub in base[1]] - shape = Part.makeFace(edges, 'Part::FaceMakerSimple') + shape = Part.makeFace(edges, "Part::FaceMakerSimple") - env = PathUtils.getEnvelope(base[0].Shape, subshape=shape, depthparams=self.depthparams) + env = PathUtils.getEnvelope( + base[0].Shape, subshape=shape, depthparams=self.depthparams + ) obj.removalshape = env.cut(base[0].Shape) # obj.removalshape.tessellate(0.1) - removalshapes.append((obj.removalshape, False, '3DPocket')) + removalshapes.append((obj.removalshape, False, "3DPocket")) else: # process the job base object as a whole PathLog.debug("processing the whole job base object") @@ -137,36 +187,40 @@ class ObjectPocket(PathPocketBase.ObjectPocket): if obj.ProcessStockArea is True: job = PathUtils.findParentJob(obj) - stockEnvShape = PathUtils.getEnvelope(job.Stock.Shape, subshape=None, depthparams=self.depthparams) + stockEnvShape = PathUtils.getEnvelope( + job.Stock.Shape, subshape=None, depthparams=self.depthparams + ) obj.removalshape = stockEnvShape.cut(base.Shape) # obj.removalshape.tessellate(0.1) else: - env = PathUtils.getEnvelope(base.Shape, subshape=None, depthparams=self.depthparams) + env = PathUtils.getEnvelope( + base.Shape, subshape=None, depthparams=self.depthparams + ) obj.removalshape = env.cut(base.Shape) # obj.removalshape.tessellate(0.1) - removalshapes.append((obj.removalshape, False, '3DPocket')) + removalshapes.append((obj.removalshape, False, "3DPocket")) return removalshapes def areaOpSetDefaultValues(self, obj, job): - '''areaOpSetDefaultValues(obj, job) ... set default values''' + """areaOpSetDefaultValues(obj, job) ... set default values""" obj.StepOver = 100 obj.ZigZagAngle = 45 - obj.HandleMultipleFeatures = 'Collectively' + obj.HandleMultipleFeatures = "Collectively" obj.AdaptivePocketStart = False obj.AdaptivePocketFinish = False obj.ProcessStockArea = False # methods for eliminating air milling with some pockets: adpative start and finish def calculateAdaptivePocket(self, obj, base, subObjTups): - '''calculateAdaptivePocket(obj, base, subObjTups) + """calculateAdaptivePocket(obj, base, subObjTups) Orient multiple faces around common facial center of mass. Identify edges that are connections for adjacent faces. Attempt to separate unconnected edges into top and bottom loops of the pocket. Trim the top and bottom of the pocket if available and requested. - return: tuple with pocket shape information''' + return: tuple with pocket shape information""" low = [] high = [] removeList = [] @@ -203,23 +257,35 @@ class ObjectPocket(PathPocketBase.ObjectPocket): highFaceShape = Part.Face(Part.Wire(Part.__sortEdges__(allEdges))) except Exception as ee: PathLog.warning(ee) - PathLog.error(translate("Path", "A planar adaptive start is unavailable. The non-planar will be attempted.")) + PathLog.error( + translate( + "Path", + "A planar adaptive start is unavailable. The non-planar will be attempted.", + ) + ) tryNonPlanar = True else: makeHighFace = 1 if tryNonPlanar is True: try: - highFaceShape = Part.makeFilledFace(Part.__sortEdges__(allEdges)) # NON-planar face method + highFaceShape = Part.makeFilledFace( + Part.__sortEdges__(allEdges) + ) # NON-planar face method except Exception as eee: PathLog.warning(eee) - PathLog.error(translate("Path", "The non-planar adaptive start is also unavailable.") + "(1)") + PathLog.error( + translate( + "Path", "The non-planar adaptive start is also unavailable." + ) + + "(1)" + ) isHighFacePlanar = False else: makeHighFace = 2 if makeHighFace > 0: - FreeCAD.ActiveDocument.addObject('Part::Feature', 'topEdgeFace') + FreeCAD.ActiveDocument.addObject("Part::Feature", "topEdgeFace") highFace = FreeCAD.ActiveDocument.ActiveObject highFace.Shape = highFaceShape removeList.append(highFace.Name) @@ -228,9 +294,22 @@ class ObjectPocket(PathPocketBase.ObjectPocket): if makeHighFace == 2: mx = hzmax + obj.StepDown.Value mn = hzmin - obj.StepDown.Value - if highFace.Shape.BoundBox.ZMax > mx or highFace.Shape.BoundBox.ZMin < mn: - PathLog.warning("ZMaxDiff: {}; ZMinDiff: {}".format(highFace.Shape.BoundBox.ZMax - mx, highFace.Shape.BoundBox.ZMin - mn)) - PathLog.error(translate("Path", "The non-planar adaptive start is also unavailable.") + "(2)") + if ( + highFace.Shape.BoundBox.ZMax > mx + or highFace.Shape.BoundBox.ZMin < mn + ): + PathLog.warning( + "ZMaxDiff: {}; ZMinDiff: {}".format( + highFace.Shape.BoundBox.ZMax - mx, + highFace.Shape.BoundBox.ZMin - mn, + ) + ) + PathLog.error( + translate( + "Path", "The non-planar adaptive start is also unavailable." + ) + + "(2)" + ) isHighFacePlanar = False makeHighFace = 0 else: @@ -252,7 +331,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket): PathLog.error("An adaptive finish is unavailable.") isLowFacePlanar = False else: - FreeCAD.ActiveDocument.addObject('Part::Feature', 'bottomEdgeFace') + FreeCAD.ActiveDocument.addObject("Part::Feature", "bottomEdgeFace") lowFace = FreeCAD.ActiveDocument.ActiveObject lowFace.Shape = lowFaceShape removeList.append(lowFace.Name) @@ -279,9 +358,12 @@ class ObjectPocket(PathPocketBase.ObjectPocket): step_down=obj.StepDown.Value, z_finish_step=finish_step, final_depth=finDep, - user_depths=None) + user_depths=None, + ) shape = Part.makeCompound(Faces) - env = PathUtils.getEnvelope(base[0].Shape, subshape=shape, depthparams=depthparams) + env = PathUtils.getEnvelope( + base[0].Shape, subshape=shape, depthparams=depthparams + ) cuts.append(env.cut(base[0].Shape)) # Might need to change to .cut(job.Stock.Shape) if pocket has no bottom @@ -305,8 +387,11 @@ class ObjectPocket(PathPocketBase.ObjectPocket): step_down=obj.StepDown.Value, z_finish_step=finish_step, final_depth=finDep1, - user_depths=None) - envTop = PathUtils.getEnvelope(base[0].Shape, subshape=highFace.Shape, depthparams=depthparams1) + user_depths=None, + ) + envTop = PathUtils.getEnvelope( + base[0].Shape, subshape=highFace.Shape, depthparams=depthparams1 + ) cbi = len(cuts) - 1 cuts.append(cuts[cbi].cut(envTop)) @@ -326,8 +411,11 @@ class ObjectPocket(PathPocketBase.ObjectPocket): step_down=obj.StepDown.Value, z_finish_step=finish_step, final_depth=finDep2, - user_depths=None) - envBottom = PathUtils.getEnvelope(base[0].Shape, subshape=lowFace.Shape, depthparams=depthparams2) + user_depths=None, + ) + envBottom = PathUtils.getEnvelope( + base[0].Shape, subshape=lowFace.Shape, depthparams=depthparams2 + ) cbi = len(cuts) - 1 cuts.append(cuts[cbi].cut(envBottom)) @@ -335,9 +423,10 @@ class ObjectPocket(PathPocketBase.ObjectPocket): sdi = len(starts) - 1 fdi = len(finals) - 1 cbi = len(cuts) - 1 - pocket = (cuts[cbi], False, '3DPocket') + pocket = (cuts[cbi], False, "3DPocket") if FreeCAD.GuiUp: import FreeCADGui + for rn in removeList: FreeCADGui.ActiveDocument.getObject(rn).Visibility = False @@ -347,11 +436,12 @@ class ObjectPocket(PathPocketBase.ObjectPocket): return pocket def orderFacesAroundCenterOfMass(self, subObjTups): - '''orderFacesAroundCenterOfMass(subObjTups) + """orderFacesAroundCenterOfMass(subObjTups) Order list of faces by center of mass in angular order around average center of mass for all faces. Positive X-axis is zero degrees. - return: subObjTups [ordered/sorted]''' + return: subObjTups [ordered/sorted]""" import math + newList = [] vectList = [] comList = [] @@ -364,7 +454,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket): return vectItem[3] def getFaceIdx(sub): - return int(sub.replace('Face', '')) - 1 + return int(sub.replace("Face", "")) - 1 # get CenterOfMass for each face and add to sumCenterOfMass for average calculation for (sub, face) in subObjTups: @@ -382,22 +472,26 @@ class ObjectPocket(PathPocketBase.ObjectPocket): # calculate vector (mag, direct) for each face from avgCom for (sub, face, com) in comList: - adjCom = com.sub(avgCom) # effectively treats avgCom as origin for each face. - mag = math.sqrt(adjCom.x**2 + adjCom.y**2) # adjCom.Length without Z values + adjCom = com.sub( + avgCom + ) # effectively treats avgCom as origin for each face. + mag = math.sqrt( + adjCom.x ** 2 + adjCom.y ** 2 + ) # adjCom.Length without Z values drctn = 0.0 # Determine direction of vector if adjCom.x > 0.0: if adjCom.y > 0.0: # Q1 - drctn = math.degrees(math.atan(adjCom.y/adjCom.x)) + drctn = math.degrees(math.atan(adjCom.y / adjCom.x)) elif adjCom.y < 0.0: - drctn = -math.degrees(math.atan(adjCom.x/adjCom.y)) + 270.0 + drctn = -math.degrees(math.atan(adjCom.x / adjCom.y)) + 270.0 elif adjCom.y == 0.0: drctn = 0.0 elif adjCom.x < 0.0: if adjCom.y < 0.0: - drctn = math.degrees(math.atan(adjCom.y/adjCom.x)) + 180.0 + drctn = math.degrees(math.atan(adjCom.y / adjCom.x)) + 180.0 elif adjCom.y > 0.0: - drctn = -math.degrees(math.atan(adjCom.x/adjCom.y)) + 90.0 + drctn = -math.degrees(math.atan(adjCom.x / adjCom.y)) + 90.0 elif adjCom.y == 0.0: drctn = 180.0 elif adjCom.x == 0.0: @@ -434,8 +528,8 @@ class ObjectPocket(PathPocketBase.ObjectPocket): return newList def findSharedEdges(self, subObjTups): - '''findSharedEdges(self, subObjTups) - Find connected edges given a group of faces''' + """findSharedEdges(self, subObjTups) + Find connected edges given a group of faces""" checkoutList = [] searchedList = [] shared = [] @@ -471,7 +565,11 @@ class ObjectPocket(PathPocketBase.ObjectPocket): for ei2 in range(0, len(face2.Edges)): edg2 = face2.Edges[ei2] if edg1.isSame(edg2) is True: - PathLog.debug("{}.Edges[{}] connects at {}.Edges[{}]".format(sub1, ei1, sub2, ei2)) + PathLog.debug( + "{}.Edges[{}] connects at {}.Edges[{}]".format( + sub1, ei1, sub2, ei2 + ) + ) shared.append((sub1, face1, ei1)) touching[sub1].append(ei1) touching[sub2].append(ei2) @@ -486,8 +584,8 @@ class ObjectPocket(PathPocketBase.ObjectPocket): return (shared, touchingCleaned) def identifyUnconnectedEdges(self, subObjTups, touching): - '''identifyUnconnectedEdges(subObjTups, touching) - Categorize unconnected edges into two groups, if possible: low and high''' + """identifyUnconnectedEdges(subObjTups, touching) + Categorize unconnected edges into two groups, if possible: low and high""" # Identify unconnected edges # (should be top edge loop if all faces form loop with bottom face(s) included) high = [] @@ -518,7 +616,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket): com = FreeCAD.Vector(0, 0, 0) com.add(edg0.CenterOfMass) com.add(edg1.CenterOfMass) - avgCom = FreeCAD.Vector(com.x/2.0, com.y/2.0, com.z/2.0) + avgCom = FreeCAD.Vector(com.x / 2.0, com.y / 2.0, com.z / 2.0) if avgCom.z > face.CenterOfMass.z: high.extend(holding) else: @@ -534,9 +632,9 @@ class ObjectPocket(PathPocketBase.ObjectPocket): return (low, high) def hasCommonVertex(self, edge1, edge2, show=False): - '''findCommonVertexIndexes(edge1, edge2, show=False) + """findCommonVertexIndexes(edge1, edge2, show=False) Compare vertexes of two edges to identify a common vertex. - Returns the vertex index of edge1 to which edge2 is connected''' + Returns the vertex index of edge1 to which edge2 is connected""" if show is True: PathLog.info("New findCommonVertex()... ") @@ -561,9 +659,9 @@ class ObjectPocket(PathPocketBase.ObjectPocket): return -1 def groupConnectedEdges(self, holding): - '''groupConnectedEdges(self, holding) + """groupConnectedEdges(self, holding) Take edges and determine which are connected. - Group connected chains/loops into: low and high''' + Group connected chains/loops into: low and high""" holds = [] grps = [] searched = [] @@ -607,7 +705,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket): while len(holds) > 0: if loops > 500: - PathLog.error('BREAK --- LOOPS LIMIT of 500 ---') + PathLog.error("BREAK --- LOOPS LIMIT of 500 ---") break save = False @@ -701,8 +799,8 @@ class ObjectPocket(PathPocketBase.ObjectPocket): return (low, high) def getMinMaxOfFaces(self, Faces): - '''getMinMaxOfFaces(Faces) - return the zmin and zmax values for given set of faces or edges.''' + """getMinMaxOfFaces(Faces) + return the zmin and zmax values for given set of faces or edges.""" zmin = Faces[0].BoundBox.ZMax zmax = Faces[0].BoundBox.ZMin for f in Faces: @@ -718,7 +816,7 @@ def SetupProperties(): def Create(name, obj=None, parentJob=None): - '''Create(name) ... Creates and returns a Pocket operation.''' + """Create(name) ... Creates and returns a Pocket operation.""" if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) obj.Proxy = ObjectPocket(obj, name, parentJob)