Merge branch 'master' into bugfix/path-invalid-base-geometry-robustness
This commit is contained in:
@@ -34,6 +34,11 @@ import math
|
||||
import area
|
||||
from pivy import coin
|
||||
|
||||
# lazily loaded modules
|
||||
from lazy_loader.lazy_loader import LazyLoader
|
||||
Part = LazyLoader('Part', globals(), 'Part')
|
||||
TechDraw = LazyLoader('TechDraw', globals(), 'TechDraw')
|
||||
|
||||
__doc__ = "Class and implementation of the Adaptive path operation."
|
||||
|
||||
def convertTo2d(pathArray):
|
||||
@@ -399,14 +404,9 @@ def Execute(op,obj):
|
||||
if obj.Tolerance < 0.001:
|
||||
obj.Tolerance = 0.001
|
||||
|
||||
pathArray = []
|
||||
for base, subs in obj.Base:
|
||||
for sub in subs:
|
||||
shape = base.Shape.getElement(sub)
|
||||
for edge in shape.Edges:
|
||||
pathArray.append([discretize(edge)])
|
||||
# Get list of working edges for adaptive algorithm
|
||||
pathArray = _get_working_edges(op, obj)
|
||||
|
||||
#pathArray=connectEdges(edges)
|
||||
path2d = convertTo2d(pathArray)
|
||||
|
||||
stockPaths = []
|
||||
@@ -529,6 +529,35 @@ def Execute(op,obj):
|
||||
sceneClean()
|
||||
|
||||
|
||||
def _get_working_edges(op, obj):
|
||||
"""_get_working_edges(op, obj)...
|
||||
Compile all working edges from the Base Geometry selection (obj.Base)
|
||||
for the current operation.
|
||||
Additional modifications to selected region(face), such as extensions,
|
||||
should be placed within this function.
|
||||
"""
|
||||
pathArray = list()
|
||||
|
||||
for base, subs in obj.Base:
|
||||
for sub in subs:
|
||||
if obj.UseOutline:
|
||||
face = base.Shape.getElement(sub)
|
||||
zmin = face.BoundBox.ZMin
|
||||
# get face outline with same method in PocketShape
|
||||
wire = TechDraw.findShapeOutline(face, 1, FreeCAD.Vector(0.0, 0.0, 1.0))
|
||||
shape = Part.Face(wire)
|
||||
# translate to face height if necessary
|
||||
if shape.BoundBox.ZMin != zmin:
|
||||
shape.translate(FreeCAD.Vector(0.0, 0.0, zmin - shape.BoundBox.ZMin))
|
||||
else:
|
||||
shape = base.Shape.getElement(sub)
|
||||
|
||||
for edge in shape.Edges:
|
||||
pathArray.append([discretize(edge)])
|
||||
|
||||
return pathArray
|
||||
|
||||
|
||||
class PathAdaptive(PathOp.ObjectOp):
|
||||
def opFeatures(self, obj):
|
||||
'''opFeatures(obj) ... returns the OR'ed list of features used and supported by the operation.
|
||||
@@ -574,6 +603,11 @@ class PathAdaptive(PathOp.ObjectOp):
|
||||
obj.addProperty("App::PropertyAngle", "HelixConeAngle", "Adaptive", "Helix cone angle (degrees)")
|
||||
obj.addProperty("App::PropertyLength", "HelixDiameterLimit", "Adaptive", "Limit helix entry diameter, if limit larger than tool diameter or 0, tool diameter is used")
|
||||
|
||||
if not hasattr(obj, "UseOutline"):
|
||||
obj.addProperty("App::PropertyBool",
|
||||
"UseOutline",
|
||||
"Adaptive",
|
||||
"Uses the outline of the base geometry.")
|
||||
|
||||
def opSetDefaultValues(self, obj, job):
|
||||
obj.Side="Inside"
|
||||
@@ -594,6 +628,7 @@ class PathAdaptive(PathOp.ObjectOp):
|
||||
obj.StockToLeave = 0
|
||||
obj.KeepToolDownRatio = 3.0
|
||||
obj.UseHelixArcs = False
|
||||
obj.UseOutline = False
|
||||
|
||||
def opExecute(self, obj):
|
||||
'''opExecute(obj) ... called whenever the receiver needs to be recalculated.
|
||||
|
||||
@@ -140,6 +140,11 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
form.FinishingProfile.setChecked(True)
|
||||
formLayout.addRow(QtGui.QLabel("Finishing Profile"), form.FinishingProfile)
|
||||
|
||||
# Use outline checkbox
|
||||
form.useOutline = QtGui.QCheckBox()
|
||||
form.useOutline.setChecked(False)
|
||||
formLayout.addRow(QtGui.QLabel("Use outline"), form.useOutline)
|
||||
|
||||
layout.addLayout(formLayout)
|
||||
|
||||
# stop button
|
||||
@@ -170,6 +175,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
# signals.append(self.form.ProcessHoles.stateChanged)
|
||||
signals.append(self.form.ForceInsideOut.stateChanged)
|
||||
signals.append(self.form.FinishingProfile.stateChanged)
|
||||
signals.append(self.form.useOutline.stateChanged)
|
||||
signals.append(self.form.StopButton.toggled)
|
||||
return signals
|
||||
|
||||
@@ -191,6 +197,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
# self.form.ProcessHoles.setChecked(obj.ProcessHoles)
|
||||
self.form.ForceInsideOut.setChecked(obj.ForceInsideOut)
|
||||
self.form.FinishingProfile.setChecked(obj.FinishingProfile)
|
||||
self.form.useOutline.setChecked(obj.UseOutline)
|
||||
self.setupToolController(obj, self.form.ToolController)
|
||||
self.setupCoolant(obj, self.form.coolantController)
|
||||
self.form.StopButton.setChecked(obj.Stopped)
|
||||
@@ -221,6 +228,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
|
||||
obj.ForceInsideOut = self.form.ForceInsideOut.isChecked()
|
||||
obj.FinishingProfile = self.form.FinishingProfile.isChecked()
|
||||
obj.UseOutline = self.form.useOutline.isChecked()
|
||||
obj.Stopped = self.form.StopButton.isChecked()
|
||||
if(obj.Stopped):
|
||||
self.form.StopButton.setChecked(False) # reset the button
|
||||
|
||||
@@ -284,29 +284,21 @@ def cmdsForEdge(edge, flip = False, useHelixForBSpline = True, segm = 50, hSpeed
|
||||
else:
|
||||
# We're dealing with a helix or a more complex shape and it has to get approximated
|
||||
# by a number of straight segments
|
||||
eStraight = Part.Edge(Part.LineSegment(p1, p3))
|
||||
esP2 = eStraight.valueAt((eStraight.FirstParameter + eStraight.LastParameter)/2)
|
||||
deviation = (p2 - esP2).Length
|
||||
if isRoughly(deviation, 0):
|
||||
return [ Path.Command('G1', {'X': p3.x, 'Y': p3.y, 'Z': p3.z}) ]
|
||||
# at this point pixellation is all we can do
|
||||
points = edge.discretize(Deflection=0.01)
|
||||
if flip:
|
||||
points = points[::-1]
|
||||
|
||||
commands = []
|
||||
segments = int(math.ceil((deviation / eStraight.Length) * segm))
|
||||
#print("**** pixellation with %d segments" % segments)
|
||||
dParameter = (edge.LastParameter - edge.FirstParameter) / segments
|
||||
# starting point
|
||||
p0 = edge.valueAt(edge.LastParameter) if flip else edge.valueAt(edge.FirstParameter)
|
||||
for i in range(0, segments):
|
||||
if flip:
|
||||
p = edge.valueAt(edge.LastParameter - (i + 1) * dParameter)
|
||||
else:
|
||||
p = edge.valueAt(edge.FirstParameter + (i + 1) * dParameter)
|
||||
if hSpeed > 0 and vSpeed > 0:
|
||||
params.update({'F': speedBetweenPoints(p0, p, hSpeed, vSpeed)})
|
||||
cmd = Path.Command('G1', {'X': p.x, 'Y': p.y, 'Z': p.z})
|
||||
#print("***** %s" % cmd)
|
||||
commands.append(cmd)
|
||||
p0 = p
|
||||
if points:
|
||||
p0 = points[0]
|
||||
for p in points[1:]:
|
||||
params = {'X': p.x, 'Y': p.y, 'Z': p.z}
|
||||
if hSpeed > 0 and vSpeed > 0:
|
||||
params['F'] = speedBetweenPoints(p0, p, hSpeed, vSpeed)
|
||||
cmd = Path.Command('G1', params)
|
||||
# print("***** {}".format(cmd))
|
||||
commands.append(cmd)
|
||||
p0 = p
|
||||
#print commands
|
||||
return commands
|
||||
|
||||
@@ -542,13 +534,18 @@ def flipEdge(edge):
|
||||
flipped.buildFromPolesMultsKnots(poles, mults , knots, perio, degree, weights, ratio)
|
||||
|
||||
return Part.Edge(flipped)
|
||||
elif type(edge.Curve) == Part.OffsetCurve:
|
||||
return edge.reversed()
|
||||
|
||||
global OddsAndEnds # pylint: disable=global-statement
|
||||
OddsAndEnds.append(edge)
|
||||
PathLog.warning(translate('PathGeom', "%s not support for flipping") % type(edge.Curve))
|
||||
PathLog.warning(translate('PathGeom', "%s not supported for flipping") % type(edge.Curve))
|
||||
|
||||
Wire = []
|
||||
|
||||
def flipWire(wire):
|
||||
'''Flip the entire wire and all its edges so it is being processed the other way around.'''
|
||||
Wire.append(wire)
|
||||
edges = [flipEdge(e) for e in wire.Edges]
|
||||
edges.reverse()
|
||||
PathLog.debug(edges)
|
||||
|
||||
@@ -29,10 +29,10 @@ from FreeCAD import Vector
|
||||
from PathTests.PathTestUtils import PathTestBase
|
||||
|
||||
class TestPathGeom(PathTestBase):
|
||||
"""Test Path <-> Wire conversion."""
|
||||
'''Test Path <-> Wire conversion.'''
|
||||
|
||||
def test00(self):
|
||||
"""Verify getAngle functionality."""
|
||||
'''Verify getAngle functionality.'''
|
||||
self.assertRoughly(PathGeom.getAngle(Vector(1, 0, 0)), 0)
|
||||
self.assertRoughly(PathGeom.getAngle(Vector(1, 1, 0)), math.pi/4)
|
||||
self.assertRoughly(PathGeom.getAngle(Vector(0, 1, 0)), math.pi/2)
|
||||
@@ -43,7 +43,7 @@ class TestPathGeom(PathTestBase):
|
||||
self.assertRoughly(PathGeom.getAngle(Vector(1, -1, 0)), -math.pi/4)
|
||||
|
||||
def test01(self):
|
||||
"""Verify diffAngle functionality."""
|
||||
'''Verify diffAngle functionality.'''
|
||||
self.assertRoughly(PathGeom.diffAngle(0, +0*math.pi/4, 'CW') / math.pi, 0/4.)
|
||||
self.assertRoughly(PathGeom.diffAngle(0, +3*math.pi/4, 'CW') / math.pi, 5/4.)
|
||||
self.assertRoughly(PathGeom.diffAngle(0, -3*math.pi/4, 'CW') / math.pi, 3/4.)
|
||||
@@ -68,7 +68,7 @@ class TestPathGeom(PathTestBase):
|
||||
self.assertRoughly(PathGeom.diffAngle(-math.pi/4, -1*math.pi/4, 'CCW') / math.pi, 0/4.)
|
||||
|
||||
def test02(self):
|
||||
"""Verify isVertical/isHorizontal for Vector"""
|
||||
'''Verify isVertical/isHorizontal for Vector'''
|
||||
self.assertTrue(PathGeom.isVertical(Vector(0, 0, 1)))
|
||||
self.assertTrue(PathGeom.isVertical(Vector(0, 0, -1)))
|
||||
self.assertFalse(PathGeom.isVertical(Vector(1, 0, 1)))
|
||||
@@ -89,7 +89,7 @@ class TestPathGeom(PathTestBase):
|
||||
self.assertFalse(PathGeom.isHorizontal(Vector(0, -1, -1)))
|
||||
|
||||
def test03(self):
|
||||
"""Verify isVertical/isHorizontal for Edges"""
|
||||
'''Verify isVertical/isHorizontal for Edges'''
|
||||
|
||||
# lines
|
||||
self.assertTrue(PathGeom.isVertical(Part.Edge(Part.LineSegment(Vector(-1, -1, -1), Vector(-1, -1, 8)))))
|
||||
@@ -136,7 +136,7 @@ class TestPathGeom(PathTestBase):
|
||||
|
||||
|
||||
def test04(self):
|
||||
"""Verify isVertical/isHorizontal for faces"""
|
||||
'''Verify isVertical/isHorizontal for faces'''
|
||||
|
||||
# planes
|
||||
xPlane = Part.makePlane(100, 100, Vector(), Vector(1, 0, 0))
|
||||
@@ -176,7 +176,7 @@ class TestPathGeom(PathTestBase):
|
||||
self.assertFalse(PathGeom.isHorizontal(yzCylinder))
|
||||
|
||||
def test07(self):
|
||||
"""Verify speed interpolation works for different pitches"""
|
||||
'''Verify speed interpolation works for different pitches'''
|
||||
# horizontal
|
||||
self.assertRoughly(100, PathGeom.speedBetweenPoints(Vector(), Vector(1,1,0), 100, 50))
|
||||
self.assertRoughly(100, PathGeom.speedBetweenPoints(Vector(1,1,0), Vector(), 100, 50))
|
||||
@@ -202,7 +202,7 @@ class TestPathGeom(PathTestBase):
|
||||
self.assertRoughly( 83.33, PathGeom.speedBetweenPoints(Vector(0,1,0.5774), Vector(), 100, 50), 0.01)
|
||||
|
||||
def test08(self):
|
||||
"""Verify speed interpolation works for different pitches if vSpeed > hSpeed"""
|
||||
'''Verify speed interpolation works for different pitches if vSpeed > hSpeed'''
|
||||
# horizontal
|
||||
self.assertRoughly( 50, PathGeom.speedBetweenPoints(Vector(), Vector(1,1,0), 50, 100))
|
||||
self.assertRoughly( 50, PathGeom.speedBetweenPoints(Vector(1,1,0), Vector(), 50, 100))
|
||||
@@ -228,13 +228,13 @@ class TestPathGeom(PathTestBase):
|
||||
self.assertRoughly( 66.66, PathGeom.speedBetweenPoints(Vector(0,1,0.5774), Vector(), 50, 100), 0.01)
|
||||
|
||||
def test10(self):
|
||||
"""Verify proper geometry objects for G1 and G01 commands are created."""
|
||||
'''Verify proper geometry objects for G1 and G01 commands are created.'''
|
||||
spt = Vector(1,2,3)
|
||||
self.assertLine(PathGeom.edgeForCmd(Path.Command('G1', {'X': 7, 'Y': 2, 'Z': 3}), spt), spt, Vector(7, 2, 3))
|
||||
self.assertLine(PathGeom.edgeForCmd(Path.Command('G01', {'X': 1, 'Y': 3, 'Z': 5}), spt), spt, Vector(1, 3, 5))
|
||||
|
||||
def test20(self):
|
||||
"""Verify proper geometry for arcs in the XY-plane are created."""
|
||||
'''Verify proper geometry for arcs in the XY-plane are created.'''
|
||||
p1 = Vector(0, -1, 2)
|
||||
p2 = Vector(-1, 0, 2)
|
||||
self.assertArc(
|
||||
@@ -247,7 +247,7 @@ class TestPathGeom(PathTestBase):
|
||||
p2, p1, 'CCW')
|
||||
|
||||
def test30(self):
|
||||
"""Verify proper geometry for arcs with rising and fall ing Z-axis are created."""
|
||||
'''Verify proper geometry for arcs with rising and fall ing Z-axis are created.'''
|
||||
#print("------ rising helix -------")
|
||||
p1 = Vector(0, 1, 0)
|
||||
p2 = Vector(1, 0, 2)
|
||||
@@ -277,7 +277,7 @@ class TestPathGeom(PathTestBase):
|
||||
p1, Vector(-1/math.sqrt(2), -1/math.sqrt(2), 1), p2)
|
||||
|
||||
def test40(self):
|
||||
"""Verify arc results in proper G2/3 command."""
|
||||
'''Verify arc results in proper G2/3 command.'''
|
||||
p1 = Vector( 0, -10, 0)
|
||||
p2 = Vector(-10, 0, 0)
|
||||
p3 = Vector( 0, +10, 0)
|
||||
@@ -295,7 +295,7 @@ class TestPathGeom(PathTestBase):
|
||||
self.assertCommandEqual(cmds(p1, p4, p3, True), cmd('G2', p1, Vector(0, -10, 0)))
|
||||
|
||||
def test41(self):
|
||||
"""Verify circle results in proper G2/G3 commands."""
|
||||
'''Verify circle results in proper G2/G3 commands.'''
|
||||
|
||||
def cmds(center, radius, up = True):
|
||||
norm = Vector(0, 0, 1) if up else Vector(0, 0, -1)
|
||||
@@ -308,8 +308,16 @@ class TestPathGeom(PathTestBase):
|
||||
|
||||
self.assertCommandEqual(cmds(center, radius), cmd('G3', Vector(15, 10, 0), Vector(-5, 0, 0)))
|
||||
|
||||
def test42(self):
|
||||
'''Verify ellipsis results in a proper segmentation of G1 commands.'''
|
||||
ellipse = Part.Edge(Part.Ellipse())
|
||||
cmds = PathGeom.cmdsForEdge(ellipse)
|
||||
# let's make sure all commands are G1 and there are more than 20 of those
|
||||
self.assertGreater(len(cmds), 20)
|
||||
self.assertTrue(all([cmd.Name == 'G1' for cmd in cmds]))
|
||||
|
||||
def test50(self):
|
||||
"""Verify proper wire(s) aggregation from a Path."""
|
||||
'''Verify proper wire(s) aggregation from a Path.'''
|
||||
commands = []
|
||||
commands.append(Path.Command('G1', {'X': 1}))
|
||||
commands.append(Path.Command('G1', {'Y': 1}))
|
||||
@@ -335,7 +343,7 @@ class TestPathGeom(PathTestBase):
|
||||
|
||||
|
||||
def test60(self):
|
||||
"""Verify arcToHelix returns proper helix."""
|
||||
'''Verify arcToHelix returns proper helix.'''
|
||||
p1 = Vector(10,-10,0)
|
||||
p2 = Vector(0,0,0)
|
||||
p3 = Vector(10,10,0)
|
||||
@@ -368,7 +376,7 @@ class TestPathGeom(PathTestBase):
|
||||
|
||||
|
||||
def test62(self):
|
||||
"""Verify splitArcAt returns proper subarcs."""
|
||||
'''Verify splitArcAt returns proper subarcs.'''
|
||||
p1 = Vector(10,-10,0)
|
||||
p2 = Vector(0,0,0)
|
||||
p3 = Vector(10,10,0)
|
||||
@@ -392,7 +400,7 @@ class TestPathGeom(PathTestBase):
|
||||
|
||||
|
||||
def test65(self):
|
||||
"""Verify splitEdgeAt."""
|
||||
'''Verify splitEdgeAt.'''
|
||||
|
||||
# split a line segment
|
||||
e = PathGeom.splitEdgeAt(Part.Edge(Part.LineSegment(Vector(), Vector(2, 4, 6))), Vector(1, 2, 3))
|
||||
@@ -522,4 +530,16 @@ class TestPathGeom(PathTestBase):
|
||||
edge = Part.Edge(Part.BSplineCurve([Vector(-8,4,0), Vector(1,-5,0), Vector(5,11,0), Vector(12,-5,0)], weights=[2,3,5,7]))
|
||||
self.assertEdgeShapesMatch(edge, PathGeom.flipEdge(edge))
|
||||
|
||||
def test76(self):
|
||||
'''Flip an offset wire'''
|
||||
|
||||
e0 = Part.Edge(Part.BSplineCurve([Vector(-8,4,0), Vector(1,-5,0), Vector(5,11,0), Vector(12,-5,0)], weights=[2,3,5,7]))
|
||||
e1 = Part.Edge(Part.LineSegment(Vector(12,-5,0), Vector(0,-7,0)))
|
||||
e2 = Part.Edge(Part.LineSegment(Vector(0,-7,0), Vector(-8,4,0)))
|
||||
w0 = Part.Wire([e0, e1, e2])
|
||||
w1 = w0.makeOffset2D(1)
|
||||
w2 = PathGeom.flipWire(w1)
|
||||
# do some sanity checks
|
||||
self.assertTrue(w2.isValid())
|
||||
self.assertTrue(w2.isClosed())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user