| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import Path.Base.Generator.helix as helix |
| from PathScripts.PathUtils import fmt |
| from PySide.QtCore import QT_TRANSLATE_NOOP |
| import FreeCAD |
| import Part |
| import Path |
| import Path.Base.FeedRate as PathFeedRate |
| import Path.Op.Base as PathOp |
| import Path.Op.CircularHoleBase as PathCircularHoleBase |
|
|
|
|
| __title__ = "CAM Helix Operation" |
| __author__ = "Lorenz Hüdepohl" |
| __url__ = "https://www.freecad.org" |
| __doc__ = "Class and implementation of Helix Drill operation" |
| __contributors__ = "russ4262 (Russell Johnson)" |
| __created__ = "2016" |
| __scriptVersion__ = "1b testing" |
| __lastModified__ = "2019-07-12 09:50 CST" |
|
|
|
|
| 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()) |
|
|
| translate = FreeCAD.Qt.translate |
|
|
|
|
| def _caclulatePathDirection(mode, side): |
| """Calculates the path direction from cut mode and cut side""" |
| |
| if mode == "Conventional" and side == "Inside": |
| return "CW" |
| elif mode == "Conventional" and side == "Outside": |
| return "CCW" |
| elif mode == "Climb" and side == "Inside": |
| return "CCW" |
| elif mode == "Climb" and side == "Outside": |
| return "CW" |
| else: |
| raise ValueError(f"No mapping for '{mode}'/'{side}'") |
|
|
|
|
| def _caclulateCutMode(direction, side): |
| """Calculates the cut mode from path direction and cut side""" |
| |
| if direction == "CW" and side == "Inside": |
| return "Conventional" |
| elif direction == "CW" and side == "Outside": |
| return "Climb" |
| elif direction == "CCW" and side == "Inside": |
| return "Climb" |
| elif direction == "CCW" and side == "Outside": |
| return "Conventional" |
| else: |
| raise ValueError(f"No mapping for '{direction}'/'{side}'") |
|
|
|
|
| class ObjectHelix(PathCircularHoleBase.ObjectOp): |
| """Proxy class for Helix operations.""" |
|
|
| @classmethod |
| def helixOpPropertyEnumerations(self, dataType="data"): |
| """helixOpPropertyEnumerations(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 |
| """ |
|
|
| |
| enums = { |
| "Direction": [ |
| (translate("CAM_Helix", "CW"), "CW"), |
| (translate("CAM_Helix", "CCW"), "CCW"), |
| ], |
| "StartSide": [ |
| (translate("PathProfile", "Outside"), "Outside"), |
| (translate("PathProfile", "Inside"), "Inside"), |
| ], |
| "CutMode": [ |
| (translate("CAM_Helix", "Climb"), "Climb"), |
| (translate("CAM_Helix", "Conventional"), "Conventional"), |
| ], |
| } |
|
|
| 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 circularHoleFeatures(self, obj): |
| """circularHoleFeatures(obj) ... enable features supported by Helix.""" |
| return PathOp.FeatureStepDown | PathOp.FeatureBaseEdges | PathOp.FeatureBaseFaces |
|
|
| def initCircularHoleOperation(self, obj): |
| """initCircularHoleOperation(obj) ... create helix specific properties.""" |
| obj.addProperty( |
| "App::PropertyEnumeration", |
| "Direction", |
| "Helix Drill", |
| QT_TRANSLATE_NOOP( |
| "App::Property", |
| "The direction of the circular cuts, ClockWise (CW), or CounterClockWise (CCW)", |
| ), |
| ) |
| obj.setEditorMode("Direction", ["ReadOnly", "Hidden"]) |
| obj.setPropertyStatus("Direction", ["ReadOnly", "Output"]) |
|
|
| obj.addProperty( |
| "App::PropertyEnumeration", |
| "StartSide", |
| "Helix Drill", |
| QT_TRANSLATE_NOOP("App::Property", "Start cutting from the inside or outside"), |
| ) |
|
|
| |
| obj.addProperty( |
| "App::PropertyEnumeration", |
| "CutMode", |
| "Helix Drill", |
| QT_TRANSLATE_NOOP( |
| "App::Property", |
| "The direction of the circular cuts, ClockWise (Climb), or CounterClockWise (Conventional)", |
| ), |
| ) |
|
|
| obj.addProperty( |
| "App::PropertyPercent", |
| "StepOver", |
| "Helix Drill", |
| QT_TRANSLATE_NOOP( |
| "App::Property", "Percent of cutter diameter to step over on each pass" |
| ), |
| ) |
| obj.addProperty( |
| "App::PropertyLength", |
| "StartRadius", |
| "Helix Drill", |
| QT_TRANSLATE_NOOP("App::Property", "Starting Radius"), |
| ) |
| obj.addProperty( |
| "App::PropertyDistance", |
| "OffsetExtra", |
| "Helix Drill", |
| QT_TRANSLATE_NOOP( |
| "App::Property", |
| "Extra value to stay away from final profile- good for roughing toolpath", |
| ), |
| ) |
|
|
| ENUMS = self.helixOpPropertyEnumerations() |
| for n in ENUMS: |
| setattr(obj, n[0], n[1]) |
| obj.StepOver = 50 |
|
|
| def opOnDocumentRestored(self, obj): |
| if not hasattr(obj, "StartRadius"): |
| obj.addProperty( |
| "App::PropertyLength", |
| "StartRadius", |
| "Helix Drill", |
| QT_TRANSLATE_NOOP("App::Property", "Starting Radius"), |
| ) |
|
|
| if not hasattr(obj, "OffsetExtra"): |
| obj.addProperty( |
| "App::PropertyDistance", |
| "OffsetExtra", |
| "Helix Drill", |
| QT_TRANSLATE_NOOP( |
| "App::Property", |
| "Extra value to stay away from final profile- good for roughing toolpath", |
| ), |
| ) |
|
|
| if not hasattr(obj, "CutMode"): |
| |
| |
| obj.addProperty( |
| "App::PropertyEnumeration", |
| "CutMode", |
| "Helix Drill", |
| QT_TRANSLATE_NOOP( |
| "App::Property", |
| "The direction of the circular cuts, ClockWise (Climb), or CounterClockWise (Conventional)", |
| ), |
| ) |
| obj.CutMode = ["Climb", "Conventional"] |
| if obj.Direction in ["Climb", "Conventional"]: |
| |
| |
| new_dir = "CW" if obj.Direction == "Climb" else "CCW" |
| obj.Direction = ["CW", "CCW"] |
| obj.Direction = new_dir |
| obj.CutMode = _caclulateCutMode(obj.Direction, obj.StartSide) |
| obj.setEditorMode("Direction", ["ReadOnly", "Hidden"]) |
| obj.setPropertyStatus("Direction", ["ReadOnly", "Output"]) |
|
|
| def circularHoleExecute(self, obj, holes): |
| """circularHoleExecute(obj, holes) ... generate helix commands for each hole in holes""" |
| Path.Log.track() |
| obj.Direction = _caclulatePathDirection(obj.CutMode, obj.StartSide) |
|
|
| self.commandlist.append(Path.Command("(helix cut operation)")) |
|
|
| self.commandlist.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value})) |
|
|
| tool = obj.ToolController.Tool |
| tooldiamter = tool.Diameter.Value if hasattr(tool.Diameter, "Value") else tool.Diameter |
|
|
| args = { |
| "edge": None, |
| "hole_radius": None, |
| "step_down": obj.StepDown.Value, |
| "step_over": obj.StepOver / 100, |
| "tool_diameter": tooldiamter, |
| "inner_radius": obj.StartRadius.Value + obj.OffsetExtra.Value, |
| "direction": obj.Direction, |
| "startAt": obj.StartSide, |
| } |
|
|
| for hole in holes: |
| args["hole_radius"] = (hole["r"] / 2) - (obj.OffsetExtra.Value) |
| startPoint = FreeCAD.Vector(hole["x"], hole["y"], obj.StartDepth.Value) |
| endPoint = FreeCAD.Vector(hole["x"], hole["y"], obj.FinalDepth.Value) |
| args["edge"] = Part.makeLine(startPoint, endPoint) |
|
|
| |
| self.commandlist.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value})) |
| self.commandlist.append( |
| Path.Command( |
| "G0", |
| { |
| "X": startPoint.x, |
| "Y": startPoint.y, |
| "Z": obj.ClearanceHeight.Value, |
| }, |
| ) |
| ) |
| self.commandlist.append( |
| Path.Command("G0", {"X": startPoint.x, "Y": startPoint.y, "Z": startPoint.z}) |
| ) |
|
|
| results = helix.generate(**args) |
|
|
| for command in results: |
| self.commandlist.append(command) |
|
|
| PathFeedRate.setFeedRate(self.commandlist, obj.ToolController) |
|
|
|
|
| def SetupProperties(): |
| """Returns property names for which the "Setup Sheet" should provide defaults.""" |
| setup = [] |
| setup.append("CutMode") |
| setup.append("StartSide") |
| setup.append("StepOver") |
| setup.append("StartRadius") |
| return setup |
|
|
|
|
| def Create(name, obj=None, parentJob=None): |
| """Create(name) ... Creates and returns a Helix operation.""" |
| if obj is None: |
| obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) |
| obj.Proxy = ObjectHelix(obj, name, parentJob) |
| if obj.Proxy: |
| obj.Proxy.findAllHoles(obj) |
| return obj |
|
|