diff --git a/src/Mod/Path/PathScripts/PathAreaOp.py b/src/Mod/Path/PathScripts/PathAreaOp.py index 3c9284cc1e..a14574fff2 100644 --- a/src/Mod/Path/PathScripts/PathAreaOp.py +++ b/src/Mod/Path/PathScripts/PathAreaOp.py @@ -31,8 +31,9 @@ from PySide import QtCore # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader -Draft = LazyLoader('Draft', globals(), 'Draft') -Part = LazyLoader('Part', globals(), 'Part') + +Draft = LazyLoader("Draft", globals(), "Draft") +Part = LazyLoader("Part", globals(), "Part") if FreeCAD.GuiUp: import FreeCADGui @@ -54,85 +55,104 @@ def translate(context, text, disambig=None): class ObjectOp(PathOp.ObjectOp): - '''Base class for all Path.Area based operations. + """Base class for all Path.Area based operations. Provides standard features including debugging properties AreaParams, PathParams and removalshape, all hidden. The main reason for existence is to implement the standard interface to Path.Area so subclasses only have to provide the shapes for the - operations.''' + operations.""" def opFeatures(self, obj): - '''opFeatures(obj) ... returns the base features supported by all Path.Area based operations. + """opFeatures(obj) ... returns the base features supported by all Path.Area based operations. The standard feature list is OR'ed with the return value of areaOpFeatures(). - Do not overwrite, implement areaOpFeatures(obj) instead.''' - return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureStepDown \ - | PathOp.FeatureHeights | PathOp.FeatureStartPoint \ - | self.areaOpFeatures(obj) | PathOp.FeatureCoolant + Do not overwrite, implement areaOpFeatures(obj) instead.""" + return ( + PathOp.FeatureTool + | PathOp.FeatureDepths + | PathOp.FeatureStepDown + | PathOp.FeatureHeights + | PathOp.FeatureStartPoint + | self.areaOpFeatures(obj) + | PathOp.FeatureCoolant + ) def areaOpFeatures(self, obj): - '''areaOpFeatures(obj) ... overwrite to add operation specific features. - Can safely be overwritten by subclasses.''' + """areaOpFeatures(obj) ... overwrite to add operation specific features. + Can safely be overwritten by subclasses.""" # pylint: disable=unused-argument return 0 def initOperation(self, obj): - '''initOperation(obj) ... sets up standard Path.Area properties and calls initAreaOp(). - Do not overwrite, overwrite initAreaOp(obj) instead.''' + """initOperation(obj) ... sets up standard Path.Area properties and calls initAreaOp(). + Do not overwrite, overwrite initAreaOp(obj) instead.""" PathLog.track() # Debugging obj.addProperty("App::PropertyString", "AreaParams", "Path") - obj.setEditorMode('AreaParams', 2) # hide + obj.setEditorMode("AreaParams", 2) # hide obj.addProperty("App::PropertyString", "PathParams", "Path") - obj.setEditorMode('PathParams', 2) # hide + obj.setEditorMode("PathParams", 2) # hide obj.addProperty("Part::PropertyPartShape", "removalshape", "Path") - obj.setEditorMode('removalshape', 2) # hide + obj.setEditorMode("removalshape", 2) # hide - obj.addProperty("App::PropertyBool", "SplitArcs", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Split Arcs into discrete segments")) + obj.addProperty( + "App::PropertyBool", + "SplitArcs", + "Path", + QtCore.QT_TRANSLATE_NOOP( + "App::Property", "Split Arcs into discrete segments" + ), + ) # obj.Proxy = self self.initAreaOp(obj) def initAreaOp(self, obj): - '''initAreaOp(obj) ... overwrite if the receiver class needs initialisation. - Can safely be overwritten by subclasses.''' - pass # pylint: disable=unnecessary-pass + """initAreaOp(obj) ... overwrite if the receiver class needs initialisation. + Can safely be overwritten by subclasses.""" + pass # pylint: disable=unnecessary-pass def areaOpShapeForDepths(self, obj, job): - '''areaOpShapeForDepths(obj) ... returns the shape used to make an initial calculation for the depths being used. - The default implementation returns the job's Base.Shape''' + """areaOpShapeForDepths(obj) ... returns the shape used to make an initial calculation for the depths being used. + The default implementation returns the job's Base.Shape""" if job: if job.Stock: - PathLog.debug("job=%s base=%s shape=%s" % (job, job.Stock, job.Stock.Shape)) + PathLog.debug( + "job=%s base=%s shape=%s" % (job, job.Stock, job.Stock.Shape) + ) return job.Stock.Shape else: - PathLog.warning(translate("PathAreaOp", "job %s has no Base.") % job.Label) + PathLog.warning( + translate("PathAreaOp", "job %s has no Base.") % job.Label + ) else: - PathLog.warning(translate("PathAreaOp", "no job for op %s found.") % obj.Label) + PathLog.warning( + translate("PathAreaOp", "no job for op %s found.") % obj.Label + ) return None def areaOpOnChanged(self, obj, prop): - '''areaOpOnChanged(obj, porp) ... overwrite to process operation specific changes to properties. - Can safely be overwritten by subclasses.''' - pass # pylint: disable=unnecessary-pass + """areaOpOnChanged(obj, porp) ... overwrite to process operation specific changes to properties. + Can safely be overwritten by subclasses.""" + pass # pylint: disable=unnecessary-pass def opOnChanged(self, obj, prop): - '''opOnChanged(obj, prop) ... base implementation of the notification framework - do not overwrite. + """opOnChanged(obj, prop) ... base implementation of the notification framework - do not overwrite. The base implementation takes a stab at determining Heights and Depths if the operations's Base changes. - Do not overwrite, overwrite areaOpOnChanged(obj, prop) instead.''' + Do not overwrite, overwrite areaOpOnChanged(obj, prop) instead.""" # PathLog.track(obj.Label, prop) - if prop in ['AreaParams', 'PathParams', 'removalshape']: + if prop in ["AreaParams", "PathParams", "removalshape"]: obj.setEditorMode(prop, 2) - if prop == 'Base' and len(obj.Base) == 1: + if prop == "Base" and len(obj.Base) == 1: (base, sub) = obj.Base[0] bb = base.Shape.BoundBox # parent boundbox subobj = base.Shape.getElement(sub[0]) fbb = subobj.BoundBox # feature boundbox - if hasattr(obj, 'Side'): + if hasattr(obj, "Side"): if bb.XLength == fbb.XLength and bb.YLength == fbb.YLength: obj.Side = "Outside" else: @@ -141,29 +161,36 @@ class ObjectOp(PathOp.ObjectOp): self.areaOpOnChanged(obj, prop) def opOnDocumentRestored(self, obj): - for prop in ['AreaParams', 'PathParams', 'removalshape']: + for prop in ["AreaParams", "PathParams", "removalshape"]: if hasattr(obj, prop): obj.setEditorMode(prop, 2) - if not hasattr(obj, 'SplitArcs'): - obj.addProperty("App::PropertyBool", "SplitArcs", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Split Arcs into discrete segments")) + if not hasattr(obj, "SplitArcs"): + obj.addProperty( + "App::PropertyBool", + "SplitArcs", + "Path", + QtCore.QT_TRANSLATE_NOOP( + "App::Property", "Split Arcs into discrete segments" + ), + ) self.areaOpOnDocumentRestored(obj) def areaOpOnDocumentRestored(self, obj): - '''areaOpOnDocumentRestored(obj) ... overwrite to fully restore receiver''' - pass # pylint: disable=unnecessary-pass + """areaOpOnDocumentRestored(obj) ... overwrite to fully restore receiver""" + pass # pylint: disable=unnecessary-pass def opSetDefaultValues(self, obj, job): - '''opSetDefaultValues(obj) ... base implementation, do not overwrite. + """opSetDefaultValues(obj) ... base implementation, do not overwrite. The base implementation sets the depths and heights based on the areaOpShapeForDepths() return value. - Do not overwrite, overwrite areaOpSetDefaultValues(obj, job) instead.''' + Do not overwrite, overwrite areaOpSetDefaultValues(obj, job) instead.""" PathLog.debug("opSetDefaultValues(%s, %s)" % (obj.Label, job.Label)) if PathOp.FeatureDepths & self.opFeatures(obj): try: shape = self.areaOpShapeForDepths(obj, job) - except Exception as ee: # pylint: disable=broad-except + except Exception as ee: # pylint: disable=broad-except PathLog.error(ee) shape = None @@ -182,136 +209,158 @@ class ObjectOp(PathOp.ObjectOp): obj.OpStartDepth.Value = startDepth obj.OpFinalDepth.Value = finalDepth - PathLog.debug("Default OpDepths are Start: {}, and Final: {}".format(obj.OpStartDepth.Value, obj.OpFinalDepth.Value)) - PathLog.debug("Default Depths are Start: {}, and Final: {}".format(startDepth, finalDepth)) + PathLog.debug( + "Default OpDepths are Start: {}, and Final: {}".format( + obj.OpStartDepth.Value, obj.OpFinalDepth.Value + ) + ) + PathLog.debug( + "Default Depths are Start: {}, and Final: {}".format( + startDepth, finalDepth + ) + ) self.areaOpSetDefaultValues(obj, job) def areaOpSetDefaultValues(self, obj, job): - '''areaOpSetDefaultValues(obj, job) ... overwrite to set initial values of operation specific properties. - Can safely be overwritten by subclasses.''' - pass # pylint: disable=unnecessary-pass + """areaOpSetDefaultValues(obj, job) ... overwrite to set initial values of operation specific properties. + Can safely be overwritten by subclasses.""" + pass # pylint: disable=unnecessary-pass def _buildPathArea(self, obj, baseobject, isHole, start, getsim): - '''_buildPathArea(obj, baseobject, isHole, start, getsim) ... internal function.''' + """_buildPathArea(obj, baseobject, isHole, start, getsim) ... internal function.""" # pylint: disable=unused-argument PathLog.track() area = Path.Area() area.setPlane(PathUtils.makeWorkplane(baseobject)) area.add(baseobject) - areaParams = self.areaOpAreaParams(obj, isHole) # pylint: disable=assignment-from-no-return + areaParams = self.areaOpAreaParams( + obj, isHole + ) # pylint: disable=assignment-from-no-return heights = [i for i in self.depthparams] - PathLog.debug('depths: {}'.format(heights)) + PathLog.debug("depths: {}".format(heights)) area.setParams(**areaParams) obj.AreaParams = str(area.getParams()) PathLog.debug("Area with params: {}".format(area.getParams())) - sections = area.makeSections(mode=0, project=self.areaOpUseProjection(obj), heights=heights) + sections = area.makeSections( + mode=0, project=self.areaOpUseProjection(obj), heights=heights + ) PathLog.debug("sections = %s" % sections) shapelist = [sec.getShape() for sec in sections] PathLog.debug("shapelist = %s" % shapelist) - pathParams = self.areaOpPathParams(obj, isHole) # pylint: disable=assignment-from-no-return - pathParams['shapes'] = shapelist - pathParams['feedrate'] = self.horizFeed - pathParams['feedrate_v'] = self.vertFeed - pathParams['verbose'] = True - pathParams['resume_height'] = obj.SafeHeight.Value - pathParams['retraction'] = obj.ClearanceHeight.Value - pathParams['return_end'] = True + pathParams = self.areaOpPathParams( + obj, isHole + ) # pylint: disable=assignment-from-no-return + pathParams["shapes"] = shapelist + pathParams["feedrate"] = self.horizFeed + pathParams["feedrate_v"] = self.vertFeed + pathParams["verbose"] = True + pathParams["resume_height"] = obj.SafeHeight.Value + pathParams["retraction"] = obj.ClearanceHeight.Value + pathParams["return_end"] = True # Note that emitting preambles between moves breaks some dressups and prevents path optimization on some controllers - pathParams['preamble'] = False + pathParams["preamble"] = False if not self.areaOpRetractTool(obj): - pathParams['threshold'] = 2.001 * self.radius + pathParams["threshold"] = 2.001 * self.radius if self.endVector is not None: - pathParams['start'] = self.endVector + pathParams["start"] = self.endVector elif PathOp.FeatureStartPoint & self.opFeatures(obj) and obj.UseStartPoint: - pathParams['start'] = obj.StartPoint + pathParams["start"] = obj.StartPoint - obj.PathParams = str({key: value for key, value in pathParams.items() if key != 'shapes'}) + obj.PathParams = str( + {key: value for key, value in pathParams.items() if key != "shapes"} + ) PathLog.debug("Path with params: {}".format(obj.PathParams)) (pp, end_vector) = Path.fromShapes(**pathParams) - PathLog.debug('pp: {}, end vector: {}'.format(pp, end_vector)) - self.endVector = end_vector # pylint: disable=attribute-defined-outside-init + PathLog.debug("pp: {}, end vector: {}".format(pp, end_vector)) + self.endVector = end_vector # pylint: disable=attribute-defined-outside-init simobj = None if getsim: - areaParams['Thicken'] = True - areaParams['ToolRadius'] = self.radius - self.radius * .005 + areaParams["Thicken"] = True + areaParams["ToolRadius"] = self.radius - self.radius * 0.005 area.setParams(**areaParams) - sec = area.makeSections(mode=0, project=False, heights=heights)[-1].getShape() + sec = area.makeSections(mode=0, project=False, heights=heights)[ + -1 + ].getShape() simobj = sec.extrude(FreeCAD.Vector(0, 0, baseobject.BoundBox.ZMax)) return pp, simobj def _buildProfileOpenEdges(self, obj, edgeList, isHole, start, getsim): - '''_buildPathArea(obj, edgeList, isHole, start, getsim) ... internal function.''' + """_buildPathArea(obj, edgeList, isHole, start, getsim) ... internal function.""" # pylint: disable=unused-argument PathLog.track() paths = [] heights = [i for i in self.depthparams] - PathLog.debug('depths: {}'.format(heights)) + PathLog.debug("depths: {}".format(heights)) for i in range(0, len(heights)): for baseShape in edgeList: hWire = Part.Wire(Part.__sortEdges__(baseShape.Edges)) hWire.translate(FreeCAD.Vector(0, 0, heights[i] - hWire.BoundBox.ZMin)) - pathParams = {} # pylint: disable=assignment-from-no-return - pathParams['shapes'] = [hWire] - pathParams['feedrate'] = self.horizFeed - pathParams['feedrate_v'] = self.vertFeed - pathParams['verbose'] = True - pathParams['resume_height'] = obj.SafeHeight.Value - pathParams['retraction'] = obj.ClearanceHeight.Value - pathParams['return_end'] = True + pathParams = {} # pylint: disable=assignment-from-no-return + pathParams["shapes"] = [hWire] + pathParams["feedrate"] = self.horizFeed + pathParams["feedrate_v"] = self.vertFeed + pathParams["verbose"] = True + pathParams["resume_height"] = obj.SafeHeight.Value + pathParams["retraction"] = obj.ClearanceHeight.Value + pathParams["return_end"] = True # Note that emitting preambles between moves breaks some dressups and prevents path optimization on some controllers - pathParams['preamble'] = False + pathParams["preamble"] = False if self.endVector is None: V = hWire.Wires[0].Vertexes lv = len(V) - 1 - pathParams['start'] = FreeCAD.Vector(V[0].X, V[0].Y, V[0].Z) - if obj.Direction == 'CCW': - pathParams['start'] = FreeCAD.Vector(V[lv].X, V[lv].Y, V[lv].Z) + pathParams["start"] = FreeCAD.Vector(V[0].X, V[0].Y, V[0].Z) + if obj.Direction == "CCW": + pathParams["start"] = FreeCAD.Vector(V[lv].X, V[lv].Y, V[lv].Z) else: - pathParams['start'] = self.endVector + pathParams["start"] = self.endVector - obj.PathParams = str({key: value for key, value in pathParams.items() if key != 'shapes'}) + obj.PathParams = str( + {key: value for key, value in pathParams.items() if key != "shapes"} + ) PathLog.debug("Path with params: {}".format(obj.PathParams)) (pp, end_vector) = Path.fromShapes(**pathParams) paths.extend(pp.Commands) - PathLog.debug('pp: {}, end vector: {}'.format(pp, end_vector)) + PathLog.debug("pp: {}, end vector: {}".format(pp, end_vector)) self.endVector = end_vector simobj = None return paths, simobj - def opExecute(self, obj, getsim=False): # pylint: disable=arguments-differ - '''opExecute(obj, getsim=False) ... implementation of Path.Area ops. + def opExecute(self, obj, getsim=False): # pylint: disable=arguments-differ + """opExecute(obj, getsim=False) ... implementation of Path.Area ops. determines the parameters for _buildPathArea(). Do not overwrite, implement areaOpAreaParams(obj, isHole) ... op specific area param dictionary areaOpPathParams(obj, isHole) ... op specific path param dictionary areaOpShapes(obj) ... the shape for path area to process areaOpUseProjection(obj) ... return true if operation can use projection - instead.''' + instead.""" PathLog.track() # Instantiate class variables for operation reference - self.endVector = None # pylint: disable=attribute-defined-outside-init + self.endVector = None # pylint: disable=attribute-defined-outside-init self.leadIn = 2.0 # pylint: disable=attribute-defined-outside-init # Initiate depthparams and calculate operation heights for operation - self.depthparams = self._customDepthParams(obj, obj.StartDepth.Value, obj.FinalDepth.Value) + self.depthparams = self._customDepthParams( + obj, obj.StartDepth.Value, obj.FinalDepth.Value + ) # Set start point if PathOp.FeatureStartPoint & self.opFeatures(obj) and obj.UseStartPoint: @@ -319,7 +368,7 @@ class ObjectOp(PathOp.ObjectOp): else: start = None - aOS = self.areaOpShapes(obj) # pylint: disable=assignment-from-no-return + aOS = self.areaOpShapes(obj) # pylint: disable=assignment-from-no-return # Adjust tuples length received from other PathWB tools/operations shapes = [] @@ -327,7 +376,7 @@ class ObjectOp(PathOp.ObjectOp): if len(shp) == 2: (fc, iH) = shp # fc, iH, sub or description - tup = fc, iH, 'otherOp' + tup = fc, iH, "otherOp" shapes.append(tup) else: shapes.append(shp) @@ -335,38 +384,47 @@ class ObjectOp(PathOp.ObjectOp): if len(shapes) > 1: locations = [] for s in shapes: - if s[2] == 'OpenEdge': + if s[2] == "OpenEdge": shp = Part.makeCompound(s[0]) else: shp = s[0] - locations.append({ - 'x': shp.BoundBox.XMax, - 'y': shp.BoundBox.YMax, - 'shape': s - }) + locations.append( + {"x": shp.BoundBox.XMax, "y": shp.BoundBox.YMax, "shape": s} + ) - locations = PathUtils.sort_locations(locations, ['x', 'y']) + locations = PathUtils.sort_locations(locations, ["x", "y"]) - shapes = [j['shape'] for j in locations] + shapes = [j["shape"] for j in locations] sims = [] for shape, isHole, sub in shapes: profileEdgesIsOpen = False - if sub == 'OpenEdge': + if sub == "OpenEdge": profileEdgesIsOpen = True - if PathOp.FeatureStartPoint & self.opFeatures(obj) and obj.UseStartPoint: + if ( + PathOp.FeatureStartPoint & self.opFeatures(obj) + and obj.UseStartPoint + ): osp = obj.StartPoint - self.commandlist.append(Path.Command('G0', {'X': osp.x, 'Y': osp.y, 'F': self.horizRapid})) + self.commandlist.append( + Path.Command( + "G0", {"X": osp.x, "Y": osp.y, "F": self.horizRapid} + ) + ) try: if profileEdgesIsOpen: - (pp, sim) = self._buildProfileOpenEdges(obj, shape, isHole, start, getsim) + (pp, sim) = self._buildProfileOpenEdges( + obj, shape, isHole, start, getsim + ) else: (pp, sim) = self._buildPathArea(obj, shape, isHole, start, getsim) - except Exception as e: # pylint: disable=broad-except + except Exception as e: # pylint: disable=broad-except FreeCAD.Console.PrintError(e) - FreeCAD.Console.PrintError("Something unexpected happened. Check project and tool config.") + FreeCAD.Console.PrintError( + "Something unexpected happened. Check project and tool config." + ) else: if profileEdgesIsOpen: ppCmds = pp @@ -378,41 +436,49 @@ class ObjectOp(PathOp.ObjectOp): sims.append(sim) # Eif - if self.areaOpRetractTool(obj) and self.endVector is not None and len(self.commandlist) > 1: + if ( + self.areaOpRetractTool(obj) + and self.endVector is not None + and len(self.commandlist) > 1 + ): self.endVector[2] = obj.ClearanceHeight.Value - self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) + self.commandlist.append( + Path.Command( + "G0", {"Z": obj.ClearanceHeight.Value, "F": self.vertRapid} + ) + ) PathLog.debug("obj.Name: " + str(obj.Name) + "\n\n") return sims def areaOpRetractTool(self, obj): - '''areaOpRetractTool(obj) ... return False to keep the tool at current level between shapes. Default is True.''' + """areaOpRetractTool(obj) ... return False to keep the tool at current level between shapes. Default is True.""" # pylint: disable=unused-argument return True def areaOpAreaParams(self, obj, isHole): - '''areaOpAreaParams(obj, isHole) ... return operation specific area parameters in a dictionary. + """areaOpAreaParams(obj, isHole) ... return operation specific area parameters in a dictionary. Note that the resulting parameters are stored in the property AreaParams. - Must be overwritten by subclasses.''' + Must be overwritten by subclasses.""" # pylint: disable=unused-argument - pass # pylint: disable=unnecessary-pass + pass # pylint: disable=unnecessary-pass def areaOpPathParams(self, obj, isHole): - '''areaOpPathParams(obj, isHole) ... return operation specific path parameters in a dictionary. + """areaOpPathParams(obj, isHole) ... return operation specific path parameters in a dictionary. Note that the resulting parameters are stored in the property PathParams. - Must be overwritten by subclasses.''' + Must be overwritten by subclasses.""" # pylint: disable=unused-argument - pass # pylint: disable=unnecessary-pass + pass # pylint: disable=unnecessary-pass def areaOpShapes(self, obj): - '''areaOpShapes(obj) ... return all shapes to be processed by Path.Area for this op. - Must be overwritten by subclasses.''' + """areaOpShapes(obj) ... return all shapes to be processed by Path.Area for this op. + Must be overwritten by subclasses.""" # pylint: disable=unused-argument - pass # pylint: disable=unnecessary-pass + pass # pylint: disable=unnecessary-pass def areaOpUseProjection(self, obj): - '''areaOpUseProcjection(obj) ... return True if the operation can use procjection, defaults to False. - Can safely be overwritten by subclasses.''' + """areaOpUseProcjection(obj) ... return True if the operation can use procjection, defaults to False. + Can safely be overwritten by subclasses.""" # pylint: disable=unused-argument return False @@ -426,10 +492,14 @@ class ObjectOp(PathOp.ObjectOp): step_down=obj.StepDown.Value, z_finish_step=finish_step, final_depth=finDep, - user_depths=None) + user_depths=None, + ) return cdp + + # Eclass + def SetupProperties(): setup = [] return setup diff --git a/src/Mod/Path/PathScripts/PathOpTools.py b/src/Mod/Path/PathScripts/PathOpTools.py index 4f66953b02..814ae8c382 100644 --- a/src/Mod/Path/PathScripts/PathOpTools.py +++ b/src/Mod/Path/PathScripts/PathOpTools.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__ = "PathOpTools - Tools for Path operations." __author__ = "sliptonic (Brad Collette)" @@ -38,7 +39,7 @@ __url__ = "https://www.freecadweb.org" __doc__ = "Collection of functions used by various Path operations. The functions are specific to Path and the algorithms employed by Path's operations." PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -#PathLog.trackModule(PathLog.thisModule()) +# PathLog.trackModule(PathLog.thisModule()) PrintWireDebug = False @@ -46,15 +47,19 @@ PrintWireDebug = False def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) + def debugEdge(label, e): - '''debugEdge(label, e) ... prints a python statement to create e - Currently lines and arcs are supported.''' + """debugEdge(label, e) ... prints a python statement to create e + Currently lines and arcs are supported.""" if not PrintWireDebug: return p0 = e.valueAt(e.FirstParameter) p1 = e.valueAt(e.LastParameter) if Part.Line == type(e.Curve): - print("%s Part.makeLine((%.2f, %.2f, %.2f), (%.2f, %.2f, %.2f))" % (label, p0.x, p0.y, p0.z, p1.x, p1.y, p1.z)) + print( + "%s Part.makeLine((%.2f, %.2f, %.2f), (%.2f, %.2f, %.2f))" + % (label, p0.x, p0.y, p0.z, p1.x, p1.y, p1.z) + ) elif Part.Circle == type(e.Curve): r = e.Curve.Radius c = e.Curve.Center @@ -65,26 +70,37 @@ def debugEdge(label, e): else: first = math.degrees(xu + e.FirstParameter) last = first + math.degrees(e.LastParameter - e.FirstParameter) - print("%s Part.makeCircle(%.2f, App.Vector(%.2f, %.2f, %.2f), App.Vector(%.2f, %.2f, %.2f), %.2f, %.2f)" % (label, r, c.x, c.y, c.z, a.x, a.y, a.z, first, last)) + print( + "%s Part.makeCircle(%.2f, App.Vector(%.2f, %.2f, %.2f), App.Vector(%.2f, %.2f, %.2f), %.2f, %.2f)" + % (label, r, c.x, c.y, c.z, a.x, a.y, a.z, first, last) + ) else: - print("%s %s (%.2f, %.2f, %.2f) -> (%.2f, %.2f, %.2f)" % (label, type(e.Curve).__name__, p0.x, p0.y, p0.z, p1.x, p1.y, p1.z)) + print( + "%s %s (%.2f, %.2f, %.2f) -> (%.2f, %.2f, %.2f)" + % (label, type(e.Curve).__name__, p0.x, p0.y, p0.z, p1.x, p1.y, p1.z) + ) + def debugWire(label, w): - '''debugWire(label, w) ... prints python statements for all edges of w to be added to the object tree in a group.''' + """debugWire(label, w) ... prints python statements for all edges of w to be added to the object tree in a group.""" if not PrintWireDebug: return print("#%s wire >>>>>>>>>>>>>>>>>>>>>>>>" % label) - print("grp = FreeCAD.ActiveDocument.addObject('App::DocumentObjectGroup', '%s')" % label) - for i,e in enumerate(w.Edges): + print( + "grp = FreeCAD.ActiveDocument.addObject('App::DocumentObjectGroup', '%s')" + % label + ) + for i, e in enumerate(w.Edges): edge = "%s_e%d" % (label, i) debugEdge("%s = " % edge, e) print("Part.show(%s, '%s')" % (edge, edge)) print("grp.addObject(FreeCAD.ActiveDocument.ActiveObject)") print("#%s wire <<<<<<<<<<<<<<<<<<<<<<<<" % label) + def _orientEdges(inEdges): - '''_orientEdges(inEdges) ... internal worker function to orient edges so the last vertex of one edge connects to the first vertex of the next edge. - Assumes the edges are in an order so they can be connected.''' + """_orientEdges(inEdges) ... internal worker function to orient edges so the last vertex of one edge connects to the first vertex of the next edge. + Assumes the edges are in an order so they can be connected.""" PathLog.track() # orient all edges of the wire so each edge's last value connects to the next edge's first value e0 = inEdges[0] @@ -92,21 +108,28 @@ def _orientEdges(inEdges): if 1 < len(inEdges): last = e0.valueAt(e0.LastParameter) e1 = inEdges[1] - if not PathGeom.pointsCoincide(last, e1.valueAt(e1.FirstParameter)) and not PathGeom.pointsCoincide(last, e1.valueAt(e1.LastParameter)): - debugEdge('# _orientEdges - flip first', e0) + if not PathGeom.pointsCoincide( + last, e1.valueAt(e1.FirstParameter) + ) and not PathGeom.pointsCoincide(last, e1.valueAt(e1.LastParameter)): + debugEdge("# _orientEdges - flip first", e0) e0 = PathGeom.flipEdge(e0) edges = [e0] last = e0.valueAt(e0.LastParameter) for e in inEdges[1:]: - edge = e if PathGeom.pointsCoincide(last, e.valueAt(e.FirstParameter)) else PathGeom.flipEdge(e) + edge = ( + e + if PathGeom.pointsCoincide(last, e.valueAt(e.FirstParameter)) + else PathGeom.flipEdge(e) + ) edges.append(edge) last = edge.valueAt(edge.LastParameter) return edges + def _isWireClockwise(w): - '''_isWireClockwise(w) ... return True if wire is oriented clockwise. - Assumes the edges of w are already properly oriented - for generic access use isWireClockwise(w).''' + """_isWireClockwise(w) ... return True if wire is oriented clockwise. + Assumes the edges of w are already properly oriented - for generic access use isWireClockwise(w).""" # handle wires consisting of a single circle or 2 edges where one is an arc. # in both cases, because the edges are expected to be oriented correctly, the orientation can be # determined by looking at (one of) the circle curves. @@ -125,31 +148,33 @@ def _isWireClockwise(w): PathLog.track(area) return area < 0 + def isWireClockwise(w): - '''isWireClockwise(w) ... returns True if the wire winds clockwise. ''' + """isWireClockwise(w) ... returns True if the wire winds clockwise.""" return _isWireClockwise(Part.Wire(_orientEdges(w.Edges))) def orientWire(w, forward=True): - '''orientWire(w, forward=True) ... orients given wire in a specific direction. + """orientWire(w, forward=True) ... orients given wire in a specific direction. If forward = True (the default) the wire is oriented clockwise, looking down the negative Z axis. If forward = False the wire is oriented counter clockwise. - If forward = None the orientation is determined by the order in which the edges appear in the wire.''' - PathLog.debug('orienting forward: {}'.format(forward)) + If forward = None the orientation is determined by the order in which the edges appear in the wire.""" + PathLog.debug("orienting forward: {}".format(forward)) wire = Part.Wire(_orientEdges(w.Edges)) if forward is not None: if forward != _isWireClockwise(wire): - PathLog.track('orientWire - needs flipping') + PathLog.track("orientWire - needs flipping") return PathGeom.flipWire(wire) - PathLog.track('orientWire - ok') + PathLog.track("orientWire - ok") return wire -def offsetWire(wire, base, offset, forward, Side = None): - '''offsetWire(wire, base, offset, forward) ... offsets the wire away from base and orients the wire accordingly. + +def offsetWire(wire, base, offset, forward, Side=None): + """offsetWire(wire, base, offset, forward) ... offsets the wire away from base and orients the wire accordingly. The function tries to avoid most of the pitfalls of Part.makeOffset2D which is possible because all offsetting happens in the XY plane. - ''' - PathLog.track('offsetWire') + """ + PathLog.track("offsetWire") if 1 == len(wire.Edges): edge = wire.Edges[0] @@ -159,24 +184,34 @@ def offsetWire(wire, base, offset, forward, Side = None): # https://www.freecadweb.org/wiki/Part%20Offset2D # it's easy to construct them manually though z = -1 if forward else 1 - new_edge = Part.makeCircle(curve.Radius + offset, curve.Center, FreeCAD.Vector(0, 0, z)) - if base.isInside(new_edge.Vertexes[0].Point, offset/2, True): + new_edge = Part.makeCircle( + curve.Radius + offset, curve.Center, FreeCAD.Vector(0, 0, z) + ) + if base.isInside(new_edge.Vertexes[0].Point, offset / 2, True): if offset > curve.Radius or PathGeom.isRoughly(offset, curve.Radius): # offsetting a hole by its own radius (or more) makes the hole vanish return None if Side: Side[0] = "Inside" print("inside") - new_edge = Part.makeCircle(curve.Radius - offset, curve.Center, FreeCAD.Vector(0, 0, -z)) - + new_edge = Part.makeCircle( + curve.Radius - offset, curve.Center, FreeCAD.Vector(0, 0, -z) + ) + return Part.Wire([new_edge]) - + if Part.Circle == type(curve) and not wire.isClosed(): # Process arc segment z = -1 if forward else 1 - l1 = math.sqrt((edge.Vertexes[0].Point.x - curve.Center.x)**2 + (edge.Vertexes[0].Point.y - curve.Center.y)**2) - l2 = math.sqrt((edge.Vertexes[1].Point.x - curve.Center.x)**2 + (edge.Vertexes[1].Point.y - curve.Center.y)**2) - + l1 = math.sqrt( + (edge.Vertexes[0].Point.x - curve.Center.x) ** 2 + + (edge.Vertexes[0].Point.y - curve.Center.y) ** 2 + ) + l2 = math.sqrt( + (edge.Vertexes[1].Point.x - curve.Center.x) ** 2 + + (edge.Vertexes[1].Point.y - curve.Center.y) ** 2 + ) + # Calculate angles based on x-axis (0 - PI/2) start_angle = math.acos((edge.Vertexes[0].Point.x - curve.Center.x) / l1) end_angle = math.acos((edge.Vertexes[1].Point.x - curve.Center.x) / l2) @@ -186,26 +221,41 @@ def offsetWire(wire, base, offset, forward, Side = None): start_angle *= -1 if edge.Vertexes[1].Point.y < curve.Center.y: end_angle *= -1 - - if (edge.Vertexes[0].Point.x > curve.Center.x or edge.Vertexes[1].Point.x > curve.Center.x) and curve.AngleXU < 0: + + if ( + edge.Vertexes[0].Point.x > curve.Center.x + or edge.Vertexes[1].Point.x > curve.Center.x + ) and curve.AngleXU < 0: tmp = start_angle start_angle = end_angle end_angle = tmp # Inside / Outside - if base.isInside(edge.Vertexes[0].Point, offset/2, True): + if base.isInside(edge.Vertexes[0].Point, offset / 2, True): offset *= -1 if Side: Side[0] = "Inside" # Create new arc if curve.AngleXU > 0: - edge = Part.ArcOfCircle(Part.Circle(curve.Center, FreeCAD.Vector(0,0,1), curve.Radius+offset), start_angle, end_angle).toShape() + edge = Part.ArcOfCircle( + Part.Circle( + curve.Center, FreeCAD.Vector(0, 0, 1), curve.Radius + offset + ), + start_angle, + end_angle, + ).toShape() else: - edge = Part.ArcOfCircle(Part.Circle(curve.Center, FreeCAD.Vector(0,0,1), curve.Radius-offset), start_angle, end_angle).toShape() + edge = Part.ArcOfCircle( + Part.Circle( + curve.Center, FreeCAD.Vector(0, 0, 1), curve.Radius - offset + ), + start_angle, + end_angle, + ).toShape() return Part.Wire([edge]) - + if Part.Line == type(curve) or Part.LineSegment == type(curve): # offsetting a single edge doesn't work because there is an infinite # possible planes into which the edge could be offset @@ -217,7 +267,11 @@ def offsetWire(wire, base, offset, forward, Side = None): edge.translate(o) # offset edde the other way if the result is inside - if base.isInside(edge.valueAt((edge.FirstParameter + edge.LastParameter) / 2), offset / 2, True): + if base.isInside( + edge.valueAt((edge.FirstParameter + edge.LastParameter) / 2), + offset / 2, + True, + ): edge.translate(-2 * o) # flip the edge if it's not on the right side of the original edge @@ -229,23 +283,23 @@ def offsetWire(wire, base, offset, forward, Side = None): return Part.Wire([edge]) # if we get to this point the assumption is that makeOffset2D can deal with the edge - pass # pylint: disable=unnecessary-pass + pass # pylint: disable=unnecessary-pass owire = orientWire(wire.makeOffset2D(offset), True) - debugWire('makeOffset2D_%d' % len(wire.Edges), owire) + debugWire("makeOffset2D_%d" % len(wire.Edges), owire) if wire.isClosed(): - if not base.isInside(owire.Edges[0].Vertexes[0].Point, offset/2, True): - PathLog.track('closed - outside') + if not base.isInside(owire.Edges[0].Vertexes[0].Point, offset / 2, True): + PathLog.track("closed - outside") if Side: Side[0] = "Outside" return orientWire(owire, forward) - PathLog.track('closed - inside') + PathLog.track("closed - inside") if Side: Side[0] = "Inside" try: owire = wire.makeOffset2D(-offset) - except Exception: # pylint: disable=broad-except + except Exception: # pylint: disable=broad-except # most likely offsetting didn't work because the wire is a hole # and the offset is too big - making the hole vanish return None @@ -269,8 +323,8 @@ def offsetWire(wire, base, offset, forward, Side = None): # determine the start and end point start = edges[0].firstVertex().Point end = edges[-1].lastVertex().Point - debugWire('wire', wire) - debugWire('wedges', Part.Wire(edges)) + debugWire("wire", wire) + debugWire("wedges", Part.Wire(edges)) # find edges that are not inside the shape common = base.common(owire) @@ -281,7 +335,9 @@ def offsetWire(wire, base, offset, forward, Side = None): p0 = edge.firstVertex().Point p1 = edge.lastVertex().Point for p in insideEndpoints: - if PathGeom.pointsCoincide(p, p0, 0.01) or PathGeom.pointsCoincide(p, p1, 0.01): + if PathGeom.pointsCoincide(p, p0, 0.01) or PathGeom.pointsCoincide( + p, p1, 0.01 + ): return True return False @@ -292,16 +348,15 @@ def offsetWire(wire, base, offset, forward, Side = None): if not longestWire or longestWire.Length < w.Length: longestWire = w - debugWire('outside', Part.Wire(outside)) - debugWire('longest', longestWire) + debugWire("outside", Part.Wire(outside)) + debugWire("longest", longestWire) def isCircleAt(edge, center): - '''isCircleAt(edge, center) ... helper function returns True if edge is a circle at the given center.''' + """isCircleAt(edge, center) ... helper function returns True if edge is a circle at the given center.""" if Part.Circle == type(edge.Curve) or Part.ArcOfCircle == type(edge.Curve): return PathGeom.pointsCoincide(edge.Curve.Center, center) return False - # split offset wire into edges to the left side and edges to the right side collectLeft = False collectRight = False @@ -312,7 +367,7 @@ def offsetWire(wire, base, offset, forward, Side = None): # an end point (circle centered at one of the end points of the original wire). # should we come to an end point and determine that we've already collected the # next side, we're done - for e in (owire.Edges + owire.Edges): + for e in owire.Edges + owire.Edges: if isCircleAt(e, start): if PathGeom.pointsCoincide(e.Curve.Axis, FreeCAD.Vector(0, 0, 1)): if not collectLeft and leftSideEdges: @@ -340,8 +395,8 @@ def offsetWire(wire, base, offset, forward, Side = None): elif collectRight: rightSideEdges.append(e) - debugWire('left', Part.Wire(leftSideEdges)) - debugWire('right', Part.Wire(rightSideEdges)) + debugWire("left", Part.Wire(leftSideEdges)) + debugWire("right", Part.Wire(rightSideEdges)) # figure out if all the left sided edges or the right sided edges are the ones # that are 'outside'. However, we return the full side. @@ -365,4 +420,3 @@ def offsetWire(wire, base, offset, forward, Side = None): edges.reverse() return orientWire(Part.Wire(edges), None) - diff --git a/src/Mod/Path/PathScripts/PathPropertyBag.py b/src/Mod/Path/PathScripts/PathPropertyBag.py index 2142457ed1..7f797126ed 100644 --- a/src/Mod/Path/PathScripts/PathPropertyBag.py +++ b/src/Mod/Path/PathScripts/PathPropertyBag.py @@ -24,27 +24,29 @@ import FreeCAD import PySide import re -__title__ = 'Generic property container to store some values.' -__author__ = 'sliptonic (Brad Collette)' -__url__ = 'https://www.freecadweb.org' -__doc__ = 'A generic container for typed properties in arbitrary categories.' +__title__ = "Generic property container to store some values." +__author__ = "sliptonic (Brad Collette)" +__url__ = "https://www.freecadweb.org" +__doc__ = "A generic container for typed properties in arbitrary categories." + def translate(context, text, disambig=None): return PySide.QtCore.QCoreApplication.translate(context, text, disambig) SupportedPropertyType = { - 'Angle' : 'App::PropertyAngle', - 'Bool' : 'App::PropertyBool', - 'Distance' : 'App::PropertyDistance', - 'Enumeration' : 'App::PropertyEnumeration', - 'File' : 'App::PropertyFile', - 'Float' : 'App::PropertyFloat', - 'Integer' : 'App::PropertyInteger', - 'Length' : 'App::PropertyLength', - 'Percent' : 'App::PropertyPercent', - 'String' : 'App::PropertyString', - } + "Angle": "App::PropertyAngle", + "Bool": "App::PropertyBool", + "Distance": "App::PropertyDistance", + "Enumeration": "App::PropertyEnumeration", + "File": "App::PropertyFile", + "Float": "App::PropertyFloat", + "Integer": "App::PropertyInteger", + "Length": "App::PropertyLength", + "Percent": "App::PropertyPercent", + "String": "App::PropertyString", +} + def getPropertyTypeName(o): for typ in SupportedPropertyType: @@ -52,14 +54,22 @@ def getPropertyTypeName(o): return typ raise IndexError() -class PropertyBag(object): - '''Property container object.''' - CustomPropertyGroups = 'CustomPropertyGroups' - CustomPropertyGroupDefault = 'User' +class PropertyBag(object): + """Property container object.""" + + CustomPropertyGroups = "CustomPropertyGroups" + CustomPropertyGroupDefault = "User" def __init__(self, obj): - obj.addProperty('App::PropertyStringList', self.CustomPropertyGroups, 'Base', PySide.QtCore.QT_TRANSLATE_NOOP('PathPropertyBag', 'List of custom property groups')) + obj.addProperty( + "App::PropertyStringList", + self.CustomPropertyGroups, + "Base", + PySide.QtCore.QT_TRANSLATE_NOOP( + "PathPropertyBag", "List of custom property groups" + ), + ) self.onDocumentRestored(obj) def __getstate__(self): @@ -69,14 +79,14 @@ class PropertyBag(object): return None def __sanitizePropertyName(self, name): - if(len(name) == 0): + if len(name) == 0: return clean = name[0] for i in range(1, len(name)): - if (name[i] == ' '): + if name[i] == " ": clean += name[i + 1].upper() i += 1 - elif(name[i - 1] != ' '): + elif name[i - 1] != " ": clean += name[i] return clean @@ -85,20 +95,24 @@ class PropertyBag(object): obj.setEditorMode(self.CustomPropertyGroups, 2) # hide def getCustomProperties(self): - '''getCustomProperties() ... Return a list of all custom properties created in this container.''' - return [p for p in self.obj.PropertiesList if self.obj.getGroupOfProperty(p) in self.obj.CustomPropertyGroups] + """getCustomProperties() ... Return a list of all custom properties created in this container.""" + return [ + p + for p in self.obj.PropertiesList + if self.obj.getGroupOfProperty(p) in self.obj.CustomPropertyGroups + ] def addCustomProperty(self, propertyType, name, group=None, desc=None): - '''addCustomProperty(propertyType, name, group=None, desc=None) ... adds a custom property and tracks its group.''' + """addCustomProperty(propertyType, name, group=None, desc=None) ... adds a custom property and tracks its group.""" if desc is None: - desc = '' + desc = "" if group is None: group = self.CustomPropertyGroupDefault groups = self.obj.CustomPropertyGroups name = self.__sanitizePropertyName(name) if not re.match("^[A-Za-z0-9_]*$", name): - raise ValueError('Property Name can only contain letters and numbers') + raise ValueError("Property Name can only contain letters and numbers") if not group in groups: groups.append(group) @@ -107,7 +121,7 @@ class PropertyBag(object): return name def refreshCustomPropertyGroups(self): - '''refreshCustomPropertyGroups() ... removes empty property groups, should be called after deleting properties.''' + """refreshCustomPropertyGroups() ... removes empty property groups, should be called after deleting properties.""" customGroups = [] for p in self.obj.PropertiesList: group = self.obj.getGroupOfProperty(p) @@ -116,17 +130,17 @@ class PropertyBag(object): self.obj.CustomPropertyGroups = customGroups -def Create(name = 'PropertyBag'): - obj = FreeCAD.ActiveDocument.addObject('App::FeaturePython', name) +def Create(name="PropertyBag"): + obj = FreeCAD.ActiveDocument.addObject("App::FeaturePython", name) obj.Proxy = PropertyBag(obj) return obj + def IsPropertyBag(obj): - '''Returns True if the supplied object is a property container (or its Proxy).''' + """Returns True if the supplied object is a property container (or its Proxy).""" if type(obj) == PropertyBag: return True - if hasattr(obj, 'Proxy'): + if hasattr(obj, "Proxy"): return IsPropertyBag(obj.Proxy) return False - diff --git a/src/Mod/Path/PathScripts/PathPropertyBagGui.py b/src/Mod/Path/PathScripts/PathPropertyBagGui.py index 5b438f80c0..60e905d98b 100644 --- a/src/Mod/Path/PathScripts/PathPropertyBagGui.py +++ b/src/Mod/Path/PathScripts/PathPropertyBagGui.py @@ -22,7 +22,8 @@ import FreeCAD import FreeCADGui -#import PathGui + +# import PathGui import PathScripts.PathIconViewProvider as PathIconViewProvider import PathScripts.PathLog as PathLog import PathScripts.PathPropertyBag as PathPropertyBag @@ -38,15 +39,16 @@ __url__ = "https://www.freecadweb.org" __doc__ = "Task panel editor for a PropertyBag" PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -#PathLog.trackModule(PathLog.thisModule()) +# PathLog.trackModule(PathLog.thisModule()) # Qt translation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) + class ViewProvider(object): - '''ViewProvider for a PropertyBag. - It's sole job is to provide an icon and invoke the TaskPanel on edit.''' + """ViewProvider for a PropertyBag. + It's sole job is to provide an icon and invoke the TaskPanel on edit.""" def __init__(self, vobj, name): PathLog.track(name) @@ -73,7 +75,7 @@ class ViewProvider(object): def getDisplayMode(self, mode): # pylint: disable=unused-argument - return 'Default' + return "Default" def setEdit(self, vobj, mode=0): # pylint: disable=unused-argument @@ -95,18 +97,20 @@ class ViewProvider(object): def doubleClicked(self, vobj): self.setEdit(vobj) + class Delegate(QtGui.QStyledItemDelegate): - RoleObject = QtCore.Qt.UserRole + 1 + RoleObject = QtCore.Qt.UserRole + 1 RoleProperty = QtCore.Qt.UserRole + 2 - RoleEditor = QtCore.Qt.UserRole + 3 + RoleEditor = QtCore.Qt.UserRole + 3 - - #def paint(self, painter, option, index): + # def paint(self, painter, option, index): # #PathLog.track(index.column(), type(option)) def createEditor(self, parent, option, index): # pylint: disable=unused-argument - editor = PathPropertyEditor.Editor(index.data(self.RoleObject), index.data(self.RoleProperty)) + editor = PathPropertyEditor.Editor( + index.data(self.RoleObject), index.data(self.RoleProperty) + ) index.model().setData(index, editor, self.RoleEditor) return editor.widget(parent) @@ -125,8 +129,8 @@ class Delegate(QtGui.QStyledItemDelegate): # pylint: disable=unused-argument widget.setGeometry(option.rect) -class PropertyCreate(object): +class PropertyCreate(object): def __init__(self, obj, grp, typ, another): self.obj = obj self.form = FreeCADGui.PySideUic.loadUi(":panels/PropertyCreate.ui") @@ -144,7 +148,7 @@ class PropertyCreate(object): if typ: self.form.propertyType.setCurrentText(typ) else: - self.form.propertyType.setCurrentText('String') + self.form.propertyType.setCurrentText("String") self.form.createAnother.setChecked(another) self.form.propertyGroup.currentTextChanged.connect(self.updateUI) @@ -159,12 +163,12 @@ class PropertyCreate(object): if self.propertyIsEnumeration(): self.form.labelEnum.setEnabled(True) self.form.propertyEnum.setEnabled(True) - typeSet = self.form.propertyEnum.text().strip() != '' + typeSet = self.form.propertyEnum.text().strip() != "" else: self.form.labelEnum.setEnabled(False) self.form.propertyEnum.setEnabled(False) if self.form.propertyEnum.text().strip(): - self.form.propertyEnum.setText('') + self.form.propertyEnum.setText("") ok = self.form.buttonBox.button(QtGui.QDialogButtonBox.Ok) @@ -178,25 +182,35 @@ class PropertyCreate(object): def propertyName(self): return self.form.propertyName.text().strip() + def propertyGroup(self): return self.form.propertyGroup.currentText().strip() + def propertyType(self): - return PathPropertyBag.SupportedPropertyType[self.form.propertyType.currentText()].strip() + return PathPropertyBag.SupportedPropertyType[ + self.form.propertyType.currentText() + ].strip() + def propertyInfo(self): return self.form.propertyInfo.toPlainText().strip() + def createAnother(self): return self.form.createAnother.isChecked() + def propertyEnumerations(self): - return [s.strip() for s in self.form.propertyEnum.text().strip().split(',')] + return [s.strip() for s in self.form.propertyEnum.text().strip().split(",")] + def propertyIsEnumeration(self): - return self.propertyType() == 'App::PropertyEnumeration' + return self.propertyType() == "App::PropertyEnumeration" def exec_(self, name): if name: # property exists - this is an edit operation self.form.propertyName.setText(name) if self.propertyIsEnumeration(): - self.form.propertyEnum.setText(','.join(self.obj.getEnumerationsOfProperty(name))) + self.form.propertyEnum.setText( + ",".join(self.obj.getEnumerationsOfProperty(name)) + ) self.form.propertyInfo.setText(self.obj.getDocumentationOfProperty(name)) self.form.labelName.setEnabled(False) @@ -206,65 +220,82 @@ class PropertyCreate(object): self.form.createAnother.setEnabled(False) else: - self.form.propertyName.setText('') - self.form.propertyInfo.setText('') - self.form.propertyEnum.setText('') - #self.form.propertyName.setFocus() + self.form.propertyName.setText("") + self.form.propertyInfo.setText("") + self.form.propertyEnum.setText("") + # self.form.propertyName.setFocus() self.updateUI() return self.form.exec_() + Panel = [] + class TaskPanel(object): ColumnName = 0 - #ColumnType = 1 - ColumnVal = 1 - #TableHeaders = ['Property', 'Type', 'Value'] - TableHeaders = ['Property', 'Value'] + # ColumnType = 1 + ColumnVal = 1 + # TableHeaders = ['Property', 'Type', 'Value'] + TableHeaders = ["Property", "Value"] def __init__(self, vobj): - self.obj = vobj.Object + self.obj = vobj.Object self.props = sorted(self.obj.Proxy.getCustomProperties()) - self.form = FreeCADGui.PySideUic.loadUi(":panels/PropertyBag.ui") + self.form = FreeCADGui.PySideUic.loadUi(":panels/PropertyBag.ui") # initialized later - self.model = None + self.model = None self.delegate = None - FreeCAD.ActiveDocument.openTransaction(translate("PathPropertyBag", "Edit PropertyBag")) + FreeCAD.ActiveDocument.openTransaction( + translate("PathPropertyBag", "Edit PropertyBag") + ) Panel.append(self) def updateData(self, topLeft, bottomRight): pass - def _setupProperty(self, i, name): typ = PathPropertyBag.getPropertyTypeName(self.obj.getTypeIdOfProperty(name)) - val = PathUtil.getPropertyValueString(self.obj, name) + val = PathUtil.getPropertyValueString(self.obj, name) info = self.obj.getDocumentationOfProperty(name) - self.model.setData(self.model.index(i, self.ColumnName), name, QtCore.Qt.EditRole) - #self.model.setData(self.model.index(i, self.ColumnType), typ, QtCore.Qt.EditRole) - self.model.setData(self.model.index(i, self.ColumnVal), self.obj, Delegate.RoleObject) - self.model.setData(self.model.index(i, self.ColumnVal), name, Delegate.RoleProperty) - self.model.setData(self.model.index(i, self.ColumnVal), val, QtCore.Qt.DisplayRole) + self.model.setData( + self.model.index(i, self.ColumnName), name, QtCore.Qt.EditRole + ) + # self.model.setData(self.model.index(i, self.ColumnType), typ, QtCore.Qt.EditRole) + self.model.setData( + self.model.index(i, self.ColumnVal), self.obj, Delegate.RoleObject + ) + self.model.setData( + self.model.index(i, self.ColumnVal), name, Delegate.RoleProperty + ) + self.model.setData( + self.model.index(i, self.ColumnVal), val, QtCore.Qt.DisplayRole + ) - self.model.setData(self.model.index(i, self.ColumnName), typ, QtCore.Qt.ToolTipRole) - #self.model.setData(self.model.index(i, self.ColumnType), info, QtCore.Qt.ToolTipRole) - self.model.setData(self.model.index(i, self.ColumnVal), info, QtCore.Qt.ToolTipRole) + self.model.setData( + self.model.index(i, self.ColumnName), typ, QtCore.Qt.ToolTipRole + ) + # self.model.setData(self.model.index(i, self.ColumnType), info, QtCore.Qt.ToolTipRole) + self.model.setData( + self.model.index(i, self.ColumnVal), info, QtCore.Qt.ToolTipRole + ) self.model.item(i, self.ColumnName).setEditable(False) - #self.model.item(i, self.ColumnType).setEditable(False) + # self.model.item(i, self.ColumnType).setEditable(False) def setupUi(self): PathLog.track() self.delegate = Delegate(self.form) - self.model = QtGui.QStandardItemModel(len(self.props), len(self.TableHeaders), self.form) + self.model = QtGui.QStandardItemModel( + len(self.props), len(self.TableHeaders), self.form + ) self.model.setHorizontalHeaderLabels(self.TableHeaders) - for i,name in enumerate(self.props): + for i, name in enumerate(self.props): self._setupProperty(i, name) self.form.table.setModel(self.model) @@ -301,8 +332,8 @@ class TaskPanel(object): def addCustomProperty(self, obj, dialog): name = dialog.propertyName() - typ = dialog.propertyType() - grp = dialog.propertyGroup() + typ = dialog.propertyType() + grp = dialog.propertyGroup() info = dialog.propertyInfo() propname = self.obj.Proxy.addCustomProperty(typ, name, grp, info) if dialog.propertyIsEnumeration(): @@ -318,17 +349,22 @@ class TaskPanel(object): dialog = PropertyCreate(self.obj, grp, typ, more) if dialog.exec_(None): # if we block signals the view doesn't get updated, surprise, surprise - #self.model.blockSignals(True) + # self.model.blockSignals(True) name, info = self.addCustomProperty(self.obj, dialog) index = 0 for i in range(self.model.rowCount()): index = i - if self.model.item(i, self.ColumnName).data(QtCore.Qt.EditRole) > dialog.propertyName(): + if ( + self.model.item(i, self.ColumnName).data(QtCore.Qt.EditRole) + > dialog.propertyName() + ): break self.model.insertRows(index, 1) self._setupProperty(index, name) - self.form.table.selectionModel().setCurrentIndex(self.model.index(index, 0), QtCore.QItemSelectionModel.Rows) - #self.model.blockSignals(False) + self.form.table.selectionModel().setCurrentIndex( + self.model.index(index, 0), QtCore.QItemSelectionModel.Rows + ) + # self.model.blockSignals(False) more = dialog.createAnother() else: more = False @@ -355,10 +391,14 @@ class TaskPanel(object): # this can happen if the old enumeration value doesn't exist anymore pass newVal = PathUtil.getPropertyValueString(obj, nam) - self.model.setData(self.model.index(row, self.ColumnVal), newVal, QtCore.Qt.DisplayRole) + self.model.setData( + self.model.index(row, self.ColumnVal), newVal, QtCore.Qt.DisplayRole + ) - #self.model.setData(self.model.index(row, self.ColumnType), info, QtCore.Qt.ToolTipRole) - self.model.setData(self.model.index(row, self.ColumnVal), info, QtCore.Qt.ToolTipRole) + # self.model.setData(self.model.index(row, self.ColumnType), info, QtCore.Qt.ToolTipRole) + self.model.setData( + self.model.index(row, self.ColumnVal), info, QtCore.Qt.ToolTipRole + ) def propertyModify(self): PathLog.track() @@ -371,7 +411,6 @@ class TaskPanel(object): self.propertyModifyIndex(index) - def propertyRemove(self): PathLog.track() # first find all rows which need to be removed @@ -387,24 +426,33 @@ class TaskPanel(object): self.model.removeRow(row) -def Create(name = 'PropertyBag'): - '''Create(name = 'PropertyBag') ... creates a new setup sheet''' - FreeCAD.ActiveDocument.openTransaction(translate("PathPropertyBag", "Create PropertyBag")) +def Create(name="PropertyBag"): + """Create(name = 'PropertyBag') ... creates a new setup sheet""" + FreeCAD.ActiveDocument.openTransaction( + translate("PathPropertyBag", "Create PropertyBag") + ) pcont = PathPropertyBag.Create(name) PathIconViewProvider.Attach(pcont.ViewObject, name) return pcont -PathIconViewProvider.RegisterViewProvider('PropertyBag', ViewProvider) + +PathIconViewProvider.RegisterViewProvider("PropertyBag", ViewProvider) + class PropertyBagCreateCommand(object): - '''Command to create a property container object''' + """Command to create a property container object""" def __init__(self): pass def GetResources(self): - return {'MenuText': translate('PathPropertyBag', 'PropertyBag'), - 'ToolTip': translate('PathPropertyBag', 'Creates an object which can be used to store reference properties.')} + return { + "MenuText": translate("PathPropertyBag", "PropertyBag"), + "ToolTip": translate( + "PathPropertyBag", + "Creates an object which can be used to store reference properties.", + ), + } def IsActive(self): return not FreeCAD.ActiveDocument is None @@ -414,17 +462,18 @@ class PropertyBagCreateCommand(object): obj = Create() body = None if sel: - if 'PartDesign::Body' == sel[0].Object.TypeId: + if "PartDesign::Body" == sel[0].Object.TypeId: body = sel[0].Object - elif hasattr(sel[0].Object, 'getParentGeoFeatureGroup'): + elif hasattr(sel[0].Object, "getParentGeoFeatureGroup"): body = sel[0].Object.getParentGeoFeatureGroup() if body: - obj.Label = 'Attributes' + obj.Label = "Attributes" group = body.Group group.append(obj) body.Group = group + if FreeCAD.GuiUp: - FreeCADGui.addCommand('Path_PropertyBag', PropertyBagCreateCommand()) + FreeCADGui.addCommand("Path_PropertyBag", PropertyBagCreateCommand()) FreeCAD.Console.PrintLog("Loading PathPropertyBagGui ... done\n") diff --git a/src/Mod/Path/PathScripts/PathSurfaceGui.py b/src/Mod/Path/PathScripts/PathSurfaceGui.py index 25a5cea8c7..e3bc454c63 100644 --- a/src/Mod/Path/PathScripts/PathSurfaceGui.py +++ b/src/Mod/Path/PathScripts/PathSurfaceGui.py @@ -22,7 +22,7 @@ import FreeCAD import FreeCADGui -import PathGui as PGui # ensure Path/Gui/Resources are loaded +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathSurface as PathSurface import PathScripts.PathGui as PathGui import PathScripts.PathOpGui as PathOpGui @@ -36,7 +36,7 @@ __doc__ = "Surface operation page controller and command implementation." class TaskPanelOpPage(PathOpGui.TaskPanelPage): - '''Page controller class for the Surface operation.''' + """Page controller class for the Surface operation.""" def initPage(self, obj): self.setTitle("3D Surface - " + obj.Label) @@ -45,11 +45,11 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.propEnums = PathSurface.ObjectSurface.opPropertyEnumerations(False) def getForm(self): - '''getForm() ... returns UI''' + """getForm() ... returns UI""" return FreeCADGui.PySideUic.loadUi(":/panels/PageOpSurfaceEdit.ui") def getFields(self, obj): - '''getFields(obj) ... transfers values from UI to obj's proprties''' + """getFields(obj) ... transfers values from UI to obj's proprties""" self.updateToolController(obj, self.form.toolController) self.updateCoolant(obj, self.form.coolantController) @@ -74,29 +74,33 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): This type of dynamic combobox population is done for the Tool Controller selection. """ - val = self.propEnums['CutPattern'][self.form.cutPattern.currentIndex()] + val = self.propEnums["CutPattern"][self.form.cutPattern.currentIndex()] if obj.CutPattern != val: obj.CutPattern = val - val = self.propEnums['ProfileEdges'][self.form.profileEdges.currentIndex()] + val = self.propEnums["ProfileEdges"][self.form.profileEdges.currentIndex()] if obj.ProfileEdges != val: obj.ProfileEdges = val if obj.AvoidLastX_Faces != self.form.avoidLastX_Faces.value(): obj.AvoidLastX_Faces = self.form.avoidLastX_Faces.value() - obj.DropCutterExtraOffset.x = FreeCAD.Units.Quantity(self.form.boundBoxExtraOffsetX.text()).Value - obj.DropCutterExtraOffset.y = FreeCAD.Units.Quantity(self.form.boundBoxExtraOffsetY.text()).Value + obj.DropCutterExtraOffset.x = FreeCAD.Units.Quantity( + self.form.boundBoxExtraOffsetX.text() + ).Value + obj.DropCutterExtraOffset.y = FreeCAD.Units.Quantity( + self.form.boundBoxExtraOffsetY.text() + ).Value if obj.DropCutterDir != str(self.form.dropCutterDirSelect.currentText()): obj.DropCutterDir = str(self.form.dropCutterDirSelect.currentText()) - PathGui.updateInputField(obj, 'DepthOffset', self.form.depthOffset) + PathGui.updateInputField(obj, "DepthOffset", self.form.depthOffset) if obj.StepOver != self.form.stepOver.value(): obj.StepOver = self.form.stepOver.value() - PathGui.updateInputField(obj, 'SampleInterval', self.form.sampleInterval) + PathGui.updateInputField(obj, "SampleInterval", self.form.sampleInterval) if obj.UseStartPoint != self.form.useStartPoint.isChecked(): obj.UseStartPoint = self.form.useStartPoint.isChecked() @@ -107,11 +111,16 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): if obj.OptimizeLinearPaths != self.form.optimizeEnabled.isChecked(): obj.OptimizeLinearPaths = self.form.optimizeEnabled.isChecked() - if obj.OptimizeStepOverTransitions != self.form.optimizeStepOverTransitions.isChecked(): - obj.OptimizeStepOverTransitions = self.form.optimizeStepOverTransitions.isChecked() + if ( + obj.OptimizeStepOverTransitions + != self.form.optimizeStepOverTransitions.isChecked() + ): + obj.OptimizeStepOverTransitions = ( + self.form.optimizeStepOverTransitions.isChecked() + ) def setFields(self, obj): - '''setFields(obj) ... transfers obj's property values to UI''' + """setFields(obj) ... transfers obj's property values to UI""" self.setupToolController(obj, self.form.toolController) self.setupCoolant(obj, self.form.coolantController) self.selectInComboBox(obj.BoundBox, self.form.boundBoxSelect) @@ -126,20 +135,36 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): and the UI panel QComboBox list. The original method is commented out below. """ - idx = self.propEnums['CutPattern'].index(obj.CutPattern) + idx = self.propEnums["CutPattern"].index(obj.CutPattern) self.form.cutPattern.setCurrentIndex(idx) - idx = self.propEnums['ProfileEdges'].index(obj.ProfileEdges) + idx = self.propEnums["ProfileEdges"].index(obj.ProfileEdges) self.form.profileEdges.setCurrentIndex(idx) # self.selectInComboBox(obj.CutPattern, self.form.cutPattern) # self.selectInComboBox(obj.ProfileEdges, self.form.profileEdges) self.form.avoidLastX_Faces.setValue(obj.AvoidLastX_Faces) - self.form.boundBoxExtraOffsetX.setText(FreeCAD.Units.Quantity(obj.DropCutterExtraOffset.x, FreeCAD.Units.Length).UserString) - self.form.boundBoxExtraOffsetY.setText(FreeCAD.Units.Quantity(obj.DropCutterExtraOffset.y, FreeCAD.Units.Length).UserString) + self.form.boundBoxExtraOffsetX.setText( + FreeCAD.Units.Quantity( + obj.DropCutterExtraOffset.x, FreeCAD.Units.Length + ).UserString + ) + self.form.boundBoxExtraOffsetY.setText( + FreeCAD.Units.Quantity( + obj.DropCutterExtraOffset.y, FreeCAD.Units.Length + ).UserString + ) self.selectInComboBox(obj.DropCutterDir, self.form.dropCutterDirSelect) - self.form.depthOffset.setText(FreeCAD.Units.Quantity(obj.DepthOffset.Value, FreeCAD.Units.Length).UserString) + self.form.depthOffset.setText( + FreeCAD.Units.Quantity( + obj.DepthOffset.Value, FreeCAD.Units.Length + ).UserString + ) self.form.stepOver.setValue(obj.StepOver) - self.form.sampleInterval.setText(FreeCAD.Units.Quantity(obj.SampleInterval.Value, FreeCAD.Units.Length).UserString) + self.form.sampleInterval.setText( + FreeCAD.Units.Quantity( + obj.SampleInterval.Value, FreeCAD.Units.Length + ).UserString + ) if obj.UseStartPoint: self.form.useStartPoint.setCheckState(QtCore.Qt.Checked) @@ -164,7 +189,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.updateVisibility() def getSignalsForUpdate(self, obj): - '''getSignalsForUpdate(obj) ... return list of signals for updating obj''' + """getSignalsForUpdate(obj) ... return list of signals for updating obj""" signals = [] signals.append(self.form.toolController.currentIndexChanged) signals.append(self.form.coolantController.currentIndexChanged) @@ -188,12 +213,12 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): return signals def updateVisibility(self, sentObj=None): - '''updateVisibility(sentObj=None)... Updates visibility of Tasks panel objects.''' - if self.form.scanType.currentText() == 'Planar': + """updateVisibility(sentObj=None)... Updates visibility of Tasks panel objects.""" + if self.form.scanType.currentText() == "Planar": self.form.cutPattern.show() self.form.cutPattern_label.show() self.form.optimizeStepOverTransitions.show() - if hasattr(self.form, 'profileEdges'): + if hasattr(self.form, "profileEdges"): self.form.profileEdges.show() self.form.profileEdges_label.show() self.form.avoidLastX_Faces.show() @@ -204,11 +229,11 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.form.boundBoxExtraOffset_label.hide() self.form.dropCutterDirSelect.hide() self.form.dropCutterDirSelect_label.hide() - elif self.form.scanType.currentText() == 'Rotational': + elif self.form.scanType.currentText() == "Rotational": self.form.cutPattern.hide() self.form.cutPattern_label.hide() self.form.optimizeStepOverTransitions.hide() - if hasattr(self.form, 'profileEdges'): + if hasattr(self.form, "profileEdges"): self.form.profileEdges.hide() self.form.profileEdges_label.hide() self.form.avoidLastX_Faces.hide() @@ -224,12 +249,16 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.form.scanType.currentIndexChanged.connect(self.updateVisibility) -Command = PathOpGui.SetupOperation('Surface', - PathSurface.Create, - TaskPanelOpPage, - 'Path_3DSurface', - QtCore.QT_TRANSLATE_NOOP("Path_Surface", "3D Surface"), - QtCore.QT_TRANSLATE_NOOP("Path_Surface", "Create a 3D Surface Operation from a model"), - PathSurface.SetupProperties) +Command = PathOpGui.SetupOperation( + "Surface", + PathSurface.Create, + TaskPanelOpPage, + "Path_3DSurface", + QtCore.QT_TRANSLATE_NOOP("Path_Surface", "3D Surface"), + QtCore.QT_TRANSLATE_NOOP( + "Path_Surface", "Create a 3D Surface Operation from a model" + ), + PathSurface.SetupProperties, +) FreeCAD.Console.PrintLog("Loading PathSurfaceGui... done\n") diff --git a/src/Mod/Path/PathScripts/PathSurfaceSupport.py b/src/Mod/Path/PathScripts/PathSurfaceSupport.py index d9a7c6fb94..9369faa26d 100644 --- a/src/Mod/Path/PathScripts/PathSurfaceSupport.py +++ b/src/Mod/Path/PathScripts/PathSurfaceSupport.py @@ -38,8 +38,9 @@ import math # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader + # MeshPart = LazyLoader('MeshPart', globals(), 'MeshPart') -Part = LazyLoader('Part', globals(), 'Part') +Part = LazyLoader("Part", globals(), "Part") PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) @@ -52,22 +53,22 @@ def translate(context, text, disambig=None): class PathGeometryGenerator: - '''Creates a path geometry shape from an assigned pattern for conversion to tool paths. + """Creates a path geometry shape from an assigned pattern for conversion to tool paths. PathGeometryGenerator(obj, shape, pattern) `obj` is the operation object, `shape` is the horizontal planar shape object, and `pattern` is the name of the geometric pattern to apply. First, call the getCenterOfPattern() method for the CenterOfMass for patterns allowing a custom center. - Next, call the generatePathGeometry() method to request the path geometry shape.''' + Next, call the generatePathGeometry() method to request the path geometry shape.""" # Register valid patterns here by name # Create a corresponding processing method below. Precede the name with an underscore(_) - patterns = ('Circular', 'CircularZigZag', 'Line', 'Offset', 'Spiral', 'ZigZag') + patterns = ("Circular", "CircularZigZag", "Line", "Offset", "Spiral", "ZigZag") def __init__(self, obj, shape, pattern): - '''__init__(obj, shape, pattern)... Instantiate PathGeometryGenerator class. - Required arguments are the operation object, horizontal planar shape, and pattern name.''' + """__init__(obj, shape, pattern)... Instantiate PathGeometryGenerator class. + Required arguments are the operation object, horizontal planar shape, and pattern name.""" self.debugObjectsGroup = False - self.pattern = 'None' + self.pattern = "None" self.shape = None self.pathGeometry = None self.rawGeoList = None @@ -85,15 +86,16 @@ class PathGeometryGenerator: # validate requested pattern if pattern in self.patterns: - if hasattr(self, '_' + pattern): + if hasattr(self, "_" + pattern): self.pattern = pattern if shape.BoundBox.ZMin != 0.0: shape.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - shape.BoundBox.ZMin)) if shape.BoundBox.ZLength > 1.0e-8: - msg = translate('PathSurfaceSupport', - 'Shape appears to not be horizontal planar.') - msg += ' ZMax == {} mm.\n'.format(shape.BoundBox.ZMax) + msg = translate( + "PathSurfaceSupport", "Shape appears to not be horizontal planar." + ) + msg += " ZMax == {} mm.\n".format(shape.BoundBox.ZMax) FreeCAD.Console.PrintWarning(msg) else: self.shape = shape @@ -101,8 +103,8 @@ class PathGeometryGenerator: def _prepareConstants(self): # Compute weighted center of mass of all faces combined - if self.pattern in ['Circular', 'CircularZigZag', 'Spiral']: - if self.obj.PatternCenterAt == 'CenterOfMass': + if self.pattern in ["Circular", "CircularZigZag", "Spiral"]: + if self.obj.PatternCenterAt == "CenterOfMass": fCnt = 0 totArea = 0.0 zeroCOM = FreeCAD.Vector(0.0, 0.0, 0.0) @@ -111,12 +113,20 @@ class PathGeometryGenerator: areaF = F.Area totArea += areaF fCnt += 1 - zeroCOM = zeroCOM.add(FreeCAD.Vector(comF.x, comF.y, 0.0).multiply(areaF)) + zeroCOM = zeroCOM.add( + FreeCAD.Vector(comF.x, comF.y, 0.0).multiply(areaF) + ) if fCnt == 0: - msg = translate('PathSurfaceSupport', - 'Cannot calculate the Center Of Mass.') - msg += ' ' + translate('PathSurfaceSupport', - 'Using Center of Boundbox instead.') + '\n' + msg = translate( + "PathSurfaceSupport", "Cannot calculate the Center Of Mass." + ) + msg += ( + " " + + translate( + "PathSurfaceSupport", "Using Center of Boundbox instead." + ) + + "\n" + ) FreeCAD.Console.PrintError(msg) bbC = self.shape.BoundBox.Center zeroCOM = FreeCAD.Vector(bbC.x, bbC.y, 0.0) @@ -133,33 +143,39 @@ class PathGeometryGenerator: # get X, Y, Z spans; Compute center of rotation self.deltaX = self.shape.BoundBox.XLength self.deltaY = self.shape.BoundBox.YLength - self.deltaC = self.shape.BoundBox.DiagonalLength # math.sqrt(self.deltaX**2 + self.deltaY**2) - lineLen = self.deltaC + (2.0 * self.toolDiam) # Line length to span boundbox diag with 2x cutter diameter extra on each end + self.deltaC = ( + self.shape.BoundBox.DiagonalLength + ) # math.sqrt(self.deltaX**2 + self.deltaY**2) + lineLen = self.deltaC + ( + 2.0 * self.toolDiam + ) # Line length to span boundbox diag with 2x cutter diameter extra on each end self.halfDiag = math.ceil(lineLen / 2.0) - cutPasses = math.ceil(lineLen / self.cutOut) + 1 # Number of lines(passes) required to cover boundbox diagonal + cutPasses = ( + math.ceil(lineLen / self.cutOut) + 1 + ) # Number of lines(passes) required to cover boundbox diagonal self.halfPasses = math.ceil(cutPasses / 2.0) # Public methods def setDebugObjectsGroup(self, tmpGrpObject): - '''setDebugObjectsGroup(tmpGrpObject)... - Pass the temporary object group to show temporary construction objects''' + """setDebugObjectsGroup(tmpGrpObject)... + Pass the temporary object group to show temporary construction objects""" self.debugObjectsGroup = tmpGrpObject def getCenterOfPattern(self): - '''getCenterOfPattern()... - Returns the Center Of Mass for the current class instance.''' + """getCenterOfPattern()... + Returns the Center Of Mass for the current class instance.""" return self.centerOfPattern def generatePathGeometry(self): - '''generatePathGeometry()... - Call this function to obtain the path geometry shape, generated by this class.''' - if self.pattern == 'None': + """generatePathGeometry()... + Call this function to obtain the path geometry shape, generated by this class.""" + if self.pattern == "None": return False if self.shape is None: return False - cmd = 'self._' + self.pattern + '()' + cmd = "self._" + self.pattern + "()" exec(cmd) if self.obj.CutPatternReversed is True: @@ -169,26 +185,30 @@ class PathGeometryGenerator: geomShape = Part.makeCompound(self.rawGeoList) # Position and rotate the Line and ZigZag geometry - if self.pattern in ['Line', 'ZigZag']: + if self.pattern in ["Line", "ZigZag"]: if self.obj.CutPatternAngle != 0.0: - geomShape.Placement.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), self.obj.CutPatternAngle) + geomShape.Placement.Rotation = FreeCAD.Rotation( + FreeCAD.Vector(0, 0, 1), self.obj.CutPatternAngle + ) bbC = self.shape.BoundBox.Center - geomShape.Placement.Base = FreeCAD.Vector(bbC.x, bbC.y, 0.0 - geomShape.BoundBox.ZMin) + geomShape.Placement.Base = FreeCAD.Vector( + bbC.x, bbC.y, 0.0 - geomShape.BoundBox.ZMin + ) if self.debugObjectsGroup: - F = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpGeometrySet') + F = FreeCAD.ActiveDocument.addObject("Part::Feature", "tmpGeometrySet") F.Shape = geomShape F.purgeTouched() self.debugObjectsGroup.addObject(F) - if self.pattern == 'Offset': + if self.pattern == "Offset": return geomShape # Identify intersection of cross-section face and lineset cmnShape = self.shape.common(geomShape) if self.debugObjectsGroup: - F = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpPathGeometry') + F = FreeCAD.ActiveDocument.addObject("Part::Feature", "tmpPathGeometry") F.Shape = cmnShape F.purgeTouched() self.debugObjectsGroup.addObject(F) @@ -206,14 +226,14 @@ class PathGeometryGenerator: if minRad < minRadSI: minRad = minRadSI - PathLog.debug(' -centerOfPattern: {}'.format(self.centerOfPattern)) + PathLog.debug(" -centerOfPattern: {}".format(self.centerOfPattern)) # Make small center circle to start pattern if self.obj.StepOver > 50: circle = Part.makeCircle(minRad, self.centerOfPattern) GeoSet.append(circle) for lc in range(1, radialPasses + 1): - rad = (lc * self.cutOut) + rad = lc * self.cutOut if rad >= minRad: circle = Part.makeCircle(rad, self.centerOfPattern) GeoSet.append(circle) @@ -225,7 +245,9 @@ class PathGeometryGenerator: def _Line(self): GeoSet = list() - centRot = FreeCAD.Vector(0.0, 0.0, 0.0) # Bottom left corner of face/selection/model + centRot = FreeCAD.Vector( + 0.0, 0.0, 0.0 + ) # Bottom left corner of face/selection/model # Create end points for set of lines to intersect with cross-section face pntTuples = list() @@ -263,12 +285,14 @@ class PathGeometryGenerator: # Set tool properties and calculate cutout cutOut = self.cutOut / twoPi - segLen = self.obj.SampleInterval.Value # CutterDiameter / 10.0 # SampleInterval.Value + segLen = ( + self.obj.SampleInterval.Value + ) # CutterDiameter / 10.0 # SampleInterval.Value stepAng = segLen / ((loopCnt + 1) * self.cutOut) # math.pi / 18.0 # 10 degrees stopRadians = maxDist / cutOut if self.obj.CutPatternReversed: - if self.obj.CutMode == 'Conventional': + if self.obj.CutMode == "Conventional": getPoint = self._makeOppSpiralPnt else: getPoint = self._makeRegSpiralPnt @@ -276,13 +300,17 @@ class PathGeometryGenerator: while draw: radAng = sumRadians + stepAng p1 = lastPoint - p2 = getPoint(move, cutOut, radAng) # cutOut is 'b' in the equation r = b * radAng + p2 = getPoint( + move, cutOut, radAng + ) # cutOut is 'b' in the equation r = b * radAng sumRadians += stepAng # Increment sumRadians loopRadians += stepAng # Increment loopRadians if loopRadians > twoPi: loopCnt += 1 loopRadians -= twoPi - stepAng = segLen / ((loopCnt + 1) * self.cutOut) # adjust stepAng with each loop/cycle + stepAng = segLen / ( + (loopCnt + 1) * self.cutOut + ) # adjust stepAng with each loop/cycle segCnt += 1 lastPoint = p2 if sumRadians > stopRadians: @@ -293,7 +321,7 @@ class PathGeometryGenerator: # Ewhile SEGS.reverse() else: - if self.obj.CutMode == 'Climb': + if self.obj.CutMode == "Climb": getPoint = self._makeOppSpiralPnt else: getPoint = self._makeRegSpiralPnt @@ -301,13 +329,17 @@ class PathGeometryGenerator: while draw: radAng = sumRadians + stepAng p1 = lastPoint - p2 = getPoint(move, cutOut, radAng) # cutOut is 'b' in the equation r = b * radAng + p2 = getPoint( + move, cutOut, radAng + ) # cutOut is 'b' in the equation r = b * radAng sumRadians += stepAng # Increment sumRadians loopRadians += stepAng # Increment loopRadians if loopRadians > twoPi: loopCnt += 1 loopRadians -= twoPi - stepAng = segLen / ((loopCnt + 1) * self.cutOut) # adjust stepAng with each loop/cycle + stepAng = segLen / ( + (loopCnt + 1) * self.cutOut + ) # adjust stepAng with each loop/cycle segCnt += 1 lastPoint = p2 if sumRadians > stopRadians: @@ -329,18 +361,22 @@ class PathGeometryGenerator: def _getPatternCenter(self): centerAt = self.obj.PatternCenterAt - if centerAt == 'CenterOfMass': + if centerAt == "CenterOfMass": cntrPnt = FreeCAD.Vector(self.centerOfMass.x, self.centerOfMass.y, 0.0) - elif centerAt == 'CenterOfBoundBox': + elif centerAt == "CenterOfBoundBox": cent = self.shape.BoundBox.Center cntrPnt = FreeCAD.Vector(cent.x, cent.y, 0.0) - elif centerAt == 'XminYmin': - cntrPnt = FreeCAD.Vector(self.shape.BoundBox.XMin, self.shape.BoundBox.YMin, 0.0) - elif centerAt == 'Custom': - cntrPnt = FreeCAD.Vector(self.obj.PatternCenterCustom.x, self.obj.PatternCenterCustom.y, 0.0) + elif centerAt == "XminYmin": + cntrPnt = FreeCAD.Vector( + self.shape.BoundBox.XMin, self.shape.BoundBox.YMin, 0.0 + ) + elif centerAt == "Custom": + cntrPnt = FreeCAD.Vector( + self.obj.PatternCenterCustom.x, self.obj.PatternCenterCustom.y, 0.0 + ) # Update centerOfPattern point - if centerAt != 'Custom': + if centerAt != "Custom": self.obj.PatternCenterCustom = cntrPnt self.centerOfPattern = cntrPnt @@ -349,7 +385,7 @@ class PathGeometryGenerator: def _getRadialPasses(self): # recalculate number of passes, if need be radialPasses = self.halfPasses - if self.obj.PatternCenterAt != 'CenterOfBoundBox': + if self.obj.PatternCenterAt != "CenterOfBoundBox": # make 4 corners of boundbox in XY plane, find which is greatest distance to new circular center EBB = self.shape.BoundBox CORNERS = [ @@ -363,8 +399,12 @@ class PathGeometryGenerator: dist = CORNERS[c].sub(self.centerOfPattern).Length if dist > dMax: dMax = dist - diag = dMax + (2.0 * self.toolDiam) # Line length to span boundbox diag with 2x cutter diameter extra on each end - radialPasses = math.ceil(diag / self.cutOut) + 1 # Number of lines(passes) required to cover boundbox diagonal + diag = dMax + ( + 2.0 * self.toolDiam + ) # Line length to span boundbox diag with 2x cutter diameter extra on each end + radialPasses = ( + math.ceil(diag / self.cutOut) + 1 + ) # Number of lines(passes) required to cover boundbox diagonal return radialPasses @@ -379,7 +419,7 @@ class PathGeometryGenerator: return FreeCAD.Vector(-1 * x, y, 0.0).add(move) def _extractOffsetFaces(self): - PathLog.debug('_extractOffsetFaces()') + PathLog.debug("_extractOffsetFaces()") wires = list() shape = self.shape offset = 0.0 # Start right at the edge of cut area @@ -409,7 +449,7 @@ class PathGeometryGenerator: if direction == 0: first_face_wire = offsetArea.Faces[0].Wires[0] direction = _get_direction(first_face_wire) - if self.obj.CutMode == 'Climb': + if self.obj.CutMode == "Climb": if direction == 1: direction = -1 else: @@ -417,7 +457,7 @@ class PathGeometryGenerator: direction = 1 # Correct cut direction for `Conventional` cuts - if self.obj.CutMode == 'Conventional': + if self.obj.CutMode == "Conventional": if loop_cnt == 1: direction = direction * -1 @@ -442,6 +482,8 @@ class PathGeometryGenerator: offset -= self.cutOut loop_cnt += 1 return wires + + # Eclass @@ -460,15 +502,21 @@ class ProcessSelectedFaces: self.module = None self.radius = None self.depthParams = None - self.msgNoFaces = translate('PathSurfaceSupport', - 'Face selection is unavailable for Rotational scans.') + '\n' - self.msgNoFaces += ' ' + translate('PathSurfaceSupport', - 'Ignoring selected faces.') + '\n' + self.msgNoFaces = ( + translate( + "PathSurfaceSupport", + "Face selection is unavailable for Rotational scans.", + ) + + "\n" + ) + self.msgNoFaces += ( + " " + translate("PathSurfaceSupport", "Ignoring selected faces.") + "\n" + ) self.JOB = JOB self.obj = obj - self.profileEdges = 'None' + self.profileEdges = "None" - if hasattr(obj, 'ProfileEdges'): + if hasattr(obj, "ProfileEdges"): self.profileEdges = obj.ProfileEdges # Setup STL, model type, and bound box containers for each model in Job @@ -483,7 +531,7 @@ class ProcessSelectedFaces: if self.obj.Base: if len(self.obj.Base) > 0: self.checkBase = True - if self.obj.ScanType == 'Rotational': + if self.obj.ScanType == "Rotational": self.checkBase = False FreeCAD.Console.PrintWarning(self.msgNoFaces) @@ -491,7 +539,7 @@ class ProcessSelectedFaces: if self.obj.Base: if len(self.obj.Base) > 0: self.checkBase = True - if self.obj.Algorithm in ['OCL Dropcutter', 'Experimental']: + if self.obj.Algorithm in ["OCL Dropcutter", "Experimental"]: self.checkBase = False FreeCAD.Console.PrintWarning(self.msgNoFaces) @@ -501,7 +549,7 @@ class ProcessSelectedFaces: self.showDebugObjects = val def preProcessModel(self, module): - PathLog.debug('preProcessModel()') + PathLog.debug("preProcessModel()") if not self._isReady(module): return False @@ -523,15 +571,19 @@ class ProcessSelectedFaces: # The user has selected subobjects from the base. Pre-Process each. if self.checkBase: - PathLog.debug(' -obj.Base exists. Pre-processing for selected faces.') + PathLog.debug(" -obj.Base exists. Pre-processing for selected faces.") - (hasFace, hasVoid) = self._identifyFacesAndVoids(FACES, VOIDS) # modifies FACES and VOIDS + (hasFace, hasVoid) = self._identifyFacesAndVoids( + FACES, VOIDS + ) # modifies FACES and VOIDS hasGeometry = True if hasFace or hasVoid else False # Cycle through each base model, processing faces for each for m in range(0, lenGRP): base = GRP[m] - (mFS, mVS, mPS) = self._preProcessFacesAndVoids(base, FACES[m], VOIDS[m]) + (mFS, mVS, mPS) = self._preProcessFacesAndVoids( + base, FACES[m], VOIDS[m] + ) fShapes[m] = mFS vShapes[m] = mVS self.profileShapes[m] = mPS @@ -540,55 +592,64 @@ class ProcessSelectedFaces: if hasGeometry and not proceed: return False else: - PathLog.debug(' -No obj.Base data.') + PathLog.debug(" -No obj.Base data.") for m in range(0, lenGRP): self.modelSTLs[m] = True # Process each model base, as a whole, as needed for m in range(0, lenGRP): if self.modelSTLs[m] and not fShapes[m]: - PathLog.debug(' -Pre-processing {} as a whole.'.format(GRP[m].Label)) - if self.obj.BoundBox == 'BaseBoundBox': + PathLog.debug(" -Pre-processing {} as a whole.".format(GRP[m].Label)) + if self.obj.BoundBox == "BaseBoundBox": base = GRP[m] - elif self.obj.BoundBox == 'Stock': + elif self.obj.BoundBox == "Stock": base = self.JOB.Stock pPEB = self._preProcessEntireBase(base, m) if pPEB is False: - msg = translate('PathSurfaceSupport', - 'Failed to pre-process base as a whole.') + '\n' + msg = ( + translate( + "PathSurfaceSupport", + "Failed to pre-process base as a whole.", + ) + + "\n" + ) FreeCAD.Console.PrintError(msg) else: (fcShp, prflShp) = pPEB if fcShp: if fcShp is True: - PathLog.debug(' -fcShp is True.') + PathLog.debug(" -fcShp is True.") fShapes[m] = True else: fShapes[m] = [fcShp] if prflShp: if fcShp: - PathLog.debug('vShapes[{}]: {}'.format(m, vShapes[m])) + PathLog.debug("vShapes[{}]: {}".format(m, vShapes[m])) if vShapes[m]: - PathLog.debug(' -Cutting void from base profile shape.') + PathLog.debug(" -Cutting void from base profile shape.") adjPS = prflShp.cut(vShapes[m][0]) self.profileShapes[m] = [adjPS] else: - PathLog.debug(' -vShapes[m] is False.') + PathLog.debug(" -vShapes[m] is False.") self.profileShapes[m] = [prflShp] else: - PathLog.debug(' -Saving base profile shape.') + PathLog.debug(" -Saving base profile shape.") self.profileShapes[m] = [prflShp] - PathLog.debug('self.profileShapes[{}]: {}'.format(m, self.profileShapes[m])) + PathLog.debug( + "self.profileShapes[{}]: {}".format( + m, self.profileShapes[m] + ) + ) # Efor return (fShapes, vShapes) # private class methods def _isReady(self, module): - '''_isReady(module)... Internal method. - Checks if required attributes are available for processing obj.Base (the Base Geometry).''' - PathLog.debug('ProcessSelectedFaces _isReady({})'.format(module)) + """_isReady(module)... Internal method. + Checks if required attributes are available for processing obj.Base (the Base Geometry).""" + PathLog.debug("ProcessSelectedFaces _isReady({})".format(module)) if hasattr(self, module): self.module = module modMethod = getattr(self, module) # gets the attribute only @@ -598,11 +659,11 @@ class ProcessSelectedFaces: return False if not self.radius: - PathLog.error('PSF._isReady() no cutter radius available.') + PathLog.error("PSF._isReady() no cutter radius available.") return False if not self.depthParams: - PathLog.error('PSF._isReady() no depth params available.') + PathLog.error("PSF._isReady() no depth params available.") return False return True @@ -638,13 +699,13 @@ class ProcessSelectedFaces: if F[m] is False: F[m] = list() F[m].append((shape, faceIdx)) - PathLog.debug('.. Cutting {}'.format(sub)) + PathLog.debug(".. Cutting {}".format(sub)) hasFace = True else: if V[m] is False: V[m] = list() V[m].append((shape, faceIdx)) - PathLog.debug('.. Avoiding {}'.format(sub)) + PathLog.debug(".. Avoiding {}".format(sub)) hasVoid = True return (hasFace, hasVoid) @@ -656,34 +717,32 @@ class ProcessSelectedFaces: if FCS: isHole = False - if self.obj.HandleMultipleFeatures == 'Collectively': + if self.obj.HandleMultipleFeatures == "Collectively": cont = True - PathLog.debug( - 'Attempting to get cross-section of collective faces.') + PathLog.debug("Attempting to get cross-section of collective faces.") outFCS, ifL = self.findUnifiedRegions(FCS) if self.obj.InternalFeaturesCut and ifL: ifL = list() # clear avoid shape list if len(outFCS) == 0: msg = translate( - 'PathSurfaceSupport', - 'Cannot process selected faces. Check horizontal ' - 'surface exposure.') - FreeCAD.Console.PrintError(msg + '\n') + "PathSurfaceSupport", + "Cannot process selected faces. Check horizontal " + "surface exposure.", + ) + FreeCAD.Console.PrintError(msg + "\n") cont = False else: cfsL = Part.makeCompound(outFCS) # Handle profile edges request - if cont and self.profileEdges != 'None': - PathLog.debug('.. include Profile Edge') + if cont and self.profileEdges != "None": + PathLog.debug(".. include Profile Edge") ofstVal = self._calculateOffsetValue(isHole) - psOfst = PathUtils.getOffsetArea(cfsL, - ofstVal, - plane=self.wpc) + psOfst = PathUtils.getOffsetArea(cfsL, ofstVal, plane=self.wpc) if psOfst: mPS = [psOfst] - if self.profileEdges == 'Only': + if self.profileEdges == "Only": mFS = True cont = False else: @@ -691,17 +750,22 @@ class ProcessSelectedFaces: if cont: if self.showDebugObjects: - T = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpCollectiveShape') + T = FreeCAD.ActiveDocument.addObject( + "Part::Feature", "tmpCollectiveShape" + ) T.Shape = cfsL T.purgeTouched() self.tempGroup.addObject(T) ofstVal = self._calculateOffsetValue(isHole) - faceOfstShp = PathUtils.getOffsetArea( - cfsL, ofstVal, plane=self.wpc) + faceOfstShp = PathUtils.getOffsetArea(cfsL, ofstVal, plane=self.wpc) if not faceOfstShp: - msg = translate('PathSurfaceSupport', - 'Failed to create offset face.') + '\n' + msg = ( + translate( + "PathSurfaceSupport", "Failed to create offset face." + ) + + "\n" + ) FreeCAD.Console.PrintError(msg) cont = False @@ -709,26 +773,29 @@ class ProcessSelectedFaces: lenIfL = len(ifL) if not self.obj.InternalFeaturesCut: if lenIfL == 0: - PathLog.debug(' -No internal features saved.') + PathLog.debug(" -No internal features saved.") else: if lenIfL == 1: casL = ifL[0] else: casL = Part.makeCompound(ifL) if self.showDebugObjects: - C = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpCompoundIntFeat') + C = FreeCAD.ActiveDocument.addObject( + "Part::Feature", "tmpCompoundIntFeat" + ) C.Shape = casL C.purgeTouched() self.tempGroup.addObject(C) ofstVal = self._calculateOffsetValue(isHole=True) intOfstShp = PathUtils.getOffsetArea( - casL, ofstVal, plane=self.wpc) + casL, ofstVal, plane=self.wpc + ) mIFS.append(intOfstShp) mFS = [faceOfstShp] # Eif - elif self.obj.HandleMultipleFeatures == 'Individually': + elif self.obj.HandleMultipleFeatures == "Individually": for (fcshp, fcIdx) in FCS: cont = True fNum = fcIdx + 1 @@ -741,17 +808,20 @@ class ProcessSelectedFaces: ifL = list() # avoid shape list if outerFace: - PathLog.debug('Attempting to create offset face of Face{}'.format(fNum)) + PathLog.debug( + "Attempting to create offset face of Face{}".format(fNum) + ) - if self.profileEdges != 'None': + if self.profileEdges != "None": ofstVal = self._calculateOffsetValue(isHole) psOfst = PathUtils.getOffsetArea( - outerFace, ofstVal, plane=self.wpc) + outerFace, ofstVal, plane=self.wpc + ) if psOfst: if mPS is False: mPS = list() mPS.append(psOfst) - if self.profileEdges == 'Only': + if self.profileEdges == "Only": if mFS is False: mFS = list() mFS.append(True) @@ -762,7 +832,8 @@ class ProcessSelectedFaces: if cont: ofstVal = self._calculateOffsetValue(isHole) faceOfstShp = PathUtils.getOffsetArea( - outerFace, ofstVal, plane=self.wpc) + outerFace, ofstVal, plane=self.wpc + ) lenIfl = len(ifL) if self.obj.InternalFeaturesCut is False and lenIfl > 0: @@ -773,7 +844,8 @@ class ProcessSelectedFaces: ofstVal = self._calculateOffsetValue(isHole=True) intOfstShp = PathUtils.getOffsetArea( - casL, ofstVal, plane=self.wpc) + casL, ofstVal, plane=self.wpc + ) mIFS.append(intOfstShp) # faceOfstShp = faceOfstShp.cut(intOfstShp) @@ -792,7 +864,7 @@ class ProcessSelectedFaces: mVS.append(ifs) if VDS: - PathLog.debug('Processing avoid faces.') + PathLog.debug("Processing avoid faces.") cont = True isHole = False @@ -810,25 +882,29 @@ class ProcessSelectedFaces: avoid = Part.makeCompound(outFCS) if self.showDebugObjects: - P = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpVoidEnvelope') + P = FreeCAD.ActiveDocument.addObject( + "Part::Feature", "tmpVoidEnvelope" + ) P.Shape = avoid P.purgeTouched() self.tempGroup.addObject(P) if cont: if self.showDebugObjects: - P = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpVoidCompound') + P = FreeCAD.ActiveDocument.addObject( + "Part::Feature", "tmpVoidCompound" + ) P.Shape = avoid P.purgeTouched() self.tempGroup.addObject(P) ofstVal = self._calculateOffsetValue(isHole, isVoid=True) - avdOfstShp = PathUtils.getOffsetArea(avoid, - ofstVal, - plane=self.wpc) + avdOfstShp = PathUtils.getOffsetArea(avoid, ofstVal, plane=self.wpc) if avdOfstShp is False: - msg = translate('PathSurfaceSupport', - 'Failed to create collective offset avoid face.') - FreeCAD.Console.PrintError(msg + '\n') + msg = translate( + "PathSurfaceSupport", + "Failed to create collective offset avoid face.", + ) + FreeCAD.Console.PrintError(msg + "\n") cont = False if cont: @@ -840,12 +916,15 @@ class ProcessSelectedFaces: else: ifc = intFEAT[0] ofstVal = self._calculateOffsetValue(isHole=True) - ifOfstShp = PathUtils.getOffsetArea(ifc, - ofstVal, - plane=self.wpc) + ifOfstShp = PathUtils.getOffsetArea(ifc, ofstVal, plane=self.wpc) if ifOfstShp is False: - msg = translate('PathSurfaceSupport', - 'Failed to create collective offset avoid internal features.') + '\n' + msg = ( + translate( + "PathSurfaceSupport", + "Failed to create collective offset avoid internal features.", + ) + + "\n" + ) FreeCAD.Console.PrintError(msg) else: avdShp = avdOfstShp.cut(ifOfstShp) @@ -864,13 +943,17 @@ class ProcessSelectedFaces: # baseEnv = PathUtils.getEnvelope(base.Shape, subshape=None, depthparams=self.depthParams) try: - baseEnv = PathUtils.getEnvelope(partshape=base.Shape, subshape=None, depthparams=self.depthParams) # Produces .Shape + baseEnv = PathUtils.getEnvelope( + partshape=base.Shape, subshape=None, depthparams=self.depthParams + ) # Produces .Shape except Exception as ee: PathLog.error(str(ee)) shell = base.Shape.Shells[0] solid = Part.makeSolid(shell) try: - baseEnv = PathUtils.getEnvelope(partshape=solid, subshape=None, depthparams=self.depthParams) # Produces .Shape + baseEnv = PathUtils.getEnvelope( + partshape=solid, subshape=None, depthparams=self.depthParams + ) # Produces .Shape except Exception as eee: PathLog.error(str(eee)) cont = False @@ -882,17 +965,15 @@ class ProcessSelectedFaces: if csFaceShape is False: csFaceShape = getSliceFromEnvelope(baseEnv) if csFaceShape is False: - PathLog.debug('Failed to slice baseEnv shape.') + PathLog.debug("Failed to slice baseEnv shape.") cont = False - if cont and self.profileEdges != 'None': - PathLog.debug(' -Attempting profile geometry for model base.') + if cont and self.profileEdges != "None": + PathLog.debug(" -Attempting profile geometry for model base.") ofstVal = self._calculateOffsetValue(isHole) - psOfst = PathUtils.getOffsetArea(csFaceShape, - ofstVal, - plane=self.wpc) + psOfst = PathUtils.getOffsetArea(csFaceShape, ofstVal, plane=self.wpc) if psOfst: - if self.profileEdges == 'Only': + if self.profileEdges == "Only": return (True, psOfst) prflShp = psOfst else: @@ -900,24 +981,28 @@ class ProcessSelectedFaces: if cont: ofstVal = self._calculateOffsetValue(isHole) - faceOffsetShape = PathUtils.getOffsetArea(csFaceShape, ofstVal, - plane=self.wpc) + faceOffsetShape = PathUtils.getOffsetArea( + csFaceShape, ofstVal, plane=self.wpc + ) if faceOffsetShape is False: - PathLog.debug('getOffsetArea() failed for entire base.') + PathLog.debug("getOffsetArea() failed for entire base.") else: - faceOffsetShape.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - faceOffsetShape.BoundBox.ZMin)) + faceOffsetShape.translate( + FreeCAD.Vector(0.0, 0.0, 0.0 - faceOffsetShape.BoundBox.ZMin) + ) return (faceOffsetShape, prflShp) return False def _calculateOffsetValue(self, isHole, isVoid=False): - '''_calculateOffsetValue(self.obj, isHole, isVoid) ... internal function. - Calculate the offset for the Path.Area() function.''' + """_calculateOffsetValue(self.obj, isHole, isVoid) ... internal function. + Calculate the offset for the Path.Area() function.""" self.JOB = PathUtils.findParentJob(self.obj) # We need to offset by at least our linear tessellation deflection # (default GeometryTolerance / 4) to avoid false retracts at the # boundaries. - tolrnc = max(self.JOB.GeometryTolerance.Value / 10.0, - self.obj.LinearDeflection.Value) + tolrnc = max( + self.JOB.GeometryTolerance.Value / 10.0, self.obj.LinearDeflection.Value + ) if isVoid is False: if isHole is True: @@ -936,13 +1021,10 @@ class ProcessSelectedFaces: return offset - def findUnifiedRegions( - self, - shapeAndIndexTuples, - useAreaImplementation=True): + def findUnifiedRegions(self, shapeAndIndexTuples, useAreaImplementation=True): """Wrapper around area and wire based region unification implementations.""" - PathLog.debug('findUnifiedRegions()') + PathLog.debug("findUnifiedRegions()") # Allow merging of faces within the LinearDeflection tolerance. tolerance = self.obj.LinearDeflection.Value # Default: normal to Z=1 (XY plane), at Z=0 @@ -956,47 +1038,48 @@ class ProcessSelectedFaces: 0.0 - tolerance / 10, removeHoles=True, # Outline has holes filled in tolerance=tolerance, - plane=self.wpc) + plane=self.wpc, + ) projectionShape = PathUtils.getOffsetArea( shapes, # Make the projection very slightly larger tolerance / 10, removeHoles=False, # Projection has holes preserved tolerance=tolerance, - plane=self.wpc) + plane=self.wpc, + ) internalShape = outlineShape.cut(projectionShape) # Filter out tiny faces, usually artifacts around the perimeter of # the cut. - minArea = (10 * tolerance)**2 - internalFaces = [ - f for f in internalShape.Faces if f.Area > minArea - ] + minArea = (10 * tolerance) ** 2 + internalFaces = [f for f in internalShape.Faces if f.Area > minArea] if internalFaces: internalFaces = Part.makeCompound(internalFaces) return ([outlineShape], [internalFaces]) except Exception as e: PathLog.warning( - "getOffsetArea failed: {}; Using FindUnifiedRegions.".format( - e)) + "getOffsetArea failed: {}; Using FindUnifiedRegions.".format(e) + ) # Use face-unifying class FUR = FindUnifiedRegions(shapeAndIndexTuples, tolerance) if self.showDebugObjects: FUR.setTempGroup(self.tempGroup) return (FUR.getUnifiedRegions(), FUR.getInternalFeatures) + # Eclass # Functions for getting a shape envelope and cross-section def getExtrudedShape(wire): - PathLog.debug('getExtrudedShape()') + PathLog.debug("getExtrudedShape()") wBB = wire.BoundBox extFwd = math.floor(2.0 * wBB.ZLength) + 10.0 try: shell = wire.extrude(FreeCAD.Vector(0.0, 0.0, extFwd)) except Exception as ee: - PathLog.error(' -extrude wire failed: \n{}'.format(ee)) + PathLog.error(" -extrude wire failed: \n{}".format(ee)) return False SHP = Part.makeSolid(shell) @@ -1004,7 +1087,7 @@ def getExtrudedShape(wire): def getShapeSlice(shape): - PathLog.debug('getShapeSlice()') + PathLog.debug("getShapeSlice()") bb = shape.BoundBox mid = (bb.ZMin + bb.ZMax) / 2.0 @@ -1032,7 +1115,7 @@ def getShapeSlice(shape): if slcArea < midArea: for W in slcShp.Wires: if W.isClosed() is False: - PathLog.debug(' -wire.isClosed() is False') + PathLog.debug(" -wire.isClosed() is False") return False if len(slcShp.Wires) == 1: wire = slcShp.Wires[0] @@ -1053,8 +1136,9 @@ def getShapeSlice(shape): def getProjectedFace(tempGroup, wire): import Draft - PathLog.debug('getProjectedFace()') - F = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpProjectionWire') + + PathLog.debug("getProjectedFace()") + F = FreeCAD.ActiveDocument.addObject("Part::Feature", "tmpProjectionWire") F.Shape = wire F.purgeTouched() tempGroup.addObject(F) @@ -1076,7 +1160,7 @@ def getProjectedFace(tempGroup, wire): def getCrossSection(shape): - PathLog.debug('getCrossSection()') + PathLog.debug("getCrossSection()") wires = list() bb = shape.BoundBox mid = (bb.ZMin + bb.ZMax) / 2.0 @@ -1089,19 +1173,19 @@ def getCrossSection(shape): comp.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - comp.BoundBox.ZMin)) csWire = comp.Wires[0] if csWire.isClosed() is False: - PathLog.debug(' -comp.Wires[0] is not closed') + PathLog.debug(" -comp.Wires[0] is not closed") return False CS = Part.Face(csWire) CS.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - CS.BoundBox.ZMin)) return CS else: - PathLog.debug(' -No wires from .slice() method') + PathLog.debug(" -No wires from .slice() method") return False def getShapeEnvelope(shape): - PathLog.debug('getShapeEnvelope()') + PathLog.debug("getShapeEnvelope()") wBB = shape.BoundBox extFwd = wBB.ZLength + 10.0 @@ -1111,16 +1195,18 @@ def getShapeEnvelope(shape): dep_par = PathUtils.depth_params(maxz + 5.0, maxz + 3.0, maxz, stpDwn, 0.0, minz) try: - env = PathUtils.getEnvelope(partshape=shape, depthparams=dep_par) # Produces .Shape + env = PathUtils.getEnvelope( + partshape=shape, depthparams=dep_par + ) # Produces .Shape except Exception as ee: - FreeCAD.Console.PrintError('PathUtils.getEnvelope() failed.\n' + str(ee) + '\n') + FreeCAD.Console.PrintError("PathUtils.getEnvelope() failed.\n" + str(ee) + "\n") return False else: return env def getSliceFromEnvelope(env): - PathLog.debug('getSliceFromEnvelope()') + PathLog.debug("getSliceFromEnvelope()") eBB = env.BoundBox extFwd = eBB.ZLength + 10.0 maxz = eBB.ZMin + extFwd @@ -1137,12 +1223,10 @@ def getSliceFromEnvelope(env): return tf - - def _prepareModelSTLs(self, JOB, obj, m, ocl): """Tessellate model shapes or copy existing meshes into ocl.STLSurf objects""" - PathLog.debug('_prepareModelSTLs()') + PathLog.debug("_prepareModelSTLs()") if self.modelSTLs[m] is True: model = JOB.Model.Group[m] if self.modelSTLs[m] is True: @@ -1150,11 +1234,11 @@ def _prepareModelSTLs(self, JOB, obj, m, ocl): def _makeSafeSTL(self, JOB, obj, mdlIdx, faceShapes, voidShapes, ocl): - '''_makeSafeSTL(JOB, obj, mdlIdx, faceShapes, voidShapes)... + """_makeSafeSTL(JOB, obj, mdlIdx, faceShapes, voidShapes)... Creates and OCL.stl object with combined data with waste stock, model, and avoided faces. Travel lines can be checked against this - STL object to determine minimum travel height to clear stock and model.''' - PathLog.debug('_makeSafeSTL()') + STL object to determine minimum travel height to clear stock and model.""" + PathLog.debug("_makeSafeSTL()") fuseShapes = list() Mdl = JOB.Model.Group[mdlIdx] @@ -1164,23 +1248,29 @@ def _makeSafeSTL(self, JOB, obj, mdlIdx, faceShapes, voidShapes, ocl): # add Model shape to safeSTL shape fuseShapes.append(Mdl.Shape) - if obj.BoundBox == 'BaseBoundBox': + if obj.BoundBox == "BaseBoundBox": cont = False - extFwd = (sBB.ZLength) + extFwd = sBB.ZLength zmin = mBB.ZMin zmax = mBB.ZMin + extFwd stpDwn = (zmax - zmin) / 4.0 - dep_par = PathUtils.depth_params(zmax + 5.0, zmax + 3.0, zmax, stpDwn, 0.0, zmin) + dep_par = PathUtils.depth_params( + zmax + 5.0, zmax + 3.0, zmax, stpDwn, 0.0, zmin + ) try: - envBB = PathUtils.getEnvelope(partshape=Mdl.Shape, depthparams=dep_par) # Produces .Shape + envBB = PathUtils.getEnvelope( + partshape=Mdl.Shape, depthparams=dep_par + ) # Produces .Shape cont = True except Exception as ee: PathLog.error(str(ee)) shell = Mdl.Shape.Shells[0] solid = Part.makeSolid(shell) try: - envBB = PathUtils.getEnvelope(partshape=solid, depthparams=dep_par) # Produces .Shape + envBB = PathUtils.getEnvelope( + partshape=solid, depthparams=dep_par + ) # Produces .Shape cont = True except Exception as eee: PathLog.error(str(eee)) @@ -1189,15 +1279,19 @@ def _makeSafeSTL(self, JOB, obj, mdlIdx, faceShapes, voidShapes, ocl): stckWst = JOB.Stock.Shape.cut(envBB) if obj.BoundaryAdjustment > 0.0: cmpndFS = Part.makeCompound(faceShapes) - baBB = PathUtils.getEnvelope(partshape=cmpndFS, depthparams=self.depthParams) # Produces .Shape + baBB = PathUtils.getEnvelope( + partshape=cmpndFS, depthparams=self.depthParams + ) # Produces .Shape adjStckWst = stckWst.cut(baBB) else: adjStckWst = stckWst fuseShapes.append(adjStckWst) else: - msg = translate('PathSurfaceSupport', - 'Path transitions might not avoid the model. Verify paths.') - FreeCAD.Console.PrintWarning(msg + '\n') + msg = translate( + "PathSurfaceSupport", + "Path transitions might not avoid the model. Verify paths.", + ) + FreeCAD.Console.PrintWarning(msg + "\n") else: # If boundbox is Job.Stock, add hidden pad under stock as base plate toolDiam = self.cutter.getDiameter() @@ -1213,13 +1307,15 @@ def _makeSafeSTL(self, JOB, obj, mdlIdx, faceShapes, voidShapes, ocl): if voidShapes: voidComp = Part.makeCompound(voidShapes) - voidEnv = PathUtils.getEnvelope(partshape=voidComp, depthparams=self.depthParams) # Produces .Shape + voidEnv = PathUtils.getEnvelope( + partshape=voidComp, depthparams=self.depthParams + ) # Produces .Shape fuseShapes.append(voidEnv) fused = Part.makeCompound(fuseShapes) if self.showDebugObjects: - T = FreeCAD.ActiveDocument.addObject('Part::Feature', 'safeSTLShape') + T = FreeCAD.ActiveDocument.addObject("Part::Feature", "safeSTLShape") T.Shape = fused T.purgeTouched() self.tempGroup.addObject(T) @@ -1231,32 +1327,34 @@ def _makeSTL(model, obj, ocl, model_type=None): """Convert a mesh or shape into an OCL STL, using the tessellation tolerance specified in obj.LinearDeflection. Returns an ocl.STLSurf().""" - if model_type == 'M': + if model_type == "M": facets = model.Mesh.Facets.Points else: - if hasattr(model, 'Shape'): + if hasattr(model, "Shape"): shape = model.Shape else: shape = model - vertices, facet_indices = shape.tessellate( - obj.LinearDeflection.Value) - facets = ((vertices[f[0]], vertices[f[1]], vertices[f[2]]) - for f in facet_indices) + vertices, facet_indices = shape.tessellate(obj.LinearDeflection.Value) + facets = ( + (vertices[f[0]], vertices[f[1]], vertices[f[2]]) for f in facet_indices + ) stl = ocl.STLSurf() for tri in facets: v1, v2, v3 = tri - t = ocl.Triangle(ocl.Point(v1[0], v1[1], v1[2]), - ocl.Point(v2[0], v2[1], v2[2]), - ocl.Point(v3[0], v3[1], v3[2])) + t = ocl.Triangle( + ocl.Point(v1[0], v1[1], v1[2]), + ocl.Point(v2[0], v2[1], v2[2]), + ocl.Point(v3[0], v3[1], v3[2]), + ) stl.addTriangle(t) return stl # Functions to convert path geometry into line/arc segments for OCL input or directly to g-code def pathGeomToLinesPointSet(self, obj, compGeoShp): - '''pathGeomToLinesPointSet(self, obj, compGeoShp)... - Convert a compound set of sequential line segments to directionally-oriented collinear groupings.''' - PathLog.debug('pathGeomToLinesPointSet()') + """pathGeomToLinesPointSet(self, obj, compGeoShp)... + Convert a compound set of sequential line segments to directionally-oriented collinear groupings.""" + PathLog.debug("pathGeomToLinesPointSet()") # Extract intersection line segments for return value as list() LINES = list() inLine = list() @@ -1288,7 +1386,7 @@ def pathGeomToLinesPointSet(self, obj, compGeoShp): # iC = sp.isOnLineSegment(ep, cp) iC = cp.isOnLineSegment(sp, ep) if iC is True: - inLine.append('BRK') + inLine.append("BRK") chkGap = True else: if self.CutClimb is True: @@ -1315,7 +1413,12 @@ def pathGeomToLinesPointSet(self, obj, compGeoShp): if chkGap: if gap < obj.GapThreshold.Value: inLine.pop() # pop off 'BRK' marker - (vA, vB) = inLine.pop() # pop off previous line segment for combining with current + ( + vA, + vB, + ) = ( + inLine.pop() + ) # pop off previous line segment for combining with current tup = (vA, tup[1]) self.closedGap = True else: @@ -1337,7 +1440,7 @@ def pathGeomToLinesPointSet(self, obj, compGeoShp): F = LINES.pop(0) rev = list() for iL in F: - if iL == 'BRK': + if iL == "BRK": rev.append(iL) else: (p1, p2) = iL @@ -1347,17 +1450,18 @@ def pathGeomToLinesPointSet(self, obj, compGeoShp): isEven = lnCnt % 2 if isEven == 0: - PathLog.debug('Line count is ODD: {}.'.format(lnCnt)) + PathLog.debug("Line count is ODD: {}.".format(lnCnt)) else: - PathLog.debug('Line count is even: {}.'.format(lnCnt)) + PathLog.debug("Line count is even: {}.".format(lnCnt)) return LINES + def pathGeomToZigzagPointSet(self, obj, compGeoShp): - '''_pathGeomToZigzagPointSet(self, obj, compGeoShp)... + """_pathGeomToZigzagPointSet(self, obj, compGeoShp)... Convert a compound set of sequential line segments to directionally-oriented collinear groupings - with a ZigZag directional indicator included for each collinear group.''' - PathLog.debug('_pathGeomToZigzagPointSet()') + with a ZigZag directional indicator included for each collinear group.""" + PathLog.debug("_pathGeomToZigzagPointSet()") # Extract intersection line segments for return value as list() LINES = list() inLine = list() @@ -1391,7 +1495,7 @@ def pathGeomToZigzagPointSet(self, obj, compGeoShp): ep = FreeCAD.Vector(v2[0], v2[1], 0.0) # end point iC = cp.isOnLineSegment(sp, ep) if iC: - inLine.append('BRK') + inLine.append("BRK") chkGap = True gap = abs(self.toolDiam - lst.sub(cp).Length) else: @@ -1413,7 +1517,12 @@ def pathGeomToZigzagPointSet(self, obj, compGeoShp): if chkGap: if gap < obj.GapThreshold.Value: inLine.pop() # pop off 'BRK' marker - (vA, vB) = inLine.pop() # pop off previous line segment for combining with current + ( + vA, + vB, + ) = ( + inLine.pop() + ) # pop off previous line segment for combining with current if dirFlg == 1: tup = (vA, tup[1]) else: @@ -1431,9 +1540,9 @@ def pathGeomToZigzagPointSet(self, obj, compGeoShp): # Fix directional issue with LAST line when line count is even isEven = lnCnt % 2 if isEven == 0: # Changed to != with 90 degree CutPatternAngle - PathLog.debug('Line count is even: {}.'.format(lnCnt)) + PathLog.debug("Line count is even: {}.".format(lnCnt)) else: - PathLog.debug('Line count is ODD: {}.'.format(lnCnt)) + PathLog.debug("Line count is ODD: {}.".format(lnCnt)) dirFlg = -1 * dirFlg if not obj.CutPatternReversed: if self.CutClimb: @@ -1446,7 +1555,7 @@ def pathGeomToZigzagPointSet(self, obj, compGeoShp): if dirFlg == 1: rev = list() for iL in inLine: - if iL == 'BRK': + if iL == "BRK": rev.append(iL) else: (p1, p2) = iL @@ -1457,7 +1566,7 @@ def pathGeomToZigzagPointSet(self, obj, compGeoShp): else: rev2 = list() for iL in rev: - if iL == 'BRK': + if iL == "BRK": rev2.append(iL) else: (p1, p2) = iL @@ -1470,12 +1579,13 @@ def pathGeomToZigzagPointSet(self, obj, compGeoShp): return LINES + def pathGeomToCircularPointSet(self, obj, compGeoShp): - '''pathGeomToCircularPointSet(self, obj, compGeoShp)... + """pathGeomToCircularPointSet(self, obj, compGeoShp)... Convert a compound set of arcs/circles to a set of directionally-oriented arc end points - and the corresponding center point.''' + and the corresponding center point.""" # Extract intersection line segments for return value as list() - PathLog.debug('pathGeomToCircularPointSet()') + PathLog.debug("pathGeomToCircularPointSet()") ARCS = list() stpOvrEI = list() segEI = list() @@ -1484,8 +1594,8 @@ def pathGeomToCircularPointSet(self, obj, compGeoShp): ec = len(compGeoShp.Edges) def gapDist(sp, ep): - X = (ep[0] - sp[0])**2 - Y = (ep[1] - sp[1])**2 + X = (ep[0] - sp[0]) ** 2 + Y = (ep[1] - sp[1]) ** 2 return math.sqrt(X + Y) # the 'z' value is zero in both points def dist_to_cent(item): @@ -1508,7 +1618,7 @@ def pathGeomToCircularPointSet(self, obj, compGeoShp): for ei in range(0, ec): edg = compGeoShp.Edges[ei] if edg.Closed is True: - stpOvrEI.append(('L', ei, False)) + stpOvrEI.append(("L", ei, False)) else: if isSame is False: segEI.append(ei) @@ -1525,7 +1635,7 @@ def pathGeomToCircularPointSet(self, obj, compGeoShp): segEI.append(ei) else: # Move co-radial arc segments - stpOvrEI.append(['A', segEI, False]) + stpOvrEI.append(["A", segEI, False]) # Start new list of arc segments segEI = [ei] isSame = True @@ -1533,12 +1643,12 @@ def pathGeomToCircularPointSet(self, obj, compGeoShp): sameRad = pnt.sub(self.tmpCOM).Length # Process trailing `segEI` data, if available if isSame is True: - stpOvrEI.append(['A', segEI, False]) + stpOvrEI.append(["A", segEI, False]) # Identify adjacent arcs with y=0 start/end points that connect for so in range(0, len(stpOvrEI)): SO = stpOvrEI[so] - if SO[0] == 'A': + if SO[0] == "A": startOnAxis = list() endOnAxis = list() EI = SO[1] # list of corresponding compGeoShp.Edges indexes @@ -1581,19 +1691,23 @@ def pathGeomToCircularPointSet(self, obj, compGeoShp): # Cycle through stepOver data for so in range(0, len(stpOvrEI)): SO = stpOvrEI[so] - if SO[0] == 'L': # L = Loop/Ring/Circle + if SO[0] == "L": # L = Loop/Ring/Circle # PathLog.debug("SO[0] == 'Loop'") lei = SO[1] # loop Edges index v1 = compGeoShp.Edges[lei].Vertexes[0] # space = obj.SampleInterval.Value / 10.0 # space = 0.000001 - space = self.toolDiam * 0.005 # If too small, OCL will fail to scan the loop + space = ( + self.toolDiam * 0.005 + ) # If too small, OCL will fail to scan the loop # p1 = FreeCAD.Vector(v1.X, v1.Y, v1.Z) - p1 = FreeCAD.Vector(v1.X, v1.Y, 0.0) # z=0.0 for waterline; z=v1.Z for 3D Surface + p1 = FreeCAD.Vector( + v1.X, v1.Y, 0.0 + ) # z=0.0 for waterline; z=v1.Z for 3D Surface rad = p1.sub(self.tmpCOM).Length - spcRadRatio = space/rad + spcRadRatio = space / rad if spcRadRatio < 1.0: tolrncAng = math.asin(spcRadRatio) else: @@ -1606,9 +1720,13 @@ def pathGeomToCircularPointSet(self, obj, compGeoShp): if dirFlg == 1: arc = (sp, ep, cp) else: - arc = (ep, sp, cp) # OCL.Arc(firstPnt, lastPnt, centerPnt, dir=True(CCW direction)) - ARCS.append(('L', dirFlg, [arc])) - elif SO[0] == 'A': # A = Arc + arc = ( + ep, + sp, + cp, + ) # OCL.Arc(firstPnt, lastPnt, centerPnt, dir=True(CCW direction)) + ARCS.append(("L", dirFlg, [arc])) + elif SO[0] == "A": # A = Arc # PathLog.debug("SO[0] == 'Arc'") PRTS = list() EI = SO[1] # list of corresponding Edges indexes @@ -1626,7 +1744,11 @@ def pathGeomToCircularPointSet(self, obj, compGeoShp): arc = (sp, ep, cp) lst = ep else: - arc = (ep, sp, cp) # OCL.Arc(firstPnt, lastPnt, centerPnt, dir=True(CCW direction)) + arc = ( + ep, + sp, + cp, + ) # OCL.Arc(firstPnt, lastPnt, centerPnt, dir=True(CCW direction)) lst = sp PRTS.append(arc) # Pop connected edge index values from arc segments index list @@ -1639,12 +1761,12 @@ def pathGeomToCircularPointSet(self, obj, compGeoShp): EI.pop(iSi) EI.pop(iEi) if len(EI) > 0: - PRTS.append('BRK') + PRTS.append("BRK") chkGap = True cnt = 0 for ei in EI: if cnt > 0: - PRTS.append('BRK') + PRTS.append("BRK") chkGap = True v1 = compGeoShp.Edges[ei].Vertexes[0] v2 = compGeoShp.Edges[ei].Vertexes[1] @@ -1653,17 +1775,31 @@ def pathGeomToCircularPointSet(self, obj, compGeoShp): if dirFlg == 1: arc = (sp, ep, cp) if chkGap: - gap = abs(self.toolDiam - gapDist(lst, sp)) # abs(self.toolDiam - lst.sub(sp).Length) + gap = abs( + self.toolDiam - gapDist(lst, sp) + ) # abs(self.toolDiam - lst.sub(sp).Length) lst = ep else: - arc = (ep, sp, cp) # OCL.Arc(firstPnt, lastPnt, centerPnt, dir=True(CCW direction)) + arc = ( + ep, + sp, + cp, + ) # OCL.Arc(firstPnt, lastPnt, centerPnt, dir=True(CCW direction)) if chkGap: - gap = abs(self.toolDiam - gapDist(lst, ep)) # abs(self.toolDiam - lst.sub(ep).Length) + gap = abs( + self.toolDiam - gapDist(lst, ep) + ) # abs(self.toolDiam - lst.sub(ep).Length) lst = sp if chkGap: if gap < obj.GapThreshold.Value: PRTS.pop() # pop off 'BRK' marker - (vA, vB, vC) = PRTS.pop() # pop off previous arc segment for combining with current + ( + vA, + vB, + vC, + ) = ( + PRTS.pop() + ) # pop off previous arc segment for combining with current arc = (vA, arc[1], vC) self.closedGap = True else: @@ -1677,9 +1813,9 @@ def pathGeomToCircularPointSet(self, obj, compGeoShp): if dirFlg == -1: PRTS.reverse() - ARCS.append(('A', dirFlg, PRTS)) + ARCS.append(("A", dirFlg, PRTS)) # Eif - if obj.CutPattern == 'CircularZigZag': + if obj.CutPattern == "CircularZigZag": dirFlg = -1 * dirFlg # Efor @@ -1687,10 +1823,11 @@ def pathGeomToCircularPointSet(self, obj, compGeoShp): return ARCS + def pathGeomToSpiralPointSet(obj, compGeoShp): - '''_pathGeomToSpiralPointSet(obj, compGeoShp)... - Convert a compound set of sequential line segments to directional, connected groupings.''' - PathLog.debug('_pathGeomToSpiralPointSet()') + """_pathGeomToSpiralPointSet(obj, compGeoShp)... + Convert a compound set of sequential line segments to directional, connected groupings.""" + PathLog.debug("_pathGeomToSpiralPointSet()") # Extract intersection line segments for return value as list() LINES = list() inLine = list() @@ -1699,19 +1836,27 @@ def pathGeomToSpiralPointSet(obj, compGeoShp): start = 2 if obj.CutPatternReversed: - edg1 = compGeoShp.Edges[0] # Skip first edge, as it is the closing edge: center to outer tail + edg1 = compGeoShp.Edges[ + 0 + ] # Skip first edge, as it is the closing edge: center to outer tail ec -= 1 start = 1 else: - edg1 = compGeoShp.Edges[1] # Skip first edge, as it is the closing edge: center to outer tail + edg1 = compGeoShp.Edges[ + 1 + ] # Skip first edge, as it is the closing edge: center to outer tail p1 = FreeCAD.Vector(edg1.Vertexes[0].X, edg1.Vertexes[0].Y, 0.0) p2 = FreeCAD.Vector(edg1.Vertexes[1].X, edg1.Vertexes[1].Y, 0.0) tup = ((p1.x, p1.y), (p2.x, p2.y)) inLine.append(tup) - for ei in range(start, ec): # Skipped first edge, started with second edge above as edg1 + for ei in range( + start, ec + ): # Skipped first edge, started with second edge above as edg1 edg = compGeoShp.Edges[ei] # Get edge for vertexes - sp = FreeCAD.Vector(edg.Vertexes[0].X, edg.Vertexes[0].Y, 0.0) # check point (first / middle point) + sp = FreeCAD.Vector( + edg.Vertexes[0].X, edg.Vertexes[0].Y, 0.0 + ) # check point (first / middle point) ep = FreeCAD.Vector(edg.Vertexes[1].X, edg.Vertexes[1].Y, 0.0) # end point tup = ((sp.x, sp.y), (ep.x, ep.y)) @@ -1731,10 +1876,11 @@ def pathGeomToSpiralPointSet(obj, compGeoShp): return LINES + def pathGeomToOffsetPointSet(obj, compGeoShp): - '''pathGeomToOffsetPointSet(obj, compGeoShp)... - Convert a compound set of 3D profile segmented wires to 2D segments, applying linear optimization.''' - PathLog.debug('pathGeomToOffsetPointSet()') + """pathGeomToOffsetPointSet(obj, compGeoShp)... + Convert a compound set of 3D profile segmented wires to 2D segments, applying linear optimization.""" + PathLog.debug("pathGeomToOffsetPointSet()") LINES = list() optimize = obj.OptimizeLinearPaths @@ -1747,7 +1893,7 @@ def pathGeomToOffsetPointSet(obj, compGeoShp): lenOS = len(OS) if ei > 0: - LINES.append('BRK') + LINES.append("BRK") fp = FreeCAD.Vector(OS[0].x, OS[0].y, OS[0].z) OS.append(fp) @@ -1781,8 +1927,8 @@ def pathGeomToOffsetPointSet(obj, compGeoShp): class FindUnifiedRegions: - '''FindUnifiedRegions() This class requires a list of face shapes. - It finds the unified horizontal unified regions, if they exist.''' + """FindUnifiedRegions() This class requires a list of face shapes. + It finds the unified horizontal unified regions, if they exist.""" def __init__(self, facesList, geomToler): self.FACES = facesList # format is tuple (faceShape, faceIndex_on_base) @@ -1806,7 +1952,7 @@ class FindUnifiedRegions: # Internal processing methods def _showShape(self, shape, name): if self.tempGroup: - S = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmp' + name) + S = FreeCAD.ActiveDocument.addObject("Part::Feature", "tmp" + name) S.Shape = shape S.purgeTouched() self.tempGroup.addObject(S) @@ -1830,7 +1976,11 @@ class FindUnifiedRegions: base = ef.cut(cutBox) if base.Volume == 0: - PathLog.debug('Ignoring Face{}. It is likely vertical with no horizontal exposure.'.format(fcIdx)) + PathLog.debug( + "Ignoring Face{}. It is likely vertical with no horizontal exposure.".format( + fcIdx + ) + ) cont = False if cont: @@ -1861,9 +2011,11 @@ class FindUnifiedRegions: tfBB_Area = tfBB.XLength * tfBB.YLength # self._showShape(topFace, 'topFaceAlt_2_{}'.format(fNum)) if tfBB_Area < (fBB_Area * 0.9): - msg = translate('PathSurfaceSupport', - 'Faild to extract processing region for Face') - FreeCAD.Console.PrintError(msg + '{}.\n'.format(fNum)) + msg = translate( + "PathSurfaceSupport", + "Faild to extract processing region for Face", + ) + FreeCAD.Console.PrintError(msg + "{}.\n".format(fNum)) cont = False # Eif @@ -1899,7 +2051,7 @@ class FindUnifiedRegions: count[1] += 1 def _groupEdgesByLength(self): - PathLog.debug('_groupEdgesByLength()') + PathLog.debug("_groupEdgesByLength()") threshold = self.geomToler grp = list() processLast = False @@ -1947,7 +2099,7 @@ class FindUnifiedRegions: self.idGroups.append(grp) def _identifySharedEdgesByLength(self, grp): - PathLog.debug('_identifySharedEdgesByLength()') + PathLog.debug("_identifySharedEdgesByLength()") holds = list() specialIndexes = [] threshold = self.geomToler @@ -2005,7 +2157,7 @@ class FindUnifiedRegions: self.noSharedEdges = False def _extractWiresFromEdges(self): - PathLog.debug('_extractWiresFromEdges()') + PathLog.debug("_extractWiresFromEdges()") DATA = self.edgeData holds = list() firstEdge = None @@ -2102,11 +2254,11 @@ class FindUnifiedRegions: # Ewhile numLoops = len(LOOPS) - PathLog.debug(' -numLoops: {}.'.format(numLoops)) + PathLog.debug(" -numLoops: {}.".format(numLoops)) if numLoops > 0: for li in range(0, numLoops): Edges = LOOPS[li] - #for e in Edges: + # for e in Edges: # self._showShape(e, 'Loop_{}_Edge'.format(li)) wire = Part.Wire(Part.__sortEdges__(Edges)) if wire.isClosed(): @@ -2119,15 +2271,19 @@ class FindUnifiedRegions: # closed wires mentioned above. extWire = wire.extrude(FreeCAD.Vector(0.0, 0.0, 2.0)) wireSolid = Part.makeSolid(extWire) - extdBBFace1 = makeExtendedBoundBox(wireSolid.BoundBox, 5.0, wireSolid.BoundBox.ZMin + 1.0) - extdBBFace2 = makeExtendedBoundBox(wireSolid.BoundBox, 5.0, wireSolid.BoundBox.ZMin + 1.0) + extdBBFace1 = makeExtendedBoundBox( + wireSolid.BoundBox, 5.0, wireSolid.BoundBox.ZMin + 1.0 + ) + extdBBFace2 = makeExtendedBoundBox( + wireSolid.BoundBox, 5.0, wireSolid.BoundBox.ZMin + 1.0 + ) inverse = extdBBFace1.cut(wireSolid) face = extdBBFace2.cut(inverse) self.REGIONS.append(face) self.REGIONS.sort(key=faceArea, reverse=True) def _identifyInternalFeatures(self): - PathLog.debug('_identifyInternalFeatures()') + PathLog.debug("_identifyInternalFeatures()") remList = list() for (top, fcIdx) in self.topFaces: @@ -2142,14 +2298,14 @@ class FindUnifiedRegions: remList.append(s) break else: - PathLog.debug(' - No common area.\n') + PathLog.debug(" - No common area.\n") remList.sort(reverse=True) for ri in remList: self.REGIONS.pop(ri) def _processNestedRegions(self): - PathLog.debug('_processNestedRegions()') + PathLog.debug("_processNestedRegions()") cont = True hold = list() Ids = list() @@ -2194,7 +2350,7 @@ class FindUnifiedRegions: # Accessory methods def _getCompleteCrossSection(self, shape): - PathLog.debug('_getCompleteCrossSection()') + PathLog.debug("_getCompleteCrossSection()") wires = list() bb = shape.BoundBox mid = (bb.ZMin + bb.ZMax) / 2.0 @@ -2208,7 +2364,7 @@ class FindUnifiedRegions: CS.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - CS.BoundBox.ZMin)) return CS - PathLog.debug(' -No wires from .slice() method') + PathLog.debug(" -No wires from .slice() method") return False def _edgesAreConnected(self, e1, e2): @@ -2245,17 +2401,19 @@ class FindUnifiedRegions: # Public methods def setTempGroup(self, grpObj): - '''setTempGroup(grpObj)... For debugging, pass temporary object group.''' + """setTempGroup(grpObj)... For debugging, pass temporary object group.""" self.tempGroup = grpObj def getUnifiedRegions(self): - '''getUnifiedRegions()... Returns a list of unified regions from list - of tuples (faceShape, faceIndex) received at instantiation of the class object.''' - PathLog.debug('getUnifiedRegions()') + """getUnifiedRegions()... Returns a list of unified regions from list + of tuples (faceShape, faceIndex) received at instantiation of the class object.""" + PathLog.debug("getUnifiedRegions()") if len(self.FACES) == 0: - msg = translate('PathSurfaceSupport', - 'No FACE data tuples received at instantiation of class.') - FreeCAD.Console.PrintError(msg + '\n') + msg = translate( + "PathSurfaceSupport", + "No FACE data tuples received at instantiation of class.", + ) + FreeCAD.Console.PrintError(msg + "\n") return [] self._extractTopFaces() @@ -2266,7 +2424,7 @@ class FindUnifiedRegions: # if single topFace, return it if lenFaces == 1: topFace = self.topFaces[0][0] - self._showShape(topFace, 'TopFace') + self._showShape(topFace, "TopFace") # prepare inner wires as faces for internal features lenWrs = len(topFace.Wires) if lenWrs > 1: @@ -2282,16 +2440,18 @@ class FindUnifiedRegions: return [face] else: (faceShp, fcIdx) = self.FACES[0] - msg = translate('PathSurfaceSupport', - 'Failed to identify a horizontal cross-section for Face') - msg += '{}.\n'.format(fcIdx + 1) + msg = translate( + "PathSurfaceSupport", + "Failed to identify a horizontal cross-section for Face", + ) + msg += "{}.\n".format(fcIdx + 1) FreeCAD.Console.PrintWarning(msg) return [] # process multiple top faces, unifying if possible self._fuseTopFaces() for F in self.fusedFaces.Faces: - self._showShape(F, 'TopFaceFused') + self._showShape(F, "TopFaceFused") self._getEdgesData() self._groupEdgesByLength() @@ -2299,7 +2459,7 @@ class FindUnifiedRegions: self._identifySharedEdgesByLength(grp) if self.noSharedEdges: - PathLog.debug('No shared edges by length detected.') + PathLog.debug("No shared edges by length detected.") allTopFaces = list() for (topFace, fcIdx) in self.topFaces: allTopFaces.append(topFace) @@ -2327,21 +2487,26 @@ class FindUnifiedRegions: return self.REGIONS def getInternalFeatures(self): - '''getInternalFeatures()... Returns internal features identified - after calling getUnifiedRegions().''' + """getInternalFeatures()... Returns internal features identified + after calling getUnifiedRegions().""" if self.internalsReady: if len(self.INTERNALS) > 0: return self.INTERNALS else: return False - msg = translate('PathSurfaceSupport', - 'getUnifiedRegions() must be called before getInternalFeatures().') - FreeCAD.Console.PrintError(msg + '\n') + msg = translate( + "PathSurfaceSupport", + "getUnifiedRegions() must be called before getInternalFeatures().", + ) + FreeCAD.Console.PrintError(msg + "\n") return False + + # Eclass -class OCL_Tool(): + +class OCL_Tool: """The OCL_Tool class is designed to translate a FreeCAD standard ToolBit shape, or Legacy tool type, in the active Tool Controller, into an OCL tool type.""" @@ -2364,19 +2529,21 @@ class OCL_Tool(): # Default to zero. ToolBit likely is without. self.lengthOffset = 0.0 - if hasattr(obj, 'ToolController'): - if hasattr(obj.ToolController, 'Tool'): + if hasattr(obj, "ToolController"): + if hasattr(obj.ToolController, "Tool"): self.tool = obj.ToolController.Tool - if hasattr(self.tool, 'ShapeName'): + if hasattr(self.tool, "ShapeName"): self.toolType = self.tool.ShapeName # Indicates ToolBit tool - self.toolMode = 'ToolBit' - elif hasattr(self.tool, 'ToolType'): + self.toolMode = "ToolBit" + elif hasattr(self.tool, "ToolType"): self.toolType = self.tool.ToolType # Indicates Legacy tool - self.toolMode = 'Legacy' + self.toolMode = "Legacy" if self.toolType: - PathLog.debug('OCL_Tool tool mode, type: {}, {}'.format(self.toolMode, self.toolType)) + PathLog.debug( + "OCL_Tool tool mode, type: {}, {}".format(self.toolMode, self.toolType) + ) - ''' + """ #### FreeCAD Legacy tool shape properties per tool type shape = EndMill Diameter @@ -2508,25 +2675,26 @@ class OCL_Tool(): ShankDiameter FlatHeight;Height of the flat extension of the v-bit FlatRadius; Diameter of the flat end of the tip - ''' + """ # Private methods def _setDimensions(self): - '''_setDimensions() ... Set values for possible dimensions.''' - if hasattr(self.tool, 'Diameter'): + """_setDimensions() ... Set values for possible dimensions.""" + if hasattr(self.tool, "Diameter"): self.diameter = float(self.tool.Diameter) else: - msg = translate('PathSurfaceSupport', - 'Diameter dimension missing from ToolBit shape.') - FreeCAD.Console.PrintError(msg + '\n') + msg = translate( + "PathSurfaceSupport", "Diameter dimension missing from ToolBit shape." + ) + FreeCAD.Console.PrintError(msg + "\n") return False - if hasattr(self.tool, 'LengthOffset'): + if hasattr(self.tool, "LengthOffset"): self.lengthOffset = float(self.tool.LengthOffset) - if hasattr(self.tool, 'FlatRadius'): + if hasattr(self.tool, "FlatRadius"): self.flatRadius = float(self.tool.FlatRadius) - if hasattr(self.tool, 'CuttingEdgeHeight'): + if hasattr(self.tool, "CuttingEdgeHeight"): self.cutEdgeHeight = float(self.tool.CuttingEdgeHeight) - if hasattr(self.tool, 'CuttingEdgeAngle'): + if hasattr(self.tool, "CuttingEdgeAngle"): self.cutEdgeAngle = float(self.tool.CuttingEdgeAngle) return True @@ -2542,83 +2710,85 @@ class OCL_Tool(): def _oclCylCutter(self): # Standard End Mill, Slot cutter, or Fly cutter # OCL -> CylCutter::CylCutter(diameter, length) - if (self.diameter == -1.0 or self.cutEdgeHeight == -1.0): + if self.diameter == -1.0 or self.cutEdgeHeight == -1.0: return self.oclTool = self.ocl.CylCutter( - self.diameter, - self.cutEdgeHeight + self.lengthOffset - ) + self.diameter, self.cutEdgeHeight + self.lengthOffset + ) def _oclBallCutter(self): # Standard Ball End Mill # OCL -> BallCutter::BallCutter(diameter, length) - if (self.diameter == -1.0 or self.cutEdgeHeight == -1.0): + if self.diameter == -1.0 or self.cutEdgeHeight == -1.0: return self.tiltCutter = True - if self.cutEdgeHeight==0 : self.cutEdgeHeight = self.diameter/2 + if self.cutEdgeHeight == 0: + self.cutEdgeHeight = self.diameter / 2 self.oclTool = self.ocl.BallCutter( - self.diameter, - self.cutEdgeHeight + self.lengthOffset - ) + self.diameter, self.cutEdgeHeight + self.lengthOffset + ) def _oclBullCutter(self): # Standard Bull Nose cutter # Reference: https://www.fine-tools.com/halbstabfraeser.html # OCL -> BullCutter::BullCutter(diameter, minor radius, length) - if (self.diameter == -1.0 or - self.flatRadius == -1.0 or - self.cutEdgeHeight == -1.0): + if ( + self.diameter == -1.0 + or self.flatRadius == -1.0 + or self.cutEdgeHeight == -1.0 + ): return self.oclTool = self.ocl.BullCutter( - self.diameter, - self.diameter - self.flatRadius, - self.cutEdgeHeight + self.lengthOffset - ) + self.diameter, + self.diameter - self.flatRadius, + self.cutEdgeHeight + self.lengthOffset, + ) def _oclConeCutter(self): # Engraver or V-bit cutter # OCL -> ConeCutter::ConeCutter(diameter, angle, length) - if (self.diameter == -1.0 or - self.cutEdgeAngle == -1.0 or self.cutEdgeHeight == -1.0): + if ( + self.diameter == -1.0 + or self.cutEdgeAngle == -1.0 + or self.cutEdgeHeight == -1.0 + ): return self.oclTool = self.ocl.ConeCutter( - self.diameter, - self.cutEdgeAngle/2, - self.lengthOffset - ) + self.diameter, self.cutEdgeAngle / 2, self.lengthOffset + ) def _setToolMethod(self): toolMap = dict() - if self.toolMode == 'Legacy': + if self.toolMode == "Legacy": # Set cutter details # https://www.freecadweb.org/api/dd/dfe/classPath_1_1Tool.html#details toolMap = { - 'EndMill': 'CylCutter', - 'BallEndMill': 'BallCutter', - 'SlotCutter': 'CylCutter', - 'Engraver': 'ConeCutter', - 'Drill': 'ConeCutter', - 'CounterSink': 'ConeCutter', - 'FlyCutter': 'CylCutter', - 'CenterDrill': 'None', - 'CounterBore': 'None', - 'Reamer': 'None', - 'Tap': 'None', - 'ChamferMill': 'None', - 'CornerRound': 'None' + "EndMill": "CylCutter", + "BallEndMill": "BallCutter", + "SlotCutter": "CylCutter", + "Engraver": "ConeCutter", + "Drill": "ConeCutter", + "CounterSink": "ConeCutter", + "FlyCutter": "CylCutter", + "CenterDrill": "None", + "CounterBore": "None", + "Reamer": "None", + "Tap": "None", + "ChamferMill": "None", + "CornerRound": "None", } - elif self.toolMode == 'ToolBit': + elif self.toolMode == "ToolBit": toolMap = { - 'endmill': 'CylCutter', - 'ballend': 'BallCutter', - 'bullnose': 'BullCutter', - 'drill': 'ConeCutter', - 'engraver': 'ConeCutter', - 'v-bit': 'ConeCutter', - 'chamfer': 'None' + "endmill": "CylCutter", + "ballend": "BallCutter", + "bullnose": "BullCutter", + "drill": "ConeCutter", + "engraver": "ConeCutter", + "v-bit": "ConeCutter", + "chamfer": "None", } - self.toolMethod = 'None' + self.toolMethod = "None" if self.toolType in toolMap: self.toolMethod = toolMap[self.toolType] @@ -2628,9 +2798,8 @@ class OCL_Tool(): to return OCL tool object.""" # Check for tool controller and tool object if not self.tool or not self.toolMode: - msg = translate('PathSurface', - 'Failed to identify tool for operation.') - FreeCAD.Console.PrintError(msg + '\n') + msg = translate("PathSurface", "Failed to identify tool for operation.") + FreeCAD.Console.PrintError(msg + "\n") return False if not self._setDimensions(): @@ -2638,23 +2807,25 @@ class OCL_Tool(): self._setToolMethod() - if self.toolMethod == 'None': - err = translate('PathSurface', - 'Failed to map selected tool to an OCL tool type.') - FreeCAD.Console.PrintError(err + '\n') + if self.toolMethod == "None": + err = translate( + "PathSurface", "Failed to map selected tool to an OCL tool type." + ) + FreeCAD.Console.PrintError(err + "\n") return False else: - PathLog.debug('OCL_Tool tool method: {}'.format(self.toolMethod)) - oclToolMethod = getattr(self, '_ocl' + self.toolMethod) + PathLog.debug("OCL_Tool tool method: {}".format(self.toolMethod)) + oclToolMethod = getattr(self, "_ocl" + self.toolMethod) oclToolMethod() if self.oclTool: return self.oclTool # Set error messages - err = translate('PathSurface', - 'Failed to translate active tool to OCL tool type.') - FreeCAD.Console.PrintError(err + '\n') + err = translate( + "PathSurface", "Failed to translate active tool to OCL tool type." + ) + FreeCAD.Console.PrintError(err + "\n") return False def useTiltCutter(self): @@ -2662,16 +2833,20 @@ class OCL_Tool(): to return status of cutter tilt availability - generally this is for a ball end mill.""" if not self.tool or not self.oclTool: - err = translate('PathSurface', - 'OCL tool not available. Cannot determine is cutter has tilt available.') - FreeCAD.Console.PrintError(err + '\n') + err = translate( + "PathSurface", + "OCL tool not available. Cannot determine is cutter has tilt available.", + ) + FreeCAD.Console.PrintError(err + "\n") return False return self.tiltCutter + + # Eclass # Support functions def makeExtendedBoundBox(wBB, bbBfr, zDep): - PathLog.debug('makeExtendedBoundBox()') + PathLog.debug("makeExtendedBoundBox()") p1 = FreeCAD.Vector(wBB.XMin - bbBfr, wBB.YMin - bbBfr, zDep) p2 = FreeCAD.Vector(wBB.XMax + bbBfr, wBB.YMin - bbBfr, zDep) p3 = FreeCAD.Vector(wBB.XMax + bbBfr, wBB.YMax + bbBfr, zDep) @@ -2683,5 +2858,3 @@ def makeExtendedBoundBox(wBB, bbBfr, zDep): L4 = Part.makeLine(p4, p1) return Part.Face(Part.Wire([L1, L2, L3, L4])) - - diff --git a/src/Mod/Path/PathScripts/PathWaterlineGui.py b/src/Mod/Path/PathScripts/PathWaterlineGui.py index 52a5e04a37..73124f5d02 100644 --- a/src/Mod/Path/PathScripts/PathWaterlineGui.py +++ b/src/Mod/Path/PathScripts/PathWaterlineGui.py @@ -23,7 +23,7 @@ import FreeCAD import FreeCADGui -import PathGui as PGui # ensure Path/Gui/Resources are loaded +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathWaterline as PathWaterline import PathScripts.PathGui as PathGui import PathScripts.PathOpGui as PathOpGui @@ -37,18 +37,18 @@ __doc__ = "Waterline operation page controller and command implementation." class TaskPanelOpPage(PathOpGui.TaskPanelPage): - '''Page controller class for the Waterline operation.''' + """Page controller class for the Waterline operation.""" def initPage(self, obj): self.setTitle("Waterline - " + obj.Label) self.updateVisibility() def getForm(self): - '''getForm() ... returns UI''' + """getForm() ... returns UI""" return FreeCADGui.PySideUic.loadUi(":/panels/PageOpWaterlineEdit.ui") def getFields(self, obj): - '''getFields(obj) ... transfers values from UI to obj's proprties''' + """getFields(obj) ... transfers values from UI to obj's proprties""" self.updateToolController(obj, self.form.toolController) self.updateCoolant(obj, self.form.coolantController) @@ -64,27 +64,37 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): if obj.CutPattern != str(self.form.cutPattern.currentText()): obj.CutPattern = str(self.form.cutPattern.currentText()) - PathGui.updateInputField(obj, 'BoundaryAdjustment', self.form.boundaryAdjustment) + PathGui.updateInputField( + obj, "BoundaryAdjustment", self.form.boundaryAdjustment + ) if obj.StepOver != self.form.stepOver.value(): obj.StepOver = self.form.stepOver.value() - PathGui.updateInputField(obj, 'SampleInterval', self.form.sampleInterval) + PathGui.updateInputField(obj, "SampleInterval", self.form.sampleInterval) if obj.OptimizeLinearPaths != self.form.optimizeEnabled.isChecked(): obj.OptimizeLinearPaths = self.form.optimizeEnabled.isChecked() def setFields(self, obj): - '''setFields(obj) ... transfers obj's property values to UI''' + """setFields(obj) ... transfers obj's property values to UI""" self.setupToolController(obj, self.form.toolController) self.setupCoolant(obj, self.form.coolantController) self.selectInComboBox(obj.Algorithm, self.form.algorithmSelect) self.selectInComboBox(obj.BoundBox, self.form.boundBoxSelect) self.selectInComboBox(obj.LayerMode, self.form.layerMode) self.selectInComboBox(obj.CutPattern, self.form.cutPattern) - self.form.boundaryAdjustment.setText(FreeCAD.Units.Quantity(obj.BoundaryAdjustment.Value, FreeCAD.Units.Length).UserString) + self.form.boundaryAdjustment.setText( + FreeCAD.Units.Quantity( + obj.BoundaryAdjustment.Value, FreeCAD.Units.Length + ).UserString + ) self.form.stepOver.setValue(obj.StepOver) - self.form.sampleInterval.setText(FreeCAD.Units.Quantity(obj.SampleInterval.Value, FreeCAD.Units.Length).UserString) + self.form.sampleInterval.setText( + FreeCAD.Units.Quantity( + obj.SampleInterval.Value, FreeCAD.Units.Length + ).UserString + ) if obj.OptimizeLinearPaths: self.form.optimizeEnabled.setCheckState(QtCore.Qt.Checked) @@ -94,7 +104,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.updateVisibility() def getSignalsForUpdate(self, obj): - '''getSignalsForUpdate(obj) ... return list of signals for updating obj''' + """getSignalsForUpdate(obj) ... return list of signals for updating obj""" signals = [] signals.append(self.form.toolController.currentIndexChanged) signals.append(self.form.coolantController.currentIndexChanged) @@ -110,11 +120,11 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): return signals def updateVisibility(self, sentObj=None): - '''updateVisibility(sentObj=None)... Updates visibility of Tasks panel objects.''' + """updateVisibility(sentObj=None)... Updates visibility of Tasks panel objects.""" Algorithm = self.form.algorithmSelect.currentText() self.form.optimizeEnabled.hide() # Has no independent QLabel object - if Algorithm == 'OCL Dropcutter': + if Algorithm == "OCL Dropcutter": self.form.cutPattern.hide() self.form.cutPattern_label.hide() self.form.boundaryAdjustment.hide() @@ -123,12 +133,12 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.form.stepOver_label.hide() self.form.sampleInterval.show() self.form.sampleInterval_label.show() - elif Algorithm == 'Experimental': + elif Algorithm == "Experimental": self.form.cutPattern.show() self.form.boundaryAdjustment.show() self.form.cutPattern_label.show() self.form.boundaryAdjustment_label.show() - if self.form.cutPattern.currentText() == 'None': + if self.form.cutPattern.currentText() == "None": self.form.stepOver.hide() self.form.stepOver_label.hide() else: @@ -142,12 +152,16 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.form.cutPattern.currentIndexChanged.connect(self.updateVisibility) -Command = PathOpGui.SetupOperation('Waterline', - PathWaterline.Create, - TaskPanelOpPage, - 'Path_Waterline', - QtCore.QT_TRANSLATE_NOOP("Path_Waterline", "Waterline"), - QtCore.QT_TRANSLATE_NOOP("Path_Waterline", "Create a Waterline Operation from a model"), - PathWaterline.SetupProperties) +Command = PathOpGui.SetupOperation( + "Waterline", + PathWaterline.Create, + TaskPanelOpPage, + "Path_Waterline", + QtCore.QT_TRANSLATE_NOOP("Path_Waterline", "Waterline"), + QtCore.QT_TRANSLATE_NOOP( + "Path_Waterline", "Create a Waterline Operation from a model" + ), + PathWaterline.SetupProperties, +) FreeCAD.Console.PrintLog("Loading PathWaterlineGui... done\n")