diff --git a/src/Mod/CAM/Gui/Resources/panels/PageOpWaterlineEdit.ui b/src/Mod/CAM/Gui/Resources/panels/PageOpWaterlineEdit.ui
index 1e7b8c6cf4..9b565219d3 100644
--- a/src/Mod/CAM/Gui/Resources/panels/PageOpWaterlineEdit.ui
+++ b/src/Mod/CAM/Gui/Resources/panels/PageOpWaterlineEdit.ui
@@ -26,14 +26,14 @@
-
- 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,7 @@
-
- Coolant mode
+ Coolant Mode
@@ -63,7 +63,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).
@@ -76,7 +76,7 @@
- Bounding box
+ Bounding Box
@@ -88,14 +88,14 @@
- Select the overall boundary for the operation
+ Select the overall boundary for the operation.
-
- Layer mode
+ Layer Mode
@@ -107,14 +107,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 +126,7 @@
- Set the geometric clearing pattern to use for the operation
+ Set the geometric clearing pattern to use for the operation.
@@ -139,14 +139,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
@@ -198,16 +198,60 @@ 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.
- Optimize linear paths
+ 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
+
+
+
@@ -242,7 +286,10 @@ A step over of 100% results in no overlap between two different cycles.
cutPattern
stepOver
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 dba1cd1533..641187ae50 100644
--- a/src/Mod/CAM/Path/Op/Gui/Waterline.py
+++ b/src/Mod/CAM/Path/Op/Gui/Waterline.py
@@ -86,7 +86,13 @@ 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.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()
@@ -103,9 +109,21 @@ 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
- )
+ )
+
+ 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)
@@ -125,7 +143,14 @@ 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)
+ 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)
else: # Qt version < 6.7.0
@@ -139,15 +164,40 @@ 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()
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()
+ 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()
+ 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()
+ 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()
@@ -158,8 +208,13 @@ 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()
+ 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/SurfaceSupport.py b/src/Mod/CAM/Path/Op/SurfaceSupport.py
index 55bce6c161..64e3b64427 100644
--- a/src/Mod/CAM/Path/Op/SurfaceSupport.py
+++ b/src/Mod/CAM/Path/Op/SurfaceSupport.py
@@ -36,7 +36,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")
@@ -1260,7 +1260,11 @@ 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=0.001, AngularDeflection=0.25)
+ 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 5714565d85..f9c11e1706 100644
--- a/src/Mod/CAM/Path/Op/Waterline.py
+++ b/src/Mod/CAM/Path/Op/Waterline.py
@@ -23,6 +23,7 @@
import FreeCAD
+
__title__ = "CAM Waterline Operation"
__author__ = "russ4262 (Russell Johnson), sliptonic (Brad Collette)"
__url__ = "https://www.freecad.org"
@@ -48,6 +49,7 @@ 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
@@ -94,11 +96,12 @@ 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": [
- (translate("path_waterline", "BaseBoundBox"), "BaseBoundBox"),
(translate("path_waterline", "Stock"), "Stock"),
+ (translate("path_waterline", "BaseBoundBox"), "BaseBoundBox"),
],
"PatternCenterAt": [
(translate("path_waterline", "CenterOfMass"), "CenterOfMass"),
@@ -288,7 +291,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).",
),
),
(
@@ -391,6 +394,33 @@ 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::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",
@@ -478,6 +508,9 @@ class ObjectWaterline(PathOp.ObjectOp):
"CutPatternAngle": 0.0,
"DepthOffset": 0.0,
"SampleInterval": 1.0,
+ "MinSampleInterval": 0.005,
+ "OptimizeInternalFeatures": False,
+ "GapDetectionThershold": 3.50,
"BoundaryAdjustment": 0.0,
"InternalFeaturesAdjustment": 0.0,
"AvoidLastX_Faces": 0,
@@ -504,25 +537,28 @@ 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)
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
+ 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 = H = show = hide = 2
cutPattern = obj.CutPattern
if obj.ClearLastLayer != "Off":
@@ -548,6 +584,11 @@ 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)
@@ -644,6 +685,42 @@ class ObjectWaterline(PathOp.ObjectOp):
"Sample interval limits are 0.0001 to 25.4 millimeters.",
)
)
+
+ # 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 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:
@@ -908,7 +985,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]
@@ -926,7 +1003,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]))
@@ -1023,7 +1100,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
@@ -1049,7 +1126,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
@@ -1176,11 +1253,11 @@ class ObjectWaterline(PathOp.ObjectOp):
pdc = ocl.PathDropCutter() # create a pdc [PathDropCutter] object
pdc.setSTL(stl) # add stl model
pdc.setCutter(cutter) # add cutter
- pdc.setZ(finalDep) # set minimumZ (final / target depth value)
+ pdc.setZ(finalDep) # set minimumZ (final / target depth value)
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 = []
@@ -1197,30 +1274,40 @@ class ObjectWaterline(PathOp.ObjectOp):
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 obj.Algorithm == "OCL Adaptive":
+ # Get Stock boundbox for OCL Adaptive
+ BS = JOB.Stock
+ bb = BS.Shape.BoundBox
+ xmin = abs(bb.XMin)
+ xmax = abs(bb.XMax)
+ ymin = abs(bb.YMin)
+ ymax = abs(bb.YMax)
+
+ else:
+ 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
- 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
+ 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
+ minSmplInt = obj.MinSampleInterval.Value
+ if minSmplInt > smplInt:
+ minSmplInt = smplInt
# Determine bounding box length for the OCL scan
bbLength = math.fabs(ymax - ymin)
@@ -1235,20 +1322,42 @@ class ObjectWaterline(PathOp.ObjectOp):
# 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)
+ zheights = []
+ 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
+ )
+ oclScan = [FreeCAD.Vector(P.x, P.y, P.z + depOfst) for P in oclScan]
+
+ 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])
+ # 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 "
@@ -1263,7 +1372,9 @@ class ObjectWaterline(PathOp.ObjectOp):
for layDep in depthparams:
cmds = self._getWaterline(obj, scanLines, layDep, lyr, lenSL, pntsPerLine)
commands.extend(cmds)
- lyr += 1
+ 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")
return commands
@@ -1289,25 +1400,121 @@ 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."""
+ aloops = []
+
+ msg = translate("Waterline", ": Steps below the model's top Face will be the only ones processed.")
+ Path.Log.info("Waterline " + msg)
+
+ awl = ocl.AdaptiveWaterline()
+ awl.setSTL(stl)
+ awl.setCutter(self.cutter)
+ awl.setSampling(smplInt)
+ awl.setMinSampling(minSmplInt)
+
+ # Create Adaptive loops
+ adapt_loops = []
+ acnt = 0
+ skippedZ = []
+ for zh in zheights:
+ #zh = round(zh, 3)
+ temp_loops = []
+ finalZ_loops = []
+ skipZ = False
+ awl.setZ(zh)
+ awl.run()
+ temp_loops = awl.getLoops()
+ if not temp_loops:
+ # Skip if height is above model
+ 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
+
+ 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
+
+ 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)
commands.extend(cmds)
+
return commands
def _createTopoMap(self, scanLines, layDep, lenSL, pntsPerLine):
@@ -1555,6 +1762,7 @@ class ObjectWaterline(PathOp.ObjectOp):
+ str(loopNum)
+ " loops."
)
+
return loopList
def _trackLoop(self, oclScan, lC, pC, L, P, loopNum):
@@ -1634,14 +1842,22 @@ class ObjectWaterline(PathOp.ObjectOp):
# generate the path commands
output = []
- # prev = FreeCAD.Vector(2135984513.165, -58351896873.17455, 13838638431.861)
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}))
@@ -1652,13 +1868,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
-
+ 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
@@ -1750,7 +1972,8 @@ class ObjectWaterline(PathOp.ObjectOp):
if cont:
# Identify solid areas in the offset data
if obj.CutPattern == "Offset" or obj.CutPattern == "None":
- ofstSolidFacesList = self._getSolidAreasFromPlanarFaces(ofstArea)
+ if ofstArea:
+ ofstSolidFacesList = self._getSolidAreasFromPlanarFaces(ofstArea)
if ofstSolidFacesList:
clearArea = Part.makeCompound(ofstSolidFacesList)
self.showDebugObject(clearArea, "ClearArea_{}".format(caCnt))