diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index b25189233a..db02b58762 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -204,6 +204,7 @@ SET(PathTests_SRCS PathTests/TestPathToolController.py PathTests/TestPathTooltable.py PathTests/TestPathUtil.py + PathTests/TestPathVcarve.py PathTests/TestPathVoronoi.py PathTests/boxtest.fcstd PathTests/test_centroid_00.ngc diff --git a/src/Mod/Path/PathScripts/PathVcarve.py b/src/Mod/Path/PathScripts/PathVcarve.py index 00e812392f..472c61fc4f 100644 --- a/src/Mod/Path/PathScripts/PathVcarve.py +++ b/src/Mod/Path/PathScripts/PathVcarve.py @@ -157,6 +157,45 @@ def _sortVoronoiWires(wires, start=FreeCAD.Vector(0, 0, 0)): return result +class _Geometry(object): + '''POD class so the limits only have to be calculated once.''' + + def __init__(self, zStart, zStop, zScale): + self.start = zStart + self.stop = zStop + self.scale = zScale + + @classmethod + def FromTool(cls, tool, zStart, zFinal): + rMax = float(tool.Diameter) / 2.0 + rMin = float(tool.TipDiameter) / 2.0 + toolangle = math.tan(math.radians(tool.CuttingEdgeAngle.Value / 2.0)) + zScale = 1.0 / toolangle + zStop = zStart - rMax * zScale + zOff = rMin * zScale + + return _Geometry(zStart + zOff, max(zStop + zOff, zFinal), zScale) + + @classmethod + def FromObj(cls, obj, model): + zStart = model.Shape.BoundBox.ZMax + finalDepth = obj.FinalDepth.Value + + return cls.FromTool(obj.ToolController.Tool, zStart, finalDepth) + +def _calculate_depth(MIC, geom): + # given a maximum inscribed circle (MIC) and tool angle, + # return depth of cut relative to zStart. + depth = geom.start - round(MIC / geom.scale, 4) + PathLog.debug('zStart value: {} depth: {}'.format(geom.start, depth)) + + return max(depth, geom.stop) + +def _getPartEdge(edge, depths): + dist = edge.getDistances() + zBegin = _calculate_depth(dist[0], depths) + zEnd = _calculate_depth(dist[1], depths) + return edge.toShape(zBegin, zEnd) class ObjectVcarve(PathEngraveBase.ObjectOp): '''Proxy class for Vcarve operation.''' @@ -197,42 +236,10 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): # upgrade ... self.setupAdditionalProperties(obj) - def _calculate_depth(self, MIC, zStart, zStop, zScale, finaldepth): - # given a maximum inscribed circle (MIC) and tool angle, - # return depth of cut relative to zStart. - depth = zStart - round(MIC / zScale, 4) - PathLog.debug('zStart value: {} depth: {}'.format(zStart, depth)) - - # Never go below the operation final depth. - zStop = zStop if zStop > finaldepth else finaldepth - - return depth if depth > zStop else zStop - - def _getPartEdge(self, edge, zStart, zStop, zScale, finaldepth): - dist = edge.getDistances() - return edge.toShape(self._calculate_depth(dist[0], - zStart, - zStop, - zScale, - finaldepth), - self._calculate_depth(dist[1], - zStart, - zStop, - zScale, - finaldepth)) - - def _getPartEdges(self, obj, vWire): - # pre-calculate the depth limits - pre-mature optimisation ;) - r = float(obj.ToolController.Tool.Diameter) / 2 - toolangle = obj.ToolController.Tool.CuttingEdgeAngle - zStart = self.model[0].Shape.BoundBox.ZMin - zStop = zStart - r / math.tan(math.radians(toolangle/2)) - zScale = 1.0 / math.tan(math.radians(toolangle / 2)) - finaldepth = obj.FinalDepth.Value - + def _getPartEdges(self, obj, vWire, geom): edges = [] for e in vWire: - edges.append(self._getPartEdge(e, zStart, zStop, zScale, finaldepth)) + edges.append(_getPartEdge(e, geom)) return edges def buildPathMedial(self, obj, Faces): @@ -290,10 +297,12 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): if _sorting == 'global': voronoiWires = _sortVoronoiWires(voronoiWires) + geom = _Geometry.FromObj(obj, self.model[0]) + pathlist = [] pathlist.append(Path.Command("(starting)")) for w in voronoiWires: - pWire = self._getPartEdges(obj, w) + pWire = self._getPartEdges(obj, w, geom) if pWire: wires.append(pWire) pathlist.extend(cutWire(pWire)) @@ -355,6 +364,9 @@ operation will produce no output.')) else: obj.OpFinalDepth = -0.1 + def isToolSupported(self, obj, tool): + '''isToolSupported(obj, tool) ... returns True if v-carve op can work with tool.''' + return hasattr(tool, 'Diameter') and hasattr(tool, 'CuttingEdgeAngle') and hasattr(tool, 'TipDiameter') def SetupProperties(): return ["Discretize"] diff --git a/src/Mod/Path/PathTests/TestPathVcarve.py b/src/Mod/Path/PathTests/TestPathVcarve.py new file mode 100644 index 0000000000..686cc4ddff --- /dev/null +++ b/src/Mod/Path/PathTests/TestPathVcarve.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- + +# *************************************************************************** +# * * +# * Copyright (c) 2020 sliptonic * +# * * +# * 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 LICENCE 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 PathScripts.PathGeom as PathGeom +import PathScripts.PathToolBit as PathToolBit +import PathScripts.PathVcarve as PathVcarve +import math + +from PathTests.PathTestUtils import PathTestBase + +class VbitTool(object): + '''Faked out vcarve tool''' + + def __init__(self, dia, angle, tipDia): + self.Diameter = FreeCAD.Units.Quantity(dia, FreeCAD.Units.Length) + self.CuttingEdgeAngle = FreeCAD.Units.Quantity(angle, FreeCAD.Units.Angle) + self.TipDiameter = FreeCAD.Units.Quantity(tipDia, FreeCAD.Units.Length) + +Scale45 = 2.414214 +Scale60 = math.sqrt(3) + +class TestPathVcarve(PathTestBase): + '''Test Vcarve milling basics.''' + + def test00(self): + '''Verify 90 deg depth calculation''' + tool = VbitTool(10, 90, 0) + geom = PathVcarve._Geometry.FromTool(tool, 0, -10) + self.assertRoughly(geom.start, 0) + self.assertRoughly(geom.stop, -5) + self.assertRoughly(geom.scale, 1) + + def test01(self): + '''Verify 90 deg depth limit''' + tool = VbitTool(10, 90, 0) + geom = PathVcarve._Geometry.FromTool(tool, 0, -3) + self.assertRoughly(geom.start, 0) + self.assertRoughly(geom.stop, -3) + self.assertRoughly(geom.scale, 1) + + def test02(self): + '''Verify 60 deg depth calculation''' + tool = VbitTool(10, 60, 0) + geom = PathVcarve._Geometry.FromTool(tool, 0, -10) + self.assertRoughly(geom.start, 0) + self.assertRoughly(geom.stop, -5 * Scale60) + self.assertRoughly(geom.scale, Scale60) + + def test03(self): + '''Verify 60 deg depth limit''' + tool = VbitTool(10, 60, 0) + geom = PathVcarve._Geometry.FromTool(tool, 0, -3) + self.assertRoughly(geom.start, 0) + self.assertRoughly(geom.stop, -3) + self.assertRoughly(geom.scale, Scale60) + + def test10(self): + '''Verify 90 deg with tip dia depth calculation''' + tool = VbitTool(10, 90, 2) + geom = PathVcarve._Geometry.FromTool(tool, 0, -10) + # in order for the width to be correct the height needs to be shifted + self.assertRoughly(geom.start, 1) + self.assertRoughly(geom.stop, -4) + self.assertRoughly(geom.scale, 1) + + def test11(self): + '''Verify 90 deg with tip dia depth limit calculation''' + tool = VbitTool(10, 90, 2) + geom = PathVcarve._Geometry.FromTool(tool, 0, -3) + # in order for the width to be correct the height needs to be shifted + self.assertRoughly(geom.start, 1) + self.assertRoughly(geom.stop, -3) + self.assertRoughly(geom.scale, 1) + + def test12(self): + '''Verify 45 deg with tip dia depth calculation''' + tool = VbitTool(10, 45, 2) + geom = PathVcarve._Geometry.FromTool(tool, 0, -10) + # in order for the width to be correct the height needs to be shifted + self.assertRoughly(geom.start, Scale45) + self.assertRoughly(geom.stop, -4 * Scale45) + self.assertRoughly(geom.scale, Scale45) + + def test13(self): + '''Verify 45 deg with tip dia depth limit calculation''' + tool = VbitTool(10, 45, 2) + geom = PathVcarve._Geometry.FromTool(tool, 0, -3) + # in order for the width to be correct the height needs to be shifted + self.assertRoughly(geom.start, Scale45) + self.assertRoughly(geom.stop, -3) + self.assertRoughly(geom.scale, Scale45) + diff --git a/src/Mod/Path/TestPathApp.py b/src/Mod/Path/TestPathApp.py index 623bf180ee..0adebedfc6 100644 --- a/src/Mod/Path/TestPathApp.py +++ b/src/Mod/Path/TestPathApp.py @@ -42,6 +42,7 @@ from PathTests.TestPathDeburr import TestPathDeburr from PathTests.TestPathHelix import TestPathHelix from PathTests.TestPathVoronoi import TestPathVoronoi from PathTests.TestPathThreadMilling import TestPathThreadMilling +from PathTests.TestPathVcarve import TestPathVcarve # dummy usage to get flake8 and lgtm quiet False if TestApp.__name__ else True @@ -64,4 +65,5 @@ False if TestPathPreferences.__name__ else True False if TestPathToolBit.__name__ else True False if TestPathVoronoi.__name__ else True False if TestPathThreadMilling.__name__ else True +False if TestPathVcarve.__name__ else True diff --git a/src/Mod/Path/Tools/Bit/60degree_Vbit.fctb b/src/Mod/Path/Tools/Bit/60degree_Vbit.fctb index 715361ec34..69d181d4a3 100644 --- a/src/Mod/Path/Tools/Bit/60degree_Vbit.fctb +++ b/src/Mod/Path/Tools/Bit/60degree_Vbit.fctb @@ -5,10 +5,10 @@ "parameter": { "CuttingEdgeAngle": "60.0000 \u00b0", "Diameter": "10.0000 mm", - "FlatHeight": "1.0000 mm", - "FlatRadius": "0.5000 mm", + "CuttingEdgeHeight": "1.0000 mm", + "TipDiameter": "1.0000 mm", "Length": "20.0000 mm", "ShankDiameter": "5.0000 mm" }, "attribute": {} -} \ No newline at end of file +} diff --git a/src/Mod/Path/Tools/Shape/v-bit.fcstd b/src/Mod/Path/Tools/Shape/v-bit.fcstd index 5d35188c81..d2711ad0af 100644 Binary files a/src/Mod/Path/Tools/Shape/v-bit.fcstd and b/src/Mod/Path/Tools/Shape/v-bit.fcstd differ