Fixed v-carve depth calculation in the presence of a bottom diameter, added unit tests and cleaned up the v-bit parameter naming.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"]
|
||||
|
||||
114
src/Mod/Path/PathTests/TestPathVcarve.py
Normal file
114
src/Mod/Path/PathTests/TestPathVcarve.py
Normal file
@@ -0,0 +1,114 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# ***************************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2020 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * *
|
||||
# * 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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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": {}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user