diff --git a/src/Mod/CAM/Gui/Resources/panels/PageOpWaterlineEdit.ui b/src/Mod/CAM/Gui/Resources/panels/PageOpWaterlineEdit.ui
index 173f0d78b4..4b1036a8df 100644
--- a/src/Mod/CAM/Gui/Resources/panels/PageOpWaterlineEdit.ui
+++ b/src/Mod/CAM/Gui/Resources/panels/PageOpWaterlineEdit.ui
@@ -70,7 +70,7 @@
-
- Select the algorithm to use: 'OCL Dropcutter*', or 'Experimental' (not OCL based).
+ Select the algorithm to use: 'OCL Dropcutter*', 'OCL Adaptive*' or 'Experimental' (not OCL based).
@@ -205,7 +205,24 @@ A step over of 100% results in no overlap between two different cycles.
- -
+
-
+
+
+ Min Sample interval
+
+
+
+ -
+
+
+ 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.
@@ -249,6 +266,7 @@ A step over of 100% results in no overlap between two different cycles.
cutPattern
stepOver
sampleInterval
+ minSampleInterval
optimizeEnabled
diff --git a/src/Mod/CAM/Path/Op/Gui/Waterline.py b/src/Mod/CAM/Path/Op/Gui/Waterline.py
index 53633f5307..29f1ec48c7 100644
--- a/src/Mod/CAM/Path/Op/Gui/Waterline.py
+++ b/src/Mod/CAM/Path/Op/Gui/Waterline.py
@@ -87,6 +87,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
if obj.StepOver != self.form.stepOver.value():
obj.StepOver = self.form.stepOver.value()
+ PathGuiUtil.updateInputField(obj, "MinSampleInterval", self.form.minSampleInterval)
PathGuiUtil.updateInputField(obj, "SampleInterval", self.form.sampleInterval)
if obj.OptimizeLinearPaths != self.form.optimizeEnabled.isChecked():
@@ -104,6 +105,9 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
FreeCAD.Units.Quantity(obj.BoundaryAdjustment.Value, FreeCAD.Units.Length).UserString
)
self.form.stepOver.setValue(obj.StepOver)
+ self.form.minSampleInterval.setText(
+ FreeCAD.Units.Quantity(obj.MinSampleInterval.Value, FreeCAD.Units.Length).UserString
+ )
self.form.sampleInterval.setText(
FreeCAD.Units.Quantity(obj.SampleInterval.Value, FreeCAD.Units.Length).UserString
)
@@ -126,7 +130,9 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
signals.append(self.form.cutPattern.currentIndexChanged)
signals.append(self.form.boundaryAdjustment.editingFinished)
signals.append(self.form.stepOver.editingFinished)
+ signals.append(self.form.minSampleInterval.editingFinished)
signals.append(self.form.sampleInterval.editingFinished)
+
if hasattr(self.form.optimizeEnabled, "checkStateChanged"): # Qt version >= 6.7.0
signals.append(self.form.optimizeEnabled.checkStateChanged)
else: # Qt version < 6.7.0
@@ -146,6 +152,19 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
self.form.boundaryAdjustment_label.hide()
self.form.stepOver.hide()
self.form.stepOver_label.hide()
+ self.form.minSampleInterval.hide()
+ self.form.minSampleInterval_label.hide()
+ self.form.sampleInterval.show()
+ self.form.sampleInterval_label.show()
+ elif Algorithm == "OCL Adaptive":
+ self.form.cutPattern.hide()
+ self.form.cutPattern_label.hide()
+ self.form.boundaryAdjustment.hide()
+ self.form.boundaryAdjustment_label.hide()
+ self.form.stepOver.hide()
+ self.form.stepOver_label.hide()
+ self.form.minSampleInterval.show()
+ self.form.minSampleInterval_label.show()
self.form.sampleInterval.show()
self.form.sampleInterval_label.show()
elif Algorithm == "Experimental":
@@ -159,6 +178,8 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
else:
self.form.stepOver.show()
self.form.stepOver_label.show()
+ self.form.minSampleInterval.hide()
+ self.form.minSampleInterval_label.hide()
self.form.sampleInterval.hide()
self.form.sampleInterval_label.hide()
diff --git a/src/Mod/CAM/Path/Op/SurfaceSupport.py b/src/Mod/CAM/Path/Op/SurfaceSupport.py
index 0d31e60fff..4c8a7a6d24 100644
--- a/src/Mod/CAM/Path/Op/SurfaceSupport.py
+++ b/src/Mod/CAM/Path/Op/SurfaceSupport.py
@@ -37,7 +37,7 @@ import math
# lazily loaded modules
from lazy_loader.lazy_loader import LazyLoader
-# MeshPart = LazyLoader('MeshPart', globals(), 'MeshPart')
+MeshPart = LazyLoader("MeshPart", globals(), "MeshPart") # tessellate bug Workaround
Part = LazyLoader("Part", globals(), "Part")
@@ -1254,6 +1254,22 @@ 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()."""
+ # Determine Deflection Values
+ lin_def = obj.LinearDeflection.Value
+ ang_def = obj.AngularDeflection.Value
+
+ # Apply Overrides for Waterline OCL Adaptive
+ # OCL Adaptive is a Vector-based algorithm, not a Grid-based algorithm (like Dropcutter)
+ # This fundamental difference makes it sensitive to Topology (how points connect) rather than just density
+ # Models with internal features can cause the algorithm to be confused even with very high density values.
+ # The following values create the cleanest possible Topology for a vector-slicing algorithm
+ # Setting those values here rather than hacking the Obj values in Waterline.py is preferable.
+ algo = getattr(obj, "Algorithm", None)
+ if algo == "OCL Adaptive":
+ # Force the "Sweet Spot" values for topology stability (Good enough for 99% or more of operations)
+ lin_def = 0.001
+ ang_def = 0.15
+
if model_type == "M":
facets = model.Mesh.Facets.Points
else:
@@ -1261,7 +1277,15 @@ def _makeSTL(model, obj, ocl, model_type=None):
shape = model.Shape
else:
shape = model
- vertices, facet_indices = shape.tessellate(obj.LinearDeflection.Value)
+ # vertices, facet_indices = shape.tessellate(obj.LinearDeflection.Value) # tessellate workaround
+ # Workaround for tessellate bug
+ mesh = MeshPart.meshFromShape(
+ Shape=shape,
+ LinearDeflection=obj.lin_def,
+ AngularDeflection=obj.ang_def,
+ )
+ vertices = [point.Vector for point in mesh.Points]
+ facet_indices = [facet.PointIndices for facet in mesh.Facets]
facets = ((vertices[f[0]], vertices[f[1]], vertices[f[2]]) for f in facet_indices)
stl = ocl.STLSurf()
for tri in facets:
diff --git a/src/Mod/CAM/Path/Op/Waterline.py b/src/Mod/CAM/Path/Op/Waterline.py
index 36b93214a8..88d915d2a2 100644
--- a/src/Mod/CAM/Path/Op/Waterline.py
+++ b/src/Mod/CAM/Path/Op/Waterline.py
@@ -95,6 +95,7 @@ class ObjectWaterline(PathOp.ObjectOp):
enums = {
"Algorithm": [
(translate("path_waterline", "OCL Dropcutter"), "OCL Dropcutter"),
+ (translate("path_waterline", "OCL Adaptive"), "OCL Adaptive"),
(translate("path_waterline", "Experimental"), "Experimental"),
],
"BoundBox": [
@@ -289,7 +290,7 @@ class ObjectWaterline(PathOp.ObjectOp):
"Clearing Options",
QT_TRANSLATE_NOOP(
"App::Property",
- "Select the algorithm to use: OCL Dropcutter*, or Experimental (Not OCL based).",
+ "Select the algorithm to use: OCL Dropcutter*, OCL Adaptive or Experimental (Not OCL based).",
),
),
(
@@ -392,6 +393,15 @@ class ObjectWaterline(PathOp.ObjectOp):
"Set the sampling resolution. Smaller values quickly increase processing time.",
),
),
+ (
+ "App::PropertyDistance",
+ "MinSampleInterval",
+ "Clearing Options",
+ QT_TRANSLATE_NOOP(
+ "App::Property",
+ "Set the minimum sampling resolution. Smaller values quickly increase processing time.",
+ ),
+ ),
(
"App::PropertyFloat",
"StepOver",
@@ -479,6 +489,7 @@ class ObjectWaterline(PathOp.ObjectOp):
"CutPatternAngle": 0.0,
"DepthOffset": 0.0,
"SampleInterval": 1.0,
+ "MinSampleInterval": 0.005,
"BoundaryAdjustment": 0.0,
"InternalFeaturesAdjustment": 0.0,
"AvoidLastX_Faces": 0,
@@ -505,7 +516,7 @@ class ObjectWaterline(PathOp.ObjectOp):
def setEditorProperties(self, obj):
# Used to hide inputs in properties list
expMode = G = 0
- show = hide = A = B = C = 2
+ show = hide = A = B = C = D = 2
obj.setEditorMode("BoundaryEnforcement", hide)
obj.setEditorMode("InternalFeaturesAdjustment", hide)
@@ -521,9 +532,12 @@ class ObjectWaterline(PathOp.ObjectOp):
if obj.Algorithm == "OCL Dropcutter":
pass
+ elif obj.Algorithm == "OCL Adaptive":
+ D = 0
+ expMode = 2
elif obj.Algorithm == "Experimental":
A = B = C = 0
- expMode = G = show = hide = 2
+ expMode = G = D = show = hide = 2
cutPattern = obj.CutPattern
if obj.ClearLastLayer != "Off":
@@ -549,6 +563,7 @@ class ObjectWaterline(PathOp.ObjectOp):
obj.setEditorMode("IgnoreOuterAbove", B)
obj.setEditorMode("CutPattern", C)
obj.setEditorMode("SampleInterval", G)
+ obj.setEditorMode("MinSampleInterval", D)
obj.setEditorMode("LinearDeflection", expMode)
obj.setEditorMode("AngularDeflection", expMode)
@@ -649,6 +664,24 @@ class ObjectWaterline(PathOp.ObjectOp):
)
)
+ # Limit min sample interval
+ if obj.MinSampleInterval.Value < 0.0001:
+ obj.MinSampleInterval.Value = 0.0001
+ Path.Log.error(
+ translate(
+ "PathWaterline",
+ "Min Sample interval limits are 0.0001 to 25.4 millimeters.",
+ )
+ )
+ if obj.MinSampleInterval.Value > 25.4:
+ obj.MinSampleInterval.Value = 25.4
+ Path.Log.error(
+ translate(
+ "PathWaterline",
+ "Min Sample interval limits are 0.0001 to 25.4 millimeters.",
+ )
+ )
+
# Limit cut pattern angle
if obj.CutPatternAngle < -360.0:
obj.CutPatternAngle = 0.0
@@ -912,7 +945,7 @@ class ObjectWaterline(PathOp.ObjectOp):
for m in range(0, len(JOB.Model.Group)):
# Create OCL.stl model objects
- if obj.Algorithm == "OCL Dropcutter":
+ if obj.Algorithm == "OCL Dropcutter" or obj.Algorithm == "OCL Adaptive":
PathSurfaceSupport._prepareModelSTLs(self, JOB, obj, m, ocl)
Mdl = JOB.Model.Group[m]
@@ -930,7 +963,7 @@ class ObjectWaterline(PathOp.ObjectOp):
)
Path.Log.info("Working on Model.Group[{}]: {}".format(m, Mdl.Label))
# make stock-model-voidShapes STL model for avoidance detection on transitions
- if obj.Algorithm == "OCL Dropcutter":
+ if obj.Algorithm == "OCL Dropcutter" or obj.Algorithm == "OCL Adaptive":
PathSurfaceSupport._makeSafeSTL(self, JOB, obj, m, FACES[m], VOIDS[m], ocl)
# Process model/faces - OCL objects must be ready
CMDS.extend(self._processWaterlineAreas(JOB, obj, m, FACES[m], VOIDS[m]))
@@ -1027,7 +1060,7 @@ class ObjectWaterline(PathOp.ObjectOp):
COMP = ADD
final.append(Path.Command("G0", {"Z": obj.SafeHeight.Value, "F": self.vertRapid}))
- if obj.Algorithm == "OCL Dropcutter":
+ if obj.Algorithm == "OCL Dropcutter" or obj.Algorithm == "OCL Adaptive":
final.extend(
self._oclWaterlineOp(JOB, obj, mdlIdx, COMP)
) # independent method set for Waterline
@@ -1053,7 +1086,7 @@ class ObjectWaterline(PathOp.ObjectOp):
COMP = ADD
final.append(Path.Command("G0", {"Z": obj.SafeHeight.Value, "F": self.vertRapid}))
- if obj.Algorithm == "OCL Dropcutter":
+ if obj.Algorithm == "OCL Dropcutter" or obj.Algorithm == "OCL Adaptive":
final.extend(
self._oclWaterlineOp(JOB, obj, mdlIdx, COMP)
) # independent method set for Waterline
@@ -1184,7 +1217,7 @@ class ObjectWaterline(PathOp.ObjectOp):
pdc.setSampling(SampleInterval) # set sampling size
return pdc
- # OCL Dropcutter waterline functions
+ # OCL Dropcutter - OCL Adaptive waterline functions
def _oclWaterlineOp(self, JOB, obj, mdlIdx, subShp=None):
"""_oclWaterlineOp(obj, base) ... Main waterline function to perform waterline extraction from model."""
commands = []
@@ -1200,35 +1233,10 @@ 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
-
- if subShp is None:
- # Get correct boundbox
- if obj.BoundBox == "Stock":
- BS = JOB.Stock
- bb = BS.Shape.BoundBox
- elif obj.BoundBox == "BaseBoundBox":
- BS = base
- bb = base.Shape.BoundBox
-
- xmin = bb.XMin
- xmax = bb.XMax
- ymin = bb.YMin
- ymax = bb.YMax
- else:
- xmin = subShp.BoundBox.XMin
- xmax = subShp.BoundBox.XMax
- ymin = subShp.BoundBox.YMin
- ymax = subShp.BoundBox.YMax
-
smplInt = obj.SampleInterval.Value
- minSampInt = 0.001 # value is mm
- if smplInt < minSampInt:
- smplInt = minSampInt
-
- # Determine bounding box length for the OCL scan
- bbLength = math.fabs(ymax - ymin)
- numScanLines = int(math.ceil(bbLength / smplInt) + 1) # Number of lines
+ minSmplInt = obj.MinSampleInterval.Value
+ if minSmplInt > smplInt:
+ minSmplInt = smplInt
# Compute number and size of stepdowns, and final depth
if obj.LayerMode == "Single-pass":
@@ -1238,37 +1246,107 @@ class ObjectWaterline(PathOp.ObjectOp):
lenDP = len(depthparams)
# Scan the piece to depth at smplInt
- oclScan = []
- oclScan = self._waterlineDropCutScan(
- stl, smplInt, xmin, xmax, ymin, depthparams[lenDP - 1], numScanLines
- )
- oclScan = [FreeCAD.Vector(P.x, P.y, P.z + depOfst) for P in oclScan]
- lenOS = len(oclScan)
- ptPrLn = int(lenOS / numScanLines)
+ if obj.Algorithm == "OCL Adaptive":
+ # Get Stock Bounding Box
+ BS = JOB.Stock
+ stock_bb = BS.Shape.BoundBox
- # 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)
+ # Stock Limits
+ s_xmin = stock_bb.XMin
+ s_xmax = stock_bb.XMax
+ s_ymin = stock_bb.YMin
+ s_ymax = stock_bb.YMax
+
+ # Calculate Tool Path Limits based on OCL STL
+ path_min_x = stl.bb.minpt.x - self.radius
+ path_min_y = stl.bb.minpt.y - self.radius
+ path_max_x = stl.bb.maxpt.x + self.radius
+ path_max_y = stl.bb.maxpt.y + self.radius
+
+ # Compare with a tiny tolerance
+ tol = 0.001
+ if (
+ (path_min_x < s_xmin - tol)
+ or (path_min_y < s_ymin - tol)
+ or (path_max_x > s_xmax + tol)
+ or (path_max_y > s_ymax + tol)
+ ):
+
+ 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":
+ BS = JOB.Stock
+ bb = BS.Shape.BoundBox
+ elif obj.BoundBox == "BaseBoundBox":
+ BS = base
+ bb = BS.Shape.BoundBox
+
+ xmin = bb.XMin
+ xmax = bb.XMax
+ ymin = bb.YMin
+ ymax = bb.YMax
+ else:
+ xmin = subShp.BoundBox.XMin
+ xmax = subShp.BoundBox.XMax
+ ymin = subShp.BoundBox.YMin
+ ymax = subShp.BoundBox.YMax
+
+ # 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)
+ scanLines = []
+ for L in range(0, numScanLines):
+ scanLines.append([])
+ for P in range(0, ptPrLn):
+ pi = L * ptPrLn + P
+ scanLines[L].append(oclScan[pi])
+
+ # 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")
- # 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)
- 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):
@@ -1294,20 +1372,66 @@ class ObjectWaterline(PathOp.ObjectOp):
# return the list of points
return pdc.getCLPoints()
+ def _waterlineAdaptiveScan(self, stl, smplInt, minSmplInt, zheights, depOfst):
+ """Perform OCL Adaptive scan for waterline purpose."""
+
+ msg = translate(
+ "Waterline", ": Steps below the model's top Face will be the only ones processed."
+ )
+ Path.Log.info("Waterline " + msg)
+
+ # Setup OCL AdaptiveWaterline
+ awl = ocl.AdaptiveWaterline()
+ awl.setSTL(stl)
+ awl.setCutter(self.cutter)
+ awl.setSampling(smplInt)
+ awl.setMinSampling(minSmplInt)
+
+ adapt_loops = []
+
+ # Iterate through each Z-depth
+ for zh in zheights:
+ 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:
+ # 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")
+ continue
+
+ # 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
+
def _getWaterline(self, obj, scanLines, layDep, lyr, lenSL, pntsPerLine):
"""_getWaterline(obj, scanLines, layDep, lyr, lenSL, pntsPerLine) ... Get waterline."""
commands = []
cmds = []
loopList = []
self.topoMap = []
- # Create topo map from scanLines (highs and lows)
- self.topoMap = self._createTopoMap(scanLines, layDep, lenSL, pntsPerLine)
- # Add buffer lines and columns to topo map
- self._bufferTopoMap(lenSL, pntsPerLine)
- # Identify layer waterline from OCL scan
- self._highlightWaterline(4, 9)
- # Extract waterline and convert to gcode
- loopList = self._extractWaterlines(obj, scanLines, lyr, layDep)
+ if obj.Algorithm == "OCL Adaptive":
+ loopList = scanLines
+ else:
+ # Create topo map from scanLines (highs and lows)
+ self.topoMap = self._createTopoMap(scanLines, layDep, lenSL, pntsPerLine)
+ # Add buffer lines and columns to topo map
+ self._bufferTopoMap(lenSL, pntsPerLine)
+ # Identify layer waterline from OCL scan
+ self._highlightWaterline(4, 9)
+ # Extract waterline and convert to gcode
+ loopList = self._extractWaterlines(obj, scanLines, lyr, layDep)
+
# save commands
for loop in loopList:
cmds = self._loopToGcode(obj, layDep, loop)
@@ -1638,14 +1762,27 @@ class ObjectWaterline(PathOp.ObjectOp):
# generate the path commands
output = []
- # prev = FreeCAD.Vector(2135984513.165, -58351896873.17455, 13838638431.861)
+ # Safety check for empty loops
+ if not loop:
+ return output
+
nxt = FreeCAD.Vector(0.0, 0.0, 0.0)
- # Create first point
- pnt = FreeCAD.Vector(loop[0].x, loop[0].y, layDep)
+ # Create (first and last) point
+ if obj.Algorithm == "OCL Adaptive":
+ if obj.CutMode == "Climb":
+ # Reverse loop for Climb Milling
+ loop.reverse()
+ pnt = pnt1 = FreeCAD.Vector(loop[0].x, loop[0].y, loop[0].z)
+ else:
+ pnt = FreeCAD.Vector(loop[0].x, loop[0].y, layDep)
# Position cutter to begin loop
- output.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value, "F": self.vertRapid}))
+ if self.layerEndPnt.x == 0 and self.layerEndPnt.y == 0: # First to Clearance Height
+ 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}))
@@ -1656,13 +1793,19 @@ class ObjectWaterline(PathOp.ObjectOp):
if i < lastIdx:
nxt.x = loop[i + 1].x
nxt.y = loop[i + 1].y
- nxt.z = layDep
-
+ if obj.Algorithm == "OCL Adaptive":
+ nxt.z = loop[i + 1].z
+ else:
+ nxt.z = layDep
output.append(Path.Command("G1", {"X": pnt.x, "Y": pnt.y, "F": self.horizFeed}))
# Rotate point data
pnt = nxt
+ # Connect first and last points for Adaptive
+ if obj.Algorithm == "OCL Adaptive":
+ output.append(Path.Command("G1", {"X": pnt1.x, "Y": pnt1.y, "F": self.horizFeed}))
+
# Save layer end point for use in transitioning to next layer
self.layerEndPnt = pnt