| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | __title__ = "CAM Mill Facing Operation" |
| | __author__ = "sliptonic (Brad Collette)" |
| | __url__ = "https://www.freecad.org" |
| | __doc__ = "Class and implementation of Mill Facing operation." |
| | __contributors__ = "" |
| |
|
| | import FreeCAD |
| | from PySide import QtCore |
| | import Path |
| | import Path.Op.Base as PathOp |
| |
|
| | import Path.Base.Generator.spiral_facing as spiral_facing |
| | import Path.Base.Generator.zigzag_facing as zigzag_facing |
| | import Path.Base.Generator.directional_facing as directional_facing |
| | import Path.Base.Generator.bidirectional_facing as bidirectional_facing |
| | import Path.Base.Generator.linking as linking |
| | import PathScripts.PathUtils as PathUtils |
| | import Path.Base.FeedRate as FeedRate |
| |
|
| | |
| | from lazy_loader.lazy_loader import LazyLoader |
| |
|
| | Part = LazyLoader("Part", globals(), "Part") |
| | Arcs = LazyLoader("draftgeoutils.arcs", globals(), "draftgeoutils.arcs") |
| | if FreeCAD.GuiUp: |
| | FreeCADGui = LazyLoader("FreeCADGui", globals(), "FreeCADGui") |
| |
|
| |
|
| | translate = FreeCAD.Qt.translate |
| |
|
| |
|
| | if False: |
| | Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) |
| | Path.Log.trackModule(Path.Log.thisModule()) |
| | else: |
| | Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) |
| |
|
| |
|
| | class ObjectMillFacing(PathOp.ObjectOp): |
| | """Proxy object for Mill Facing operation.""" |
| |
|
| | def opFeatures(self, obj): |
| | """opFeatures(obj) ... return all standard features""" |
| | return ( |
| | PathOp.FeatureTool |
| | | PathOp.FeatureDepths |
| | | PathOp.FeatureHeights |
| | | PathOp.FeatureStepDown |
| | | PathOp.FeatureCoolant |
| | ) |
| |
|
| | def initOperation(self, obj): |
| | """initOperation(obj) ... Initialize the operation by |
| | managing property creation and property editor status.""" |
| | self.propertiesReady = False |
| |
|
| | self.initOpProperties(obj) |
| |
|
| | def initOpProperties(self, obj, warn=False): |
| | """initOpProperties(obj) ... create operation specific properties""" |
| | Path.Log.track() |
| | self.addNewProps = list() |
| |
|
| | for prtyp, nm, grp, tt in self.opPropertyDefinitions(): |
| | if not hasattr(obj, nm): |
| | obj.addProperty(prtyp, nm, grp, tt) |
| | self.addNewProps.append(nm) |
| |
|
| | |
| | if len(self.addNewProps) > 0: |
| | ENUMS = self.propertyEnumerations() |
| | for n in ENUMS: |
| | if n[0] in self.addNewProps: |
| | setattr(obj, n[0], n[1]) |
| | if warn: |
| | newPropMsg = translate("CAM_MIllFacing", "New property added to") |
| | newPropMsg += ' "{}": {}'.format(obj.Label, self.addNewProps) + ". " |
| | newPropMsg += translate("CAM_MillFacing", "Check default value(s).") |
| | FreeCAD.Console.PrintWarning(newPropMsg + "\n") |
| |
|
| | self.propertiesReady = True |
| |
|
| | def onChanged(self, obj, prop): |
| | """onChanged(obj, prop) ... Called when a property changes""" |
| | if prop == "StepOver" and hasattr(obj, "StepOver"): |
| | |
| | if obj.StepOver < 0: |
| | obj.StepOver = 0 |
| | elif obj.StepOver > 100: |
| | obj.StepOver = 100 |
| |
|
| | if prop == "Active" and obj.ViewObject: |
| | obj.ViewObject.signalChangeIcon() |
| |
|
| | def opPropertyDefinitions(self): |
| | """opPropertyDefinitions(obj) ... Store operation specific properties""" |
| |
|
| | return [ |
| | ( |
| | "App::PropertyEnumeration", |
| | "CutMode", |
| | "Facing", |
| | QtCore.QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "Set the cut mode for the operation.", |
| | ), |
| | ), |
| | ( |
| | "App::PropertyEnumeration", |
| | "ClearingPattern", |
| | "Facing", |
| | QtCore.QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "Set the clearing pattern for the operation.", |
| | ), |
| | ), |
| | ( |
| | "App::PropertyAngle", |
| | "Angle", |
| | "Facing", |
| | QtCore.QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "Set the angle for the operation.", |
| | ), |
| | ), |
| | ( |
| | "App::PropertyPercent", |
| | "StepOver", |
| | "Facing", |
| | QtCore.QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "Set the stepover percentage of tool diameter.", |
| | ), |
| | ), |
| | ( |
| | "App::PropertyDistance", |
| | "AxialStockToLeave", |
| | "Facing", |
| | QtCore.QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "Set the stock to leave for the operation.", |
| | ), |
| | ), |
| | ( |
| | "App::PropertyDistance", |
| | "PassExtension", |
| | "Facing", |
| | QtCore.QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "Distance to extend cuts beyond polygon boundary for tool disengagement.", |
| | ), |
| | ), |
| | ( |
| | "App::PropertyDistance", |
| | "StockExtension", |
| | "Facing", |
| | QtCore.QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "Extends the boundary in both direction.", |
| | ), |
| | ), |
| | ( |
| | "App::PropertyBool", |
| | "Reverse", |
| | "Facing", |
| | QtCore.QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "Reverse the cutting direction for the selected pattern.", |
| | ), |
| | ), |
| | ] |
| |
|
| | @classmethod |
| | def propertyEnumerations(self, dataType="data"): |
| | """propertyEnumerations(dataType="data")... return property enumeration lists of specified dataType. |
| | Args: |
| | dataType = 'data', 'raw', 'translated' |
| | Notes: |
| | 'data' is list of internal string literals used in code |
| | 'raw' is list of (translated_text, data_string) tuples |
| | 'translated' is list of translated string literals |
| | """ |
| | Path.Log.track() |
| |
|
| | enums = { |
| | "CutMode": [ |
| | (translate("CAM_MillFacing", "Climb"), "Climb"), |
| | (translate("CAM_MillFacing", "Conventional"), "Conventional"), |
| | ], |
| | "ClearingPattern": [ |
| | (translate("CAM_MillFacing", "ZigZag"), "ZigZag"), |
| | (translate("CAM_MillFacing", "Bidirectional"), "Bidirectional"), |
| | (translate("CAM_MillFacing", "Directional"), "Directional"), |
| | (translate("CAM_MillFacing", "Spiral"), "Spiral"), |
| | ], |
| | } |
| |
|
| | if dataType == "raw": |
| | return enums |
| |
|
| | data = list() |
| | idx = 0 if dataType == "translated" else 1 |
| |
|
| | Path.Log.debug(enums) |
| |
|
| | for k, v in enumerate(enums): |
| | data.append((v, [tup[idx] for tup in enums[v]])) |
| | Path.Log.debug(data) |
| |
|
| | return data |
| |
|
| | def opPropertyDefaults(self, obj, job): |
| | """opPropertyDefaults(obj, job) ... returns a dictionary of default values |
| | for the operation's properties.""" |
| | defaults = { |
| | "CutMode": "Climb", |
| | "ClearingPattern": "ZigZag", |
| | "Angle": 0, |
| | "StepOver": 25, |
| | "AxialStockToLeave": 0.0, |
| | } |
| |
|
| | return defaults |
| |
|
| | def opSetDefaultValues(self, obj, job): |
| | """opSetDefaultValues(obj, job) ... set default values for operation-specific properties""" |
| | Path.Log.track() |
| |
|
| | |
| | obj.CutMode = "Climb" |
| | obj.ClearingPattern = "ZigZag" |
| | obj.Angle = 0.0 |
| | obj.StepOver = 25 |
| | obj.AxialStockToLeave = 0.0 |
| | obj.PassExtension = ( |
| | 3.0 |
| | ) |
| | obj.Reverse = False |
| |
|
| | def opExecute(self, obj): |
| | """opExecute(obj) ... process Mill Facing operation""" |
| | Path.Log.track() |
| | Path.Log.debug("MillFacing.opExecute() starting") |
| |
|
| | |
| | tool = obj.ToolController.Tool |
| | Path.Log.debug(f"Tool: {tool.Label if tool else 'None'}") |
| | tool_diameter = tool.Diameter.Value |
| | Path.Log.debug(f"Tool diameter: {tool_diameter}") |
| |
|
| | |
| | finish_step = 0.0 |
| | Path.Log.debug( |
| | f"Depth parameters: clearance={obj.ClearanceHeight.Value}, safe={obj.SafeHeight.Value}, start={obj.StartDepth.Value}, step={obj.StepDown.Value}, final={obj.FinalDepth.Value + obj.AxialStockToLeave.Value}" |
| | ) |
| | depthparams = PathUtils.depth_params( |
| | clearance_height=obj.ClearanceHeight.Value, |
| | safe_height=obj.SafeHeight.Value, |
| | start_depth=obj.StartDepth.Value, |
| | step_down=obj.StepDown.Value, |
| | z_finish_step=finish_step, |
| | final_depth=obj.FinalDepth.Value + obj.AxialStockToLeave.Value, |
| | user_depths=None, |
| | ) |
| | Path.Log.debug(f"Depth params object: {depthparams}") |
| |
|
| | |
| | job = PathUtils.findParentJob(obj) |
| | Path.Log.debug(f"Job: {job.Label if job else 'None'}") |
| | if job and job.Stock: |
| | Path.Log.debug(f"Stock: {job.Stock.Label}") |
| | stock_faces = job.Stock.Shape.Faces |
| | Path.Log.debug(f"Number of stock faces: {len(stock_faces)}") |
| |
|
| | |
| | z_up_faces = [] |
| | for face in stock_faces: |
| | |
| | u_mid = (face.ParameterRange[0] + face.ParameterRange[1]) / 2 |
| | v_mid = (face.ParameterRange[2] + face.ParameterRange[3]) / 2 |
| | normal = face.normalAt(u_mid, v_mid) |
| | Path.Log.debug(f"Face normal: {normal}, Z component: {normal.z}") |
| |
|
| | |
| | if normal.z > 0.9: |
| | z_up_faces.append(face) |
| | Path.Log.debug(f"Found upward-facing face at Z={face.BoundBox.ZMax}") |
| |
|
| | if not z_up_faces: |
| | Path.Log.error("No upward-facing faces found in stock") |
| | raise ValueError("No upward-facing faces found in stock") |
| |
|
| | |
| | top_face = max(z_up_faces, key=lambda f: f.BoundBox.ZMax) |
| | Path.Log.debug(f"Selected top face ZMax: {top_face.BoundBox.ZMax}") |
| | boundary_wire = top_face.OuterWire |
| | Path.Log.debug(f"Wire vertices: {len(boundary_wire.Vertexes)}") |
| | else: |
| | Path.Log.error("No stock found for facing operation") |
| | raise ValueError("No stock found for facing operation") |
| |
|
| | boundary_wire = boundary_wire.makeOffset2D( |
| | obj.StockExtension.Value, 2 |
| | ) |
| |
|
| | |
| | milling_direction = "climb" if obj.CutMode == "Climb" else "conventional" |
| |
|
| | |
| | stepover_percent = obj.StepOver |
| | pass_extension = ( |
| | obj.PassExtension.Value if hasattr(obj, "PassExtension") else tool_diameter * 0.5 |
| | ) |
| | retract_height = obj.SafeHeight.Value |
| |
|
| | |
| | try: |
| | if obj.ClearingPattern == "Spiral": |
| | |
| | Path.Log.debug("Generating spiral toolpath") |
| | base_commands = spiral_facing.spiral( |
| | polygon=boundary_wire, |
| | tool_diameter=tool_diameter, |
| | stepover_percent=stepover_percent, |
| | milling_direction=milling_direction, |
| | reverse=bool(getattr(obj, "Reverse", False)), |
| | angle_degrees=getattr(obj.Angle, "Value", obj.Angle), |
| | ) |
| | elif obj.ClearingPattern == "ZigZag": |
| | Path.Log.debug("Generating zigzag toolpath") |
| | base_commands = zigzag_facing.zigzag( |
| | polygon=boundary_wire, |
| | tool_diameter=tool_diameter, |
| | stepover_percent=stepover_percent, |
| | pass_extension=pass_extension, |
| | retract_height=retract_height, |
| | milling_direction=milling_direction, |
| | reverse=bool(getattr(obj, "Reverse", False)), |
| | angle_degrees=getattr(obj.Angle, "Value", obj.Angle), |
| | ) |
| | elif obj.ClearingPattern == "Bidirectional": |
| | Path.Log.debug("Generating bidirectional toolpath") |
| | base_commands = bidirectional_facing.bidirectional( |
| | polygon=boundary_wire, |
| | tool_diameter=tool_diameter, |
| | stepover_percent=stepover_percent, |
| | pass_extension=pass_extension, |
| | milling_direction=milling_direction, |
| | reverse=bool(getattr(obj, "Reverse", False)), |
| | angle_degrees=getattr(obj.Angle, "Value", obj.Angle), |
| | ) |
| | elif obj.ClearingPattern == "Directional": |
| | Path.Log.debug("Generating directional toolpath") |
| | base_commands = directional_facing.directional( |
| | polygon=boundary_wire, |
| | tool_diameter=tool_diameter, |
| | stepover_percent=stepover_percent, |
| | pass_extension=pass_extension, |
| | retract_height=retract_height, |
| | milling_direction=milling_direction, |
| | reverse=bool(getattr(obj, "Reverse", False)), |
| | angle_degrees=getattr(obj.Angle, "Value", obj.Angle), |
| | ) |
| | else: |
| | Path.Log.error(f"Unknown clearing pattern: {obj.ClearingPattern}") |
| | raise ValueError(f"Unknown clearing pattern: {obj.ClearingPattern}") |
| |
|
| | Path.Log.debug(f"Generated {len(base_commands)} base commands") |
| | Path.Log.debug(base_commands) |
| |
|
| | except Exception as e: |
| | Path.Log.error(f"Error generating toolpath: {e}") |
| | raise |
| |
|
| | |
| | self.commandlist = [] |
| |
|
| | |
| | targetZ = obj.ClearanceHeight.Value |
| | self.commandlist.append(Path.Command("G0", {"Z": targetZ})) |
| |
|
| | |
| | depth_count = 0 |
| | try: |
| | while True: |
| | depth = depthparams.next() |
| | depth_count += 1 |
| | Path.Log.debug(f"Processing depth {depth_count}: {depth}") |
| |
|
| | if depth_count == 1: |
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| | first_xy = None |
| | first_move_idx = None |
| | for i, bc in enumerate(base_commands): |
| | if "X" in bc.Parameters and "Y" in bc.Parameters: |
| | first_xy = (bc.Parameters["X"], bc.Parameters["Y"]) |
| | first_move_idx = i |
| | break |
| |
|
| | if first_xy is not None: |
| | |
| | pre1 = {"X": first_xy[0], "Y": first_xy[1]} |
| | if not self.commandlist or any( |
| | abs(pre1[k] - self.commandlist[-1].Parameters.get(k, pre1[k] + 1)) |
| | > 1e-9 |
| | for k in ("X", "Y") |
| | ): |
| | self.commandlist.append(Path.Command("G0", pre1)) |
| |
|
| | |
| | pre2 = {"Z": obj.SafeHeight.Value} |
| | if ( |
| | abs(pre2["Z"] - self.commandlist[-1].Parameters.get("Z", pre2["Z"] + 1)) |
| | > 1e-9 |
| | ): |
| | self.commandlist.append(Path.Command("G0", pre2)) |
| |
|
| | |
| | pre3 = {"Z": depth} |
| | if ( |
| | abs(pre3["Z"] - self.commandlist[-1].Parameters.get("Z", pre3["Z"] + 1)) |
| | > 1e-9 |
| | ): |
| | self.commandlist.append(Path.Command("G0", pre3)) |
| |
|
| | |
| | for i, cmd in enumerate(base_commands): |
| | |
| | if i == first_move_idx: |
| | |
| | pass |
| | else: |
| | new_params = dict(cmd.Parameters) |
| | |
| | if "Z" in new_params: |
| | if cmd.Name == "G0": |
| | |
| | |
| | |
| | if ( |
| | abs(new_params["Z"] - retract_height) < 1.0 |
| | ): |
| | |
| | pass |
| | else: |
| | |
| | new_params["Z"] = depth |
| | else: |
| | |
| | new_params["Z"] = depth |
| | else: |
| | |
| | if cmd.Name == "G1": |
| | |
| | new_params["Z"] = depth |
| | else: |
| | |
| | if self.commandlist: |
| | new_params["Z"] = self.commandlist[-1].Parameters.get( |
| | "Z", depth |
| | ) |
| |
|
| | |
| | if self.commandlist: |
| | last = self.commandlist[-1].Parameters |
| | if "X" not in cmd.Parameters: |
| | new_params.setdefault("X", last.get("X")) |
| | if "Y" not in cmd.Parameters: |
| | new_params.setdefault("Y", last.get("Y")) |
| | |
| | if self.commandlist: |
| | last_params = self.commandlist[-1].Parameters |
| | |
| | if all( |
| | abs(new_params[k] - last_params.get(k, new_params[k] + 1)) |
| | <= 1e-9 |
| | for k in ("X", "Y") |
| | ): |
| | |
| | |
| | z_new = new_params.get("Z", float("inf")) |
| | z_last = last_params.get("Z", float("-inf")) |
| | z_changed = abs(z_new - z_last) > 1e-9 |
| | if not z_changed: |
| | continue |
| | self.commandlist.append(Path.Command(cmd.Name, new_params)) |
| | Path.Log.debug( |
| | f"First stepdown: Added {len(base_commands)} commands for depth {depth}" |
| | ) |
| | else: |
| | |
| | |
| | copy_commands = [] |
| | for cmd in base_commands: |
| | new_params = dict(cmd.Parameters) |
| | |
| | if "Z" in new_params: |
| | if cmd.Name == "G0": |
| | |
| | if ( |
| | abs(new_params["Z"] - retract_height) < 1.0 |
| | ): |
| | |
| | pass |
| | else: |
| | |
| | new_params["Z"] = depth |
| | else: |
| | |
| | new_params["Z"] = depth |
| | else: |
| | |
| | if cmd.Name == "G1": |
| | |
| | new_params["Z"] = depth |
| | |
| | copy_commands.append(Path.Command(cmd.Name, new_params)) |
| |
|
| | |
| | last_cmd = self.commandlist[-1] |
| | last_position = FreeCAD.Vector( |
| | last_cmd.Parameters.get("X", 0), |
| | last_cmd.Parameters.get("Y", 0), |
| | last_cmd.Parameters.get("Z", depth), |
| | ) |
| |
|
| | |
| | bundle_start = None |
| | bundle_end = None |
| | target_xy = None |
| | for i, cmd in enumerate(copy_commands): |
| | if cmd.Name == "G0": |
| | bundle_start = i |
| | |
| | j = i |
| | while j < len(copy_commands) and copy_commands[j].Name == "G0": |
| | |
| | if ( |
| | "X" in copy_commands[j].Parameters |
| | and "Y" in copy_commands[j].Parameters |
| | ): |
| | target_xy = ( |
| | copy_commands[j].Parameters.get("X"), |
| | copy_commands[j].Parameters.get("Y"), |
| | ) |
| | j += 1 |
| | bundle_end = j |
| | break |
| |
|
| | if bundle_start is not None and target_xy is not None: |
| | |
| | first_position = FreeCAD.Vector(target_xy[0], target_xy[1], depth) |
| |
|
| | |
| | link_commands = linking.get_linking_moves( |
| | start_position=last_position, |
| | target_position=first_position, |
| | local_clearance=obj.SafeHeight.Value, |
| | global_clearance=obj.ClearanceHeight.Value, |
| | tool_shape=obj.ToolController.Tool.Shape, |
| | ) |
| | |
| | current = last_position |
| | for lc in link_commands: |
| | params = lc.Parameters |
| | X = params["X"] |
| | Y = params["Y"] |
| | Z = params["Z"] |
| | |
| | if not ( |
| | abs(X - current.x) <= 1e-9 |
| | and abs(Y - current.y) <= 1e-9 |
| | and abs(Z - current.z) <= 1e-9 |
| | ): |
| | self.commandlist.append( |
| | Path.Command(lc.Name, {"X": X, "Y": Y, "Z": Z}) |
| | ) |
| | current = FreeCAD.Vector(X, Y, Z) |
| |
|
| | |
| | del copy_commands[bundle_start:bundle_end] |
| |
|
| | |
| | for cc in copy_commands: |
| | cp = dict(cc.Parameters) |
| | if self.commandlist: |
| | last = self.commandlist[-1].Parameters |
| | |
| | if "X" not in cc.Parameters: |
| | cp.setdefault("X", last.get("X")) |
| | if "Y" not in cc.Parameters: |
| | cp.setdefault("Y", last.get("Y")) |
| | |
| | if "Z" not in cc.Parameters: |
| | |
| | if cc.Name == "G1": |
| | cp["Z"] = depth |
| | else: |
| | cp.setdefault("Z", last.get("Z")) |
| | |
| | if self.commandlist: |
| | last = self.commandlist[-1].Parameters |
| | |
| | if all( |
| | abs(cp.get(k, float("inf")) - last.get(k, float("-inf"))) <= 1e-9 |
| | for k in ("X", "Y", "Z") |
| | ): |
| | continue |
| | self.commandlist.append(Path.Command(cc.Name, cp)) |
| | Path.Log.debug( |
| | f"Stepdown {depth_count}: Added linking + {len(copy_commands)} commands for depth {depth}" |
| | ) |
| |
|
| | except StopIteration: |
| | Path.Log.debug(f"All depths processed. Total depth levels: {depth_count}") |
| |
|
| | |
| | targetZ = obj.ClearanceHeight.Value |
| | if self.commandlist: |
| | last = self.commandlist[-1].Parameters |
| | lastZ = last.get("Z") |
| | if lastZ is None or abs(targetZ - lastZ) > 1e-9: |
| | |
| | self.commandlist.append(Path.Command("G0", {"Z": targetZ})) |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| |
|
| | |
| | try: |
| | FeedRate.setFeedRate(self.commandlist, obj.ToolController) |
| | except Exception as e: |
| | |
| | n = len(self.commandlist) |
| | start = max(0, n - 12) |
| | Path.Log.error("FeedRate failure. Dumping last commands:") |
| | for i in range(start, n): |
| | c = self.commandlist[i] |
| | Path.Log.error(f" #{i}: {c.Name} {c.Parameters}") |
| | raise |
| |
|
| | Path.Log.debug(f"Total commands in commandlist: {len(self.commandlist)}") |
| | Path.Log.debug("MillFacing.opExecute() completed successfully") |
| | Path.Log.debug(self.commandlist) |
| |
|
| |
|
| | |
| |
|
| |
|
| | def Create(name, obj=None, parentJob=None): |
| | """Create(name) ... Creates and returns a Mill Facing operation.""" |
| | if obj is None: |
| | obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) |
| | obj.Proxy = ObjectMillFacing(obj, name, parentJob) |
| | return obj |
| |
|
| |
|
| | def SetupProperties(): |
| | """SetupProperties() ... Return list of properties required for the operation.""" |
| | setup = [] |
| | setup.append("CutMode") |
| | setup.append("ClearingPattern") |
| | setup.append("Angle") |
| | setup.append("StepOver") |
| | setup.append("AxialStockToLeave") |
| | setup.append("PassExtension") |
| | setup.append("Reverse") |
| | return setup |
| |
|