diff --git a/src/Mod/CAM/Gui/Resources/panels/PageOpVcarveEdit.ui b/src/Mod/CAM/Gui/Resources/panels/PageOpVcarveEdit.ui
index a4df0aed4c..be95da54d9 100644
--- a/src/Mod/CAM/Gui/Resources/panels/PageOpVcarveEdit.ui
+++ b/src/Mod/CAM/Gui/Resources/panels/PageOpVcarveEdit.ui
@@ -6,8 +6,8 @@
0
0
- 400
- 197
+ 739
+ 379
@@ -55,65 +55,109 @@
-
-
-
-
-
-
-
-
-
-
- Discretization Deflection
-
-
-
- -
-
-
- This value is used in discretizing arcs into segments. Smaller values will result in larger gcode. Larger values may cause unwanted segments in the medial line path.
-
-
- 3
-
-
- 0.001000000000000
-
-
- 1.000000000000000
-
-
- 0.010000000000000
-
-
- 0.010000000000000
-
-
-
- -
-
-
-
-
-
- Filter Colinear lines
-
-
-
- -
-
-
- Sets how aggressively colinear segments are filtered from the Voronoi diagram. Valid values are 0 - 90 degrees (larger numbers filter more). Default = 10
-
-
- 90
-
-
- 10
-
-
-
-
-
+
+ -
+
+
+
+
+
+ Discretization Deflection
+
+
+
+ -
+
+
+ This value is used in discretizing arcs into segments. Smaller values will result in larger gcode. Larger values may cause unwanted segments in the medial line path.
+
+
+ 3
+
+
+ 0.001000000000000
+
+
+ 1.000000000000000
+
+
+ 0.010000000000000
+
+
+ 0.010000000000000
+
+
+
+ -
+
+
+
+
+
+ Filter Colinear lines
+
+
+
+ -
+
+
+ Sets how aggressively colinear segments are filtered from the Voronoi diagram. Valid values are 0 - 90 degrees (larger numbers filter more). Default = 10
+
+
+ 90
+
+
+ 10
+
+
+
+ -
+
+
+ Finishing pass Z offset
+
+
+
+ -
+
+
+ Endmill offset for the finishing pass run. Use small value like -0.2 mm to help clean "fuzzy skin" or other artefacts.
+
+
+
+
+
+ 0.100000000000000
+
+
+
+ -
+
+
+ true
+
+
+ After carving travel again the path to remove artifacts and imperfections
+
+
+
+
+
+ Finishing pass
+
+
+
+ -
+
+
+ Optimize path to avoid raising endmill when moving to adjacent edges. May result in sub-millimeter inaccuracies.
+
+
+ Optimize movements
+
+
+
+
-
@@ -130,6 +174,13 @@
+
+
+ Gui::QuantitySpinBox
+ QWidget
+
+
+
diff --git a/src/Mod/CAM/Path/Log.py b/src/Mod/CAM/Path/Log.py
index 0f2b436c1c..e33cbc1282 100644
--- a/src/Mod/CAM/Path/Log.py
+++ b/src/Mod/CAM/Path/Log.py
@@ -102,6 +102,7 @@ def _caller():
def _log(level, module_line_func, msg):
"""internal function to do the logging"""
module, line, func = module_line_func
+
if getLevel(module) >= level:
message = "%s.%s: %s" % (module, Level.toString(level), msg)
if _useConsole:
@@ -122,9 +123,10 @@ def _log(level, module_line_func, msg):
def debug(msg):
"""(message)"""
- module, line, func = _caller()
+ caller_info = _caller()
+ _, line, _ = caller_info
msg = "({}) - {}".format(line, msg)
- return _log(Level.DEBUG, _caller(), msg)
+ return _log(Level.DEBUG, caller_info, msg)
def info(msg):
diff --git a/src/Mod/CAM/Path/Op/Base.py b/src/Mod/CAM/Path/Op/Base.py
index 25726db074..2600d4edda 100644
--- a/src/Mod/CAM/Path/Op/Base.py
+++ b/src/Mod/CAM/Path/Op/Base.py
@@ -463,6 +463,15 @@ class ObjectOp(object):
QT_TRANSLATE_NOOP("App::Property", "Operations Cycle Time Estimation"),
)
+ if FeatureStepDown & features and not hasattr(obj, "StepDown"):
+ obj.addProperty(
+ "App::PropertyDistance",
+ "StepDown",
+ "Depth",
+ QT_TRANSLATE_NOOP("App::Property", "Incremental Step Down of Tool"),
+ )
+ obj.StepDown = 0
+
self.setEditorModes(obj, features)
self.opOnDocumentRestored(obj)
diff --git a/src/Mod/CAM/Path/Op/Gui/Vcarve.py b/src/Mod/CAM/Path/Op/Gui/Vcarve.py
index 1c275e533a..ef6e8818d1 100644
--- a/src/Mod/CAM/Path/Op/Gui/Vcarve.py
+++ b/src/Mod/CAM/Path/Op/Gui/Vcarve.py
@@ -25,6 +25,8 @@ import FreeCADGui
import Path
import Path.Op.Gui.Base as PathOpGui
import Path.Op.Vcarve as PathVcarve
+import Path.Base.Gui.Util as PathGuiUtil
+
import PathGui
import PathScripts.PathUtils as PathUtils
from PySide import QtCore, QtGui
@@ -35,6 +37,7 @@ __author__ = "sliptonic (Brad Collette)"
__url__ = "https://www.freecad.org"
__doc__ = "Vcarve operation page controller and command implementation."
+# There is a bug in logging library. To enable debugging - set True also in Op/Vcarve.py
if False:
Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule())
@@ -124,9 +127,37 @@ class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage):
class TaskPanelOpPage(PathOpGui.TaskPanelPage):
"""Page controller class for the Vcarve operation."""
+ def initPage(self, obj):
+ self.finishingPassZOffsetSpinBox = PathGuiUtil.QuantitySpinBox(
+ self.form.finishingPassZOffset, obj, "FinishingPassZOffset"
+ )
+
def getForm(self):
"""getForm() ... returns UI"""
- return FreeCADGui.PySideUic.loadUi(":/panels/PageOpVcarveEdit.ui")
+ form = FreeCADGui.PySideUic.loadUi(":/panels/PageOpVcarveEdit.ui")
+ self.updateFormConditionalState(form)
+ return form
+
+ def updateFormConditionalState(self, form):
+ """
+ Update conditional form controls - i.e settings that should be
+ visible only under certain conditions (other settings enabled, etc).
+ """
+
+ if form.finishingPassEnabled.isChecked():
+ form.finishingPassZOffset.setVisible(True)
+ form.finishingPassZOffsetLabel.setVisible(True)
+ else:
+ form.finishingPassZOffset.setVisible(False)
+ form.finishingPassZOffsetLabel.setVisible(False)
+
+ def updateFormCallback(self):
+ return self.updateFormConditionalState(self.form)
+
+ def registerSignalHandlers(self, obj):
+ """Register signal handlers to update conditiona UI states"""
+
+ self.form.finishingPassEnabled.stateChanged.connect(self.updateFormCallback)
def getFields(self, obj):
"""getFields(obj) ... transfers values from UI to obj's properties"""
@@ -134,6 +165,15 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
obj.Discretize = self.form.discretize.value()
if obj.Colinear != self.form.colinearFilter.value():
obj.Colinear = self.form.colinearFilter.value()
+
+ if obj.FinishingPass != self.form.finishingPassEnabled.isChecked():
+ obj.FinishingPass = self.form.finishingPassEnabled.isChecked()
+
+ if obj.OptimizeMovements != self.form.optimizeMovementsEnabled.isChecked():
+ obj.OptimizeMovements = self.form.optimizeMovementsEnabled.isChecked()
+
+ self.finishingPassZOffsetSpinBox.updateProperty()
+
self.updateToolController(obj, self.form.toolController)
self.updateCoolant(obj, self.form.coolantController)
@@ -141,14 +181,27 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
"""setFields(obj) ... transfers obj's property values to UI"""
self.form.discretize.setValue(obj.Discretize)
self.form.colinearFilter.setValue(obj.Colinear)
+ self.form.finishingPassEnabled.setChecked(obj.FinishingPass)
+ self.form.optimizeMovementsEnabled.setChecked(obj.OptimizeMovements)
+
+ self.finishingPassZOffsetSpinBox.updateSpinBox()
+
self.setupToolController(obj, self.form.toolController)
self.setupCoolant(obj, self.form.coolantController)
+ self.updateFormConditionalState(self.form)
+
def getSignalsForUpdate(self, obj):
"""getSignalsForUpdate(obj) ... return list of signals for updating obj"""
signals = []
signals.append(self.form.discretize.editingFinished)
signals.append(self.form.colinearFilter.editingFinished)
+ signals.append(self.form.finishingPassEnabled.stateChanged)
+ signals.append(self.form.finishingPassZOffset.editingFinished)
+
+ signals.append(self.form.optimizeMovementsEnabled.stateChanged)
+
+
signals.append(self.form.toolController.currentIndexChanged)
signals.append(self.form.coolantController.currentIndexChanged)
return signals
diff --git a/src/Mod/CAM/Path/Op/Vcarve.py b/src/Mod/CAM/Path/Op/Vcarve.py
index 5e81e20d18..f1b1230002 100644
--- a/src/Mod/CAM/Path/Op/Vcarve.py
+++ b/src/Mod/CAM/Path/Op/Vcarve.py
@@ -29,7 +29,6 @@ import PathScripts.PathUtils as PathUtils
import math
from PySide.QtCore import QT_TRANSLATE_NOOP
-
from PySide import QtCore
__doc__ = "Class and implementation of CAM Vcarve operation"
@@ -42,6 +41,8 @@ COLINEAR = 4
TWIN = 5
BORDERLINE = 6
+# There is a bug in logging library. To enable debugging - set True also in Gui/Vcarve.py
+
if False:
Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule())
Path.Log.trackModule(Path.Log.thisModule())
@@ -50,7 +51,6 @@ else:
translate = FreeCAD.Qt.translate
-_sorting = "global"
def _collectVoronoiWires(vd):
@@ -150,13 +150,50 @@ def _sortVoronoiWires(wires, start=FreeCAD.Vector(0, 0, 0)):
class _Geometry(object):
"""POD class so the limits only have to be calculated once."""
- def __init__(self, zStart, zStop, zScale):
+ def __init__(self, zStart, zStop, zScale, zStepDown):
self.start = zStart
self.stop = zStop
self.scale = zScale
+ self.stepDown = zStepDown
+ self.stepDownPass = 1
+
+ # offset is used in finishing passes to override
+ # any calculated vcarving depths. Usually going deeper 0.1-0.2 mm on finishing pass can help
+ # remove "fuzzy skin" or other imperfections.
+ self.offset = 0
+
+ def incrementStepDownDepth(self, maximumUsableDepth):
+ """
+ Increase stepDown depth before staring new carving pass.
+ :returns: True if successful, False if maximum depth achieved
+ """
+
+ # do not allow to increase depth if we are already at stop depth
+ if self.maximumDepth == self.stop:
+ return False
+
+ # do not allow to increase depth if we are already at
+ # maximum usable depth
+
+ if self.maximumDepth <= maximumUsableDepth:
+ return False
+
+ self.stepDownPass += 1
+ return True
+
+ @property
+ def maximumDepth(self):
+ """
+ Return maximum vcarving depth computed from step down setting and pass number
+ """
+
+ if self.stepDown == 0:
+ return self.stop
+
+ return max(self.stop, self.start - (self.stepDownPass * self.stepDown))
@classmethod
- def FromTool(cls, tool, zStart, zFinal):
+ def FromTool(cls, tool, zStart, zFinal, zStepDown=0):
rMax = float(tool.Diameter) / 2.0
rMin = float(tool.TipDiameter) / 2.0
toolangle = math.tan(math.radians(tool.CuttingEdgeAngle.Value / 2.0))
@@ -164,32 +201,65 @@ class _Geometry(object):
zStop = zStart - rMax * zScale
zOff = rMin * zScale
- return _Geometry(zStart + zOff, max(zStop + zOff, zFinal), zScale)
+ return _Geometry(zStart + zOff, max(zStop + zOff, zFinal), zScale, zStepDown)
@classmethod
def FromObj(cls, obj, model):
zStart = model.Shape.BoundBox.ZMax
finalDepth = obj.FinalDepth.Value
+ stepDown = abs(obj.StepDown.Value)
- return cls.FromTool(obj.ToolController.Tool, zStart, finalDepth)
+ return cls.FromTool(obj.ToolController.Tool, zStart, finalDepth, stepDown)
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)
- Path.Log.debug("zStart value: {} depth: {}".format(geom.start, depth))
- return max(depth, geom.stop)
+ return max(depth, geom.maximumDepth) + geom.offset
-def _getPartEdge(edge, depths):
+def _get_maximumUsableDepth(wires, geom):
+ """
+ Calculate maximum engraving depth for a list of wires
+ belonging to one face.
+ """
+
+ def _get_depth(MIC, geom):
+ """Similar logic to _calculate_depth but without stepdown and offset calculations"""
+ depth = geom.start - round(MIC * geom.scale, 4)
+ return max(depth, geom.stop)
+
+ min_depth = None
+
+ for wire in wires:
+ for edge in wire:
+ dist = edge.getDistances()
+ depth = min(_get_depth(dist[0], geom), _get_depth(dist[1], geom))
+
+ if min_depth is None:
+ min_depth = depth
+ else:
+ min_depth = min(min_depth, depth)
+
+ return min_depth
+
+
+def _getPartEdge(edge, geom):
dist = edge.getDistances()
- zBegin = _calculate_depth(dist[0], depths)
- zEnd = _calculate_depth(dist[1], depths)
+ zBegin = _calculate_depth(dist[0], geom)
+ zEnd = _calculate_depth(dist[1], geom)
return edge.toShape(zBegin, zEnd)
+def _getPartEdges(obj, vWire, geom):
+ edges = []
+ for e in vWire:
+ edges.append(_getPartEdge(e, geom))
+ return edges
+
+
class ObjectVcarve(PathEngraveBase.ObjectOp):
"""Proxy class for Vcarve operation."""
@@ -199,6 +269,7 @@ class ObjectVcarve(PathEngraveBase.ObjectOp):
PathOp.FeatureTool
| PathOp.FeatureHeights
| PathOp.FeatureDepths
+ | PathOp.FeatureStepDown
| PathOp.FeatureBaseFaces
| PathOp.FeatureCoolant
)
@@ -215,6 +286,30 @@ class ObjectVcarve(PathEngraveBase.ObjectOp):
)
obj.setEditorMode("BaseShapes", 2) # hide
+ obj.addProperty(
+ "App::PropertyBool",
+ "OptimizeMovements",
+ "Path",
+ QT_TRANSLATE_NOOP("App::Property", "Optimize movements"),
+ )
+
+ obj.addProperty(
+ "App::PropertyBool",
+ "FinishingPass",
+ "Path",
+ QT_TRANSLATE_NOOP("App::Property", "Add finishing pass"),
+ )
+
+ obj.addProperty(
+ "App::PropertyDistance",
+ "FinishingPassZOffset",
+ "Path",
+ QT_TRANSLATE_NOOP("App::Property", "Finishing pass Z offset"),
+ )
+
+ obj.FinishingPass = False
+ obj.FinishingPassZOffset = "0.00"
+
def initOperation(self, obj):
"""initOperation(obj) ... create vcarve specific properties."""
obj.addProperty(
@@ -241,6 +336,7 @@ class ObjectVcarve(PathEngraveBase.ObjectOp):
"Path",
QT_TRANSLATE_NOOP("App::Property", "Vcarve Tolerance"),
)
+
obj.Colinear = 10.0
obj.Discretize = 0.01
obj.Tolerance = Path.Preferences.defaultGeometryTolerance()
@@ -250,14 +346,13 @@ class ObjectVcarve(PathEngraveBase.ObjectOp):
# upgrade ...
self.setupAdditionalProperties(obj)
- def _getPartEdges(self, obj, vWire, geom):
- edges = []
- for e in vWire:
- edges.append(_getPartEdge(e, geom))
- return edges
+ def buildMedialWires(self, obj, faces):
+ """
+ constructs a medial axis path using openvoronoi
+ :returns: dictionary - each face object is a key containing list of wires"""
- def buildPathMedial(self, obj, faces):
- """constructs a medial axis path using openvoronoi"""
+ wires_by_face = dict()
+ self.voronoiDebugCache = dict()
def insert_many_wires(vd, wires):
for wire in wires:
@@ -276,34 +371,18 @@ class ObjectVcarve(PathEngraveBase.ObjectOp):
dist = ptv[-1].distanceToPoint(ptv[0])
if dist < FreeCAD.Base.Precision.confusion():
Path.Log.debug(
- "Removing bad carve point: {} from polygon origin"
- .format(dist))
+ "Removing bad carve point: {} from polygon origin".format(
+ dist
+ )
+ )
del ptv[-1]
ptv.append(ptv[0])
- for i in range(len(ptv)-1):
+ for i in range(len(ptv) - 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))
- )
- 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))
- )
- for e in edges:
- path.extend(Path.Geom.cmdsForEdge(e, hSpeed=hSpeed, vSpeed=vSpeed))
-
- return path
-
- voronoiWires = []
for f in faces:
+ voronoiWires = []
vd = Path.Voronoi.Diagram()
insert_many_wires(vd, f.Wires)
@@ -328,28 +407,136 @@ class ObjectVcarve(PathEngraveBase.ObjectOp):
vd.colorTwins(TWIN)
wires = _collectVoronoiWires(vd)
- if _sorting != "global":
- wires = _sortVoronoiWires(wires)
+ wires = _sortVoronoiWires(wires)
voronoiWires.extend(wires)
- if _sorting == "global":
- voronoiWires = _sortVoronoiWires(voronoiWires)
+ wires_by_face[f] = voronoiWires
+ self.voronoiDebugCache = wires_by_face
- geom = _Geometry.FromObj(obj, self.model[0])
+ return wires_by_face
+
+ def buildCommandList(self, obj, faces):
+ """
+ Build command list to cut wires - based on voronoi
+ wire list from buildMedialWires
+ """
+
+ def getCurrentPosition(wire):
+ """
+ Calculate CNC head position assuming it reached the end of the wire
+ """
+
+ if not wire:
+ return None
+
+ lastEdge = wire[-1]
+ return lastEdge.valueAt(lastEdge.LastParameter)
+
+ def cutWires(wires, pathlist, optimizeMovements=False):
+ currentPosition = None
+ for w in wires:
+ pWire = _getPartEdges(obj, w, geom)
+ if pWire:
+ pathlist.extend(_cutWire(pWire, currentPosition))
+
+ # movement optimization only works if we provide current head position
+ if optimizeMovements:
+ currentPosition = getCurrentPosition(pWire)
+
+ def canSkipRepositioning(currentPosition, newPosition):
+ """
+ Calculate if it makes sense to raise head to safe height and reposition before
+ starting to cut another edge
+ """
+
+ if not currentPosition:
+ return False
+
+ # get vertex position on X/Y plane only
+ v0 = FreeCAD.Base.Vector(currentPosition.x, currentPosition.y)
+ v1 = FreeCAD.Base.Vector(newPosition.x, newPosition.y)
+
+ return v0.distanceToPoint(v1) <= 0.5
+
+ def _cutWire(wire, currentPosition=None):
+ path = []
+
+ e = wire[0]
+ newPosition = e.valueAt(e.FirstParameter)
+
+ # raise and reposition the head only if new wire starts further than 0.5 mm
+ # from current head position
+ if not canSkipRepositioning(currentPosition, newPosition):
+ path.append(Path.Command("G0 Z{}".format(obj.SafeHeight.Value)))
+ path.append(
+ Path.Command(
+ "G0 X{} Y{} Z{}".format(
+ newPosition.x, newPosition.y, obj.SafeHeight.Value
+ )
+ )
+ )
+
+ hSpeed = obj.ToolController.HorizFeed.Value
+ vSpeed = obj.ToolController.VertFeed.Value
+ path.append(
+ Path.Command(
+ "G1 X{} Y{} Z{} F{}".format(
+ newPosition.x, newPosition.y, newPosition.z, vSpeed
+ )
+ )
+ )
+ for e in wire:
+ path.extend(Path.Geom.cmdsForEdge(e, hSpeed=hSpeed, vSpeed=vSpeed))
+
+ return path
pathlist = []
pathlist.append(Path.Command("(starting)"))
- for w in voronoiWires:
- pWire = self._getPartEdges(obj, w, geom)
- if pWire:
- wires.append(pWire)
- pathlist.extend(cutWire(pWire))
+
+ # iterate over each face separatedly
+ for face, wires in self.buildMedialWires(obj, faces).items():
+
+ geom = _Geometry.FromObj(obj, self.model[0])
+
+ # If using depth step-down, calculate maximum usable depth for current face.
+ # This is done to avoid adding additional step-down engraving passes when it
+ # would make no sense as depth is limited by Maximum Inscribed Circle anyway.
+
+ maximumUsableDepth = geom.stop
+
+ if geom.stepDown > 0:
+ _maximumUsableDepth = _get_maximumUsableDepth(wires, geom)
+ if _maximumUsableDepth is not None:
+ maximumUsableDepth = _maximumUsableDepth
+ Path.Log.debug(
+ f"Maximum usable depth for current face: {maximumUsableDepth}"
+ )
+
+ # first pass
+ cutWires(wires, pathlist, obj.OptimizeMovements)
+
+ # subsequent stepDown depth passes (if any)
+ while geom.incrementStepDownDepth(maximumUsableDepth):
+ cutWires(wires, pathlist, obj.OptimizeMovements)
+
+ # add finishing pass if enabled
+
+ # if obj.FinishingPass:
+ # geom.offset = obj.FinishingPassZOffset.Value
+
+ # for w in wires:
+ # pWire = self._getPartEdges(obj, w, geom)
+ # if pWire:
+ # pathlist.extend(cutWire(pWire))
+
self.commandlist = pathlist
def opExecute(self, obj):
"""opExecute(obj) ... process engraving operation"""
Path.Log.track()
+ self.voronoiDebugCache = None
+
if not hasattr(obj.ToolController.Tool, "CuttingEdgeAngle"):
Path.Log.error(
translate(
@@ -386,7 +573,7 @@ class ObjectVcarve(PathEngraveBase.ObjectOp):
faces.extend(model.Shape.Faces)
if faces:
- self.buildPathMedial(obj, faces)
+ self.buildCommandList(obj, faces)
else:
Path.Log.error(
translate(
@@ -399,6 +586,9 @@ class ObjectVcarve(PathEngraveBase.ObjectOp):
Path.Log.error(
"Error processing Base object. Engraving operation will produce no output."
)
+ import traceback
+
+ Path.Log.error(f"Engraving operation exception: {traceback.format_exc()}")
def opUpdateDepths(self, obj, ignoreErrors=False):
"""updateDepths(obj) ... engraving is always done at the top most z-value"""
@@ -423,6 +613,38 @@ class ObjectVcarve(PathEngraveBase.ObjectOp):
and hasattr(tool, "TipDiameter")
)
+ def debugVoronoi(self, obj):
+ """Debug function to display calculated voronoi edges"""
+
+ if not getattr(self, "voronoiDebugCache", None):
+ Path.Log.error(
+ "debugVoronoi: empty debug cache. Recompute VCarve operation first"
+ )
+ return
+
+ vPart = FreeCAD.activeDocument().addObject(
+ "App::Part", f"{obj.Name}-VoronoiDebug"
+ )
+
+ wiresToShow = []
+
+ for face, wires in self.voronoiDebugCache.items():
+ for wire in wires:
+ lastEdge = None
+ currentPartWire = Part.Wire()
+ currentPartWire.fixTolerance(0.01)
+ for edge in wire:
+ currentEdge = edge.toShape()
+
+ for v in currentEdge.Vertexes:
+ v.fixTolerance(0.1)
+
+ currentPartWire.add(currentEdge)
+ wiresToShow.append(currentPartWire)
+
+ for w in wiresToShow:
+ vPart.addObject(Part.show(w))
+
def SetupProperties():
return ["Discretize"]
diff --git a/src/Mod/CAM/Tests/TestPathVcarve.py b/src/Mod/CAM/Tests/TestPathVcarve.py
index c91e5ecb95..e66a461162 100644
--- a/src/Mod/CAM/Tests/TestPathVcarve.py
+++ b/src/Mod/CAM/Tests/TestPathVcarve.py
@@ -111,3 +111,37 @@ class TestPathVcarve(PathTestBase):
self.assertRoughly(geom.start, Scale45)
self.assertRoughly(geom.stop, -3)
self.assertRoughly(geom.scale, Scale45)
+
+ def test14(self):
+ """Verify if max dept is calculated properly when step-down is disabled"""
+
+ tool = VbitTool(10, 45, 2)
+ geom = PathVcarve._Geometry.FromTool(tool, zStart=0, zFinal=-3, zStepDown=0)
+
+ self.assertEqual(geom.maximumDepth, -3)
+ self.assertEqual(geom.maximumDepth, geom.stop)
+
+ def test15(self):
+ """Verify if step-down sections match final max depth"""
+
+ tool = VbitTool(10, 45, 2)
+ geom = PathVcarve._Geometry.FromTool(tool, zStart=0, zFinal=-3, zStepDown=0.13)
+
+ while geom.incrementStepDownDepth(maximumUsableDepth=-3):
+ pass
+
+ self.assertEqual(geom.maximumDepth, -3)
+
+ def test16(self):
+ """Verify 90 deg with tip dia depth calculation with step-down enabled"""
+ tool = VbitTool(10, 90, 2)
+ geom = PathVcarve._Geometry.FromTool(tool, zStart=0, zFinal=-10, zStepDown=0.13)
+
+ while geom.incrementStepDownDepth(maximumUsableDepth=-10):
+ pass
+
+ # in order for the width to be correct the height needs to be shifted
+ self.assertRoughly(geom.start, 1)
+ self.assertRoughly(geom.stop, -4)
+ self.assertRoughly(geom.scale, 1)
+ self.assertRoughly(geom.maximumDepth, -4)