diff --git a/src/Mod/CAM/CAMTests/TestPathAdaptive.py b/src/Mod/CAM/CAMTests/TestPathAdaptive.py
index 70d72ac086..a544de3553 100644
--- a/src/Mod/CAM/CAMTests/TestPathAdaptive.py
+++ b/src/Mod/CAM/CAMTests/TestPathAdaptive.py
@@ -463,6 +463,67 @@ class TestPathAdaptive(PathTestBase):
self.assertTrue(okAt10 and okAt5 and okAt0, "Path boundaries outside of expected regions")
+ def test09(self):
+ """test09() Tests Z stock to leave- with 1mm Z stock to leave, machining
+ at the top of the model should not touch the top model face"""
+ # Instantiate a Adaptive operation and set Base Geometry
+ adaptive = PathAdaptive.Create("Adaptive")
+ adaptive.Base = [(self.doc.Fusion, ["Face3", "Face10"])] # (base, subs_list)
+ adaptive.Label = "test09+"
+ adaptive.Comment = "test09() Verify Z stock is left as requested"
+
+ # Set additional operation properties
+ setDepthsAndHeights(adaptive, 15, 10)
+ adaptive.FinishingProfile = False
+ adaptive.HelixAngle = 75.0
+ adaptive.HelixDiameterLimit.Value = 1.0
+ adaptive.LiftDistance.Value = 1.0
+ adaptive.StepOver = 75
+ adaptive.UseOutline = False
+ adaptive.setExpression("StepDown", None)
+ adaptive.StepDown.Value = (
+ 5.0 # Have to set expression to None before numerical value assignment
+ )
+ # Add some Z stock to leave so we avoid Face3 in this stepdown at Z=10
+ adaptive.setExpression("ZStockToLeave", None)
+ adaptive.ZStockToLeave.Value = 1
+
+ _addViewProvider(adaptive)
+ self.doc.recompute()
+
+ # Check:
+ # - No feed path at depth Z=10 touchs Face3
+ toolr = adaptive.OpToolDiameter.Value / 2
+ tol = adaptive.Tolerance
+
+ # Make clean up math below- combine tool radius and tolerance into a
+ # single field that can be added/subtracted to/from bounding boxes
+ moffset = toolr - tol
+
+ # Offset the face we don't expect to touch, verify no move is within
+ # that boundary
+ # NOTE: This isn't a perfect test (won't catch moves that start and end
+ # outside of our face, but cut through/across it), but combined with
+ # other tests should be sufficient.
+ noPathTouchesFace3 = True
+ foffset = self.doc.Fusion.Shape.getElement("Face3").makeOffset2D(moffset)
+ # NOTE: Face3 is at Z=10, and the only feed moves will be at Z=10
+ lastpt = FreeCAD.Vector(0, 0, 10)
+ for p in [c.Parameters for c in adaptive.Path.Commands if c.Name in ["G1", "G01"]]:
+ pt = FreeCAD.Vector(lastpt)
+ if "X" in p:
+ pt.x = p.get("X")
+ if "Y" in p:
+ pt.x = p.get("Y")
+
+ if foffset.isInside(pt, 0.001, True):
+ noPathTouchesFace3 = False
+ break
+
+ lastpt = pt
+
+ self.assertTrue(noPathTouchesFace3, "No feed moves within the top face.")
+
# Eclass
diff --git a/src/Mod/CAM/Gui/Resources/panels/PageOpAdaptiveEdit.ui b/src/Mod/CAM/Gui/Resources/panels/PageOpAdaptiveEdit.ui
index 08db3a609f..91ae04896c 100644
--- a/src/Mod/CAM/Gui/Resources/panels/PageOpAdaptiveEdit.ui
+++ b/src/Mod/CAM/Gui/Resources/panels/PageOpAdaptiveEdit.ui
@@ -43,103 +43,19 @@
-
- QFrame::StyledPanel
+ QFrame::Shape::StyledPanel
- QFrame::Raised
+ QFrame::Shadow::Raised
-
-
-
-
- Use Outline
-
-
-
- -
-
-
- Keep Tool Down Ratio
-
-
-
- -
-
-
- Helix Max Diameter
-
-
-
- -
-
-
- Type of adaptive operation
-
-
-
- -
-
-
- Cut Region
-
-
-
- -
-
-
- Step Over Percent
-
-
-
- -
-
-
- Force Clearing Inside-out
-
-
-
- -
-
-
- Lift Distance
-
-
-
- -
-
-
- Stock to Leave
-
-
-
- -
-
-
- Operation Type
-
-
-
- -
-
-
- Helix Ramp Angle
-
-
-
- -
-
-
- Finishing Profile
-
-
-
-
- QFrame::StyledPanel
+ QFrame::Shape::StyledPanel
- QFrame::Raised
+ QFrame::Shadow::Raised
-
@@ -152,7 +68,9 @@
-
- Influences calculation performance vs stability and accuracy
+ Influences calculation performance vs stability and accuracy.
+
+Larger values (further to the right) will calculate faster; smaller values (further to the left) will result in more accurate toolpaths.
5
@@ -167,7 +85,7 @@
10
- Qt::Horizontal
+ Qt::Orientation::Horizontal
1
@@ -177,6 +95,13 @@
+ -
+
+
+ Force Clearing Inside-out
+
+
+
-
@@ -184,53 +109,37 @@
- -
-
+
-
+
+
+ Type of adaptive operation
+
+
+
+ -
+
- Helix Cone Angle
-
-
-
- -
-
-
- If greater than zero it limits the helix ramp diameter, otherwise 75 percent of tool diameter is used
-
-
-
-
-
-
- -
-
-
- How much to lift the tool up during the rapid linking moves over cleared regions. If linking path is not clear tool is raised to clearance height.
-
-
-
-
-
-
- -
-
-
- Max length of keep-tool-down linking path compared to direct distance between points. If exceeded link will be done by raising the tool to clearance height.
-
-
-
+ Finishing Profile
-
-
+
- How much material to leave (i.e. for finishing operation)
+ How much material to leave in the XY plane (i.e. for finishing operation)
+ -
+
+
+ XY Stock to Leave
+
+
+
-
@@ -250,13 +159,10 @@
- -
-
-
- Angle of the helix ramp entry
-
-
-
+
-
+
+
+ Step Over Percent
@@ -270,6 +176,128 @@
+ -
+
+
+ Helix Ramp Angle
+
+
+
+ -
+
+
+ Use Outline
+
+
+
+ -
+
+
+ Operation Type
+
+
+
+ -
+
+
+ How much to lift the tool up during the rapid linking moves over cleared regions. If linking path is not clear tool is raised to clearance height.
+
+
+
+
+
+
+ -
+
+
+ Keep Tool Down Ratio
+
+
+
+ -
+
+
+ If greater than zero it limits the helix ramp diameter, otherwise 75 percent of tool diameter is used
+
+
+
+
+
+
+ -
+
+
+ Helix Cone Angle
+
+
+
+ -
+
+
+ Angle of the helix ramp entry
+
+
+
+
+
+
+ -
+
+
+ Lift Distance
+
+
+
+ -
+
+
+ Cut Region
+
+
+
+ -
+
+
+ Max length of keep-tool-down linking path compared to direct distance between points. If exceeded link will be done by raising the tool to clearance height.
+
+
+
+
+
+
+ -
+
+
+ Helix Max Diameter
+
+
+
+ -
+
+
+ After calculating toolpaths, the default cut order is by depth- all regions at a given stepdown are cleared before moving to the next stepdown.
+
+This option changes that behavior to cut each discrete area to its full depth before moving on to the next.
+
+
+ Order cuts by region
+
+
+
+ -
+
+
+ Z Stock to Leave
+
+
+
+ -
+
+
+ How much material to leave along the Z axis (i.e. for finishing operation)
+
+
+
@@ -283,7 +311,7 @@
-
- Qt::Vertical
+ Qt::Orientation::Vertical
diff --git a/src/Mod/CAM/Path/Op/Adaptive.py b/src/Mod/CAM/Path/Op/Adaptive.py
index 75d3b62aa5..51c7d8b656 100644
--- a/src/Mod/CAM/Path/Op/Adaptive.py
+++ b/src/Mod/CAM/Path/Op/Adaptive.py
@@ -628,6 +628,8 @@ def Execute(op, obj):
{
"opType": outsideOpType,
"path2d": convertTo2d(rdict["edges"]),
+ "id": rdict["id"],
+ "children": rdict["children"],
# FIXME: Kinda gross- just use this to match up with the
# appropriate stockpaths entry...
"startdepth": rdict["depths"][0],
@@ -641,6 +643,8 @@ def Execute(op, obj):
{
"opType": insideOpType,
"path2d": convertTo2d(rdict["edges"]),
+ "id": rdict["id"],
+ "children": rdict["children"],
# FIXME: Kinda gross- just use this to match up with the
# appropriate stockpaths entry...
"startdepth": rdict["depths"][0],
@@ -668,6 +672,8 @@ def Execute(op, obj):
"finishingProfile": obj.FinishingProfile,
"keepToolDownRatio": keepToolDownRatio,
"stockToLeave": obj.StockToLeave.Value,
+ "zStockToLeave": obj.ZStockToLeave.Value,
+ "orderCutsByRegion": obj.OrderCutsByRegion,
}
insideInputStateObject = {
@@ -683,6 +689,8 @@ def Execute(op, obj):
"finishingProfile": obj.FinishingProfile,
"keepToolDownRatio": keepToolDownRatio,
"stockToLeave": obj.StockToLeave.Value,
+ "zStockToLeave": obj.ZStockToLeave.Value,
+ "orderCutsByRegion": obj.OrderCutsByRegion,
}
inputStateObject = [outsideInputStateObject, insideInputStateObject]
@@ -740,6 +748,7 @@ def Execute(op, obj):
a2d.toolDiameter = op.tool.Diameter.Value
a2d.helixRampDiameter = helixDiameter
a2d.keepToolDownDistRatio = keepToolDownRatio
+ # NOTE: Z stock is handled in our stepdowns
a2d.stockToLeave = obj.StockToLeave.Value
a2d.tolerance = obj.Tolerance
a2d.forceInsideOut = obj.ForceInsideOut
@@ -762,9 +771,25 @@ def Execute(op, obj):
for t in alltuples:
depths += [d for d in t[0]]
depths = sorted(list(set(depths)), reverse=True)
- for d in depths:
- cutlist += [([d], o[1]) for o in outsidePathArray2dDepthTuples if d in o[0]]
- cutlist += [([d], i[1]) for i in insidePathArray2dDepthTuples if d in i[0]]
+ if obj.OrderCutsByRegion:
+ # Translate child ID numbers to an actual reference to the
+ # associated tuple
+ for rdict in regionOps:
+ rdict["childTuples"] = [t for t in alltuples if t[1]["id"] in rdict["children"]]
+
+ # Helper function to recurse down children
+ def addToCutList(tuples):
+ for k in tuples:
+ if k in cutlist:
+ continue
+ cutlist.append(k)
+ addToCutList(k[1]["childTuples"])
+
+ addToCutList(alltuples)
+ else:
+ for d in depths:
+ cutlist += [([d], o[1]) for o in outsidePathArray2dDepthTuples if d in o[0]]
+ cutlist += [([d], i[1]) for i in insidePathArray2dDepthTuples if d in i[0]]
# need to convert results to python object to be JSON serializable
stepdown = max(obj.StepDown.Value, _ADAPTIVE_MIN_STEPDOWN)
@@ -972,7 +997,9 @@ def _workingEdgeHelperManual(op, obj, depths):
lastdepth = depth
continue
- aboveRefined = _getSolidProjection(shps, depth)
+ # NOTE: Slice stock lower than cut depth to effectively leave (at least)
+ # obj.ZStockToLeave
+ aboveRefined = _getSolidProjection(shps, depth - obj.ZStockToLeave.Value)
# Create appropriate tuples and add to list, processing inside/outside
# as requested by operation
@@ -1079,10 +1106,14 @@ def _getWorkingEdges(op, obj):
# Get the stock outline at each stepdown. Used to calculate toolpaths and
# for calcuating cut regions in some instances
+ # NOTE: Slice stock lower than cut depth to effectively leave (at least)
+ # obj.ZStockToLeave
# NOTE: Stock is handled DIFFERENTLY than inside and outside regions!
# Combining different depths just adds code to look up the correct outline
# when computing inside/outside regions, for no real benefit.
- stockProjectionDict = {d: _getSolidProjection(op.stock.Shape, d) for d in depths}
+ stockProjectionDict = {
+ d: _getSolidProjection(op.stock.Shape, d - obj.ZStockToLeave.Value) for d in depths
+ }
# If user specified edges, calculate the machining regions based on that
# input. Otherwise, process entire model
@@ -1092,6 +1123,101 @@ def _getWorkingEdges(op, obj):
# to be avoided at those depths.
insideRegions, outsideRegions = _workingEdgeHelperManual(op, obj, depths)
+ # Find all children of each region. A child of region X is any region Y such
+ # that Y is a subset of X AND Y starts within one stepdown of X (ie, direct
+ # children only).
+ # NOTE: Inside and outside regions are inverses of each other, so above
+ # refers to the area to be machined!
+
+ # Assign an ID number to track each region
+ idnumber = 0
+ for r in insideRegions + outsideRegions:
+ r["id"] = idnumber
+ r["children"] = list()
+ idnumber += 1
+
+ # NOTE: Inside and outside regions are inverses of each other
+ # NOTE: Outside regions can't have parents
+ for rx in insideRegions:
+ for ry in [k for k in insideRegions if k != rx]:
+ dist = min(rx["depths"]) - max(ry["depths"])
+ # Ignore regions at our level or above, or more than one step down
+ if dist <= 0 or dist > depthParams.step_down:
+ continue
+ if not ry["region"].cut(rx["region"]).Wires:
+ rx["children"].append(ry["id"])
+ # See which outside region this is a child of- basically inverse of above
+ for ry in [k for k in outsideRegions]:
+ dist = min(ry["depths"]) - max(rx["depths"])
+ # Ignore regions at our level or above, or more than one step down
+ if dist <= 0 or dist > depthParams.step_down:
+ continue
+ # child if there is NO overlap between the stay-outside and stay-
+ # inside regions
+ # Also a child if the outer region is NULL (includes everything)
+ # NOTE: See "isNull() note" at top of file
+ if not ry["region"].Wires or not rx["region"].common(ry["region"]).Wires:
+ ry["children"].append(rx["id"])
+
+ # Further split regions as necessary for when the stock changes- a region as
+ # reported here is where a toolpath will be generated, and can be projected
+ # along all of the depths associated with it. By doing this, we can minimize
+ # the number of toolpaths that need to be generated AND avoid more complex
+ # logic in depth-first vs region-first sorting of regions.
+ # NOTE: For internal regions, stock is "the same" if the region cut with
+ # the stock results in the same region.
+ # NOTE: For external regions, stock is "the same" if the stock cut by the
+ # region results in the same region
+ def _regionChildSplitterHelper(regions, areInsideRegions):
+ nonlocal stockProjectionDict
+ nonlocal idnumber
+ for r in regions:
+ depths = sorted(r["depths"], reverse=True)
+ if areInsideRegions:
+ rcut = r["region"].cut(stockProjectionDict[depths[0]])
+ else:
+ # NOTE: We may end up with empty "outside" regions in the space
+ # between the top of the stock and the top of the model- want
+ # to machine the entire stock in that case
+ # NOTE: See "isNull() note" at top of file
+ if not r["region"].Wires:
+ rcut = stockProjectionDict[depths[0]]
+ else:
+ rcut = stockProjectionDict[depths[0]].cut(r["region"])
+ parentdepths = depths[0:1]
+ # If the region cut with the stock at a new depth is different than
+ # the original cut, we need to split this region
+ # The new region gets all of the children, and becomes a child of
+ # the existing region.
+ for d in depths[1:]:
+ if (
+ areInsideRegions and r["region"].cut(stockProjectionDict[d]).cut(rcut).Wires
+ ) or stockProjectionDict[d].cut(r["region"]).cut(rcut).Wires:
+ newregion = {
+ "id": idnumber,
+ "depths": [k for k in depths if k not in parentdepths],
+ "region": r["region"],
+ "children": r["children"],
+ }
+ # Update parent with the new region as a child, along with all
+ # the depths it was unchanged on
+ r["children"] = [idnumber]
+ r["depths"] = parentdepths
+
+ # Add the new region to the end of the list and stop processing
+ # this region
+ # When the new region is processed at the end, we'll effectively
+ # recurse and handle splitting that new region if required
+ regions.append(newregion)
+ idnumber += 1
+ continue
+ # If we didn't split at this depth, the parent will keep "control"
+ # of this depth
+ parentdepths.append(d)
+
+ _regionChildSplitterHelper(insideRegions, True)
+ _regionChildSplitterHelper(outsideRegions, False)
+
# Create discretized regions
def _createDiscretizedRegions(regionDicts):
discretizedRegions = list()
@@ -1100,6 +1226,8 @@ def _getWorkingEdges(op, obj):
{
"edges": [[discretize(w)] for w in rdict["region"].Wires],
"depths": rdict["depths"],
+ "id": rdict["id"],
+ "children": rdict["children"],
}
)
return discretizedRegions
@@ -1190,11 +1318,6 @@ class PathAdaptive(PathOp.ObjectOp):
"Side of selected faces that tool should cut",
),
)
- # obj.Side = [
- # "Outside",
- # "Inside",
- # ] # side of profile that cutter is on in relation to direction of profile
-
obj.addProperty(
"App::PropertyEnumeration",
"OperationType",
@@ -1204,11 +1327,6 @@ class PathAdaptive(PathOp.ObjectOp):
"Type of adaptive operation",
),
)
- # obj.OperationType = [
- # "Clearing",
- # "Profiling",
- # ] # side of profile that cutter is on in relation to direction of profile
-
obj.addProperty(
"App::PropertyFloat",
"Tolerance",
@@ -1251,7 +1369,16 @@ class PathAdaptive(PathOp.ObjectOp):
"Adaptive",
QT_TRANSLATE_NOOP(
"App::Property",
- "How much stock to leave (i.e. for finishing operation)",
+ "How much stock to leave in the XY plane (eg for finishing operation)",
+ ),
+ )
+ obj.addProperty(
+ "App::PropertyDistance",
+ "ZStockToLeave",
+ "Adaptive",
+ QT_TRANSLATE_NOOP(
+ "App::Property",
+ "How much stock to leave along the Z axis (eg for finishing operation)",
),
)
obj.addProperty(
@@ -1279,7 +1406,6 @@ class PathAdaptive(PathOp.ObjectOp):
QT_TRANSLATE_NOOP("App::Property", "Stop processing"),
)
obj.setEditorMode("Stopped", 2) # hide this property
-
obj.addProperty(
"App::PropertyBool",
"StopProcessing",
@@ -1290,7 +1416,6 @@ class PathAdaptive(PathOp.ObjectOp):
),
)
obj.setEditorMode("StopProcessing", 2) # hide this property
-
obj.addProperty(
"App::PropertyBool",
"UseHelixArcs",
@@ -1300,7 +1425,6 @@ class PathAdaptive(PathOp.ObjectOp):
"Use Arcs (G2) for helix ramp",
),
)
-
obj.addProperty(
"App::PropertyPythonObject",
"AdaptiveInputState",
@@ -1348,7 +1472,6 @@ class PathAdaptive(PathOp.ObjectOp):
"Limit helix entry diameter, if limit larger than tool diameter or 0, tool diameter is used",
),
)
-
obj.addProperty(
"App::PropertyBool",
"UseOutline",
@@ -1358,7 +1481,15 @@ class PathAdaptive(PathOp.ObjectOp):
"Uses the outline of the base geometry.",
),
)
-
+ obj.addProperty(
+ "App::PropertyBool",
+ "OrderCutsByRegion",
+ "Adaptive",
+ QT_TRANSLATE_NOOP(
+ "App::Property",
+ "Orders cuts by region instead of depth.",
+ ),
+ )
obj.addProperty(
"Part::PropertyPartShape",
"removalshape",
@@ -1390,9 +1521,11 @@ class PathAdaptive(PathOp.ObjectOp):
obj.AdaptiveInputState = ""
obj.AdaptiveOutputState = ""
obj.StockToLeave = 0
+ obj.ZStockToLeave = 0
obj.KeepToolDownRatio = 3.0
obj.UseHelixArcs = False
obj.UseOutline = False
+ obj.OrderCutsByRegion = False
FeatureExtensions.set_default_property_values(obj, job)
def opExecute(self, obj):
@@ -1427,6 +1560,28 @@ class PathAdaptive(PathOp.ObjectOp):
"Uses the outline of the base geometry.",
)
+ if not hasattr(obj, "OrderCutsByRegion"):
+ obj.addProperty(
+ "App::PropertyBool",
+ "OrderCutsByRegion",
+ "Adaptive",
+ QT_TRANSLATE_NOOP(
+ "App::Property",
+ "Orders cuts by region instead of depth.",
+ ),
+ )
+
+ if not hasattr(obj, "ZStockToLeave"):
+ obj.addProperty(
+ "App::PropertyDistance",
+ "ZStockToLeave",
+ "Adaptive",
+ QT_TRANSLATE_NOOP(
+ "App::Property",
+ "How much stock to leave along the Z axis (eg for finishing operation)",
+ ),
+ )
+
if not hasattr(obj, "removalshape"):
obj.addProperty("Part::PropertyPartShape", "removalshape", "Path", "")
obj.setEditorMode("removalshape", 2) # hide
@@ -1446,6 +1601,7 @@ def SetupProperties():
"LiftDistance",
"KeepToolDownRatio",
"StockToLeave",
+ "ZStockToLeave",
"ForceInsideOut",
"FinishingProfile",
"Stopped",
@@ -1457,6 +1613,7 @@ def SetupProperties():
"HelixConeAngle",
"HelixDiameterLimit",
"UseOutline",
+ "OrderCutsByRegion",
]
return setup
diff --git a/src/Mod/CAM/Path/Op/Gui/Adaptive.py b/src/Mod/CAM/Path/Op/Gui/Adaptive.py
index a9ae651faa..c2438dd21c 100644
--- a/src/Mod/CAM/Path/Op/Gui/Adaptive.py
+++ b/src/Mod/CAM/Path/Op/Gui/Adaptive.py
@@ -50,6 +50,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
)
self.form.KeepToolDownRatio.setProperty("unit", obj.KeepToolDownRatio.getUserPreferred()[2])
self.form.StockToLeave.setProperty("unit", obj.StockToLeave.getUserPreferred()[2])
+ self.form.ZStockToLeave.setProperty("unit", obj.ZStockToLeave.getUserPreferred()[2])
def getSignalsForUpdate(self, obj):
"""getSignalsForUpdate(obj) ... return list of signals for updating obj"""
@@ -65,10 +66,12 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
signals.append(self.form.LiftDistance.valueChanged)
signals.append(self.form.KeepToolDownRatio.valueChanged)
signals.append(self.form.StockToLeave.valueChanged)
+ signals.append(self.form.ZStockToLeave.valueChanged)
signals.append(self.form.coolantController.currentIndexChanged)
signals.append(self.form.ForceInsideOut.stateChanged)
signals.append(self.form.FinishingProfile.stateChanged)
signals.append(self.form.useOutline.stateChanged)
+ signals.append(self.form.orderCutsByRegion.stateChanged)
signals.append(self.form.StopButton.toggled)
return signals
@@ -97,9 +100,13 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
if hasattr(obj, "StockToLeave"):
self.form.StockToLeave.setProperty("rawValue", obj.StockToLeave.Value)
+ if hasattr(obj, "ZStockToLeave"):
+ self.form.ZStockToLeave.setProperty("rawValue", obj.ZStockToLeave.Value)
+
self.form.ForceInsideOut.setChecked(obj.ForceInsideOut)
self.form.FinishingProfile.setChecked(obj.FinishingProfile)
self.form.useOutline.setChecked(obj.UseOutline)
+ self.form.orderCutsByRegion.setChecked(obj.OrderCutsByRegion)
self.setupToolController(obj, self.form.ToolController)
self.setupCoolant(obj, self.form.coolantController)
self.form.StopButton.setChecked(obj.Stopped)
@@ -130,9 +137,13 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
if hasattr(obj, "StockToLeave"):
PathGuiUtil.updateInputField(obj, "StockToLeave", self.form.StockToLeave)
+ if hasattr(obj, "ZStockToLeave"):
+ PathGuiUtil.updateInputField(obj, "ZStockToLeave", self.form.ZStockToLeave)
+
obj.ForceInsideOut = self.form.ForceInsideOut.isChecked()
obj.FinishingProfile = self.form.FinishingProfile.isChecked()
obj.UseOutline = self.form.useOutline.isChecked()
+ obj.OrderCutsByRegion = self.form.orderCutsByRegion.isChecked()
obj.Stopped = self.form.StopButton.isChecked()
if obj.Stopped:
self.form.StopButton.setChecked(False) # reset the button