diff --git a/src/Mod/CAM/Gui/Resources/panels/PageOpDrillingEdit.ui b/src/Mod/CAM/Gui/Resources/panels/PageOpDrillingEdit.ui index 572218d997..22dec12f54 100644 --- a/src/Mod/CAM/Gui/Resources/panels/PageOpDrillingEdit.ui +++ b/src/Mod/CAM/Gui/Resources/panels/PageOpDrillingEdit.ui @@ -170,6 +170,16 @@ + + + + Feed retract + + + G85: Retract from the hole at the given feedrate instead of rapid move + + + @@ -197,6 +207,7 @@ toolController peckEnabled + feedRetractEnabled dwellEnabled diff --git a/src/Mod/CAM/Path/Base/Generator/drill.py b/src/Mod/CAM/Path/Base/Generator/drill.py index a8da08c431..73b43ddd48 100644 --- a/src/Mod/CAM/Path/Base/Generator/drill.py +++ b/src/Mod/CAM/Path/Base/Generator/drill.py @@ -38,7 +38,7 @@ else: def generate( - edge, dwelltime=0.0, peckdepth=0.0, repeat=1, retractheight=None, chipBreak=False + edge, dwelltime=0.0, peckdepth=0.0, repeat=1, retractheight=None, chipBreak=False, feedRetract=False ): """ Generates Gcode for drilling a single hole. @@ -59,6 +59,11 @@ def generate( full retracts to clear chips from the hole. http://linuxcnc.org/docs/html/gcode/g-code.html#gcode:g73 + If feedRetract is True, the generator will produce G85 cycles which retract + the tool at the specified feed rate instead of performing a rapid move. + This is useful for boring or reaming operations. Peck or dwell cannot be used with feed retract. + http://linuxcnc.org/docs/html/gcode/g-code.html#gcode:g85 + """ startPoint = edge.Vertexes[0].Point endPoint = edge.Vertexes[1].Point @@ -73,6 +78,12 @@ def generate( if dwelltime > 0.0 and peckdepth > 0.0: raise ValueError("Peck and Dwell cannot be used together") + if dwelltime > 0.0 and feedRetract: + raise ValueError("Dwell and feed retract cannot be used together") + + if peckdepth > 0.0 and feedRetract: + raise ValueError("Peck and feed retract cannot be used together") + if repeat < 1: raise ValueError("repeat must be 1 or greater") @@ -112,7 +123,9 @@ def generate( if repeat > 1: cmdParams["L"] = repeat - if peckdepth == 0.0: + if feedRetract: + cmd = "G85" + elif peckdepth == 0.0: if dwelltime > 0.0: cmd = "G82" cmdParams["P"] = dwelltime diff --git a/src/Mod/CAM/Path/Op/Drilling.py b/src/Mod/CAM/Path/Op/Drilling.py index c0d4499648..7d864a127d 100644 --- a/src/Mod/CAM/Path/Op/Drilling.py +++ b/src/Mod/CAM/Path/Op/Drilling.py @@ -105,6 +105,14 @@ class ObjectDrilling(PathCircularHoleBase.ObjectOp): QT_TRANSLATE_NOOP("App::Property", "Use chipbreaking"), ) + if not hasattr(obj, "feedRetractEnabled"): + obj.addProperty( + "App::PropertyBool", "feedRetractEnabled", "Drill", + QT_TRANSLATE_NOOP( + "App::Property", + "Use G85 boring cycle with feed out") + ) + def initCircularHoleOperation(self, obj): """initCircularHoleOperation(obj) ... add drilling specific properties to obj.""" obj.addProperty( @@ -178,6 +186,12 @@ class ObjectDrilling(PathCircularHoleBase.ObjectOp): "App::Property", "Apply G99 retraction: only retract to RetractHeight between holes in this operation") ) + obj.addProperty( + "App::PropertyBool", "feedRetractEnabled", "Drill", + QT_TRANSLATE_NOOP( + "App::Property", + "Use G85 boring cycle with feed out") + ) for n in self.propertyEnumerations(): setattr(obj, n[0], n[1]) @@ -276,6 +290,7 @@ class ObjectDrilling(PathCircularHoleBase.ObjectOp): repeat, obj.RetractHeight.Value, chipBreak=chipBreak, + feedRetract=obj.feedRetractEnabled ) except ValueError as e: # any targets that fail the generator are ignored diff --git a/src/Mod/CAM/Path/Op/Gui/Drilling.py b/src/Mod/CAM/Path/Op/Gui/Drilling.py index 64d01c3d67..aba21c970b 100644 --- a/src/Mod/CAM/Path/Op/Gui/Drilling.py +++ b/src/Mod/CAM/Path/Op/Gui/Drilling.py @@ -62,11 +62,20 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): def registerSignalHandlers(self, obj): self.form.peckEnabled.toggled.connect(self.form.peckDepth.setEnabled) self.form.peckEnabled.toggled.connect(self.form.dwellEnabled.setDisabled) + self.form.peckEnabled.toggled.connect(self.form.feedRetractEnabled.setDisabled) self.form.peckEnabled.toggled.connect(self.setChipBreakControl) + self.form.feedRetractEnabled.toggled.connect(self.form.peckDepth.setDisabled) + self.form.feedRetractEnabled.toggled.connect(self.form.peckEnabled.setDisabled) + self.form.feedRetractEnabled.toggled.connect(self.form.dwellEnabled.setDisabled) + self.form.feedRetractEnabled.toggled.connect(self.form.chipBreakEnabled.setDisabled) + self.form.feedRetractEnabled.toggled.connect(self.form.peckEnabled.setDisabled) + self.form.feedRetractEnabled.toggled.connect(self.setChipBreakControl) + self.form.dwellEnabled.toggled.connect(self.form.dwellTime.setEnabled) self.form.dwellEnabled.toggled.connect(self.form.dwellTimelabel.setEnabled) self.form.dwellEnabled.toggled.connect(self.form.peckEnabled.setDisabled) + self.form.dwellEnabled.toggled.connect(self.form.feedRetractEnabled.setDisabled) self.form.dwellEnabled.toggled.connect(self.setChipBreakControl) self.form.peckRetractHeight.setEnabled(True) @@ -74,15 +83,18 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): if self.form.peckEnabled.isChecked(): self.form.dwellEnabled.setEnabled(False) + self.form.feedRetractEnabled.setEnabled(False) self.form.peckDepth.setEnabled(True) self.form.peckDepthLabel.setEnabled(True) self.form.chipBreakEnabled.setEnabled(True) elif self.form.dwellEnabled.isChecked(): + self.form.feedRetractEnabled.setEnabled(False) self.form.peckEnabled.setEnabled(False) self.form.dwellTime.setEnabled(True) self.form.dwellTimelabel.setEnabled(True) self.form.chipBreakEnabled.setEnabled(False) else: + self.form.feedRetractEnabled.setEnabled(True) self.form.chipBreakEnabled.setEnabled(False) def setChipBreakControl(self): @@ -116,6 +128,8 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): obj.DwellEnabled = self.form.dwellEnabled.isChecked() if obj.PeckEnabled != self.form.peckEnabled.isChecked(): obj.PeckEnabled = self.form.peckEnabled.isChecked() + if obj.feedRetractEnabled != self.form.feedRetractEnabled.isChecked(): + obj.feedRetractEnabled = self.form.feedRetractEnabled.isChecked() if obj.chipBreakEnabled != self.form.chipBreakEnabled.isChecked(): obj.chipBreakEnabled = self.form.chipBreakEnabled.isChecked() if obj.ExtraOffset != str(self.form.ExtraOffset.currentData()): @@ -154,6 +168,11 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): else: self.form.chipBreakEnabled.setCheckState(QtCore.Qt.Unchecked) + if obj.feedRetractEnabled: + self.form.feedRetractEnabled.setCheckState(QtCore.Qt.Checked) + else: + self.form.feedRetractEnabled.setCheckState(QtCore.Qt.Unchecked) + self.selectInComboBox(obj.ExtraOffset, self.form.ExtraOffset) self.setupToolController(obj, self.form.toolController) @@ -173,6 +192,7 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): signals.append(self.form.coolantController.currentIndexChanged) signals.append(self.form.ExtraOffset.currentIndexChanged) signals.append(self.form.KeepToolDownEnabled.stateChanged) + signals.append(self.form.feedRetractEnabled.stateChanged) return signals diff --git a/src/Mod/CAM/Tests/TestPathDrillGenerator.py b/src/Mod/CAM/Tests/TestPathDrillGenerator.py index 5e1c008a8d..bf707ff9df 100644 --- a/src/Mod/CAM/Tests/TestPathDrillGenerator.py +++ b/src/Mod/CAM/Tests/TestPathDrillGenerator.py @@ -175,3 +175,69 @@ class TestPathDrillGenerator(PathTestUtils.PathTestBase): command = result[0] self.assertTrue(command.Name == "G73") + + def test70(self): + """Test feed retract enabled produces G85""" + v1 = FreeCAD.Vector(0, 0, 10) + v2 = FreeCAD.Vector(0, 0, 0) + + e = Part.makeLine(v1, v2) + + args = {"edge": e, "feedRetract": True} + result = generator.generate(**args) + command = result[0] + + self.assertEqual(command.Name, "G85") + # G85 does not support peck or dwell + self.assertFalse(hasattr(command.Parameters, "Q")) + self.assertFalse(hasattr(command.Parameters, "P")) + + def test71(self): + """Test that dwell can be used when feed retract is not enabled""" + v1 = FreeCAD.Vector(0, 0, 10) + v2 = FreeCAD.Vector(0, 0, 0) + + e = Part.makeLine(v1, v2) + + args = {"edge": e, "dwelltime": 0.5, "feedRetract": False} + result = generator.generate(**args) + + command = result[0] + + self.assertEqual(command.Name, "G82") + self.assertEqual(command.Parameters["P"], 0.5) + + def test72(self): + """Test that peck can be used when feed retract is not enabled""" + v1 = FreeCAD.Vector(0, 0, 10) + v2 = FreeCAD.Vector(0, 0, 0) + + e = Part.makeLine(v1, v2) + + args = {"edge": e, "peckdepth": 1.0, "feedRetract": False} + result = generator.generate(**args) + + command = result[0] + + self.assertTrue(command.Name == "G83") + self.assertEqual(command.Parameters["Q"], 1.0) + + def test73(self): + """Test error when feed retract and dwell are enabled""" + v1 = FreeCAD.Vector(0, 0, 10) + v2 = FreeCAD.Vector(0, 0, 0) + + e = Part.makeLine(v1, v2) + + args = {"edge": e, "dwelltime": 1.0, "feedRetract": True} + self.assertRaises(ValueError, generator.generate, **args) + + def test74(self): + """Test error when feed retract and peck are enabled""" + v1 = FreeCAD.Vector(0, 0, 10) + v2 = FreeCAD.Vector(0, 0, 0) + + e = Part.makeLine(v1, v2) + + args = {"edge": e, "peckdepth": 1.0, "chipBreak": True, "feedRetract": True} + self.assertRaises(ValueError, generator.generate, **args)