Merge pull request #23149 from Dimitris75/OCL-Adaptive
CAM: Add OCL Adaptive Algorithm to Waterline Operation
This commit is contained in:
@@ -70,7 +70,7 @@
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="algorithmSelect">
|
||||
<property name="toolTip">
|
||||
<string>Select the algorithm to use: 'OCL Dropcutter*', or 'Experimental' (not OCL based).</string>
|
||||
<string>Select the algorithm to use: 'OCL Dropcutter*', 'OCL Adaptive*' or 'Experimental' (not OCL based).</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -205,7 +205,24 @@ A step over of 100% results in no overlap between two different cycles.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="minSampleInterval_label">
|
||||
<property name="text">
|
||||
<string>Min Sample interval</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="Gui::InputField" name="minSampleInterval">
|
||||
<property name="toolTip">
|
||||
<string>Set the minimum sampling resolution. Smaller values quickly increase processing time.</string>
|
||||
</property>
|
||||
<property name="unit" stdset="0">
|
||||
<string notr="true">mm</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<widget class="QCheckBox" name="optimizeEnabled">
|
||||
<property name="toolTip">
|
||||
<string>Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-code output.</string>
|
||||
@@ -249,6 +266,7 @@ A step over of 100% results in no overlap between two different cycles.</string>
|
||||
<tabstop>cutPattern</tabstop>
|
||||
<tabstop>stepOver</tabstop>
|
||||
<tabstop>sampleInterval</tabstop>
|
||||
<tabstop>minSampleInterval</tabstop>
|
||||
<tabstop>optimizeEnabled</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user