Download src/Mod/CAM/Path/Op/Tapping.py from AbdulElahGwaith/FreeCAD: direct link, hf CLI and curl.
- Browser
- Download file 12.4 kB
-
https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/CAM/Path/Op/Tapping.py
- Command line
-
hf download hf://AbdulElahGwaith/FreeCAD/src/Mod/CAM/Path/Op/Tapping.py
-
curl -L -o Tapping.py https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/CAM/Path/Op/Tapping.py
12.4 kB
| # SPDX-License-Identifier: LGPL-2.1-or-later | |
| # *************************************************************************** | |
| # * Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> * | |
| # * Copyright (c) 2020 Schildkroet * | |
| # * Copyright (c) 2023 luvtofish * | |
| # * * | |
| # * This program is free software; you can redistribute it and/or modify * | |
| # * it under the terms of the GNU Lesser General Public License (LGPL) * | |
| # * as published by the Free Software Foundation; either version 2 of * | |
| # * the License, or (at your option) any later version. * | |
| # * for detail see the LICENSE text file. * | |
| # * * | |
| # * This program is distributed in the hope that it will be useful, * | |
| # * but WITHOUT ANY WARRANTY; without even the implied warranty of * | |
| # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * | |
| # * GNU Library General Public License for more details. * | |
| # * * | |
| # * You should have received a copy of the GNU Library General Public * | |
| # * License along with this program; if not, write to the Free Software * | |
| # * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * | |
| # * USA * | |
| # * * | |
| # *************************************************************************** | |
| import FreeCAD | |
| import Part | |
| import Path | |
| import Path.Base.FeedRate as PathFeedRate | |
| import Path.Base.Generator.tapping as tapping | |
| import Path.Base.MachineState as PathMachineState | |
| import Path.Op.Base as PathOp | |
| import Path.Op.CircularHoleBase as PathCircularHoleBase | |
| import PathScripts.PathUtils as PathUtils | |
| from PySide.QtCore import QT_TRANSLATE_NOOP | |
| __title__ = "Path Tapping Operation" | |
| __author__ = "sliptonic (Brad Collette)" | |
| __url__ = "https://www.freecad.org" | |
| __doc__ = "Path Tapping operation." | |
| __contributors__ = "luvtofish (Dan Henderson)" | |
| 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 | |
| class ObjectTapping(PathCircularHoleBase.ObjectOp): | |
| """Proxy object for Tapping operation.""" | |
| def propertyEnumerations(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 | |
| """ | |
| # Enumeration lists for App::PropertyEnumeration properties | |
| enums = { | |
| "ReturnLevel": [ | |
| (translate("CAM_Tapping", "G98"), "G98"), | |
| (translate("CAM_Tapping", "G99"), "G99"), | |
| ], # How high to retract after a tapping move | |
| "ExtraOffset": [ | |
| (translate("CAM_Tapping", "None"), "None"), | |
| (translate("CAM_Tapping", "Drill Tip"), "Drill Tip"), | |
| (translate("CAM_Tapping", "2x Drill Tip"), "2x Drill Tip"), | |
| ], # extra drilling depth to clear tap taper | |
| } | |
| 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) ... tapping works on anything, turn on all Base geometries and Locations.""" | |
| return PathOp.FeatureBaseGeometry | PathOp.FeatureLocations | PathOp.FeatureCoolant | |
| def initCircularHoleOperation(self, obj): | |
| """initCircularHoleOperation(obj) ... add tapping specific properties to obj.""" | |
| obj.addProperty( | |
| "App::PropertyFloat", | |
| "DwellTime", | |
| "Tap", | |
| QT_TRANSLATE_NOOP("App::Property", "The time to dwell at bottom of tapping cycle"), | |
| ) | |
| obj.addProperty( | |
| "App::PropertyBool", | |
| "DwellEnabled", | |
| "Tap", | |
| QT_TRANSLATE_NOOP("App::Property", "Enable dwell"), | |
| ) | |
| obj.addProperty( | |
| "App::PropertyBool", | |
| "AddTipLength", | |
| "Tap", | |
| QT_TRANSLATE_NOOP( | |
| "App::Property", | |
| "Calculate the tip length and subtract from final depth", | |
| ), | |
| ) | |
| obj.addProperty( | |
| "App::PropertyEnumeration", | |
| "ReturnLevel", | |
| "Tap", | |
| QT_TRANSLATE_NOOP("App::Property", "Controls how tool retracts Default=G98"), | |
| ) | |
| obj.addProperty( | |
| "App::PropertyDistance", | |
| "RetractHeight", | |
| "Tap", | |
| QT_TRANSLATE_NOOP( | |
| "App::Property", | |
| "The height where feed starts and height during retract tool when path is finished while in a peck operation", | |
| ), | |
| ) | |
| obj.addProperty( | |
| "App::PropertyEnumeration", | |
| "ExtraOffset", | |
| "Tap", | |
| QT_TRANSLATE_NOOP("App::Property", "How far the tap depth is extended"), | |
| ) | |
| for n in self.propertyEnumerations(): | |
| setattr(obj, n[0], n[1]) | |
| def circularHoleExecute(self, obj, holes): | |
| """circularHoleExecute(obj, holes) ... generate tapping operation for each hole in holes.""" | |
| Path.Log.track() | |
| machine = PathMachineState.MachineState() | |
| if not hasattr(obj.ToolController.Tool, "Pitch"): | |
| Path.Log.error( | |
| translate( | |
| "Path_Tapping", | |
| "Tapping Operation requires a Tap tool with Pitch", | |
| ) | |
| ) | |
| return | |
| self.commandlist.append(Path.Command("(Begin Tapping)")) | |
| # rapid to clearance height | |
| command = Path.Command("G0", {"Z": obj.ClearanceHeight.Value}) | |
| machine.addCommand(command) | |
| self.commandlist.append(command) | |
| self.commandlist.append(Path.Command("G90")) # Absolute distance mode | |
| # Calculate offsets to add to target edge | |
| endoffset = 0.0 | |
| if obj.ExtraOffset == "Drill Tip": | |
| endoffset = PathUtils.drillTipLength(self.tool) | |
| elif obj.ExtraOffset == "2x Drill Tip": | |
| endoffset = PathUtils.drillTipLength(self.tool) * 2 | |
| # http://linuxcnc.org/docs/html/gcode/g-code.html#gcode:g98-g99 | |
| self.commandlist.append( | |
| Path.Command(obj.ReturnLevel).addAnnotations({"operation": "tapping"}) | |
| ) | |
| # This section is technical debt. The computation of the | |
| # target shapes should be factored out for reuse. | |
| # This will likely mean refactoring upstream CircularHoleBase to pass | |
| # spot shapes instead of holes. | |
| startHeight = obj.StartDepth.Value + self.job.SetupSheet.SafeHeightOffset.Value | |
| edgelist = [] | |
| for hole in holes: | |
| v1 = FreeCAD.Vector(hole["x"], hole["y"], obj.StartDepth.Value) | |
| v2 = FreeCAD.Vector(hole["x"], hole["y"], obj.FinalDepth.Value - endoffset) | |
| edgelist.append(Part.makeLine(v1, v2)) | |
| # iterate the edgelist and generate gcode | |
| for edge in edgelist: | |
| Path.Log.debug(edge) | |
| # move to hole location | |
| startPoint = edge.Vertexes[0].Point | |
| command = Path.Command( | |
| "G0", {"X": startPoint.x, "Y": startPoint.y, "Z": startHeight} | |
| ) # DLH added Z offset to fix issue with Dynapath Control. | |
| self.commandlist.append(command) | |
| machine.addCommand(command) | |
| # command = Path.Command("G0", {"Z": startHeight}) | |
| # self.commandlist.append(command) | |
| # machine.addCommand(command) | |
| # command = Path.Command("G1", {"Z": obj.StartDepth.Value}) | |
| # self.commandlist.append(command) | |
| # machine.addCommand(command) | |
| # Technical Debt: We are assuming the edges are aligned. | |
| # This assumption should be corrected and the necessary rotations | |
| # performed to align the edge with the Z axis for drilling | |
| # Perform tapping | |
| dwelltime = obj.DwellTime if obj.DwellEnabled else 0.0 | |
| repeat = 1 # technical debt: Add a repeat property for user control | |
| # Get attribute from obj.tool, assign default and set to bool for passing to generate | |
| isRightHand = ( | |
| getattr(obj.ToolController.Tool, "SpindleDirection", "Forward") == "Forward" | |
| ) | |
| # Get pitch in mm as a float (no unit string) | |
| pitch = getattr(obj.ToolController.Tool, "Pitch", None) | |
| if pitch is None or pitch == 0: | |
| Path.Log.error( | |
| translate( | |
| "Path_Tapping", | |
| "Tapping Operation requires a Tap tool with non-zero Pitch", | |
| ) | |
| ) | |
| continue | |
| spindle_speed = getattr(obj.ToolController, "SpindleSpeed", None) | |
| if spindle_speed is None or spindle_speed == 0: | |
| Path.Log.error( | |
| translate( | |
| "Path_Tapping", | |
| "Tapping Operation requires a ToolController with non-zero SpindleSpeed", | |
| ) | |
| ) | |
| continue | |
| try: | |
| tappingcommands = tapping.generate( | |
| edge, | |
| dwelltime, | |
| repeat, | |
| obj.RetractHeight.Value, | |
| isRightHand, | |
| pitch, | |
| spindle_speed, | |
| ) | |
| except ValueError as e: # any targets that fail the generator are ignored | |
| Path.Log.info(e) | |
| continue | |
| for command in tappingcommands: | |
| self.commandlist.append(command) | |
| machine.addCommand(command) | |
| # Cancel canned tapping cycle | |
| self.commandlist.append(Path.Command("G80").addAnnotations({"operation": "tapping"})) | |
| # command = Path.Command("G0", {"Z": obj.SafeHeight.Value}) DLH- Not needed, adds unnecessary move to Z SafeHeight. | |
| # self.commandlist.append(command) | |
| # machine.addCommand(command) DLH - Not needed. | |
| # Apply feed rates to commands | |
| PathFeedRate.setFeedRate(self.commandlist, obj.ToolController) | |
| def opSetDefaultValues(self, obj, job): | |
| """opSetDefaultValues(obj, job) ... set default value for RetractHeight""" | |
| obj.ExtraOffset = "None" | |
| if hasattr(job.SetupSheet, "RetractHeight"): | |
| obj.RetractHeight = job.SetupSheet.RetractHeight | |
| elif self.applyExpression(obj, "RetractHeight", "StartDepth+SetupSheet.SafeHeightOffset"): | |
| if not job: | |
| obj.RetractHeight = 10 | |
| else: | |
| obj.RetractHeight.Value = obj.StartDepth.Value + 1.0 | |
| if hasattr(job.SetupSheet, "DwellTime"): | |
| obj.DwellTime = job.SetupSheet.DwellTime | |
| else: | |
| obj.DwellTime = 1 | |
| def SetupProperties(): | |
| setup = [] | |
| setup.append("DwellTime") | |
| setup.append("DwellEnabled") | |
| setup.append("AddTipLength") | |
| setup.append("ReturnLevel") | |
| setup.append("ExtraOffset") | |
| setup.append("RetractHeight") | |
| return setup | |
| def Create(name, obj=None, parentJob=None): | |
| """Create(name) ... Creates and returns a Tapping operation.""" | |
| if obj is None: | |
| obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) | |
| obj.Proxy = ObjectTapping(obj, name, parentJob) | |
| if obj.Proxy: | |
| obj.Proxy.findAllHoles(obj) | |
| return obj | |