Skip to content

Commit

Permalink
Fixed v-carve depth calculation in the presence of a bottom diameter,…
Browse files Browse the repository at this point in the history
… added unit tests and cleaned up the v-bit parameter naming.
  • Loading branch information
mlampert committed Dec 13, 2020
1 parent 68eba58 commit c955bf4
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 38 deletions.
1 change: 1 addition & 0 deletions src/Mod/Path/CMakeLists.txt
Expand Up @@ -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
Expand Down
82 changes: 47 additions & 35 deletions src/Mod/Path/PathScripts/PathVcarve.py
Expand Up @@ -157,6 +157,45 @@ def closestTo(start, point):

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.'''
Expand Down Expand Up @@ -197,42 +236,10 @@ def opOnDocumentRestored(self, obj):
# 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):
Expand Down Expand Up @@ -290,10 +297,12 @@ def cutWire(edges):
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))
Expand Down Expand Up @@ -355,6 +364,9 @@ def opSetDefaultValues(self, obj, job):
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"]
Expand Down
114 changes: 114 additions & 0 deletions src/Mod/Path/PathTests/TestPathVcarve.py
@@ -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)

2 changes: 2 additions & 0 deletions src/Mod/Path/TestPathApp.py
Expand Up @@ -42,6 +42,7 @@
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
Expand All @@ -64,4 +65,5 @@
False if TestPathToolBit.__name__ else True
False if TestPathVoronoi.__name__ else True
False if TestPathThreadMilling.__name__ else True
False if TestPathVcarve.__name__ else True

6 changes: 3 additions & 3 deletions src/Mod/Path/Tools/Bit/60degree_Vbit.fctb
Expand Up @@ -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 modified src/Mod/Path/Tools/Shape/v-bit.fcstd
Binary file not shown.

0 comments on commit c955bf4

Please sign in to comment.