From 871f29a8837240a22bfe6813ec8dd090b5646f93 Mon Sep 17 00:00:00 2001
From: Dimitris75 <30848292+Dimitris75@users.noreply.github.com>
Date: Tue, 16 Dec 2025 01:57:45 +0200
Subject: [PATCH] Simplify code and UI
Simplify code and UI
---
.../Resources/panels/PageOpWaterlineEdit.ui | 86 +++---
src/Mod/CAM/Path/Op/Gui/Waterline.py | 37 +--
src/Mod/CAM/Path/Op/Waterline.py | 268 ++++++------------
3 files changed, 124 insertions(+), 267 deletions(-)
diff --git a/src/Mod/CAM/Gui/Resources/panels/PageOpWaterlineEdit.ui b/src/Mod/CAM/Gui/Resources/panels/PageOpWaterlineEdit.ui
index 204283833a..aef29eb173 100644
--- a/src/Mod/CAM/Gui/Resources/panels/PageOpWaterlineEdit.ui
+++ b/src/Mod/CAM/Gui/Resources/panels/PageOpWaterlineEdit.ui
@@ -22,18 +22,18 @@
QFrame::Raised
-
+
-
- Tool Controller
+ Tool controller
-
- The tool and its settings to be used for this operation.
+ The tool and its settings to be used for this operation
@@ -43,7 +43,14 @@
-
- Coolant Mode
+ Coolant mode
+
+
+
+ -
+
+
+ Edit Tool Controller
@@ -63,7 +70,7 @@
-
- Select the algorithm to use: OCL Dropcutter*, OCL Adaptive* or Experimental (Not OCL based).
+ Select the algorithm to use: 'OCL Dropcutter*', or 'Experimental' (not OCL based).
@@ -76,7 +83,7 @@
- Bounding Box
+ Bounding box
@@ -88,14 +95,14 @@
- Select the overall boundary for the operation.
+ Select the overall boundary for the operation
-
- Layer Mode
+ Layer mode
@@ -107,14 +114,14 @@
- Complete the operation in a single pass at depth, or multiple passes to final depth.
+ Complete the operation in a single pass at depth, or multiple passes to final depth
-
- Cut Pattern
+ Cut pattern
@@ -126,7 +133,7 @@
- Set the geometric clearing pattern to use for the operation.
+ Set the geometric clearing pattern to use for the operation
@@ -139,14 +146,14 @@
- Boundary Adjustment
+ Boundary adjustment
-
- Set the Z-axis depth offset from the target surface.
+ Set the Z-axis depth offset from the target surface
mm
@@ -205,50 +212,23 @@ A step over of 100% results in no overlap between two different cycles.
- -
-
-
- Set the minimum sampling resolution. Smaller values quickly increase processing time.
-
-
- mm
-
-
-
- -
+
-
+
+
+ Set the minimum sampling resolution. Smaller values quickly increase processing time.
+
+
+ mm
+
+
+
+ -
Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-code output.
- Optimize Linear Paths
-
-
-
- -
-
-
- Detect the interconnection of internal features or holes and raise the tool for transition.
-
-
- Optimize Internal Features
-
-
-
- -
-
-
- Gap Detection Thershold
-
-
-
- -
-
-
- Minimum distance between the perimeter and any internal features. Lower values than Sample Interval will be ignored.
-
-
- mm
+ Optimize linear paths
@@ -288,8 +268,6 @@ A step over of 100% results in no overlap between two different cycles.
sampleInterval
minSampleInterval
optimizeEnabled
- optimizeInternal
- gapDetectionThershold
diff --git a/src/Mod/CAM/Path/Op/Gui/Waterline.py b/src/Mod/CAM/Path/Op/Gui/Waterline.py
index 817786ec3a..29f1ec48c7 100644
--- a/src/Mod/CAM/Path/Op/Gui/Waterline.py
+++ b/src/Mod/CAM/Path/Op/Gui/Waterline.py
@@ -1,4 +1,5 @@
-# -*- coding: utf-8 -*-
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
# ***************************************************************************
# * Copyright (c) 2020 sliptonic *
# * Copyright (c) 2020 russ4262 *
@@ -89,11 +90,6 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
PathGuiUtil.updateInputField(obj, "MinSampleInterval", self.form.minSampleInterval)
PathGuiUtil.updateInputField(obj, "SampleInterval", self.form.sampleInterval)
- if obj.OptimizeInternalFeatures != self.form.optimizeInternal.isChecked():
- obj.OptimizeInternalFeatures = self.form.optimizeInternal.isChecked()
-
- PathGuiUtil.updateInputField(obj, "GapDetectionThershold", self.form.gapDetectionThershold)
-
if obj.OptimizeLinearPaths != self.form.optimizeEnabled.isChecked():
obj.OptimizeLinearPaths = self.form.optimizeEnabled.isChecked()
@@ -116,15 +112,6 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
FreeCAD.Units.Quantity(obj.SampleInterval.Value, FreeCAD.Units.Length).UserString
)
- if obj.OptimizeInternalFeatures:
- self.form.optimizeInternal.setCheckState(QtCore.Qt.Checked)
- else:
- self.form.optimizeInternal.setCheckState(QtCore.Qt.Unchecked)
-
- self.form.gapDetectionThershold.setText(
- FreeCAD.Units.Quantity(obj.GapDetectionThershold.Value, FreeCAD.Units.Length).UserString
- )
-
if obj.OptimizeLinearPaths:
self.form.optimizeEnabled.setCheckState(QtCore.Qt.Checked)
else:
@@ -145,11 +132,6 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
signals.append(self.form.stepOver.editingFinished)
signals.append(self.form.minSampleInterval.editingFinished)
signals.append(self.form.sampleInterval.editingFinished)
- signals.append(self.form.gapDetectionThershold.editingFinished)
- if hasattr(self.form.optimizeInternal, "checkStateChanged"): # Qt version >= 6.7.0
- signals.append(self.form.optimizeInternal.checkStateChanged)
- else: # Qt version < 6.7.0
- signals.append(self.form.optimizeInternal.stateChanged)
if hasattr(self.form.optimizeEnabled, "checkStateChanged"): # Qt version >= 6.7.0
signals.append(self.form.optimizeEnabled.checkStateChanged)
@@ -164,8 +146,6 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
self.form.optimizeEnabled.hide() # Has no independent QLabel object
if Algorithm == "OCL Dropcutter":
- self.form.boundBoxSelect.show()
- self.form.boundBoxSelect_label.show()
self.form.cutPattern.hide()
self.form.cutPattern_label.hide()
self.form.boundaryAdjustment.hide()
@@ -176,12 +156,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
self.form.minSampleInterval_label.hide()
self.form.sampleInterval.show()
self.form.sampleInterval_label.show()
- self.form.optimizeInternal.hide()
- self.form.gapDetectionThershold.hide()
- self.form.gapDetectionThershold_label.hide()
elif Algorithm == "OCL Adaptive":
- self.form.boundBoxSelect.hide()
- self.form.boundBoxSelect_label.hide()
self.form.cutPattern.hide()
self.form.cutPattern_label.hide()
self.form.boundaryAdjustment.hide()
@@ -192,12 +167,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
self.form.minSampleInterval_label.show()
self.form.sampleInterval.show()
self.form.sampleInterval_label.show()
- self.form.optimizeInternal.show()
- self.form.gapDetectionThershold.show()
- self.form.gapDetectionThershold_label.show()
elif Algorithm == "Experimental":
- self.form.boundBoxSelect.show()
- self.form.boundBoxSelect_label.show()
self.form.cutPattern.show()
self.form.boundaryAdjustment.show()
self.form.cutPattern_label.show()
@@ -212,9 +182,6 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
self.form.minSampleInterval_label.hide()
self.form.sampleInterval.hide()
self.form.sampleInterval_label.hide()
- self.form.optimizeInternal.hide()
- self.form.gapDetectionThershold.hide()
- self.form.gapDetectionThershold_label.hide()
def registerSignalHandlers(self, obj):
self.form.algorithmSelect.currentIndexChanged.connect(self.updateVisibility)
diff --git a/src/Mod/CAM/Path/Op/Waterline.py b/src/Mod/CAM/Path/Op/Waterline.py
index fdf796f4aa..b7502ecb0f 100644
--- a/src/Mod/CAM/Path/Op/Waterline.py
+++ b/src/Mod/CAM/Path/Op/Waterline.py
@@ -1,4 +1,5 @@
-# -*- coding: utf-8 -*-
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
# ***************************************************************************
# * Copyright (c) 2019 Russell Johnson (russ4262) *
# * Copyright (c) 2019 sliptonic *
@@ -23,7 +24,6 @@
import FreeCAD
-
__title__ = "CAM Waterline Operation"
__author__ = "russ4262 (Russell Johnson), sliptonic (Brad Collette)"
__url__ = "https://www.freecad.org"
@@ -49,7 +49,6 @@ import Path.Op.SurfaceSupport as PathSurfaceSupport
import PathScripts.PathUtils as PathUtils
import math
import time
-
from PySide.QtCore import QT_TRANSLATE_NOOP
# lazily loaded modules
@@ -100,8 +99,8 @@ class ObjectWaterline(PathOp.ObjectOp):
(translate("path_waterline", "Experimental"), "Experimental"),
],
"BoundBox": [
- (translate("path_waterline", "Stock"), "Stock"),
(translate("path_waterline", "BaseBoundBox"), "BaseBoundBox"),
+ (translate("path_waterline", "Stock"), "Stock"),
],
"PatternCenterAt": [
(translate("path_waterline", "CenterOfMass"), "CenterOfMass"),
@@ -403,24 +402,6 @@ class ObjectWaterline(PathOp.ObjectOp):
"Set the minimum sampling resolution. Smaller values quickly increase processing time.",
),
),
- (
- "App::PropertyBool",
- "OptimizeInternalFeatures",
- "Optimization",
- QT_TRANSLATE_NOOP(
- "App::Property",
- "Detect the interconnection of internal features or holes and raise the tool for transition.",
- ),
- ),
- (
- "App::PropertyDistance",
- "GapDetectionThershold",
- "Optimization",
- QT_TRANSLATE_NOOP(
- "App::Property",
- "Minimum distance between the perimeter and any internal features. Lower values than Sample Interval will be ignored.",
- ),
- ),
(
"App::PropertyFloat",
"StepOver",
@@ -509,8 +490,6 @@ class ObjectWaterline(PathOp.ObjectOp):
"DepthOffset": 0.0,
"SampleInterval": 1.0,
"MinSampleInterval": 0.005,
- "OptimizeInternalFeatures": False,
- "GapDetectionThershold": 3.50,
"BoundaryAdjustment": 0.0,
"InternalFeaturesAdjustment": 0.0,
"AvoidLastX_Faces": 0,
@@ -544,12 +523,12 @@ class ObjectWaterline(PathOp.ObjectOp):
obj.setEditorMode("InternalFeaturesCut", hide)
obj.setEditorMode("AvoidLastX_Faces", hide)
obj.setEditorMode("AvoidLastX_InternalFeatures", hide)
+ obj.setEditorMode("BoundaryAdjustment", hide)
obj.setEditorMode("HandleMultipleFeatures", hide)
obj.setEditorMode("OptimizeLinearPaths", hide)
obj.setEditorMode("OptimizeStepOverTransitions", hide)
obj.setEditorMode("GapThreshold", hide)
obj.setEditorMode("GapSizes", hide)
- obj.setEditorMode("BoundaryAdjustment", hide)
if obj.Algorithm == "OCL Dropcutter":
pass
@@ -584,11 +563,7 @@ class ObjectWaterline(PathOp.ObjectOp):
obj.setEditorMode("IgnoreOuterAbove", B)
obj.setEditorMode("CutPattern", C)
obj.setEditorMode("SampleInterval", G)
-
obj.setEditorMode("MinSampleInterval", D)
- obj.setEditorMode("OptimizeInternalFeatures", D)
- obj.setEditorMode("GapDetectionThershold", D)
-
obj.setEditorMode("LinearDeflection", expMode)
obj.setEditorMode("AngularDeflection", expMode)
@@ -598,6 +573,9 @@ class ObjectWaterline(PathOp.ObjectOp):
if prop in ["Algorithm", "CutPattern"]:
self.setEditorProperties(obj)
+ if prop == "Active" and obj.ViewObject:
+ obj.ViewObject.signalChangeIcon()
+
def opOnDocumentRestored(self, obj):
self.propertiesReady = False
job = PathUtils.findParentJob(obj)
@@ -704,24 +682,6 @@ class ObjectWaterline(PathOp.ObjectOp):
)
)
- # Limit Gap Detection Threshold Adaptive
- if obj.GapDetectionThershold.Value < 1.00:
- obj.GapDetectionThershold.Value = 1.00
- Path.Log.error(
- translate(
- "PathWaterline",
- "Gap Detection Thershold limits are 1.00 to 999.99 millimeters.",
- )
- )
- if obj.GapDetectionThershold.Value > 999.99:
- obj.GapDetectionThershold.Value = 999.99
- Path.Log.error(
- translate(
- "PathWaterline",
- "Gap Detection Thershold limits are 1.00 to 999.99 millimeters.",
- )
- )
-
# Limit cut pattern angle
if obj.CutPatternAngle < -360.0:
obj.CutPatternAngle = 0.0
@@ -1273,8 +1233,19 @@ class ObjectWaterline(PathOp.ObjectOp):
if self.layerEndPnt is None:
self.layerEndPnt = FreeCAD.Vector(0.0, 0.0, 0.0)
- # Set extra offset to diameter of cutter to allow cutter to move around perimeter of model
+ smplInt = obj.SampleInterval.Value
+ minSmplInt = obj.MinSampleInterval.Value
+ if minSmplInt > smplInt:
+ minSmplInt = smplInt
+ # Compute number and size of stepdowns, and final depth
+ if obj.LayerMode == "Single-pass":
+ depthparams = [obj.FinalDepth.Value]
+ else:
+ depthparams = [dp for dp in self.depthParams]
+ lenDP = len(depthparams)
+
+ # Scan the piece to depth at smplInt
if obj.Algorithm == "OCL Adaptive":
# Get Stock boundbox for OCL Adaptive
BS = JOB.Stock
@@ -1284,7 +1255,31 @@ class ObjectWaterline(PathOp.ObjectOp):
ymin = round(abs(bb.YMin), 6)
ymax = round(abs(bb.YMax), 6)
+ # Check Stock's bounding box and Tool Path limits
+ MinX = round(abs(stl.bb.minpt.x) + self.toolDiam, 6)
+ MinY = round(abs(stl.bb.minpt.y) + self.toolDiam, 6)
+ MaxX = round(abs(stl.bb.maxpt.x) + self.toolDiam, 6)
+ MaxY = round(abs(stl.bb.maxpt.y) + self.toolDiam, 6)
+ if MinX < xmin or MinY < ymin or MaxX > xmax or MaxY > ymax:
+ newPropMsg = translate(
+ "PathWaterline",
+ "The toolpath has exceeded the stock bounding box limits. Consider using a Boundary Dressup.",
+ )
+ FreeCAD.Console.PrintWarning(newPropMsg + "\n")
+ # Run the Scan (Processing ALL depths at once)
+ scanLines = self._waterlineAdaptiveScan(stl, smplInt, minSmplInt, depthparams, depOfst)
+
+ # Generate G-Code
+ layTime = time.time()
+ for loop in scanLines:
+ # We pass '0.0' as layDep because Adaptive loops have their own Z embedded
+ cmds = self._loopToGcode(obj, 0.0, loop)
+ commands.extend(cmds)
+
+ Path.Log.debug("--Adaptive generation took " + str(time.time() - layTime) + " s")
+
else:
+ # Setup BoundBox for Dropcutter grid
if subShp is None:
# Get correct boundbox
if obj.BoundBox == "Stock":
@@ -1303,81 +1298,43 @@ class ObjectWaterline(PathOp.ObjectOp):
xmax = subShp.BoundBox.XMax
ymin = subShp.BoundBox.YMin
ymax = subShp.BoundBox.YMax
-
- smplInt = obj.SampleInterval.Value
- minSmplInt = obj.MinSampleInterval.Value
- if minSmplInt > smplInt:
- minSmplInt = smplInt
-
- # Determine bounding box length for the OCL scan
- bbLength = math.fabs(ymax - ymin)
- numScanLines = int(math.ceil(bbLength / smplInt) + 1) # Number of lines
-
- # Compute number and size of stepdowns, and final depth
- if obj.LayerMode == "Single-pass":
- depthparams = [obj.FinalDepth.Value]
- else:
- depthparams = [dp for dp in self.depthParams]
- lenDP = len(depthparams)
-
- # Scan the piece to depth at smplInt
- oclScan = []
- scanLines = []
- if obj.Algorithm == "OCL Adaptive":
- # Check Stock's bounding box and Tool Path limits
- MinX = round(abs(stl.bb.minpt.x) + self.toolDiam, 6)
- MinY = round(abs(stl.bb.minpt.y) + self.toolDiam, 6)
- MaxX = round(abs(stl.bb.maxpt.x) + self.toolDiam, 6)
- MaxY = round(abs(stl.bb.maxpt.y) + self.toolDiam, 6)
- if MinX < xmin or MinY < ymin or MaxX > xmax or MaxY > ymax:
- newPropMsg = translate(
- "PathWaterline",
- "The toolpath has exceeded the stock bounding box limits. Consider using a Boundary Dressup.",
- )
- FreeCAD.Console.PrintWarning(newPropMsg + "\n")
- # Scan the piece
- scanLines = self._waterlineAdaptiveScan(stl, smplInt, minSmplInt, depthparams, depOfst)
- # Optimize loop. Separate the connected Path of the perimeter and internal features.
- if obj.OptimizeInternalFeatures:
- GapDetec = float(obj.GapDetectionThershold)
- if smplInt >= GapDetec:
- GapDetec = smplInt + 1 # We need smaller smplInt than GapDetec to identify Gaps
- optimize = self._optimizeAdaptive(scanLines, GapDetec)
- scanLines = optimize
- else: # Drop Cutter
- oclScan = self._waterlineDropCutScan(
- stl, smplInt, xmin, xmax, ymin, depthparams[lenDP - 1], numScanLines
- )
+
+ # Determine bounding box length for the OCL scan
+ bbLength = math.fabs(ymax - ymin)
+ numScanLines = int(math.ceil(bbLength / smplInt) + 1)
+
+ # Run Scan (Grid based)
+ fd = depthparams[-1]
+ oclScan = self._waterlineDropCutScan(stl, smplInt, xmin, xmax, ymin, fd, numScanLines)
oclScan = [FreeCAD.Vector(P.x, P.y, P.z + depOfst) for P in oclScan]
+ # Convert point list to grid (scanLines)
lenOS = len(oclScan)
ptPrLn = int(lenOS / numScanLines)
-
- # Convert oclScan list of points to multi-dimensional list
scanLines = []
for L in range(0, numScanLines):
scanLines.append([])
for P in range(0, ptPrLn):
pi = L * ptPrLn + P
scanLines[L].append(oclScan[pi])
- lenSL = len(scanLines)
- pntsPerLine = len(scanLines[0])
- msg = "--OCL scan: " + str(lenSL * pntsPerLine) + " points, with "
- msg += str(numScanLines) + " lines and " + str(pntsPerLine) + " pts/line"
- Path.Log.debug(msg)
- # Extract Wl layers per depthparams
- lyr = 0
- cmds = []
- layTime = time.time()
- self.topoMap = []
- for layDep in depthparams:
- cmds = self._getWaterline(obj, scanLines, layDep, lyr, lenSL, pntsPerLine)
- commands.extend(cmds)
- if obj.Algorithm == "OCL Adaptive":
- break # OCL_Adaptive processes all depths simultaneously (break loop)
- lyr += 1
- Path.Log.debug("--All layer scans combined took " + str(time.time() - layTime) + " s")
+ # Extract Waterline Layers Iteratively
+ lenSL = len(scanLines)
+ pntsPerLine = len(scanLines[0])
+ msg = "--OCL scan: " + str(lenSL * pntsPerLine) + " points, with "
+ msg += str(numScanLines) + " lines and " + str(pntsPerLine) + " pts/line"
+ Path.Log.debug(msg)
+
+ lyr = 0
+ cmds = []
+ layTime = time.time()
+ self.topoMap = []
+ for layDep in depthparams:
+ cmds = self._getWaterline(obj, scanLines, layDep, lyr, lenSL, pntsPerLine)
+ commands.extend(cmds)
+ lyr += 1
+ Path.Log.debug("--All layer scans combined took " + str(time.time() - layTime) + " s")
+
return commands
def _waterlineDropCutScan(self, stl, smplInt, xmin, xmax, ymin, fd, numScanLines):
@@ -1411,84 +1368,37 @@ class ObjectWaterline(PathOp.ObjectOp):
)
Path.Log.info("Waterline " + msg)
+ # Setup OCL AdaptiveWaterline
awl = ocl.AdaptiveWaterline()
awl.setSTL(stl)
awl.setCutter(self.cutter)
awl.setSampling(smplInt)
awl.setMinSampling(minSmplInt)
- # Create Adaptive loops
adapt_loops = []
- acnt = 0
+
+ # Iterate through each Z-depth
for zh in zheights:
- # zh = round(zh, 3)
- temp_loops = []
- finalZ_loops = []
- skipZ = False
awl.setZ(zh)
awl.run()
+
+ # OCL returns a list of separate loops (list of lists of Points)
+ # Example: [[PerimeterPoints], [HolePoints]]
temp_loops = awl.getLoops()
+
if not temp_loops:
- # Skip if height is above model
+ # Warn if the step is outside the model bounds
newPropMsg = translate("PathWaterline", "Step Down above model. Skipping height : ")
newPropMsg += "{} mm".format(zh)
FreeCAD.Console.PrintWarning(newPropMsg + "\n")
- skipZ = True
- acnt -= 1
- else:
- for tmp in temp_loops:
- finalZ_loops += tmp
+ continue
- if not skipZ:
- adapt_loops.append(acnt)
- adapt_loops[acnt] = [FreeCAD.Vector(P.x, P.y, P.z + depOfst) for P in finalZ_loops]
- acnt += 1
-
- # return the list of loops
- return adapt_loops
-
- def _optimizeAdaptive(self, adapt_loops, GapDetec):
- """Attempt to repair holes and internal features on model"""
-
- # Search for the gaps that caused by Internal features.
- new_adapt = []
- for adapt in adapt_loops:
- fz = adapt
- firstX = fz[0].x
- firstY = fz[0].y
- secLastX = fz[-1].x
- secLastY = fz[-1].y
-
- # First and last points in loop should not be in greater distance than Gap Detection Threshold.
- if not (secLastX - GapDetec) <= firstX <= (secLastX + GapDetec) or not (
- secLastY - GapDetec
- ) <= firstY <= (secLastY + GapDetec):
- # List with internal features found. Points in greater distance than GapDetec.
- fz.reverse()
- start_cut = 0
- # First point is known. Search for next points to break loop.
- for r in range(len(fz)):
- # This is the last Step of loop. Close what has been left.
- if r == (len(fz) - 1):
- r_fz = fz[(start_cut + 2) : len(fz)]
- r_fz.reverse()
- if len(r_fz) != 0: # check if anything left to append after cut
- new_adapt.append(r_fz)
- break
- if not (fz[r].x - GapDetec) <= fz[r + 1].x <= (fz[r].x + GapDetec) or not (
- fz[r].y - GapDetec
- ) <= fz[r + 1].y <= (fz[r].y + GapDetec):
- # Next point found.
- r_fz = fz[(start_cut + 2) : r]
- r_fz.reverse()
- if len(r_fz) != 0: # check if anything left to append after cut
- new_adapt.append(r_fz)
- start_cut = r
- # List without Gap, add as is.
- else:
- new_adapt.append(adapt)
-
- adapt_loops = new_adapt
+ # Process each loop separately.
+ # This ensures that islands (holes) remain distinct from perimeters.
+ for loop in temp_loops:
+ # Convert OCL Points to FreeCAD Vectors and apply Z offset
+ fc_loop = [FreeCAD.Vector(P.x, P.y, P.z + depOfst) for P in loop]
+ adapt_loops.append(fc_loop)
return adapt_loops
@@ -1514,7 +1424,6 @@ class ObjectWaterline(PathOp.ObjectOp):
for loop in loopList:
cmds = self._loopToGcode(obj, layDep, loop)
commands.extend(cmds)
-
return commands
def _createTopoMap(self, scanLines, layDep, lenSL, pntsPerLine):
@@ -1762,7 +1671,6 @@ class ObjectWaterline(PathOp.ObjectOp):
+ str(loopNum)
+ " loops."
)
-
return loopList
def _trackLoop(self, oclScan, lC, pC, L, P, loopNum):
@@ -1841,6 +1749,10 @@ class ObjectWaterline(PathOp.ObjectOp):
"""_loopToGcode(obj, layDep, loop) ... Convert set of loop points to Gcode."""
# generate the path commands
output = []
+
+ # Safety check for empty loops
+ if not loop:
+ return output
nxt = FreeCAD.Vector(0.0, 0.0, 0.0)
@@ -1858,6 +1770,7 @@ class ObjectWaterline(PathOp.ObjectOp):
output.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value, "F": self.vertRapid}))
else:
output.append(Path.Command("G0", {"Z": obj.SafeHeight.Value, "F": self.vertRapid}))
+
output.append(Path.Command("G0", {"X": pnt.x, "Y": pnt.y, "F": self.horizRapid}))
output.append(Path.Command("G1", {"Z": pnt.z, "F": self.vertFeed}))
@@ -1972,8 +1885,7 @@ class ObjectWaterline(PathOp.ObjectOp):
if cont:
# Identify solid areas in the offset data
if obj.CutPattern == "Offset" or obj.CutPattern == "None":
- if ofstArea:
- ofstSolidFacesList = self._getSolidAreasFromPlanarFaces(ofstArea)
+ ofstSolidFacesList = self._getSolidAreasFromPlanarFaces(ofstArea)
if ofstSolidFacesList:
clearArea = Part.makeCompound(ofstSolidFacesList)
self.showDebugObject(clearArea, "ClearArea_{}".format(caCnt))