Merge pull request #4143 from mlampert/bugfix/vcarve-depth

Path: Bugfix/vcarve depth
This commit is contained in:
sliptonic
2020-12-14 17:18:30 -06:00
committed by GitHub
16 changed files with 181 additions and 50 deletions

View File

@@ -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

View File

@@ -222,7 +222,7 @@ def speedBetweenPoints(p0, p1, hSpeed, vSpeed):
pitch = pitch + 1
while pitch > 1:
pitch = pitch - 1
print(" pitch = %g %g (%.2f, %.2f, %.2f) -> %.2f" % (pitch, math.atan2(xy(d).Length, d.z), d.x, d.y, d.z, xy(d).Length))
PathLog.debug(" pitch = %g %g (%.2f, %.2f, %.2f) -> %.2f" % (pitch, math.atan2(xy(d).Length, d.z), d.x, d.y, d.z, xy(d).Length))
speed = vSpeed + pitch * (hSpeed - vSpeed)
if speed > hSpeed and speed > vSpeed:
return max(hSpeed, vSpeed)

View File

@@ -524,6 +524,10 @@ class ObjectOp(object):
result = self.opExecute(obj) # pylint: disable=assignment-from-no-return
if self.commandlist and (FeatureHeights & self.opFeatures(obj)):
# Let's finish by rapid to clearance...just for safety
self.commandlist.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value}))
path = Path.Path(self.commandlist)
obj.Path = path
obj.CycleTime = self.getCycleTimeEstimate(obj)

View File

@@ -203,7 +203,7 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage):
Command = PathOpGui.SetupOperation('Thread Milling',
PathThreadMilling.Create,
TaskPanelOpPage,
'Path-ThreadMilling',
'Path_ThreadMilling',
QtCore.QT_TRANSLATE_NOOP("PathThreadMilling", "Thread Milling"),
QtCore.QT_TRANSLATE_NOOP("PathThreadMilling", "Creates a Path Thread Milling operation from features of a base object"),
PathThreadMilling.SetupProperties)

View File

@@ -334,7 +334,7 @@ class ToolBitSelector(object):
tools = self.selectedOrAllTools()
for tool in tools:
tc = PathToolControllerGui.Create(tool[1].Label, tool[1], tool[0])
tc = PathToolControllerGui.Create("TC: {}".format(tool[1].Label), tool[1], tool[0])
job.Proxy.addToolController(tc)
FreeCAD.ActiveDocument.recompute()

View File

@@ -225,13 +225,13 @@ class ToolController:
obj.addProperty("App::PropertyLink", "Tool", "Base", QtCore.QT_TRANSLATE_NOOP("PathToolController", "The tool used by this controller"))
def Create(name='Default Tool', tool=None, toolNumber=1, assignViewProvider=True):
def Create(name='TC: Default Tool', tool=None, toolNumber=1, assignViewProvider=True):
legacyTool = PathPreferences.toolsReallyUseLegacyTools() if tool is None else isinstance(tool, Path.Tool)
PathLog.track(tool, toolNumber, legacyTool)
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name)
obj.Label = "TC: {}".format(name)
obj.Label = name
obj.Proxy = ToolController(obj, legacyTool)
if FreeCAD.GuiUp and assignViewProvider:

View File

@@ -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):
@@ -253,14 +260,12 @@ class ObjectVcarve(PathEngraveBase.ObjectOp):
path.append(Path.Command("G0 Z{}".format(obj.SafeHeight.Value)))
e = edges[0]
p = e.valueAt(e.FirstParameter)
path.append(Path.Command("G0 X{} Y{} Z{}".format(p.x, p.y,
obj.SafeHeight.Value)))
c = Path.Command("G1 X{} Y{} Z{} F{}".format(p.x, p.y, p.z,
obj.ToolController.HorizFeed.Value))
path.append(c)
path.append(Path.Command("G0 X{} Y{} Z{}".format(p.x, p.y, obj.SafeHeight.Value)))
hSpeed = obj.ToolController.HorizFeed.Value
vSpeed = obj.ToolController.VertFeed.Value
path.append(Path.Command("G1 X{} Y{} Z{} F{}".format(p.x, p.y, p.z, vSpeed)))
for e in edges:
path.extend(PathGeom.cmdsForEdge(e,
hSpeed=obj.ToolController.HorizFeed.Value))
path.extend(PathGeom.cmdsForEdge(e, hSpeed=hSpeed, vSpeed=vSpeed))
return path
@@ -290,10 +295,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 +362,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"]

View 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)

View File

@@ -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

View File

@@ -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.