diff --git a/src/Mod/Path/PathScripts/PathVcarve.py b/src/Mod/Path/PathScripts/PathVcarve.py index 27fbbee8ef..898f5faf1f 100644 --- a/src/Mod/Path/PathScripts/PathVcarve.py +++ b/src/Mod/Path/PathScripts/PathVcarve.py @@ -30,20 +30,18 @@ import PathScripts.PathUtils as PathUtils import PathScripts.PathGeom as PathGeom import PathScripts.PathPreferences as PathPreferences -import traceback - import math from PySide import QtCore __doc__ = "Class and implementation of Path Vcarve operation" -PRIMARY = 0 +PRIMARY = 0 SECONDARY = 1 EXTERIOR1 = 2 EXTERIOR2 = 3 -COLINEAR = 4 -TWIN = 5 +COLINEAR = 4 +TWIN = 5 PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) # PathLog.trackModule(PathLog.thisModule()) @@ -57,7 +55,7 @@ def translate(context, text, disambig=None): VD = [] Vertex = {} -_sorting = 'global' +_sorting = "global" def _collectVoronoiWires(vd): @@ -104,13 +102,13 @@ def _collectVoronoiWires(vd): we = [] vFirst = knots[0] vStart = vFirst - vLast = vFirst + vLast = vFirst if len(vertex[vStart]): while vStart is not None: - vLast = vStart - edges = vertex[vStart] + vLast = vStart + edges = vertex[vStart] if len(edges) > 0: - edge = edges[0] + edge = edges[0] vStart = traverse(vStart, edge, we) else: vStart = None @@ -133,11 +131,11 @@ def _sortVoronoiWires(wires, start=FreeCAD.Vector(0, 0, 0)): return (p, l) begin = {} - end = {} + end = {} for i, w in enumerate(wires): begin[i] = w[0].Vertices[0].toPoint() - end[i] = w[-1].Vertices[1].toPoint() + end[i] = w[-1].Vertices[1].toPoint() result = [] while begin: @@ -145,23 +143,24 @@ def _sortVoronoiWires(wires, start=FreeCAD.Vector(0, 0, 0)): (eIdx, eLen) = closestTo(start, end) if bLen < eLen: result.append(wires[bIdx]) - start = end[bIdx] - del begin[bIdx] - del end[bIdx] + start = end[bIdx] + del begin[bIdx] + del end[bIdx] else: result.append([e.Twin for e in reversed(wires[eIdx])]) start = begin[eIdx] - del begin[eIdx] - del end[eIdx] + del begin[eIdx] + del end[eIdx] return result + class _Geometry(object): - '''POD class so the limits only have to be calculated once.''' + """POD class so the limits only have to be calculated once.""" def __init__(self, zStart, zStop, zScale): self.start = zStart - self.stop = zStop + self.stop = zStop self.scale = zScale @classmethod @@ -170,8 +169,8 @@ class _Geometry(object): rMin = float(tool.TipDiameter) / 2.0 toolangle = math.tan(math.radians(tool.CuttingEdgeAngle.Value / 2.0)) zScale = 1.0 / toolangle - zStop = zStart - rMax * zScale - zOff = rMin * zScale + zStop = zStart - rMax * zScale + zOff = rMin * zScale return _Geometry(zStart + zOff, max(zStop + zOff, zFinal), zScale) @@ -182,45 +181,74 @@ class _Geometry(object): return cls.FromTool(obj.ToolController.Tool, zStart, finalDepth) + def _calculate_depth(MIC, geom): # given a maximum inscribed circle (MIC) and tool angle, # return depth of cut relative to zStart. depth = geom.start - round(MIC * geom.scale, 4) - PathLog.debug('zStart value: {} depth: {}'.format(geom.start, depth)) + PathLog.debug("zStart value: {} depth: {}".format(geom.start, depth)) return max(depth, geom.stop) + def _getPartEdge(edge, depths): dist = edge.getDistances() zBegin = _calculate_depth(dist[0], depths) - zEnd = _calculate_depth(dist[1], depths) + zEnd = _calculate_depth(dist[1], depths) return edge.toShape(zBegin, zEnd) + class ObjectVcarve(PathEngraveBase.ObjectOp): - '''Proxy class for Vcarve operation.''' + """Proxy class for Vcarve operation.""" def opFeatures(self, obj): - '''opFeatures(obj) ... return all standard features and edges based geomtries''' - return PathOp.FeatureTool | PathOp.FeatureHeights | PathOp.FeatureDepths | PathOp.FeatureBaseFaces | PathOp.FeatureCoolant + """opFeatures(obj) ... return all standard features and edges based geomtries""" + return ( + PathOp.FeatureTool + | PathOp.FeatureHeights + | PathOp.FeatureDepths + | PathOp.FeatureBaseFaces + | PathOp.FeatureCoolant + ) def setupAdditionalProperties(self, obj): - if not hasattr(obj, 'BaseShapes'): - obj.addProperty("App::PropertyLinkList", "BaseShapes", "Path", - QtCore.QT_TRANSLATE_NOOP("PathVcarve", - "Additional base objects to be engraved")) - obj.setEditorMode('BaseShapes', 2) # hide + if not hasattr(obj, "BaseShapes"): + obj.addProperty( + "App::PropertyLinkList", + "BaseShapes", + "Path", + QtCore.QT_TRANSLATE_NOOP( + "PathVcarve", "Additional base objects to be engraved" + ), + ) + obj.setEditorMode("BaseShapes", 2) # hide def initOperation(self, obj): - '''initOperation(obj) ... create vcarve specific properties.''' - obj.addProperty("App::PropertyFloat", "Discretize", "Path", - QtCore.QT_TRANSLATE_NOOP("PathVcarve", - "The deflection value for discretizing arcs")) - obj.addProperty("App::PropertyFloat", "Colinear", "Path", - QtCore.QT_TRANSLATE_NOOP("PathVcarve", - "Cutoff for removing colinear segments (degrees). \ - default=10.0.")) - obj.addProperty("App::PropertyFloat", "Tolerance", "Path", - QtCore.QT_TRANSLATE_NOOP("PathVcarve", "")) + """initOperation(obj) ... create vcarve specific properties.""" + obj.addProperty( + "App::PropertyFloat", + "Discretize", + "Path", + QtCore.QT_TRANSLATE_NOOP( + "PathVcarve", "The deflection value for discretizing arcs" + ), + ) + obj.addProperty( + "App::PropertyFloat", + "Colinear", + "Path", + QtCore.QT_TRANSLATE_NOOP( + "PathVcarve", + "Cutoff for removing colinear segments (degrees). \ + default=10.0.", + ), + ) + obj.addProperty( + "App::PropertyFloat", + "Tolerance", + "Path", + QtCore.QT_TRANSLATE_NOOP("PathVcarve", ""), + ) obj.Colinear = 10.0 obj.Discretize = 0.01 obj.Tolerance = PathPreferences.defaultGeometryTolerance() @@ -237,27 +265,31 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): return edges def buildPathMedial(self, obj, faces): - '''constructs a medial axis path using openvoronoi''' + """constructs a medial axis path using openvoronoi""" def insert_many_wires(vd, wires): for wire in wires: - PathLog.debug('discretize value: {}'.format(obj.Discretize)) + PathLog.debug("discretize value: {}".format(obj.Discretize)) pts = wire.discretize(QuasiDeflection=obj.Discretize) ptv = [FreeCAD.Vector(p.x, p.y) for p in pts] ptv.append(ptv[0]) for i in range(len(pts)): - vd.addSegment(ptv[i], ptv[i+1]) + vd.addSegment(ptv[i], ptv[i + 1]) def cutWire(edges): path = [] path.append(Path.Command("G0 Z{}".format(obj.SafeHeight.Value))) e = edges[0] p = e.valueAt(e.FirstParameter) - path.append(Path.Command("G0 X{} Y{} Z{}".format(p.x, p.y, obj.SafeHeight.Value))) + path.append( + Path.Command("G0 X{} Y{} Z{}".format(p.x, p.y, obj.SafeHeight.Value)) + ) hSpeed = obj.ToolController.HorizFeed.Value vSpeed = obj.ToolController.VertFeed.Value - path.append(Path.Command("G1 X{} Y{} Z{} F{}".format(p.x, p.y, p.z, vSpeed))) + path.append( + Path.Command("G1 X{} Y{} Z{} F{}".format(p.x, p.y, p.z, vSpeed)) + ) for e in edges: path.extend(PathGeom.cmdsForEdge(e, hSpeed=hSpeed, vSpeed=vSpeed)) @@ -274,19 +306,22 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): for e in vd.Edges: e.Color = PRIMARY if e.isPrimary() else SECONDARY vd.colorExterior(EXTERIOR1) - vd.colorExterior(EXTERIOR2, - lambda v: not f.isInside(v.toPoint(f.BoundBox.ZMin), - obj.Tolerance, True)) + vd.colorExterior( + EXTERIOR2, + lambda v: not f.isInside( + v.toPoint(f.BoundBox.ZMin), obj.Tolerance, True + ), + ) vd.colorColinear(COLINEAR, obj.Colinear) vd.colorTwins(TWIN) wires = _collectVoronoiWires(vd) - if _sorting != 'global': + if _sorting != "global": wires = _sortVoronoiWires(wires) voronoiWires.extend(wires) VD.append((f, vd, wires)) - if _sorting == 'global': + if _sorting == "global": voronoiWires = _sortVoronoiWires(voronoiWires) geom = _Geometry.FromObj(obj, self.model[0]) @@ -301,14 +336,23 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): self.commandlist = pathlist def opExecute(self, obj): - '''opExecute(obj) ... process engraving operation''' + """opExecute(obj) ... process engraving operation""" PathLog.track() if not hasattr(obj.ToolController.Tool, "CuttingEdgeAngle"): - PathLog.error(translate("Path_Vcarve", "VCarve requires an engraving cutter with CuttingEdgeAngle")) + PathLog.error( + translate( + "Path_Vcarve", + "VCarve requires an engraving cutter with CuttingEdgeAngle", + ) + ) if obj.ToolController.Tool.CuttingEdgeAngle >= 180.0: - PathLog.error(translate("Path_Vcarve", "Engraver Cutting Edge Angle must be < 180 degrees.")) + PathLog.error( + translate( + "Path_Vcarve", "Engraver Cutting Edge Angle must be < 180 degrees." + ) + ) return try: @@ -325,27 +369,39 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): if not faces: for model in self.model: - if model.isDerivedFrom('Sketcher::SketchObject') or model.isDerivedFrom('Part::Part2DObject'): + if model.isDerivedFrom( + "Sketcher::SketchObject" + ) or model.isDerivedFrom("Part::Part2DObject"): faces.extend(model.Shape.Faces) if faces: self.buildPathMedial(obj, faces) else: - PathLog.error(translate('PathVcarve', 'The Job Base Object has no engraveable element. Engraving operation will produce no output.')) + PathLog.error( + translate( + "PathVcarve", + "The Job Base Object has no engraveable element. Engraving operation will produce no output.", + ) + ) except Exception as e: - #PathLog.error(e) - #traceback.print_exc() - PathLog.error(translate('PathVcarve', 'Error processing Base object. Engraving operation will produce no output.')) - #raise e + # PathLog.error(e) + # traceback.print_exc() + PathLog.error( + translate( + "PathVcarve", + "Error processing Base object. Engraving operation will produce no output.", + ) + ) + # raise e def opUpdateDepths(self, obj, ignoreErrors=False): - '''updateDepths(obj) ... engraving is always done at the top most z-value''' + """updateDepths(obj) ... engraving is always done at the top most z-value""" job = PathUtils.findParentJob(obj) self.opSetDefaultValues(obj, job) def opSetDefaultValues(self, obj, job): - '''opSetDefaultValues(obj) ... set depths for vcarving''' + """opSetDefaultValues(obj) ... set depths for vcarving""" if PathOp.FeatureDepths & self.opFeatures(obj): if job and len(job.Model.Group) > 0: bb = job.Proxy.modelBoundBox(job) @@ -355,15 +411,20 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): obj.OpFinalDepth = -0.1 def isToolSupported(self, obj, tool): - '''isToolSupported(obj, tool) ... returns True if v-carve op can work with tool.''' - return hasattr(tool, 'Diameter') and hasattr(tool, 'CuttingEdgeAngle') and hasattr(tool, 'TipDiameter') + """isToolSupported(obj, tool) ... returns True if v-carve op can work with tool.""" + return ( + hasattr(tool, "Diameter") + and hasattr(tool, "CuttingEdgeAngle") + and hasattr(tool, "TipDiameter") + ) + def SetupProperties(): return ["Discretize"] def Create(name, obj=None, parentJob=None): - '''Create(name) ... Creates and returns a Vcarve operation.''' + """Create(name) ... Creates and returns a Vcarve operation.""" if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) obj.Proxy = ObjectVcarve(obj, name, parentJob) diff --git a/src/Mod/Path/PathScripts/PathVcarveGui.py b/src/Mod/Path/PathScripts/PathVcarveGui.py index a2daf6aac1..4cf0f99d63 100644 --- a/src/Mod/Path/PathScripts/PathVcarveGui.py +++ b/src/Mod/Path/PathScripts/PathVcarveGui.py @@ -22,7 +22,7 @@ import FreeCAD import FreeCADGui -import PathGui as PGui # ensure Path/Gui/Resources are loaded +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathVcarve as PathVcarve import PathScripts.PathLog as PathLog import PathScripts.PathOpGui as PathOpGui @@ -47,7 +47,7 @@ def translate(context, text, disambig=None): class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): - '''Enhanced base geometry page to also allow special base objects.''' + """Enhanced base geometry page to also allow special base objects.""" def super(self): return super(TaskPanelBaseGeometryPage, self) @@ -60,16 +60,25 @@ class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): job = PathUtils.findParentJob(self.obj) base = job.Proxy.resourceClone(job, sel.Object) if not base: - PathLog.notice((translate("Path", "%s is not a Base Model object of the job %s") + "\n") % (sel.Object.Label, job.Label)) + PathLog.notice( + ( + translate("Path", "%s is not a Base Model object of the job %s") + + "\n" + ) + % (sel.Object.Label, job.Label) + ) continue if base in shapes: - PathLog.notice((translate("Path", "Base shape %s already in the list") + "\n") % (sel.Object.Label)) + PathLog.notice( + (translate("Path", "Base shape %s already in the list") + "\n") + % (sel.Object.Label) + ) continue - if base.isDerivedFrom('Part::Part2DObject'): + if base.isDerivedFrom("Part::Part2DObject"): if sel.HasSubObjects: # selectively add some elements of the drawing to the Base for sub in sel.SubElementNames: - if 'Vertex' in sub: + if "Vertex" in sub: PathLog.info(translate("Path", "Ignoring vertex")) else: self.obj.Proxy.addBase(self.obj, base, sub) @@ -107,20 +116,22 @@ class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): sub = item.data(self.super().DataObjectSub) if not sub: shapes.append(obj) - PathLog.debug("Setting new base shapes: %s -> %s" % (self.obj.BaseShapes, shapes)) + PathLog.debug( + "Setting new base shapes: %s -> %s" % (self.obj.BaseShapes, shapes) + ) self.obj.BaseShapes = shapes return self.super().updateBase() class TaskPanelOpPage(PathOpGui.TaskPanelPage): - '''Page controller class for the Vcarve operation.''' + """Page controller class for the Vcarve operation.""" def getForm(self): - '''getForm() ... returns UI''' + """getForm() ... returns UI""" return FreeCADGui.PySideUic.loadUi(":/panels/PageOpVcarveEdit.ui") def getFields(self, obj): - '''getFields(obj) ... transfers values from UI to obj's proprties''' + """getFields(obj) ... transfers values from UI to obj's proprties""" if obj.Discretize != self.form.discretize.value(): obj.Discretize = self.form.discretize.value() if obj.Colinear != self.form.colinearFilter.value(): @@ -129,14 +140,14 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.updateCoolant(obj, self.form.coolantController) def setFields(self, obj): - '''setFields(obj) ... transfers obj's property values to UI''' + """setFields(obj) ... transfers obj's property values to UI""" self.form.discretize.setValue(obj.Discretize) self.form.colinearFilter.setValue(obj.Colinear) self.setupToolController(obj, self.form.toolController) self.setupCoolant(obj, self.form.coolantController) 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.discretize.editingFinished) signals.append(self.form.colinearFilter.editingFinished) @@ -145,16 +156,18 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): return signals def taskPanelBaseGeometryPage(self, obj, features): - '''taskPanelBaseGeometryPage(obj, features) ... return page for adding base geometries.''' + """taskPanelBaseGeometryPage(obj, features) ... return page for adding base geometries.""" return TaskPanelBaseGeometryPage(obj, features) -Command = PathOpGui.SetupOperation('Vcarve', - PathVcarve.Create, - TaskPanelOpPage, - 'Path_Vcarve', - QtCore.QT_TRANSLATE_NOOP("Path_Vcarve", "Vcarve"), - QtCore.QT_TRANSLATE_NOOP("Path_Vcarve", "Creates a medial line engraving path"), - PathVcarve.SetupProperties) +Command = PathOpGui.SetupOperation( + "Vcarve", + PathVcarve.Create, + TaskPanelOpPage, + "Path_Vcarve", + QtCore.QT_TRANSLATE_NOOP("Path_Vcarve", "Vcarve"), + QtCore.QT_TRANSLATE_NOOP("Path_Vcarve", "Creates a medial line engraving path"), + PathVcarve.SetupProperties, +) FreeCAD.Console.PrintLog("Loading PathVcarveGui... done\n")