diff --git a/src/Mod/Path/PathScripts/PathCircularHoleBase.py b/src/Mod/Path/PathScripts/PathCircularHoleBase.py index c6739738c7..9e5253e474 100644 --- a/src/Mod/Path/PathScripts/PathCircularHoleBase.py +++ b/src/Mod/Path/PathScripts/PathCircularHoleBase.py @@ -29,10 +29,10 @@ from PySide import QtCore # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader -ArchPanel = LazyLoader('ArchPanel', globals(), 'ArchPanel') -Draft = LazyLoader('Draft', globals(), 'Draft') -Part = LazyLoader('Part', globals(), 'Part') -DraftGeomUtils = LazyLoader('DraftGeomUtils', globals(), 'DraftGeomUtils') + +Draft = LazyLoader("Draft", globals(), "Draft") +Part = LazyLoader("Part", globals(), "Part") +DraftGeomUtils = LazyLoader("DraftGeomUtils", globals(), "DraftGeomUtils") __title__ = "Path Circular Holes Base Operation" @@ -51,81 +51,70 @@ PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) class ObjectOp(PathOp.ObjectOp): - '''Base class for proxy objects of all operations on circular holes.''' + """Base class for proxy objects of all operations on circular holes.""" def opFeatures(self, obj): - '''opFeatures(obj) ... calls circularHoleFeatures(obj) and ORs in the standard features required for processing circular holes. - Do not overwrite, implement circularHoleFeatures(obj) instead''' - return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureHeights \ - | PathOp.FeatureBaseFaces | self.circularHoleFeatures(obj) \ + """opFeatures(obj) ... calls circularHoleFeatures(obj) and ORs in the standard features required for processing circular holes. + Do not overwrite, implement circularHoleFeatures(obj) instead""" + return ( + PathOp.FeatureTool + | PathOp.FeatureDepths + | PathOp.FeatureHeights + | PathOp.FeatureBaseFaces + | self.circularHoleFeatures(obj) | PathOp.FeatureCoolant + ) def circularHoleFeatures(self, obj): - '''circularHoleFeatures(obj) ... overwrite to add operations specific features. - Can safely be overwritten by subclasses.''' + """circularHoleFeatures(obj) ... overwrite to add operations specific features. + Can safely be overwritten by subclasses.""" return 0 def initOperation(self, obj): - '''initOperation(obj) ... adds Disabled properties and calls initCircularHoleOperation(obj). - Do not overwrite, implement initCircularHoleOperation(obj) instead.''' - obj.addProperty("App::PropertyStringList", "Disabled", "Base", QtCore.QT_TRANSLATE_NOOP("Path", "List of disabled features")) + """initOperation(obj) ... adds Disabled properties and calls initCircularHoleOperation(obj). + Do not overwrite, implement initCircularHoleOperation(obj) instead.""" + obj.addProperty( + "App::PropertyStringList", + "Disabled", + "Base", + QtCore.QT_TRANSLATE_NOOP("Path", "List of disabled features"), + ) self.initCircularHoleOperation(obj) def initCircularHoleOperation(self, obj): - '''initCircularHoleOperation(obj) ... overwrite if the subclass needs initialisation. - Can safely be overwritten by subclasses.''' + """initCircularHoleOperation(obj) ... overwrite if the subclass needs initialisation. + Can safely be overwritten by subclasses.""" pass - def baseIsArchPanel(self, obj, base): - '''baseIsArchPanel(obj, base) ... return true if op deals with an Arch.Panel.''' - return hasattr(base, "Proxy") and isinstance(base.Proxy, ArchPanel.PanelSheet) - - def getArchPanelEdge(self, obj, base, sub): - '''getArchPanelEdge(obj, base, sub) ... helper function to identify a specific edge of an Arch.Panel. - Edges are identified by 3 numbers: - .. - Let's say the edge is specified as "3.2.7", then the 7th edge of the 2nd wire in the 3rd hole returned - by the panel sheet is the edge returned. - Obviously this is as fragile as can be, but currently the best we can do while the panel sheets - hide the actual features from Path and they can't be referenced directly. - ''' - ids = sub.split(".") - holeId = int(ids[0]) - wireId = int(ids[1]) - edgeId = int(ids[2]) - - for holeNr, hole in enumerate(base.Proxy.getHoles(base, transform=True)): - if holeNr == holeId: - for wireNr, wire in enumerate(hole.Wires): - if wireNr == wireId: - for edgeNr, edge in enumerate(wire.Edges): - if edgeNr == edgeId: - return edge - def holeDiameter(self, obj, base, sub): - '''holeDiameter(obj, base, sub) ... returns the diameter of the specified hole.''' - if self.baseIsArchPanel(obj, base): - edge = self.getArchPanelEdge(obj, base, sub) - return edge.BoundBox.XLength - + """holeDiameter(obj, base, sub) ... returns the diameter of the specified hole.""" try: shape = base.Shape.getElement(sub) - if shape.ShapeType == 'Vertex': + if shape.ShapeType == "Vertex": return 0 - if shape.ShapeType == 'Edge' and type(shape.Curve) == Part.Circle: + if shape.ShapeType == "Edge" and type(shape.Curve) == Part.Circle: return shape.Curve.Radius * 2 - if shape.ShapeType == 'Face': + if shape.ShapeType == "Face": for i in range(len(shape.Edges)): - if (type(shape.Edges[i].Curve) == Part.Circle and - shape.Edges[i].Curve.Radius * 2 < shape.BoundBox.XLength*1.1 and - shape.Edges[i].Curve.Radius * 2 > shape.BoundBox.XLength*0.9): + if ( + type(shape.Edges[i].Curve) == Part.Circle + and shape.Edges[i].Curve.Radius * 2 + < shape.BoundBox.XLength * 1.1 + and shape.Edges[i].Curve.Radius * 2 + > shape.BoundBox.XLength * 0.9 + ): return shape.Edges[i].Curve.Radius * 2 # for all other shapes the diameter is just the dimension in X. # This may be inaccurate as the BoundBox is calculated on the tessellated geometry - PathLog.warning(translate("Path", "Hole diameter may be inaccurate due to tessellation on face. Consider selecting hole edge.")) + PathLog.warning( + translate( + "Path", + "Hole diameter may be inaccurate due to tessellation on face. Consider selecting hole edge.", + ) + ) return shape.BoundBox.XLength except Part.OCCError as e: PathLog.error(e) @@ -133,44 +122,48 @@ class ObjectOp(PathOp.ObjectOp): return 0 def holePosition(self, obj, base, sub): - '''holePosition(obj, base, sub) ... returns a Vector for the position defined by the given features. - Note that the value for Z is set to 0.''' - if self.baseIsArchPanel(obj, base): - edge = self.getArchPanelEdge(obj, base, sub) - center = edge.Curve.Center - return FreeCAD.Vector(center.x, center.y, 0) + """holePosition(obj, base, sub) ... returns a Vector for the position defined by the given features. + Note that the value for Z is set to 0.""" try: shape = base.Shape.getElement(sub) - if shape.ShapeType == 'Vertex': + if shape.ShapeType == "Vertex": return FreeCAD.Vector(shape.X, shape.Y, 0) - if shape.ShapeType == 'Edge' and hasattr(shape.Curve, 'Center'): + if shape.ShapeType == "Edge" and hasattr(shape.Curve, "Center"): return FreeCAD.Vector(shape.Curve.Center.x, shape.Curve.Center.y, 0) - if shape.ShapeType == 'Face': - if hasattr(shape.Surface, 'Center'): - return FreeCAD.Vector(shape.Surface.Center.x, shape.Surface.Center.y, 0) + if shape.ShapeType == "Face": + if hasattr(shape.Surface, "Center"): + return FreeCAD.Vector( + shape.Surface.Center.x, shape.Surface.Center.y, 0 + ) if len(shape.Edges) == 1 and type(shape.Edges[0].Curve) == Part.Circle: return shape.Edges[0].Curve.Center except Part.OCCError as e: PathLog.error(e) - PathLog.error(translate("Path", "Feature %s.%s cannot be processed as a circular hole - please remove from Base geometry list.") % (base.Label, sub)) + PathLog.error( + translate( + "Path", + "Feature %s.%s cannot be processed as a circular hole - please remove from Base geometry list.", + ) + % (base.Label, sub) + ) return None def isHoleEnabled(self, obj, base, sub): - '''isHoleEnabled(obj, base, sub) ... return true if hole is enabled.''' + """isHoleEnabled(obj, base, sub) ... return true if hole is enabled.""" name = "%s.%s" % (base.Name, sub) - return not name in obj.Disabled + return name not in obj.Disabled def opExecute(self, obj): - '''opExecute(obj) ... processes all Base features and Locations and collects + """opExecute(obj) ... processes all Base features and Locations and collects them in a list of positions and radii which is then passed to circularHoleExecute(obj, holes). If no Base geometries and no Locations are present, the job's Base is inspected and all drillable features are added to Base. In this case appropriate values for depths are also calculated and assigned. - Do not overwrite, implement circularHoleExecute(obj, holes) instead.''' + Do not overwrite, implement circularHoleExecute(obj, holes) instead.""" PathLog.track() def haveLocations(self, obj): @@ -182,80 +175,91 @@ class ObjectOp(PathOp.ObjectOp): for base, subs in obj.Base: for sub in subs: - PathLog.debug('processing {} in {}'.format(sub, base.Name)) + PathLog.debug("processing {} in {}".format(sub, base.Name)) if self.isHoleEnabled(obj, base, sub): pos = self.holePosition(obj, base, sub) if pos: - holes.append({'x': pos.x, 'y': pos.y, 'r': self.holeDiameter(obj, base, sub)}) + holes.append( + { + "x": pos.x, + "y": pos.y, + "r": self.holeDiameter(obj, base, sub), + } + ) if haveLocations(self, obj): for location in obj.Locations: - holes.append({'x': location.x, 'y': location.y, 'r': 0}) + holes.append({"x": location.x, "y": location.y, "r": 0}) if len(holes) > 0: self.circularHoleExecute(obj, holes) def circularHoleExecute(self, obj, holes): - '''circularHoleExecute(obj, holes) ... implement processing of holes. + """circularHoleExecute(obj, holes) ... implement processing of holes. holes is a list of dictionaries with 'x', 'y' and 'r' specified for each hole. Note that for Vertexes, non-circular Edges and Locations r=0. - Must be overwritten by subclasses.''' + Must be overwritten by subclasses.""" pass def findAllHoles(self, obj): - '''findAllHoles(obj) ... find all holes of all base models and assign as features.''' + """findAllHoles(obj) ... find all holes of all base models and assign as features.""" PathLog.track() if not self.getJob(obj): return features = [] - if 1 == len(self.model) and self.baseIsArchPanel(obj, self.model[0]): - panel = self.model[0] - holeshapes = panel.Proxy.getHoles(panel, transform=True) - tooldiameter = float(obj.ToolController.Proxy.getTool(obj.ToolController).Diameter) - for holeNr, hole in enumerate(holeshapes): - PathLog.debug('Entering new HoleShape') - for wireNr, wire in enumerate(hole.Wires): - PathLog.debug('Entering new Wire') - for edgeNr, edge in enumerate(wire.Edges): - if PathUtils.isDrillable(panel, edge, tooldiameter): - PathLog.debug('Found drillable hole edges: {}'.format(edge)) - features.append((panel, "%d.%d.%d" % (holeNr, wireNr, edgeNr))) - else: - for base in self.model: - features.extend(self.findHoles(obj, base)) + for base in self.model: + features.extend(self.findHoles(obj, base)) obj.Base = features obj.Disabled = [] def findHoles(self, obj, baseobject): - '''findHoles(obj, baseobject) ... inspect baseobject and identify all features that resemble a straight cricular hole.''' + """findHoles(obj, baseobject) ... inspect baseobject and identify all features that resemble a straight cricular hole.""" shape = baseobject.Shape - PathLog.track('obj: {} shape: {}'.format(obj, shape)) + PathLog.track("obj: {} shape: {}".format(obj, shape)) holelist = [] features = [] # tooldiameter = float(obj.ToolController.Proxy.getTool(obj.ToolController).Diameter) tooldiameter = None - PathLog.debug('search for holes larger than tooldiameter: {}: '.format(tooldiameter)) + PathLog.debug( + "search for holes larger than tooldiameter: {}: ".format(tooldiameter) + ) if DraftGeomUtils.isPlanar(shape): PathLog.debug("shape is planar") for i in range(len(shape.Edges)): candidateEdgeName = "Edge" + str(i + 1) e = shape.getElement(candidateEdgeName) if PathUtils.isDrillable(shape, e, tooldiameter): - PathLog.debug('edge candidate: {} (hash {})is drillable '.format(e, e.hashCode())) + PathLog.debug( + "edge candidate: {} (hash {})is drillable ".format( + e, e.hashCode() + ) + ) x = e.Curve.Center.x y = e.Curve.Center.y diameter = e.BoundBox.XLength - holelist.append({'featureName': candidateEdgeName, 'feature': e, 'x': x, 'y': y, 'd': diameter, 'enabled': True}) + holelist.append( + { + "featureName": candidateEdgeName, + "feature": e, + "x": x, + "y": y, + "d": diameter, + "enabled": True, + } + ) features.append((baseobject, candidateEdgeName)) - PathLog.debug("Found hole feature %s.%s" % (baseobject.Label, candidateEdgeName)) + PathLog.debug( + "Found hole feature %s.%s" + % (baseobject.Label, candidateEdgeName) + ) else: PathLog.debug("shape is not planar") for i in range(len(shape.Faces)): candidateFaceName = "Face" + str(i + 1) f = shape.getElement(candidateFaceName) if PathUtils.isDrillable(shape, f, tooldiameter): - PathLog.debug('face candidate: {} is drillable '.format(f)) - if hasattr(f.Surface, 'Center'): + PathLog.debug("face candidate: {} is drillable ".format(f)) + if hasattr(f.Surface, "Center"): x = f.Surface.Center.x y = f.Surface.Center.y diameter = f.BoundBox.XLength @@ -264,9 +268,21 @@ class ObjectOp(PathOp.ObjectOp): x = center.x y = center.y diameter = f.Edges[0].Curve.Radius * 2 - holelist.append({'featureName': candidateFaceName, 'feature': f, 'x': x, 'y': y, 'd': diameter, 'enabled': True}) + holelist.append( + { + "featureName": candidateFaceName, + "feature": f, + "x": x, + "y": y, + "d": diameter, + "enabled": True, + } + ) features.append((baseobject, candidateFaceName)) - PathLog.debug("Found hole feature %s.%s" % (baseobject.Label, candidateFaceName)) + PathLog.debug( + "Found hole feature %s.%s" + % (baseobject.Label, candidateFaceName) + ) PathLog.debug("holes found: {}".format(holelist)) - return features \ No newline at end of file + return features diff --git a/src/Mod/Path/PathScripts/PathEngrave.py b/src/Mod/Path/PathScripts/PathEngrave.py index b60d2c977e..848b40a067 100644 --- a/src/Mod/Path/PathScripts/PathEngrave.py +++ b/src/Mod/Path/PathScripts/PathEngrave.py @@ -31,13 +31,13 @@ from PySide import QtCore # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader -ArchPanel = LazyLoader('ArchPanel', globals(), 'ArchPanel') -Part = LazyLoader('Part', globals(), 'Part') + +Part = LazyLoader("Part", globals(), "Part") __doc__ = "Class and implementation of Path Engrave operation" PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -#PathLog.trackModule(PathLog.thisModule()) +# PathLog.trackModule(PathLog.thisModule()) # Qt translation handling @@ -46,27 +46,55 @@ def translate(context, text, disambig=None): class ObjectEngrave(PathEngraveBase.ObjectOp): - '''Proxy class for Engrave operation.''' + """Proxy class for Engrave operation.""" def __init__(self, obj, name, parentJob): super(ObjectEngrave, self).__init__(obj, name, parentJob) self.wires = [] def opFeatures(self, obj): - '''opFeatures(obj) ... return all standard features and edges based geomtries''' - return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureHeights | PathOp.FeatureStepDown | PathOp.FeatureBaseEdges | PathOp.FeatureCoolant + """opFeatures(obj) ... return all standard features and edges based geomtries""" + return ( + PathOp.FeatureTool + | PathOp.FeatureDepths + | PathOp.FeatureHeights + | PathOp.FeatureStepDown + | PathOp.FeatureBaseEdges + | PathOp.FeatureCoolant + ) def setupAdditionalProperties(self, obj): - if not hasattr(obj, 'BaseShapes'): - obj.addProperty("App::PropertyLinkList", "BaseShapes", "Path", QtCore.QT_TRANSLATE_NOOP("PathEngrave", "Additional base objects to be engraved")) - obj.setEditorMode('BaseShapes', 2) # hide - if not hasattr(obj, 'BaseObject'): - obj.addProperty("App::PropertyLink", "BaseObject", "Path", QtCore.QT_TRANSLATE_NOOP("PathEngrave", "Additional base objects to be engraved")) - obj.setEditorMode('BaseObject', 2) # hide + if not hasattr(obj, "BaseShapes"): + obj.addProperty( + "App::PropertyLinkList", + "BaseShapes", + "Path", + QtCore.QT_TRANSLATE_NOOP( + "PathEngrave", "Additional base objects to be engraved" + ), + ) + obj.setEditorMode("BaseShapes", 2) # hide + if not hasattr(obj, "BaseObject"): + obj.addProperty( + "App::PropertyLink", + "BaseObject", + "Path", + QtCore.QT_TRANSLATE_NOOP( + "PathEngrave", "Additional base objects to be engraved" + ), + ) + obj.setEditorMode("BaseObject", 2) # hide def initOperation(self, obj): - '''initOperation(obj) ... create engraving specific properties.''' - obj.addProperty("App::PropertyInteger", "StartVertex", "Path", QtCore.QT_TRANSLATE_NOOP("PathEngrave", "The vertex index to start the path from")) + """initOperation(obj) ... create engraving specific properties.""" + obj.addProperty( + "App::PropertyInteger", + "StartVertex", + "Path", + QtCore.QT_TRANSLATE_NOOP( + "PathEngrave", "The vertex index to start the path from" + ), + ) self.setupAdditionalProperties(obj) def opOnDocumentRestored(self, obj): @@ -74,7 +102,7 @@ class ObjectEngrave(PathEngraveBase.ObjectOp): self.setupAdditionalProperties(obj) def opExecute(self, obj): - '''opExecute(obj) ... process engraving operation''' + """opExecute(obj) ... process engraving operation""" PathLog.track() jobshapes = [] @@ -106,36 +134,36 @@ class ObjectEngrave(PathEngraveBase.ObjectOp): PathLog.track(self.model) for base in self.model: PathLog.track(base.Label) - if base.isDerivedFrom('Part::Part2DObject'): + if base.isDerivedFrom("Part::Part2DObject"): jobshapes.append(base.Shape) - elif base.isDerivedFrom('Sketcher::SketchObject'): + elif base.isDerivedFrom("Sketcher::SketchObject"): jobshapes.append(base.Shape) - elif hasattr(base, 'ArrayType'): + elif hasattr(base, "ArrayType"): jobshapes.append(base.Shape) - elif isinstance(base.Proxy, ArchPanel.PanelSheet): - for tag in self.model[0].Proxy.getTags(self.model[0], transform=True): - tagWires = [] - for w in tag.Wires: - tagWires.append(Part.Wire(w.Edges)) - jobshapes.append(Part.makeCompound(tagWires)) if len(jobshapes) > 0: - PathLog.debug('processing {} jobshapes'.format(len(jobshapes))) + PathLog.debug("processing {} jobshapes".format(len(jobshapes))) wires = [] for shape in jobshapes: shapeWires = shape.Wires - PathLog.debug('jobshape has {} edges'.format(len(shape.Edges))) - self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) + PathLog.debug("jobshape has {} edges".format(len(shape.Edges))) + self.commandlist.append( + Path.Command( + "G0", {"Z": obj.ClearanceHeight.Value, "F": self.vertRapid} + ) + ) self.buildpathocc(obj, shapeWires, self.getZValues(obj)) wires.extend(shapeWires) self.wires = wires - PathLog.debug('processing {} jobshapes -> {} wires'.format(len(jobshapes), len(wires))) + PathLog.debug( + "processing {} jobshapes -> {} wires".format(len(jobshapes), len(wires)) + ) # the last command is a move to clearance, which is automatically added by PathOp if self.commandlist: self.commandlist.pop() def opUpdateDepths(self, obj): - '''updateDepths(obj) ... engraving is always done at the top most z-value''' + """updateDepths(obj) ... engraving is always done at the top most z-value""" job = PathUtils.findParentJob(obj) self.opSetDefaultValues(obj, job) diff --git a/src/Mod/Path/PathScripts/PathHelix.py b/src/Mod/Path/PathScripts/PathHelix.py index b74669f53f..a19910ed8c 100644 --- a/src/Mod/Path/PathScripts/PathHelix.py +++ b/src/Mod/Path/PathScripts/PathHelix.py @@ -51,7 +51,7 @@ class ObjectHelix(PathCircularHoleBase.ObjectOp): def circularHoleFeatures(self, obj): '''circularHoleFeatures(obj) ... enable features supported by Helix.''' - return PathOp.FeatureStepDown | PathOp.FeatureBaseEdges | PathOp.FeatureBaseFaces | PathOp.FeatureBasePanels + return PathOp.FeatureStepDown | PathOp.FeatureBaseEdges | PathOp.FeatureBaseFaces def initCircularHoleOperation(self, obj): '''initCircularHoleOperation(obj) ... create helix specific properties.''' diff --git a/src/Mod/Path/PathScripts/PathJob.py b/src/Mod/Path/PathScripts/PathJob.py index eaf736fb6f..2d7aec834a 100644 --- a/src/Mod/Path/PathScripts/PathJob.py +++ b/src/Mod/Path/PathScripts/PathJob.py @@ -20,6 +20,8 @@ # * * # *************************************************************************** +from PathScripts.PathPostProcessor import PostProcessor +from PySide import QtCore import FreeCAD import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences @@ -29,13 +31,11 @@ import PathScripts.PathToolController as PathToolController import PathScripts.PathUtil as PathUtil import json import time -from PathScripts.PathPostProcessor import PostProcessor -from PySide import QtCore # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader -ArchPanel = LazyLoader('ArchPanel', globals(), 'ArchPanel') -Draft = LazyLoader('Draft', globals(), 'Draft') + +Draft = LazyLoader("Draft", globals(), "Draft") PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) @@ -48,45 +48,41 @@ def translate(context, text, disambig=None): class JobTemplate: # pylint: disable=no-init - '''Attribute and sub element strings for template export/import.''' - Description = 'Desc' - GeometryTolerance = 'Tolerance' - Job = 'Job' - PostProcessor = 'Post' - PostProcessorArgs = 'PostArgs' - PostProcessorOutputFile = 'Output' - Fixtures = 'Fixtures' - OrderOutputBy = 'OrderOutputBy' - SplitOutput = 'SplitOutput' - SetupSheet = 'SetupSheet' - Stock = 'Stock' + """Attribute and sub element strings for template export/import.""" + Description = "Desc" + GeometryTolerance = "Tolerance" + Job = "Job" + PostProcessor = "Post" + PostProcessorArgs = "PostArgs" + PostProcessorOutputFile = "Output" + Fixtures = "Fixtures" + OrderOutputBy = "OrderOutputBy" + SplitOutput = "SplitOutput" + SetupSheet = "SetupSheet" + Stock = "Stock" # TCs are grouped under Tools in a job, the template refers to them directly though - ToolController = 'ToolController' - Version = 'Version' - - -def isArchPanelSheet(obj): - return hasattr(obj, 'Proxy') and isinstance(obj.Proxy, ArchPanel.PanelSheet) + ToolController = "ToolController" + Version = "Version" def isResourceClone(obj, propLink, resourceName): # pylint: disable=unused-argument - if hasattr(propLink, 'PathResource') and (resourceName is None or resourceName == propLink.PathResource): + if hasattr(propLink, "PathResource") and ( + resourceName is None or resourceName == propLink.PathResource + ): return True return False def createResourceClone(obj, orig, name, icon): - if isArchPanelSheet(orig): - # can't clone panel sheets - they have to be panel sheets - return orig clone = Draft.clone(orig) clone.Label = "%s-%s" % (name, orig.Label) - clone.addProperty('App::PropertyString', 'PathResource') + clone.addProperty("App::PropertyString", "PathResource") clone.PathResource = name if clone.ViewObject: import PathScripts.PathIconViewProvider + PathScripts.PathIconViewProvider.Attach(clone.ViewObject, icon) clone.ViewObject.Visibility = False clone.ViewObject.Transparency = 80 @@ -95,7 +91,7 @@ def createResourceClone(obj, orig, name, icon): def createModelResourceClone(obj, orig): - return createResourceClone(obj, orig, 'Model', 'BaseGeometry') + return createResourceClone(obj, orig, "Model", "BaseGeometry") class NotificationClass(QtCore.QObject): @@ -106,30 +102,108 @@ Notification = NotificationClass() class ObjectJob: - def __init__(self, obj, models, templateFile=None): self.obj = obj - obj.addProperty("App::PropertyFile", "PostProcessorOutputFile", "Output", QtCore.QT_TRANSLATE_NOOP("PathJob", "The NC output file for this project")) - obj.addProperty("App::PropertyEnumeration", "PostProcessor", "Output", QtCore.QT_TRANSLATE_NOOP("PathJob", "Select the Post Processor")) - obj.addProperty("App::PropertyString", "PostProcessorArgs", "Output", QtCore.QT_TRANSLATE_NOOP("PathJob", "Arguments for the Post Processor (specific to the script)")) - obj.addProperty("App::PropertyString", "LastPostProcessDate", "Output", QtCore.QT_TRANSLATE_NOOP("PathJob", "Last Time the Job was post-processed")) - obj.setEditorMode('LastPostProcessDate', 2) # Hide - obj.addProperty("App::PropertyString", "LastPostProcessOutput", "Output", QtCore.QT_TRANSLATE_NOOP("PathJob", "Last Time the Job was post-processed")) - obj.setEditorMode('LastPostProcessOutput', 2) # Hide + obj.addProperty( + "App::PropertyFile", + "PostProcessorOutputFile", + "Output", + QtCore.QT_TRANSLATE_NOOP("PathJob", "The NC output file for this project"), + ) + obj.addProperty( + "App::PropertyEnumeration", + "PostProcessor", + "Output", + QtCore.QT_TRANSLATE_NOOP("PathJob", "Select the Post Processor"), + ) + obj.addProperty( + "App::PropertyString", + "PostProcessorArgs", + "Output", + QtCore.QT_TRANSLATE_NOOP( + "PathJob", "Arguments for the Post Processor (specific to the script)" + ), + ) + obj.addProperty( + "App::PropertyString", + "LastPostProcessDate", + "Output", + QtCore.QT_TRANSLATE_NOOP("PathJob", "Last Time the Job was post-processed"), + ) + obj.setEditorMode("LastPostProcessDate", 2) # Hide + obj.addProperty( + "App::PropertyString", + "LastPostProcessOutput", + "Output", + QtCore.QT_TRANSLATE_NOOP("PathJob", "Last Time the Job was post-processed"), + ) + obj.setEditorMode("LastPostProcessOutput", 2) # Hide - obj.addProperty("App::PropertyString", "Description", "Path", QtCore.QT_TRANSLATE_NOOP("PathJob", "An optional description for this job")) - obj.addProperty("App::PropertyString", "CycleTime", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "Job Cycle Time Estimation")) - obj.setEditorMode('CycleTime', 1) # read-only - obj.addProperty("App::PropertyDistance", "GeometryTolerance", "Geometry", QtCore.QT_TRANSLATE_NOOP("PathJob", "For computing Paths; smaller increases accuracy, but slows down computation")) + obj.addProperty( + "App::PropertyString", + "Description", + "Path", + QtCore.QT_TRANSLATE_NOOP("PathJob", "An optional description for this job"), + ) + obj.addProperty( + "App::PropertyString", + "CycleTime", + "Path", + QtCore.QT_TRANSLATE_NOOP("PathOp", "Job Cycle Time Estimation"), + ) + obj.setEditorMode("CycleTime", 1) # read-only + obj.addProperty( + "App::PropertyDistance", + "GeometryTolerance", + "Geometry", + QtCore.QT_TRANSLATE_NOOP( + "PathJob", + "For computing Paths; smaller increases accuracy, but slows down computation", + ), + ) - obj.addProperty("App::PropertyLink", "Stock", "Base", QtCore.QT_TRANSLATE_NOOP("PathJob", "Solid object to be used as stock.")) - obj.addProperty("App::PropertyLink", "Operations", "Base", QtCore.QT_TRANSLATE_NOOP("PathJob", "Compound path of all operations in the order they are processed.")) + obj.addProperty( + "App::PropertyLink", + "Stock", + "Base", + QtCore.QT_TRANSLATE_NOOP("PathJob", "Solid object to be used as stock."), + ) + obj.addProperty( + "App::PropertyLink", + "Operations", + "Base", + QtCore.QT_TRANSLATE_NOOP( + "PathJob", + "Compound path of all operations in the order they are processed.", + ), + ) - obj.addProperty("App::PropertyBool", "SplitOutput", "Output", QtCore.QT_TRANSLATE_NOOP("PathJob", "Split output into multiple gcode files")) - obj.addProperty("App::PropertyEnumeration", "OrderOutputBy", "WCS", QtCore.QT_TRANSLATE_NOOP("PathJob", "If multiple WCS, order the output this way")) - obj.addProperty("App::PropertyStringList", "Fixtures", "WCS", QtCore.QT_TRANSLATE_NOOP("PathJob", "The Work Coordinate Systems for the Job")) - obj.OrderOutputBy = ['Fixture', 'Tool', 'Operation'] - obj.Fixtures = ['G54'] + obj.addProperty( + "App::PropertyBool", + "SplitOutput", + "Output", + QtCore.QT_TRANSLATE_NOOP( + "PathJob", "Split output into multiple gcode files" + ), + ) + obj.addProperty( + "App::PropertyEnumeration", + "OrderOutputBy", + "WCS", + QtCore.QT_TRANSLATE_NOOP( + "PathJob", "If multiple WCS, order the output this way" + ), + ) + obj.addProperty( + "App::PropertyStringList", + "Fixtures", + "WCS", + QtCore.QT_TRANSLATE_NOOP( + "PathJob", "The Work Coordinate Systems for the Job" + ), + ) + obj.OrderOutputBy = ["Fixture", "Tool", "Operation"] + obj.Fixtures = ["G54"] obj.PostProcessorOutputFile = PathPreferences.defaultOutputFile() obj.PostProcessor = postProcessors = PathPreferences.allEnabledPostProcessors() @@ -142,14 +216,16 @@ class ObjectJob: obj.PostProcessorArgs = PathPreferences.defaultPostProcessorArgs() obj.GeometryTolerance = PathPreferences.defaultGeometryTolerance() - ops = FreeCAD.ActiveDocument.addObject("Path::FeatureCompoundPython", "Operations") + ops = FreeCAD.ActiveDocument.addObject( + "Path::FeatureCompoundPython", "Operations" + ) if ops.ViewObject: ops.ViewObject.Proxy = 0 ops.ViewObject.Visibility = False obj.Operations = ops - obj.setEditorMode('Operations', 2) # hide - obj.setEditorMode('Placement', 2) + obj.setEditorMode("Operations", 2) # hide + obj.setEditorMode("Placement", 2) self.setupSetupSheet(obj) self.setupBaseModel(obj, models) @@ -171,41 +247,73 @@ class ObjectJob: obj.Stock.ViewObject.Visibility = False def setupSetupSheet(self, obj): - if not getattr(obj, 'SetupSheet', None): - obj.addProperty('App::PropertyLink', 'SetupSheet', 'Base', QtCore.QT_TRANSLATE_NOOP('PathJob', 'SetupSheet holding the settings for this job')) + if not getattr(obj, "SetupSheet", None): + obj.addProperty( + "App::PropertyLink", + "SetupSheet", + "Base", + QtCore.QT_TRANSLATE_NOOP( + "PathJob", "SetupSheet holding the settings for this job" + ), + ) obj.SetupSheet = PathSetupSheet.Create() if obj.SetupSheet.ViewObject: import PathScripts.PathIconViewProvider - PathScripts.PathIconViewProvider.Attach(obj.SetupSheet.ViewObject, 'SetupSheet') + + PathScripts.PathIconViewProvider.Attach( + obj.SetupSheet.ViewObject, "SetupSheet" + ) self.setupSheet = obj.SetupSheet.Proxy def setupBaseModel(self, obj, models=None): PathLog.track(obj.Label, models) - if not hasattr(obj, 'Model'): - obj.addProperty("App::PropertyLink", "Model", "Base", QtCore.QT_TRANSLATE_NOOP("PathJob", "The base objects for all operations")) - model = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", "Model") + if not hasattr(obj, "Model"): + obj.addProperty( + "App::PropertyLink", + "Model", + "Base", + QtCore.QT_TRANSLATE_NOOP( + "PathJob", "The base objects for all operations" + ), + ) + model = FreeCAD.ActiveDocument.addObject( + "App::DocumentObjectGroup", "Model" + ) if model.ViewObject: model.ViewObject.Visibility = False if models: - model.addObjects([createModelResourceClone(obj, base) for base in models]) + model.addObjects( + [createModelResourceClone(obj, base) for base in models] + ) obj.Model = model - if hasattr(obj, 'Base'): - PathLog.info("Converting Job.Base to new Job.Model for {}".format(obj.Label)) + if hasattr(obj, "Base"): + PathLog.info( + "Converting Job.Base to new Job.Model for {}".format(obj.Label) + ) obj.Model.addObject(obj.Base) obj.Base = None - obj.removeProperty('Base') + obj.removeProperty("Base") def setupToolTable(self, obj): - if not hasattr(obj, 'Tools'): - obj.addProperty("App::PropertyLink", "Tools", "Base", QtCore.QT_TRANSLATE_NOOP("PathJob", "Collection of all tool controllers for the job")) - toolTable = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", "Tools") - toolTable.Label = 'Tools' + if not hasattr(obj, "Tools"): + obj.addProperty( + "App::PropertyLink", + "Tools", + "Base", + QtCore.QT_TRANSLATE_NOOP( + "PathJob", "Collection of all tool controllers for the job" + ), + ) + toolTable = FreeCAD.ActiveDocument.addObject( + "App::DocumentObjectGroup", "Tools" + ) + toolTable.Label = "Tools" if toolTable.ViewObject: toolTable.ViewObject.Visibility = False - if hasattr(obj, 'ToolController'): + if hasattr(obj, "ToolController"): toolTable.addObjects(obj.ToolController) - obj.removeProperty('ToolController') + obj.removeProperty("ToolController") obj.Tools = toolTable def removeBase(self, obj, base, removeFromModel): @@ -219,16 +327,22 @@ class ObjectJob: return PathStock.shapeBoundBox(obj.Model.Group) def onDelete(self, obj, arg2=None): - '''Called by the view provider, there doesn't seem to be a callback on the obj itself.''' + """Called by the view provider, there doesn't seem to be a callback on the obj itself.""" PathLog.track(obj.Label, arg2) doc = obj.Document - if getattr(obj, 'Operations', None): + if getattr(obj, "Operations", None): # the first to tear down are the ops, they depend on other resources - PathLog.debug('taking down ops: %s' % [o.Name for o in self.allOperations()]) + PathLog.debug( + "taking down ops: %s" % [o.Name for o in self.allOperations()] + ) while obj.Operations.Group: op = obj.Operations.Group[0] - if not op.ViewObject or not hasattr(op.ViewObject.Proxy, 'onDelete') or op.ViewObject.Proxy.onDelete(op.ViewObject, ()): + if ( + not op.ViewObject + or not hasattr(op.ViewObject.Proxy, "onDelete") + or op.ViewObject.Proxy.onDelete(op.ViewObject, ()) + ): PathUtil.clearExpressionEngine(op) doc.removeObject(op.Name) obj.Operations.Group = [] @@ -236,14 +350,14 @@ class ObjectJob: obj.Operations = None # stock could depend on Model, so delete it first - if getattr(obj, 'Stock', None): - PathLog.debug('taking down stock') + if getattr(obj, "Stock", None): + PathLog.debug("taking down stock") PathUtil.clearExpressionEngine(obj.Stock) doc.removeObject(obj.Stock.Name) obj.Stock = None # base doesn't depend on anything inside job - if getattr(obj, 'Model', None): + if getattr(obj, "Model", None): for base in obj.Model.Group: PathLog.debug("taking down base %s" % base.Label) self.removeBase(obj, base, False) @@ -252,8 +366,8 @@ class ObjectJob: obj.Model = None # Tool controllers might refer to either legacy tool or toolbit - if getattr(obj, 'Tools', None): - PathLog.debug('taking down tool controller') + if getattr(obj, "Tools", None): + PathLog.debug("taking down tool controller") for tc in obj.Tools.Group: if hasattr(tc.Tool, "Proxy"): PathUtil.clearExpressionEngine(tc.Tool) @@ -266,7 +380,7 @@ class ObjectJob: obj.Tools = None # SetupSheet - if getattr(obj, 'SetupSheet', None): + if getattr(obj, "SetupSheet", None): PathUtil.clearExpressionEngine(obj.SetupSheet) doc.removeObject(obj.SetupSheet.Name) obj.SetupSheet = None @@ -274,13 +388,15 @@ class ObjectJob: return True def fixupOperations(self, obj): - if getattr(obj.Operations, 'ViewObject', None): + if getattr(obj.Operations, "ViewObject", None): try: obj.Operations.ViewObject.DisplayMode except Exception: # pylint: disable=broad-except name = obj.Operations.Name label = obj.Operations.Label - ops = FreeCAD.ActiveDocument.addObject("Path::FeatureCompoundPython", "Operations") + ops = FreeCAD.ActiveDocument.addObject( + "Path::FeatureCompoundPython", "Operations" + ) ops.ViewObject.Proxy = 0 ops.Group = obj.Operations.Group obj.Operations.Group = [] @@ -294,23 +410,49 @@ class ObjectJob: self.setupSetupSheet(obj) self.setupToolTable(obj) - obj.setEditorMode('Operations', 2) # hide - obj.setEditorMode('Placement', 2) + obj.setEditorMode("Operations", 2) # hide + obj.setEditorMode("Placement", 2) - if not hasattr(obj, 'CycleTime'): - obj.addProperty("App::PropertyString", "CycleTime", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "Operations Cycle Time Estimation")) - obj.setEditorMode('CycleTime', 1) # read-only + if not hasattr(obj, "CycleTime"): + obj.addProperty( + "App::PropertyString", + "CycleTime", + "Path", + QtCore.QT_TRANSLATE_NOOP("PathOp", "Operations Cycle Time Estimation"), + ) + obj.setEditorMode("CycleTime", 1) # read-only if not hasattr(obj, "Fixtures"): - obj.addProperty("App::PropertyStringList", "Fixtures", "WCS", QtCore.QT_TRANSLATE_NOOP("PathJob", "The Work Coordinate Systems for the Job")) - obj.Fixtures = ['G54'] + obj.addProperty( + "App::PropertyStringList", + "Fixtures", + "WCS", + QtCore.QT_TRANSLATE_NOOP( + "PathJob", "The Work Coordinate Systems for the Job" + ), + ) + obj.Fixtures = ["G54"] if not hasattr(obj, "OrderOutputBy"): - obj.addProperty("App::PropertyEnumeration", "OrderOutputBy", "WCS", QtCore.QT_TRANSLATE_NOOP("PathJob", "If multiple WCS, order the output this way")) - obj.OrderOutputBy = ['Fixture', 'Tool', 'Operation'] + obj.addProperty( + "App::PropertyEnumeration", + "OrderOutputBy", + "WCS", + QtCore.QT_TRANSLATE_NOOP( + "PathJob", "If multiple WCS, order the output this way" + ), + ) + obj.OrderOutputBy = ["Fixture", "Tool", "Operation"] if not hasattr(obj, "SplitOutput"): - obj.addProperty("App::PropertyBool", "SplitOutput", "Output", QtCore.QT_TRANSLATE_NOOP("PathJob", "Split output into multiple gcode files")) + obj.addProperty( + "App::PropertyBool", + "SplitOutput", + "Output", + QtCore.QT_TRANSLATE_NOOP( + "PathJob", "Split output into multiple gcode files" + ), + ) obj.SplitOutput = False def onChanged(self, obj, prop): @@ -320,17 +462,17 @@ class ObjectJob: self.tooltipArgs = processor.tooltipArgs def baseObject(self, obj, base): - '''Return the base object, not its clone.''' - if isResourceClone(obj, base, 'Model') or isResourceClone(obj, base, 'Base'): + """Return the base object, not its clone.""" + if isResourceClone(obj, base, "Model") or isResourceClone(obj, base, "Base"): return base.Objects[0] return base def baseObjects(self, obj): - '''Return the base objects, not their clones.''' + """Return the base objects, not their clones.""" return [self.baseObject(obj, base) for base in obj.Model.Group] def resourceClone(self, obj, base): - '''resourceClone(obj, base) ... Return the resource clone for base if it exists.''' + """resourceClone(obj, base) ... Return the resource clone for base if it exists.""" if isResourceClone(obj, base, None): return base for b in obj.Model.Group: @@ -339,11 +481,11 @@ class ObjectJob: return None def setFromTemplateFile(self, obj, template): - '''setFromTemplateFile(obj, template) ... extract the properties from the given template file and assign to receiver. - This will also create any TCs stored in the template.''' + """setFromTemplateFile(obj, template) ... extract the properties from the given template file and assign to receiver. + This will also create any TCs stored in the template.""" tcs = [] if template: - with open(PathUtil.toUnicode(template), 'rb') as fp: + with open(PathUtil.toUnicode(template), "rb") as fp: attrs = json.load(fp) if attrs.get(JobTemplate.Version) and 1 == int(attrs[JobTemplate.Version]): @@ -352,15 +494,19 @@ class ObjectJob: self.setupSheet.setFromTemplate(attrs[JobTemplate.SetupSheet]) if attrs.get(JobTemplate.GeometryTolerance): - obj.GeometryTolerance = float(attrs.get(JobTemplate.GeometryTolerance)) + obj.GeometryTolerance = float( + attrs.get(JobTemplate.GeometryTolerance) + ) if attrs.get(JobTemplate.PostProcessor): obj.PostProcessor = attrs.get(JobTemplate.PostProcessor) if attrs.get(JobTemplate.PostProcessorArgs): obj.PostProcessorArgs = attrs.get(JobTemplate.PostProcessorArgs) else: - obj.PostProcessorArgs = '' + obj.PostProcessorArgs = "" if attrs.get(JobTemplate.PostProcessorOutputFile): - obj.PostProcessorOutputFile = attrs.get(JobTemplate.PostProcessorOutputFile) + obj.PostProcessorOutputFile = attrs.get( + JobTemplate.PostProcessorOutputFile + ) if attrs.get(JobTemplate.Description): obj.Description = attrs.get(JobTemplate.Description) @@ -368,10 +514,14 @@ class ObjectJob: for tc in attrs.get(JobTemplate.ToolController): tcs.append(PathToolController.FromTemplate(tc)) if attrs.get(JobTemplate.Stock): - obj.Stock = PathStock.CreateFromTemplate(obj, attrs.get(JobTemplate.Stock)) + obj.Stock = PathStock.CreateFromTemplate( + obj, attrs.get(JobTemplate.Stock) + ) if attrs.get(JobTemplate.Fixtures): - obj.Fixtures = [x for y in attrs.get(JobTemplate.Fixtures) for x in y] + obj.Fixtures = [ + x for y in attrs.get(JobTemplate.Fixtures) for x in y + ] if attrs.get(JobTemplate.OrderOutputBy): obj.OrderOutputBy = attrs.get(JobTemplate.OrderOutputBy) @@ -382,12 +532,15 @@ class ObjectJob: PathLog.debug("setting tool controllers (%d)" % len(tcs)) obj.Tools.Group = tcs else: - PathLog.error(translate('PathJob', "Unsupported PathJob template version %s") % attrs.get(JobTemplate.Version)) + PathLog.error( + translate("PathJob", "Unsupported PathJob template version %s") + % attrs.get(JobTemplate.Version) + ) if not tcs: self.addToolController(PathToolController.Create()) def templateAttrs(self, obj): - '''templateAttrs(obj) ... answer a dictionary with all properties of the receiver that should be stored in a template file.''' + """templateAttrs(obj) ... answer a dictionary with all properties of the receiver that should be stored in a template file.""" attrs = {} attrs[JobTemplate.Version] = 1 if obj.PostProcessor: @@ -408,13 +561,13 @@ class ObjectJob: def __setstate__(self, state): for obj in FreeCAD.ActiveDocument.Objects: - if hasattr(obj, 'Proxy') and obj.Proxy == self: + if hasattr(obj, "Proxy") and obj.Proxy == self: self.obj = obj break return None def execute(self, obj): - if getattr(obj, 'Operations', None): + if getattr(obj, "Operations", None): obj.Path = obj.Operations.Path self.getCycleTime() @@ -425,18 +578,23 @@ class ObjectJob: for op in self.obj.Operations.Group: # Skip inactive operations - if PathUtil.opProperty(op, 'Active') is False: + if PathUtil.opProperty(op, "Active") is False: continue # Skip operations that don't have a cycletime attribute - if PathUtil.opProperty(op, 'CycleTime') is None: + if PathUtil.opProperty(op, "CycleTime") is None: continue - formattedCycleTime = PathUtil.opProperty(op, 'CycleTime') + formattedCycleTime = PathUtil.opProperty(op, "CycleTime") opCycleTime = 0 try: # Convert the formatted time from HH:MM:SS to just seconds - opCycleTime = sum(x * int(t) for x, t in zip([1, 60, 3600], reversed(formattedCycleTime.split(":")))) + opCycleTime = sum( + x * int(t) + for x, t in zip( + [1, 60, 3600], reversed(formattedCycleTime.split(":")) + ) + ) except Exception: continue @@ -469,10 +627,26 @@ class ObjectJob: def addToolController(self, tc): group = self.obj.Tools.Group - PathLog.debug("addToolController(%s): %s" % (tc.Label, [t.Label for t in group])) + PathLog.debug( + "addToolController(%s): %s" % (tc.Label, [t.Label for t in group]) + ) if tc.Name not in [str(t.Name) for t in group]: - tc.setExpression('VertRapid', "%s.%s" % (self.setupSheet.expressionReference(), PathSetupSheet.Template.VertRapid)) - tc.setExpression('HorizRapid', "%s.%s" % (self.setupSheet.expressionReference(), PathSetupSheet.Template.HorizRapid)) + tc.setExpression( + "VertRapid", + "%s.%s" + % ( + self.setupSheet.expressionReference(), + PathSetupSheet.Template.VertRapid, + ), + ) + tc.setExpression( + "HorizRapid", + "%s.%s" + % ( + self.setupSheet.expressionReference(), + PathSetupSheet.Template.HorizRapid, + ), + ) self.obj.Tools.addObject(tc) Notification.updateTC.emit(self.obj, tc) @@ -480,17 +654,19 @@ class ObjectJob: ops = [] def collectBaseOps(op): - if hasattr(op, 'TypeId'): - if op.TypeId == 'Path::FeaturePython': + if hasattr(op, "TypeId"): + if op.TypeId == "Path::FeaturePython": ops.append(op) - if hasattr(op, 'Base'): + if hasattr(op, "Base"): collectBaseOps(op.Base) - if op.TypeId == 'Path::FeatureCompoundPython': + if op.TypeId == "Path::FeatureCompoundPython": ops.append(op) for sub in op.Group: collectBaseOps(sub) - if getattr(self.obj, 'Operations', None) and getattr(self.obj.Operations, 'Group', None): + if getattr(self.obj, "Operations", None) and getattr( + self.obj.Operations, "Group", None + ): for op in self.obj.Operations.Group: collectBaseOps(op) @@ -505,25 +681,32 @@ class ObjectJob: @classmethod def baseCandidates(cls): - '''Answer all objects in the current document which could serve as a Base for a job.''' - return sorted([obj for obj in FreeCAD.ActiveDocument.Objects if cls.isBaseCandidate(obj)], key=lambda o: o.Label) + """Answer all objects in the current document which could serve as a Base for a job.""" + return sorted( + [obj for obj in FreeCAD.ActiveDocument.Objects if cls.isBaseCandidate(obj)], + key=lambda o: o.Label, + ) @classmethod def isBaseCandidate(cls, obj): - '''Answer true if the given object can be used as a Base for a job.''' - return PathUtil.isValidBaseObject(obj) or isArchPanelSheet(obj) + """Answer true if the given object can be used as a Base for a job.""" + return PathUtil.isValidBaseObject(obj) def Instances(): - '''Instances() ... Return all Jobs in the current active document.''' + """Instances() ... Return all Jobs in the current active document.""" if FreeCAD.ActiveDocument: - return [job for job in FreeCAD.ActiveDocument.Objects if hasattr(job, 'Proxy') and isinstance(job.Proxy, ObjectJob)] + return [ + job + for job in FreeCAD.ActiveDocument.Objects + if hasattr(job, "Proxy") and isinstance(job.Proxy, ObjectJob) + ] return [] def Create(name, base, templateFile=None): - '''Create(name, base, templateFile=None) ... creates a new job and all it's resources. - If a template file is specified the new job is initialized with the values from the template.''' + """Create(name, base, templateFile=None) ... creates a new job and all it's resources. + If a template file is specified the new job is initialized with the values from the template.""" if isinstance(base[0], str): models = [] for baseName in base: diff --git a/src/Mod/Path/PathScripts/PathJobGui.py b/src/Mod/Path/PathScripts/PathJobGui.py index 327b676b30..b9b96c68af 100644 --- a/src/Mod/Path/PathScripts/PathJobGui.py +++ b/src/Mod/Path/PathScripts/PathJobGui.py @@ -21,37 +21,38 @@ # *************************************************************************** +from PySide import QtCore, QtGui from collections import Counter from contextlib import contextmanager +from pivy import coin +import json import math import traceback -from pivy import coin -from PySide import QtCore, QtGui -import json import FreeCAD import FreeCADGui +import PathScripts.PathGeom as PathGeom +import PathScripts.PathGuiInit as PathGuiInit import PathScripts.PathJob as PathJob import PathScripts.PathJobCmd as PathJobCmd import PathScripts.PathJobDlg as PathJobDlg -import PathScripts.PathGeom as PathGeom -import PathScripts.PathGuiInit as PathGuiInit import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences import PathScripts.PathSetupSheetGui as PathSetupSheetGui import PathScripts.PathStock as PathStock +import PathScripts.PathToolBitGui as PathToolBitGui import PathScripts.PathToolControllerGui as PathToolControllerGui import PathScripts.PathToolLibraryEditor as PathToolLibraryEditor import PathScripts.PathUtil as PathUtil import PathScripts.PathUtils as PathUtils -import PathScripts.PathToolBitGui as PathToolBitGui # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader -Draft = LazyLoader('Draft', globals(), 'Draft') -Part = LazyLoader('Part', globals(), 'Part') -DraftVecUtils = LazyLoader('DraftVecUtils', globals(), 'DraftVecUtils') + +Draft = LazyLoader("Draft", globals(), "Draft") +Part = LazyLoader("Part", globals(), "Part") +DraftVecUtils = LazyLoader("DraftVecUtils", globals(), "DraftVecUtils") # Qt translation handling @@ -71,11 +72,11 @@ def _OpenCloseResourceEditor(obj, vobj, edit): else: job.ViewObject.Proxy.uneditObject(obj) else: - missing = 'Job' + missing = "Job" if job: - missing = 'ViewObject' + missing = "ViewObject" if job.ViewObject: - missing = 'Proxy' + missing = "Proxy" PathLog.warning("Cannot edit %s - no %s" % (obj.Label, missing)) @@ -94,14 +95,13 @@ def selectionEx(): class ViewProvider: - def __init__(self, vobj): mode = 2 - vobj.setEditorMode('BoundingBox', mode) - vobj.setEditorMode('DisplayMode', mode) - vobj.setEditorMode('Selectable', mode) - vobj.setEditorMode('ShapeColor', mode) - vobj.setEditorMode('Transparency', mode) + vobj.setEditorMode("BoundingBox", mode) + vobj.setEditorMode("DisplayMode", mode) + vobj.setEditorMode("Selectable", mode) + vobj.setEditorMode("ShapeColor", mode) + vobj.setEditorMode("Transparency", mode) self.deleteOnReject = True # initialized later @@ -122,27 +122,27 @@ class ViewProvider: self.vobj = vobj self.obj = vobj.Object self.taskPanel = None - if not hasattr(self, 'baseVisibility'): + if not hasattr(self, "baseVisibility"): self.baseVisibility = {} - if not hasattr(self, 'stockVisibility'): + if not hasattr(self, "stockVisibility"): self.stockVisibility = False # setup the axis display at the origin self.switch = coin.SoSwitch() self.sep = coin.SoSeparator() - self.axs = coin.SoType.fromName('SoAxisCrossKit').createInstance() - self.axs.set('xHead.transform', 'scaleFactor 2 3 2') - self.axs.set('yHead.transform', 'scaleFactor 2 3 2') - self.axs.set('zHead.transform', 'scaleFactor 2 3 2') - self.sca = coin.SoType.fromName('SoShapeScale').createInstance() - self.sca.setPart('shape', self.axs) + self.axs = coin.SoType.fromName("SoAxisCrossKit").createInstance() + self.axs.set("xHead.transform", "scaleFactor 2 3 2") + self.axs.set("yHead.transform", "scaleFactor 2 3 2") + self.axs.set("zHead.transform", "scaleFactor 2 3 2") + self.sca = coin.SoType.fromName("SoShapeScale").createInstance() + self.sca.setPart("shape", self.axs) self.sca.scaleFactor.setValue(0.5) self.mat = coin.SoMaterial() self.mat.diffuseColor = coin.SbColor(0.9, 0, 0.9) self.mat.transparency = 0.85 self.sph = coin.SoSphere() - self.scs = coin.SoType.fromName('SoShapeScale').createInstance() - self.scs.setPart('shape', self.sph) + self.scs = coin.SoType.fromName("SoShapeScale").createInstance() + self.scs.setPart("shape", self.sph) self.scs.scaleFactor.setValue(10) self.sep.addChild(self.sca) self.sep.addChild(self.mat) @@ -162,7 +162,7 @@ class ViewProvider: return None def deleteObjectsOnReject(self): - return hasattr(self, 'deleteOnReject') and self.deleteOnReject + return hasattr(self, "deleteOnReject") and self.deleteOnReject def setEdit(self, vobj=None, mode=0): PathLog.track(mode) @@ -189,10 +189,12 @@ class ViewProvider: def editObject(self, obj): if obj: if obj in self.obj.Model.Group: - return self.openTaskPanel('Model') + return self.openTaskPanel("Model") if obj == self.obj.Stock: - return self.openTaskPanel('Stock') - PathLog.info("Expected a specific object to edit - %s not recognized" % obj.Label) + return self.openTaskPanel("Stock") + PathLog.info( + "Expected a specific object to edit - %s not recognized" % obj.Label + ) return self.openTaskPanel() def uneditObject(self, obj=None): @@ -204,17 +206,17 @@ class ViewProvider: def claimChildren(self): children = [] children.append(self.obj.Operations) - if hasattr(self.obj, 'Model'): + if hasattr(self.obj, "Model"): # unfortunately this function is called before the object has been fully loaded # which means we could be dealing with an old job which doesn't have the new Model # yet. children.append(self.obj.Model) if self.obj.Stock: children.append(self.obj.Stock) - if hasattr(self.obj, 'SetupSheet'): + if hasattr(self.obj, "SetupSheet"): # when loading a job that didn't have a setup sheet they might not've been created yet children.append(self.obj.SetupSheet) - if hasattr(self.obj, 'Tools'): + if hasattr(self.obj, "Tools"): children.append(self.obj.Tools) return children @@ -226,17 +228,30 @@ class ViewProvider: def updateData(self, obj, prop): PathLog.track(obj.Label, prop) # make sure the resource view providers are setup properly - if prop == 'Model' and self.obj.Model: + if prop == "Model" and self.obj.Model: for base in self.obj.Model.Group: - if base.ViewObject and base.ViewObject.Proxy and not PathJob.isArchPanelSheet(base): + if ( + base.ViewObject + and base.ViewObject.Proxy + ): base.ViewObject.Proxy.onEdit(_OpenCloseResourceEditor) - if prop == 'Stock' and self.obj.Stock and self.obj.Stock.ViewObject and self.obj.Stock.ViewObject.Proxy: + if ( + prop == "Stock" + and self.obj.Stock + and self.obj.Stock.ViewObject + and self.obj.Stock.ViewObject.Proxy + ): self.obj.Stock.ViewObject.Proxy.onEdit(_OpenCloseResourceEditor) def rememberBaseVisibility(self, obj, base): if base.ViewObject: orig = PathUtil.getPublicObject(obj.Proxy.baseObject(obj, base)) - self.baseVisibility[base.Name] = (base, base.ViewObject.Visibility, orig, orig.ViewObject.Visibility) + self.baseVisibility[base.Name] = ( + base, + base.ViewObject.Visibility, + orig, + orig.ViewObject.Visibility, + ) orig.ViewObject.Visibility = False base.ViewObject.Visibility = True @@ -267,7 +282,7 @@ class ViewProvider: PathLog.track() for action in menu.actions(): menu.removeAction(action) - action = QtGui.QAction(translate('Path', 'Edit'), menu) + action = QtGui.QAction(translate("Path", "Edit"), menu) action.triggered.connect(self.setEdit) menu.addAction(action) @@ -295,6 +310,7 @@ class StockEdit(object): widget.show() else: widget.hide() + if select: self.form.stock.setCurrentIndex(self.Index) editor = self.editorFrame() @@ -315,7 +331,9 @@ class StockEdit(object): stock.ViewObject.Proxy.onEdit(_OpenCloseResourceEditor) def setLengthField(self, widget, prop): - widget.setText(FreeCAD.Units.Quantity(prop.Value, FreeCAD.Units.Length).UserString) + widget.setText( + FreeCAD.Units.Quantity(prop.Value, FreeCAD.Units.Length).UserString + ) # the following members must be overwritten by subclasses def editorFrame(self): @@ -345,31 +363,31 @@ class StockFromBaseBoundBoxEdit(StockEdit): def getFieldsStock(self, stock, fields=None): if fields is None: - fields = ['xneg', 'xpos', 'yneg', 'ypos', 'zneg', 'zpos'] + fields = ["xneg", "xpos", "yneg", "ypos", "zneg", "zpos"] try: - if 'xneg' in fields: + if "xneg" in fields: stock.ExtXneg = FreeCAD.Units.Quantity(self.form.stockExtXneg.text()) - if 'xpos' in fields: + if "xpos" in fields: stock.ExtXpos = FreeCAD.Units.Quantity(self.form.stockExtXpos.text()) - if 'yneg' in fields: + if "yneg" in fields: stock.ExtYneg = FreeCAD.Units.Quantity(self.form.stockExtYneg.text()) - if 'ypos' in fields: + if "ypos" in fields: stock.ExtYpos = FreeCAD.Units.Quantity(self.form.stockExtYpos.text()) - if 'zneg' in fields: + if "zneg" in fields: stock.ExtZneg = FreeCAD.Units.Quantity(self.form.stockExtZneg.text()) - if 'zpos' in fields: + if "zpos" in fields: stock.ExtZpos = FreeCAD.Units.Quantity(self.form.stockExtZpos.text()) except Exception: pass def getFields(self, obj, fields=None): if fields is None: - fields = ['xneg', 'xpos', 'yneg', 'ypos', 'zneg', 'zpos'] + fields = ["xneg", "xpos", "yneg", "ypos", "zneg", "zpos"] PathLog.track(obj.Label, fields) if self.IsStock(obj): self.getFieldsStock(obj.Stock, fields) else: - PathLog.error(translate('PathJob', 'Stock not from Base bound box!')) + PathLog.error(translate("PathJob", "Stock not from Base bound box!")) def setFields(self, obj): PathLog.track() @@ -399,40 +417,40 @@ class StockFromBaseBoundBoxEdit(StockEdit): self.form.stockExtXpos.textChanged.connect(self.checkXpos) self.form.stockExtYpos.textChanged.connect(self.checkYpos) self.form.stockExtZpos.textChanged.connect(self.checkZpos) - if hasattr(self.form, 'linkStockAndModel'): + if hasattr(self.form, "linkStockAndModel"): self.form.linkStockAndModel.setChecked(False) def checkXpos(self): self.trackXpos = self.form.stockExtXneg.text() == self.form.stockExtXpos.text() - self.getFields(self.obj, ['xpos']) + self.getFields(self.obj, ["xpos"]) def checkYpos(self): self.trackYpos = self.form.stockExtYneg.text() == self.form.stockExtYpos.text() - self.getFields(self.obj, ['ypos']) + self.getFields(self.obj, ["ypos"]) def checkZpos(self): self.trackZpos = self.form.stockExtZneg.text() == self.form.stockExtZpos.text() - self.getFields(self.obj, ['zpos']) + self.getFields(self.obj, ["zpos"]) def updateXpos(self): - fields = ['xneg'] + fields = ["xneg"] if self.trackXpos: self.form.stockExtXpos.setText(self.form.stockExtXneg.text()) - fields.append('xpos') + fields.append("xpos") self.getFields(self.obj, fields) def updateYpos(self): - fields = ['yneg'] + fields = ["yneg"] if self.trackYpos: self.form.stockExtYpos.setText(self.form.stockExtYneg.text()) - fields.append('ypos') + fields.append("ypos") self.getFields(self.obj, fields) def updateZpos(self): - fields = ['zneg'] + fields = ["zneg"] if self.trackZpos: self.form.stockExtZpos.setText(self.form.stockExtZneg.text()) - fields.append('zpos') + fields.append("zpos") self.getFields(self.obj, fields) @@ -445,17 +463,23 @@ class StockCreateBoxEdit(StockEdit): def getFields(self, obj, fields=None): if fields is None: - fields = ['length', 'widht', 'height'] + fields = ["length", "widht", "height"] try: if self.IsStock(obj): - if 'length' in fields: - obj.Stock.Length = FreeCAD.Units.Quantity(self.form.stockBoxLength.text()) - if 'width' in fields: - obj.Stock.Width = FreeCAD.Units.Quantity(self.form.stockBoxWidth.text()) - if 'height' in fields: - obj.Stock.Height = FreeCAD.Units.Quantity(self.form.stockBoxHeight.text()) + if "length" in fields: + obj.Stock.Length = FreeCAD.Units.Quantity( + self.form.stockBoxLength.text() + ) + if "width" in fields: + obj.Stock.Width = FreeCAD.Units.Quantity( + self.form.stockBoxWidth.text() + ) + if "height" in fields: + obj.Stock.Height = FreeCAD.Units.Quantity( + self.form.stockBoxHeight.text() + ) else: - PathLog.error(translate('PathJob', 'Stock not a box!')) + PathLog.error(translate("PathJob", "Stock not a box!")) except Exception: pass @@ -469,9 +493,15 @@ class StockCreateBoxEdit(StockEdit): def setupUi(self, obj): self.setFields(obj) - self.form.stockBoxLength.textChanged.connect(lambda: self.getFields(obj, ['length'])) - self.form.stockBoxWidth.textChanged.connect(lambda: self.getFields(obj, ['width'])) - self.form.stockBoxHeight.textChanged.connect(lambda: self.getFields(obj, ['height'])) + self.form.stockBoxLength.textChanged.connect( + lambda: self.getFields(obj, ["length"]) + ) + self.form.stockBoxWidth.textChanged.connect( + lambda: self.getFields(obj, ["width"]) + ) + self.form.stockBoxHeight.textChanged.connect( + lambda: self.getFields(obj, ["height"]) + ) class StockCreateCylinderEdit(StockEdit): @@ -483,15 +513,19 @@ class StockCreateCylinderEdit(StockEdit): def getFields(self, obj, fields=None): if fields is None: - fields = ['radius', 'height'] + fields = ["radius", "height"] try: if self.IsStock(obj): - if 'radius' in fields: - obj.Stock.Radius = FreeCAD.Units.Quantity(self.form.stockCylinderRadius.text()) - if 'height' in fields: - obj.Stock.Height = FreeCAD.Units.Quantity(self.form.stockCylinderHeight.text()) + if "radius" in fields: + obj.Stock.Radius = FreeCAD.Units.Quantity( + self.form.stockCylinderRadius.text() + ) + if "height" in fields: + obj.Stock.Height = FreeCAD.Units.Quantity( + self.form.stockCylinderHeight.text() + ) else: - PathLog.error(translate('PathJob', 'Stock not a cylinder!')) + PathLog.error(translate("PathJob", "Stock not a cylinder!")) except Exception: pass @@ -504,23 +538,33 @@ class StockCreateCylinderEdit(StockEdit): def setupUi(self, obj): self.setFields(obj) - self.form.stockCylinderRadius.textChanged.connect(lambda: self.getFields(obj, ['radius'])) - self.form.stockCylinderHeight.textChanged.connect(lambda: self.getFields(obj, ['height'])) + self.form.stockCylinderRadius.textChanged.connect( + lambda: self.getFields(obj, ["radius"]) + ) + self.form.stockCylinderHeight.textChanged.connect( + lambda: self.getFields(obj, ["height"]) + ) class StockFromExistingEdit(StockEdit): Index = 3 StockType = PathStock.StockType.Unknown - StockLabelPrefix = 'Stock' + StockLabelPrefix = "Stock" def editorFrame(self): return self.form.stockFromExisting def getFields(self, obj): stock = self.form.stockExisting.itemData(self.form.stockExisting.currentIndex()) - if not (hasattr(obj.Stock, 'Objects') and len(obj.Stock.Objects) == 1 and obj.Stock.Objects[0] == stock): + if not ( + hasattr(obj.Stock, "Objects") + and len(obj.Stock.Objects) == 1 + and obj.Stock.Objects[0] == stock + ): if stock: - stock = PathJob.createResourceClone(obj, stock, self.StockLabelPrefix, 'Stock') + stock = PathJob.createResourceClone( + obj, stock, self.StockLabelPrefix, "Stock" + ) stock.ViewObject.Visibility = True PathStock.SetupStockObject(stock, PathStock.StockType.Unknown) stock.Proxy.execute(stock) @@ -528,12 +572,12 @@ class StockFromExistingEdit(StockEdit): def candidates(self, obj): solids = [o for o in obj.Document.Objects if PathUtil.isSolid(o)] - if hasattr(obj, 'Model'): + if hasattr(obj, "Model"): job = obj else: job = PathUtils.findParentJob(obj) for base in job.Model.Group: - if base in solids and PathJob.isResourceClone(job, base, 'Model'): + if base in solids and PathJob.isResourceClone(job, base, "Model"): solids.remove(base) if job.Stock in solids: # regardless, what stock is/was, it's not a valid choice @@ -571,18 +615,24 @@ class TaskPanel: self.obj = vobj.Object self.deleteOnReject = deleteOnReject self.form = FreeCADGui.PySideUic.loadUi(":/panels/PathEdit.ui") - self.template = PathJobDlg.JobTemplateExport(self.obj, self.form.jobBox.widget(1)) + self.template = PathJobDlg.JobTemplateExport( + self.obj, self.form.jobBox.widget(1) + ) self.name = self.obj.Name vUnit = FreeCAD.Units.Quantity(1, FreeCAD.Units.Velocity).getUserPreferred()[2] - self.form.toolControllerList.horizontalHeaderItem(1).setText('#') + self.form.toolControllerList.horizontalHeaderItem(1).setText("#") self.form.toolControllerList.horizontalHeaderItem(2).setText(vUnit) self.form.toolControllerList.horizontalHeaderItem(3).setText(vUnit) - self.form.toolControllerList.horizontalHeader().setResizeMode(0, QtGui.QHeaderView.Stretch) + self.form.toolControllerList.horizontalHeader().setResizeMode( + 0, QtGui.QHeaderView.Stretch + ) self.form.toolControllerList.resizeColumnsToContents() currentPostProcessor = self.obj.PostProcessor - postProcessors = PathPreferences.allEnabledPostProcessors(['', currentPostProcessor]) + postProcessors = PathPreferences.allEnabledPostProcessors( + ["", currentPostProcessor] + ) for post in postProcessors: self.form.postProcessor.addItem(post) # update the enumeration values, just to make sure all selections are valid @@ -590,7 +640,9 @@ class TaskPanel: self.obj.PostProcessor = currentPostProcessor self.postProcessorDefaultTooltip = self.form.postProcessor.toolTip() - self.postProcessorArgsDefaultTooltip = self.form.postProcessorArguments.toolTip() + self.postProcessorArgsDefaultTooltip = ( + self.form.postProcessorArguments.toolTip() + ) self.vproxy.setupEditVisibility(self.obj) @@ -600,8 +652,12 @@ class TaskPanel: self.stockCreateCylinder = None self.stockEdit = None - self.setupGlobal = PathSetupSheetGui.GlobalEditor(self.obj.SetupSheet, self.form) - self.setupOps = PathSetupSheetGui.OpsDefaultEditor(self.obj.SetupSheet, self.form) + self.setupGlobal = PathSetupSheetGui.GlobalEditor( + self.obj.SetupSheet, self.form + ) + self.setupOps = PathSetupSheetGui.OpsDefaultEditor( + self.obj.SetupSheet, self.form + ) def preCleanup(self): PathLog.track() @@ -626,12 +682,18 @@ class TaskPanel: FreeCAD.ActiveDocument.abortTransaction() if self.deleteOnReject and FreeCAD.ActiveDocument.getObject(self.name): PathLog.info("Uncreate Job") - FreeCAD.ActiveDocument.openTransaction(translate("Path_Job", "Uncreate Job")) + FreeCAD.ActiveDocument.openTransaction( + translate("Path_Job", "Uncreate Job") + ) if self.obj.ViewObject.Proxy.onDelete(self.obj.ViewObject, None): FreeCAD.ActiveDocument.removeObject(self.obj.Name) FreeCAD.ActiveDocument.commitTransaction() else: - PathLog.track(self.name, self.deleteOnReject, FreeCAD.ActiveDocument.getObject(self.name)) + PathLog.track( + self.name, + self.deleteOnReject, + FreeCAD.ActiveDocument.getObject(self.name), + ) self.cleanup(resetEdit) return True @@ -643,37 +705,57 @@ class TaskPanel: FreeCAD.ActiveDocument.recompute() def updateTooltips(self): - if hasattr(self.obj, "Proxy") and hasattr(self.obj.Proxy, "tooltip") and self.obj.Proxy.tooltip: + if ( + hasattr(self.obj, "Proxy") + and hasattr(self.obj.Proxy, "tooltip") + and self.obj.Proxy.tooltip + ): self.form.postProcessor.setToolTip(self.obj.Proxy.tooltip) if hasattr(self.obj.Proxy, "tooltipArgs") and self.obj.Proxy.tooltipArgs: self.form.postProcessorArguments.setToolTip(self.obj.Proxy.tooltipArgs) else: - self.form.postProcessorArguments.setToolTip(self.postProcessorArgsDefaultTooltip) + self.form.postProcessorArguments.setToolTip( + self.postProcessorArgsDefaultTooltip + ) else: self.form.postProcessor.setToolTip(self.postProcessorDefaultTooltip) - self.form.postProcessorArguments.setToolTip(self.postProcessorArgsDefaultTooltip) + self.form.postProcessorArguments.setToolTip( + self.postProcessorArgsDefaultTooltip + ) def getFields(self): - '''sets properties in the object to match the form''' + """sets properties in the object to match the form""" if self.obj: self.obj.PostProcessor = str(self.form.postProcessor.currentText()) - self.obj.PostProcessorArgs = str(self.form.postProcessorArguments.displayText()) - self.obj.PostProcessorOutputFile = str(self.form.postProcessorOutputFile.text()) + self.obj.PostProcessorArgs = str( + self.form.postProcessorArguments.displayText() + ) + self.obj.PostProcessorOutputFile = str( + self.form.postProcessorOutputFile.text() + ) self.obj.Label = str(self.form.jobLabel.text()) self.obj.Description = str(self.form.jobDescription.toPlainText()) - self.obj.Operations.Group = [self.form.operationsList.item(i).data(self.DataObject) for i in range(self.form.operationsList.count())] + self.obj.Operations.Group = [ + self.form.operationsList.item(i).data(self.DataObject) + for i in range(self.form.operationsList.count()) + ] try: self.obj.SplitOutput = self.form.splitOutput.isChecked() self.obj.OrderOutputBy = str(self.form.orderBy.currentText()) flist = [] for i in range(self.form.wcslist.count()): - if self.form.wcslist.item(i).checkState() == QtCore.Qt.CheckState.Checked: + if ( + self.form.wcslist.item(i).checkState() + == QtCore.Qt.CheckState.Checked + ): flist.append(self.form.wcslist.item(i).text()) self.obj.Fixtures = flist except Exception: - FreeCAD.Console.PrintWarning("The Job was created without fixture support. Please delete and recreate the job\r\n") + FreeCAD.Console.PrintWarning( + "The Job was created without fixture support. Please delete and recreate the job\r\n" + ) self.updateTooltips() self.stockEdit.getFields(self.obj) @@ -714,31 +796,33 @@ class TaskPanel: item = QtGui.QTableWidgetItem(tc.Label) item.setData(self.DataObject, tc) - item.setData(self.DataProperty, 'Label') + item.setData(self.DataProperty, "Label") self.form.toolControllerList.setItem(row, 0, item) item = QtGui.QTableWidgetItem("%d" % tc.ToolNumber) item.setTextAlignment(QtCore.Qt.AlignRight) item.setData(self.DataObject, tc) - item.setData(self.DataProperty, 'Number') + item.setData(self.DataProperty, "Number") self.form.toolControllerList.setItem(row, 1, item) item = QtGui.QTableWidgetItem("%g" % tc.HorizFeed.getValueAs(vUnit)) item.setTextAlignment(QtCore.Qt.AlignRight) item.setData(self.DataObject, tc) - item.setData(self.DataProperty, 'HorizFeed') + item.setData(self.DataProperty, "HorizFeed") self.form.toolControllerList.setItem(row, 2, item) item = QtGui.QTableWidgetItem("%g" % tc.VertFeed.getValueAs(vUnit)) item.setTextAlignment(QtCore.Qt.AlignRight) item.setData(self.DataObject, tc) - item.setData(self.DataProperty, 'VertFeed') + item.setData(self.DataProperty, "VertFeed") self.form.toolControllerList.setItem(row, 3, item) - item = QtGui.QTableWidgetItem("%s%g" % ('+' if tc.SpindleDir == 'Forward' else '-', tc.SpindleSpeed)) + item = QtGui.QTableWidgetItem( + "%s%g" % ("+" if tc.SpindleDir == "Forward" else "-", tc.SpindleSpeed) + ) item.setTextAlignment(QtCore.Qt.AlignRight) item.setData(self.DataObject, tc) - item.setData(self.DataProperty, 'Spindle') + item.setData(self.DataProperty, "Spindle") self.form.toolControllerList.setItem(row, 4, item) if index != -1: @@ -750,7 +834,7 @@ class TaskPanel: self.form.toolControllerList.blockSignals(False) def setFields(self): - '''sets fields in the form to match the object''' + """sets fields in the form to match the object""" self.form.jobLabel.setText(self.obj.Label) self.form.jobDescription.setPlainText(self.obj.Description) @@ -778,7 +862,14 @@ class TaskPanel: self.form.operationsList.addItem(item) self.form.jobModel.clear() - for name, count in PathUtil.keyValueIter(Counter([self.obj.Proxy.baseObject(self.obj, o).Label for o in self.obj.Model.Group])): + for name, count in PathUtil.keyValueIter( + Counter( + [ + self.obj.Proxy.baseObject(self.obj, o).Label + for o in self.obj.Model.Group + ] + ) + ): if count == 1: self.form.jobModel.addItem(name) else: @@ -790,7 +881,12 @@ class TaskPanel: self.setupOps.setFields() def setPostProcessorOutputFile(self): - filename = QtGui.QFileDialog.getSaveFileName(self.form, translate("Path_Job", "Select Output File"), None, translate("Path_Job", "All Files (*.*)")) + filename = QtGui.QFileDialog.getSaveFileName( + self.form, + translate("Path_Job", "Select Output File"), + None, + translate("Path_Job", "All Files (*.*)"), + ) if filename and filename[0]: self.obj.PostProcessorOutputFile = str(filename[0]) self.setFields() @@ -801,7 +897,9 @@ class TaskPanel: self.form.operationMove.setEnabled(True) row = self.form.operationsList.currentRow() self.form.operationUp.setEnabled(row > 0) - self.form.operationDown.setEnabled(row < self.form.operationsList.count() - 1) + self.form.operationDown.setEnabled( + row < self.form.operationsList.count() - 1 + ) else: self.form.operationModify.setEnabled(False) self.form.operationMove.setEnabled(False) @@ -809,7 +907,11 @@ class TaskPanel: def objectDelete(self, widget): for item in widget.selectedItems(): obj = item.data(self.DataObject) - if obj.ViewObject and hasattr(obj.ViewObject, 'Proxy') and hasattr(obj.ViewObject.Proxy, 'onDelete'): + if ( + obj.ViewObject + and hasattr(obj.ViewObject, "Proxy") + and hasattr(obj.ViewObject.Proxy, "onDelete") + ): obj.ViewObject.Proxy.onDelete(obj.ViewObject, None) FreeCAD.ActiveDocument.removeObject(obj.Name) self.setFields() @@ -845,7 +947,9 @@ class TaskPanel: # can only delete what is selected delete = edit # ... but we want to make sure there's at least one TC left - if len(self.obj.Tools.Group) == len(self.form.toolControllerList.selectedItems()): + if len(self.obj.Tools.Group) == len( + self.form.toolControllerList.selectedItems() + ): delete = False # ... also don't want to delete any TCs that are already used if delete: @@ -869,7 +973,9 @@ class TaskPanel: # use next available number if PathPreferences.toolsUseLegacyTools(): - PathToolLibraryEditor.CommandToolLibraryEdit().edit(self.obj, self.updateToolController) + PathToolLibraryEditor.CommandToolLibraryEdit().edit( + self.obj, self.updateToolController + ) else: tools = PathToolBitGui.LoadTools() @@ -883,12 +989,14 @@ class TaskPanel: for tool in tools: toolNum = self.obj.Proxy.nextToolNumber() if library is not None: - for toolBit in library['tools']: + for toolBit in library["tools"]: - if toolBit['path'] == tool.File: - toolNum = toolBit['nr'] + if toolBit["path"] == tool.File: + toolNum = toolBit["nr"] - tc = PathToolControllerGui.Create(name=tool.Label, tool=tool, toolNumber=toolNum) + tc = PathToolControllerGui.Create( + name=tool.Label, tool=tool, toolNumber=toolNum + ) self.obj.Proxy.addToolController(tc) FreeCAD.ActiveDocument.recompute() @@ -900,29 +1008,33 @@ class TaskPanel: def toolControllerChanged(self, item): tc = item.data(self.DataObject) prop = item.data(self.DataProperty) - if 'Label' == prop: + if "Label" == prop: tc.Label = item.text() item.setText(tc.Label) - elif 'Number' == prop: + elif "Number" == prop: try: tc.ToolNumber = int(item.text()) except Exception: pass item.setText("%d" % tc.ToolNumber) - elif 'Spindle' == prop: + elif "Spindle" == prop: try: speed = float(item.text()) - rot = 'Forward' + rot = "Forward" if speed < 0: - rot = 'Reverse' + rot = "Reverse" speed = -speed tc.SpindleDir = rot tc.SpindleSpeed = speed except Exception: pass - item.setText("%s%g" % ('+' if tc.SpindleDir == 'Forward' else '-', tc.SpindleSpeed)) - elif 'HorizFeed' == prop or 'VertFeed' == prop: - vUnit = FreeCAD.Units.Quantity(1, FreeCAD.Units.Velocity).getUserPreferred()[2] + item.setText( + "%s%g" % ("+" if tc.SpindleDir == "Forward" else "-", tc.SpindleSpeed) + ) + elif "HorizFeed" == prop or "VertFeed" == prop: + vUnit = FreeCAD.Units.Quantity( + 1, FreeCAD.Units.Velocity + ).getUserPreferred()[2] try: val = FreeCAD.Units.Quantity(item.text()) if FreeCAD.Units.Velocity == val.Unit: @@ -947,7 +1059,9 @@ class TaskPanel: PathLog.track(axis) def alignSel(sel, normal, flip=False): - PathLog.track("Vector(%.2f, %.2f, %.2f)" % (normal.x, normal.y, normal.z), flip) + PathLog.track( + "Vector(%.2f, %.2f, %.2f)" % (normal.x, normal.y, normal.z), flip + ) vector = axis if flip: vector = axis.negative() @@ -966,13 +1080,19 @@ class TaskPanel: PathLog.track(selObject.Label, feature) sub = sel.Object.Shape.getElement(feature) - if 'Face' == sub.ShapeType: + if "Face" == sub.ShapeType: normal = sub.normalAt(0, 0) - if sub.Orientation == 'Reversed': + if sub.Orientation == "Reversed": normal = FreeCAD.Vector() - normal - PathLog.debug("(%.2f, %.2f, %.2f) -> reversed (%s)" % (normal.x, normal.y, normal.z, sub.Orientation)) + PathLog.debug( + "(%.2f, %.2f, %.2f) -> reversed (%s)" + % (normal.x, normal.y, normal.z, sub.Orientation) + ) else: - PathLog.debug("(%.2f, %.2f, %.2f) -> forward (%s)" % (normal.x, normal.y, normal.z, sub.Orientation)) + PathLog.debug( + "(%.2f, %.2f, %.2f) -> forward (%s)" + % (normal.x, normal.y, normal.z, sub.Orientation) + ) if PathGeom.pointsCoincide(axis, normal): alignSel(sel, normal, True) @@ -981,9 +1101,13 @@ class TaskPanel: else: alignSel(sel, normal) - elif 'Edge' == sub.ShapeType: - normal = (sub.Vertexes[1].Point - sub.Vertexes[0].Point).normalize() - if PathGeom.pointsCoincide(axis, normal) or PathGeom.pointsCoincide(axis, FreeCAD.Vector() - normal): + elif "Edge" == sub.ShapeType: + normal = ( + sub.Vertexes[1].Point - sub.Vertexes[0].Point + ).normalize() + if PathGeom.pointsCoincide( + axis, normal + ) or PathGeom.pointsCoincide(axis, FreeCAD.Vector() - normal): # Don't really know the orientation of an edge, so let's just flip the object # and if the user doesn't like it they can flip again alignSel(sel, normal, True) @@ -1012,7 +1136,9 @@ class TaskPanel: PathLog.track(selObject.Label, name) feature = selObject.Shape.getElement(name) bb = feature.BoundBox - offset = FreeCAD.Vector(axis.x * bb.XMax, axis.y * bb.YMax, axis.z * bb.ZMax) + offset = FreeCAD.Vector( + axis.x * bb.XMax, axis.y * bb.YMax, axis.z * bb.ZMax + ) PathLog.track(feature.BoundBox.ZMax, offset) p = selObject.Placement p.move(offset) @@ -1044,7 +1170,9 @@ class TaskPanel: Draft.rotate(sel.Object, angle, bb.Center, axis) else: for sel in selection: - Draft.rotate(sel.Object, angle, sel.Object.Shape.BoundBox.Center, axis) + Draft.rotate( + sel.Object, angle, sel.Object.Shape.BoundBox.Center, axis + ) def alignSetOrigin(self): (obj, by) = self.alignMoveToOrigin() @@ -1069,11 +1197,11 @@ class TaskPanel: for feature in sel.SubElementNames: selFeature = feature sub = sel.Object.Shape.getElement(feature) - if 'Vertex' == sub.ShapeType: + if "Vertex" == sub.ShapeType: p = FreeCAD.Vector() - sub.Point - if 'Edge' == sub.ShapeType: + if "Edge" == sub.ShapeType: p = FreeCAD.Vector() - sub.Curve.Location - if 'Face' == sub.ShapeType: + if "Face" == sub.ShapeType: p = FreeCAD.Vector() - sub.BoundBox.Center if p: @@ -1085,11 +1213,12 @@ class TaskPanel: return (selObject, p) def updateStockEditor(self, index, force=False): - def setupFromBaseEdit(): PathLog.track(index, force) if force or not self.stockFromBase: - self.stockFromBase = StockFromBaseBoundBoxEdit(self.obj, self.form, force) + self.stockFromBase = StockFromBaseBoundBoxEdit( + self.obj, self.form, force + ) self.stockEdit = self.stockFromBase def setupCreateBoxEdit(): @@ -1101,13 +1230,17 @@ class TaskPanel: def setupCreateCylinderEdit(): PathLog.track(index, force) if force or not self.stockCreateCylinder: - self.stockCreateCylinder = StockCreateCylinderEdit(self.obj, self.form, force) + self.stockCreateCylinder = StockCreateCylinderEdit( + self.obj, self.form, force + ) self.stockEdit = self.stockCreateCylinder def setupFromExisting(): PathLog.track(index, force) if force or not self.stockFromExisting: - self.stockFromExisting = StockFromExistingEdit(self.obj, self.form, force) + self.stockFromExisting = StockFromExistingEdit( + self.obj, self.form, force + ) if self.stockFromExisting.candidates(self.obj): self.stockEdit = self.stockFromExisting return True @@ -1123,7 +1256,10 @@ class TaskPanel: elif StockFromExistingEdit.IsStock(self.obj): setupFromExisting() else: - PathLog.error(translate('PathJob', "Unsupported stock object %s") % self.obj.Stock.Label) + PathLog.error( + translate("PathJob", "Unsupported stock object %s") + % self.obj.Stock.Label + ) else: if index == StockFromBaseBoundBoxEdit.Index: setupFromBaseEdit() @@ -1136,7 +1272,10 @@ class TaskPanel: setupFromBaseEdit() index = -1 else: - PathLog.error(translate('PathJob', "Unsupported stock type %s (%d)") % (self.form.stock.currentText(), index)) + PathLog.error( + translate("PathJob", "Unsupported stock type %s (%d)") + % (self.form.stock.currentText(), index) + ) self.stockEdit.activate(self.obj, index == -1) if -1 != index: @@ -1161,8 +1300,8 @@ class TaskPanel: Draft.move(sel.Object, by) def isValidDatumSelection(self, sel): - if sel.ShapeType in ['Vertex', 'Edge', 'Face']: - if hasattr(sel, 'Curve') and type(sel.Curve) not in [Part.Circle]: + if sel.ShapeType in ["Vertex", "Edge", "Face"]: + if hasattr(sel, "Curve") and type(sel.Curve) not in [Part.Circle]: return False return True @@ -1170,10 +1309,10 @@ class TaskPanel: return False def isValidAxisSelection(self, sel): - if sel.ShapeType in ['Vertex', 'Edge', 'Face']: - if hasattr(sel, 'Curve') and type(sel.Curve) in [Part.Circle]: + if sel.ShapeType in ["Vertex", "Edge", "Face"]: + if hasattr(sel, "Curve") and type(sel.Curve) in [Part.Circle]: return False - if hasattr(sel, 'Surface') and sel.Surface.curvature(0, 0, "Max") != 0: + if hasattr(sel, "Surface") and sel.Surface.curvature(0, 0, "Max") != 0: return False return True @@ -1244,7 +1383,11 @@ class TaskPanel: for model, count in PathUtil.keyValueIter(obsolete): for i in range(count): # it seems natural to remove the last of all the base objects for a given model - base = [b for b in obj.Model.Group if proxy.baseObject(obj, b) == model][-1] + base = [ + b + for b in obj.Model.Group + if proxy.baseObject(obj, b) == model + ][-1] self.vproxy.forgetBaseVisibility(obj, base) self.obj.Proxy.removeBase(obj, base, True) # do not access any of the retired objects after this point, they don't exist anymore @@ -1260,7 +1403,7 @@ class TaskPanel: if obsolete or additions: self.setFields() else: - PathLog.track('no changes to model') + PathLog.track("no changes to model") def tabPageChanged(self, index): if index == 0: @@ -1285,7 +1428,9 @@ class TaskPanel: self.form.postProcessor.currentIndexChanged.connect(self.getFields) self.form.postProcessorArguments.editingFinished.connect(self.getFields) self.form.postProcessorOutputFile.editingFinished.connect(self.getFields) - self.form.postProcessorSetOutputFile.clicked.connect(self.setPostProcessorOutputFile) + self.form.postProcessorSetOutputFile.clicked.connect( + self.setPostProcessorOutputFile + ) # Workplan self.form.operationsList.itemSelectionChanged.connect(self.operationSelect) @@ -1298,7 +1443,9 @@ class TaskPanel: self.form.activeToolGroup.hide() # not supported yet # Tool controller - self.form.toolControllerList.itemSelectionChanged.connect(self.toolControllerSelect) + self.form.toolControllerList.itemSelectionChanged.connect( + self.toolControllerSelect + ) self.form.toolControllerList.itemChanged.connect(self.toolControllerChanged) self.form.toolControllerEdit.clicked.connect(self.toolControllerEdit) self.form.toolControllerDelete.clicked.connect(self.toolControllerDelete) @@ -1314,42 +1461,74 @@ class TaskPanel: self.form.stock.currentIndexChanged.connect(self.updateStockEditor) self.form.refreshStock.clicked.connect(self.refreshStock) - self.form.modelSetXAxis.clicked.connect(lambda: self.modelSetAxis(FreeCAD.Vector(1, 0, 0))) - self.form.modelSetYAxis.clicked.connect(lambda: self.modelSetAxis(FreeCAD.Vector(0, 1, 0))) - self.form.modelSetZAxis.clicked.connect(lambda: self.modelSetAxis(FreeCAD.Vector(0, 0, 1))) - self.form.modelSetX0.clicked.connect(lambda: self.modelSet0(FreeCAD.Vector(-1, 0, 0))) - self.form.modelSetY0.clicked.connect(lambda: self.modelSet0(FreeCAD.Vector(0, -1, 0))) - self.form.modelSetZ0.clicked.connect(lambda: self.modelSet0(FreeCAD.Vector(0, 0, -1))) + self.form.modelSetXAxis.clicked.connect( + lambda: self.modelSetAxis(FreeCAD.Vector(1, 0, 0)) + ) + self.form.modelSetYAxis.clicked.connect( + lambda: self.modelSetAxis(FreeCAD.Vector(0, 1, 0)) + ) + self.form.modelSetZAxis.clicked.connect( + lambda: self.modelSetAxis(FreeCAD.Vector(0, 0, 1)) + ) + self.form.modelSetX0.clicked.connect( + lambda: self.modelSet0(FreeCAD.Vector(-1, 0, 0)) + ) + self.form.modelSetY0.clicked.connect( + lambda: self.modelSet0(FreeCAD.Vector(0, -1, 0)) + ) + self.form.modelSetZ0.clicked.connect( + lambda: self.modelSet0(FreeCAD.Vector(0, 0, -1)) + ) self.form.setOrigin.clicked.connect(self.alignSetOrigin) self.form.moveToOrigin.clicked.connect(self.alignMoveToOrigin) - self.form.modelMoveLeftUp.clicked.connect(lambda: self.modelMove(FreeCAD.Vector(-1, 1, 0))) - self.form.modelMoveLeft.clicked.connect(lambda: self.modelMove(FreeCAD.Vector(-1, 0, 0))) - self.form.modelMoveLeftDown.clicked.connect(lambda: self.modelMove(FreeCAD.Vector(-1, -1, 0))) + self.form.modelMoveLeftUp.clicked.connect( + lambda: self.modelMove(FreeCAD.Vector(-1, 1, 0)) + ) + self.form.modelMoveLeft.clicked.connect( + lambda: self.modelMove(FreeCAD.Vector(-1, 0, 0)) + ) + self.form.modelMoveLeftDown.clicked.connect( + lambda: self.modelMove(FreeCAD.Vector(-1, -1, 0)) + ) - self.form.modelMoveUp.clicked.connect(lambda: self.modelMove(FreeCAD.Vector(0, 1, 0))) - self.form.modelMoveDown.clicked.connect(lambda: self.modelMove(FreeCAD.Vector(0, -1, 0))) + self.form.modelMoveUp.clicked.connect( + lambda: self.modelMove(FreeCAD.Vector(0, 1, 0)) + ) + self.form.modelMoveDown.clicked.connect( + lambda: self.modelMove(FreeCAD.Vector(0, -1, 0)) + ) - self.form.modelMoveRightUp.clicked.connect(lambda: self.modelMove(FreeCAD.Vector(1, 1, 0))) - self.form.modelMoveRight.clicked.connect(lambda: self.modelMove(FreeCAD.Vector(1, 0, 0))) - self.form.modelMoveRightDown.clicked.connect(lambda: self.modelMove(FreeCAD.Vector(1, -1, 0))) + self.form.modelMoveRightUp.clicked.connect( + lambda: self.modelMove(FreeCAD.Vector(1, 1, 0)) + ) + self.form.modelMoveRight.clicked.connect( + lambda: self.modelMove(FreeCAD.Vector(1, 0, 0)) + ) + self.form.modelMoveRightDown.clicked.connect( + lambda: self.modelMove(FreeCAD.Vector(1, -1, 0)) + ) - self.form.modelRotateLeft.clicked.connect(lambda: self.modelRotate(FreeCAD.Vector(0, 0, 1))) - self.form.modelRotateRight.clicked.connect(lambda: self.modelRotate(FreeCAD.Vector(0, 0, -1))) + self.form.modelRotateLeft.clicked.connect( + lambda: self.modelRotate(FreeCAD.Vector(0, 0, 1)) + ) + self.form.modelRotateRight.clicked.connect( + lambda: self.modelRotate(FreeCAD.Vector(0, 0, -1)) + ) self.updateSelection() # set active page - if activate in ['General', 'Model']: + if activate in ["General", "Model"]: self.form.setCurrentIndex(0) - if activate in ['Output', 'Post Processor']: + if activate in ["Output", "Post Processor"]: self.form.setCurrentIndex(1) - if activate in ['Layout', 'Stock']: + if activate in ["Layout", "Stock"]: self.form.setCurrentIndex(2) - if activate in ['Tools', 'Tool Controller']: + if activate in ["Tools", "Tool Controller"]: self.form.setCurrentIndex(3) - if activate in ['Workplan', 'Operations']: + if activate in ["Workplan", "Operations"]: self.form.setCurrentIndex(4) self.form.currentChanged.connect(self.tabPageChanged) @@ -1377,12 +1556,12 @@ class TaskPanel: def Create(base, template=None): - '''Create(base, template) ... creates a job instance for the given base object - using template to configure it.''' - FreeCADGui.addModule('PathScripts.PathJob') + """Create(base, template) ... creates a job instance for the given base object + using template to configure it.""" + FreeCADGui.addModule("PathScripts.PathJob") FreeCAD.ActiveDocument.openTransaction(translate("Path_Job", "Create Job")) try: - obj = PathJob.Create('Job', base, template) + obj = PathJob.Create("Job", base, template) obj.ViewObject.Proxy = ViewProvider(obj.ViewObject) FreeCAD.ActiveDocument.commitTransaction() obj.Document.recompute() diff --git a/src/Mod/Path/PathScripts/PathOp.py b/src/Mod/Path/PathScripts/PathOp.py index d9537f99f7..e5e3a02dd6 100644 --- a/src/Mod/Path/PathScripts/PathOp.py +++ b/src/Mod/Path/PathScripts/PathOp.py @@ -24,17 +24,18 @@ import time from PySide import QtCore +from PathScripts.PathUtils import waiting_effects import Path import PathScripts.PathGeom as PathGeom import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences import PathScripts.PathUtil as PathUtil import PathScripts.PathUtils as PathUtils -from PathScripts.PathUtils import waiting_effects # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader -Part = LazyLoader('Part', globals(), 'Part') + +Part = LazyLoader("Part", globals(), "Part") __title__ = "Base class for all operations." __author__ = "sliptonic (Brad Collette)" @@ -65,11 +66,11 @@ FeatureLocations = 0x1000 # Locations FeatureCoolant = 0x2000 # Coolant FeatureDiameters = 0x4000 # Turning Diameters -FeatureBaseGeometry = FeatureBaseVertexes | FeatureBaseFaces | FeatureBaseEdges | FeatureBasePanels +FeatureBaseGeometry = FeatureBaseVertexes | FeatureBaseFaces | FeatureBaseEdges class ObjectOp(object): - ''' + """ Base class for proxy objects of all Path operations. Use this class as a base class for new operations. It provides properties @@ -88,7 +89,6 @@ class ObjectOp(object): FeatureBaseVertexes ... Base geometry support for vertexes FeatureBaseEdges ... Base geometry support for edges FeatureBaseFaces ... Base geometry support for faces - FeatureBasePanels ... Base geometry support for Arch.Panels FeatureLocations ... Base location support FeatureCoolant ... Support for operation coolant FeatureDiameters ... Support for turning operation diameters @@ -98,35 +98,93 @@ class ObjectOp(object): but implement the function opOnChanged(). If a base class overwrites a base API function it should call the super's implementation - otherwise the base functionality might be broken. - ''' + """ def addBaseProperty(self, obj): - obj.addProperty("App::PropertyLinkSubListGlobal", "Base", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "The base geometry for this operation")) + obj.addProperty( + "App::PropertyLinkSubListGlobal", + "Base", + "Path", + QtCore.QT_TRANSLATE_NOOP("PathOp", "The base geometry for this operation"), + ) def addOpValues(self, obj, values): - if 'start' in values: - obj.addProperty("App::PropertyDistance", "OpStartDepth", "Op Values", QtCore.QT_TRANSLATE_NOOP("PathOp", "Holds the calculated value for the StartDepth")) - obj.setEditorMode('OpStartDepth', 1) # read-only - if 'final' in values: - obj.addProperty("App::PropertyDistance", "OpFinalDepth", "Op Values", QtCore.QT_TRANSLATE_NOOP("PathOp", "Holds the calculated value for the FinalDepth")) - obj.setEditorMode('OpFinalDepth', 1) # read-only - if 'tooldia' in values: - obj.addProperty("App::PropertyDistance", "OpToolDiameter", "Op Values", QtCore.QT_TRANSLATE_NOOP("PathOp", "Holds the diameter of the tool")) - obj.setEditorMode('OpToolDiameter', 1) # read-only - if 'stockz' in values: - obj.addProperty("App::PropertyDistance", "OpStockZMax", "Op Values", QtCore.QT_TRANSLATE_NOOP("PathOp", "Holds the max Z value of Stock")) - obj.setEditorMode('OpStockZMax', 1) # read-only - obj.addProperty("App::PropertyDistance", "OpStockZMin", "Op Values", QtCore.QT_TRANSLATE_NOOP("PathOp", "Holds the min Z value of Stock")) - obj.setEditorMode('OpStockZMin', 1) # read-only + if "start" in values: + obj.addProperty( + "App::PropertyDistance", + "OpStartDepth", + "Op Values", + QtCore.QT_TRANSLATE_NOOP( + "PathOp", "Holds the calculated value for the StartDepth" + ), + ) + obj.setEditorMode("OpStartDepth", 1) # read-only + if "final" in values: + obj.addProperty( + "App::PropertyDistance", + "OpFinalDepth", + "Op Values", + QtCore.QT_TRANSLATE_NOOP( + "PathOp", "Holds the calculated value for the FinalDepth" + ), + ) + obj.setEditorMode("OpFinalDepth", 1) # read-only + if "tooldia" in values: + obj.addProperty( + "App::PropertyDistance", + "OpToolDiameter", + "Op Values", + QtCore.QT_TRANSLATE_NOOP("PathOp", "Holds the diameter of the tool"), + ) + obj.setEditorMode("OpToolDiameter", 1) # read-only + if "stockz" in values: + obj.addProperty( + "App::PropertyDistance", + "OpStockZMax", + "Op Values", + QtCore.QT_TRANSLATE_NOOP("PathOp", "Holds the max Z value of Stock"), + ) + obj.setEditorMode("OpStockZMax", 1) # read-only + obj.addProperty( + "App::PropertyDistance", + "OpStockZMin", + "Op Values", + QtCore.QT_TRANSLATE_NOOP("PathOp", "Holds the min Z value of Stock"), + ) + obj.setEditorMode("OpStockZMin", 1) # read-only def __init__(self, obj, name, parentJob=None): PathLog.track() - obj.addProperty("App::PropertyBool", "Active", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "Make False, to prevent operation from generating code")) - obj.addProperty("App::PropertyString", "Comment", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "An optional comment for this Operation")) - obj.addProperty("App::PropertyString", "UserLabel", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "User Assigned Label")) - obj.addProperty("App::PropertyString", "CycleTime", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "Operations Cycle Time Estimation")) - obj.setEditorMode('CycleTime', 1) # read-only + obj.addProperty( + "App::PropertyBool", + "Active", + "Path", + QtCore.QT_TRANSLATE_NOOP( + "PathOp", "Make False, to prevent operation from generating code" + ), + ) + obj.addProperty( + "App::PropertyString", + "Comment", + "Path", + QtCore.QT_TRANSLATE_NOOP( + "PathOp", "An optional comment for this Operation" + ), + ) + obj.addProperty( + "App::PropertyString", + "UserLabel", + "Path", + QtCore.QT_TRANSLATE_NOOP("PathOp", "User Assigned Label"), + ) + obj.addProperty( + "App::PropertyString", + "CycleTime", + "Path", + QtCore.QT_TRANSLATE_NOOP("PathOp", "Operations Cycle Time Estimation"), + ) + obj.setEditorMode("CycleTime", 1) # read-only features = self.opFeatures(obj) @@ -134,45 +192,136 @@ class ObjectOp(object): self.addBaseProperty(obj) if FeatureLocations & features: - obj.addProperty("App::PropertyVectorList", "Locations", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "Base locations for this operation")) + obj.addProperty( + "App::PropertyVectorList", + "Locations", + "Path", + QtCore.QT_TRANSLATE_NOOP("PathOp", "Base locations for this operation"), + ) if FeatureTool & features: - obj.addProperty("App::PropertyLink", "ToolController", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "The tool controller that will be used to calculate the path")) - self.addOpValues(obj, ['tooldia']) + obj.addProperty( + "App::PropertyLink", + "ToolController", + "Path", + QtCore.QT_TRANSLATE_NOOP( + "PathOp", + "The tool controller that will be used to calculate the path", + ), + ) + self.addOpValues(obj, ["tooldia"]) if FeatureCoolant & features: - obj.addProperty("App::PropertyString", "CoolantMode", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "Coolant mode for this operation")) + obj.addProperty( + "App::PropertyString", + "CoolantMode", + "Path", + QtCore.QT_TRANSLATE_NOOP("PathOp", "Coolant mode for this operation"), + ) if FeatureDepths & features: - obj.addProperty("App::PropertyDistance", "StartDepth", "Depth", QtCore.QT_TRANSLATE_NOOP("PathOp", "Starting Depth of Tool- first cut depth in Z")) - obj.addProperty("App::PropertyDistance", "FinalDepth", "Depth", QtCore.QT_TRANSLATE_NOOP("PathOp", "Final Depth of Tool- lowest value in Z")) + obj.addProperty( + "App::PropertyDistance", + "StartDepth", + "Depth", + QtCore.QT_TRANSLATE_NOOP( + "PathOp", "Starting Depth of Tool- first cut depth in Z" + ), + ) + obj.addProperty( + "App::PropertyDistance", + "FinalDepth", + "Depth", + QtCore.QT_TRANSLATE_NOOP( + "PathOp", "Final Depth of Tool- lowest value in Z" + ), + ) if FeatureNoFinalDepth & features: - obj.setEditorMode('FinalDepth', 2) # hide - self.addOpValues(obj, ['start', 'final']) + obj.setEditorMode("FinalDepth", 2) # hide + self.addOpValues(obj, ["start", "final"]) else: # StartDepth has become necessary for expressions on other properties - obj.addProperty("App::PropertyDistance", "StartDepth", "Depth", QtCore.QT_TRANSLATE_NOOP("PathOp", "Starting Depth internal use only for derived values")) - obj.setEditorMode('StartDepth', 1) # read-only + obj.addProperty( + "App::PropertyDistance", + "StartDepth", + "Depth", + QtCore.QT_TRANSLATE_NOOP( + "PathOp", "Starting Depth internal use only for derived values" + ), + ) + obj.setEditorMode("StartDepth", 1) # read-only - self.addOpValues(obj, ['stockz']) + self.addOpValues(obj, ["stockz"]) if FeatureStepDown & features: - obj.addProperty("App::PropertyDistance", "StepDown", "Depth", QtCore.QT_TRANSLATE_NOOP("PathOp", "Incremental Step Down of Tool")) + obj.addProperty( + "App::PropertyDistance", + "StepDown", + "Depth", + QtCore.QT_TRANSLATE_NOOP("PathOp", "Incremental Step Down of Tool"), + ) if FeatureFinishDepth & features: - obj.addProperty("App::PropertyDistance", "FinishDepth", "Depth", QtCore.QT_TRANSLATE_NOOP("PathOp", "Maximum material removed on final pass.")) + obj.addProperty( + "App::PropertyDistance", + "FinishDepth", + "Depth", + QtCore.QT_TRANSLATE_NOOP( + "PathOp", "Maximum material removed on final pass." + ), + ) if FeatureHeights & features: - obj.addProperty("App::PropertyDistance", "ClearanceHeight", "Depth", QtCore.QT_TRANSLATE_NOOP("PathOp", "The height needed to clear clamps and obstructions")) - obj.addProperty("App::PropertyDistance", "SafeHeight", "Depth", QtCore.QT_TRANSLATE_NOOP("PathOp", "Rapid Safety Height between locations.")) + obj.addProperty( + "App::PropertyDistance", + "ClearanceHeight", + "Depth", + QtCore.QT_TRANSLATE_NOOP( + "PathOp", "The height needed to clear clamps and obstructions" + ), + ) + obj.addProperty( + "App::PropertyDistance", + "SafeHeight", + "Depth", + QtCore.QT_TRANSLATE_NOOP( + "PathOp", "Rapid Safety Height between locations." + ), + ) if FeatureStartPoint & features: - obj.addProperty("App::PropertyVectorDistance", "StartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("PathOp", "The start point of this path")) - obj.addProperty("App::PropertyBool", "UseStartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("PathOp", "Make True, if specifying a Start Point")) + obj.addProperty( + "App::PropertyVectorDistance", + "StartPoint", + "Start Point", + QtCore.QT_TRANSLATE_NOOP("PathOp", "The start point of this path"), + ) + obj.addProperty( + "App::PropertyBool", + "UseStartPoint", + "Start Point", + QtCore.QT_TRANSLATE_NOOP( + "PathOp", "Make True, if specifying a Start Point" + ), + ) if FeatureDiameters & features: - obj.addProperty("App::PropertyDistance", "MinDiameter", "Diameter", QtCore.QT_TRANSLATE_NOOP("PathOp", "Lower limit of the turning diameter")) - obj.addProperty("App::PropertyDistance", "MaxDiameter", "Diameter", QtCore.QT_TRANSLATE_NOOP("PathOp", "Upper limit of the turning diameter.")) + obj.addProperty( + "App::PropertyDistance", + "MinDiameter", + "Diameter", + QtCore.QT_TRANSLATE_NOOP( + "PathOp", "Lower limit of the turning diameter" + ), + ) + obj.addProperty( + "App::PropertyDistance", + "MaxDiameter", + "Diameter", + QtCore.QT_TRANSLATE_NOOP( + "PathOp", "Upper limit of the turning diameter." + ), + ) # members being set later self.commandlist = None @@ -199,135 +348,153 @@ class ObjectOp(object): obj.Proxy = self def setEditorModes(self, obj, features): - '''Editor modes are not preserved during document store/restore, set editor modes for all properties''' + """Editor modes are not preserved during document store/restore, set editor modes for all properties""" - for op in ['OpStartDepth', 'OpFinalDepth', 'OpToolDiameter', 'CycleTime']: + for op in ["OpStartDepth", "OpFinalDepth", "OpToolDiameter", "CycleTime"]: if hasattr(obj, op): obj.setEditorMode(op, 1) # read-only if FeatureDepths & features: if FeatureNoFinalDepth & features: - obj.setEditorMode('OpFinalDepth', 2) + obj.setEditorMode("OpFinalDepth", 2) def onDocumentRestored(self, obj): features = self.opFeatures(obj) - if FeatureBaseGeometry & features and 'App::PropertyLinkSubList' == obj.getTypeIdOfProperty('Base'): + if ( + FeatureBaseGeometry & features + and "App::PropertyLinkSubList" == obj.getTypeIdOfProperty("Base") + ): PathLog.info("Replacing link property with global link (%s)." % obj.State) base = obj.Base - obj.removeProperty('Base') + obj.removeProperty("Base") self.addBaseProperty(obj) obj.Base = base obj.touch() obj.Document.recompute() - if FeatureTool & features and not hasattr(obj, 'OpToolDiameter'): - self.addOpValues(obj, ['tooldia']) + if FeatureTool & features and not hasattr(obj, "OpToolDiameter"): + self.addOpValues(obj, ["tooldia"]) - if FeatureCoolant & features and not hasattr(obj, 'CoolantMode'): - obj.addProperty("App::PropertyString", "CoolantMode", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "Coolant option for this operation")) + if FeatureCoolant & features and not hasattr(obj, "CoolantMode"): + obj.addProperty( + "App::PropertyString", + "CoolantMode", + "Path", + QtCore.QT_TRANSLATE_NOOP("PathOp", "Coolant option for this operation"), + ) - if FeatureDepths & features and not hasattr(obj, 'OpStartDepth'): - self.addOpValues(obj, ['start', 'final']) + if FeatureDepths & features and not hasattr(obj, "OpStartDepth"): + self.addOpValues(obj, ["start", "final"]) if FeatureNoFinalDepth & features: - obj.setEditorMode('OpFinalDepth', 2) + obj.setEditorMode("OpFinalDepth", 2) - if not hasattr(obj, 'OpStockZMax'): - self.addOpValues(obj, ['stockz']) + if not hasattr(obj, "OpStockZMax"): + self.addOpValues(obj, ["stockz"]) - if not hasattr(obj, 'CycleTime'): - obj.addProperty("App::PropertyString", "CycleTime", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "Operations Cycle Time Estimation")) + if not hasattr(obj, "CycleTime"): + obj.addProperty( + "App::PropertyString", + "CycleTime", + "Path", + QtCore.QT_TRANSLATE_NOOP("PathOp", "Operations Cycle Time Estimation"), + ) self.setEditorModes(obj, features) self.opOnDocumentRestored(obj) def __getstate__(self): - '''__getstat__(self) ... called when receiver is saved. - Can safely be overwritten by subclasses.''' + """__getstat__(self) ... called when receiver is saved. + Can safely be overwritten by subclasses.""" return None def __setstate__(self, state): - '''__getstat__(self) ... called when receiver is restored. - Can safely be overwritten by subclasses.''' + """__getstat__(self) ... called when receiver is restored. + Can safely be overwritten by subclasses.""" return None def opFeatures(self, obj): - '''opFeatures(obj) ... returns the OR'ed list of features used and supported by the operation. + """opFeatures(obj) ... returns the OR'ed list of features used and supported by the operation. The default implementation returns "FeatureTool | FeatureDepths | FeatureHeights | FeatureStartPoint" - Should be overwritten by subclasses.''' + Should be overwritten by subclasses.""" # pylint: disable=unused-argument - return FeatureTool | FeatureDepths | FeatureHeights | FeatureStartPoint | FeatureBaseGeometry | FeatureFinishDepth | FeatureCoolant + return ( + FeatureTool + | FeatureDepths + | FeatureHeights + | FeatureStartPoint + | FeatureBaseGeometry + | FeatureFinishDepth + | FeatureCoolant + ) def initOperation(self, obj): - '''initOperation(obj) ... implement to create additional properties. - Should be overwritten by subclasses.''' + """initOperation(obj) ... implement to create additional properties. + Should be overwritten by subclasses.""" pass # pylint: disable=unnecessary-pass def opOnDocumentRestored(self, obj): - '''opOnDocumentRestored(obj) ... implement if an op needs special handling like migrating the data model. - Should be overwritten by subclasses.''' + """opOnDocumentRestored(obj) ... implement if an op needs special handling like migrating the data model. + Should be overwritten by subclasses.""" pass # pylint: disable=unnecessary-pass def opOnChanged(self, obj, prop): - '''opOnChanged(obj, prop) ... overwrite to process property changes. + """opOnChanged(obj, prop) ... overwrite to process property changes. This is a callback function that is invoked each time a property of the receiver is assigned a value. Note that the FC framework does not distinguish between assigning a different value and assigning the same value again. - Can safely be overwritten by subclasses.''' + Can safely be overwritten by subclasses.""" pass # pylint: disable=unnecessary-pass def opSetDefaultValues(self, obj, job): - '''opSetDefaultValues(obj, job) ... overwrite to set initial default values. + """opSetDefaultValues(obj, job) ... overwrite to set initial default values. Called after the receiver has been fully created with all properties. - Can safely be overwritten by subclasses.''' + Can safely be overwritten by subclasses.""" pass # pylint: disable=unnecessary-pass def opUpdateDepths(self, obj): - '''opUpdateDepths(obj) ... overwrite to implement special depths calculation. - Can safely be overwritten by subclass.''' + """opUpdateDepths(obj) ... overwrite to implement special depths calculation. + Can safely be overwritten by subclass.""" pass # pylint: disable=unnecessary-pass def opExecute(self, obj): - '''opExecute(obj) ... called whenever the receiver needs to be recalculated. + """opExecute(obj) ... called whenever the receiver needs to be recalculated. See documentation of execute() for a list of base functionality provided. - Should be overwritten by subclasses.''' + Should be overwritten by subclasses.""" pass # pylint: disable=unnecessary-pass def opRejectAddBase(self, obj, base, sub): - '''opRejectAddBase(base, sub) ... if op returns True the addition of the feature is prevented. - Should be overwritten by subclasses.''' + """opRejectAddBase(base, sub) ... if op returns True the addition of the feature is prevented. + Should be overwritten by subclasses.""" # pylint: disable=unused-argument return False def onChanged(self, obj, prop): - '''onChanged(obj, prop) ... base implementation of the FC notification framework. - Do not overwrite, overwrite opOnChanged() instead.''' + """onChanged(obj, prop) ... base implementation of the FC notification framework. + Do not overwrite, overwrite opOnChanged() instead.""" # there's a bit of cycle going on here, if sanitizeBase causes the transaction to # be cancelled we end right here again with the unsainitized Base - if that is the # case, stop the cycle and return immediately - if prop == 'Base' and self.sanitizeBase(obj): + if prop == "Base" and self.sanitizeBase(obj): return - if 'Restore' not in obj.State and prop in ['Base', 'StartDepth', 'FinalDepth']: + if "Restore" not in obj.State and prop in ["Base", "StartDepth", "FinalDepth"]: self.updateDepths(obj, True) self.opOnChanged(obj, prop) def applyExpression(self, obj, prop, expr): - '''applyExpression(obj, prop, expr) ... set expression expr on obj.prop if expr is set''' + """applyExpression(obj, prop, expr) ... set expression expr on obj.prop if expr is set""" if expr: obj.setExpression(prop, expr) return True return False def setDefaultValues(self, obj): - '''setDefaultValues(obj) ... base implementation. - Do not overwrite, overwrite opSetDefaultValues() instead.''' - if self.job: - job = self.job - else: - job = PathUtils.addToJob(obj) + """setDefaultValues(obj) ... base implementation. + Do not overwrite, overwrite opSetDefaultValues() instead.""" + job = PathUtils.addToJob(obj) obj.Active = True @@ -335,7 +502,9 @@ class ObjectOp(object): if FeatureTool & features: if 1 < len(job.Operations.Group): - obj.ToolController = PathUtil.toolControllerForOp(job.Operations.Group[-2]) + obj.ToolController = PathUtil.toolControllerForOp( + job.Operations.Group[-2] + ) else: obj.ToolController = PathUtils.findToolController(obj, self) if not obj.ToolController: @@ -346,11 +515,15 @@ class ObjectOp(object): obj.CoolantMode = job.SetupSheet.CoolantMode if FeatureDepths & features: - if self.applyExpression(obj, 'StartDepth', job.SetupSheet.StartDepthExpression): + if self.applyExpression( + obj, "StartDepth", job.SetupSheet.StartDepthExpression + ): obj.OpStartDepth = 1.0 else: obj.StartDepth = 1.0 - if self.applyExpression(obj, 'FinalDepth', job.SetupSheet.FinalDepthExpression): + if self.applyExpression( + obj, "FinalDepth", job.SetupSheet.FinalDepthExpression + ): obj.OpFinalDepth = 0.0 else: obj.FinalDepth = 0.0 @@ -358,20 +531,26 @@ class ObjectOp(object): obj.StartDepth = 1.0 if FeatureStepDown & features: - if not self.applyExpression(obj, 'StepDown', job.SetupSheet.StepDownExpression): - obj.StepDown = '1 mm' + if not self.applyExpression( + obj, "StepDown", job.SetupSheet.StepDownExpression + ): + obj.StepDown = "1 mm" if FeatureHeights & features: if job.SetupSheet.SafeHeightExpression: - if not self.applyExpression(obj, 'SafeHeight', job.SetupSheet.SafeHeightExpression): - obj.SafeHeight = '3 mm' + if not self.applyExpression( + obj, "SafeHeight", job.SetupSheet.SafeHeightExpression + ): + obj.SafeHeight = "3 mm" if job.SetupSheet.ClearanceHeightExpression: - if not self.applyExpression(obj, 'ClearanceHeight', job.SetupSheet.ClearanceHeightExpression): - obj.ClearanceHeight = '5 mm' + if not self.applyExpression( + obj, "ClearanceHeight", job.SetupSheet.ClearanceHeightExpression + ): + obj.ClearanceHeight = "5 mm" if FeatureDiameters & features: - obj.MinDiameter = '0 mm' - obj.MaxDiameter = '0 mm' + obj.MinDiameter = "0 mm" + obj.MaxDiameter = "0 mm" if job.Stock: obj.MaxDiameter = job.Stock.Shape.BoundBox.XLength @@ -389,7 +568,10 @@ class ObjectOp(object): return False if not job.Model.Group: if not ignoreErrors: - PathLog.error(translate("Path", "Parent job %s doesn't have a base object") % job.Label) + PathLog.error( + translate("Path", "Parent job %s doesn't have a base object") + % job.Label + ) return False self.job = job self.model = job.Model.Group @@ -397,15 +579,15 @@ class ObjectOp(object): return True def getJob(self, obj): - '''getJob(obj) ... return the job this operation is part of.''' - if not hasattr(self, 'job') or self.job is None: + """getJob(obj) ... return the job this operation is part of.""" + if not hasattr(self, "job") or self.job is None: if not self._setBaseAndStock(obj): return None return self.job def updateDepths(self, obj, ignoreErrors=False): - '''updateDepths(obj) ... base implementation calculating depths depending on base geometry. - Should not be overwritten.''' + """updateDepths(obj) ... base implementation calculating depths depending on base geometry. + Should not be overwritten.""" def faceZmin(bb, fbb): if fbb.ZMax == fbb.ZMin and fbb.ZMax == bb.ZMax: # top face @@ -428,7 +610,7 @@ class ObjectOp(object): obj.OpStockZMin = zmin obj.OpStockZMax = zmax - if hasattr(obj, 'Base') and obj.Base: + if hasattr(obj, "Base") and obj.Base: for base, sublist in obj.Base: bb = base.Shape.BoundBox zmax = max(zmax, bb.ZMax) @@ -456,7 +638,9 @@ class ObjectOp(object): zmin = obj.OpFinalDepth.Value def minZmax(z): - if hasattr(obj, 'StepDown') and not PathGeom.isRoughly(obj.StepDown.Value, 0): + if hasattr(obj, "StepDown") and not PathGeom.isRoughly( + obj.StepDown.Value, 0 + ): return z + obj.StepDown.Value else: return z + 1 @@ -476,21 +660,23 @@ class ObjectOp(object): self.opUpdateDepths(obj) def sanitizeBase(self, obj): - '''sanitizeBase(obj) ... check if Base is valid and clear on errors.''' - if hasattr(obj, 'Base'): + """sanitizeBase(obj) ... check if Base is valid and clear on errors.""" + if hasattr(obj, "Base"): try: for (o, sublist) in obj.Base: for sub in sublist: - e = o.Shape.getElement(sub) - except Part.OCCError as e: - PathLog.error("{} - stale base geometry detected - clearing.".format(obj.Label)) + o.Shape.getElement(sub) + except Part.OCCError: + PathLog.error( + "{} - stale base geometry detected - clearing.".format(obj.Label) + ) obj.Base = [] return True return False @waiting_effects def execute(self, obj): - '''execute(obj) ... base implementation - do not overwrite! + """execute(obj) ... base implementation - do not overwrite! Verifies that the operation is assigned to a job and that the job also has a valid Base. It also sets the following instance variables that can and should be safely be used by implementation of opExecute(): @@ -508,7 +694,7 @@ class ObjectOp(object): opExecute(obj) - which is expected to add the generated commands to self.commandlist Finally the base implementation adds a rapid move to clearance height and assigns the receiver's Path property from the command list. - ''' + """ PathLog.track() if not obj.Active: @@ -523,13 +709,22 @@ class ObjectOp(object): self.sanitizeBase(obj) if FeatureCoolant & self.opFeatures(obj): - if not hasattr(obj, 'CoolantMode'): - PathLog.error(translate("Path", "No coolant property found. Please recreate operation.")) + if not hasattr(obj, "CoolantMode"): + PathLog.error( + translate( + "Path", "No coolant property found. Please recreate operation." + ) + ) if FeatureTool & self.opFeatures(obj): tc = obj.ToolController if tc is None or tc.ToolNumber == 0: - PathLog.error(translate("Path", "No Tool Controller is selected. We need a tool to build a Path.")) + PathLog.error( + translate( + "Path", + "No Tool Controller is selected. We need a tool to build a Path.", + ) + ) return else: self.vertFeed = tc.VertFeed.Value @@ -538,7 +733,12 @@ class ObjectOp(object): self.horizRapid = tc.HorizRapid.Value tool = tc.Proxy.getTool(tc) if not tool or float(tool.Diameter) == 0: - PathLog.error(translate("Path", "No Tool found or diameter is zero. We need a tool to build a Path.")) + PathLog.error( + translate( + "Path", + "No Tool found or diameter is zero. We need a tool to build a Path.", + ) + ) return self.radius = float(tool.Diameter) / 2.0 self.tool = tool @@ -558,7 +758,9 @@ class ObjectOp(object): if self.commandlist and (FeatureHeights & self.opFeatures(obj)): # Let's finish by rapid to clearance...just for safety - self.commandlist.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value})) + self.commandlist.append( + Path.Command("G0", {"Z": obj.ClearanceHeight.Value}) + ) path = Path.Path(self.commandlist) obj.Path = path @@ -572,25 +774,39 @@ class ObjectOp(object): if tc is None or tc.ToolNumber == 0: PathLog.error(translate("Path", "No Tool Controller selected.")) - return translate('Path', 'Tool Error') + return translate("Path", "Tool Error") hFeedrate = tc.HorizFeed.Value vFeedrate = tc.VertFeed.Value hRapidrate = tc.HorizRapid.Value vRapidrate = tc.VertRapid.Value - if (hFeedrate == 0 or vFeedrate == 0) and not PathPreferences.suppressAllSpeedsWarning(): - PathLog.warning(translate("Path", "Tool Controller feedrates required to calculate the cycle time.")) - return translate('Path', 'Feedrate Error') + if ( + hFeedrate == 0 or vFeedrate == 0 + ) and not PathPreferences.suppressAllSpeedsWarning(): + PathLog.warning( + translate( + "Path", + "Tool Controller feedrates required to calculate the cycle time.", + ) + ) + return translate("Path", "Feedrate Error") - if (hRapidrate == 0 or vRapidrate == 0) and not PathPreferences.suppressRapidSpeedsWarning(): - PathLog.warning(translate("Path", "Add Tool Controller Rapid Speeds on the SetupSheet for more accurate cycle times.")) + if ( + hRapidrate == 0 or vRapidrate == 0 + ) and not PathPreferences.suppressRapidSpeedsWarning(): + PathLog.warning( + translate( + "Path", + "Add Tool Controller Rapid Speeds on the SetupSheet for more accurate cycle times.", + ) + ) # Get the cycle time in seconds seconds = obj.Path.getCycleTime(hFeedrate, vFeedrate, hRapidrate, vRapidrate) if not seconds: - return translate('Path', 'Cycletime Error') + return translate("Path", "Cycletime Error") # Convert the cycle time to a HH:MM:SS format cycleTime = time.strftime("%H:%M:%S", time.gmtime(seconds)) @@ -613,17 +829,29 @@ class ObjectOp(object): for p, el in baselist: if p == base and sub in el: - PathLog.notice((translate("Path", "Base object %s.%s already in the list") + "\n") % (base.Label, sub)) + PathLog.notice( + ( + translate("Path", "Base object %s.%s already in the list") + + "\n" + ) + % (base.Label, sub) + ) return if not self.opRejectAddBase(obj, base, sub): baselist.append((base, sub)) obj.Base = baselist else: - PathLog.notice((translate("Path", "Base object %s.%s rejected by operation") + "\n") % (base.Label, sub)) + PathLog.notice( + ( + translate("Path", "Base object %s.%s rejected by operation") + + "\n" + ) + % (base.Label, sub) + ) def isToolSupported(self, obj, tool): - '''toolSupported(obj, tool) ... Returns true if the op supports the given tool. - This function can safely be overwritten by subclasses.''' + """toolSupported(obj, tool) ... Returns true if the op supports the given tool. + This function can safely be overwritten by subclasses.""" return True diff --git a/src/Mod/Path/PathScripts/PathProfile.py b/src/Mod/Path/PathScripts/PathProfile.py index 07901949f8..508ad3482b 100644 --- a/src/Mod/Path/PathScripts/PathProfile.py +++ b/src/Mod/Path/PathScripts/PathProfile.py @@ -24,26 +24,28 @@ import FreeCAD import Path +import PathScripts.PathAreaOp as PathAreaOp import PathScripts.PathLog as PathLog import PathScripts.PathOp as PathOp -import PathScripts.PathAreaOp as PathAreaOp import PathScripts.PathUtils as PathUtils -import numpy import math +import numpy from PySide import QtCore # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader -ArchPanel = LazyLoader('ArchPanel', globals(), 'ArchPanel') -Part = LazyLoader('Part', globals(), 'Part') -DraftGeomUtils = LazyLoader('DraftGeomUtils', globals(), 'DraftGeomUtils') + +Part = LazyLoader("Part", globals(), "Part") +DraftGeomUtils = LazyLoader("DraftGeomUtils", globals(), "DraftGeomUtils") __title__ = "Path Profile Operation" __author__ = "sliptonic (Brad Collette)" __url__ = "http://www.freecadweb.org" -__doc__ = "Path Profile operation based on entire model, selected faces or selected edges." +__doc__ = ( + "Path Profile operation based on entire model, selected faces or selected edges." +) __contributors__ = "Schildkroet" PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) @@ -55,23 +57,22 @@ def translate(context, text, disambig=None): class ObjectProfile(PathAreaOp.ObjectOp): - '''Proxy object for Profile operations based on faces.''' + """Proxy object for Profile operations based on faces.""" def areaOpFeatures(self, obj): - '''areaOpFeatures(obj) ... returns operation-specific features''' - return PathOp.FeatureBaseFaces | PathOp.FeatureBasePanels \ - | PathOp.FeatureBaseEdges + """areaOpFeatures(obj) ... returns operation-specific features""" + return PathOp.FeatureBaseFaces | PathOp.FeatureBaseEdges def initAreaOp(self, obj): - '''initAreaOp(obj) ... creates all profile specific properties.''' + """initAreaOp(obj) ... creates all profile specific properties.""" self.propertiesReady = False self.initAreaOpProperties(obj) - obj.setEditorMode('MiterLimit', 2) - obj.setEditorMode('JoinType', 2) + obj.setEditorMode("MiterLimit", 2) + obj.setEditorMode("JoinType", 2) def initAreaOpProperties(self, obj, warn=False): - '''initAreaOpProperties(obj) ... create operation specific properties''' + """initAreaOpProperties(obj) ... create operation specific properties""" self.addNewProps = list() for (prtyp, nm, grp, tt) in self.areaOpProperties(): @@ -86,65 +87,134 @@ class ObjectProfile(PathAreaOp.ObjectOp): if n in self.addNewProps: setattr(obj, n, ENUMS[n]) if warn: - newPropMsg = translate('PathProfile', 'New property added to') - newPropMsg += ' "{}": {}'.format(obj.Label, self.addNewProps) + '. ' - newPropMsg += translate('PathProfile', 'Check its default value.') + '\n' + newPropMsg = translate("PathProfile", "New property added to") + newPropMsg += ' "{}": {}'.format(obj.Label, self.addNewProps) + ". " + newPropMsg += ( + translate("PathProfile", "Check its default value.") + "\n" + ) FreeCAD.Console.PrintWarning(newPropMsg) self.propertiesReady = True def areaOpProperties(self): - '''areaOpProperties(obj) ... returns a tuples. + """areaOpProperties(obj) ... returns a tuples. Each tuple contains property declaration information in the - form of (prototype, name, section, tooltip).''' + form of (prototype, name, section, tooltip).""" return [ - ("App::PropertyEnumeration", "Direction", "Profile", - QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction that the toolpath should go around the part ClockWise (CW) or CounterClockWise (CCW)")), - ("App::PropertyEnumeration", "HandleMultipleFeatures", "Profile", - QtCore.QT_TRANSLATE_NOOP("PathPocket", "Choose how to process multiple Base Geometry features.")), - ("App::PropertyEnumeration", "JoinType", "Profile", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Controls how tool moves around corners. Default=Round")), - ("App::PropertyFloat", "MiterLimit", "Profile", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Maximum distance before a miter join is truncated")), - ("App::PropertyDistance", "OffsetExtra", "Profile", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Extra value to stay away from final profile- good for roughing toolpath")), - ("App::PropertyBool", "processHoles", "Profile", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile holes as well as the outline")), - ("App::PropertyBool", "processPerimeter", "Profile", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile the outline")), - ("App::PropertyBool", "processCircles", "Profile", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile round holes")), - ("App::PropertyEnumeration", "Side", "Profile", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Side of edge that tool should cut")), - ("App::PropertyBool", "UseComp", "Profile", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Make True, if using Cutter Radius Compensation")), + ( + "App::PropertyEnumeration", + "Direction", + "Profile", + QtCore.QT_TRANSLATE_NOOP( + "App::Property", + "The direction that the toolpath should go around the part ClockWise (CW) or CounterClockWise (CCW)", + ), + ), + ( + "App::PropertyEnumeration", + "HandleMultipleFeatures", + "Profile", + QtCore.QT_TRANSLATE_NOOP( + "PathPocket", + "Choose how to process multiple Base Geometry features.", + ), + ), + ( + "App::PropertyEnumeration", + "JoinType", + "Profile", + QtCore.QT_TRANSLATE_NOOP( + "App::Property", + "Controls how tool moves around corners. Default=Round", + ), + ), + ( + "App::PropertyFloat", + "MiterLimit", + "Profile", + QtCore.QT_TRANSLATE_NOOP( + "App::Property", "Maximum distance before a miter join is truncated" + ), + ), + ( + "App::PropertyDistance", + "OffsetExtra", + "Profile", + QtCore.QT_TRANSLATE_NOOP( + "App::Property", + "Extra value to stay away from final profile- good for roughing toolpath", + ), + ), + ( + "App::PropertyBool", + "processHoles", + "Profile", + QtCore.QT_TRANSLATE_NOOP( + "App::Property", "Profile holes as well as the outline" + ), + ), + ( + "App::PropertyBool", + "processPerimeter", + "Profile", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile the outline"), + ), + ( + "App::PropertyBool", + "processCircles", + "Profile", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile round holes"), + ), + ( + "App::PropertyEnumeration", + "Side", + "Profile", + QtCore.QT_TRANSLATE_NOOP( + "App::Property", "Side of edge that tool should cut" + ), + ), + ( + "App::PropertyBool", + "UseComp", + "Profile", + QtCore.QT_TRANSLATE_NOOP( + "App::Property", "Make True, if using Cutter Radius Compensation" + ), + ), ] def areaOpPropertyEnumerations(self): - '''areaOpPropertyEnumerations() ... returns a dictionary of enumeration lists - for the operation's enumeration type properties.''' + """areaOpPropertyEnumerations() ... returns a dictionary of enumeration lists + for the operation's enumeration type properties.""" # Enumeration lists for App::PropertyEnumeration properties return { - 'Direction': ['CW', 'CCW'], # this is the direction that the profile runs - 'HandleMultipleFeatures': ['Collectively', 'Individually'], - 'JoinType': ['Round', 'Square', 'Miter'], # this is the direction that the Profile runs - 'Side': ['Outside', 'Inside'], # side of profile that cutter is on in relation to direction of profile + "Direction": ["CW", "CCW"], # this is the direction that the profile runs + "HandleMultipleFeatures": ["Collectively", "Individually"], + "JoinType": [ + "Round", + "Square", + "Miter", + ], # this is the direction that the Profile runs + "Side": [ + "Outside", + "Inside", + ], # side of profile that cutter is on in relation to direction of profile } def areaOpPropertyDefaults(self, obj, job): - '''areaOpPropertyDefaults(obj, job) ... returns a dictionary of default values - for the operation's properties.''' + """areaOpPropertyDefaults(obj, job) ... returns a dictionary of default values + for the operation's properties.""" return { - 'Direction': 'CW', - 'HandleMultipleFeatures': 'Collectively', - 'JoinType': 'Round', - 'MiterLimit': 0.1, - 'OffsetExtra': 0.0, - 'Side': 'Outside', - 'UseComp': True, - 'processCircles': False, - 'processHoles': False, - 'processPerimeter': True + "Direction": "CW", + "HandleMultipleFeatures": "Collectively", + "JoinType": "Round", + "MiterLimit": 0.1, + "OffsetExtra": 0.0, + "Side": "Outside", + "UseComp": True, + "processCircles": False, + "processHoles": False, + "processPerimeter": True, } def areaOpApplyPropertyDefaults(self, obj, job, propList): @@ -155,13 +225,13 @@ class ObjectProfile(PathAreaOp.ObjectOp): prop = getattr(obj, n) val = PROP_DFLTS[n] setVal = False - if hasattr(prop, 'Value'): + if hasattr(prop, "Value"): if isinstance(val, int) or isinstance(val, float): setVal = True if setVal: # propVal = getattr(prop, 'Value') # Need to check if `val` below should be `propVal` commented out above - setattr(prop, 'Value', val) + setattr(prop, "Value", val) else: setattr(obj, n, val) @@ -170,30 +240,30 @@ class ObjectProfile(PathAreaOp.ObjectOp): self.areaOpApplyPropertyDefaults(obj, job, self.addNewProps) def setOpEditorProperties(self, obj): - '''setOpEditorProperties(obj, porp) ... Process operation-specific changes to properties visibility.''' + """setOpEditorProperties(obj, porp) ... Process operation-specific changes to properties visibility.""" fc = 2 # ml = 0 if obj.JoinType == 'Miter' else 2 side = 0 if obj.UseComp else 2 opType = self._getOperationType(obj) - if opType == 'Contour': + if opType == "Contour": side = 2 - elif opType == 'Face': + elif opType == "Face": fc = 0 - elif opType == 'Edge': + elif opType == "Edge": pass - obj.setEditorMode('JoinType', 2) - obj.setEditorMode('MiterLimit', 2) # ml - obj.setEditorMode('Side', side) - obj.setEditorMode('HandleMultipleFeatures', fc) - obj.setEditorMode('processCircles', fc) - obj.setEditorMode('processHoles', fc) - obj.setEditorMode('processPerimeter', fc) + obj.setEditorMode("JoinType", 2) + obj.setEditorMode("MiterLimit", 2) # ml + obj.setEditorMode("Side", side) + obj.setEditorMode("HandleMultipleFeatures", fc) + obj.setEditorMode("processCircles", fc) + obj.setEditorMode("processHoles", fc) + obj.setEditorMode("processPerimeter", fc) def _getOperationType(self, obj): if len(obj.Base) == 0: - return 'Contour' + return "Contour" # return first geometry type selected (_, subsList) = obj.Base[0] @@ -207,39 +277,39 @@ class ObjectProfile(PathAreaOp.ObjectOp): self.setOpEditorProperties(obj) def areaOpOnChanged(self, obj, prop): - '''areaOpOnChanged(obj, prop) ... updates certain property visibilities depending on changed properties.''' - if prop in ['UseComp', 'JoinType', 'Base']: - if hasattr(self, 'propertiesReady') and self.propertiesReady: + """areaOpOnChanged(obj, prop) ... updates certain property visibilities depending on changed properties.""" + if prop in ["UseComp", "JoinType", "Base"]: + if hasattr(self, "propertiesReady") and self.propertiesReady: self.setOpEditorProperties(obj) def areaOpAreaParams(self, obj, isHole): - '''areaOpAreaParams(obj, isHole) ... returns dictionary with area parameters. - Do not overwrite.''' + """areaOpAreaParams(obj, isHole) ... returns dictionary with area parameters. + Do not overwrite.""" params = {} - params['Fill'] = 0 - params['Coplanar'] = 0 - params['SectionCount'] = -1 + params["Fill"] = 0 + params["Coplanar"] = 0 + params["SectionCount"] = -1 offset = obj.OffsetExtra.Value # 0.0 if obj.UseComp: offset = self.radius + obj.OffsetExtra.Value - if obj.Side == 'Inside': + if obj.Side == "Inside": offset = 0 - offset if isHole: offset = 0 - offset - params['Offset'] = offset + params["Offset"] = offset - jointype = ['Round', 'Square', 'Miter'] - params['JoinType'] = jointype.index(obj.JoinType) + jointype = ["Round", "Square", "Miter"] + params["JoinType"] = jointype.index(obj.JoinType) - if obj.JoinType == 'Miter': - params['MiterLimit'] = obj.MiterLimit + if obj.JoinType == "Miter": + params["MiterLimit"] = obj.MiterLimit return params def areaOpPathParams(self, obj, isHole): - '''areaOpPathParams(obj, isHole) ... returns dictionary with path parameters. - Do not overwrite.''' + """areaOpPathParams(obj, isHole) ... returns dictionary with path parameters. + Do not overwrite.""" params = {} # Reverse the direction for holes @@ -248,65 +318,78 @@ class ObjectProfile(PathAreaOp.ObjectOp): else: direction = obj.Direction - if direction == 'CCW': - params['orientation'] = 0 + if direction == "CCW": + params["orientation"] = 0 else: - params['orientation'] = 1 + params["orientation"] = 1 if not obj.UseComp: - if direction == 'CCW': - params['orientation'] = 1 + if direction == "CCW": + params["orientation"] = 1 else: - params['orientation'] = 0 + params["orientation"] = 0 return params def areaOpUseProjection(self, obj): - '''areaOpUseProjection(obj) ... returns True''' + """areaOpUseProjection(obj) ... returns True""" return True def opUpdateDepths(self, obj): - if hasattr(obj, 'Base') and obj.Base.__len__() == 0: + if hasattr(obj, "Base") and obj.Base.__len__() == 0: obj.OpStartDepth = obj.OpStockZMax obj.OpFinalDepth = obj.OpStockZMin def areaOpShapes(self, obj): - '''areaOpShapes(obj) ... returns envelope for all base shapes or wires for Arch.Panels.''' + """areaOpShapes(obj) ... returns envelope for all base shapes or wires""" PathLog.track() shapes = [] remainingObjBaseFeatures = list() self.isDebug = True if PathLog.getLevel(PathLog.thisModule()) == 4 else False - self.inaccessibleMsg = translate('PathProfile', 'The selected edge(s) are inaccessible. If multiple, re-ordering selection might work.') + self.inaccessibleMsg = translate( + "PathProfile", + "The selected edge(s) are inaccessible. If multiple, re-ordering selection might work.", + ) self.offsetExtra = obj.OffsetExtra.Value if self.isDebug: - for grpNm in ['tmpDebugGrp', 'tmpDebugGrp001']: + for grpNm in ["tmpDebugGrp", "tmpDebugGrp001"]: if hasattr(FreeCAD.ActiveDocument, grpNm): for go in FreeCAD.ActiveDocument.getObject(grpNm).Group: FreeCAD.ActiveDocument.removeObject(go.Name) FreeCAD.ActiveDocument.removeObject(grpNm) - self.tmpGrp = FreeCAD.ActiveDocument.addObject('App::DocumentObjectGroup', 'tmpDebugGrp') + self.tmpGrp = FreeCAD.ActiveDocument.addObject( + "App::DocumentObjectGroup", "tmpDebugGrp" + ) tmpGrpNm = self.tmpGrp.Name self.JOB = PathUtils.findParentJob(obj) if obj.UseComp: self.useComp = True self.ofstRadius = self.radius + self.offsetExtra - self.commandlist.append(Path.Command("(Compensated Tool Path. Diameter: " + str(self.radius * 2) + ")")) + self.commandlist.append( + Path.Command( + "(Compensated Tool Path. Diameter: " + str(self.radius * 2) + ")" + ) + ) else: self.useComp = False self.ofstRadius = self.offsetExtra self.commandlist.append(Path.Command("(Uncompensated Tool Path)")) # Pre-process Base Geometry to process edges - if obj.Base and len(obj.Base) > 0: # The user has selected subobjects from the base. Process each. + if ( + obj.Base and len(obj.Base) > 0 + ): # The user has selected subobjects from the base. Process each. shapes.extend(self._processEdges(obj, remainingObjBaseFeatures)) if obj.Base and len(obj.Base) > 0 and not remainingObjBaseFeatures: # Edges were already processed, or whole model targeted. PathLog.debug("remainingObjBaseFeatures is False") - elif remainingObjBaseFeatures and len(remainingObjBaseFeatures) > 0: # Process remaining features after edges processed above. + elif ( + remainingObjBaseFeatures and len(remainingObjBaseFeatures) > 0 + ): # Process remaining features after edges processed above. for (base, subsList) in remainingObjBaseFeatures: holes = [] faces = [] @@ -317,20 +400,25 @@ class ObjectProfile(PathAreaOp.ObjectOp): # only process faces here if isinstance(shape, Part.Face): faces.append(shape) - if numpy.isclose(abs(shape.normalAt(0, 0).z), 1): # horizontal face + if numpy.isclose( + abs(shape.normalAt(0, 0).z), 1 + ): # horizontal face for wire in shape.Wires[1:]: holes.append((base.Shape, wire)) # Add face depth to list faceDepths.append(shape.BoundBox.ZMin) else: - ignoreSub = base.Name + '.' + sub - msg = translate('PathProfile', "Found a selected object which is not a face. Ignoring:") + ignoreSub = base.Name + "." + sub + msg = translate( + "PathProfile", + "Found a selected object which is not a face. Ignoring:", + ) PathLog.warning(msg + " {}".format(ignoreSub)) for baseShape, wire in holes: cont = False - f = Part.makeFace(wire, 'Part::FaceMakerSimple') + f = Part.makeFace(wire, "Part::FaceMakerSimple") drillable = PathUtils.isDrillable(baseShape, wire) if obj.processCircles: @@ -341,40 +429,48 @@ class ObjectProfile(PathAreaOp.ObjectOp): cont = True if cont: - shapeEnv = PathUtils.getEnvelope(baseShape, subshape=f, depthparams=self.depthparams) + shapeEnv = PathUtils.getEnvelope( + baseShape, subshape=f, depthparams=self.depthparams + ) if shapeEnv: - self._addDebugObject('HoleShapeEnvelope', shapeEnv) - tup = shapeEnv, True, 'pathProfile' + self._addDebugObject("HoleShapeEnvelope", shapeEnv) + tup = shapeEnv, True, "pathProfile" shapes.append(tup) if faces and obj.processPerimeter: - if obj.HandleMultipleFeatures == 'Collectively': + if obj.HandleMultipleFeatures == "Collectively": custDepthparams = self.depthparams cont = True profileshape = Part.makeCompound(faces) try: - shapeEnv = PathUtils.getEnvelope(profileshape, depthparams=custDepthparams) - except Exception as ee: # pylint: disable=broad-except + shapeEnv = PathUtils.getEnvelope( + profileshape, depthparams=custDepthparams + ) + except Exception as ee: # pylint: disable=broad-except # PathUtils.getEnvelope() failed to return an object. - msg = translate('Path', 'Unable to create path for face(s).') - PathLog.error(msg + '\n{}'.format(ee)) + msg = translate( + "Path", "Unable to create path for face(s)." + ) + PathLog.error(msg + "\n{}".format(ee)) cont = False if cont: - self._addDebugObject('CollectCutShapeEnv', shapeEnv) - tup = shapeEnv, False, 'pathProfile' + self._addDebugObject("CollectCutShapeEnv", shapeEnv) + tup = shapeEnv, False, "pathProfile" shapes.append(tup) - elif obj.HandleMultipleFeatures == 'Individually': + elif obj.HandleMultipleFeatures == "Individually": for shape in faces: custDepthparams = self.depthparams - self._addDebugObject('Indiv_Shp', shape) - shapeEnv = PathUtils.getEnvelope(shape, depthparams=custDepthparams) + self._addDebugObject("Indiv_Shp", shape) + shapeEnv = PathUtils.getEnvelope( + shape, depthparams=custDepthparams + ) if shapeEnv: - self._addDebugObject('IndivCutShapeEnv', shapeEnv) - tup = shapeEnv, False, 'pathProfile' + self._addDebugObject("IndivCutShapeEnv", shapeEnv) + tup = shapeEnv, False, "pathProfile" shapes.append(tup) else: # Try to build targets from the job models @@ -382,42 +478,19 @@ class ObjectProfile(PathAreaOp.ObjectOp): self.opUpdateDepths(obj) if 1 == len(self.model) and hasattr(self.model[0], "Proxy"): - if isinstance(self.model[0].Proxy, ArchPanel.PanelSheet): # process the sheet - modelProxy = self.model[0].Proxy - # Process circles and holes if requested by user - if obj.processCircles or obj.processHoles: - for shape in modelProxy.getHoles(self.model[0], transform=True): - for wire in shape.Wires: - drillable = PathUtils.isDrillable(modelProxy, wire) - if (drillable and obj.processCircles) or (not drillable and obj.processHoles): - f = Part.makeFace(wire, 'Part::FaceMakerSimple') - env = PathUtils.getEnvelope(self.model[0].Shape, subshape=f, depthparams=self.depthparams) - tup = env, True, 'pathProfile' - shapes.append(tup) - - # Process perimeter if requested by user - if obj.processPerimeter: - for shape in modelProxy.getOutlines(self.model[0], transform=True): - for wire in shape.Wires: - f = Part.makeFace(wire, 'Part::FaceMakerSimple') - env = PathUtils.getEnvelope(self.model[0].Shape, subshape=f, depthparams=self.depthparams) - tup = env, False, 'pathProfile' - shapes.append(tup) - else: - # shapes.extend([(PathUtils.getEnvelope(partshape=base.Shape, subshape=None, depthparams=self.depthparams), False) for base in self.model if hasattr(base, 'Shape')]) - PathLog.debug('Single model processed.') - shapes.extend(self._processEachModel(obj)) + PathLog.debug("Single model processed.") + shapes.extend(self._processEachModel(obj)) else: - # shapes.extend([(PathUtils.getEnvelope(partshape=base.Shape, subshape=None, depthparams=self.depthparams), False) for base in self.model if hasattr(base, 'Shape')]) shapes.extend(self._processEachModel(obj)) - self.removalshapes = shapes # pylint: disable=attribute-defined-outside-init + self.removalshapes = shapes # pylint: disable=attribute-defined-outside-init PathLog.debug("%d shapes" % len(shapes)) # Delete the temporary objects if self.isDebug: if FreeCAD.GuiUp: import FreeCADGui + FreeCADGui.ActiveDocument.getObject(tmpGrpNm).Visibility = False self.tmpGrp.purgeTouched() @@ -427,8 +500,10 @@ class ObjectProfile(PathAreaOp.ObjectOp): def _processEachModel(self, obj): shapeTups = list() for base in self.model: - if hasattr(base, 'Shape'): - env = PathUtils.getEnvelope(partshape=base.Shape, subshape=None, depthparams=self.depthparams) + if hasattr(base, "Shape"): + env = PathUtils.getEnvelope( + partshape=base.Shape, subshape=None, depthparams=self.depthparams + ) if env: shapeTups.append((env, False)) return shapeTups @@ -467,20 +542,27 @@ class ObjectProfile(PathAreaOp.ObjectOp): # f = Part.makeFace(wire, 'Part::FaceMakerSimple') # if planar error, Comment out previous line, uncomment the next two - (origWire, flatWire) = self._flattenWire(obj, wire, obj.FinalDepth.Value) + (origWire, flatWire) = self._flattenWire( + obj, wire, obj.FinalDepth.Value + ) f = flatWire.Wires[0] if f: - shapeEnv = PathUtils.getEnvelope(Part.Face(f), depthparams=self.depthparams) + shapeEnv = PathUtils.getEnvelope( + Part.Face(f), depthparams=self.depthparams + ) if shapeEnv: - tup = shapeEnv, False, 'pathProfile' + tup = shapeEnv, False, "pathProfile" shapes.append(tup) else: PathLog.error(self.inaccessibleMsg) else: # Attempt open-edges profile if self.JOB.GeometryTolerance.Value == 0.0: - msg = self.JOB.Label + '.GeometryTolerance = 0.0. ' - msg += translate('PathProfile', 'Please set to an acceptable value greater than zero.') + msg = self.JOB.Label + ".GeometryTolerance = 0.0. " + msg += translate( + "PathProfile", + "Please set to an acceptable value greater than zero.", + ) PathLog.error(msg) else: flattened = self._flattenWire(obj, wire, obj.FinalDepth.Value) @@ -491,13 +573,17 @@ class ObjectProfile(PathAreaOp.ObjectOp): passOffsets = [self.ofstRadius] (origWire, flatWire) = flattened - self._addDebugObject('FlatWire', flatWire) + self._addDebugObject("FlatWire", flatWire) for po in passOffsets: self.ofstRadius = po - cutShp = self._getCutAreaCrossSection(obj, base, origWire, flatWire) + cutShp = self._getCutAreaCrossSection( + obj, base, origWire, flatWire + ) if cutShp: - cutWireObjs = self._extractPathWire(obj, base, flatWire, cutShp) + cutWireObjs = self._extractPathWire( + obj, base, flatWire, cutShp + ) if cutWireObjs: for cW in cutWireObjs: @@ -506,11 +592,14 @@ class ObjectProfile(PathAreaOp.ObjectOp): PathLog.error(self.inaccessibleMsg) if openEdges: - tup = openEdges, False, 'OpenEdge' + tup = openEdges, False, "OpenEdge" shapes.append(tup) else: if zDiff < self.JOB.GeometryTolerance.Value: - msg = translate('PathProfile', 'Check edge selection and Final Depth requirements for profiling open edge(s).') + msg = translate( + "PathProfile", + "Check edge selection and Final Depth requirements for profiling open edge(s).", + ) PathLog.error(msg) else: PathLog.error(self.inaccessibleMsg) @@ -522,12 +611,12 @@ class ObjectProfile(PathAreaOp.ObjectOp): return shapes def _flattenWire(self, obj, wire, trgtDep): - '''_flattenWire(obj, wire)... Return a flattened version of the wire''' - PathLog.debug('_flattenWire()') + """_flattenWire(obj, wire)... Return a flattened version of the wire""" + PathLog.debug("_flattenWire()") wBB = wire.BoundBox if wBB.ZLength > 0.0: - PathLog.debug('Wire is not horizontally co-planar. Flattening it.') + PathLog.debug("Wire is not horizontally co-planar. Flattening it.") # Extrude non-horizontal wire extFwdLen = (wBB.ZLength + 2.0) * 2.0 @@ -548,17 +637,19 @@ class ObjectProfile(PathAreaOp.ObjectOp): # Open-edges methods def _getCutAreaCrossSection(self, obj, base, origWire, flatWire): - PathLog.debug('_getCutAreaCrossSection()') + PathLog.debug("_getCutAreaCrossSection()") # FCAD = FreeCAD.ActiveDocument tolerance = self.JOB.GeometryTolerance.Value - toolDiam = 2 * self.radius # self.radius defined in PathAreaOp or PathProfileBase modules + toolDiam = ( + 2 * self.radius + ) # self.radius defined in PathAreaOp or PathProfileBase modules minBfr = toolDiam * 1.25 bbBfr = (self.ofstRadius * 2) * 1.25 if bbBfr < minBfr: bbBfr = minBfr # fwBB = flatWire.BoundBox wBB = origWire.BoundBox - minArea = (self.ofstRadius - tolerance)**2 * math.pi + minArea = (self.ofstRadius - tolerance) ** 2 * math.pi useWire = origWire.Wires[0] numOrigEdges = len(useWire.Edges) @@ -566,9 +657,10 @@ class ObjectProfile(PathAreaOp.ObjectOp): fdv = obj.FinalDepth.Value extLenFwd = sdv - fdv if extLenFwd <= 0.0: - msg = translate('PathProfile', - 'For open edges, verify Final Depth for this operation.') - FreeCAD.Console.PrintError(msg + '\n') + msg = translate( + "PathProfile", "For open edges, verify Final Depth for this operation." + ) + FreeCAD.Console.PrintError(msg + "\n") # return False extLenFwd = 0.1 WIRE = flatWire.Wires[0] @@ -592,16 +684,22 @@ class ObjectProfile(PathAreaOp.ObjectOp): # Obtain beginning point perpendicular points if blen > 0.1: - bcp = begE.valueAt(begE.getParameterByLength(0.1)) # point returned 0.1 mm along edge + bcp = begE.valueAt( + begE.getParameterByLength(0.1) + ) # point returned 0.1 mm along edge else: bcp = FreeCAD.Vector(begE.Vertexes[1].X, begE.Vertexes[1].Y, fdv) if elen > 0.1: - ecp = endE.valueAt(endE.getParameterByLength(elen - 0.1)) # point returned 0.1 mm along edge + ecp = endE.valueAt( + endE.getParameterByLength(elen - 0.1) + ) # point returned 0.1 mm along edge else: ecp = FreeCAD.Vector(endE.Vertexes[1].X, endE.Vertexes[1].Y, fdv) # Create intersection tags for determining which side of wire to cut - (begInt, begExt, iTAG, eTAG) = self._makeIntersectionTags(useWire, numOrigEdges, fdv) + (begInt, begExt, iTAG, eTAG) = self._makeIntersectionTags( + useWire, numOrigEdges, fdv + ) if not begInt or not begExt: return False self.iTAG = iTAG @@ -613,7 +711,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): # Cut model(selected edges) from extended edges boundbox cutArea = extBndboxEXT.cut(base.Shape) - self._addDebugObject('CutArea', cutArea) + self._addDebugObject("CutArea", cutArea) # Get top and bottom faces of cut area (CA), and combine faces when necessary topFc = list() @@ -622,15 +720,23 @@ class ObjectProfile(PathAreaOp.ObjectOp): bbZMin = cutArea.BoundBox.ZMin for f in range(0, len(cutArea.Faces)): FcBB = cutArea.Faces[f].BoundBox - if abs(FcBB.ZMax - bbZMax) < tolerance and abs(FcBB.ZMin - bbZMax) < tolerance: + if ( + abs(FcBB.ZMax - bbZMax) < tolerance + and abs(FcBB.ZMin - bbZMax) < tolerance + ): topFc.append(f) - if abs(FcBB.ZMax - bbZMin) < tolerance and abs(FcBB.ZMin - bbZMin) < tolerance: + if ( + abs(FcBB.ZMax - bbZMin) < tolerance + and abs(FcBB.ZMin - bbZMin) < tolerance + ): botFc.append(f) if len(topFc) == 0: - PathLog.error('Failed to identify top faces of cut area.') + PathLog.error("Failed to identify top faces of cut area.") return False topComp = Part.makeCompound([cutArea.Faces[f] for f in topFc]) - topComp.translate(FreeCAD.Vector(0, 0, fdv - topComp.BoundBox.ZMin)) # Translate face to final depth + topComp.translate( + FreeCAD.Vector(0, 0, fdv - topComp.BoundBox.ZMin) + ) # Translate face to final depth if len(botFc) > 1: # PathLog.debug('len(botFc) > 1') bndboxFace = Part.Face(extBndbox.Wires[0]) @@ -640,38 +746,42 @@ class ObjectProfile(PathAreaOp.ObjectOp): tmpFace = Q botComp = bndboxFace.cut(tmpFace) else: - botComp = Part.makeCompound([cutArea.Faces[f] for f in botFc]) # Part.makeCompound([CA.Shape.Faces[f] for f in botFc]) - botComp.translate(FreeCAD.Vector(0, 0, fdv - botComp.BoundBox.ZMin)) # Translate face to final depth + botComp = Part.makeCompound( + [cutArea.Faces[f] for f in botFc] + ) # Part.makeCompound([CA.Shape.Faces[f] for f in botFc]) + botComp.translate( + FreeCAD.Vector(0, 0, fdv - botComp.BoundBox.ZMin) + ) # Translate face to final depth # Make common of the two comFC = topComp.common(botComp) # Determine with which set of intersection tags the model intersects - (cmnIntArea, cmnExtArea) = self._checkTagIntersection(iTAG, eTAG, 'QRY', comFC) + (cmnIntArea, cmnExtArea) = self._checkTagIntersection(iTAG, eTAG, "QRY", comFC) if cmnExtArea > cmnIntArea: - PathLog.debug('Cutting on Ext side.') - self.cutSide = 'E' + PathLog.debug("Cutting on Ext side.") + self.cutSide = "E" self.cutSideTags = eTAG tagCOM = begExt.CenterOfMass else: - PathLog.debug('Cutting on Int side.') - self.cutSide = 'I' + PathLog.debug("Cutting on Int side.") + self.cutSide = "I" self.cutSideTags = iTAG tagCOM = begInt.CenterOfMass # Make two beginning style(oriented) 'L' shape stops - begStop = self._makeStop('BEG', bcp, pb, 'BegStop') - altBegStop = self._makeStop('END', bcp, pb, 'BegStop') + begStop = self._makeStop("BEG", bcp, pb, "BegStop") + altBegStop = self._makeStop("END", bcp, pb, "BegStop") # Identify to which style 'L' stop the beginning intersection tag is closest, # and create partner end 'L' stop geometry, and save for application later lenBS_extETag = begStop.CenterOfMass.sub(tagCOM).Length lenABS_extETag = altBegStop.CenterOfMass.sub(tagCOM).Length if lenBS_extETag < lenABS_extETag: - endStop = self._makeStop('END', ecp, pe, 'EndStop') + endStop = self._makeStop("END", ecp, pe, "EndStop") pathStops = Part.makeCompound([begStop, endStop]) else: - altEndStop = self._makeStop('BEG', ecp, pe, 'EndStop') + altEndStop = self._makeStop("BEG", ecp, pe, "EndStop") pathStops = Part.makeCompound([altBegStop, altEndStop]) pathStops.translate(FreeCAD.Vector(0, 0, fdv - pathStops.BoundBox.ZMin)) @@ -698,10 +808,12 @@ class ObjectProfile(PathAreaOp.ObjectOp): # Efor if wi is None: - PathLog.error('The cut area cross-section wire does not coincide with selected edge. Wires[] index is None.') + PathLog.error( + "The cut area cross-section wire does not coincide with selected edge. Wires[] index is None." + ) return False else: - PathLog.debug('Cross-section Wires[] index is {}.'.format(wi)) + PathLog.debug("Cross-section Wires[] index is {}.".format(wi)) nWire = Part.Wire(Part.__sortEdges__(workShp.Wires[wi].Edges)) fcShp = Part.Face(nWire) @@ -710,22 +822,22 @@ class ObjectProfile(PathAreaOp.ObjectOp): # verify that wire chosen is not inside the physical model if wi > 0: # and isInterior is False: - PathLog.debug('Multiple wires in cut area. First choice is not 0. Testing.') + PathLog.debug("Multiple wires in cut area. First choice is not 0. Testing.") testArea = fcShp.cut(base.Shape) isReady = self._checkTagIntersection(iTAG, eTAG, self.cutSide, testArea) - PathLog.debug('isReady {}.'.format(isReady)) + PathLog.debug("isReady {}.".format(isReady)) if isReady is False: - PathLog.debug('Using wire index {}.'.format(wi - 1)) + PathLog.debug("Using wire index {}.".format(wi - 1)) pWire = Part.Wire(Part.__sortEdges__(workShp.Wires[wi - 1].Edges)) pfcShp = Part.Face(pWire) pfcShp.translate(FreeCAD.Vector(0, 0, fdv - workShp.BoundBox.ZMin)) workShp = pfcShp.cut(fcShp) if testArea.Area < minArea: - PathLog.debug('offset area is less than minArea of {}.'.format(minArea)) - PathLog.debug('Using wire index {}.'.format(wi - 1)) + PathLog.debug("offset area is less than minArea of {}.".format(minArea)) + PathLog.debug("Using wire index {}.".format(wi - 1)) pWire = Part.Wire(Part.__sortEdges__(workShp.Wires[wi - 1].Edges)) pfcShp = Part.Face(pWire) pfcShp.translate(FreeCAD.Vector(0, 0, fdv - workShp.BoundBox.ZMin)) @@ -734,12 +846,12 @@ class ObjectProfile(PathAreaOp.ObjectOp): # Add path stops at ends of wire cutShp = workShp.cut(pathStops) - self._addDebugObject('CutShape', cutShp) + self._addDebugObject("CutShape", cutShp) return cutShp def _checkTagIntersection(self, iTAG, eTAG, cutSide, tstObj): - PathLog.debug('_checkTagIntersection()') + PathLog.debug("_checkTagIntersection()") # Identify intersection of Common area and Interior Tags intCmn = tstObj.common(iTAG) @@ -749,26 +861,28 @@ class ObjectProfile(PathAreaOp.ObjectOp): # Calculate common intersection (solid model side, or the non-cut side) area with tags, to determine physical cut side cmnIntArea = intCmn.Area cmnExtArea = extCmn.Area - if cutSide == 'QRY': + if cutSide == "QRY": return (cmnIntArea, cmnExtArea) if cmnExtArea > cmnIntArea: - PathLog.debug('Cutting on Ext side.') - if cutSide == 'E': + PathLog.debug("Cutting on Ext side.") + if cutSide == "E": return True else: - PathLog.debug('Cutting on Int side.') - if cutSide == 'I': + PathLog.debug("Cutting on Int side.") + if cutSide == "I": return True return False def _extractPathWire(self, obj, base, flatWire, cutShp): - PathLog.debug('_extractPathWire()') + PathLog.debug("_extractPathWire()") subLoops = list() rtnWIRES = list() osWrIdxs = list() - subDistFactor = 1.0 # Raise to include sub wires at greater distance from original + subDistFactor = ( + 1.0 # Raise to include sub wires at greater distance from original + ) fdv = obj.FinalDepth.Value wire = flatWire lstVrtIdx = len(wire.Vertexes) - 1 @@ -787,20 +901,20 @@ class ObjectProfile(PathAreaOp.ObjectOp): if osArea: # Make LGTM parser happy pass else: - PathLog.error('No area to offset shape returned.') + PathLog.error("No area to offset shape returned.") return list() except Exception as ee: - PathLog.error('No area to offset shape returned.\n{}'.format(ee)) + PathLog.error("No area to offset shape returned.\n{}".format(ee)) return list() - self._addDebugObject('OffsetShape', ofstShp) + self._addDebugObject("OffsetShape", ofstShp) numOSWires = len(ofstShp.Wires) for w in range(0, numOSWires): osWrIdxs.append(w) # Identify two vertexes for dividing offset loop - NEAR0 = self._findNearestVertex(ofstShp, cent0) + NEAR0 = self._findNearestVertex(ofstShp, cent0) min0i = 0 min0 = NEAR0[0][4] for n in range(0, len(NEAR0)): @@ -810,9 +924,9 @@ class ObjectProfile(PathAreaOp.ObjectOp): min0i = n (w0, vi0, pnt0, _, _) = NEAR0[0] # min0i near0Shp = Part.makeLine(cent0, pnt0) - self._addDebugObject('Near0', near0Shp) + self._addDebugObject("Near0", near0Shp) - NEAR1 = self._findNearestVertex(ofstShp, cent1) + NEAR1 = self._findNearestVertex(ofstShp, cent1) min1i = 0 min1 = NEAR1[0][4] for n in range(0, len(NEAR1)): @@ -822,18 +936,22 @@ class ObjectProfile(PathAreaOp.ObjectOp): min1i = n (w1, vi1, pnt1, _, _) = NEAR1[0] # min1i near1Shp = Part.makeLine(cent1, pnt1) - self._addDebugObject('Near1', near1Shp) + self._addDebugObject("Near1", near1Shp) if w0 != w1: - PathLog.warning('Offset wire endpoint indexes are not equal - w0, w1: {}, {}'.format(w0, w1)) + PathLog.warning( + "Offset wire endpoint indexes are not equal - w0, w1: {}, {}".format( + w0, w1 + ) + ) if self.isDebug and False: - PathLog.debug('min0i is {}.'.format(min0i)) - PathLog.debug('min1i is {}.'.format(min1i)) - PathLog.debug('NEAR0[{}] is {}.'.format(w0, NEAR0[w0])) - PathLog.debug('NEAR1[{}] is {}.'.format(w1, NEAR1[w1])) - PathLog.debug('NEAR0 is {}.'.format(NEAR0)) - PathLog.debug('NEAR1 is {}.'.format(NEAR1)) + PathLog.debug("min0i is {}.".format(min0i)) + PathLog.debug("min1i is {}.".format(min1i)) + PathLog.debug("NEAR0[{}] is {}.".format(w0, NEAR0[w0])) + PathLog.debug("NEAR1[{}] is {}.".format(w1, NEAR1[w1])) + PathLog.debug("NEAR0 is {}.".format(NEAR0)) + PathLog.debug("NEAR1 is {}.".format(NEAR1)) mainWire = ofstShp.Wires[w0] @@ -865,9 +983,11 @@ class ObjectProfile(PathAreaOp.ObjectOp): # Break offset loop into two wires - one of which is the desired profile path wire. try: - (edgeIdxs0, edgeIdxs1) = self._separateWireAtVertexes(mainWire, mainWire.Vertexes[vi0], mainWire.Vertexes[vi1]) + (edgeIdxs0, edgeIdxs1) = self._separateWireAtVertexes( + mainWire, mainWire.Vertexes[vi0], mainWire.Vertexes[vi1] + ) except Exception as ee: - PathLog.error('Failed to identify offset edge.\n{}'.format(ee)) + PathLog.error("Failed to identify offset edge.\n{}".format(ee)) return False edgs0 = list() edgs1 = list() @@ -890,9 +1010,9 @@ class ObjectProfile(PathAreaOp.ObjectOp): return rtnWIRES def _getOffsetArea(self, obj, fcShape, isHole): - '''Get an offset area for a shape. Wrapper around - PathUtils.getOffsetArea.''' - PathLog.debug('_getOffsetArea()') + """Get an offset area for a shape. Wrapper around + PathUtils.getOffsetArea.""" + PathLog.debug("_getOffsetArea()") JOB = PathUtils.findParentJob(obj) tolerance = JOB.GeometryTolerance.Value @@ -901,13 +1021,12 @@ class ObjectProfile(PathAreaOp.ObjectOp): if isHole is False: offset = 0 - offset - return PathUtils.getOffsetArea(fcShape, - offset, - plane=fcShape, - tolerance=tolerance) + return PathUtils.getOffsetArea( + fcShape, offset, plane=fcShape, tolerance=tolerance + ) def _findNearestVertex(self, shape, point): - PathLog.debug('_findNearestVertex()') + PathLog.debug("_findNearestVertex()") PT = FreeCAD.Vector(point.x, point.y, 0.0) def sortDist(tup): @@ -936,7 +1055,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): return PNTS def _separateWireAtVertexes(self, wire, VV1, VV2): - PathLog.debug('_separateWireAtVertexes()') + PathLog.debug("_separateWireAtVertexes()") tolerance = self.JOB.GeometryTolerance.Value grps = [[], []] wireIdxs = [[], []] @@ -1060,20 +1179,20 @@ class ObjectProfile(PathAreaOp.ObjectOp): # Remove `and False` when debugging open edges, as needed if self.isDebug and False: - PathLog.debug('grps[0]: {}'.format(grps[0])) - PathLog.debug('grps[1]: {}'.format(grps[1])) - PathLog.debug('wireIdxs[0]: {}'.format(wireIdxs[0])) - PathLog.debug('wireIdxs[1]: {}'.format(wireIdxs[1])) - PathLog.debug('PRE: {}'.format(PRE)) - PathLog.debug('IDXS: {}'.format(IDXS)) + PathLog.debug("grps[0]: {}".format(grps[0])) + PathLog.debug("grps[1]: {}".format(grps[1])) + PathLog.debug("wireIdxs[0]: {}".format(wireIdxs[0])) + PathLog.debug("wireIdxs[1]: {}".format(wireIdxs[1])) + PathLog.debug("PRE: {}".format(PRE)) + PathLog.debug("IDXS: {}".format(IDXS)) return (wireIdxs[0], wireIdxs[1]) def _makeCrossSection(self, shape, sliceZ, zHghtTrgt=False): - '''_makeCrossSection(shape, sliceZ, zHghtTrgt=None)... + """_makeCrossSection(shape, sliceZ, zHghtTrgt=None)... Creates cross-section objectc from shape. Translates cross-section to zHghtTrgt if available. - Makes face shape from cross-section object. Returns face shape at zHghtTrgt.''' - PathLog.debug('_makeCrossSection()') + Makes face shape from cross-section object. Returns face shape at zHghtTrgt.""" + PathLog.debug("_makeCrossSection()") # Create cross-section of shape and translate wires = list() slcs = shape.slice(FreeCAD.Vector(0, 0, 1), sliceZ) @@ -1088,7 +1207,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): return False def _makeExtendedBoundBox(self, 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) @@ -1102,11 +1221,11 @@ class ObjectProfile(PathAreaOp.ObjectOp): return Part.Face(Part.Wire([L1, L2, L3, L4])) def _makeIntersectionTags(self, useWire, numOrigEdges, fdv): - PathLog.debug('_makeIntersectionTags()') + PathLog.debug("_makeIntersectionTags()") # Create circular probe tags around perimiter of wire extTags = list() intTags = list() - tagRad = (self.radius / 2) + tagRad = self.radius / 2 tagCnt = 0 begInt = False begExt = False @@ -1114,7 +1233,9 @@ class ObjectProfile(PathAreaOp.ObjectOp): E = useWire.Edges[e] LE = E.Length if LE > (self.radius * 2): - nt = math.ceil(LE / (tagRad * math.pi)) # (tagRad * 2 * math.pi) is circumference + nt = math.ceil( + LE / (tagRad * math.pi) + ) # (tagRad * 2 * math.pi) is circumference else: nt = 4 # desired + 1 mid = LE / nt @@ -1128,7 +1249,9 @@ class ObjectProfile(PathAreaOp.ObjectOp): aspc = LE * 0.75 cp1 = E.valueAt(E.getParameterByLength(0)) cp2 = E.valueAt(E.getParameterByLength(aspc)) - (intTObj, extTObj) = self._makeOffsetCircleTag(cp1, cp2, tagRad, fdv, 'BeginEdge[{}]_'.format(e)) + (intTObj, extTObj) = self._makeOffsetCircleTag( + cp1, cp2, tagRad, fdv, "BeginEdge[{}]_".format(e) + ) if intTObj and extTObj: begInt = intTObj begExt = extTObj @@ -1142,7 +1265,9 @@ class ObjectProfile(PathAreaOp.ObjectOp): posTestLen = d + (LE * 0.25) cp1 = E.valueAt(E.getParameterByLength(negTestLen)) cp2 = E.valueAt(E.getParameterByLength(posTestLen)) - (intTObj, extTObj) = self._makeOffsetCircleTag(cp1, cp2, tagRad, fdv, 'Edge[{}]_'.format(e)) + (intTObj, extTObj) = self._makeOffsetCircleTag( + cp1, cp2, tagRad, fdv, "Edge[{}]_".format(e) + ) if intTObj and extTObj: tagCnt += nt intTags.append(intTObj) @@ -1164,8 +1289,12 @@ class ObjectProfile(PathAreaOp.ObjectOp): # Probably a vertical line segment return (False, False) - cutFactor = (cutterRad / 2.1) / lenToMid # = 2 is tangent to wire; > 2 allows tag to overlap wire; < 2 pulls tag away from wire - perpE = FreeCAD.Vector(-1 * toMid.y, toMid.x, 0.0).multiply(-1 * cutFactor) # exterior tag + cutFactor = ( + cutterRad / 2.1 + ) / lenToMid # = 2 is tangent to wire; > 2 allows tag to overlap wire; < 2 pulls tag away from wire + perpE = FreeCAD.Vector(-1 * toMid.y, toMid.x, 0.0).multiply( + -1 * cutFactor + ) # exterior tag extPnt = pb.add(toMid.add(perpE)) # make exterior tag @@ -1174,7 +1303,9 @@ class ObjectProfile(PathAreaOp.ObjectOp): extTag = Part.Face(ecw) # make interior tag - perpI = FreeCAD.Vector(-1 * toMid.y, toMid.x, 0.0).multiply(cutFactor) # interior tag + perpI = FreeCAD.Vector(-1 * toMid.y, toMid.x, 0.0).multiply( + cutFactor + ) # interior tag intPnt = pb.add(toMid.add(perpI)) iCntr = intPnt.add(FreeCAD.Vector(0, 0, depth)) icw = Part.Wire(Part.makeCircle((cutterRad / 2), iCntr).Edges[0]) @@ -1204,13 +1335,13 @@ class ObjectProfile(PathAreaOp.ObjectOp): # -----3-------| # positive dist in _makePerp2DVector() is CCW rotation p1 = E - if sType == 'BEG': + if sType == "BEG": p2 = self._makePerp2DVector(C, E, -1 * shrt) # E1 p3 = self._makePerp2DVector(p1, p2, ofstRad + lng + extra) # E2 p4 = self._makePerp2DVector(p2, p3, shrt + ofstRad + extra) # E3 p5 = self._makePerp2DVector(p3, p4, lng + extra) # E4 p6 = self._makePerp2DVector(p4, p5, ofstRad + extra) # E5 - elif sType == 'END': + elif sType == "END": p2 = self._makePerp2DVector(C, E, shrt) # E1 p3 = self._makePerp2DVector(p1, p2, -1 * (ofstRad + lng + extra)) # E2 p4 = self._makePerp2DVector(p2, p3, -1 * (shrt + ofstRad + extra)) # E3 @@ -1232,16 +1363,28 @@ class ObjectProfile(PathAreaOp.ObjectOp): # |-----4------| # positive dist in _makePerp2DVector() is CCW rotation p1 = E - if sType == 'BEG': - p2 = self._makePerp2DVector(C, E, -1 * (shrt + abs(self.offsetExtra))) # left, shrt + if sType == "BEG": + p2 = self._makePerp2DVector( + C, E, -1 * (shrt + abs(self.offsetExtra)) + ) # left, shrt p3 = self._makePerp2DVector(p1, p2, shrt + abs(self.offsetExtra)) - p4 = self._makePerp2DVector(p2, p3, (med + abs(self.offsetExtra))) # FIRST POINT - p5 = self._makePerp2DVector(p3, p4, shrt + abs(self.offsetExtra)) # E1 SECOND - elif sType == 'END': - p2 = self._makePerp2DVector(C, E, (shrt + abs(self.offsetExtra))) # left, shrt + p4 = self._makePerp2DVector( + p2, p3, (med + abs(self.offsetExtra)) + ) # FIRST POINT + p5 = self._makePerp2DVector( + p3, p4, shrt + abs(self.offsetExtra) + ) # E1 SECOND + elif sType == "END": + p2 = self._makePerp2DVector( + C, E, (shrt + abs(self.offsetExtra)) + ) # left, shrt p3 = self._makePerp2DVector(p1, p2, -1 * (shrt + abs(self.offsetExtra))) - p4 = self._makePerp2DVector(p2, p3, -1 * (med + abs(self.offsetExtra))) # FIRST POINT - p5 = self._makePerp2DVector(p3, p4, -1 * (shrt + abs(self.offsetExtra))) # E1 SECOND + p4 = self._makePerp2DVector( + p2, p3, -1 * (med + abs(self.offsetExtra)) + ) # FIRST POINT + p5 = self._makePerp2DVector( + p3, p4, -1 * (shrt + abs(self.offsetExtra)) + ) # E1 SECOND p6 = p1 # E4 L1 = Part.makeLine(p1, p2) L2 = Part.makeLine(p2, p3) @@ -1289,10 +1432,10 @@ class ObjectProfile(PathAreaOp.ObjectOp): # Method to add temporary debug object def _addDebugObject(self, objName, objShape): if self.isDebug: - O = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmp_' + objName) - O.Shape = objShape - O.purgeTouched() - self.tmpGrp.addObject(O) + newDocObj = FreeCAD.ActiveDocument.addObject("Part::Feature", "tmp_" + objName) + newDocObj.Shape = objShape + newDocObj.purgeTouched() + self.tmpGrp.addObject(newDocObj) def SetupProperties(): @@ -1301,9 +1444,9 @@ def SetupProperties(): return setup -def Create(name, obj=None, parentJob=None): - '''Create(name) ... Creates and returns a Profile based on faces operation.''' +def Create(name, obj=None): + """Create(name) ... Creates and returns a Profile based on faces operation.""" if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) - obj.Proxy = ObjectProfile(obj, name, parentJob) + obj.Proxy = ObjectProfile(obj, name) return obj