[CAM] implement multipass profile operations (#17326)

* implement multipass profile operations

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
David Kaufman
2024-12-13 12:20:32 -05:00
committed by GitHub
parent 00f6fbeaa3
commit 1955f28053
4 changed files with 115 additions and 5 deletions

View File

@@ -2420,14 +2420,10 @@ void Area::makeOffset(list<shared_ptr<CArea>>& areas,
#endif
if (offset < 0) {
stepover = -fabs(stepover);
if (count < 0) {
if (!last_stepover) {
last_stepover = offset * 0.5;
}
else {
last_stepover = -fabs(last_stepover);
}
}
else {
last_stepover = 0;

View File

@@ -115,6 +115,43 @@
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="numPassesLabel">
<property name="text">
<string>Number of Passes</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="numPasses">
<property name="minimum">
<number>1</number>
</property>
<property name="toolTip">
<string>The number of passes to do. If more than one, requires a non-zero value for Pass Stepover.</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="stepoverLabel">
<property name="text">
<string>Pass Stepover</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="Gui::InputField" name="stepover">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>If doing multiple passes, the extra offset of each additional pass.</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@@ -77,6 +77,8 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
if obj.Direction != str(self.form.direction.currentData()):
obj.Direction = str(self.form.direction.currentData())
PathGuiUtil.updateInputField(obj, "OffsetExtra", self.form.extraOffset)
obj.NumPasses = self.form.numPasses.value()
PathGuiUtil.updateInputField(obj, "Stepover", self.form.stepover)
if obj.UseComp != self.form.useCompensation.isChecked():
obj.UseComp = self.form.useCompensation.isChecked()
@@ -100,6 +102,10 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
self.form.extraOffset.setText(
FreeCAD.Units.Quantity(obj.OffsetExtra.Value, FreeCAD.Units.Length).UserString
)
self.form.numPasses.setValue(obj.NumPasses)
self.form.stepover.setText(
FreeCAD.Units.Quantity(obj.Stepover.Value, FreeCAD.Units.Length).UserString
)
self.form.useCompensation.setChecked(obj.UseComp)
self.form.useStartPoint.setChecked(obj.UseStartPoint)
@@ -117,6 +123,8 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
signals.append(self.form.cutSide.currentIndexChanged)
signals.append(self.form.direction.currentIndexChanged)
signals.append(self.form.extraOffset.editingFinished)
signals.append(self.form.numPasses.editingFinished)
signals.append(self.form.stepover.editingFinished)
signals.append(self.form.useCompensation.stateChanged)
signals.append(self.form.useStartPoint.stateChanged)
signals.append(self.form.processHoles.stateChanged)
@@ -148,8 +156,11 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
self.form.processHoles.hide()
self.form.processPerimeter.hide()
self.form.stepover.setEnabled(self.obj.NumPasses > 1)
def registerSignalHandlers(self, obj):
self.form.useCompensation.stateChanged.connect(self.updateVisibility)
self.form.numPasses.editingFinished.connect(self.updateVisibility)
# Eclass

View File

@@ -172,6 +172,24 @@ class ObjectProfile(PathAreaOp.ObjectOp):
"App::Property", "Make True, if using Cutter Radius Compensation"
),
),
(
"App::PropertyInteger",
"NumPasses",
"Profile",
QT_TRANSLATE_NOOP(
"App::Property",
"The number of passes to do. If more than one, requires a non-zero value for Stepover",
),
),
(
"App::PropertyDistance",
"Stepover",
"Profile",
QT_TRANSLATE_NOOP(
"App::Property",
"If doing multiple passes, the extra offset of each additional pass",
),
),
]
@classmethod
@@ -235,6 +253,8 @@ class ObjectProfile(PathAreaOp.ObjectOp):
"processCircles": False,
"processHoles": False,
"processPerimeter": True,
"Stepover": 0,
"NumPasses": 1,
}
def areaOpApplyPropertyDefaults(self, obj, job, propList):
@@ -295,6 +315,26 @@ class ObjectProfile(PathAreaOp.ObjectOp):
self.initAreaOpProperties(obj, warn=True)
self.areaOpSetDefaultValues(obj, PathUtils.findParentJob(obj))
self.setOpEditorProperties(obj)
if not hasattr(obj, "NumPasses"):
obj.addProperty(
"App::PropertyInteger",
"NumPasses",
"Profile",
QT_TRANSLATE_NOOP(
"App::Property",
"The number of passes to do. Requires a non-zero value for Stepover",
),
)
if not hasattr(obj, "Stepover"):
obj.addProperty(
"App::PropertyDistance",
"Stepover",
"Profile",
QT_TRANSLATE_NOOP(
"App::Property",
"If doing multiple passes, the extra offset of each additional pass",
),
)
def areaOpOnChanged(self, obj, prop):
"""areaOpOnChanged(obj, prop) ... updates certain property visibilities depending on changed properties."""
@@ -311,13 +351,31 @@ class ObjectProfile(PathAreaOp.ObjectOp):
params["SectionCount"] = -1
offset = obj.OffsetExtra.Value # 0.0
num_passes = max(1, obj.NumPasses)
stepover = obj.Stepover.Value
if num_passes > 1 and stepover == 0:
# This check is important because C++ code has a default value for stepover if it's 0 and extra passes are requested
num_passes = 1
Path.Log.warning(
"Multipass profile requires a non-zero stepover. Reducing to a single pass."
)
if obj.UseComp:
offset = self.radius + obj.OffsetExtra.Value
if obj.Side == "Inside":
offset = 0 - offset
stepover = -stepover
if isHole:
offset = 0 - offset
stepover = -stepover
# Modify offset and stepover to do passes from most-offset to least
offset += stepover * (num_passes - 1)
stepover = -stepover
params["Offset"] = offset
params["ExtraPass"] = num_passes - 1
params["Stepover"] = stepover
jointype = ["Round", "Square", "Miter"]
params["JoinType"] = jointype.index(obj.JoinType)
@@ -356,6 +414,10 @@ class ObjectProfile(PathAreaOp.ObjectOp):
else:
params["orientation"] = 0
if obj.NumPasses > 1:
# Disable path sorting to ensure that offsets appear in order, from farthest offset to closest, on all layers
params["sort_mode"] = 0
return params
def areaOpUseProjection(self, obj):
@@ -590,7 +652,11 @@ class ObjectProfile(PathAreaOp.ObjectOp):
if flattened and zDiff >= self.JOB.GeometryTolerance.Value:
cutWireObjs = False
openEdges = []
passOffsets = [self.ofstRadius]
params = self.areaOpAreaParams(obj, False)
passOffsets = [
self.ofstRadius + i * abs(params["Stepover"])
for i in range(params["ExtraPass"] + 1)
][::-1]
(origWire, flatWire) = flattened
self._addDebugObject("FlatWire", flatWire)