CAM: Adaptive: Add Z stock to leave (separate from XY stock to leave) and order-by-region/order-by-depth cut ordering options
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -43,103 +43,19 @@
|
||||
<item>
|
||||
<widget class="QFrame" name="frame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
<enum>QFrame::Shape::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
<enum>QFrame::Shadow::Raised</enum>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="25" column="0">
|
||||
<widget class="QCheckBox" name="useOutline">
|
||||
<property name="text">
|
||||
<string>Use Outline</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="18" column="0">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string>Keep Tool Down Ratio</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="14" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>Helix Max Diameter</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="OperationType">
|
||||
<property name="toolTip">
|
||||
<string>Type of adaptive operation</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Cut Region</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Step Over Percent</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="23" column="0">
|
||||
<widget class="QCheckBox" name="ForceInsideOut">
|
||||
<property name="text">
|
||||
<string>Force Clearing Inside-out</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="16" column="0">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>Lift Distance</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="20" column="0">
|
||||
<widget class="QLabel" name="label_12">
|
||||
<property name="text">
|
||||
<string>Stock to Leave</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Operation Type</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Helix Ramp Angle</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="24" column="0">
|
||||
<widget class="QCheckBox" name="FinishingProfile">
|
||||
<property name="text">
|
||||
<string>Finishing Profile</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0" colspan="2">
|
||||
<widget class="QFrame" name="frame_3">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
<enum>QFrame::Shape::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
<enum>QFrame::Shadow::Raised</enum>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
@@ -152,7 +68,9 @@
|
||||
<item>
|
||||
<widget class="QSlider" name="Tolerance">
|
||||
<property name="toolTip">
|
||||
<string>Influences calculation performance vs stability and accuracy</string>
|
||||
<string>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.</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>5</number>
|
||||
@@ -167,7 +85,7 @@
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="tickInterval">
|
||||
<number>1</number>
|
||||
@@ -177,6 +95,13 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="23" column="0">
|
||||
<widget class="QCheckBox" name="ForceInsideOut">
|
||||
<property name="text">
|
||||
<string>Force Clearing Inside-out</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="Side">
|
||||
<property name="toolTip">
|
||||
@@ -184,53 +109,37 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="OperationType">
|
||||
<property name="toolTip">
|
||||
<string>Type of adaptive operation</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="24" column="0">
|
||||
<widget class="QCheckBox" name="FinishingProfile">
|
||||
<property name="text">
|
||||
<string>Helix Cone Angle</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="14" column="1">
|
||||
<widget class="Gui::QuantitySpinBox" name="HelixDiameterLimit">
|
||||
<property name="toolTip">
|
||||
<string>If greater than zero it limits the helix ramp diameter, otherwise 75 percent of tool diameter is used</string>
|
||||
</property>
|
||||
<property name="unit" stdset="0">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="16" column="1">
|
||||
<widget class="Gui::QuantitySpinBox" name="LiftDistance">
|
||||
<property name="toolTip">
|
||||
<string>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.</string>
|
||||
</property>
|
||||
<property name="unit" stdset="0">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="18" column="1">
|
||||
<widget class="Gui::QuantitySpinBox" name="KeepToolDownRatio">
|
||||
<property name="toolTip">
|
||||
<string>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.</string>
|
||||
</property>
|
||||
<property name="unit" stdset="0">
|
||||
<string notr="true"/>
|
||||
<string>Finishing Profile</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="20" column="1">
|
||||
<widget class="Gui::QuantitySpinBox" name="StockToLeave">
|
||||
<widget class="Gui::QuantitySpinBox" name="StockToLeave" native="true">
|
||||
<property name="toolTip">
|
||||
<string>How much material to leave (i.e. for finishing operation)</string>
|
||||
<string>How much material to leave in the XY plane (i.e. for finishing operation)</string>
|
||||
</property>
|
||||
<property name="unit" stdset="0">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="20" column="0">
|
||||
<widget class="QLabel" name="label_12">
|
||||
<property name="text">
|
||||
<string>XY Stock to Leave</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QSpinBox" name="stepOverPercent">
|
||||
<property name="toolTip">
|
||||
@@ -250,13 +159,10 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="1">
|
||||
<widget class="Gui::InputField" name="HelixAngle">
|
||||
<property name="toolTip">
|
||||
<string>Angle of the helix ramp entry</string>
|
||||
</property>
|
||||
<property name="unit" stdset="0">
|
||||
<string notr="true"/>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Step Over Percent</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -270,6 +176,128 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Helix Ramp Angle</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="25" column="0">
|
||||
<widget class="QCheckBox" name="useOutline">
|
||||
<property name="text">
|
||||
<string>Use Outline</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Operation Type</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="16" column="1">
|
||||
<widget class="Gui::QuantitySpinBox" name="LiftDistance" native="true">
|
||||
<property name="toolTip">
|
||||
<string>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.</string>
|
||||
</property>
|
||||
<property name="unit" stdset="0">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="18" column="0">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string>Keep Tool Down Ratio</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="14" column="1">
|
||||
<widget class="Gui::QuantitySpinBox" name="HelixDiameterLimit" native="true">
|
||||
<property name="toolTip">
|
||||
<string>If greater than zero it limits the helix ramp diameter, otherwise 75 percent of tool diameter is used</string>
|
||||
</property>
|
||||
<property name="unit" stdset="0">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>Helix Cone Angle</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="1">
|
||||
<widget class="Gui::InputField" name="HelixAngle">
|
||||
<property name="toolTip">
|
||||
<string>Angle of the helix ramp entry</string>
|
||||
</property>
|
||||
<property name="unit" stdset="0">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="16" column="0">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>Lift Distance</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Cut Region</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="18" column="1">
|
||||
<widget class="Gui::QuantitySpinBox" name="KeepToolDownRatio" native="true">
|
||||
<property name="toolTip">
|
||||
<string>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.</string>
|
||||
</property>
|
||||
<property name="unit" stdset="0">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="14" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>Helix Max Diameter</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="26" column="0">
|
||||
<widget class="QCheckBox" name="orderCutsByRegion">
|
||||
<property name="toolTip">
|
||||
<string>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.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Order cuts by region</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="21" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Z Stock to Leave</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="21" column="1">
|
||||
<widget class="Gui::QuantitySpinBox" name="ZStockToLeave" native="true">
|
||||
<property name="toolTip">
|
||||
<string>How much material to leave along the Z axis (i.e. for finishing operation)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -283,7 +311,7 @@
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user