diff --git a/src/Mod/Path/Gui/Resources/panels/PageOpSurfaceEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageOpSurfaceEdit.ui index ef1cab7307..0fd348c8eb 100644 --- a/src/Mod/Path/Gui/Resources/panels/PageOpSurfaceEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/PageOpSurfaceEdit.ui @@ -6,7 +6,7 @@ 0 0 - 420 + 498 541 @@ -82,7 +82,7 @@ - <html><head/><body><p>Additional offset to the selected bounding box along the X axis."</p></body></html> + <html><head/><body><p>Additional offset to the selected bounding box along the X axis."</p></body></html> mm @@ -92,7 +92,7 @@ - <html><head/><body><p>Additional offset to the selected bounding box along the Y axis."</p></body></html> + <html><head/><body><p>Additional offset to the selected bounding box along the Y axis."</p></body></html> mm @@ -133,16 +133,6 @@ <html><head/><body><p>Select the overall boundary for the operation.</p></body></html> - - - Stock - - - - - BaseBoundBox - - @@ -160,26 +150,6 @@ <html><head/><body><p>Profile the edges of the selection.</p></body></html> - - - None - - - - - Only - - - - - First - - - - - Last - - @@ -194,36 +164,6 @@ <html><head/><body><p>Set the geometric clearing pattern to use for the operation.</p></body></html> - - - Circular - - - - - CircularZigZag - - - - - Line - - - - - Offset - - - - - Spiral - - - - - ZigZag - - @@ -231,16 +171,6 @@ <html><head/><body><p>Planar: Flat, 3D surface scan. Rotational: 4th-axis rotational scan.</p></body></html> - - - Planar - - - - - Rotational - - @@ -262,16 +192,6 @@ <html><head/><body><p>Complete the operation in a single pass at depth, or mulitiple passes to final depth.</p></body></html> - - - Single-pass - - - - - Multi-pass - - @@ -293,16 +213,6 @@ <html><head/><body><p>Dropcutter lines are created parallel to this axis.</p></body></html> - - - X - - - - - Y - - diff --git a/src/Mod/Path/Gui/Resources/panels/PageOpWaterlineEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageOpWaterlineEdit.ui index 38462d7ed5..72de5243b4 100644 --- a/src/Mod/Path/Gui/Resources/panels/PageOpWaterlineEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/PageOpWaterlineEdit.ui @@ -6,7 +6,7 @@ 0 0 - 350 + 389 400 @@ -53,87 +53,80 @@ - + + + + Algorithm + + + + + + + <html><head/><body><p>Select the algorithm to use: OCL Dropcutter*, or Experimental (Not OCL based).</p></body></html> + + + + + + + + 0 + 0 + + + + BoundBox + + + + - 8 + 12 <html><head/><body><p>Select the overall boundary for the operation.</p></body></html> - - - Stock - - - - - BaseBoundBox - - - - - - - 0 - 0 - - - - <html><head/><body><p>Positive values push the cutter toward, or beyond, the boundary. Negative values retract the cutter away from the boundary.</p></body></html> + + + + Layer Mode - + - 8 + 12 <html><head/><body><p>Complete the operation in a single pass at depth, or mulitiple passes to final depth.</p></body></html> - - - Single-pass - - - - - Multi-pass - - - - - - <html><head/><body><p>Select the algorithm to use: OCL Dropcutter*, or Experimental (Not OCL based).</p></body></html> - - - - OCL Dropcutter - - - - - Experimental - - - - - - - - <html><head/><body><p>Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-Code output.</p></body></html> - + + - Optimize Linear Paths + Cut Pattern + + + + + + + + 12 + + + + <html><head/><body><p>Set the geometric clearing pattern to use for the operation.</p></body></html> @@ -150,54 +143,24 @@ - - - - - 8 - - + + - <html><head/><body><p>Set the geometric clearing pattern to use for the operation.</p></body></html> + <html><head/><body><p>Set the Z-axis depth offset from the target surface.</p></body></html> + + + mm - - - None - - - - - Circular - - - - - CircularZigZag - - - - - Line - - - - - Offset - - - - - Spiral - - - - - ZigZag - - - + + + + Step over + + + + @@ -216,35 +179,15 @@ - - + + - Layer Mode + Sample interval - - - - - 0 - 0 - - - - BoundBox - - - - - - - Step over - - - - - + + <html><head/><body><p>Set the sampling resolution. Smaller values quickly increase processing time.</p></body></html> @@ -253,24 +196,13 @@ - - - - Cut Pattern + + + + <html><head/><body><p>Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-Code output.</p></body></html> - - - - - Sample interval - - - - - - - Algorithm + Optimize Linear Paths @@ -306,7 +238,6 @@ boundBoxSelect layerMode cutPattern - boundaryAdjustment stepOver sampleInterval optimizeEnabled diff --git a/src/Mod/Path/PathScripts/PathAreaOp.py b/src/Mod/Path/PathScripts/PathAreaOp.py index 3c9284cc1e..876699cc18 100644 --- a/src/Mod/Path/PathScripts/PathAreaOp.py +++ b/src/Mod/Path/PathScripts/PathAreaOp.py @@ -20,22 +20,20 @@ # * * # *************************************************************************** +from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Path import PathScripts.PathLog as PathLog import PathScripts.PathOp as PathOp import PathScripts.PathUtils as PathUtils -import PathScripts.PathGeom as PathGeom -import math -from PySide import QtCore + # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader -Draft = LazyLoader('Draft', globals(), 'Draft') -Part = LazyLoader('Part', globals(), 'Part') -if FreeCAD.GuiUp: - import FreeCADGui +Draft = LazyLoader("Draft", globals(), "Draft") +Part = LazyLoader("Part", globals(), "Part") + __title__ = "Base class for PathArea based operations." __author__ = "sliptonic (Brad Collette)" @@ -44,95 +42,112 @@ __doc__ = "Base class and properties for Path.Area based operations." __contributors__ = "russ4262 (Russell Johnson)" -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# PathLog.trackModule(PathLog.thisModule()) +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) - -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) +translate = FreeCAD.Qt.translate class ObjectOp(PathOp.ObjectOp): - '''Base class for all Path.Area based operations. + """Base class for all Path.Area based operations. Provides standard features including debugging properties AreaParams, PathParams and removalshape, all hidden. The main reason for existence is to implement the standard interface to Path.Area so subclasses only have to provide the shapes for the - operations.''' + operations.""" def opFeatures(self, obj): - '''opFeatures(obj) ... returns the base features supported by all Path.Area based operations. + """opFeatures(obj) ... returns the base features supported by all Path.Area based operations. The standard feature list is OR'ed with the return value of areaOpFeatures(). - Do not overwrite, implement areaOpFeatures(obj) instead.''' - return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureStepDown \ - | PathOp.FeatureHeights | PathOp.FeatureStartPoint \ - | self.areaOpFeatures(obj) | PathOp.FeatureCoolant + Do not overwrite, implement areaOpFeatures(obj) instead.""" + return ( + PathOp.FeatureTool + | PathOp.FeatureDepths + | PathOp.FeatureStepDown + | PathOp.FeatureHeights + | PathOp.FeatureStartPoint + | self.areaOpFeatures(obj) + | PathOp.FeatureCoolant + ) def areaOpFeatures(self, obj): - '''areaOpFeatures(obj) ... overwrite to add operation specific features. - Can safely be overwritten by subclasses.''' + """areaOpFeatures(obj) ... overwrite to add operation specific features. + Can safely be overwritten by subclasses.""" # pylint: disable=unused-argument return 0 def initOperation(self, obj): - '''initOperation(obj) ... sets up standard Path.Area properties and calls initAreaOp(). - Do not overwrite, overwrite initAreaOp(obj) instead.''' + """initOperation(obj) ... sets up standard Path.Area properties and calls initAreaOp(). + Do not overwrite, overwrite initAreaOp(obj) instead.""" PathLog.track() # Debugging obj.addProperty("App::PropertyString", "AreaParams", "Path") - obj.setEditorMode('AreaParams', 2) # hide + obj.setEditorMode("AreaParams", 2) # hide obj.addProperty("App::PropertyString", "PathParams", "Path") - obj.setEditorMode('PathParams', 2) # hide + obj.setEditorMode("PathParams", 2) # hide obj.addProperty("Part::PropertyPartShape", "removalshape", "Path") - obj.setEditorMode('removalshape', 2) # hide + obj.setEditorMode("removalshape", 2) # hide - obj.addProperty("App::PropertyBool", "SplitArcs", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Split Arcs into discrete segments")) + obj.addProperty( + "App::PropertyBool", + "SplitArcs", + "Path", + QT_TRANSLATE_NOOP("App::Property", "Split Arcs into discrete segments"), + ) # obj.Proxy = self self.initAreaOp(obj) def initAreaOp(self, obj): - '''initAreaOp(obj) ... overwrite if the receiver class needs initialisation. - Can safely be overwritten by subclasses.''' - pass # pylint: disable=unnecessary-pass + """initAreaOp(obj) ... overwrite if the receiver class needs initialisation. + Can safely be overwritten by subclasses.""" + pass # pylint: disable=unnecessary-pass def areaOpShapeForDepths(self, obj, job): - '''areaOpShapeForDepths(obj) ... returns the shape used to make an initial calculation for the depths being used. - The default implementation returns the job's Base.Shape''' + """areaOpShapeForDepths(obj) ... returns the shape used to make an initial calculation for the depths being used. + The default implementation returns the job's Base.Shape""" if job: if job.Stock: - PathLog.debug("job=%s base=%s shape=%s" % (job, job.Stock, job.Stock.Shape)) + PathLog.debug( + "job=%s base=%s shape=%s" % (job, job.Stock, job.Stock.Shape) + ) return job.Stock.Shape else: - PathLog.warning(translate("PathAreaOp", "job %s has no Base.") % job.Label) + PathLog.warning( + translate("PathAreaOp", "job %s has no Base.") % job.Label + ) else: - PathLog.warning(translate("PathAreaOp", "no job for op %s found.") % obj.Label) + PathLog.warning( + translate("PathAreaOp", "no job for op %s found.") % obj.Label + ) return None def areaOpOnChanged(self, obj, prop): - '''areaOpOnChanged(obj, porp) ... overwrite to process operation specific changes to properties. - Can safely be overwritten by subclasses.''' - pass # pylint: disable=unnecessary-pass + """areaOpOnChanged(obj, porp) ... overwrite to process operation specific changes to properties. + Can safely be overwritten by subclasses.""" + pass # pylint: disable=unnecessary-pass def opOnChanged(self, obj, prop): - '''opOnChanged(obj, prop) ... base implementation of the notification framework - do not overwrite. + """opOnChanged(obj, prop) ... base implementation of the notification framework - do not overwrite. The base implementation takes a stab at determining Heights and Depths if the operations's Base changes. - Do not overwrite, overwrite areaOpOnChanged(obj, prop) instead.''' + Do not overwrite, overwrite areaOpOnChanged(obj, prop) instead.""" # PathLog.track(obj.Label, prop) - if prop in ['AreaParams', 'PathParams', 'removalshape']: + if prop in ["AreaParams", "PathParams", "removalshape"]: obj.setEditorMode(prop, 2) - if prop == 'Base' and len(obj.Base) == 1: + if prop == "Base" and len(obj.Base) == 1: (base, sub) = obj.Base[0] bb = base.Shape.BoundBox # parent boundbox subobj = base.Shape.getElement(sub[0]) fbb = subobj.BoundBox # feature boundbox - if hasattr(obj, 'Side'): + if hasattr(obj, "Side"): if bb.XLength == fbb.XLength and bb.YLength == fbb.YLength: obj.Side = "Outside" else: @@ -141,29 +156,34 @@ class ObjectOp(PathOp.ObjectOp): self.areaOpOnChanged(obj, prop) def opOnDocumentRestored(self, obj): - for prop in ['AreaParams', 'PathParams', 'removalshape']: + for prop in ["AreaParams", "PathParams", "removalshape"]: if hasattr(obj, prop): obj.setEditorMode(prop, 2) - if not hasattr(obj, 'SplitArcs'): - obj.addProperty("App::PropertyBool", "SplitArcs", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Split Arcs into discrete segments")) + if not hasattr(obj, "SplitArcs"): + obj.addProperty( + "App::PropertyBool", + "SplitArcs", + "Path", + QT_TRANSLATE_NOOP("App::Property", "Split Arcs into discrete segments"), + ) self.areaOpOnDocumentRestored(obj) def areaOpOnDocumentRestored(self, obj): - '''areaOpOnDocumentRestored(obj) ... overwrite to fully restore receiver''' - pass # pylint: disable=unnecessary-pass + """areaOpOnDocumentRestored(obj) ... overwrite to fully restore receiver""" + pass # pylint: disable=unnecessary-pass def opSetDefaultValues(self, obj, job): - '''opSetDefaultValues(obj) ... base implementation, do not overwrite. + """opSetDefaultValues(obj) ... base implementation, do not overwrite. The base implementation sets the depths and heights based on the areaOpShapeForDepths() return value. - Do not overwrite, overwrite areaOpSetDefaultValues(obj, job) instead.''' + Do not overwrite, overwrite areaOpSetDefaultValues(obj, job) instead.""" PathLog.debug("opSetDefaultValues(%s, %s)" % (obj.Label, job.Label)) if PathOp.FeatureDepths & self.opFeatures(obj): try: shape = self.areaOpShapeForDepths(obj, job) - except Exception as ee: # pylint: disable=broad-except + except Exception as ee: # pylint: disable=broad-except PathLog.error(ee) shape = None @@ -182,136 +202,158 @@ class ObjectOp(PathOp.ObjectOp): obj.OpStartDepth.Value = startDepth obj.OpFinalDepth.Value = finalDepth - PathLog.debug("Default OpDepths are Start: {}, and Final: {}".format(obj.OpStartDepth.Value, obj.OpFinalDepth.Value)) - PathLog.debug("Default Depths are Start: {}, and Final: {}".format(startDepth, finalDepth)) + PathLog.debug( + "Default OpDepths are Start: {}, and Final: {}".format( + obj.OpStartDepth.Value, obj.OpFinalDepth.Value + ) + ) + PathLog.debug( + "Default Depths are Start: {}, and Final: {}".format( + startDepth, finalDepth + ) + ) self.areaOpSetDefaultValues(obj, job) def areaOpSetDefaultValues(self, obj, job): - '''areaOpSetDefaultValues(obj, job) ... overwrite to set initial values of operation specific properties. - Can safely be overwritten by subclasses.''' - pass # pylint: disable=unnecessary-pass + """areaOpSetDefaultValues(obj, job) ... overwrite to set initial values of operation specific properties. + Can safely be overwritten by subclasses.""" + pass # pylint: disable=unnecessary-pass def _buildPathArea(self, obj, baseobject, isHole, start, getsim): - '''_buildPathArea(obj, baseobject, isHole, start, getsim) ... internal function.''' + """_buildPathArea(obj, baseobject, isHole, start, getsim) ... internal function.""" # pylint: disable=unused-argument PathLog.track() area = Path.Area() area.setPlane(PathUtils.makeWorkplane(baseobject)) area.add(baseobject) - areaParams = self.areaOpAreaParams(obj, isHole) # pylint: disable=assignment-from-no-return + areaParams = self.areaOpAreaParams( + obj, isHole + ) # pylint: disable=assignment-from-no-return heights = [i for i in self.depthparams] - PathLog.debug('depths: {}'.format(heights)) + PathLog.debug("depths: {}".format(heights)) area.setParams(**areaParams) obj.AreaParams = str(area.getParams()) PathLog.debug("Area with params: {}".format(area.getParams())) - sections = area.makeSections(mode=0, project=self.areaOpUseProjection(obj), heights=heights) + sections = area.makeSections( + mode=0, project=self.areaOpUseProjection(obj), heights=heights + ) PathLog.debug("sections = %s" % sections) shapelist = [sec.getShape() for sec in sections] PathLog.debug("shapelist = %s" % shapelist) - pathParams = self.areaOpPathParams(obj, isHole) # pylint: disable=assignment-from-no-return - pathParams['shapes'] = shapelist - pathParams['feedrate'] = self.horizFeed - pathParams['feedrate_v'] = self.vertFeed - pathParams['verbose'] = True - pathParams['resume_height'] = obj.SafeHeight.Value - pathParams['retraction'] = obj.ClearanceHeight.Value - pathParams['return_end'] = True + pathParams = self.areaOpPathParams( + obj, isHole + ) # pylint: disable=assignment-from-no-return + pathParams["shapes"] = shapelist + pathParams["feedrate"] = self.horizFeed + pathParams["feedrate_v"] = self.vertFeed + pathParams["verbose"] = True + pathParams["resume_height"] = obj.SafeHeight.Value + pathParams["retraction"] = obj.ClearanceHeight.Value + pathParams["return_end"] = True # Note that emitting preambles between moves breaks some dressups and prevents path optimization on some controllers - pathParams['preamble'] = False + pathParams["preamble"] = False if not self.areaOpRetractTool(obj): - pathParams['threshold'] = 2.001 * self.radius + pathParams["threshold"] = 2.001 * self.radius if self.endVector is not None: - pathParams['start'] = self.endVector + pathParams["start"] = self.endVector elif PathOp.FeatureStartPoint & self.opFeatures(obj) and obj.UseStartPoint: - pathParams['start'] = obj.StartPoint + pathParams["start"] = obj.StartPoint - obj.PathParams = str({key: value for key, value in pathParams.items() if key != 'shapes'}) + obj.PathParams = str( + {key: value for key, value in pathParams.items() if key != "shapes"} + ) PathLog.debug("Path with params: {}".format(obj.PathParams)) (pp, end_vector) = Path.fromShapes(**pathParams) - PathLog.debug('pp: {}, end vector: {}'.format(pp, end_vector)) - self.endVector = end_vector # pylint: disable=attribute-defined-outside-init + PathLog.debug("pp: {}, end vector: {}".format(pp, end_vector)) + self.endVector = end_vector # pylint: disable=attribute-defined-outside-init simobj = None if getsim: - areaParams['Thicken'] = True - areaParams['ToolRadius'] = self.radius - self.radius * .005 + areaParams["Thicken"] = True + areaParams["ToolRadius"] = self.radius - self.radius * 0.005 area.setParams(**areaParams) - sec = area.makeSections(mode=0, project=False, heights=heights)[-1].getShape() + sec = area.makeSections(mode=0, project=False, heights=heights)[ + -1 + ].getShape() simobj = sec.extrude(FreeCAD.Vector(0, 0, baseobject.BoundBox.ZMax)) return pp, simobj def _buildProfileOpenEdges(self, obj, edgeList, isHole, start, getsim): - '''_buildPathArea(obj, edgeList, isHole, start, getsim) ... internal function.''' + """_buildPathArea(obj, edgeList, isHole, start, getsim) ... internal function.""" # pylint: disable=unused-argument PathLog.track() paths = [] heights = [i for i in self.depthparams] - PathLog.debug('depths: {}'.format(heights)) + PathLog.debug("depths: {}".format(heights)) for i in range(0, len(heights)): for baseShape in edgeList: hWire = Part.Wire(Part.__sortEdges__(baseShape.Edges)) hWire.translate(FreeCAD.Vector(0, 0, heights[i] - hWire.BoundBox.ZMin)) - pathParams = {} # pylint: disable=assignment-from-no-return - pathParams['shapes'] = [hWire] - pathParams['feedrate'] = self.horizFeed - pathParams['feedrate_v'] = self.vertFeed - pathParams['verbose'] = True - pathParams['resume_height'] = obj.SafeHeight.Value - pathParams['retraction'] = obj.ClearanceHeight.Value - pathParams['return_end'] = True + pathParams = {} # pylint: disable=assignment-from-no-return + pathParams["shapes"] = [hWire] + pathParams["feedrate"] = self.horizFeed + pathParams["feedrate_v"] = self.vertFeed + pathParams["verbose"] = True + pathParams["resume_height"] = obj.SafeHeight.Value + pathParams["retraction"] = obj.ClearanceHeight.Value + pathParams["return_end"] = True # Note that emitting preambles between moves breaks some dressups and prevents path optimization on some controllers - pathParams['preamble'] = False + pathParams["preamble"] = False if self.endVector is None: V = hWire.Wires[0].Vertexes lv = len(V) - 1 - pathParams['start'] = FreeCAD.Vector(V[0].X, V[0].Y, V[0].Z) - if obj.Direction == 'CCW': - pathParams['start'] = FreeCAD.Vector(V[lv].X, V[lv].Y, V[lv].Z) + pathParams["start"] = FreeCAD.Vector(V[0].X, V[0].Y, V[0].Z) + if obj.Direction == "CCW": + pathParams["start"] = FreeCAD.Vector(V[lv].X, V[lv].Y, V[lv].Z) else: - pathParams['start'] = self.endVector + pathParams["start"] = self.endVector - obj.PathParams = str({key: value for key, value in pathParams.items() if key != 'shapes'}) + obj.PathParams = str( + {key: value for key, value in pathParams.items() if key != "shapes"} + ) PathLog.debug("Path with params: {}".format(obj.PathParams)) (pp, end_vector) = Path.fromShapes(**pathParams) paths.extend(pp.Commands) - PathLog.debug('pp: {}, end vector: {}'.format(pp, end_vector)) + PathLog.debug("pp: {}, end vector: {}".format(pp, end_vector)) self.endVector = end_vector simobj = None return paths, simobj - def opExecute(self, obj, getsim=False): # pylint: disable=arguments-differ - '''opExecute(obj, getsim=False) ... implementation of Path.Area ops. + def opExecute(self, obj, getsim=False): # pylint: disable=arguments-differ + """opExecute(obj, getsim=False) ... implementation of Path.Area ops. determines the parameters for _buildPathArea(). Do not overwrite, implement areaOpAreaParams(obj, isHole) ... op specific area param dictionary areaOpPathParams(obj, isHole) ... op specific path param dictionary areaOpShapes(obj) ... the shape for path area to process areaOpUseProjection(obj) ... return true if operation can use projection - instead.''' + instead.""" PathLog.track() # Instantiate class variables for operation reference - self.endVector = None # pylint: disable=attribute-defined-outside-init + self.endVector = None # pylint: disable=attribute-defined-outside-init self.leadIn = 2.0 # pylint: disable=attribute-defined-outside-init # Initiate depthparams and calculate operation heights for operation - self.depthparams = self._customDepthParams(obj, obj.StartDepth.Value, obj.FinalDepth.Value) + self.depthparams = self._customDepthParams( + obj, obj.StartDepth.Value, obj.FinalDepth.Value + ) # Set start point if PathOp.FeatureStartPoint & self.opFeatures(obj) and obj.UseStartPoint: @@ -319,7 +361,7 @@ class ObjectOp(PathOp.ObjectOp): else: start = None - aOS = self.areaOpShapes(obj) # pylint: disable=assignment-from-no-return + aOS = self.areaOpShapes(obj) # pylint: disable=assignment-from-no-return # Adjust tuples length received from other PathWB tools/operations shapes = [] @@ -327,7 +369,7 @@ class ObjectOp(PathOp.ObjectOp): if len(shp) == 2: (fc, iH) = shp # fc, iH, sub or description - tup = fc, iH, 'otherOp' + tup = fc, iH, "otherOp" shapes.append(tup) else: shapes.append(shp) @@ -335,38 +377,47 @@ class ObjectOp(PathOp.ObjectOp): if len(shapes) > 1: locations = [] for s in shapes: - if s[2] == 'OpenEdge': + if s[2] == "OpenEdge": shp = Part.makeCompound(s[0]) else: shp = s[0] - locations.append({ - 'x': shp.BoundBox.XMax, - 'y': shp.BoundBox.YMax, - 'shape': s - }) + locations.append( + {"x": shp.BoundBox.XMax, "y": shp.BoundBox.YMax, "shape": s} + ) - locations = PathUtils.sort_locations(locations, ['x', 'y']) + locations = PathUtils.sort_locations(locations, ["x", "y"]) - shapes = [j['shape'] for j in locations] + shapes = [j["shape"] for j in locations] sims = [] for shape, isHole, sub in shapes: profileEdgesIsOpen = False - if sub == 'OpenEdge': + if sub == "OpenEdge": profileEdgesIsOpen = True - if PathOp.FeatureStartPoint & self.opFeatures(obj) and obj.UseStartPoint: + if ( + PathOp.FeatureStartPoint & self.opFeatures(obj) + and obj.UseStartPoint + ): osp = obj.StartPoint - self.commandlist.append(Path.Command('G0', {'X': osp.x, 'Y': osp.y, 'F': self.horizRapid})) + self.commandlist.append( + Path.Command( + "G0", {"X": osp.x, "Y": osp.y, "F": self.horizRapid} + ) + ) try: if profileEdgesIsOpen: - (pp, sim) = self._buildProfileOpenEdges(obj, shape, isHole, start, getsim) + (pp, sim) = self._buildProfileOpenEdges( + obj, shape, isHole, start, getsim + ) else: (pp, sim) = self._buildPathArea(obj, shape, isHole, start, getsim) - except Exception as e: # pylint: disable=broad-except + except Exception as e: # pylint: disable=broad-except FreeCAD.Console.PrintError(e) - FreeCAD.Console.PrintError("Something unexpected happened. Check project and tool config.") + FreeCAD.Console.PrintError( + "Something unexpected happened. Check project and tool config." + ) else: if profileEdgesIsOpen: ppCmds = pp @@ -378,41 +429,49 @@ class ObjectOp(PathOp.ObjectOp): sims.append(sim) # Eif - if self.areaOpRetractTool(obj) and self.endVector is not None and len(self.commandlist) > 1: + if ( + self.areaOpRetractTool(obj) + and self.endVector is not None + and len(self.commandlist) > 1 + ): self.endVector[2] = obj.ClearanceHeight.Value - self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) + self.commandlist.append( + Path.Command( + "G0", {"Z": obj.ClearanceHeight.Value, "F": self.vertRapid} + ) + ) PathLog.debug("obj.Name: " + str(obj.Name) + "\n\n") return sims def areaOpRetractTool(self, obj): - '''areaOpRetractTool(obj) ... return False to keep the tool at current level between shapes. Default is True.''' + """areaOpRetractTool(obj) ... return False to keep the tool at current level between shapes. Default is True.""" # pylint: disable=unused-argument return True def areaOpAreaParams(self, obj, isHole): - '''areaOpAreaParams(obj, isHole) ... return operation specific area parameters in a dictionary. + """areaOpAreaParams(obj, isHole) ... return operation specific area parameters in a dictionary. Note that the resulting parameters are stored in the property AreaParams. - Must be overwritten by subclasses.''' + Must be overwritten by subclasses.""" # pylint: disable=unused-argument - pass # pylint: disable=unnecessary-pass + pass # pylint: disable=unnecessary-pass def areaOpPathParams(self, obj, isHole): - '''areaOpPathParams(obj, isHole) ... return operation specific path parameters in a dictionary. + """areaOpPathParams(obj, isHole) ... return operation specific path parameters in a dictionary. Note that the resulting parameters are stored in the property PathParams. - Must be overwritten by subclasses.''' + Must be overwritten by subclasses.""" # pylint: disable=unused-argument - pass # pylint: disable=unnecessary-pass + pass # pylint: disable=unnecessary-pass def areaOpShapes(self, obj): - '''areaOpShapes(obj) ... return all shapes to be processed by Path.Area for this op. - Must be overwritten by subclasses.''' + """areaOpShapes(obj) ... return all shapes to be processed by Path.Area for this op. + Must be overwritten by subclasses.""" # pylint: disable=unused-argument - pass # pylint: disable=unnecessary-pass + pass # pylint: disable=unnecessary-pass def areaOpUseProjection(self, obj): - '''areaOpUseProcjection(obj) ... return True if the operation can use procjection, defaults to False. - Can safely be overwritten by subclasses.''' + """areaOpUseProcjection(obj) ... return True if the operation can use procjection, defaults to False. + Can safely be overwritten by subclasses.""" # pylint: disable=unused-argument return False @@ -426,10 +485,14 @@ class ObjectOp(PathOp.ObjectOp): step_down=obj.StepDown.Value, z_finish_step=finish_step, final_depth=finDep, - user_depths=None) + user_depths=None, + ) return cdp + + # Eclass + def SetupProperties(): setup = [] return setup diff --git a/src/Mod/Path/PathScripts/PathCircularHoleBase.py b/src/Mod/Path/PathScripts/PathCircularHoleBase.py index 30c1c78d4d..d8b3fa0abe 100644 --- a/src/Mod/Path/PathScripts/PathCircularHoleBase.py +++ b/src/Mod/Path/PathScripts/PathCircularHoleBase.py @@ -20,15 +20,12 @@ # * * # *************************************************************************** +from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import PathScripts.PathLog as PathLog import PathScripts.PathOp as PathOp - -# import PathScripts.PathUtils as PathUtils import PathScripts.drillableLib as drillableLib -from PySide import QtCore - # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader @@ -43,9 +40,7 @@ __url__ = "https://www.freecadweb.org" __doc__ = "Base class an implementation for operations on circular holes." -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) +translate = FreeCAD.Qt.translate if False: @@ -54,6 +49,7 @@ if False: else: PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + class ObjectOp(PathOp.ObjectOp): """Base class for proxy objects of all operations on circular holes.""" @@ -81,7 +77,7 @@ class ObjectOp(PathOp.ObjectOp): "App::PropertyStringList", "Disabled", "Base", - QtCore.QT_TRANSLATE_NOOP("Path", "List of disabled features"), + QT_TRANSLATE_NOOP("App::Property", "List of disabled features"), ) self.initCircularHoleOperation(obj) diff --git a/src/Mod/Path/PathScripts/PathGui.py b/src/Mod/Path/PathScripts/PathGui.py index 3cdb2fbf64..8f4d5a7c2b 100644 --- a/src/Mod/Path/PathScripts/PathGui.py +++ b/src/Mod/Path/PathScripts/PathGui.py @@ -39,6 +39,24 @@ else: PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +def populateCombobox(form, enumTups, comboBoxesPropertyMap): + """fillComboboxes(form, comboBoxesPropertyMap) ... populate comboboxes with translated enumerations + ** comboBoxesPropertyMap will be unnecessary if UI files use strict combobox naming protocol. + Args: + form = UI form + enumTups = list of (translated_text, data_string) tuples + comboBoxesPropertyMap = list of (translated_text, data_string) tuples + """ + PathLog.track(enumTups) + + # Load appropriate enumerations in each combobox + for cb, prop in comboBoxesPropertyMap: + box = getattr(form, cb) # Get the combobox + box.clear() # clear the combobox + for text, data in enumTups[prop]: # load enumerations + box.addItem(text, data) + + def updateInputField(obj, prop, widget, onBeforeChange=None): """updateInputField(obj, prop, widget) ... update obj's property prop with the value of widget. The property's value is only assigned if the new value differs from the current value. @@ -50,6 +68,7 @@ def updateInputField(obj, prop, widget, onBeforeChange=None): """ PathLog.track() value = widget.property("rawValue") + PathLog.track("value: {}".format(value)) attr = PathUtil.getProperty(obj, prop) attrValue = attr.Value if hasattr(attr, "Value") else attr diff --git a/src/Mod/Path/PathScripts/PathLog.py b/src/Mod/Path/PathScripts/PathLog.py index 80128d3c51..43509bad90 100644 --- a/src/Mod/Path/PathScripts/PathLog.py +++ b/src/Mod/Path/PathScripts/PathLog.py @@ -107,6 +107,8 @@ def _log(level, module_line_func, msg): def debug(msg): """(message)""" + module, line, func = _caller() + msg = "({}) - {}".format(line, msg) return _log(Level.DEBUG, _caller(), msg) def info(msg): """(message)""" diff --git a/src/Mod/Path/PathScripts/PathOp.py b/src/Mod/Path/PathScripts/PathOp.py index 44b7252c13..c7a73c892c 100644 --- a/src/Mod/Path/PathScripts/PathOp.py +++ b/src/Mod/Path/PathScripts/PathOp.py @@ -20,17 +20,17 @@ # * * # *************************************************************************** -import time - -from PySide import QtCore - +import FreeCAD from PathScripts.PathUtils import waiting_effects +from PySide.QtCore import QT_TRANSLATE_NOOP 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 +import time + # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader @@ -42,13 +42,13 @@ __author__ = "sliptonic (Brad Collette)" __url__ = "https://www.freecadweb.org" __doc__ = "Base class and properties implementation for all Path operations." -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# PathLog.trackModule() +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) - -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) +translate = FreeCAD.Qt.translate FeatureTool = 0x0001 # ToolController @@ -105,7 +105,7 @@ class ObjectOp(object): "App::PropertyLinkSubListGlobal", "Base", "Path", - QtCore.QT_TRANSLATE_NOOP("PathOp", "The base geometry for this operation"), + QT_TRANSLATE_NOOP("App::Property", "The base geometry for this operation"), ) def addOpValues(self, obj, values): @@ -114,8 +114,8 @@ class ObjectOp(object): "App::PropertyDistance", "OpStartDepth", "Op Values", - QtCore.QT_TRANSLATE_NOOP( - "PathOp", "Holds the calculated value for the StartDepth" + QT_TRANSLATE_NOOP( + "App::Property", "Holds the calculated value for the StartDepth" ), ) obj.setEditorMode("OpStartDepth", 1) # read-only @@ -124,8 +124,8 @@ class ObjectOp(object): "App::PropertyDistance", "OpFinalDepth", "Op Values", - QtCore.QT_TRANSLATE_NOOP( - "PathOp", "Holds the calculated value for the FinalDepth" + QT_TRANSLATE_NOOP( + "App::Property", "Holds the calculated value for the FinalDepth" ), ) obj.setEditorMode("OpFinalDepth", 1) # read-only @@ -134,7 +134,7 @@ class ObjectOp(object): "App::PropertyDistance", "OpToolDiameter", "Op Values", - QtCore.QT_TRANSLATE_NOOP("PathOp", "Holds the diameter of the tool"), + QT_TRANSLATE_NOOP("App::Property", "Holds the diameter of the tool"), ) obj.setEditorMode("OpToolDiameter", 1) # read-only if "stockz" in values: @@ -142,14 +142,14 @@ class ObjectOp(object): "App::PropertyDistance", "OpStockZMax", "Op Values", - QtCore.QT_TRANSLATE_NOOP("PathOp", "Holds the max Z value of Stock"), + QT_TRANSLATE_NOOP("App::Property", "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"), + QT_TRANSLATE_NOOP("App::Property", "Holds the min Z value of Stock"), ) obj.setEditorMode("OpStockZMin", 1) # read-only @@ -160,29 +160,29 @@ class ObjectOp(object): "App::PropertyBool", "Active", "Path", - QtCore.QT_TRANSLATE_NOOP( - "PathOp", "Make False, to prevent operation from generating code" + QT_TRANSLATE_NOOP( + "App::Property", "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" + QT_TRANSLATE_NOOP( + "App::Property", "An optional comment for this Operation" ), ) obj.addProperty( "App::PropertyString", "UserLabel", "Path", - QtCore.QT_TRANSLATE_NOOP("PathOp", "User Assigned Label"), + QT_TRANSLATE_NOOP("App::Property", "User Assigned Label"), ) obj.addProperty( "App::PropertyString", "CycleTime", "Path", - QtCore.QT_TRANSLATE_NOOP("PathOp", "Operations Cycle Time Estimation"), + QT_TRANSLATE_NOOP("App::Property", "Operations Cycle Time Estimation"), ) obj.setEditorMode("CycleTime", 1) # read-only @@ -196,7 +196,7 @@ class ObjectOp(object): "App::PropertyVectorList", "Locations", "Path", - QtCore.QT_TRANSLATE_NOOP("PathOp", "Base locations for this operation"), + QT_TRANSLATE_NOOP("App::Property", "Base locations for this operation"), ) if FeatureTool & features: @@ -204,8 +204,8 @@ class ObjectOp(object): "App::PropertyLink", "ToolController", "Path", - QtCore.QT_TRANSLATE_NOOP( - "PathOp", + QT_TRANSLATE_NOOP( + "App::Property", "The tool controller that will be used to calculate the path", ), ) @@ -213,10 +213,10 @@ class ObjectOp(object): if FeatureCoolant & features: obj.addProperty( - "App::PropertyString", + "App::PropertyEnumeration", "CoolantMode", "Path", - QtCore.QT_TRANSLATE_NOOP("PathOp", "Coolant mode for this operation"), + QT_TRANSLATE_NOOP("App::Property", "Coolant mode for this operation"), ) if FeatureDepths & features: @@ -224,16 +224,16 @@ class ObjectOp(object): "App::PropertyDistance", "StartDepth", "Depth", - QtCore.QT_TRANSLATE_NOOP( - "PathOp", "Starting Depth of Tool- first cut depth in Z" + QT_TRANSLATE_NOOP( + "App::Property", "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" + QT_TRANSLATE_NOOP( + "App::Property", "Final Depth of Tool- lowest value in Z" ), ) if FeatureNoFinalDepth & features: @@ -245,8 +245,9 @@ class ObjectOp(object): "App::PropertyDistance", "StartDepth", "Depth", - QtCore.QT_TRANSLATE_NOOP( - "PathOp", "Starting Depth internal use only for derived values" + QT_TRANSLATE_NOOP( + "App::Property", + "Starting Depth internal use only for derived values", ), ) obj.setEditorMode("StartDepth", 1) # read-only @@ -258,7 +259,7 @@ class ObjectOp(object): "App::PropertyDistance", "StepDown", "Depth", - QtCore.QT_TRANSLATE_NOOP("PathOp", "Incremental Step Down of Tool"), + QT_TRANSLATE_NOOP("App::Property", "Incremental Step Down of Tool"), ) if FeatureFinishDepth & features: @@ -266,8 +267,8 @@ class ObjectOp(object): "App::PropertyDistance", "FinishDepth", "Depth", - QtCore.QT_TRANSLATE_NOOP( - "PathOp", "Maximum material removed on final pass." + QT_TRANSLATE_NOOP( + "App::Property", "Maximum material removed on final pass." ), ) @@ -276,16 +277,17 @@ class ObjectOp(object): "App::PropertyDistance", "ClearanceHeight", "Depth", - QtCore.QT_TRANSLATE_NOOP( - "PathOp", "The height needed to clear clamps and obstructions" + QT_TRANSLATE_NOOP( + "App::Property", + "The height needed to clear clamps and obstructions", ), ) obj.addProperty( "App::PropertyDistance", "SafeHeight", "Depth", - QtCore.QT_TRANSLATE_NOOP( - "PathOp", "Rapid Safety Height between locations." + QT_TRANSLATE_NOOP( + "App::Property", "Rapid Safety Height between locations." ), ) @@ -294,14 +296,14 @@ class ObjectOp(object): "App::PropertyVectorDistance", "StartPoint", "Start Point", - QtCore.QT_TRANSLATE_NOOP("PathOp", "The start point of this path"), + QT_TRANSLATE_NOOP("App::Property", "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" + QT_TRANSLATE_NOOP( + "App::Property", "Make True, if specifying a Start Point" ), ) @@ -310,19 +312,24 @@ class ObjectOp(object): "App::PropertyDistance", "MinDiameter", "Diameter", - QtCore.QT_TRANSLATE_NOOP( - "PathOp", "Lower limit of the turning diameter" + QT_TRANSLATE_NOOP( + "App::Property", "Lower limit of the turning diameter" ), ) obj.addProperty( "App::PropertyDistance", "MaxDiameter", "Diameter", - QtCore.QT_TRANSLATE_NOOP( - "PathOp", "Upper limit of the turning diameter." + QT_TRANSLATE_NOOP( + "App::Property", "Upper limit of the turning diameter." ), ) + for n in self.opPropertyEnumerations(): + PathLog.debug("n[0]: {} n[1]: {}".format(n[0], n[1])) + if hasattr(obj, n[0]): + setattr(obj, n[0], n[1]) + # members being set later self.commandlist = None self.horizFeed = None @@ -347,6 +354,39 @@ class ObjectOp(object): obj.recompute() obj.Proxy = self + @classmethod + def opPropertyEnumerations(self, dataType="data"): + """propertyEnumerations(dataType="data")... return property enumeration lists of specified dataType. + Args: + dataType = 'data', 'raw', 'translated' + Notes: + 'data' is list of internal string literals used in code + 'raw' is list of (translated_text, data_string) tuples + 'translated' is list of translated string literals + """ + + enums = { + "CoolantMode": [ + (translate("Path_Operation", "None"), "None"), + (translate("Path_Operation", "Flood"), "Flood"), + (translate("Path_Operation", "Mist"), "Mist"), + ], + } + + if dataType == "raw": + return enums + + data = list() + idx = 0 if dataType == "translated" else 1 + + PathLog.debug(enums) + + for k, v in enumerate(enums): + data.append((v, [tup[idx] for tup in enums[v]])) + PathLog.debug(data) + + return data + def setEditorModes(self, obj, features): """Editor modes are not preserved during document store/restore, set editor modes for all properties""" @@ -359,6 +399,7 @@ class ObjectOp(object): obj.setEditorMode("OpFinalDepth", 2) def onDocumentRestored(self, obj): + PathLog.track() features = self.opFeatures(obj) if ( FeatureBaseGeometry & features @@ -375,13 +416,28 @@ class ObjectOp(object): 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: + oldvalue = str(obj.CoolantMode) if hasattr(obj, "CoolantMode") else "None" + if ( + hasattr(obj, "CoolantMode") + and not obj.getTypeIdOfProperty("CoolantMode") + == "App::PropertyEnumeration" + ): + obj.removeProperty("CoolantMode") + + if not hasattr(obj, "CoolantMode"): + obj.addProperty( + "App::PropertyEnumeration", + "CoolantMode", + "Path", + QT_TRANSLATE_NOOP( + "App::Property", "Coolant option for this operation" + ), + ) + for n in self.opPropertyEnumerations(): + if n[0] == "CoolantMode": + setattr(obj, n[0], n[1]) + obj.CoolantMode = oldvalue if FeatureDepths & features and not hasattr(obj, "OpStartDepth"): self.addOpValues(obj, ["start", "final"]) @@ -396,7 +452,7 @@ class ObjectOp(object): "App::PropertyString", "CycleTime", "Path", - QtCore.QT_TRANSLATE_NOOP("PathOp", "Operations Cycle Time Estimation"), + QT_TRANSLATE_NOOP("App::Property", "Operations Cycle Time Estimation"), ) self.setEditorModes(obj, features) @@ -515,6 +571,8 @@ class ObjectOp(object): obj.OpToolDiameter = obj.ToolController.Tool.Diameter if FeatureCoolant & features: + PathLog.track() + PathLog.debug(obj.getEnumerationsOfProperty("CoolantMode")) obj.CoolantMode = job.SetupSheet.CoolantMode if FeatureDepths & features: @@ -711,14 +769,6 @@ class ObjectOp(object): # make sure Base is still valid or clear it 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 FeatureTool & self.opFeatures(obj): tc = obj.ToolController if tc is None or tc.ToolNumber == 0: diff --git a/src/Mod/Path/PathScripts/PathOpTools.py b/src/Mod/Path/PathScripts/PathOpTools.py index 4f66953b02..9cad4ca3a7 100644 --- a/src/Mod/Path/PathScripts/PathOpTools.py +++ b/src/Mod/Path/PathScripts/PathOpTools.py @@ -21,40 +21,46 @@ # * * # *************************************************************************** +from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import PathScripts.PathGeom as PathGeom import PathScripts.PathLog as PathLog import math -from PySide import QtCore - # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader -Part = LazyLoader('Part', globals(), 'Part') + +Part = LazyLoader("Part", globals(), "Part") __title__ = "PathOpTools - Tools for Path operations." __author__ = "sliptonic (Brad Collette)" __url__ = "https://www.freecadweb.org" __doc__ = "Collection of functions used by various Path operations. The functions are specific to Path and the algorithms employed by Path's operations." -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -#PathLog.trackModule(PathLog.thisModule()) PrintWireDebug = False -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + +translate = FreeCAD.Qt.translate + def debugEdge(label, e): - '''debugEdge(label, e) ... prints a python statement to create e - Currently lines and arcs are supported.''' + """debugEdge(label, e) ... prints a python statement to create e + Currently lines and arcs are supported.""" if not PrintWireDebug: return p0 = e.valueAt(e.FirstParameter) p1 = e.valueAt(e.LastParameter) if Part.Line == type(e.Curve): - print("%s Part.makeLine((%.2f, %.2f, %.2f), (%.2f, %.2f, %.2f))" % (label, p0.x, p0.y, p0.z, p1.x, p1.y, p1.z)) + print( + "%s Part.makeLine((%.2f, %.2f, %.2f), (%.2f, %.2f, %.2f))" + % (label, p0.x, p0.y, p0.z, p1.x, p1.y, p1.z) + ) elif Part.Circle == type(e.Curve): r = e.Curve.Radius c = e.Curve.Center @@ -65,26 +71,37 @@ def debugEdge(label, e): else: first = math.degrees(xu + e.FirstParameter) last = first + math.degrees(e.LastParameter - e.FirstParameter) - print("%s Part.makeCircle(%.2f, App.Vector(%.2f, %.2f, %.2f), App.Vector(%.2f, %.2f, %.2f), %.2f, %.2f)" % (label, r, c.x, c.y, c.z, a.x, a.y, a.z, first, last)) + print( + "%s Part.makeCircle(%.2f, App.Vector(%.2f, %.2f, %.2f), App.Vector(%.2f, %.2f, %.2f), %.2f, %.2f)" + % (label, r, c.x, c.y, c.z, a.x, a.y, a.z, first, last) + ) else: - print("%s %s (%.2f, %.2f, %.2f) -> (%.2f, %.2f, %.2f)" % (label, type(e.Curve).__name__, p0.x, p0.y, p0.z, p1.x, p1.y, p1.z)) + print( + "%s %s (%.2f, %.2f, %.2f) -> (%.2f, %.2f, %.2f)" + % (label, type(e.Curve).__name__, p0.x, p0.y, p0.z, p1.x, p1.y, p1.z) + ) + def debugWire(label, w): - '''debugWire(label, w) ... prints python statements for all edges of w to be added to the object tree in a group.''' + """debugWire(label, w) ... prints python statements for all edges of w to be added to the object tree in a group.""" if not PrintWireDebug: return print("#%s wire >>>>>>>>>>>>>>>>>>>>>>>>" % label) - print("grp = FreeCAD.ActiveDocument.addObject('App::DocumentObjectGroup', '%s')" % label) - for i,e in enumerate(w.Edges): + print( + "grp = FreeCAD.ActiveDocument.addObject('App::DocumentObjectGroup', '%s')" + % label + ) + for i, e in enumerate(w.Edges): edge = "%s_e%d" % (label, i) debugEdge("%s = " % edge, e) print("Part.show(%s, '%s')" % (edge, edge)) print("grp.addObject(FreeCAD.ActiveDocument.ActiveObject)") print("#%s wire <<<<<<<<<<<<<<<<<<<<<<<<" % label) + def _orientEdges(inEdges): - '''_orientEdges(inEdges) ... internal worker function to orient edges so the last vertex of one edge connects to the first vertex of the next edge. - Assumes the edges are in an order so they can be connected.''' + """_orientEdges(inEdges) ... internal worker function to orient edges so the last vertex of one edge connects to the first vertex of the next edge. + Assumes the edges are in an order so they can be connected.""" PathLog.track() # orient all edges of the wire so each edge's last value connects to the next edge's first value e0 = inEdges[0] @@ -92,21 +109,28 @@ def _orientEdges(inEdges): if 1 < len(inEdges): last = e0.valueAt(e0.LastParameter) e1 = inEdges[1] - if not PathGeom.pointsCoincide(last, e1.valueAt(e1.FirstParameter)) and not PathGeom.pointsCoincide(last, e1.valueAt(e1.LastParameter)): - debugEdge('# _orientEdges - flip first', e0) + if not PathGeom.pointsCoincide( + last, e1.valueAt(e1.FirstParameter) + ) and not PathGeom.pointsCoincide(last, e1.valueAt(e1.LastParameter)): + debugEdge("# _orientEdges - flip first", e0) e0 = PathGeom.flipEdge(e0) edges = [e0] last = e0.valueAt(e0.LastParameter) for e in inEdges[1:]: - edge = e if PathGeom.pointsCoincide(last, e.valueAt(e.FirstParameter)) else PathGeom.flipEdge(e) + edge = ( + e + if PathGeom.pointsCoincide(last, e.valueAt(e.FirstParameter)) + else PathGeom.flipEdge(e) + ) edges.append(edge) last = edge.valueAt(edge.LastParameter) return edges + def _isWireClockwise(w): - '''_isWireClockwise(w) ... return True if wire is oriented clockwise. - Assumes the edges of w are already properly oriented - for generic access use isWireClockwise(w).''' + """_isWireClockwise(w) ... return True if wire is oriented clockwise. + Assumes the edges of w are already properly oriented - for generic access use isWireClockwise(w).""" # handle wires consisting of a single circle or 2 edges where one is an arc. # in both cases, because the edges are expected to be oriented correctly, the orientation can be # determined by looking at (one of) the circle curves. @@ -125,31 +149,33 @@ def _isWireClockwise(w): PathLog.track(area) return area < 0 + def isWireClockwise(w): - '''isWireClockwise(w) ... returns True if the wire winds clockwise. ''' + """isWireClockwise(w) ... returns True if the wire winds clockwise.""" return _isWireClockwise(Part.Wire(_orientEdges(w.Edges))) def orientWire(w, forward=True): - '''orientWire(w, forward=True) ... orients given wire in a specific direction. + """orientWire(w, forward=True) ... orients given wire in a specific direction. If forward = True (the default) the wire is oriented clockwise, looking down the negative Z axis. If forward = False the wire is oriented counter clockwise. - If forward = None the orientation is determined by the order in which the edges appear in the wire.''' - PathLog.debug('orienting forward: {}'.format(forward)) + If forward = None the orientation is determined by the order in which the edges appear in the wire.""" + PathLog.debug("orienting forward: {}".format(forward)) wire = Part.Wire(_orientEdges(w.Edges)) if forward is not None: if forward != _isWireClockwise(wire): - PathLog.track('orientWire - needs flipping') + PathLog.track("orientWire - needs flipping") return PathGeom.flipWire(wire) - PathLog.track('orientWire - ok') + PathLog.track("orientWire - ok") return wire -def offsetWire(wire, base, offset, forward, Side = None): - '''offsetWire(wire, base, offset, forward) ... offsets the wire away from base and orients the wire accordingly. + +def offsetWire(wire, base, offset, forward, Side=None): + """offsetWire(wire, base, offset, forward) ... offsets the wire away from base and orients the wire accordingly. The function tries to avoid most of the pitfalls of Part.makeOffset2D which is possible because all offsetting happens in the XY plane. - ''' - PathLog.track('offsetWire') + """ + PathLog.track("offsetWire") if 1 == len(wire.Edges): edge = wire.Edges[0] @@ -159,24 +185,34 @@ def offsetWire(wire, base, offset, forward, Side = None): # https://www.freecadweb.org/wiki/Part%20Offset2D # it's easy to construct them manually though z = -1 if forward else 1 - new_edge = Part.makeCircle(curve.Radius + offset, curve.Center, FreeCAD.Vector(0, 0, z)) - if base.isInside(new_edge.Vertexes[0].Point, offset/2, True): + new_edge = Part.makeCircle( + curve.Radius + offset, curve.Center, FreeCAD.Vector(0, 0, z) + ) + if base.isInside(new_edge.Vertexes[0].Point, offset / 2, True): if offset > curve.Radius or PathGeom.isRoughly(offset, curve.Radius): # offsetting a hole by its own radius (or more) makes the hole vanish return None if Side: Side[0] = "Inside" print("inside") - new_edge = Part.makeCircle(curve.Radius - offset, curve.Center, FreeCAD.Vector(0, 0, -z)) - + new_edge = Part.makeCircle( + curve.Radius - offset, curve.Center, FreeCAD.Vector(0, 0, -z) + ) + return Part.Wire([new_edge]) - + if Part.Circle == type(curve) and not wire.isClosed(): # Process arc segment z = -1 if forward else 1 - l1 = math.sqrt((edge.Vertexes[0].Point.x - curve.Center.x)**2 + (edge.Vertexes[0].Point.y - curve.Center.y)**2) - l2 = math.sqrt((edge.Vertexes[1].Point.x - curve.Center.x)**2 + (edge.Vertexes[1].Point.y - curve.Center.y)**2) - + l1 = math.sqrt( + (edge.Vertexes[0].Point.x - curve.Center.x) ** 2 + + (edge.Vertexes[0].Point.y - curve.Center.y) ** 2 + ) + l2 = math.sqrt( + (edge.Vertexes[1].Point.x - curve.Center.x) ** 2 + + (edge.Vertexes[1].Point.y - curve.Center.y) ** 2 + ) + # Calculate angles based on x-axis (0 - PI/2) start_angle = math.acos((edge.Vertexes[0].Point.x - curve.Center.x) / l1) end_angle = math.acos((edge.Vertexes[1].Point.x - curve.Center.x) / l2) @@ -186,26 +222,41 @@ def offsetWire(wire, base, offset, forward, Side = None): start_angle *= -1 if edge.Vertexes[1].Point.y < curve.Center.y: end_angle *= -1 - - if (edge.Vertexes[0].Point.x > curve.Center.x or edge.Vertexes[1].Point.x > curve.Center.x) and curve.AngleXU < 0: + + if ( + edge.Vertexes[0].Point.x > curve.Center.x + or edge.Vertexes[1].Point.x > curve.Center.x + ) and curve.AngleXU < 0: tmp = start_angle start_angle = end_angle end_angle = tmp # Inside / Outside - if base.isInside(edge.Vertexes[0].Point, offset/2, True): + if base.isInside(edge.Vertexes[0].Point, offset / 2, True): offset *= -1 if Side: Side[0] = "Inside" # Create new arc if curve.AngleXU > 0: - edge = Part.ArcOfCircle(Part.Circle(curve.Center, FreeCAD.Vector(0,0,1), curve.Radius+offset), start_angle, end_angle).toShape() + edge = Part.ArcOfCircle( + Part.Circle( + curve.Center, FreeCAD.Vector(0, 0, 1), curve.Radius + offset + ), + start_angle, + end_angle, + ).toShape() else: - edge = Part.ArcOfCircle(Part.Circle(curve.Center, FreeCAD.Vector(0,0,1), curve.Radius-offset), start_angle, end_angle).toShape() + edge = Part.ArcOfCircle( + Part.Circle( + curve.Center, FreeCAD.Vector(0, 0, 1), curve.Radius - offset + ), + start_angle, + end_angle, + ).toShape() return Part.Wire([edge]) - + if Part.Line == type(curve) or Part.LineSegment == type(curve): # offsetting a single edge doesn't work because there is an infinite # possible planes into which the edge could be offset @@ -217,7 +268,11 @@ def offsetWire(wire, base, offset, forward, Side = None): edge.translate(o) # offset edde the other way if the result is inside - if base.isInside(edge.valueAt((edge.FirstParameter + edge.LastParameter) / 2), offset / 2, True): + if base.isInside( + edge.valueAt((edge.FirstParameter + edge.LastParameter) / 2), + offset / 2, + True, + ): edge.translate(-2 * o) # flip the edge if it's not on the right side of the original edge @@ -229,23 +284,23 @@ def offsetWire(wire, base, offset, forward, Side = None): return Part.Wire([edge]) # if we get to this point the assumption is that makeOffset2D can deal with the edge - pass # pylint: disable=unnecessary-pass + pass # pylint: disable=unnecessary-pass owire = orientWire(wire.makeOffset2D(offset), True) - debugWire('makeOffset2D_%d' % len(wire.Edges), owire) + debugWire("makeOffset2D_%d" % len(wire.Edges), owire) if wire.isClosed(): - if not base.isInside(owire.Edges[0].Vertexes[0].Point, offset/2, True): - PathLog.track('closed - outside') + if not base.isInside(owire.Edges[0].Vertexes[0].Point, offset / 2, True): + PathLog.track("closed - outside") if Side: Side[0] = "Outside" return orientWire(owire, forward) - PathLog.track('closed - inside') + PathLog.track("closed - inside") if Side: Side[0] = "Inside" try: owire = wire.makeOffset2D(-offset) - except Exception: # pylint: disable=broad-except + except Exception: # pylint: disable=broad-except # most likely offsetting didn't work because the wire is a hole # and the offset is too big - making the hole vanish return None @@ -269,8 +324,8 @@ def offsetWire(wire, base, offset, forward, Side = None): # determine the start and end point start = edges[0].firstVertex().Point end = edges[-1].lastVertex().Point - debugWire('wire', wire) - debugWire('wedges', Part.Wire(edges)) + debugWire("wire", wire) + debugWire("wedges", Part.Wire(edges)) # find edges that are not inside the shape common = base.common(owire) @@ -281,7 +336,9 @@ def offsetWire(wire, base, offset, forward, Side = None): p0 = edge.firstVertex().Point p1 = edge.lastVertex().Point for p in insideEndpoints: - if PathGeom.pointsCoincide(p, p0, 0.01) or PathGeom.pointsCoincide(p, p1, 0.01): + if PathGeom.pointsCoincide(p, p0, 0.01) or PathGeom.pointsCoincide( + p, p1, 0.01 + ): return True return False @@ -292,16 +349,15 @@ def offsetWire(wire, base, offset, forward, Side = None): if not longestWire or longestWire.Length < w.Length: longestWire = w - debugWire('outside', Part.Wire(outside)) - debugWire('longest', longestWire) + debugWire("outside", Part.Wire(outside)) + debugWire("longest", longestWire) def isCircleAt(edge, center): - '''isCircleAt(edge, center) ... helper function returns True if edge is a circle at the given center.''' + """isCircleAt(edge, center) ... helper function returns True if edge is a circle at the given center.""" if Part.Circle == type(edge.Curve) or Part.ArcOfCircle == type(edge.Curve): return PathGeom.pointsCoincide(edge.Curve.Center, center) return False - # split offset wire into edges to the left side and edges to the right side collectLeft = False collectRight = False @@ -312,7 +368,7 @@ def offsetWire(wire, base, offset, forward, Side = None): # an end point (circle centered at one of the end points of the original wire). # should we come to an end point and determine that we've already collected the # next side, we're done - for e in (owire.Edges + owire.Edges): + for e in owire.Edges + owire.Edges: if isCircleAt(e, start): if PathGeom.pointsCoincide(e.Curve.Axis, FreeCAD.Vector(0, 0, 1)): if not collectLeft and leftSideEdges: @@ -340,8 +396,8 @@ def offsetWire(wire, base, offset, forward, Side = None): elif collectRight: rightSideEdges.append(e) - debugWire('left', Part.Wire(leftSideEdges)) - debugWire('right', Part.Wire(rightSideEdges)) + debugWire("left", Part.Wire(leftSideEdges)) + debugWire("right", Part.Wire(rightSideEdges)) # figure out if all the left sided edges or the right sided edges are the ones # that are 'outside'. However, we return the full side. @@ -365,4 +421,3 @@ def offsetWire(wire, base, offset, forward, Side = None): edges.reverse() return orientWire(Part.Wire(edges), None) - diff --git a/src/Mod/Path/PathScripts/PathPropertyBag.py b/src/Mod/Path/PathScripts/PathPropertyBag.py index 2142457ed1..27fef993c5 100644 --- a/src/Mod/Path/PathScripts/PathPropertyBag.py +++ b/src/Mod/Path/PathScripts/PathPropertyBag.py @@ -20,31 +20,39 @@ # * * # *************************************************************************** +from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD -import PySide import re +import PathScripts.PathLog as PathLog -__title__ = 'Generic property container to store some values.' -__author__ = 'sliptonic (Brad Collette)' -__url__ = 'https://www.freecadweb.org' -__doc__ = 'A generic container for typed properties in arbitrary categories.' +__title__ = "Generic property container to store some values." +__author__ = "sliptonic (Brad Collette)" +__url__ = "https://www.freecadweb.org" +__doc__ = "A generic container for typed properties in arbitrary categories." -def translate(context, text, disambig=None): - return PySide.QtCore.QCoreApplication.translate(context, text, disambig) +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + + +translate = FreeCAD.Qt.translate SupportedPropertyType = { - 'Angle' : 'App::PropertyAngle', - 'Bool' : 'App::PropertyBool', - 'Distance' : 'App::PropertyDistance', - 'Enumeration' : 'App::PropertyEnumeration', - 'File' : 'App::PropertyFile', - 'Float' : 'App::PropertyFloat', - 'Integer' : 'App::PropertyInteger', - 'Length' : 'App::PropertyLength', - 'Percent' : 'App::PropertyPercent', - 'String' : 'App::PropertyString', - } + "Angle": "App::PropertyAngle", + "Bool": "App::PropertyBool", + "Distance": "App::PropertyDistance", + "Enumeration": "App::PropertyEnumeration", + "File": "App::PropertyFile", + "Float": "App::PropertyFloat", + "Integer": "App::PropertyInteger", + "Length": "App::PropertyLength", + "Percent": "App::PropertyPercent", + "String": "App::PropertyString", +} + def getPropertyTypeName(o): for typ in SupportedPropertyType: @@ -52,14 +60,22 @@ def getPropertyTypeName(o): return typ raise IndexError() -class PropertyBag(object): - '''Property container object.''' - CustomPropertyGroups = 'CustomPropertyGroups' - CustomPropertyGroupDefault = 'User' +class PropertyBag(object): + """Property container object.""" + + CustomPropertyGroups = "CustomPropertyGroups" + CustomPropertyGroupDefault = "User" def __init__(self, obj): - obj.addProperty('App::PropertyStringList', self.CustomPropertyGroups, 'Base', PySide.QtCore.QT_TRANSLATE_NOOP('PathPropertyBag', 'List of custom property groups')) + obj.addProperty( + "App::PropertyStringList", + self.CustomPropertyGroups, + "Base", + QT_TRANSLATE_NOOP( + "App::Property", "List of custom property groups" + ), + ) self.onDocumentRestored(obj) def __getstate__(self): @@ -69,14 +85,14 @@ class PropertyBag(object): return None def __sanitizePropertyName(self, name): - if(len(name) == 0): + if len(name) == 0: return clean = name[0] for i in range(1, len(name)): - if (name[i] == ' '): + if name[i] == " ": clean += name[i + 1].upper() i += 1 - elif(name[i - 1] != ' '): + elif name[i - 1] != " ": clean += name[i] return clean @@ -85,20 +101,24 @@ class PropertyBag(object): obj.setEditorMode(self.CustomPropertyGroups, 2) # hide def getCustomProperties(self): - '''getCustomProperties() ... Return a list of all custom properties created in this container.''' - return [p for p in self.obj.PropertiesList if self.obj.getGroupOfProperty(p) in self.obj.CustomPropertyGroups] + """getCustomProperties() ... Return a list of all custom properties created in this container.""" + return [ + p + for p in self.obj.PropertiesList + if self.obj.getGroupOfProperty(p) in self.obj.CustomPropertyGroups + ] def addCustomProperty(self, propertyType, name, group=None, desc=None): - '''addCustomProperty(propertyType, name, group=None, desc=None) ... adds a custom property and tracks its group.''' + """addCustomProperty(propertyType, name, group=None, desc=None) ... adds a custom property and tracks its group.""" if desc is None: - desc = '' + desc = "" if group is None: group = self.CustomPropertyGroupDefault groups = self.obj.CustomPropertyGroups name = self.__sanitizePropertyName(name) if not re.match("^[A-Za-z0-9_]*$", name): - raise ValueError('Property Name can only contain letters and numbers') + raise ValueError("Property Name can only contain letters and numbers") if not group in groups: groups.append(group) @@ -107,7 +127,7 @@ class PropertyBag(object): return name def refreshCustomPropertyGroups(self): - '''refreshCustomPropertyGroups() ... removes empty property groups, should be called after deleting properties.''' + """refreshCustomPropertyGroups() ... removes empty property groups, should be called after deleting properties.""" customGroups = [] for p in self.obj.PropertiesList: group = self.obj.getGroupOfProperty(p) @@ -116,17 +136,17 @@ class PropertyBag(object): self.obj.CustomPropertyGroups = customGroups -def Create(name = 'PropertyBag'): - obj = FreeCAD.ActiveDocument.addObject('App::FeaturePython', name) +def Create(name="PropertyBag"): + obj = FreeCAD.ActiveDocument.addObject("App::FeaturePython", name) obj.Proxy = PropertyBag(obj) return obj + def IsPropertyBag(obj): - '''Returns True if the supplied object is a property container (or its Proxy).''' + """Returns True if the supplied object is a property container (or its Proxy).""" if type(obj) == PropertyBag: return True - if hasattr(obj, 'Proxy'): + if hasattr(obj, "Proxy"): return IsPropertyBag(obj.Proxy) return False - diff --git a/src/Mod/Path/PathScripts/PathPropertyBagGui.py b/src/Mod/Path/PathScripts/PathPropertyBagGui.py index 5b438f80c0..81d59fac09 100644 --- a/src/Mod/Path/PathScripts/PathPropertyBagGui.py +++ b/src/Mod/Path/PathScripts/PathPropertyBagGui.py @@ -20,9 +20,9 @@ # * * # *************************************************************************** +from PySide import QtCore, QtGui import FreeCAD import FreeCADGui -#import PathGui import PathScripts.PathIconViewProvider as PathIconViewProvider import PathScripts.PathLog as PathLog import PathScripts.PathPropertyBag as PathPropertyBag @@ -30,23 +30,24 @@ import PathScripts.PathPropertyEditor as PathPropertyEditor import PathScripts.PathUtil as PathUtil import re -from PySide import QtCore, QtGui __title__ = "Property Bag Editor" __author__ = "sliptonic (Brad Collette)" __url__ = "https://www.freecadweb.org" __doc__ = "Task panel editor for a PropertyBag" -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -#PathLog.trackModule(PathLog.thisModule()) +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + +translate = FreeCAD.Qt.translate -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) class ViewProvider(object): - '''ViewProvider for a PropertyBag. - It's sole job is to provide an icon and invoke the TaskPanel on edit.''' + """ViewProvider for a PropertyBag. + It's sole job is to provide an icon and invoke the TaskPanel on edit.""" def __init__(self, vobj, name): PathLog.track(name) @@ -73,7 +74,7 @@ class ViewProvider(object): def getDisplayMode(self, mode): # pylint: disable=unused-argument - return 'Default' + return "Default" def setEdit(self, vobj, mode=0): # pylint: disable=unused-argument @@ -95,18 +96,20 @@ class ViewProvider(object): def doubleClicked(self, vobj): self.setEdit(vobj) + class Delegate(QtGui.QStyledItemDelegate): - RoleObject = QtCore.Qt.UserRole + 1 + RoleObject = QtCore.Qt.UserRole + 1 RoleProperty = QtCore.Qt.UserRole + 2 - RoleEditor = QtCore.Qt.UserRole + 3 + RoleEditor = QtCore.Qt.UserRole + 3 - - #def paint(self, painter, option, index): + # def paint(self, painter, option, index): # #PathLog.track(index.column(), type(option)) def createEditor(self, parent, option, index): # pylint: disable=unused-argument - editor = PathPropertyEditor.Editor(index.data(self.RoleObject), index.data(self.RoleProperty)) + editor = PathPropertyEditor.Editor( + index.data(self.RoleObject), index.data(self.RoleProperty) + ) index.model().setData(index, editor, self.RoleEditor) return editor.widget(parent) @@ -125,8 +128,8 @@ class Delegate(QtGui.QStyledItemDelegate): # pylint: disable=unused-argument widget.setGeometry(option.rect) -class PropertyCreate(object): +class PropertyCreate(object): def __init__(self, obj, grp, typ, another): self.obj = obj self.form = FreeCADGui.PySideUic.loadUi(":panels/PropertyCreate.ui") @@ -144,7 +147,7 @@ class PropertyCreate(object): if typ: self.form.propertyType.setCurrentText(typ) else: - self.form.propertyType.setCurrentText('String') + self.form.propertyType.setCurrentText("String") self.form.createAnother.setChecked(another) self.form.propertyGroup.currentTextChanged.connect(self.updateUI) @@ -159,12 +162,12 @@ class PropertyCreate(object): if self.propertyIsEnumeration(): self.form.labelEnum.setEnabled(True) self.form.propertyEnum.setEnabled(True) - typeSet = self.form.propertyEnum.text().strip() != '' + typeSet = self.form.propertyEnum.text().strip() != "" else: self.form.labelEnum.setEnabled(False) self.form.propertyEnum.setEnabled(False) if self.form.propertyEnum.text().strip(): - self.form.propertyEnum.setText('') + self.form.propertyEnum.setText("") ok = self.form.buttonBox.button(QtGui.QDialogButtonBox.Ok) @@ -178,25 +181,35 @@ class PropertyCreate(object): def propertyName(self): return self.form.propertyName.text().strip() + def propertyGroup(self): return self.form.propertyGroup.currentText().strip() + def propertyType(self): - return PathPropertyBag.SupportedPropertyType[self.form.propertyType.currentText()].strip() + return PathPropertyBag.SupportedPropertyType[ + self.form.propertyType.currentText() + ].strip() + def propertyInfo(self): return self.form.propertyInfo.toPlainText().strip() + def createAnother(self): return self.form.createAnother.isChecked() + def propertyEnumerations(self): - return [s.strip() for s in self.form.propertyEnum.text().strip().split(',')] + return [s.strip() for s in self.form.propertyEnum.text().strip().split(",")] + def propertyIsEnumeration(self): - return self.propertyType() == 'App::PropertyEnumeration' + return self.propertyType() == "App::PropertyEnumeration" def exec_(self, name): if name: # property exists - this is an edit operation self.form.propertyName.setText(name) if self.propertyIsEnumeration(): - self.form.propertyEnum.setText(','.join(self.obj.getEnumerationsOfProperty(name))) + self.form.propertyEnum.setText( + ",".join(self.obj.getEnumerationsOfProperty(name)) + ) self.form.propertyInfo.setText(self.obj.getDocumentationOfProperty(name)) self.form.labelName.setEnabled(False) @@ -206,65 +219,80 @@ class PropertyCreate(object): self.form.createAnother.setEnabled(False) else: - self.form.propertyName.setText('') - self.form.propertyInfo.setText('') - self.form.propertyEnum.setText('') - #self.form.propertyName.setFocus() + self.form.propertyName.setText("") + self.form.propertyInfo.setText("") + self.form.propertyEnum.setText("") + # self.form.propertyName.setFocus() self.updateUI() return self.form.exec_() + Panel = [] + class TaskPanel(object): ColumnName = 0 - #ColumnType = 1 - ColumnVal = 1 - #TableHeaders = ['Property', 'Type', 'Value'] - TableHeaders = ['Property', 'Value'] + # ColumnType = 1 + ColumnVal = 1 + # TableHeaders = ['Property', 'Type', 'Value'] + TableHeaders = ["Property", "Value"] def __init__(self, vobj): - self.obj = vobj.Object + self.obj = vobj.Object self.props = sorted(self.obj.Proxy.getCustomProperties()) - self.form = FreeCADGui.PySideUic.loadUi(":panels/PropertyBag.ui") + self.form = FreeCADGui.PySideUic.loadUi(":panels/PropertyBag.ui") # initialized later - self.model = None + self.model = None self.delegate = None - FreeCAD.ActiveDocument.openTransaction(translate("PathPropertyBag", "Edit PropertyBag")) + FreeCAD.ActiveDocument.openTransaction("Edit PropertyBag") Panel.append(self) def updateData(self, topLeft, bottomRight): pass - def _setupProperty(self, i, name): typ = PathPropertyBag.getPropertyTypeName(self.obj.getTypeIdOfProperty(name)) - val = PathUtil.getPropertyValueString(self.obj, name) + val = PathUtil.getPropertyValueString(self.obj, name) info = self.obj.getDocumentationOfProperty(name) - self.model.setData(self.model.index(i, self.ColumnName), name, QtCore.Qt.EditRole) - #self.model.setData(self.model.index(i, self.ColumnType), typ, QtCore.Qt.EditRole) - self.model.setData(self.model.index(i, self.ColumnVal), self.obj, Delegate.RoleObject) - self.model.setData(self.model.index(i, self.ColumnVal), name, Delegate.RoleProperty) - self.model.setData(self.model.index(i, self.ColumnVal), val, QtCore.Qt.DisplayRole) + self.model.setData( + self.model.index(i, self.ColumnName), name, QtCore.Qt.EditRole + ) + # self.model.setData(self.model.index(i, self.ColumnType), typ, QtCore.Qt.EditRole) + self.model.setData( + self.model.index(i, self.ColumnVal), self.obj, Delegate.RoleObject + ) + self.model.setData( + self.model.index(i, self.ColumnVal), name, Delegate.RoleProperty + ) + self.model.setData( + self.model.index(i, self.ColumnVal), val, QtCore.Qt.DisplayRole + ) - self.model.setData(self.model.index(i, self.ColumnName), typ, QtCore.Qt.ToolTipRole) - #self.model.setData(self.model.index(i, self.ColumnType), info, QtCore.Qt.ToolTipRole) - self.model.setData(self.model.index(i, self.ColumnVal), info, QtCore.Qt.ToolTipRole) + self.model.setData( + self.model.index(i, self.ColumnName), typ, QtCore.Qt.ToolTipRole + ) + # self.model.setData(self.model.index(i, self.ColumnType), info, QtCore.Qt.ToolTipRole) + self.model.setData( + self.model.index(i, self.ColumnVal), info, QtCore.Qt.ToolTipRole + ) self.model.item(i, self.ColumnName).setEditable(False) - #self.model.item(i, self.ColumnType).setEditable(False) + # self.model.item(i, self.ColumnType).setEditable(False) def setupUi(self): PathLog.track() self.delegate = Delegate(self.form) - self.model = QtGui.QStandardItemModel(len(self.props), len(self.TableHeaders), self.form) + self.model = QtGui.QStandardItemModel( + len(self.props), len(self.TableHeaders), self.form + ) self.model.setHorizontalHeaderLabels(self.TableHeaders) - for i,name in enumerate(self.props): + for i, name in enumerate(self.props): self._setupProperty(i, name) self.form.table.setModel(self.model) @@ -301,8 +329,8 @@ class TaskPanel(object): def addCustomProperty(self, obj, dialog): name = dialog.propertyName() - typ = dialog.propertyType() - grp = dialog.propertyGroup() + typ = dialog.propertyType() + grp = dialog.propertyGroup() info = dialog.propertyInfo() propname = self.obj.Proxy.addCustomProperty(typ, name, grp, info) if dialog.propertyIsEnumeration(): @@ -318,17 +346,22 @@ class TaskPanel(object): dialog = PropertyCreate(self.obj, grp, typ, more) if dialog.exec_(None): # if we block signals the view doesn't get updated, surprise, surprise - #self.model.blockSignals(True) + # self.model.blockSignals(True) name, info = self.addCustomProperty(self.obj, dialog) index = 0 for i in range(self.model.rowCount()): index = i - if self.model.item(i, self.ColumnName).data(QtCore.Qt.EditRole) > dialog.propertyName(): + if ( + self.model.item(i, self.ColumnName).data(QtCore.Qt.EditRole) + > dialog.propertyName() + ): break self.model.insertRows(index, 1) self._setupProperty(index, name) - self.form.table.selectionModel().setCurrentIndex(self.model.index(index, 0), QtCore.QItemSelectionModel.Rows) - #self.model.blockSignals(False) + self.form.table.selectionModel().setCurrentIndex( + self.model.index(index, 0), QtCore.QItemSelectionModel.Rows + ) + # self.model.blockSignals(False) more = dialog.createAnother() else: more = False @@ -355,10 +388,14 @@ class TaskPanel(object): # this can happen if the old enumeration value doesn't exist anymore pass newVal = PathUtil.getPropertyValueString(obj, nam) - self.model.setData(self.model.index(row, self.ColumnVal), newVal, QtCore.Qt.DisplayRole) + self.model.setData( + self.model.index(row, self.ColumnVal), newVal, QtCore.Qt.DisplayRole + ) - #self.model.setData(self.model.index(row, self.ColumnType), info, QtCore.Qt.ToolTipRole) - self.model.setData(self.model.index(row, self.ColumnVal), info, QtCore.Qt.ToolTipRole) + # self.model.setData(self.model.index(row, self.ColumnType), info, QtCore.Qt.ToolTipRole) + self.model.setData( + self.model.index(row, self.ColumnVal), info, QtCore.Qt.ToolTipRole + ) def propertyModify(self): PathLog.track() @@ -371,7 +408,6 @@ class TaskPanel(object): self.propertyModifyIndex(index) - def propertyRemove(self): PathLog.track() # first find all rows which need to be removed @@ -387,24 +423,31 @@ class TaskPanel(object): self.model.removeRow(row) -def Create(name = 'PropertyBag'): - '''Create(name = 'PropertyBag') ... creates a new setup sheet''' - FreeCAD.ActiveDocument.openTransaction(translate("PathPropertyBag", "Create PropertyBag")) +def Create(name="PropertyBag"): + """Create(name = 'PropertyBag') ... creates a new setup sheet""" + FreeCAD.ActiveDocument.openTransaction("Create PropertyBag") pcont = PathPropertyBag.Create(name) PathIconViewProvider.Attach(pcont.ViewObject, name) return pcont -PathIconViewProvider.RegisterViewProvider('PropertyBag', ViewProvider) + +PathIconViewProvider.RegisterViewProvider("PropertyBag", ViewProvider) + class PropertyBagCreateCommand(object): - '''Command to create a property container object''' + """Command to create a property container object""" def __init__(self): pass def GetResources(self): - return {'MenuText': translate('PathPropertyBag', 'PropertyBag'), - 'ToolTip': translate('PathPropertyBag', 'Creates an object which can be used to store reference properties.')} + return { + "MenuText": translate("Path_PropertyBag", "PropertyBag"), + "ToolTip": translate( + "Path_PropertyBag", + "Creates an object which can be used to store reference properties.", + ), + } def IsActive(self): return not FreeCAD.ActiveDocument is None @@ -414,17 +457,18 @@ class PropertyBagCreateCommand(object): obj = Create() body = None if sel: - if 'PartDesign::Body' == sel[0].Object.TypeId: + if "PartDesign::Body" == sel[0].Object.TypeId: body = sel[0].Object - elif hasattr(sel[0].Object, 'getParentGeoFeatureGroup'): + elif hasattr(sel[0].Object, "getParentGeoFeatureGroup"): body = sel[0].Object.getParentGeoFeatureGroup() if body: - obj.Label = 'Attributes' + obj.Label = "Attributes" group = body.Group group.append(obj) body.Group = group + if FreeCAD.GuiUp: - FreeCADGui.addCommand('Path_PropertyBag', PropertyBagCreateCommand()) + FreeCADGui.addCommand("Path_PropertyBag", PropertyBagCreateCommand()) FreeCAD.Console.PrintLog("Loading PathPropertyBagGui ... done\n") diff --git a/src/Mod/Path/PathScripts/PathSurface.py b/src/Mod/Path/PathScripts/PathSurface.py index 3c7b1d8552..6a16a8c0fa 100644 --- a/src/Mod/Path/PathScripts/PathSurface.py +++ b/src/Mod/Path/PathScripts/PathSurface.py @@ -44,13 +44,14 @@ except ImportError: # import sys # sys.exit(msg) +from PySide.QtCore import QT_TRANSLATE_NOOP import Path import PathScripts.PathLog as PathLog -import PathScripts.PathUtils as PathUtils import PathScripts.PathOp as PathOp import PathScripts.PathSurfaceSupport as PathSurfaceSupport -import time +import PathScripts.PathUtils as PathUtils import math +import time # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader @@ -60,13 +61,14 @@ Part = LazyLoader("Part", globals(), "Part") if FreeCAD.GuiUp: import FreeCADGui -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# PathLog.trackModule(PathLog.thisModule()) +translate = FreeCAD.Qt.translate -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) class ObjectSurface(PathOp.ObjectOp): @@ -99,7 +101,7 @@ class ObjectSurface(PathOp.ObjectOp): def initOpProperties(self, obj, warn=False): """initOpProperties(obj) ... create operation specific properties""" - self.addNewProps = list() + self.addNewProps = [] for (prtyp, nm, grp, tt) in self.opPropertyDefinitions(): if not hasattr(obj, nm): @@ -107,17 +109,14 @@ class ObjectSurface(PathOp.ObjectOp): self.addNewProps.append(nm) # Set enumeration lists for enumeration properties - if len(self.addNewProps) > 0: - ENUMS = self.opPropertyEnumerations() - for n in ENUMS: - if n in self.addNewProps: - setattr(obj, n, ENUMS[n]) + for n in self.propertyEnumerations(): + setattr(obj, n[0], n[1]) - if warn: - newPropMsg = translate("PathSurface", "New property added to") - newPropMsg += ' "{}": {}'.format(obj.Label, self.addNewProps) + ". " - newPropMsg += translate("PathSurface", "Check default value(s).") - FreeCAD.Console.PrintWarning(newPropMsg + "\n") + # if warn: + # newPropMsg = translate("PathSurface", "New property added to") + # newPropMsg += ' "{}": {}'.format(obj.Label, self.addNewProps) + ". " + # newPropMsg += translate("PathSurface", "Check default value(s).") + # FreeCAD.Console.PrintWarning(newPropMsg + "\n") self.propertiesReady = True @@ -129,7 +128,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyBool", "ShowTempObjects", "Debug", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Show the temporary path construction objects when module is in DEBUG mode.", ), @@ -138,7 +137,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyDistance", "AngularDeflection", "Mesh Conversion", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Smaller values yield a finer, more accurate mesh. Smaller values increase processing time a lot.", ), @@ -147,7 +146,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyDistance", "LinearDeflection", "Mesh Conversion", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Smaller values yield a finer, more accurate mesh. Smaller values do not increase processing time much.", ), @@ -156,7 +155,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyFloat", "CutterTilt", "Rotation", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Stop index(angle) for rotational scan" ), ), @@ -164,7 +163,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyEnumeration", "DropCutterDir", "Rotation", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Dropcutter lines are created parallel to this axis.", ), @@ -173,7 +172,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyVectorDistance", "DropCutterExtraOffset", "Rotation", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Additional offset to the selected bounding box" ), ), @@ -181,7 +180,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyEnumeration", "RotationAxis", "Rotation", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "The model will be rotated around this axis." ), ), @@ -189,7 +188,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyFloat", "StartIndex", "Rotation", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Start index(angle) for rotational scan" ), ), @@ -197,7 +196,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyFloat", "StopIndex", "Rotation", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Stop index(angle) for rotational scan" ), ), @@ -205,7 +204,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyEnumeration", "ScanType", "Surface", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Planar: Flat, 3D surface scan. Rotational: 4th-axis rotational scan.", ), @@ -214,7 +213,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyInteger", "AvoidLastX_Faces", "Selected Geometry Settings", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Avoid cutting the last 'N' faces in the Base Geometry list of selected faces.", ), @@ -223,7 +222,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyBool", "AvoidLastX_InternalFeatures", "Selected Geometry Settings", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Do not cut internal features on avoided faces." ), ), @@ -231,7 +230,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyDistance", "BoundaryAdjustment", "Selected Geometry Settings", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Positive values push the cutter toward, or beyond, the boundary. Negative values retract the cutter away from the boundary.", ), @@ -240,7 +239,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyBool", "BoundaryEnforcement", "Selected Geometry Settings", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "If true, the cutter will remain inside the boundaries of the model or selected face(s).", ), @@ -249,7 +248,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyEnumeration", "HandleMultipleFeatures", "Selected Geometry Settings", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Choose how to process multiple Base Geometry features.", ), @@ -258,7 +257,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyDistance", "InternalFeaturesAdjustment", "Selected Geometry Settings", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Positive values push the cutter toward, or into, the feature. Negative values retract the cutter away from the feature.", ), @@ -267,7 +266,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyBool", "InternalFeaturesCut", "Selected Geometry Settings", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Cut internal feature areas within a larger selected face.", ), @@ -276,7 +275,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyEnumeration", "BoundBox", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Select the overall boundary for the operation." ), ), @@ -284,7 +283,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyEnumeration", "CutMode", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Set the direction for the cutting tool to engage the material: Climb (ClockWise) or Conventional (CounterClockWise)", ), @@ -293,7 +292,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyEnumeration", "CutPattern", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Set the geometric clearing pattern to use for the operation.", ), @@ -302,7 +301,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyFloat", "CutPatternAngle", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "The yaw angle used for certain clearing patterns" ), ), @@ -310,7 +309,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyBool", "CutPatternReversed", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Reverse the cut order of the stepover paths. For circular cut patterns, begin at the outside and work toward the center.", ), @@ -319,7 +318,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyDistance", "DepthOffset", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Set the Z-axis depth offset from the target surface.", ), @@ -328,7 +327,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyEnumeration", "LayerMode", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Complete the operation in a single pass at depth, or mulitiple passes to final depth.", ), @@ -337,7 +336,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyVectorDistance", "PatternCenterCustom", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Set the start point for the cut pattern." ), ), @@ -345,7 +344,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyEnumeration", "PatternCenterAt", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Choose location of the center point for starting the cut pattern.", ), @@ -354,7 +353,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyEnumeration", "ProfileEdges", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Profile the edges of the selection." ), ), @@ -362,7 +361,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyDistance", "SampleInterval", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Set the sampling resolution. Smaller values quickly increase processing time.", ), @@ -371,7 +370,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyFloat", "StepOver", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Set the stepover percentage, based on the tool's diameter.", ), @@ -380,7 +379,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyBool", "OptimizeLinearPaths", "Optimization", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-Code output.", ), @@ -389,7 +388,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyBool", "OptimizeStepOverTransitions", "Optimization", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Enable separate optimization of transitions between, and breaks within, each step over path.", ), @@ -398,7 +397,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyBool", "CircularUseG2G3", "Optimization", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Convert co-planar arcs to G2/G3 gcode commands for `Circular` and `CircularZigZag` cut patterns.", ), @@ -407,7 +406,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyDistance", "GapThreshold", "Optimization", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Collinear and co-radial artifact gaps that are smaller than this threshold are closed in the path.", ), @@ -416,7 +415,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyString", "GapSizes", "Optimization", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Feedback: three smallest gaps identified in the path geometry.", ), @@ -425,7 +424,7 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyVectorDistance", "StartPoint", "Start Point", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "The custom start point for the path of this operation", ), @@ -434,39 +433,86 @@ class ObjectSurface(PathOp.ObjectOp): "App::PropertyBool", "UseStartPoint", "Start Point", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Make True, if specifying a Start Point" ), ), ] - def opPropertyEnumerations(self): + @classmethod + def propertyEnumerations(self, dataType="data"): + """propertyEnumerations(dataType="data")... return property enumeration lists of specified dataType. + Args: + dataType = 'data', 'raw', 'translated' + Notes: + 'data' is list of internal string literals used in code + 'raw' is list of (translated_text, data_string) tuples + 'translated' is list of translated string literals + """ + # Enumeration lists for App::PropertyEnumeration properties - return { - "BoundBox": ["BaseBoundBox", "Stock"], - "PatternCenterAt": [ - "CenterOfMass", - "CenterOfBoundBox", - "XminYmin", - "Custom", + enums = { + "BoundBox": [ + (translate("Path_Surface", "BaseBoundBox"), "BaseBoundBox"), + (translate("Path_Surface", "Stock"), "Stock"), + ], + "PatternCenterAt": [ + (translate("Path_Surface", "CenterOfMass"), "CenterOfMass"), + (translate("Path_Surface", "CenterOfBoundBox"), "CenterOfBoundBox"), + (translate("Path_Surface", "XminYmin"), "XminYmin"), + (translate("Path_Surface", "Custom"), "Custom"), + ], + "CutMode": [ + (translate("Path_Surface", "Conventional"), "Conventional"), + (translate("Path_Surface", "Climb"), "Climb"), ], - "CutMode": ["Conventional", "Climb"], "CutPattern": [ - "Circular", - "CircularZigZag", - "Line", - "Offset", - "Spiral", - "ZigZag", - ], # Additional goals ['Offset', 'ZigZagOffset', 'Grid', 'Triangle'] - "DropCutterDir": ["X", "Y"], - "HandleMultipleFeatures": ["Collectively", "Individually"], - "LayerMode": ["Single-pass", "Multi-pass"], - "ProfileEdges": ["None", "Only", "First", "Last"], - "RotationAxis": ["X", "Y"], - "ScanType": ["Planar", "Rotational"], + (translate("Path_Surface", "Circular"), "Circular"), + (translate("Path_Surface", "CircularZigZag"), "CircularZigZag"), + (translate("Path_Surface", "Line"), "Line"), + (translate("Path_Surface", "Offset"), "Offset"), + (translate("Path_Surface", "Spiral"), "Spiral"), + (translate("Path_Surface", "ZigZag"), "ZigZag"), + ], + "DropCutterDir": [ + (translate("Path_Surface", "X"), "X"), + (translate("Path_Surface", "Y"), "Y"), + ], + "HandleMultipleFeatures": [ + (translate("Path_Surface", "Collectively"), "Collectively"), + (translate("Path_Surface", "Individually"), "Individually"), + ], + "LayerMode": [ + (translate("Path_Surface", "Single-pass"), "Single-pass"), + (translate("Path_Surface", "Multi-pass"), "Multi-pass"), + ], + "ProfileEdges": [ + (translate("Path_Surface", "None"), "None"), + (translate("Path_Surface", "Only"), "Only"), + (translate("Path_Surface", "First"), "First"), + (translate("Path_Surface", "Last"), "Last"), + ], + "RotationAxis": [ + (translate("Path_Surface", "X"), "X"), + (translate("Path_Surface", "Y"), "Y"), + ], + "ScanType": [ + (translate("Path_Surface", "Planar"), "Planar"), + (translate("Path_Surface", "Rotational"), "Rotational"), + ], } + if dataType == "raw": + return enums + + data = [] + idx = 0 if dataType == "translated" else 1 + + for k, v in enumerate(enums): + data.append((v, [tup[idx] for tup in enums[v]])) + + return data + def opPropertyDefaults(self, obj, job): """opPropertyDefaults(obj, job) ... returns a dictionary of default values for the operation's properties.""" @@ -646,32 +692,20 @@ class ObjectSurface(PathOp.ObjectOp): # Limit sample interval if obj.SampleInterval.Value < 0.0001: obj.SampleInterval.Value = 0.0001 - PathLog.error( - translate( - "PathSurface", - "Sample interval limits are 0.001 to 25.4 millimeters.", - ) - ) + PathLog.error("Sample interval limits are 0.001 to 25.4 millimeters.") + if obj.SampleInterval.Value > 25.4: obj.SampleInterval.Value = 25.4 - PathLog.error( - translate( - "PathSurface", - "Sample interval limits are 0.001 to 25.4 millimeters.", - ) - ) + PathLog.error("Sample interval limits are 0.001 to 25.4 millimeters.") # Limit cut pattern angle if obj.CutPatternAngle < -360.0: obj.CutPatternAngle = 0.0 - PathLog.error( - translate("PathSurface", "Cut pattern angle limits are +-360 degrees.") - ) + PathLog.error("Cut pattern angle limits are +-360 degrees.") + if obj.CutPatternAngle >= 360.0: obj.CutPatternAngle = 0.0 - PathLog.error( - translate("PathSurface", "Cut pattern angle limits are +- 360 degrees.") - ) + PathLog.error("Cut pattern angle limits are +- 360 degrees.") # Limit StepOver to natural number percentage if obj.StepOver > 100.0: @@ -682,20 +716,11 @@ class ObjectSurface(PathOp.ObjectOp): # Limit AvoidLastX_Faces to zero and positive values if obj.AvoidLastX_Faces < 0: obj.AvoidLastX_Faces = 0 - PathLog.error( - translate( - "PathSurface", - "AvoidLastX_Faces: Only zero or positive values permitted.", - ) - ) + PathLog.error("AvoidLastX_Faces: Only zero or positive values permitted.") + if obj.AvoidLastX_Faces > 100: obj.AvoidLastX_Faces = 100 - PathLog.error( - translate( - "PathSurface", - "AvoidLastX_Faces: Avoid last X faces count limited to 100.", - ) - ) + PathLog.error("AvoidLastX_Faces: Avoid last X faces count limited to 100.") def opUpdateDepths(self, obj): if hasattr(obj, "Base") and obj.Base: @@ -726,22 +751,22 @@ class ObjectSurface(PathOp.ObjectOp): """opExecute(obj) ... process surface operation""" PathLog.track() - self.modelSTLs = list() - self.safeSTLs = list() - self.modelTypes = list() - self.boundBoxes = list() - self.profileShapes = list() - self.collectiveShapes = list() - self.individualShapes = list() - self.avoidShapes = list() + self.modelSTLs = [] + self.safeSTLs = [] + self.modelTypes = [] + self.boundBoxes = [] + self.profileShapes = [] + self.collectiveShapes = [] + self.individualShapes = [] + self.avoidShapes = [] self.tempGroup = None self.CutClimb = False self.closedGap = False self.tmpCOM = None self.gaps = [0.1, 0.2, 0.3] self.cancelOperation = False - CMDS = list() - modelVisibility = list() + CMDS = [] + modelVisibility = [] FCAD = FreeCAD.ActiveDocument try: @@ -876,26 +901,25 @@ class ObjectSurface(PathOp.ObjectOp): # Save model visibilities for restoration if FreeCAD.GuiUp: - for m in range(0, len(JOB.Model.Group)): - mNm = JOB.Model.Group[m].Name + for model in JOB.Model.Group: + mNm = model.Name modelVisibility.append( FreeCADGui.ActiveDocument.getObject(mNm).Visibility ) # Setup STL, model type, and bound box containers for each model in Job - for m in range(0, len(JOB.Model.Group)): - M = JOB.Model.Group[m] + for model in JOB.Model.Group: self.modelSTLs.append(False) self.safeSTLs.append(False) self.profileShapes.append(False) # Set bound box if obj.BoundBox == "BaseBoundBox": - if M.TypeId.startswith("Mesh"): + if model.TypeId.startswith("Mesh"): self.modelTypes.append("M") # Mesh - self.boundBoxes.append(M.Mesh.BoundBox) + self.boundBoxes.append(model.Mesh.BoundBox) else: self.modelTypes.append("S") # Solid - self.boundBoxes.append(M.Shape.BoundBox) + self.boundBoxes.append(model.Shape.BoundBox) elif obj.BoundBox == "Stock": self.modelTypes.append("S") # Solid self.boundBoxes.append(JOB.Stock.Shape.BoundBox) @@ -916,18 +940,20 @@ class ObjectSurface(PathOp.ObjectOp): self.modelSTLs = PSF.modelSTLs self.profileShapes = PSF.profileShapes - for m in range(0, len(JOB.Model.Group)): + for idx, model in enumerate(JOB.Model.Group): + PathLog.debug(idx) # Create OCL.stl model objects - PathSurfaceSupport._prepareModelSTLs(self, JOB, obj, m, ocl) + PathSurfaceSupport._prepareModelSTLs(self, JOB, obj, idx, ocl) - Mdl = JOB.Model.Group[m] - if FACES[m]: - PathLog.debug("Working on Model.Group[{}]: {}".format(m, Mdl.Label)) - if m > 0: + if FACES[idx]: + PathLog.debug( + "Working on Model.Group[{}]: {}".format(idx, model.Label) + ) + if idx > 0: # Raise to clearance between models CMDS.append( Path.Command( - "N (Transition to base: {}.)".format(Mdl.Label) + "N (Transition to base: {}.)".format(model.Label) ) ) CMDS.append( @@ -938,14 +964,14 @@ class ObjectSurface(PathOp.ObjectOp): ) # make stock-model-voidShapes STL model for avoidance detection on transitions PathSurfaceSupport._makeSafeSTL( - self, JOB, obj, m, FACES[m], VOIDS[m], ocl + self, JOB, obj, idx, FACES[idx], VOIDS[idx], ocl ) # Process model/faces - OCL objects must be ready - CMDS.extend(self._processCutAreas(JOB, obj, m, FACES[m], VOIDS[m])) - else: - PathLog.debug( - "No data for model base: {}".format(JOB.Model.Group[m].Label) + CMDS.extend( + self._processCutAreas(JOB, obj, idx, FACES[idx], VOIDS[idx]) ) + else: + PathLog.debug("No data for model base: {}".format(model.Label)) # Save gcode produced self.commandlist.extend(CMDS) @@ -976,7 +1002,7 @@ class ObjectSurface(PathOp.ObjectOp): tempGroup.purgeTouched() # Provide user feedback for gap sizes - gaps = list() + gaps = [] for g in self.gaps: if g != self.toolDiam: gaps.append(g) @@ -1039,7 +1065,7 @@ class ObjectSurface(PathOp.ObjectOp): It then calls the correct scan method depending on the ScanType property.""" PathLog.debug("_processCutAreas()") - final = list() + final = [] # Process faces Collectively or Individually if obj.HandleMultipleFeatures == "Collectively": @@ -1091,8 +1117,8 @@ class ObjectSurface(PathOp.ObjectOp): It calls the correct Single or Multi-pass method as needed. It returns the gcode for the operation.""" PathLog.debug("_processPlanarOp()") - final = list() - SCANDATA = list() + final = [] + SCANDATA = [] def getTransition(two): first = two[0][0][0] # [step][item][point] @@ -1121,23 +1147,23 @@ class ObjectSurface(PathOp.ObjectOp): self.cutter, ) - profScan = list() + profScan = [] if obj.ProfileEdges != "None": prflShp = self.profileShapes[mdlIdx][fsi] if prflShp is False: msg = translate("PathSurface", "No profile geometry shape returned.") PathLog.error(msg) - return list() + return [] self.showDebugObject(prflShp, "NewProfileShape") # get offset path geometry and perform OCL scan with that geometry pathOffsetGeom = self._offsetFacesToPointData(obj, prflShp) if pathOffsetGeom is False: msg = translate("PathSurface", "No profile path geometry returned.") PathLog.error(msg) - return list() + return [] profScan = [self._planarPerformOclScan(obj, pdc, pathOffsetGeom, True)] - geoScan = list() + geoScan = [] if obj.ProfileEdges != "Only": self.showDebugObject(cmpdShp, "CutArea") # get internal path geometry and perform OCL scan with that geometry @@ -1149,7 +1175,7 @@ class ObjectSurface(PathOp.ObjectOp): if pathGeom is False: msg = translate("PathSurface", "No clearing shape returned.") PathLog.error(msg) - return list() + return [] if obj.CutPattern == "Offset": useGeom = self._offsetFacesToPointData(obj, pathGeom, profile=False) if useGeom is False: @@ -1157,7 +1183,7 @@ class ObjectSurface(PathOp.ObjectOp): "PathSurface", "No clearing path geometry returned." ) PathLog.error(msg) - return list() + return [] geoScan = [self._planarPerformOclScan(obj, pdc, useGeom, True)] else: geoScan = self._planarPerformOclScan(obj, pdc, pathGeom, False) @@ -1177,7 +1203,7 @@ class ObjectSurface(PathOp.ObjectOp): if len(SCANDATA) == 0: msg = translate("PathSurface", "No scan data to convert to Gcode.") PathLog.error(msg) - return list() + return [] # Apply depth offset if obj.DepthOffset.Value != 0.0: @@ -1214,7 +1240,7 @@ class ObjectSurface(PathOp.ObjectOp): def _offsetFacesToPointData(self, obj, subShp, profile=True): PathLog.debug("_offsetFacesToPointData()") - offsetLists = list() + offsetLists = [] dist = obj.SampleInterval.Value / 5.0 # defl = obj.SampleInterval.Value / 5.0 @@ -1246,18 +1272,18 @@ class ObjectSurface(PathOp.ObjectOp): Switching function for calling the appropriate path-geometry to OCL points conversion function for the various cut patterns.""" PathLog.debug("_planarPerformOclScan()") - SCANS = list() + SCANS = [] if offsetPoints or obj.CutPattern == "Offset": PNTSET = PathSurfaceSupport.pathGeomToOffsetPointSet(obj, pathGeom) for D in PNTSET: - stpOvr = list() - ofst = list() + stpOvr = [] + ofst = [] for I in D: if I == "BRK": stpOvr.append(ofst) stpOvr.append(I) - ofst = list() + ofst = [] else: # D format is ((p1, p2), (p3, p4)) (A, B) = I @@ -1266,7 +1292,7 @@ class ObjectSurface(PathOp.ObjectOp): stpOvr.append(ofst) SCANS.extend(stpOvr) elif obj.CutPattern in ["Line", "Spiral", "ZigZag"]: - stpOvr = list() + stpOvr = [] if obj.CutPattern == "Line": # PNTSET = PathSurfaceSupport.pathGeomToLinesPointSet(obj, pathGeom, self.CutClimb, self.toolDiam, self.closedGap, self.gaps) PNTSET = PathSurfaceSupport.pathGeomToLinesPointSet(self, obj, pathGeom) @@ -1287,7 +1313,7 @@ class ObjectSurface(PathOp.ObjectOp): (A, B) = LN stpOvr.append(self._planarDropCutScan(pdc, A, B)) SCANS.append(stpOvr) - stpOvr = list() + stpOvr = [] elif obj.CutPattern in ["Circular", "CircularZigZag"]: # PNTSET is list, by stepover. # Each stepover is a list containing arc/loop descriptions, (sp, ep, cp) @@ -1295,7 +1321,7 @@ class ObjectSurface(PathOp.ObjectOp): PNTSET = PathSurfaceSupport.pathGeomToCircularPointSet(self, obj, pathGeom) for so in range(0, len(PNTSET)): - stpOvr = list() + stpOvr = [] erFlg = False (aTyp, dirFlg, ARCS) = PNTSET[so] @@ -1386,7 +1412,7 @@ class ObjectSurface(PathOp.ObjectOp): odd = True lstStpEnd = None for so in range(0, lenSCANDATA): - cmds = list() + cmds = [] PRTS = SCANDATA[so] lenPRTS = len(PRTS) first = PRTS[0][0] # first point of arc/line stepover group @@ -1489,7 +1515,7 @@ class ObjectSurface(PathOp.ObjectOp): odd = True # ZigZag directional switch lyrHasCmds = False actvSteps = 0 - LYR = list() + LYR = [] # if lyr > 0: # if prvStpLast is not None: # lastPrvStpLast = prvStpLast @@ -1503,8 +1529,8 @@ class ObjectSurface(PathOp.ObjectOp): lenSO = len(SO) # Pre-process step-over parts for layer depth and holds - ADJPRTS = list() - LMAX = list() + ADJPRTS = [] + LMAX = [] soHasPnts = False brkFlg = False for i in range(0, lenSO): @@ -1530,9 +1556,9 @@ class ObjectSurface(PathOp.ObjectOp): # Process existing parts within current step over prtsHasCmds = False stepHasCmds = False - prtsCmds = list() - stpOvrCmds = list() - transCmds = list() + prtsCmds = [] + stpOvrCmds = [] + transCmds = [] if soHasPnts is True: first = ADJPRTS[0][0] # first point of arc/line stepover group last = None @@ -1657,8 +1683,8 @@ class ObjectSurface(PathOp.ObjectOp): return GCODE def _planarMultipassPreProcess(self, obj, LN, prvDep, layDep): - ALL = list() - PTS = list() + ALL = [] + PTS = [] optLinTrans = obj.OptimizeStepOverTransitions safe = math.ceil(obj.SafeHeight.Value) @@ -1685,7 +1711,7 @@ class ObjectSurface(PathOp.ObjectOp): if optLinTrans is True: # Remove leading and trailing Hold Points - popList = list() + popList = [] for i in range(0, len(PTS)): # identify leading string if PTS[i].z == safe: popList.append(i) @@ -1695,7 +1721,7 @@ class ObjectSurface(PathOp.ObjectOp): for p in popList: # Remove hold points PTS.pop(p) ALL.pop(p) - popList = list() + popList = [] for i in range(len(PTS) - 1, -1, -1): # identify trailing string if PTS[i].z == safe: popList.append(i) @@ -1717,7 +1743,7 @@ class ObjectSurface(PathOp.ObjectOp): return (PTS, lMax) def _planarMultipassProcess(self, obj, PNTS, lMax): - output = list() + output = [] optimize = obj.OptimizeLinearPaths safe = math.ceil(obj.SafeHeight.Value) lenPNTS = len(PNTS) @@ -1800,7 +1826,7 @@ class ObjectSurface(PathOp.ObjectOp): passes, as well as other kinds of breaks. When OptimizeStepOverTransitions is enabled, uses safePDC to safely optimize short (~order of cutter diameter) transitions.""" - cmds = list() + cmds = [] rtpd = False height = obj.SafeHeight.Value # Allow cutter-down transitions with a distance up to 2x cutter @@ -1869,7 +1895,7 @@ class ObjectSurface(PathOp.ObjectOp): return cmds def _arcsToG2G3(self, LN, numPts, odd, gDIR, tolrnc): - cmds = list() + cmds = [] strtPnt = LN[0] endPnt = LN[numPts - 1] strtHght = strtPnt.z diff --git a/src/Mod/Path/PathScripts/PathSurfaceGui.py b/src/Mod/Path/PathScripts/PathSurfaceGui.py index 25a5cea8c7..83e98323cd 100644 --- a/src/Mod/Path/PathScripts/PathSurfaceGui.py +++ b/src/Mod/Path/PathScripts/PathSurfaceGui.py @@ -20,47 +20,69 @@ # * * # *************************************************************************** +from PySide import QtCore import FreeCAD import FreeCADGui -import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts.PathSurface as PathSurface +import PathScripts.PathLog as PathLog +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathGui as PathGui import PathScripts.PathOpGui as PathOpGui +import PathScripts.PathSurface as PathSurface -from PySide import QtCore __title__ = "Path Surface Operation UI" __author__ = "sliptonic (Brad Collette)" __url__ = "https://www.freecadweb.org" __doc__ = "Surface operation page controller and command implementation." +translate = FreeCAD.Qt.translate + +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + class TaskPanelOpPage(PathOpGui.TaskPanelPage): - '''Page controller class for the Surface operation.''' + """Page controller class for the Surface operation.""" def initPage(self, obj): self.setTitle("3D Surface - " + obj.Label) # self.updateVisibility() # retrieve property enumerations - self.propEnums = PathSurface.ObjectSurface.opPropertyEnumerations(False) + # self.propEnums = PathSurface.ObjectSurface.opPropertyEnumerations(False) + self.propEnums = PathSurface.ObjectSurface.propertyEnumerations(False) def getForm(self): - '''getForm() ... returns UI''' - return FreeCADGui.PySideUic.loadUi(":/panels/PageOpSurfaceEdit.ui") + """getForm() ... returns UI""" + form = FreeCADGui.PySideUic.loadUi(":/panels/PageOpSurfaceEdit.ui") + comboToPropertyMap = [ + ("boundBoxSelect", "BoundBox"), + ("scanType", "ScanType"), + ("cutPattern", "CutPattern"), + ("profileEdges", "ProfileEdges"), + ("layerMode", "LayerMode"), + ("dropCutterDirSelect", "DropCutterDir"), + ] + enumTups = PathSurface.ObjectSurface.propertyEnumerations(dataType="raw") + PathGui.populateCombobox(form, enumTups, comboToPropertyMap) + + return form def getFields(self, obj): - '''getFields(obj) ... transfers values from UI to obj's proprties''' + """getFields(obj) ... transfers values from UI to obj's proprties""" self.updateToolController(obj, self.form.toolController) self.updateCoolant(obj, self.form.coolantController) - if obj.BoundBox != str(self.form.boundBoxSelect.currentText()): - obj.BoundBox = str(self.form.boundBoxSelect.currentText()) + if obj.BoundBox != str(self.form.boundBoxSelect.currentData()): + obj.BoundBox = str(self.form.boundBoxSelect.currentData()) - if obj.ScanType != str(self.form.scanType.currentText()): - obj.ScanType = str(self.form.scanType.currentText()) + if obj.ScanType != str(self.form.scanType.currentData()): + obj.ScanType = str(self.form.scanType.currentData()) - if obj.LayerMode != str(self.form.layerMode.currentText()): - obj.LayerMode = str(self.form.layerMode.currentText()) + if obj.LayerMode != str(self.form.layerMode.currentData()): + obj.LayerMode = str(self.form.layerMode.currentData()) """ The following method of getting values from the UI form @@ -74,29 +96,36 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): This type of dynamic combobox population is done for the Tool Controller selection. """ - val = self.propEnums['CutPattern'][self.form.cutPattern.currentIndex()] - if obj.CutPattern != val: - obj.CutPattern = val + # val = self.propEnums["CutPattern"][self.form.cutPattern.currentIndex()] + # if obj.CutPattern != val: + # obj.CutPattern = val - val = self.propEnums['ProfileEdges'][self.form.profileEdges.currentIndex()] - if obj.ProfileEdges != val: - obj.ProfileEdges = val + # val = self.propEnums["ProfileEdges"][self.form.profileEdges.currentIndex()] + # if obj.ProfileEdges != val: + # obj.ProfileEdges = val + + obj.CutPattern = self.form.cutPattern.currentData() + obj.ProfileEdges = self.form.profileEdges.currentData() if obj.AvoidLastX_Faces != self.form.avoidLastX_Faces.value(): obj.AvoidLastX_Faces = self.form.avoidLastX_Faces.value() - obj.DropCutterExtraOffset.x = FreeCAD.Units.Quantity(self.form.boundBoxExtraOffsetX.text()).Value - obj.DropCutterExtraOffset.y = FreeCAD.Units.Quantity(self.form.boundBoxExtraOffsetY.text()).Value + obj.DropCutterExtraOffset.x = FreeCAD.Units.Quantity( + self.form.boundBoxExtraOffsetX.text() + ).Value + obj.DropCutterExtraOffset.y = FreeCAD.Units.Quantity( + self.form.boundBoxExtraOffsetY.text() + ).Value - if obj.DropCutterDir != str(self.form.dropCutterDirSelect.currentText()): - obj.DropCutterDir = str(self.form.dropCutterDirSelect.currentText()) + if obj.DropCutterDir != str(self.form.dropCutterDirSelect.currentData()): + obj.DropCutterDir = str(self.form.dropCutterDirSelect.currentData()) - PathGui.updateInputField(obj, 'DepthOffset', self.form.depthOffset) + PathGui.updateInputField(obj, "DepthOffset", self.form.depthOffset) if obj.StepOver != self.form.stepOver.value(): obj.StepOver = self.form.stepOver.value() - PathGui.updateInputField(obj, 'SampleInterval', self.form.sampleInterval) + PathGui.updateInputField(obj, "SampleInterval", self.form.sampleInterval) if obj.UseStartPoint != self.form.useStartPoint.isChecked(): obj.UseStartPoint = self.form.useStartPoint.isChecked() @@ -107,11 +136,16 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): if obj.OptimizeLinearPaths != self.form.optimizeEnabled.isChecked(): obj.OptimizeLinearPaths = self.form.optimizeEnabled.isChecked() - if obj.OptimizeStepOverTransitions != self.form.optimizeStepOverTransitions.isChecked(): - obj.OptimizeStepOverTransitions = self.form.optimizeStepOverTransitions.isChecked() + if ( + obj.OptimizeStepOverTransitions + != self.form.optimizeStepOverTransitions.isChecked() + ): + obj.OptimizeStepOverTransitions = ( + self.form.optimizeStepOverTransitions.isChecked() + ) def setFields(self, obj): - '''setFields(obj) ... transfers obj's property values to UI''' + """setFields(obj) ... transfers obj's property values to UI""" self.setupToolController(obj, self.form.toolController) self.setupCoolant(obj, self.form.coolantController) self.selectInComboBox(obj.BoundBox, self.form.boundBoxSelect) @@ -126,20 +160,36 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): and the UI panel QComboBox list. The original method is commented out below. """ - idx = self.propEnums['CutPattern'].index(obj.CutPattern) - self.form.cutPattern.setCurrentIndex(idx) - idx = self.propEnums['ProfileEdges'].index(obj.ProfileEdges) - self.form.profileEdges.setCurrentIndex(idx) - # self.selectInComboBox(obj.CutPattern, self.form.cutPattern) - # self.selectInComboBox(obj.ProfileEdges, self.form.profileEdges) + # idx = self.propEnums["CutPattern"].index(obj.CutPattern) + # self.form.cutPattern.setCurrentIndex(idx) + # idx = self.propEnums["ProfileEdges"].index(obj.ProfileEdges) + # self.form.profileEdges.setCurrentIndex(idx) + self.selectInComboBox(obj.CutPattern, self.form.cutPattern) + self.selectInComboBox(obj.ProfileEdges, self.form.profileEdges) self.form.avoidLastX_Faces.setValue(obj.AvoidLastX_Faces) - self.form.boundBoxExtraOffsetX.setText(FreeCAD.Units.Quantity(obj.DropCutterExtraOffset.x, FreeCAD.Units.Length).UserString) - self.form.boundBoxExtraOffsetY.setText(FreeCAD.Units.Quantity(obj.DropCutterExtraOffset.y, FreeCAD.Units.Length).UserString) + self.form.boundBoxExtraOffsetX.setText( + FreeCAD.Units.Quantity( + obj.DropCutterExtraOffset.x, FreeCAD.Units.Length + ).UserString + ) + self.form.boundBoxExtraOffsetY.setText( + FreeCAD.Units.Quantity( + obj.DropCutterExtraOffset.y, FreeCAD.Units.Length + ).UserString + ) self.selectInComboBox(obj.DropCutterDir, self.form.dropCutterDirSelect) - self.form.depthOffset.setText(FreeCAD.Units.Quantity(obj.DepthOffset.Value, FreeCAD.Units.Length).UserString) + self.form.depthOffset.setText( + FreeCAD.Units.Quantity( + obj.DepthOffset.Value, FreeCAD.Units.Length + ).UserString + ) self.form.stepOver.setValue(obj.StepOver) - self.form.sampleInterval.setText(FreeCAD.Units.Quantity(obj.SampleInterval.Value, FreeCAD.Units.Length).UserString) + self.form.sampleInterval.setText( + FreeCAD.Units.Quantity( + obj.SampleInterval.Value, FreeCAD.Units.Length + ).UserString + ) if obj.UseStartPoint: self.form.useStartPoint.setCheckState(QtCore.Qt.Checked) @@ -164,7 +214,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.updateVisibility() def getSignalsForUpdate(self, obj): - '''getSignalsForUpdate(obj) ... return list of signals for updating obj''' + """getSignalsForUpdate(obj) ... return list of signals for updating obj""" signals = [] signals.append(self.form.toolController.currentIndexChanged) signals.append(self.form.coolantController.currentIndexChanged) @@ -188,12 +238,12 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): return signals def updateVisibility(self, sentObj=None): - '''updateVisibility(sentObj=None)... Updates visibility of Tasks panel objects.''' - if self.form.scanType.currentText() == 'Planar': + """updateVisibility(sentObj=None)... Updates visibility of Tasks panel objects.""" + if self.form.scanType.currentText() == "Planar": self.form.cutPattern.show() self.form.cutPattern_label.show() self.form.optimizeStepOverTransitions.show() - if hasattr(self.form, 'profileEdges'): + if hasattr(self.form, "profileEdges"): self.form.profileEdges.show() self.form.profileEdges_label.show() self.form.avoidLastX_Faces.show() @@ -204,11 +254,11 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.form.boundBoxExtraOffset_label.hide() self.form.dropCutterDirSelect.hide() self.form.dropCutterDirSelect_label.hide() - elif self.form.scanType.currentText() == 'Rotational': + elif self.form.scanType.currentText() == "Rotational": self.form.cutPattern.hide() self.form.cutPattern_label.hide() self.form.optimizeStepOverTransitions.hide() - if hasattr(self.form, 'profileEdges'): + if hasattr(self.form, "profileEdges"): self.form.profileEdges.hide() self.form.profileEdges_label.hide() self.form.avoidLastX_Faces.hide() @@ -224,12 +274,16 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.form.scanType.currentIndexChanged.connect(self.updateVisibility) -Command = PathOpGui.SetupOperation('Surface', - PathSurface.Create, - TaskPanelOpPage, - 'Path_3DSurface', - QtCore.QT_TRANSLATE_NOOP("Path_Surface", "3D Surface"), - QtCore.QT_TRANSLATE_NOOP("Path_Surface", "Create a 3D Surface Operation from a model"), - PathSurface.SetupProperties) +Command = PathOpGui.SetupOperation( + "Surface", + PathSurface.Create, + TaskPanelOpPage, + "Path_3DSurface", + QtCore.QT_TRANSLATE_NOOP("Path_Surface", "3D Surface"), + QtCore.QT_TRANSLATE_NOOP( + "Path_Surface", "Create a 3D Surface Operation from a model" + ), + PathSurface.SetupProperties, +) FreeCAD.Console.PrintLog("Loading PathSurfaceGui... done\n") diff --git a/src/Mod/Path/PathScripts/PathSurfaceSupport.py b/src/Mod/Path/PathScripts/PathSurfaceSupport.py index d9a7c6fb94..8ba42c20b4 100644 --- a/src/Mod/Path/PathScripts/PathSurfaceSupport.py +++ b/src/Mod/Path/PathScripts/PathSurfaceSupport.py @@ -29,8 +29,6 @@ __doc__ = "Support functions and classes for 3D Surface and Waterline operations __contributors__ = "" import FreeCAD -from PySide import QtCore -import Path import PathScripts.PathLog as PathLog import PathScripts.PathUtils as PathUtils import PathScripts.PathOpTools as PathOpTools @@ -38,36 +36,38 @@ import math # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader + # MeshPart = LazyLoader('MeshPart', globals(), 'MeshPart') -Part = LazyLoader('Part', globals(), 'Part') +Part = LazyLoader("Part", globals(), "Part") -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# PathLog.trackModule(PathLog.thisModule()) +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) +translate = FreeCAD.Qt.translate class PathGeometryGenerator: - '''Creates a path geometry shape from an assigned pattern for conversion to tool paths. + """Creates a path geometry shape from an assigned pattern for conversion to tool paths. PathGeometryGenerator(obj, shape, pattern) `obj` is the operation object, `shape` is the horizontal planar shape object, and `pattern` is the name of the geometric pattern to apply. First, call the getCenterOfPattern() method for the CenterOfMass for patterns allowing a custom center. - Next, call the generatePathGeometry() method to request the path geometry shape.''' + Next, call the generatePathGeometry() method to request the path geometry shape.""" # Register valid patterns here by name # Create a corresponding processing method below. Precede the name with an underscore(_) - patterns = ('Circular', 'CircularZigZag', 'Line', 'Offset', 'Spiral', 'ZigZag') + patterns = ("Circular", "CircularZigZag", "Line", "Offset", "Spiral", "ZigZag") def __init__(self, obj, shape, pattern): - '''__init__(obj, shape, pattern)... Instantiate PathGeometryGenerator class. - Required arguments are the operation object, horizontal planar shape, and pattern name.''' + """__init__(obj, shape, pattern)... Instantiate PathGeometryGenerator class. + Required arguments are the operation object, horizontal planar shape, and pattern name.""" self.debugObjectsGroup = False - self.pattern = 'None' + self.pattern = "None" self.shape = None self.pathGeometry = None self.rawGeoList = None @@ -85,15 +85,16 @@ class PathGeometryGenerator: # validate requested pattern if pattern in self.patterns: - if hasattr(self, '_' + pattern): + if hasattr(self, "_" + pattern): self.pattern = pattern if shape.BoundBox.ZMin != 0.0: shape.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - shape.BoundBox.ZMin)) if shape.BoundBox.ZLength > 1.0e-8: - msg = translate('PathSurfaceSupport', - 'Shape appears to not be horizontal planar.') - msg += ' ZMax == {} mm.\n'.format(shape.BoundBox.ZMax) + msg = translate( + "PathSurfaceSupport", "Shape appears to not be horizontal planar." + ) + msg += " ZMax == {} mm.\n".format(shape.BoundBox.ZMax) FreeCAD.Console.PrintWarning(msg) else: self.shape = shape @@ -101,8 +102,8 @@ class PathGeometryGenerator: def _prepareConstants(self): # Compute weighted center of mass of all faces combined - if self.pattern in ['Circular', 'CircularZigZag', 'Spiral']: - if self.obj.PatternCenterAt == 'CenterOfMass': + if self.pattern in ["Circular", "CircularZigZag", "Spiral"]: + if self.obj.PatternCenterAt == "CenterOfMass": fCnt = 0 totArea = 0.0 zeroCOM = FreeCAD.Vector(0.0, 0.0, 0.0) @@ -111,12 +112,20 @@ class PathGeometryGenerator: areaF = F.Area totArea += areaF fCnt += 1 - zeroCOM = zeroCOM.add(FreeCAD.Vector(comF.x, comF.y, 0.0).multiply(areaF)) + zeroCOM = zeroCOM.add( + FreeCAD.Vector(comF.x, comF.y, 0.0).multiply(areaF) + ) if fCnt == 0: - msg = translate('PathSurfaceSupport', - 'Cannot calculate the Center Of Mass.') - msg += ' ' + translate('PathSurfaceSupport', - 'Using Center of Boundbox instead.') + '\n' + msg = translate( + "PathSurfaceSupport", "Cannot calculate the Center Of Mass." + ) + msg += ( + " " + + translate( + "PathSurfaceSupport", "Using Center of Boundbox instead." + ) + + "\n" + ) FreeCAD.Console.PrintError(msg) bbC = self.shape.BoundBox.Center zeroCOM = FreeCAD.Vector(bbC.x, bbC.y, 0.0) @@ -133,33 +142,39 @@ class PathGeometryGenerator: # get X, Y, Z spans; Compute center of rotation self.deltaX = self.shape.BoundBox.XLength self.deltaY = self.shape.BoundBox.YLength - self.deltaC = self.shape.BoundBox.DiagonalLength # math.sqrt(self.deltaX**2 + self.deltaY**2) - lineLen = self.deltaC + (2.0 * self.toolDiam) # Line length to span boundbox diag with 2x cutter diameter extra on each end + self.deltaC = ( + self.shape.BoundBox.DiagonalLength + ) # math.sqrt(self.deltaX**2 + self.deltaY**2) + lineLen = self.deltaC + ( + 2.0 * self.toolDiam + ) # Line length to span boundbox diag with 2x cutter diameter extra on each end self.halfDiag = math.ceil(lineLen / 2.0) - cutPasses = math.ceil(lineLen / self.cutOut) + 1 # Number of lines(passes) required to cover boundbox diagonal + cutPasses = ( + math.ceil(lineLen / self.cutOut) + 1 + ) # Number of lines(passes) required to cover boundbox diagonal self.halfPasses = math.ceil(cutPasses / 2.0) # Public methods def setDebugObjectsGroup(self, tmpGrpObject): - '''setDebugObjectsGroup(tmpGrpObject)... - Pass the temporary object group to show temporary construction objects''' + """setDebugObjectsGroup(tmpGrpObject)... + Pass the temporary object group to show temporary construction objects""" self.debugObjectsGroup = tmpGrpObject def getCenterOfPattern(self): - '''getCenterOfPattern()... - Returns the Center Of Mass for the current class instance.''' + """getCenterOfPattern()... + Returns the Center Of Mass for the current class instance.""" return self.centerOfPattern def generatePathGeometry(self): - '''generatePathGeometry()... - Call this function to obtain the path geometry shape, generated by this class.''' - if self.pattern == 'None': + """generatePathGeometry()... + Call this function to obtain the path geometry shape, generated by this class.""" + if self.pattern == "None": return False if self.shape is None: return False - cmd = 'self._' + self.pattern + '()' + cmd = "self._" + self.pattern + "()" exec(cmd) if self.obj.CutPatternReversed is True: @@ -169,26 +184,30 @@ class PathGeometryGenerator: geomShape = Part.makeCompound(self.rawGeoList) # Position and rotate the Line and ZigZag geometry - if self.pattern in ['Line', 'ZigZag']: + if self.pattern in ["Line", "ZigZag"]: if self.obj.CutPatternAngle != 0.0: - geomShape.Placement.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), self.obj.CutPatternAngle) + geomShape.Placement.Rotation = FreeCAD.Rotation( + FreeCAD.Vector(0, 0, 1), self.obj.CutPatternAngle + ) bbC = self.shape.BoundBox.Center - geomShape.Placement.Base = FreeCAD.Vector(bbC.x, bbC.y, 0.0 - geomShape.BoundBox.ZMin) + geomShape.Placement.Base = FreeCAD.Vector( + bbC.x, bbC.y, 0.0 - geomShape.BoundBox.ZMin + ) if self.debugObjectsGroup: - F = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpGeometrySet') + F = FreeCAD.ActiveDocument.addObject("Part::Feature", "tmpGeometrySet") F.Shape = geomShape F.purgeTouched() self.debugObjectsGroup.addObject(F) - if self.pattern == 'Offset': + if self.pattern == "Offset": return geomShape # Identify intersection of cross-section face and lineset cmnShape = self.shape.common(geomShape) if self.debugObjectsGroup: - F = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpPathGeometry') + F = FreeCAD.ActiveDocument.addObject("Part::Feature", "tmpPathGeometry") F.Shape = cmnShape F.purgeTouched() self.debugObjectsGroup.addObject(F) @@ -197,7 +216,7 @@ class PathGeometryGenerator: # Cut pattern methods def _Circular(self): - GeoSet = list() + GeoSet = [] radialPasses = self._getRadialPasses() minRad = self.toolDiam * 0.45 siX3 = 3 * self.obj.SampleInterval.Value @@ -206,14 +225,14 @@ class PathGeometryGenerator: if minRad < minRadSI: minRad = minRadSI - PathLog.debug(' -centerOfPattern: {}'.format(self.centerOfPattern)) + PathLog.debug(" -centerOfPattern: {}".format(self.centerOfPattern)) # Make small center circle to start pattern if self.obj.StepOver > 50: circle = Part.makeCircle(minRad, self.centerOfPattern) GeoSet.append(circle) for lc in range(1, radialPasses + 1): - rad = (lc * self.cutOut) + rad = lc * self.cutOut if rad >= minRad: circle = Part.makeCircle(rad, self.centerOfPattern) GeoSet.append(circle) @@ -224,11 +243,13 @@ class PathGeometryGenerator: self._Circular() # Use _Circular generator def _Line(self): - GeoSet = list() - centRot = FreeCAD.Vector(0.0, 0.0, 0.0) # Bottom left corner of face/selection/model + GeoSet = [] + centRot = FreeCAD.Vector( + 0.0, 0.0, 0.0 + ) # Bottom left corner of face/selection/model # Create end points for set of lines to intersect with cross-section face - pntTuples = list() + pntTuples = [] for lc in range((-1 * (self.halfPasses - 1)), self.halfPasses + 1): x1 = centRot.x - self.halfDiag x2 = centRot.x + self.halfDiag @@ -249,8 +270,8 @@ class PathGeometryGenerator: self.rawGeoList = self._extractOffsetFaces() def _Spiral(self): - GeoSet = list() - SEGS = list() + GeoSet = [] + SEGS = [] draw = True loopRadians = 0.0 # Used to keep track of complete loops/cycles sumRadians = 0.0 @@ -263,12 +284,14 @@ class PathGeometryGenerator: # Set tool properties and calculate cutout cutOut = self.cutOut / twoPi - segLen = self.obj.SampleInterval.Value # CutterDiameter / 10.0 # SampleInterval.Value + segLen = ( + self.obj.SampleInterval.Value + ) # CutterDiameter / 10.0 # SampleInterval.Value stepAng = segLen / ((loopCnt + 1) * self.cutOut) # math.pi / 18.0 # 10 degrees stopRadians = maxDist / cutOut if self.obj.CutPatternReversed: - if self.obj.CutMode == 'Conventional': + if self.obj.CutMode == "Conventional": getPoint = self._makeOppSpiralPnt else: getPoint = self._makeRegSpiralPnt @@ -276,13 +299,17 @@ class PathGeometryGenerator: while draw: radAng = sumRadians + stepAng p1 = lastPoint - p2 = getPoint(move, cutOut, radAng) # cutOut is 'b' in the equation r = b * radAng + p2 = getPoint( + move, cutOut, radAng + ) # cutOut is 'b' in the equation r = b * radAng sumRadians += stepAng # Increment sumRadians loopRadians += stepAng # Increment loopRadians if loopRadians > twoPi: loopCnt += 1 loopRadians -= twoPi - stepAng = segLen / ((loopCnt + 1) * self.cutOut) # adjust stepAng with each loop/cycle + stepAng = segLen / ( + (loopCnt + 1) * self.cutOut + ) # adjust stepAng with each loop/cycle segCnt += 1 lastPoint = p2 if sumRadians > stopRadians: @@ -293,7 +320,7 @@ class PathGeometryGenerator: # Ewhile SEGS.reverse() else: - if self.obj.CutMode == 'Climb': + if self.obj.CutMode == "Climb": getPoint = self._makeOppSpiralPnt else: getPoint = self._makeRegSpiralPnt @@ -301,13 +328,17 @@ class PathGeometryGenerator: while draw: radAng = sumRadians + stepAng p1 = lastPoint - p2 = getPoint(move, cutOut, radAng) # cutOut is 'b' in the equation r = b * radAng + p2 = getPoint( + move, cutOut, radAng + ) # cutOut is 'b' in the equation r = b * radAng sumRadians += stepAng # Increment sumRadians loopRadians += stepAng # Increment loopRadians if loopRadians > twoPi: loopCnt += 1 loopRadians -= twoPi - stepAng = segLen / ((loopCnt + 1) * self.cutOut) # adjust stepAng with each loop/cycle + stepAng = segLen / ( + (loopCnt + 1) * self.cutOut + ) # adjust stepAng with each loop/cycle segCnt += 1 lastPoint = p2 if sumRadians > stopRadians: @@ -329,18 +360,22 @@ class PathGeometryGenerator: def _getPatternCenter(self): centerAt = self.obj.PatternCenterAt - if centerAt == 'CenterOfMass': + if centerAt == "CenterOfMass": cntrPnt = FreeCAD.Vector(self.centerOfMass.x, self.centerOfMass.y, 0.0) - elif centerAt == 'CenterOfBoundBox': + elif centerAt == "CenterOfBoundBox": cent = self.shape.BoundBox.Center cntrPnt = FreeCAD.Vector(cent.x, cent.y, 0.0) - elif centerAt == 'XminYmin': - cntrPnt = FreeCAD.Vector(self.shape.BoundBox.XMin, self.shape.BoundBox.YMin, 0.0) - elif centerAt == 'Custom': - cntrPnt = FreeCAD.Vector(self.obj.PatternCenterCustom.x, self.obj.PatternCenterCustom.y, 0.0) + elif centerAt == "XminYmin": + cntrPnt = FreeCAD.Vector( + self.shape.BoundBox.XMin, self.shape.BoundBox.YMin, 0.0 + ) + elif centerAt == "Custom": + cntrPnt = FreeCAD.Vector( + self.obj.PatternCenterCustom.x, self.obj.PatternCenterCustom.y, 0.0 + ) # Update centerOfPattern point - if centerAt != 'Custom': + if centerAt != "Custom": self.obj.PatternCenterCustom = cntrPnt self.centerOfPattern = cntrPnt @@ -349,7 +384,7 @@ class PathGeometryGenerator: def _getRadialPasses(self): # recalculate number of passes, if need be radialPasses = self.halfPasses - if self.obj.PatternCenterAt != 'CenterOfBoundBox': + if self.obj.PatternCenterAt != "CenterOfBoundBox": # make 4 corners of boundbox in XY plane, find which is greatest distance to new circular center EBB = self.shape.BoundBox CORNERS = [ @@ -363,8 +398,12 @@ class PathGeometryGenerator: dist = CORNERS[c].sub(self.centerOfPattern).Length if dist > dMax: dMax = dist - diag = dMax + (2.0 * self.toolDiam) # Line length to span boundbox diag with 2x cutter diameter extra on each end - radialPasses = math.ceil(diag / self.cutOut) + 1 # Number of lines(passes) required to cover boundbox diagonal + diag = dMax + ( + 2.0 * self.toolDiam + ) # Line length to span boundbox diag with 2x cutter diameter extra on each end + radialPasses = ( + math.ceil(diag / self.cutOut) + 1 + ) # Number of lines(passes) required to cover boundbox diagonal return radialPasses @@ -379,8 +418,8 @@ class PathGeometryGenerator: return FreeCAD.Vector(-1 * x, y, 0.0).add(move) def _extractOffsetFaces(self): - PathLog.debug('_extractOffsetFaces()') - wires = list() + PathLog.debug("_extractOffsetFaces()") + wires = [] shape = self.shape offset = 0.0 # Start right at the edge of cut area direction = 0 @@ -392,7 +431,7 @@ class PathGeometryGenerator: return -1 def _reverse_wire(w): - rev_list = list() + rev_list = [] for e in w.Edges: rev_list.append(PathUtils.reverseEdge(e)) rev_list.reverse() @@ -409,7 +448,7 @@ class PathGeometryGenerator: if direction == 0: first_face_wire = offsetArea.Faces[0].Wires[0] direction = _get_direction(first_face_wire) - if self.obj.CutMode == 'Climb': + if self.obj.CutMode == "Climb": if direction == 1: direction = -1 else: @@ -417,7 +456,7 @@ class PathGeometryGenerator: direction = 1 # Correct cut direction for `Conventional` cuts - if self.obj.CutMode == 'Conventional': + if self.obj.CutMode == "Conventional": if loop_cnt == 1: direction = direction * -1 @@ -442,6 +481,8 @@ class PathGeometryGenerator: offset -= self.cutOut loop_cnt += 1 return wires + + # Eclass @@ -452,23 +493,29 @@ class ProcessSelectedFaces: two compound objects as a tuple: (FACES, VOIDS) or False.""" def __init__(self, JOB, obj): - self.modelSTLs = list() - self.profileShapes = list() + self.modelSTLs = [] + self.profileShapes = [] self.tempGroup = False self.showDebugObjects = False self.checkBase = False self.module = None self.radius = None self.depthParams = None - self.msgNoFaces = translate('PathSurfaceSupport', - 'Face selection is unavailable for Rotational scans.') + '\n' - self.msgNoFaces += ' ' + translate('PathSurfaceSupport', - 'Ignoring selected faces.') + '\n' + self.msgNoFaces = ( + translate( + "PathSurfaceSupport", + "Face selection is unavailable for Rotational scans.", + ) + + "\n" + ) + self.msgNoFaces += ( + " " + translate("PathSurfaceSupport", "Ignoring selected faces.") + "\n" + ) self.JOB = JOB self.obj = obj - self.profileEdges = 'None' + self.profileEdges = "None" - if hasattr(obj, 'ProfileEdges'): + if hasattr(obj, "ProfileEdges"): self.profileEdges = obj.ProfileEdges # Setup STL, model type, and bound box containers for each model in Job @@ -483,7 +530,7 @@ class ProcessSelectedFaces: if self.obj.Base: if len(self.obj.Base) > 0: self.checkBase = True - if self.obj.ScanType == 'Rotational': + if self.obj.ScanType == "Rotational": self.checkBase = False FreeCAD.Console.PrintWarning(self.msgNoFaces) @@ -491,7 +538,7 @@ class ProcessSelectedFaces: if self.obj.Base: if len(self.obj.Base) > 0: self.checkBase = True - if self.obj.Algorithm in ['OCL Dropcutter', 'Experimental']: + if self.obj.Algorithm in ["OCL Dropcutter", "Experimental"]: self.checkBase = False FreeCAD.Console.PrintWarning(self.msgNoFaces) @@ -501,15 +548,15 @@ class ProcessSelectedFaces: self.showDebugObjects = val def preProcessModel(self, module): - PathLog.debug('preProcessModel()') + PathLog.debug("preProcessModel()") if not self._isReady(module): return False - FACES = list() - VOIDS = list() - fShapes = list() - vShapes = list() + FACES = [] + VOIDS = [] + fShapes = [] + vShapes = [] GRP = self.JOB.Model.Group lenGRP = len(GRP) proceed = False @@ -523,15 +570,19 @@ class ProcessSelectedFaces: # The user has selected subobjects from the base. Pre-Process each. if self.checkBase: - PathLog.debug(' -obj.Base exists. Pre-processing for selected faces.') + PathLog.debug(" -obj.Base exists. Pre-processing for selected faces.") - (hasFace, hasVoid) = self._identifyFacesAndVoids(FACES, VOIDS) # modifies FACES and VOIDS + (hasFace, hasVoid) = self._identifyFacesAndVoids( + FACES, VOIDS + ) # modifies FACES and VOIDS hasGeometry = True if hasFace or hasVoid else False # Cycle through each base model, processing faces for each for m in range(0, lenGRP): base = GRP[m] - (mFS, mVS, mPS) = self._preProcessFacesAndVoids(base, FACES[m], VOIDS[m]) + (mFS, mVS, mPS) = self._preProcessFacesAndVoids( + base, FACES[m], VOIDS[m] + ) fShapes[m] = mFS vShapes[m] = mVS self.profileShapes[m] = mPS @@ -540,55 +591,64 @@ class ProcessSelectedFaces: if hasGeometry and not proceed: return False else: - PathLog.debug(' -No obj.Base data.') + PathLog.debug(" -No obj.Base data.") for m in range(0, lenGRP): self.modelSTLs[m] = True # Process each model base, as a whole, as needed for m in range(0, lenGRP): if self.modelSTLs[m] and not fShapes[m]: - PathLog.debug(' -Pre-processing {} as a whole.'.format(GRP[m].Label)) - if self.obj.BoundBox == 'BaseBoundBox': + PathLog.debug(" -Pre-processing {} as a whole.".format(GRP[m].Label)) + if self.obj.BoundBox == "BaseBoundBox": base = GRP[m] - elif self.obj.BoundBox == 'Stock': + elif self.obj.BoundBox == "Stock": base = self.JOB.Stock pPEB = self._preProcessEntireBase(base, m) if pPEB is False: - msg = translate('PathSurfaceSupport', - 'Failed to pre-process base as a whole.') + '\n' + msg = ( + translate( + "PathSurfaceSupport", + "Failed to pre-process base as a whole.", + ) + + "\n" + ) FreeCAD.Console.PrintError(msg) else: (fcShp, prflShp) = pPEB if fcShp: if fcShp is True: - PathLog.debug(' -fcShp is True.') + PathLog.debug(" -fcShp is True.") fShapes[m] = True else: fShapes[m] = [fcShp] if prflShp: if fcShp: - PathLog.debug('vShapes[{}]: {}'.format(m, vShapes[m])) + PathLog.debug("vShapes[{}]: {}".format(m, vShapes[m])) if vShapes[m]: - PathLog.debug(' -Cutting void from base profile shape.') + PathLog.debug(" -Cutting void from base profile shape.") adjPS = prflShp.cut(vShapes[m][0]) self.profileShapes[m] = [adjPS] else: - PathLog.debug(' -vShapes[m] is False.') + PathLog.debug(" -vShapes[m] is False.") self.profileShapes[m] = [prflShp] else: - PathLog.debug(' -Saving base profile shape.') + PathLog.debug(" -Saving base profile shape.") self.profileShapes[m] = [prflShp] - PathLog.debug('self.profileShapes[{}]: {}'.format(m, self.profileShapes[m])) + PathLog.debug( + "self.profileShapes[{}]: {}".format( + m, self.profileShapes[m] + ) + ) # Efor return (fShapes, vShapes) # private class methods def _isReady(self, module): - '''_isReady(module)... Internal method. - Checks if required attributes are available for processing obj.Base (the Base Geometry).''' - PathLog.debug('ProcessSelectedFaces _isReady({})'.format(module)) + """_isReady(module)... Internal method. + Checks if required attributes are available for processing obj.Base (the Base Geometry).""" + PathLog.debug("ProcessSelectedFaces _isReady({})".format(module)) if hasattr(self, module): self.module = module modMethod = getattr(self, module) # gets the attribute only @@ -598,17 +658,17 @@ class ProcessSelectedFaces: return False if not self.radius: - PathLog.error('PSF._isReady() no cutter radius available.') + PathLog.error("PSF._isReady() no cutter radius available.") return False if not self.depthParams: - PathLog.error('PSF._isReady() no depth params available.') + PathLog.error("PSF._isReady() no depth params available.") return False return True def _identifyFacesAndVoids(self, F, V): - TUPS = list() + TUPS = [] GRP = self.JOB.Model.Group lenGRP = len(GRP) hasFace = False @@ -636,15 +696,15 @@ class ProcessSelectedFaces: faceIdx = int(sub[4:]) - 1 if bst < add: if F[m] is False: - F[m] = list() + F[m] = [] F[m].append((shape, faceIdx)) - PathLog.debug('.. Cutting {}'.format(sub)) + PathLog.debug(".. Cutting {}".format(sub)) hasFace = True else: if V[m] is False: - V[m] = list() + V[m] = [] V[m].append((shape, faceIdx)) - PathLog.debug('.. Avoiding {}'.format(sub)) + PathLog.debug(".. Avoiding {}".format(sub)) hasVoid = True return (hasFace, hasVoid) @@ -652,38 +712,32 @@ class ProcessSelectedFaces: mFS = False mVS = False mPS = False - mIFS = list() + mIFS = [] if FCS: isHole = False - if self.obj.HandleMultipleFeatures == 'Collectively': + if self.obj.HandleMultipleFeatures == "Collectively": cont = True - PathLog.debug( - 'Attempting to get cross-section of collective faces.') + PathLog.debug("Attempting to get cross-section of collective faces.") outFCS, ifL = self.findUnifiedRegions(FCS) if self.obj.InternalFeaturesCut and ifL: - ifL = list() # clear avoid shape list + ifL = [] # clear avoid shape list if len(outFCS) == 0: - msg = translate( - 'PathSurfaceSupport', - 'Cannot process selected faces. Check horizontal ' - 'surface exposure.') - FreeCAD.Console.PrintError(msg + '\n') + msg = "PathSurfaceSupport \n Cannot process selected faces. Check horizontal \n surface exposure.\n" + FreeCAD.Console.PrintError(msg) cont = False else: cfsL = Part.makeCompound(outFCS) # Handle profile edges request - if cont and self.profileEdges != 'None': - PathLog.debug('.. include Profile Edge') + if cont and self.profileEdges != "None": + PathLog.debug(".. include Profile Edge") ofstVal = self._calculateOffsetValue(isHole) - psOfst = PathUtils.getOffsetArea(cfsL, - ofstVal, - plane=self.wpc) + psOfst = PathUtils.getOffsetArea(cfsL, ofstVal, plane=self.wpc) if psOfst: mPS = [psOfst] - if self.profileEdges == 'Only': + if self.profileEdges == "Only": mFS = True cont = False else: @@ -691,17 +745,17 @@ class ProcessSelectedFaces: if cont: if self.showDebugObjects: - T = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpCollectiveShape') + T = FreeCAD.ActiveDocument.addObject( + "Part::Feature", "tmpCollectiveShape" + ) T.Shape = cfsL T.purgeTouched() self.tempGroup.addObject(T) ofstVal = self._calculateOffsetValue(isHole) - faceOfstShp = PathUtils.getOffsetArea( - cfsL, ofstVal, plane=self.wpc) + faceOfstShp = PathUtils.getOffsetArea(cfsL, ofstVal, plane=self.wpc) if not faceOfstShp: - msg = translate('PathSurfaceSupport', - 'Failed to create offset face.') + '\n' + msg = "Failed to create offset face." FreeCAD.Console.PrintError(msg) cont = False @@ -709,26 +763,29 @@ class ProcessSelectedFaces: lenIfL = len(ifL) if not self.obj.InternalFeaturesCut: if lenIfL == 0: - PathLog.debug(' -No internal features saved.') + PathLog.debug(" -No internal features saved.") else: if lenIfL == 1: casL = ifL[0] else: casL = Part.makeCompound(ifL) if self.showDebugObjects: - C = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpCompoundIntFeat') + C = FreeCAD.ActiveDocument.addObject( + "Part::Feature", "tmpCompoundIntFeat" + ) C.Shape = casL C.purgeTouched() self.tempGroup.addObject(C) ofstVal = self._calculateOffsetValue(isHole=True) intOfstShp = PathUtils.getOffsetArea( - casL, ofstVal, plane=self.wpc) + casL, ofstVal, plane=self.wpc + ) mIFS.append(intOfstShp) mFS = [faceOfstShp] # Eif - elif self.obj.HandleMultipleFeatures == 'Individually': + elif self.obj.HandleMultipleFeatures == "Individually": for (fcshp, fcIdx) in FCS: cont = True fNum = fcIdx + 1 @@ -738,22 +795,25 @@ class ProcessSelectedFaces: if len(gUR) > 0: outerFace = gUR[0] if self.obj.InternalFeaturesCut: - ifL = list() # avoid shape list + ifL = [] # avoid shape list if outerFace: - PathLog.debug('Attempting to create offset face of Face{}'.format(fNum)) + PathLog.debug( + "Attempting to create offset face of Face{}".format(fNum) + ) - if self.profileEdges != 'None': + if self.profileEdges != "None": ofstVal = self._calculateOffsetValue(isHole) psOfst = PathUtils.getOffsetArea( - outerFace, ofstVal, plane=self.wpc) + outerFace, ofstVal, plane=self.wpc + ) if psOfst: if mPS is False: - mPS = list() + mPS = [] mPS.append(psOfst) - if self.profileEdges == 'Only': + if self.profileEdges == "Only": if mFS is False: - mFS = list() + mFS = [] mFS.append(True) cont = False else: @@ -762,7 +822,8 @@ class ProcessSelectedFaces: if cont: ofstVal = self._calculateOffsetValue(isHole) faceOfstShp = PathUtils.getOffsetArea( - outerFace, ofstVal, plane=self.wpc) + outerFace, ofstVal, plane=self.wpc + ) lenIfl = len(ifL) if self.obj.InternalFeaturesCut is False and lenIfl > 0: @@ -773,12 +834,13 @@ class ProcessSelectedFaces: ofstVal = self._calculateOffsetValue(isHole=True) intOfstShp = PathUtils.getOffsetArea( - casL, ofstVal, plane=self.wpc) + casL, ofstVal, plane=self.wpc + ) mIFS.append(intOfstShp) # faceOfstShp = faceOfstShp.cut(intOfstShp) if mFS is False: - mFS = list() + mFS = [] mFS.append(faceOfstShp) # Eif # Efor @@ -787,18 +849,18 @@ class ProcessSelectedFaces: if len(mIFS) > 0: if mVS is False: - mVS = list() + mVS = [] for ifs in mIFS: mVS.append(ifs) if VDS: - PathLog.debug('Processing avoid faces.') + PathLog.debug("Processing avoid faces.") cont = True isHole = False outFCS, intFEAT = self.findUnifiedRegions(VDS) if self.obj.InternalFeaturesCut: - intFEAT = list() + intFEAT = [] lenOtFcs = len(outFCS) if lenOtFcs == 0: @@ -810,25 +872,26 @@ class ProcessSelectedFaces: avoid = Part.makeCompound(outFCS) if self.showDebugObjects: - P = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpVoidEnvelope') + P = FreeCAD.ActiveDocument.addObject( + "Part::Feature", "tmpVoidEnvelope" + ) P.Shape = avoid P.purgeTouched() self.tempGroup.addObject(P) if cont: if self.showDebugObjects: - P = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpVoidCompound') + P = FreeCAD.ActiveDocument.addObject( + "Part::Feature", "tmpVoidCompound" + ) P.Shape = avoid P.purgeTouched() self.tempGroup.addObject(P) ofstVal = self._calculateOffsetValue(isHole, isVoid=True) - avdOfstShp = PathUtils.getOffsetArea(avoid, - ofstVal, - plane=self.wpc) + avdOfstShp = PathUtils.getOffsetArea(avoid, ofstVal, plane=self.wpc) if avdOfstShp is False: - msg = translate('PathSurfaceSupport', - 'Failed to create collective offset avoid face.') - FreeCAD.Console.PrintError(msg + '\n') + msg = "Failed to create collective offset avoid face.\n" + FreeCAD.Console.PrintError(msg) cont = False if cont: @@ -840,18 +903,15 @@ class ProcessSelectedFaces: else: ifc = intFEAT[0] ofstVal = self._calculateOffsetValue(isHole=True) - ifOfstShp = PathUtils.getOffsetArea(ifc, - ofstVal, - plane=self.wpc) + ifOfstShp = PathUtils.getOffsetArea(ifc, ofstVal, plane=self.wpc) if ifOfstShp is False: - msg = translate('PathSurfaceSupport', - 'Failed to create collective offset avoid internal features.') + '\n' + msg = "Failed to create collective offset avoid internal features.\n" FreeCAD.Console.PrintError(msg) else: avdShp = avdOfstShp.cut(ifOfstShp) if mVS is False: - mVS = list() + mVS = [] mVS.append(avdShp) return (mFS, mVS, mPS) @@ -864,13 +924,17 @@ class ProcessSelectedFaces: # baseEnv = PathUtils.getEnvelope(base.Shape, subshape=None, depthparams=self.depthParams) try: - baseEnv = PathUtils.getEnvelope(partshape=base.Shape, subshape=None, depthparams=self.depthParams) # Produces .Shape + baseEnv = PathUtils.getEnvelope( + partshape=base.Shape, subshape=None, depthparams=self.depthParams + ) # Produces .Shape except Exception as ee: PathLog.error(str(ee)) shell = base.Shape.Shells[0] solid = Part.makeSolid(shell) try: - baseEnv = PathUtils.getEnvelope(partshape=solid, subshape=None, depthparams=self.depthParams) # Produces .Shape + baseEnv = PathUtils.getEnvelope( + partshape=solid, subshape=None, depthparams=self.depthParams + ) # Produces .Shape except Exception as eee: PathLog.error(str(eee)) cont = False @@ -882,17 +946,15 @@ class ProcessSelectedFaces: if csFaceShape is False: csFaceShape = getSliceFromEnvelope(baseEnv) if csFaceShape is False: - PathLog.debug('Failed to slice baseEnv shape.') + PathLog.debug("Failed to slice baseEnv shape.") cont = False - if cont and self.profileEdges != 'None': - PathLog.debug(' -Attempting profile geometry for model base.') + if cont and self.profileEdges != "None": + PathLog.debug(" -Attempting profile geometry for model base.") ofstVal = self._calculateOffsetValue(isHole) - psOfst = PathUtils.getOffsetArea(csFaceShape, - ofstVal, - plane=self.wpc) + psOfst = PathUtils.getOffsetArea(csFaceShape, ofstVal, plane=self.wpc) if psOfst: - if self.profileEdges == 'Only': + if self.profileEdges == "Only": return (True, psOfst) prflShp = psOfst else: @@ -900,24 +962,28 @@ class ProcessSelectedFaces: if cont: ofstVal = self._calculateOffsetValue(isHole) - faceOffsetShape = PathUtils.getOffsetArea(csFaceShape, ofstVal, - plane=self.wpc) + faceOffsetShape = PathUtils.getOffsetArea( + csFaceShape, ofstVal, plane=self.wpc + ) if faceOffsetShape is False: - PathLog.debug('getOffsetArea() failed for entire base.') + PathLog.debug("getOffsetArea() failed for entire base.") else: - faceOffsetShape.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - faceOffsetShape.BoundBox.ZMin)) + faceOffsetShape.translate( + FreeCAD.Vector(0.0, 0.0, 0.0 - faceOffsetShape.BoundBox.ZMin) + ) return (faceOffsetShape, prflShp) return False def _calculateOffsetValue(self, isHole, isVoid=False): - '''_calculateOffsetValue(self.obj, isHole, isVoid) ... internal function. - Calculate the offset for the Path.Area() function.''' + """_calculateOffsetValue(self.obj, isHole, isVoid) ... internal function. + Calculate the offset for the Path.Area() function.""" self.JOB = PathUtils.findParentJob(self.obj) # We need to offset by at least our linear tessellation deflection # (default GeometryTolerance / 4) to avoid false retracts at the # boundaries. - tolrnc = max(self.JOB.GeometryTolerance.Value / 10.0, - self.obj.LinearDeflection.Value) + tolrnc = max( + self.JOB.GeometryTolerance.Value / 10.0, self.obj.LinearDeflection.Value + ) if isVoid is False: if isHole is True: @@ -936,13 +1002,10 @@ class ProcessSelectedFaces: return offset - def findUnifiedRegions( - self, - shapeAndIndexTuples, - useAreaImplementation=True): + def findUnifiedRegions(self, shapeAndIndexTuples, useAreaImplementation=True): """Wrapper around area and wire based region unification implementations.""" - PathLog.debug('findUnifiedRegions()') + PathLog.debug("findUnifiedRegions()") # Allow merging of faces within the LinearDeflection tolerance. tolerance = self.obj.LinearDeflection.Value # Default: normal to Z=1 (XY plane), at Z=0 @@ -956,47 +1019,48 @@ class ProcessSelectedFaces: 0.0 - tolerance / 10, removeHoles=True, # Outline has holes filled in tolerance=tolerance, - plane=self.wpc) + plane=self.wpc, + ) projectionShape = PathUtils.getOffsetArea( shapes, # Make the projection very slightly larger tolerance / 10, removeHoles=False, # Projection has holes preserved tolerance=tolerance, - plane=self.wpc) + plane=self.wpc, + ) internalShape = outlineShape.cut(projectionShape) # Filter out tiny faces, usually artifacts around the perimeter of # the cut. - minArea = (10 * tolerance)**2 - internalFaces = [ - f for f in internalShape.Faces if f.Area > minArea - ] + minArea = (10 * tolerance) ** 2 + internalFaces = [f for f in internalShape.Faces if f.Area > minArea] if internalFaces: internalFaces = Part.makeCompound(internalFaces) return ([outlineShape], [internalFaces]) except Exception as e: PathLog.warning( - "getOffsetArea failed: {}; Using FindUnifiedRegions.".format( - e)) + "getOffsetArea failed: {}; Using FindUnifiedRegions.".format(e) + ) # Use face-unifying class FUR = FindUnifiedRegions(shapeAndIndexTuples, tolerance) if self.showDebugObjects: FUR.setTempGroup(self.tempGroup) return (FUR.getUnifiedRegions(), FUR.getInternalFeatures) + # Eclass # Functions for getting a shape envelope and cross-section def getExtrudedShape(wire): - PathLog.debug('getExtrudedShape()') + PathLog.debug("getExtrudedShape()") wBB = wire.BoundBox extFwd = math.floor(2.0 * wBB.ZLength) + 10.0 try: shell = wire.extrude(FreeCAD.Vector(0.0, 0.0, extFwd)) except Exception as ee: - PathLog.error(' -extrude wire failed: \n{}'.format(ee)) + PathLog.error(" -extrude wire failed: \n{}".format(ee)) return False SHP = Part.makeSolid(shell) @@ -1004,7 +1068,7 @@ def getExtrudedShape(wire): def getShapeSlice(shape): - PathLog.debug('getShapeSlice()') + PathLog.debug("getShapeSlice()") bb = shape.BoundBox mid = (bb.ZMin + bb.ZMax) / 2.0 @@ -1032,7 +1096,7 @@ def getShapeSlice(shape): if slcArea < midArea: for W in slcShp.Wires: if W.isClosed() is False: - PathLog.debug(' -wire.isClosed() is False') + PathLog.debug(" -wire.isClosed() is False") return False if len(slcShp.Wires) == 1: wire = slcShp.Wires[0] @@ -1040,7 +1104,7 @@ def getShapeSlice(shape): slc.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - slc.BoundBox.ZMin)) return slc else: - fL = list() + fL = [] for W in slcShp.Wires: slc = Part.Face(W) slc.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - slc.BoundBox.ZMin)) @@ -1053,8 +1117,9 @@ def getShapeSlice(shape): def getProjectedFace(tempGroup, wire): import Draft - PathLog.debug('getProjectedFace()') - F = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpProjectionWire') + + PathLog.debug("getProjectedFace()") + F = FreeCAD.ActiveDocument.addObject("Part::Feature", "tmpProjectionWire") F.Shape = wire F.purgeTouched() tempGroup.addObject(F) @@ -1076,8 +1141,8 @@ def getProjectedFace(tempGroup, wire): def getCrossSection(shape): - PathLog.debug('getCrossSection()') - wires = list() + PathLog.debug("getCrossSection()") + wires = [] bb = shape.BoundBox mid = (bb.ZMin + bb.ZMax) / 2.0 @@ -1089,19 +1154,19 @@ def getCrossSection(shape): comp.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - comp.BoundBox.ZMin)) csWire = comp.Wires[0] if csWire.isClosed() is False: - PathLog.debug(' -comp.Wires[0] is not closed') + PathLog.debug(" -comp.Wires[0] is not closed") return False CS = Part.Face(csWire) CS.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - CS.BoundBox.ZMin)) return CS else: - PathLog.debug(' -No wires from .slice() method') + PathLog.debug(" -No wires from .slice() method") return False def getShapeEnvelope(shape): - PathLog.debug('getShapeEnvelope()') + PathLog.debug("getShapeEnvelope()") wBB = shape.BoundBox extFwd = wBB.ZLength + 10.0 @@ -1111,22 +1176,24 @@ def getShapeEnvelope(shape): dep_par = PathUtils.depth_params(maxz + 5.0, maxz + 3.0, maxz, stpDwn, 0.0, minz) try: - env = PathUtils.getEnvelope(partshape=shape, depthparams=dep_par) # Produces .Shape + env = PathUtils.getEnvelope( + partshape=shape, depthparams=dep_par + ) # Produces .Shape except Exception as ee: - FreeCAD.Console.PrintError('PathUtils.getEnvelope() failed.\n' + str(ee) + '\n') + FreeCAD.Console.PrintError("PathUtils.getEnvelope() failed.\n" + str(ee) + "\n") return False else: return env def getSliceFromEnvelope(env): - PathLog.debug('getSliceFromEnvelope()') + PathLog.debug("getSliceFromEnvelope()") eBB = env.BoundBox extFwd = eBB.ZLength + 10.0 maxz = eBB.ZMin + extFwd emax = math.floor(maxz - 1.0) - E = list() + E = [] for e in range(0, len(env.Edges)): emin = env.Edges[e].BoundBox.ZMin if emin > emax: @@ -1137,26 +1204,22 @@ def getSliceFromEnvelope(env): return tf - - def _prepareModelSTLs(self, JOB, obj, m, ocl): """Tessellate model shapes or copy existing meshes into ocl.STLSurf objects""" - PathLog.debug('_prepareModelSTLs()') if self.modelSTLs[m] is True: model = JOB.Model.Group[m] - if self.modelSTLs[m] is True: - self.modelSTLs[m] = _makeSTL(model, obj, ocl, self.modelTypes[m]) + self.modelSTLs[m] = _makeSTL(model, obj, ocl, self.modelTypes[m]) def _makeSafeSTL(self, JOB, obj, mdlIdx, faceShapes, voidShapes, ocl): - '''_makeSafeSTL(JOB, obj, mdlIdx, faceShapes, voidShapes)... + """_makeSafeSTL(JOB, obj, mdlIdx, faceShapes, voidShapes)... Creates and OCL.stl object with combined data with waste stock, model, and avoided faces. Travel lines can be checked against this - STL object to determine minimum travel height to clear stock and model.''' - PathLog.debug('_makeSafeSTL()') + STL object to determine minimum travel height to clear stock and model.""" + PathLog.debug("_makeSafeSTL()") - fuseShapes = list() + fuseShapes = [] Mdl = JOB.Model.Group[mdlIdx] mBB = Mdl.Shape.BoundBox sBB = JOB.Stock.Shape.BoundBox @@ -1164,23 +1227,29 @@ def _makeSafeSTL(self, JOB, obj, mdlIdx, faceShapes, voidShapes, ocl): # add Model shape to safeSTL shape fuseShapes.append(Mdl.Shape) - if obj.BoundBox == 'BaseBoundBox': + if obj.BoundBox == "BaseBoundBox": cont = False - extFwd = (sBB.ZLength) + extFwd = sBB.ZLength zmin = mBB.ZMin zmax = mBB.ZMin + extFwd stpDwn = (zmax - zmin) / 4.0 - dep_par = PathUtils.depth_params(zmax + 5.0, zmax + 3.0, zmax, stpDwn, 0.0, zmin) + dep_par = PathUtils.depth_params( + zmax + 5.0, zmax + 3.0, zmax, stpDwn, 0.0, zmin + ) try: - envBB = PathUtils.getEnvelope(partshape=Mdl.Shape, depthparams=dep_par) # Produces .Shape + envBB = PathUtils.getEnvelope( + partshape=Mdl.Shape, depthparams=dep_par + ) # Produces .Shape cont = True except Exception as ee: PathLog.error(str(ee)) shell = Mdl.Shape.Shells[0] solid = Part.makeSolid(shell) try: - envBB = PathUtils.getEnvelope(partshape=solid, depthparams=dep_par) # Produces .Shape + envBB = PathUtils.getEnvelope( + partshape=solid, depthparams=dep_par + ) # Produces .Shape cont = True except Exception as eee: PathLog.error(str(eee)) @@ -1189,15 +1258,16 @@ def _makeSafeSTL(self, JOB, obj, mdlIdx, faceShapes, voidShapes, ocl): stckWst = JOB.Stock.Shape.cut(envBB) if obj.BoundaryAdjustment > 0.0: cmpndFS = Part.makeCompound(faceShapes) - baBB = PathUtils.getEnvelope(partshape=cmpndFS, depthparams=self.depthParams) # Produces .Shape + baBB = PathUtils.getEnvelope( + partshape=cmpndFS, depthparams=self.depthParams + ) # Produces .Shape adjStckWst = stckWst.cut(baBB) else: adjStckWst = stckWst fuseShapes.append(adjStckWst) else: - msg = translate('PathSurfaceSupport', - 'Path transitions might not avoid the model. Verify paths.') - FreeCAD.Console.PrintWarning(msg + '\n') + msg = "Path transitions might not avoid the model. Verify paths.\n" + FreeCAD.Console.PrintWarning(msg) else: # If boundbox is Job.Stock, add hidden pad under stock as base plate toolDiam = self.cutter.getDiameter() @@ -1213,13 +1283,15 @@ def _makeSafeSTL(self, JOB, obj, mdlIdx, faceShapes, voidShapes, ocl): if voidShapes: voidComp = Part.makeCompound(voidShapes) - voidEnv = PathUtils.getEnvelope(partshape=voidComp, depthparams=self.depthParams) # Produces .Shape + voidEnv = PathUtils.getEnvelope( + partshape=voidComp, depthparams=self.depthParams + ) # Produces .Shape fuseShapes.append(voidEnv) fused = Part.makeCompound(fuseShapes) if self.showDebugObjects: - T = FreeCAD.ActiveDocument.addObject('Part::Feature', 'safeSTLShape') + T = FreeCAD.ActiveDocument.addObject("Part::Feature", "safeSTLShape") T.Shape = fused T.purgeTouched() self.tempGroup.addObject(T) @@ -1231,35 +1303,37 @@ def _makeSTL(model, obj, ocl, model_type=None): """Convert a mesh or shape into an OCL STL, using the tessellation tolerance specified in obj.LinearDeflection. Returns an ocl.STLSurf().""" - if model_type == 'M': + if model_type == "M": facets = model.Mesh.Facets.Points else: - if hasattr(model, 'Shape'): + if hasattr(model, "Shape"): shape = model.Shape else: shape = model - vertices, facet_indices = shape.tessellate( - obj.LinearDeflection.Value) - facets = ((vertices[f[0]], vertices[f[1]], vertices[f[2]]) - for f in facet_indices) + vertices, facet_indices = shape.tessellate(obj.LinearDeflection.Value) + facets = ( + (vertices[f[0]], vertices[f[1]], vertices[f[2]]) for f in facet_indices + ) stl = ocl.STLSurf() for tri in facets: v1, v2, v3 = tri - t = ocl.Triangle(ocl.Point(v1[0], v1[1], v1[2]), - ocl.Point(v2[0], v2[1], v2[2]), - ocl.Point(v3[0], v3[1], v3[2])) + t = ocl.Triangle( + ocl.Point(v1[0], v1[1], v1[2]), + ocl.Point(v2[0], v2[1], v2[2]), + ocl.Point(v3[0], v3[1], v3[2]), + ) stl.addTriangle(t) return stl # Functions to convert path geometry into line/arc segments for OCL input or directly to g-code def pathGeomToLinesPointSet(self, obj, compGeoShp): - '''pathGeomToLinesPointSet(self, obj, compGeoShp)... - Convert a compound set of sequential line segments to directionally-oriented collinear groupings.''' - PathLog.debug('pathGeomToLinesPointSet()') - # Extract intersection line segments for return value as list() - LINES = list() - inLine = list() + """pathGeomToLinesPointSet(self, obj, compGeoShp)... + Convert a compound set of sequential line segments to directionally-oriented collinear groupings.""" + PathLog.debug("pathGeomToLinesPointSet()") + # Extract intersection line segments for return value as [] + LINES = [] + inLine = [] chkGap = False lnCnt = 0 ec = len(compGeoShp.Edges) @@ -1288,14 +1362,14 @@ def pathGeomToLinesPointSet(self, obj, compGeoShp): # iC = sp.isOnLineSegment(ep, cp) iC = cp.isOnLineSegment(sp, ep) if iC is True: - inLine.append('BRK') + inLine.append("BRK") chkGap = True else: if self.CutClimb is True: inLine.reverse() LINES.append(inLine) # Save inLine segments lnCnt += 1 - inLine = list() # reset collinear container + inLine = [] # reset collinear container if self.CutClimb is True: sp = cp # FreeCAD.Vector(v1[0], v1[1], 0.0) else: @@ -1315,7 +1389,12 @@ def pathGeomToLinesPointSet(self, obj, compGeoShp): if chkGap: if gap < obj.GapThreshold.Value: inLine.pop() # pop off 'BRK' marker - (vA, vB) = inLine.pop() # pop off previous line segment for combining with current + ( + vA, + vB, + ) = ( + inLine.pop() + ) # pop off previous line segment for combining with current tup = (vA, tup[1]) self.closedGap = True else: @@ -1335,9 +1414,9 @@ def pathGeomToLinesPointSet(self, obj, compGeoShp): if obj.CutPatternReversed is True: if cpa != 0.0 and cpa % 90.0 == 0.0: F = LINES.pop(0) - rev = list() + rev = [] for iL in F: - if iL == 'BRK': + if iL == "BRK": rev.append(iL) else: (p1, p2) = iL @@ -1347,20 +1426,21 @@ def pathGeomToLinesPointSet(self, obj, compGeoShp): isEven = lnCnt % 2 if isEven == 0: - PathLog.debug('Line count is ODD: {}.'.format(lnCnt)) + PathLog.debug("Line count is ODD: {}.".format(lnCnt)) else: - PathLog.debug('Line count is even: {}.'.format(lnCnt)) + PathLog.debug("Line count is even: {}.".format(lnCnt)) return LINES + def pathGeomToZigzagPointSet(self, obj, compGeoShp): - '''_pathGeomToZigzagPointSet(self, obj, compGeoShp)... + """_pathGeomToZigzagPointSet(self, obj, compGeoShp)... Convert a compound set of sequential line segments to directionally-oriented collinear groupings - with a ZigZag directional indicator included for each collinear group.''' - PathLog.debug('_pathGeomToZigzagPointSet()') - # Extract intersection line segments for return value as list() - LINES = list() - inLine = list() + with a ZigZag directional indicator included for each collinear group.""" + PathLog.debug("_pathGeomToZigzagPointSet()") + # Extract intersection line segments for return value as [] + LINES = [] + inLine = [] lnCnt = 0 chkGap = False ec = len(compGeoShp.Edges) @@ -1391,7 +1471,7 @@ def pathGeomToZigzagPointSet(self, obj, compGeoShp): ep = FreeCAD.Vector(v2[0], v2[1], 0.0) # end point iC = cp.isOnLineSegment(sp, ep) if iC: - inLine.append('BRK') + inLine.append("BRK") chkGap = True gap = abs(self.toolDiam - lst.sub(cp).Length) else: @@ -1401,7 +1481,7 @@ def pathGeomToZigzagPointSet(self, obj, compGeoShp): LINES.append(inLine) lnCnt += 1 dirFlg = -1 * dirFlg # Change zig to zag - inLine = list() # reset collinear container + inLine = [] # reset collinear container sp = cp # FreeCAD.Vector(v1[0], v1[1], 0.0) lst = ep @@ -1413,7 +1493,12 @@ def pathGeomToZigzagPointSet(self, obj, compGeoShp): if chkGap: if gap < obj.GapThreshold.Value: inLine.pop() # pop off 'BRK' marker - (vA, vB) = inLine.pop() # pop off previous line segment for combining with current + ( + vA, + vB, + ) = ( + inLine.pop() + ) # pop off previous line segment for combining with current if dirFlg == 1: tup = (vA, tup[1]) else: @@ -1431,9 +1516,9 @@ def pathGeomToZigzagPointSet(self, obj, compGeoShp): # Fix directional issue with LAST line when line count is even isEven = lnCnt % 2 if isEven == 0: # Changed to != with 90 degree CutPatternAngle - PathLog.debug('Line count is even: {}.'.format(lnCnt)) + PathLog.debug("Line count is even: {}.".format(lnCnt)) else: - PathLog.debug('Line count is ODD: {}.'.format(lnCnt)) + PathLog.debug("Line count is ODD: {}.".format(lnCnt)) dirFlg = -1 * dirFlg if not obj.CutPatternReversed: if self.CutClimb: @@ -1444,9 +1529,9 @@ def pathGeomToZigzagPointSet(self, obj, compGeoShp): # Handle last inLine list if dirFlg == 1: - rev = list() + rev = [] for iL in inLine: - if iL == 'BRK': + if iL == "BRK": rev.append(iL) else: (p1, p2) = iL @@ -1455,9 +1540,9 @@ def pathGeomToZigzagPointSet(self, obj, compGeoShp): if not obj.CutPatternReversed: rev.reverse() else: - rev2 = list() + rev2 = [] for iL in rev: - if iL == 'BRK': + if iL == "BRK": rev2.append(iL) else: (p1, p2) = iL @@ -1470,22 +1555,23 @@ def pathGeomToZigzagPointSet(self, obj, compGeoShp): return LINES + def pathGeomToCircularPointSet(self, obj, compGeoShp): - '''pathGeomToCircularPointSet(self, obj, compGeoShp)... + """pathGeomToCircularPointSet(self, obj, compGeoShp)... Convert a compound set of arcs/circles to a set of directionally-oriented arc end points - and the corresponding center point.''' - # Extract intersection line segments for return value as list() - PathLog.debug('pathGeomToCircularPointSet()') - ARCS = list() - stpOvrEI = list() - segEI = list() + and the corresponding center point.""" + # Extract intersection line segments for return value as [] + PathLog.debug("pathGeomToCircularPointSet()") + ARCS = [] + stpOvrEI = [] + segEI = [] isSame = False sameRad = None ec = len(compGeoShp.Edges) def gapDist(sp, ep): - X = (ep[0] - sp[0])**2 - Y = (ep[1] - sp[1])**2 + X = (ep[0] - sp[0]) ** 2 + Y = (ep[1] - sp[1]) ** 2 return math.sqrt(X + Y) # the 'z' value is zero in both points def dist_to_cent(item): @@ -1508,7 +1594,7 @@ def pathGeomToCircularPointSet(self, obj, compGeoShp): for ei in range(0, ec): edg = compGeoShp.Edges[ei] if edg.Closed is True: - stpOvrEI.append(('L', ei, False)) + stpOvrEI.append(("L", ei, False)) else: if isSame is False: segEI.append(ei) @@ -1525,7 +1611,7 @@ def pathGeomToCircularPointSet(self, obj, compGeoShp): segEI.append(ei) else: # Move co-radial arc segments - stpOvrEI.append(['A', segEI, False]) + stpOvrEI.append(["A", segEI, False]) # Start new list of arc segments segEI = [ei] isSame = True @@ -1533,14 +1619,14 @@ def pathGeomToCircularPointSet(self, obj, compGeoShp): sameRad = pnt.sub(self.tmpCOM).Length # Process trailing `segEI` data, if available if isSame is True: - stpOvrEI.append(['A', segEI, False]) + stpOvrEI.append(["A", segEI, False]) # Identify adjacent arcs with y=0 start/end points that connect for so in range(0, len(stpOvrEI)): SO = stpOvrEI[so] - if SO[0] == 'A': - startOnAxis = list() - endOnAxis = list() + if SO[0] == "A": + startOnAxis = [] + endOnAxis = [] EI = SO[1] # list of corresponding compGeoShp.Edges indexes # Identify startOnAxis and endOnAxis arcs @@ -1581,19 +1667,23 @@ def pathGeomToCircularPointSet(self, obj, compGeoShp): # Cycle through stepOver data for so in range(0, len(stpOvrEI)): SO = stpOvrEI[so] - if SO[0] == 'L': # L = Loop/Ring/Circle + if SO[0] == "L": # L = Loop/Ring/Circle # PathLog.debug("SO[0] == 'Loop'") lei = SO[1] # loop Edges index v1 = compGeoShp.Edges[lei].Vertexes[0] # space = obj.SampleInterval.Value / 10.0 # space = 0.000001 - space = self.toolDiam * 0.005 # If too small, OCL will fail to scan the loop + space = ( + self.toolDiam * 0.005 + ) # If too small, OCL will fail to scan the loop # p1 = FreeCAD.Vector(v1.X, v1.Y, v1.Z) - p1 = FreeCAD.Vector(v1.X, v1.Y, 0.0) # z=0.0 for waterline; z=v1.Z for 3D Surface + p1 = FreeCAD.Vector( + v1.X, v1.Y, 0.0 + ) # z=0.0 for waterline; z=v1.Z for 3D Surface rad = p1.sub(self.tmpCOM).Length - spcRadRatio = space/rad + spcRadRatio = space / rad if spcRadRatio < 1.0: tolrncAng = math.asin(spcRadRatio) else: @@ -1606,11 +1696,15 @@ def pathGeomToCircularPointSet(self, obj, compGeoShp): if dirFlg == 1: arc = (sp, ep, cp) else: - arc = (ep, sp, cp) # OCL.Arc(firstPnt, lastPnt, centerPnt, dir=True(CCW direction)) - ARCS.append(('L', dirFlg, [arc])) - elif SO[0] == 'A': # A = Arc + arc = ( + ep, + sp, + cp, + ) # OCL.Arc(firstPnt, lastPnt, centerPnt, dir=True(CCW direction)) + ARCS.append(("L", dirFlg, [arc])) + elif SO[0] == "A": # A = Arc # PathLog.debug("SO[0] == 'Arc'") - PRTS = list() + PRTS = [] EI = SO[1] # list of corresponding Edges indexes CONN = SO[2] # list of corresponding connected edges tuples (iE, iS) chkGap = False @@ -1626,7 +1720,11 @@ def pathGeomToCircularPointSet(self, obj, compGeoShp): arc = (sp, ep, cp) lst = ep else: - arc = (ep, sp, cp) # OCL.Arc(firstPnt, lastPnt, centerPnt, dir=True(CCW direction)) + arc = ( + ep, + sp, + cp, + ) # OCL.Arc(firstPnt, lastPnt, centerPnt, dir=True(CCW direction)) lst = sp PRTS.append(arc) # Pop connected edge index values from arc segments index list @@ -1639,12 +1737,12 @@ def pathGeomToCircularPointSet(self, obj, compGeoShp): EI.pop(iSi) EI.pop(iEi) if len(EI) > 0: - PRTS.append('BRK') + PRTS.append("BRK") chkGap = True cnt = 0 for ei in EI: if cnt > 0: - PRTS.append('BRK') + PRTS.append("BRK") chkGap = True v1 = compGeoShp.Edges[ei].Vertexes[0] v2 = compGeoShp.Edges[ei].Vertexes[1] @@ -1653,17 +1751,31 @@ def pathGeomToCircularPointSet(self, obj, compGeoShp): if dirFlg == 1: arc = (sp, ep, cp) if chkGap: - gap = abs(self.toolDiam - gapDist(lst, sp)) # abs(self.toolDiam - lst.sub(sp).Length) + gap = abs( + self.toolDiam - gapDist(lst, sp) + ) # abs(self.toolDiam - lst.sub(sp).Length) lst = ep else: - arc = (ep, sp, cp) # OCL.Arc(firstPnt, lastPnt, centerPnt, dir=True(CCW direction)) + arc = ( + ep, + sp, + cp, + ) # OCL.Arc(firstPnt, lastPnt, centerPnt, dir=True(CCW direction)) if chkGap: - gap = abs(self.toolDiam - gapDist(lst, ep)) # abs(self.toolDiam - lst.sub(ep).Length) + gap = abs( + self.toolDiam - gapDist(lst, ep) + ) # abs(self.toolDiam - lst.sub(ep).Length) lst = sp if chkGap: if gap < obj.GapThreshold.Value: PRTS.pop() # pop off 'BRK' marker - (vA, vB, vC) = PRTS.pop() # pop off previous arc segment for combining with current + ( + vA, + vB, + vC, + ) = ( + PRTS.pop() + ) # pop off previous arc segment for combining with current arc = (vA, arc[1], vC) self.closedGap = True else: @@ -1677,9 +1789,9 @@ def pathGeomToCircularPointSet(self, obj, compGeoShp): if dirFlg == -1: PRTS.reverse() - ARCS.append(('A', dirFlg, PRTS)) + ARCS.append(("A", dirFlg, PRTS)) # Eif - if obj.CutPattern == 'CircularZigZag': + if obj.CutPattern == "CircularZigZag": dirFlg = -1 * dirFlg # Efor @@ -1687,31 +1799,40 @@ def pathGeomToCircularPointSet(self, obj, compGeoShp): return ARCS + def pathGeomToSpiralPointSet(obj, compGeoShp): - '''_pathGeomToSpiralPointSet(obj, compGeoShp)... - Convert a compound set of sequential line segments to directional, connected groupings.''' - PathLog.debug('_pathGeomToSpiralPointSet()') - # Extract intersection line segments for return value as list() - LINES = list() - inLine = list() + """_pathGeomToSpiralPointSet(obj, compGeoShp)... + Convert a compound set of sequential line segments to directional, connected groupings.""" + PathLog.debug("_pathGeomToSpiralPointSet()") + # Extract intersection line segments for return value as [] + LINES = [] + inLine = [] lnCnt = 0 ec = len(compGeoShp.Edges) start = 2 if obj.CutPatternReversed: - edg1 = compGeoShp.Edges[0] # Skip first edge, as it is the closing edge: center to outer tail + edg1 = compGeoShp.Edges[ + 0 + ] # Skip first edge, as it is the closing edge: center to outer tail ec -= 1 start = 1 else: - edg1 = compGeoShp.Edges[1] # Skip first edge, as it is the closing edge: center to outer tail + edg1 = compGeoShp.Edges[ + 1 + ] # Skip first edge, as it is the closing edge: center to outer tail p1 = FreeCAD.Vector(edg1.Vertexes[0].X, edg1.Vertexes[0].Y, 0.0) p2 = FreeCAD.Vector(edg1.Vertexes[1].X, edg1.Vertexes[1].Y, 0.0) tup = ((p1.x, p1.y), (p2.x, p2.y)) inLine.append(tup) - for ei in range(start, ec): # Skipped first edge, started with second edge above as edg1 + for ei in range( + start, ec + ): # Skipped first edge, started with second edge above as edg1 edg = compGeoShp.Edges[ei] # Get edge for vertexes - sp = FreeCAD.Vector(edg.Vertexes[0].X, edg.Vertexes[0].Y, 0.0) # check point (first / middle point) + sp = FreeCAD.Vector( + edg.Vertexes[0].X, edg.Vertexes[0].Y, 0.0 + ) # check point (first / middle point) ep = FreeCAD.Vector(edg.Vertexes[1].X, edg.Vertexes[1].Y, 0.0) # end point tup = ((sp.x, sp.y), (ep.x, ep.y)) @@ -1720,7 +1841,7 @@ def pathGeomToSpiralPointSet(obj, compGeoShp): else: LINES.append(inLine) # Save inLine segments lnCnt += 1 - inLine = list() # reset container + inLine = [] # reset container inLine.append(tup) # p1 = sp p2 = ep @@ -1731,12 +1852,13 @@ def pathGeomToSpiralPointSet(obj, compGeoShp): return LINES -def pathGeomToOffsetPointSet(obj, compGeoShp): - '''pathGeomToOffsetPointSet(obj, compGeoShp)... - Convert a compound set of 3D profile segmented wires to 2D segments, applying linear optimization.''' - PathLog.debug('pathGeomToOffsetPointSet()') - LINES = list() +def pathGeomToOffsetPointSet(obj, compGeoShp): + """pathGeomToOffsetPointSet(obj, compGeoShp)... + Convert a compound set of 3D profile segmented wires to 2D segments, applying linear optimization.""" + PathLog.debug("pathGeomToOffsetPointSet()") + + LINES = [] optimize = obj.OptimizeLinearPaths ofstCnt = len(compGeoShp) @@ -1747,7 +1869,7 @@ def pathGeomToOffsetPointSet(obj, compGeoShp): lenOS = len(OS) if ei > 0: - LINES.append('BRK') + LINES.append("BRK") fp = FreeCAD.Vector(OS[0].x, OS[0].y, OS[0].z) OS.append(fp) @@ -1781,22 +1903,22 @@ def pathGeomToOffsetPointSet(obj, compGeoShp): class FindUnifiedRegions: - '''FindUnifiedRegions() This class requires a list of face shapes. - It finds the unified horizontal unified regions, if they exist.''' + """FindUnifiedRegions() This class requires a list of face shapes. + It finds the unified horizontal unified regions, if they exist.""" def __init__(self, facesList, geomToler): self.FACES = facesList # format is tuple (faceShape, faceIndex_on_base) self.geomToler = geomToler self.tempGroup = None - self.topFaces = list() - self.edgeData = list() - self.circleData = list() + self.topFaces = [] + self.edgeData = [] + self.circleData = [] self.noSharedEdges = True - self.topWires = list() - self.REGIONS = list() - self.INTERNALS = list() - self.idGroups = list() - self.sharedEdgeIdxs = list() + self.topWires = [] + self.REGIONS = [] + self.INTERNALS = [] + self.idGroups = [] + self.sharedEdgeIdxs = [] self.fusedFaces = None self.internalsReady = False @@ -1806,7 +1928,7 @@ class FindUnifiedRegions: # Internal processing methods def _showShape(self, shape, name): if self.tempGroup: - S = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmp' + name) + S = FreeCAD.ActiveDocument.addObject("Part::Feature", "tmp" + name) S.Shape = shape S.purgeTouched() self.tempGroup.addObject(S) @@ -1830,7 +1952,11 @@ class FindUnifiedRegions: base = ef.cut(cutBox) if base.Volume == 0: - PathLog.debug('Ignoring Face{}. It is likely vertical with no horizontal exposure.'.format(fcIdx)) + PathLog.debug( + "Ignoring Face{}. It is likely vertical with no horizontal exposure.".format( + fcIdx + ) + ) cont = False if cont: @@ -1861,9 +1987,10 @@ class FindUnifiedRegions: tfBB_Area = tfBB.XLength * tfBB.YLength # self._showShape(topFace, 'topFaceAlt_2_{}'.format(fNum)) if tfBB_Area < (fBB_Area * 0.9): - msg = translate('PathSurfaceSupport', - 'Faild to extract processing region for Face') - FreeCAD.Console.PrintError(msg + '{}.\n'.format(fNum)) + msg = "Faild to extract processing region for Face {}\n".format( + fNum + ) + FreeCAD.Console.PrintError(msg) cont = False # Eif @@ -1899,9 +2026,9 @@ class FindUnifiedRegions: count[1] += 1 def _groupEdgesByLength(self): - PathLog.debug('_groupEdgesByLength()') + PathLog.debug("_groupEdgesByLength()") threshold = self.geomToler - grp = list() + grp = [] processLast = False def keyFirst(tup): @@ -1937,7 +2064,7 @@ class FindUnifiedRegions: if len(grp) > 1: # grp.sort() self.idGroups.append(grp) - grp = list() + grp = [] break # Ewhile # Ewhile @@ -1947,8 +2074,8 @@ class FindUnifiedRegions: self.idGroups.append(grp) def _identifySharedEdgesByLength(self, grp): - PathLog.debug('_identifySharedEdgesByLength()') - holds = list() + PathLog.debug("_identifySharedEdgesByLength()") + holds = [] specialIndexes = [] threshold = self.geomToler @@ -1996,7 +2123,7 @@ class FindUnifiedRegions: holds.extend(grp) grp = holds lenGrp = len(grp) - holds = list() + holds = [] if len(specialIndexes) > 0: # Remove shared edges from EDGES data @@ -2005,15 +2132,15 @@ class FindUnifiedRegions: self.noSharedEdges = False def _extractWiresFromEdges(self): - PathLog.debug('_extractWiresFromEdges()') + PathLog.debug("_extractWiresFromEdges()") DATA = self.edgeData - holds = list() + holds = [] firstEdge = None cont = True connectedEdges = [] connectedIndexes = [] connectedCnt = 0 - LOOPS = list() + LOOPS = [] def faceIndex(tup): return tup[3] @@ -2094,7 +2221,7 @@ class FindUnifiedRegions: holds.extend(indexes) indexes = holds idxCnt = len(indexes) - holds = list() + holds = [] if idxCnt == 0: cont = False if safety == 0: @@ -2102,11 +2229,11 @@ class FindUnifiedRegions: # Ewhile numLoops = len(LOOPS) - PathLog.debug(' -numLoops: {}.'.format(numLoops)) + PathLog.debug(" -numLoops: {}.".format(numLoops)) if numLoops > 0: for li in range(0, numLoops): Edges = LOOPS[li] - #for e in Edges: + # for e in Edges: # self._showShape(e, 'Loop_{}_Edge'.format(li)) wire = Part.Wire(Part.__sortEdges__(Edges)) if wire.isClosed(): @@ -2119,16 +2246,20 @@ class FindUnifiedRegions: # closed wires mentioned above. extWire = wire.extrude(FreeCAD.Vector(0.0, 0.0, 2.0)) wireSolid = Part.makeSolid(extWire) - extdBBFace1 = makeExtendedBoundBox(wireSolid.BoundBox, 5.0, wireSolid.BoundBox.ZMin + 1.0) - extdBBFace2 = makeExtendedBoundBox(wireSolid.BoundBox, 5.0, wireSolid.BoundBox.ZMin + 1.0) + extdBBFace1 = makeExtendedBoundBox( + wireSolid.BoundBox, 5.0, wireSolid.BoundBox.ZMin + 1.0 + ) + extdBBFace2 = makeExtendedBoundBox( + wireSolid.BoundBox, 5.0, wireSolid.BoundBox.ZMin + 1.0 + ) inverse = extdBBFace1.cut(wireSolid) face = extdBBFace2.cut(inverse) self.REGIONS.append(face) self.REGIONS.sort(key=faceArea, reverse=True) def _identifyInternalFeatures(self): - PathLog.debug('_identifyInternalFeatures()') - remList = list() + PathLog.debug("_identifyInternalFeatures()") + remList = [] for (top, fcIdx) in self.topFaces: big = Part.Face(top.OuterWire) @@ -2142,18 +2273,18 @@ class FindUnifiedRegions: remList.append(s) break else: - PathLog.debug(' - No common area.\n') + PathLog.debug(" - No common area.\n") remList.sort(reverse=True) for ri in remList: self.REGIONS.pop(ri) def _processNestedRegions(self): - PathLog.debug('_processNestedRegions()') + PathLog.debug("_processNestedRegions()") cont = True - hold = list() - Ids = list() - remList = list() + hold = [] + Ids = [] + remList = [] for i in range(0, len(self.REGIONS)): Ids.append(i) idsCnt = len(Ids) @@ -2182,7 +2313,7 @@ class FindUnifiedRegions: # Ewhile hold.extend(Ids) Ids = hold - hold = list() + hold = [] idsCnt = len(Ids) if len(Ids) == 0: cont = False @@ -2194,8 +2325,8 @@ class FindUnifiedRegions: # Accessory methods def _getCompleteCrossSection(self, shape): - PathLog.debug('_getCompleteCrossSection()') - wires = list() + PathLog.debug("_getCompleteCrossSection()") + wires = [] bb = shape.BoundBox mid = (bb.ZMin + bb.ZMax) / 2.0 @@ -2208,7 +2339,7 @@ class FindUnifiedRegions: CS.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - CS.BoundBox.ZMin)) return CS - PathLog.debug(' -No wires from .slice() method') + PathLog.debug(" -No wires from .slice() method") return False def _edgesAreConnected(self, e1, e2): @@ -2245,17 +2376,16 @@ class FindUnifiedRegions: # Public methods def setTempGroup(self, grpObj): - '''setTempGroup(grpObj)... For debugging, pass temporary object group.''' + """setTempGroup(grpObj)... For debugging, pass temporary object group.""" self.tempGroup = grpObj def getUnifiedRegions(self): - '''getUnifiedRegions()... Returns a list of unified regions from list - of tuples (faceShape, faceIndex) received at instantiation of the class object.''' - PathLog.debug('getUnifiedRegions()') + """getUnifiedRegions()... Returns a list of unified regions from list + of tuples (faceShape, faceIndex) received at instantiation of the class object.""" + PathLog.debug("getUnifiedRegions()") if len(self.FACES) == 0: - msg = translate('PathSurfaceSupport', - 'No FACE data tuples received at instantiation of class.') - FreeCAD.Console.PrintError(msg + '\n') + msg = "No FACE data tuples received at instantiation of class.\n" + FreeCAD.Console.PrintError(msg) return [] self._extractTopFaces() @@ -2266,7 +2396,7 @@ class FindUnifiedRegions: # if single topFace, return it if lenFaces == 1: topFace = self.topFaces[0][0] - self._showShape(topFace, 'TopFace') + self._showShape(topFace, "TopFace") # prepare inner wires as faces for internal features lenWrs = len(topFace.Wires) if lenWrs > 1: @@ -2282,16 +2412,18 @@ class FindUnifiedRegions: return [face] else: (faceShp, fcIdx) = self.FACES[0] - msg = translate('PathSurfaceSupport', - 'Failed to identify a horizontal cross-section for Face') - msg += '{}.\n'.format(fcIdx + 1) + msg = translate( + "PathSurfaceSupport", + "Failed to identify a horizontal cross-section for Face", + ) + msg += "{}.\n".format(fcIdx + 1) FreeCAD.Console.PrintWarning(msg) return [] # process multiple top faces, unifying if possible self._fuseTopFaces() for F in self.fusedFaces.Faces: - self._showShape(F, 'TopFaceFused') + self._showShape(F, "TopFaceFused") self._getEdgesData() self._groupEdgesByLength() @@ -2299,8 +2431,8 @@ class FindUnifiedRegions: self._identifySharedEdgesByLength(grp) if self.noSharedEdges: - PathLog.debug('No shared edges by length detected.') - allTopFaces = list() + PathLog.debug("No shared edges by length detected.") + allTopFaces = [] for (topFace, fcIdx) in self.topFaces: allTopFaces.append(topFace) # Identify internal features @@ -2327,21 +2459,20 @@ class FindUnifiedRegions: return self.REGIONS def getInternalFeatures(self): - '''getInternalFeatures()... Returns internal features identified - after calling getUnifiedRegions().''' + """getInternalFeatures()... Returns internal features identified + after calling getUnifiedRegions().""" if self.internalsReady: if len(self.INTERNALS) > 0: return self.INTERNALS else: return False - msg = translate('PathSurfaceSupport', - 'getUnifiedRegions() must be called before getInternalFeatures().') - FreeCAD.Console.PrintError(msg + '\n') + msg = "getUnifiedRegions() must be called before getInternalFeatures().\n" + FreeCAD.Console.PrintError(msg) return False -# Eclass -class OCL_Tool(): + +class OCL_Tool: """The OCL_Tool class is designed to translate a FreeCAD standard ToolBit shape, or Legacy tool type, in the active Tool Controller, into an OCL tool type.""" @@ -2364,19 +2495,21 @@ class OCL_Tool(): # Default to zero. ToolBit likely is without. self.lengthOffset = 0.0 - if hasattr(obj, 'ToolController'): - if hasattr(obj.ToolController, 'Tool'): + if hasattr(obj, "ToolController"): + if hasattr(obj.ToolController, "Tool"): self.tool = obj.ToolController.Tool - if hasattr(self.tool, 'ShapeName'): + if hasattr(self.tool, "ShapeName"): self.toolType = self.tool.ShapeName # Indicates ToolBit tool - self.toolMode = 'ToolBit' - elif hasattr(self.tool, 'ToolType'): + self.toolMode = "ToolBit" + elif hasattr(self.tool, "ToolType"): self.toolType = self.tool.ToolType # Indicates Legacy tool - self.toolMode = 'Legacy' + self.toolMode = "Legacy" if self.toolType: - PathLog.debug('OCL_Tool tool mode, type: {}, {}'.format(self.toolMode, self.toolType)) + PathLog.debug( + "OCL_Tool tool mode, type: {}, {}".format(self.toolMode, self.toolType) + ) - ''' + """ #### FreeCAD Legacy tool shape properties per tool type shape = EndMill Diameter @@ -2508,25 +2641,26 @@ class OCL_Tool(): ShankDiameter FlatHeight;Height of the flat extension of the v-bit FlatRadius; Diameter of the flat end of the tip - ''' + """ # Private methods def _setDimensions(self): - '''_setDimensions() ... Set values for possible dimensions.''' - if hasattr(self.tool, 'Diameter'): + """_setDimensions() ... Set values for possible dimensions.""" + if hasattr(self.tool, "Diameter"): self.diameter = float(self.tool.Diameter) else: - msg = translate('PathSurfaceSupport', - 'Diameter dimension missing from ToolBit shape.') - FreeCAD.Console.PrintError(msg + '\n') + msg = translate( + "PathSurfaceSupport", "Diameter dimension missing from ToolBit shape." + ) + FreeCAD.Console.PrintError(msg + "\n") return False - if hasattr(self.tool, 'LengthOffset'): + if hasattr(self.tool, "LengthOffset"): self.lengthOffset = float(self.tool.LengthOffset) - if hasattr(self.tool, 'FlatRadius'): + if hasattr(self.tool, "FlatRadius"): self.flatRadius = float(self.tool.FlatRadius) - if hasattr(self.tool, 'CuttingEdgeHeight'): + if hasattr(self.tool, "CuttingEdgeHeight"): self.cutEdgeHeight = float(self.tool.CuttingEdgeHeight) - if hasattr(self.tool, 'CuttingEdgeAngle'): + if hasattr(self.tool, "CuttingEdgeAngle"): self.cutEdgeAngle = float(self.tool.CuttingEdgeAngle) return True @@ -2542,83 +2676,85 @@ class OCL_Tool(): def _oclCylCutter(self): # Standard End Mill, Slot cutter, or Fly cutter # OCL -> CylCutter::CylCutter(diameter, length) - if (self.diameter == -1.0 or self.cutEdgeHeight == -1.0): + if self.diameter == -1.0 or self.cutEdgeHeight == -1.0: return self.oclTool = self.ocl.CylCutter( - self.diameter, - self.cutEdgeHeight + self.lengthOffset - ) + self.diameter, self.cutEdgeHeight + self.lengthOffset + ) def _oclBallCutter(self): # Standard Ball End Mill # OCL -> BallCutter::BallCutter(diameter, length) - if (self.diameter == -1.0 or self.cutEdgeHeight == -1.0): + if self.diameter == -1.0 or self.cutEdgeHeight == -1.0: return self.tiltCutter = True - if self.cutEdgeHeight==0 : self.cutEdgeHeight = self.diameter/2 + if self.cutEdgeHeight == 0: + self.cutEdgeHeight = self.diameter / 2 self.oclTool = self.ocl.BallCutter( - self.diameter, - self.cutEdgeHeight + self.lengthOffset - ) + self.diameter, self.cutEdgeHeight + self.lengthOffset + ) def _oclBullCutter(self): # Standard Bull Nose cutter # Reference: https://www.fine-tools.com/halbstabfraeser.html # OCL -> BullCutter::BullCutter(diameter, minor radius, length) - if (self.diameter == -1.0 or - self.flatRadius == -1.0 or - self.cutEdgeHeight == -1.0): + if ( + self.diameter == -1.0 + or self.flatRadius == -1.0 + or self.cutEdgeHeight == -1.0 + ): return self.oclTool = self.ocl.BullCutter( - self.diameter, - self.diameter - self.flatRadius, - self.cutEdgeHeight + self.lengthOffset - ) + self.diameter, + self.diameter - self.flatRadius, + self.cutEdgeHeight + self.lengthOffset, + ) def _oclConeCutter(self): # Engraver or V-bit cutter # OCL -> ConeCutter::ConeCutter(diameter, angle, length) - if (self.diameter == -1.0 or - self.cutEdgeAngle == -1.0 or self.cutEdgeHeight == -1.0): + if ( + self.diameter == -1.0 + or self.cutEdgeAngle == -1.0 + or self.cutEdgeHeight == -1.0 + ): return self.oclTool = self.ocl.ConeCutter( - self.diameter, - self.cutEdgeAngle/2, - self.lengthOffset - ) + self.diameter, self.cutEdgeAngle / 2, self.lengthOffset + ) def _setToolMethod(self): toolMap = dict() - if self.toolMode == 'Legacy': + if self.toolMode == "Legacy": # Set cutter details # https://www.freecadweb.org/api/dd/dfe/classPath_1_1Tool.html#details toolMap = { - 'EndMill': 'CylCutter', - 'BallEndMill': 'BallCutter', - 'SlotCutter': 'CylCutter', - 'Engraver': 'ConeCutter', - 'Drill': 'ConeCutter', - 'CounterSink': 'ConeCutter', - 'FlyCutter': 'CylCutter', - 'CenterDrill': 'None', - 'CounterBore': 'None', - 'Reamer': 'None', - 'Tap': 'None', - 'ChamferMill': 'None', - 'CornerRound': 'None' + "EndMill": "CylCutter", + "BallEndMill": "BallCutter", + "SlotCutter": "CylCutter", + "Engraver": "ConeCutter", + "Drill": "ConeCutter", + "CounterSink": "ConeCutter", + "FlyCutter": "CylCutter", + "CenterDrill": "None", + "CounterBore": "None", + "Reamer": "None", + "Tap": "None", + "ChamferMill": "None", + "CornerRound": "None", } - elif self.toolMode == 'ToolBit': + elif self.toolMode == "ToolBit": toolMap = { - 'endmill': 'CylCutter', - 'ballend': 'BallCutter', - 'bullnose': 'BullCutter', - 'drill': 'ConeCutter', - 'engraver': 'ConeCutter', - 'v-bit': 'ConeCutter', - 'chamfer': 'None' + "endmill": "CylCutter", + "ballend": "BallCutter", + "bullnose": "BullCutter", + "drill": "ConeCutter", + "engraver": "ConeCutter", + "v-bit": "ConeCutter", + "chamfer": "None", } - self.toolMethod = 'None' + self.toolMethod = "None" if self.toolType in toolMap: self.toolMethod = toolMap[self.toolType] @@ -2628,9 +2764,8 @@ class OCL_Tool(): to return OCL tool object.""" # Check for tool controller and tool object if not self.tool or not self.toolMode: - msg = translate('PathSurface', - 'Failed to identify tool for operation.') - FreeCAD.Console.PrintError(msg + '\n') + msg = translate("PathSurface", "Failed to identify tool for operation.") + FreeCAD.Console.PrintError(msg + "\n") return False if not self._setDimensions(): @@ -2638,23 +2773,25 @@ class OCL_Tool(): self._setToolMethod() - if self.toolMethod == 'None': - err = translate('PathSurface', - 'Failed to map selected tool to an OCL tool type.') - FreeCAD.Console.PrintError(err + '\n') + if self.toolMethod == "None": + err = translate( + "PathSurface", "Failed to map selected tool to an OCL tool type." + ) + FreeCAD.Console.PrintError(err + "\n") return False else: - PathLog.debug('OCL_Tool tool method: {}'.format(self.toolMethod)) - oclToolMethod = getattr(self, '_ocl' + self.toolMethod) + PathLog.debug("OCL_Tool tool method: {}".format(self.toolMethod)) + oclToolMethod = getattr(self, "_ocl" + self.toolMethod) oclToolMethod() if self.oclTool: return self.oclTool # Set error messages - err = translate('PathSurface', - 'Failed to translate active tool to OCL tool type.') - FreeCAD.Console.PrintError(err + '\n') + err = translate( + "PathSurface", "Failed to translate active tool to OCL tool type." + ) + FreeCAD.Console.PrintError(err + "\n") return False def useTiltCutter(self): @@ -2662,16 +2799,20 @@ class OCL_Tool(): to return status of cutter tilt availability - generally this is for a ball end mill.""" if not self.tool or not self.oclTool: - err = translate('PathSurface', - 'OCL tool not available. Cannot determine is cutter has tilt available.') - FreeCAD.Console.PrintError(err + '\n') + err = translate( + "PathSurface", + "OCL tool not available. Cannot determine is cutter has tilt available.", + ) + FreeCAD.Console.PrintError(err + "\n") return False return self.tiltCutter + + # Eclass # Support functions def makeExtendedBoundBox(wBB, bbBfr, zDep): - PathLog.debug('makeExtendedBoundBox()') + PathLog.debug("makeExtendedBoundBox()") p1 = FreeCAD.Vector(wBB.XMin - bbBfr, wBB.YMin - bbBfr, zDep) p2 = FreeCAD.Vector(wBB.XMax + bbBfr, wBB.YMin - bbBfr, zDep) p3 = FreeCAD.Vector(wBB.XMax + bbBfr, wBB.YMax + bbBfr, zDep) @@ -2683,5 +2824,3 @@ def makeExtendedBoundBox(wBB, bbBfr, zDep): L4 = Part.makeLine(p4, p1) return Part.Face(Part.Wire([L1, L2, L3, L4])) - - diff --git a/src/Mod/Path/PathScripts/PathWaterline.py b/src/Mod/Path/PathScripts/PathWaterline.py index aa0704042d..2f2ec32da7 100644 --- a/src/Mod/Path/PathScripts/PathWaterline.py +++ b/src/Mod/Path/PathScripts/PathWaterline.py @@ -22,6 +22,8 @@ # *************************************************************************** from __future__ import print_function +from PySide import QtCore +import FreeCAD __title__ = "Path Waterline Operation" __author__ = "russ4262 (Russell Johnson), sliptonic (Brad Collette)" @@ -29,28 +31,24 @@ __url__ = "http://www.freecadweb.org" __doc__ = "Class and implementation of Waterline operation." __contributors__ = "" -import FreeCAD -from PySide import QtCore - # OCL must be installed try: import ocl except ImportError: msg = QtCore.QCoreApplication.translate( - "PathWaterline", "This operation requires OpenCamLib to be installed." + "path_waterline", "This operation requires OpenCamLib to be installed." ) FreeCAD.Console.PrintError(msg + "\n") raise ImportError - # import sys - # sys.exit(msg) import Path import PathScripts.PathLog as PathLog -import PathScripts.PathUtils as PathUtils import PathScripts.PathOp as PathOp import PathScripts.PathSurfaceSupport as PathSurfaceSupport -import time +import PathScripts.PathUtils as PathUtils import math +import time +from PySide.QtCore import QT_TRANSLATE_NOOP # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader @@ -60,13 +58,13 @@ Part = LazyLoader("Part", globals(), "Part") if FreeCAD.GuiUp: import FreeCADGui -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# PathLog.trackModule(PathLog.thisModule()) +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) - -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) +translate = FreeCAD.Qt.translate class ObjectWaterline(PathOp.ObjectOp): @@ -83,6 +81,79 @@ class ObjectWaterline(PathOp.ObjectOp): | PathOp.FeatureBaseFaces ) + @classmethod + def propertyEnumerations(self, dataType="data"): + """propertyEnumerations(dataType="data")... return property enumeration lists of specified dataType. + Args: + dataType = 'data', 'raw', 'translated' + Notes: + 'data' is list of internal string literals used in code + 'raw' is list of (translated_text, data_string) tuples + 'translated' is list of translated string literals + """ + + # Enumeration lists for App::PropertyEnumeration properties + enums = { + "Algorithm": [ + (translate("path_waterline", "OCL Dropcutter"), "OCL Dropcutter"), + (translate("path_waterline", "Experimental"), "Experimental"), + ], + "BoundBox": [ + (translate("path_waterline", "BaseBoundBox"), "BaseBoundBox"), + (translate("path_waterline", "Stock"), "Stock"), + ], + "PatternCenterAt": [ + (translate("path_waterline", "CenterOfMass"), "CenterOfMass"), + (translate("path_waterline", "CenterOfBoundBox"), "CenterOfBoundBox"), + (translate("path_waterline", "XminYmin"), "XminYmin"), + (translate("path_waterline", "Custom"), "Custom"), + ], + "ClearLastLayer": [ + (translate("path_waterline", "Off"), "Off"), + (translate("path_waterline", "Circular"), "Circular"), + (translate("path_waterline", "CircularZigZag"), "CircularZigZag"), + (translate("path_waterline", "Line"), "Line"), + (translate("path_waterline", "Offset"), "Offset"), + (translate("path_waterline", "Spiral"), "Spiral"), + (translate("path_waterline", "ZigZag"), "ZigZag"), + ], + "CutMode": [ + (translate("path_waterline", "Conventional"), "Conventional"), + (translate("path_waterline", "Climb"), "Climb"), + ], + "CutPattern": [ + (translate("path_waterline", "None"), "None"), + (translate("path_waterline", "Circular"), "Circular"), + (translate("path_waterline", "CircularZigZag"), "CircularZigZag"), + (translate("path_waterline", "Line"), "Line"), + (translate("path_waterline", "Offset"), "Offset"), + (translate("path_waterline", "Spiral"), "Spiral"), + (translate("path_waterline", "ZigZag"), "ZigZag"), + ], + "HandleMultipleFeatures": [ + (translate("path_waterline", "Collectively"), "Collectively"), + (translate("path_waterline", "Individually"), "Individually"), + ], + "LayerMode": [ + (translate("path_waterline", "Single-pass"), "Single-pass"), + (translate("path_waterline", "Multi-pass"), "Multi-pass"), + ], + } + + if dataType == "raw": + return enums + + data = list() + idx = 0 if dataType == "translated" else 1 + + PathLog.debug(enums) + + for k, v in enumerate(enums): + data.append((v, [tup[idx] for tup in enums[v]])) + PathLog.debug(data) + + return data + def initOperation(self, obj): """initOperation(obj) ... Initialize the operation by managing property creation and property editor status.""" @@ -108,10 +179,10 @@ class ObjectWaterline(PathOp.ObjectOp): # Set enumeration lists for enumeration properties if len(self.addNewProps) > 0: - ENUMS = self.opPropertyEnumerations() + ENUMS = self.propertyEnumerations() for n in ENUMS: - if n in self.addNewProps: - setattr(obj, n, ENUMS[n]) + if n[0] in self.addNewProps: + setattr(obj, n[0], n[1]) if warn: newPropMsg = translate("PathWaterline", "New property added to") @@ -128,7 +199,7 @@ class ObjectWaterline(PathOp.ObjectOp): "App::PropertyBool", "ShowTempObjects", "Debug", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Show the temporary path construction objects when module is in DEBUG mode.", ), @@ -137,7 +208,7 @@ class ObjectWaterline(PathOp.ObjectOp): "App::PropertyDistance", "AngularDeflection", "Mesh Conversion", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Smaller values yield a finer, more accurate the mesh. Smaller values increase processing time a lot.", ), @@ -146,7 +217,7 @@ class ObjectWaterline(PathOp.ObjectOp): "App::PropertyDistance", "LinearDeflection", "Mesh Conversion", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Smaller values yield a finer, more accurate the mesh. Smaller values do not increase processing time much.", ), @@ -155,7 +226,7 @@ class ObjectWaterline(PathOp.ObjectOp): "App::PropertyInteger", "AvoidLastX_Faces", "Selected Geometry Settings", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Avoid cutting the last 'N' faces in the Base Geometry list of selected faces.", ), @@ -164,7 +235,7 @@ class ObjectWaterline(PathOp.ObjectOp): "App::PropertyBool", "AvoidLastX_InternalFeatures", "Selected Geometry Settings", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Do not cut internal features on avoided faces." ), ), @@ -172,7 +243,7 @@ class ObjectWaterline(PathOp.ObjectOp): "App::PropertyDistance", "BoundaryAdjustment", "Selected Geometry Settings", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Positive values push the cutter toward, or beyond, the boundary. Negative values retract the cutter away from the boundary.", ), @@ -181,7 +252,7 @@ class ObjectWaterline(PathOp.ObjectOp): "App::PropertyBool", "BoundaryEnforcement", "Selected Geometry Settings", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "If true, the cutter will remain inside the boundaries of the model or selected face(s).", ), @@ -190,7 +261,7 @@ class ObjectWaterline(PathOp.ObjectOp): "App::PropertyEnumeration", "HandleMultipleFeatures", "Selected Geometry Settings", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Choose how to process multiple Base Geometry features.", ), @@ -199,7 +270,7 @@ class ObjectWaterline(PathOp.ObjectOp): "App::PropertyDistance", "InternalFeaturesAdjustment", "Selected Geometry Settings", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Positive values push the cutter toward, or into, the feature. Negative values retract the cutter away from the feature.", ), @@ -208,7 +279,7 @@ class ObjectWaterline(PathOp.ObjectOp): "App::PropertyBool", "InternalFeaturesCut", "Selected Geometry Settings", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Cut internal feature areas within a larger selected face.", ), @@ -217,7 +288,7 @@ class ObjectWaterline(PathOp.ObjectOp): "App::PropertyEnumeration", "Algorithm", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Select the algorithm to use: OCL Dropcutter*, or Experimental (Not OCL based).", ), @@ -226,7 +297,7 @@ class ObjectWaterline(PathOp.ObjectOp): "App::PropertyEnumeration", "BoundBox", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Select the overall boundary for the operation." ), ), @@ -234,7 +305,7 @@ class ObjectWaterline(PathOp.ObjectOp): "App::PropertyEnumeration", "ClearLastLayer", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Set to clear last layer in a `Multi-pass` operation.", ), @@ -243,7 +314,7 @@ class ObjectWaterline(PathOp.ObjectOp): "App::PropertyEnumeration", "CutMode", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Set the direction for the cutting tool to engage the material: Climb (ClockWise) or Conventional (CounterClockWise)", ), @@ -252,7 +323,7 @@ class ObjectWaterline(PathOp.ObjectOp): "App::PropertyEnumeration", "CutPattern", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Set the geometric clearing pattern to use for the operation.", ), @@ -261,7 +332,7 @@ class ObjectWaterline(PathOp.ObjectOp): "App::PropertyFloat", "CutPatternAngle", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "The yaw angle used for certain clearing patterns" ), ), @@ -269,7 +340,7 @@ class ObjectWaterline(PathOp.ObjectOp): "App::PropertyBool", "CutPatternReversed", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Reverse the cut order of the stepover paths. For circular cut patterns, begin at the outside and work toward the center.", ), @@ -278,7 +349,7 @@ class ObjectWaterline(PathOp.ObjectOp): "App::PropertyDistance", "DepthOffset", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Set the Z-axis depth offset from the target surface.", ), @@ -287,7 +358,7 @@ class ObjectWaterline(PathOp.ObjectOp): "App::PropertyDistance", "IgnoreOuterAbove", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Ignore outer waterlines above this height." ), ), @@ -295,7 +366,7 @@ class ObjectWaterline(PathOp.ObjectOp): "App::PropertyEnumeration", "LayerMode", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Complete the operation in a single pass at depth, or mulitiple passes to final depth.", ), @@ -304,7 +375,7 @@ class ObjectWaterline(PathOp.ObjectOp): "App::PropertyVectorDistance", "PatternCenterCustom", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Set the start point for the cut pattern." ), ), @@ -312,7 +383,7 @@ class ObjectWaterline(PathOp.ObjectOp): "App::PropertyEnumeration", "PatternCenterAt", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Choose location of the center point for starting the cut pattern.", ), @@ -321,7 +392,7 @@ class ObjectWaterline(PathOp.ObjectOp): "App::PropertyDistance", "SampleInterval", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Set the sampling resolution. Smaller values quickly increase processing time.", ), @@ -330,7 +401,7 @@ class ObjectWaterline(PathOp.ObjectOp): "App::PropertyFloat", "StepOver", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Set the stepover percentage, based on the tool's diameter.", ), @@ -339,7 +410,7 @@ class ObjectWaterline(PathOp.ObjectOp): "App::PropertyBool", "OptimizeLinearPaths", "Optimization", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-Code output.", ), @@ -348,7 +419,7 @@ class ObjectWaterline(PathOp.ObjectOp): "App::PropertyBool", "OptimizeStepOverTransitions", "Optimization", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Enable separate optimization of transitions between, and breaks within, each step over path.", ), @@ -357,7 +428,7 @@ class ObjectWaterline(PathOp.ObjectOp): "App::PropertyDistance", "GapThreshold", "Optimization", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Collinear and co-radial artifact gaps that are smaller than this threshold are closed in the path.", ), @@ -366,7 +437,7 @@ class ObjectWaterline(PathOp.ObjectOp): "App::PropertyString", "GapSizes", "Optimization", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Feedback: three smallest gaps identified in the path geometry.", ), @@ -375,7 +446,7 @@ class ObjectWaterline(PathOp.ObjectOp): "App::PropertyVectorDistance", "StartPoint", "Start Point", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "The custom start point for the path of this operation", ), @@ -384,46 +455,12 @@ class ObjectWaterline(PathOp.ObjectOp): "App::PropertyBool", "UseStartPoint", "Start Point", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Make True, if specifying a Start Point" ), ), ] - def opPropertyEnumerations(self): - # Enumeration lists for App::PropertyEnumeration properties - return { - "Algorithm": ["OCL Dropcutter", "Experimental"], - "BoundBox": ["BaseBoundBox", "Stock"], - "PatternCenterAt": [ - "CenterOfMass", - "CenterOfBoundBox", - "XminYmin", - "Custom", - ], - "ClearLastLayer": [ - "Off", - "Circular", - "CircularZigZag", - "Line", - "Offset", - "Spiral", - "ZigZag", - ], - "CutMode": ["Conventional", "Climb"], - "CutPattern": [ - "None", - "Circular", - "CircularZigZag", - "Line", - "Offset", - "Spiral", - "ZigZag", - ], # Additional goals ['Offset', 'Spiral', 'ZigZagOffset', 'Grid', 'Triangle'] - "HandleMultipleFeatures": ["Collectively", "Individually"], - "LayerMode": ["Single-pass", "Multi-pass"], - } - def opPropertyDefaults(self, obj, job): """opPropertyDefaults(obj, job) ... returns a dictionary of default values for the operation's properties.""" @@ -543,15 +580,16 @@ class ObjectWaterline(PathOp.ObjectOp): obj.setEditorMode("ShowTempObjects", mode) # Repopulate enumerations in case of changes - ENUMS = self.opPropertyEnumerations() + + ENUMS = self.propertyEnumerations() for n in ENUMS: restore = False - if hasattr(obj, n): - val = obj.getPropertyByName(n) + if hasattr(obj, n[0]): + val = obj.getPropertyByName(n[0]) restore = True - setattr(obj, n, ENUMS[n]) + setattr(obj, n[0], n[1]) if restore: - setattr(obj, n, val) + setattr(obj, n[0], val) self.setEditorProperties(obj) diff --git a/src/Mod/Path/PathScripts/PathWaterlineGui.py b/src/Mod/Path/PathScripts/PathWaterlineGui.py index 52a5e04a37..ed8548051b 100644 --- a/src/Mod/Path/PathScripts/PathWaterlineGui.py +++ b/src/Mod/Path/PathScripts/PathWaterlineGui.py @@ -21,70 +21,97 @@ # * * # *************************************************************************** +from PySide import QtCore +from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import FreeCADGui -import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts.PathWaterline as PathWaterline +import PathScripts.PathLog as PathLog import PathScripts.PathGui as PathGui import PathScripts.PathOpGui as PathOpGui - -from PySide import QtCore +import PathScripts.PathWaterline as PathWaterline __title__ = "Path Waterline Operation UI" __author__ = "sliptonic (Brad Collette), russ4262 (Russell Johnson)" __url__ = "http://www.freecadweb.org" __doc__ = "Waterline operation page controller and command implementation." +translate = FreeCAD.Qt.translate + +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + class TaskPanelOpPage(PathOpGui.TaskPanelPage): - '''Page controller class for the Waterline operation.''' + """Page controller class for the Waterline operation.""" def initPage(self, obj): self.setTitle("Waterline - " + obj.Label) self.updateVisibility() def getForm(self): - '''getForm() ... returns UI''' - return FreeCADGui.PySideUic.loadUi(":/panels/PageOpWaterlineEdit.ui") + """getForm() ... returns UI""" + form = FreeCADGui.PySideUic.loadUi(":/panels/PageOpWaterlineEdit.ui") + comboToPropertyMap = [ + ("algorithmSelect", "Algorithm"), + ("boundBoxSelect", "BoundBox"), + ("layerMode", "LayerMode"), + ("cutPattern", "CutPattern"), + ] + enumTups = PathWaterline.ObjectWaterline.propertyEnumerations(dataType="raw") + PathGui.populateCombobox(form, enumTups, comboToPropertyMap) + return form def getFields(self, obj): - '''getFields(obj) ... transfers values from UI to obj's proprties''' + """getFields(obj) ... transfers values from UI to obj's proprties""" self.updateToolController(obj, self.form.toolController) self.updateCoolant(obj, self.form.coolantController) - if obj.Algorithm != str(self.form.algorithmSelect.currentText()): - obj.Algorithm = str(self.form.algorithmSelect.currentText()) + if obj.Algorithm != str(self.form.algorithmSelect.currentData()): + obj.Algorithm = str(self.form.algorithmSelect.currentData()) - if obj.BoundBox != str(self.form.boundBoxSelect.currentText()): - obj.BoundBox = str(self.form.boundBoxSelect.currentText()) + if obj.BoundBox != str(self.form.boundBoxSelect.currentData()): + obj.BoundBox = str(self.form.boundBoxSelect.currentData()) - if obj.LayerMode != str(self.form.layerMode.currentText()): - obj.LayerMode = str(self.form.layerMode.currentText()) + if obj.LayerMode != str(self.form.layerMode.currentData()): + obj.LayerMode = str(self.form.layerMode.currentData()) - if obj.CutPattern != str(self.form.cutPattern.currentText()): - obj.CutPattern = str(self.form.cutPattern.currentText()) + if obj.CutPattern != str(self.form.cutPattern.currentData()): + obj.CutPattern = str(self.form.cutPattern.currentData()) - PathGui.updateInputField(obj, 'BoundaryAdjustment', self.form.boundaryAdjustment) + PathGui.updateInputField( + obj, "BoundaryAdjustment", self.form.boundaryAdjustment + ) if obj.StepOver != self.form.stepOver.value(): obj.StepOver = self.form.stepOver.value() - PathGui.updateInputField(obj, 'SampleInterval', self.form.sampleInterval) + PathGui.updateInputField(obj, "SampleInterval", self.form.sampleInterval) if obj.OptimizeLinearPaths != self.form.optimizeEnabled.isChecked(): obj.OptimizeLinearPaths = self.form.optimizeEnabled.isChecked() def setFields(self, obj): - '''setFields(obj) ... transfers obj's property values to UI''' + """setFields(obj) ... transfers obj's property values to UI""" self.setupToolController(obj, self.form.toolController) self.setupCoolant(obj, self.form.coolantController) self.selectInComboBox(obj.Algorithm, self.form.algorithmSelect) self.selectInComboBox(obj.BoundBox, self.form.boundBoxSelect) self.selectInComboBox(obj.LayerMode, self.form.layerMode) self.selectInComboBox(obj.CutPattern, self.form.cutPattern) - self.form.boundaryAdjustment.setText(FreeCAD.Units.Quantity(obj.BoundaryAdjustment.Value, FreeCAD.Units.Length).UserString) + self.form.boundaryAdjustment.setText( + FreeCAD.Units.Quantity( + obj.BoundaryAdjustment.Value, FreeCAD.Units.Length + ).UserString + ) self.form.stepOver.setValue(obj.StepOver) - self.form.sampleInterval.setText(FreeCAD.Units.Quantity(obj.SampleInterval.Value, FreeCAD.Units.Length).UserString) + self.form.sampleInterval.setText( + FreeCAD.Units.Quantity( + obj.SampleInterval.Value, FreeCAD.Units.Length + ).UserString + ) if obj.OptimizeLinearPaths: self.form.optimizeEnabled.setCheckState(QtCore.Qt.Checked) @@ -94,7 +121,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.updateVisibility() def getSignalsForUpdate(self, obj): - '''getSignalsForUpdate(obj) ... return list of signals for updating obj''' + """getSignalsForUpdate(obj) ... return list of signals for updating obj""" signals = [] signals.append(self.form.toolController.currentIndexChanged) signals.append(self.form.coolantController.currentIndexChanged) @@ -110,11 +137,11 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): return signals def updateVisibility(self, sentObj=None): - '''updateVisibility(sentObj=None)... Updates visibility of Tasks panel objects.''' - Algorithm = self.form.algorithmSelect.currentText() + """updateVisibility(sentObj=None)... Updates visibility of Tasks panel objects.""" + Algorithm = self.form.algorithmSelect.currentData() self.form.optimizeEnabled.hide() # Has no independent QLabel object - if Algorithm == 'OCL Dropcutter': + if Algorithm == "OCL Dropcutter": self.form.cutPattern.hide() self.form.cutPattern_label.hide() self.form.boundaryAdjustment.hide() @@ -123,12 +150,12 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.form.stepOver_label.hide() self.form.sampleInterval.show() self.form.sampleInterval_label.show() - elif Algorithm == 'Experimental': + elif Algorithm == "Experimental": self.form.cutPattern.show() self.form.boundaryAdjustment.show() self.form.cutPattern_label.show() self.form.boundaryAdjustment_label.show() - if self.form.cutPattern.currentText() == 'None': + if self.form.cutPattern.currentData() == "None": self.form.stepOver.hide() self.form.stepOver_label.hide() else: @@ -142,12 +169,14 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.form.cutPattern.currentIndexChanged.connect(self.updateVisibility) -Command = PathOpGui.SetupOperation('Waterline', - PathWaterline.Create, - TaskPanelOpPage, - 'Path_Waterline', - QtCore.QT_TRANSLATE_NOOP("Path_Waterline", "Waterline"), - QtCore.QT_TRANSLATE_NOOP("Path_Waterline", "Create a Waterline Operation from a model"), - PathWaterline.SetupProperties) +Command = PathOpGui.SetupOperation( + "Waterline", + PathWaterline.Create, + TaskPanelOpPage, + "Path_Waterline", + QT_TRANSLATE_NOOP("Path_Waterline", "Waterline"), + QT_TRANSLATE_NOOP("Path_Waterline", "Create a Waterline Operation from a model"), + PathWaterline.SetupProperties, +) FreeCAD.Console.PrintLog("Loading PathWaterlineGui... done\n")