Fixed open wire offsetting and orienting.
This commit is contained in:
@@ -103,30 +103,41 @@ def offsetWire(wire, base, offset, forward):
|
||||
# offsetting a single edge doesn't work because there is an infinite
|
||||
# possible planes into which the edge could be offset
|
||||
# luckily, the plane here must be the XY-plane ...
|
||||
n = (edge.Vertexes[1].Point - edge.Vertexes[0].Point).cross(FreeCAD.Vector(0, 0, 1))
|
||||
p0 = edge.Vertexes[0].Point
|
||||
v0 = edge.Vertexes[1].Point - p0
|
||||
n = v0.cross(FreeCAD.Vector(0, 0, 1))
|
||||
o = n.normalize() * offset
|
||||
edge.translate(o)
|
||||
if base.isInside(edge.valueAt((edge.FirstParameter + edge.LastParameter)/2), offset/2, True):
|
||||
|
||||
# offset edde the other way if the result is inside
|
||||
if base.isInside(edge.valueAt((edge.FirstParameter + edge.LastParameter) / 2), offset / 2, True):
|
||||
edge.translate(-2 * o)
|
||||
w = Part.Wire([edge])
|
||||
return orientWire(w, forward)
|
||||
|
||||
# flip the edge if it's not on the right side of the original edge
|
||||
if forward is not None:
|
||||
v1 = edge.Vertexes[1].Point - p0
|
||||
left = PathGeom.Side.Left == PathGeom.Side.of(v0, v1)
|
||||
if left != forward:
|
||||
edge = PathGeom.flipEdge(edge)
|
||||
return Part.Wire([edge])
|
||||
|
||||
# if we get to this point the assumption is that makeOffset2D can deal with the edge
|
||||
pass
|
||||
|
||||
w = wire.makeOffset2D(offset)
|
||||
offsetWire = wire.makeOffset2D(offset)
|
||||
|
||||
if wire.isClosed():
|
||||
if not base.isInside(w.Edges[0].Vertexes[0].Point, offset/2, True):
|
||||
if not base.isInside(offsetWire.Edges[0].Vertexes[0].Point, offset/2, True):
|
||||
PathLog.track('closed - outside')
|
||||
return orientWire(w, forward)
|
||||
return orientWire(offsetWire, forward)
|
||||
PathLog.track('closed - inside')
|
||||
try:
|
||||
w = wire.makeOffset2D(-offset)
|
||||
offsetWire = wire.makeOffset2D(-offset)
|
||||
except:
|
||||
# most likely offsetting didn't work because the wire is a hole
|
||||
# and the offset is too big - making the hole vanish
|
||||
return None
|
||||
return orientWire(w, forward)
|
||||
return orientWire(offsetWire, forward)
|
||||
|
||||
# An edge is considered to be inside of shape if the mid point is inside
|
||||
# Of the remaining edges we take the longest wire to be the engraving side
|
||||
@@ -137,12 +148,15 @@ def offsetWire(wire, base, offset, forward):
|
||||
# if they need to be discarded, split, that should happen in a post process
|
||||
# Depending on the Axis of the circle, and which side remains we know if the wire needs to be flipped
|
||||
|
||||
# first, let's make sure all edges are oriented the proper way
|
||||
wire = orientWire(wire, None)
|
||||
|
||||
# find edges that are not inside the shape
|
||||
def isInside(edge):
|
||||
if base.isInside(edge.Vertexes[0].Point, offset/2, True) and base.isInside(edge.Vertexes[-1].Point, offset/2, True):
|
||||
return True
|
||||
return False
|
||||
outside = [e for e in w.Edges if not isInside(e)]
|
||||
outside = [e for e in offsetWire.Edges if not isInside(e)]
|
||||
# discard all edges that are not part of the longest wire
|
||||
longestWire = None
|
||||
for w in [Part.Wire(el) for el in Part.sortEdges(outside)]:
|
||||
@@ -155,17 +169,22 @@ def offsetWire(wire, base, offset, forward):
|
||||
|
||||
def isCircleAt(edge, center):
|
||||
'''isCircleAt(edge, center) ... helper function returns True if edge is a circle at the given center.'''
|
||||
if Part.Circel == type(edge.Curve) or Part.ArcOfCircle == type(edge.Curve):
|
||||
if Part.Circle == type(edge.Curve) or Part.ArcOfCircle == type(edge.Curve):
|
||||
return PathGeom.pointsCoincide(edge.Curve.Center, center)
|
||||
return False
|
||||
|
||||
|
||||
# split offset wire into edges to the left side and edges to the right side
|
||||
collectLeft = False
|
||||
collectRight = False
|
||||
leftSideEdges = []
|
||||
rightSideEdges = []
|
||||
|
||||
for e in (w.Edges + w.Edges):
|
||||
# traverse through all edges in order and start collecting them when we encounter
|
||||
# an end point (circle centered at one of the end points of the original wire).
|
||||
# should we come to an end point and determine that we've already collected the
|
||||
# next side, we're done
|
||||
for e in (offsetWire.Edges + offsetWire.Edges):
|
||||
if isCircleAt(e, start):
|
||||
if PathGeom.pointsCoincide(e.Curve.Axis, FreeCAD.Vector(0, 0, 1)):
|
||||
if not collectLeft and leftSideEdges:
|
||||
@@ -193,15 +212,22 @@ def offsetWire(wire, base, offset, forward):
|
||||
elif collectRight:
|
||||
rightSideEdges.append(e)
|
||||
|
||||
# figure out if all the left sided edges or the right sided edges are the ones
|
||||
# that are 'outside'. However, we return the full side.
|
||||
edges = leftSideEdges
|
||||
for e in longestWire.Edges:
|
||||
for e0 in rightSideEdges:
|
||||
if PathGeom.edgesMatch(e, e0):
|
||||
if forward:
|
||||
edges = [PathGeom.flipEdge(edge) for edge in rightSideEdges]
|
||||
return Part.Wire(edges)
|
||||
edges = rightSideEdges
|
||||
if not forward:
|
||||
edges.reverse()
|
||||
return orientWire(Part.Wire(edges), None)
|
||||
|
||||
# at this point we have the correct edges and they are in the order for forward
|
||||
# traversal (climb milling). If that's not what we want just reverse the order,
|
||||
# orientWire takes care of orienting the edges appropriately.
|
||||
if not forward:
|
||||
edges = [PathGeom.flipEdge(edge) for edge in rightSideEdges]
|
||||
return Part.Wire(edges)
|
||||
edges.reverse()
|
||||
|
||||
return orientWire(Part.Wire(edges), None)
|
||||
|
||||
|
||||
@@ -467,4 +467,284 @@ class TestPathGeomOp(PathTestUtils.PathTestBase):
|
||||
self.assertCoincide(Vector(0, 0, -1), e.Curve.Axis)
|
||||
|
||||
|
||||
def test30(self):
|
||||
'''Check offsetting a single outside edge forward.'''
|
||||
obj = doc.getObjectsByLabel('offset-edge')[0]
|
||||
|
||||
w = getWireOutside(obj)
|
||||
length = 40 * math.cos(math.pi/6)
|
||||
for e in w.Edges:
|
||||
self.assertRoughly(length, e.Length)
|
||||
|
||||
# let's offset the horizontal edge for starters
|
||||
hEdges = [e for e in w.Edges if PathGeom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y)]
|
||||
|
||||
x = length / 2
|
||||
y = -10
|
||||
self.assertEqual(1, len(hEdges))
|
||||
edge = hEdges[0]
|
||||
|
||||
self.assertCoincide(Vector(-x, y, 0), edge.Vertexes[0].Point)
|
||||
self.assertCoincide(Vector(+x, y, 0), edge.Vertexes[1].Point)
|
||||
|
||||
wire = PathGeomOp.offsetWire(Part.Wire([edge]), obj.Shape, 5, True)
|
||||
self.assertEqual(1, len(wire.Edges))
|
||||
|
||||
y = y - 5
|
||||
self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
||||
self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[1].Point)
|
||||
|
||||
# make sure we get the same result even if the edge is oriented the other way
|
||||
edge = PathGeom.flipEdge(edge)
|
||||
wire = PathGeomOp.offsetWire(Part.Wire([edge]), obj.Shape, 5, True)
|
||||
self.assertEqual(1, len(wire.Edges))
|
||||
|
||||
self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
||||
self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[1].Point)
|
||||
|
||||
def test31(self):
|
||||
'''Check offsetting a single outside edge not forward.'''
|
||||
obj = doc.getObjectsByLabel('offset-edge')[0]
|
||||
|
||||
w = getWireOutside(obj)
|
||||
length = 40 * math.cos(math.pi/6)
|
||||
for e in w.Edges:
|
||||
self.assertRoughly(length, e.Length)
|
||||
|
||||
# let's offset the horizontal edge for starters
|
||||
hEdges = [e for e in w.Edges if PathGeom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y)]
|
||||
|
||||
x = length / 2
|
||||
y = -10
|
||||
self.assertEqual(1, len(hEdges))
|
||||
edge = hEdges[0]
|
||||
self.assertCoincide(Vector(-x, y, 0), edge.Vertexes[0].Point)
|
||||
self.assertCoincide(Vector(+x, y, 0), edge.Vertexes[1].Point)
|
||||
|
||||
wire = PathGeomOp.offsetWire(Part.Wire([edge]), obj.Shape, 5, False)
|
||||
self.assertEqual(1, len(wire.Edges))
|
||||
|
||||
y = y - 5
|
||||
self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
||||
self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[1].Point)
|
||||
|
||||
# make sure we get the same result on a reversed edge
|
||||
edge = PathGeom.flipEdge(edge)
|
||||
wire = PathGeomOp.offsetWire(Part.Wire([edge]), obj.Shape, 5, False)
|
||||
self.assertEqual(1, len(wire.Edges))
|
||||
|
||||
self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
||||
self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[1].Point)
|
||||
|
||||
def test32(self):
|
||||
'''Check offsetting multiple outside edges.'''
|
||||
obj = doc.getObjectsByLabel('offset-edge')[0]
|
||||
|
||||
w = getWireOutside(obj)
|
||||
length = 40 * math.cos(math.pi/6)
|
||||
|
||||
# let's offset the other two legs
|
||||
lEdges = [e for e in w.Edges if not PathGeom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y)]
|
||||
self.assertEqual(2, len(lEdges))
|
||||
|
||||
wire = PathGeomOp.offsetWire(Part.Wire(lEdges), obj.Shape, 2, True)
|
||||
|
||||
x = length/2 + 2 * math.cos(math.pi/6)
|
||||
y = -10 + 2 * math.sin(math.pi/6)
|
||||
|
||||
self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
||||
self.assertCoincide(Vector(+x, y, 0), wire.Edges[-1].Vertexes[1].Point)
|
||||
|
||||
rEdges = [e for e in wire.Edges if Part.Circle == type(e.Curve)]
|
||||
|
||||
self.assertEqual(1, len(rEdges))
|
||||
self.assertCoincide(Vector(0, 20, 0), rEdges[0].Curve.Center)
|
||||
self.assertCoincide(Vector(0, 0, -1), rEdges[0].Curve.Axis)
|
||||
|
||||
#offset the other way
|
||||
wire = PathGeomOp.offsetWire(Part.Wire(lEdges), obj.Shape, 2, False)
|
||||
|
||||
self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
||||
self.assertCoincide(Vector(-x, y, 0), wire.Edges[-1].Vertexes[1].Point)
|
||||
|
||||
rEdges = [e for e in wire.Edges if Part.Circle == type(e.Curve)]
|
||||
|
||||
self.assertEqual(1, len(rEdges))
|
||||
self.assertCoincide(Vector(0, 20, 0), rEdges[0].Curve.Center)
|
||||
self.assertCoincide(Vector(0, 0, +1), rEdges[0].Curve.Axis)
|
||||
|
||||
def test33(self):
|
||||
'''Check offsetting multiple backwards outside edges.'''
|
||||
# This is exactly the same as test32, except that the wire is flipped to make
|
||||
# sure the input orientation doesn't matter
|
||||
obj = doc.getObjectsByLabel('offset-edge')[0]
|
||||
|
||||
w = getWireOutside(obj)
|
||||
length = 40 * math.cos(math.pi/6)
|
||||
|
||||
# let's offset the other two legs
|
||||
lEdges = [e for e in w.Edges if not PathGeom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y)]
|
||||
self.assertEqual(2, len(lEdges))
|
||||
|
||||
w = PathGeom.flipWire(Part.Wire(lEdges))
|
||||
wire = PathGeomOp.offsetWire(w, obj.Shape, 2, True)
|
||||
|
||||
x = length/2 + 2 * math.cos(math.pi/6)
|
||||
y = -10 + 2 * math.sin(math.pi/6)
|
||||
|
||||
self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
||||
self.assertCoincide(Vector(+x, y, 0), wire.Edges[-1].Vertexes[1].Point)
|
||||
|
||||
rEdges = [e for e in wire.Edges if Part.Circle == type(e.Curve)]
|
||||
|
||||
self.assertEqual(1, len(rEdges))
|
||||
self.assertCoincide(Vector(0, 20, 0), rEdges[0].Curve.Center)
|
||||
self.assertCoincide(Vector(0, 0, -1), rEdges[0].Curve.Axis)
|
||||
|
||||
#offset the other way
|
||||
wire = PathGeomOp.offsetWire(Part.Wire(lEdges), obj.Shape, 2, False)
|
||||
|
||||
self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
||||
self.assertCoincide(Vector(-x, y, 0), wire.Edges[-1].Vertexes[1].Point)
|
||||
|
||||
rEdges = [e for e in wire.Edges if Part.Circle == type(e.Curve)]
|
||||
|
||||
self.assertEqual(1, len(rEdges))
|
||||
self.assertCoincide(Vector(0, 20, 0), rEdges[0].Curve.Center)
|
||||
self.assertCoincide(Vector(0, 0, +1), rEdges[0].Curve.Axis)
|
||||
|
||||
def test34(self):
|
||||
'''Check offsetting a single inside edge forward.'''
|
||||
obj = doc.getObjectsByLabel('offset-edge')[0]
|
||||
|
||||
w = getWireInside(obj)
|
||||
length = 20 * math.cos(math.pi/6)
|
||||
for e in w.Edges:
|
||||
self.assertRoughly(length, e.Length)
|
||||
|
||||
# let's offset the horizontal edge for starters
|
||||
hEdges = [e for e in w.Edges if PathGeom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y)]
|
||||
|
||||
x = length / 2
|
||||
y = -5
|
||||
self.assertEqual(1, len(hEdges))
|
||||
edge = hEdges[0]
|
||||
|
||||
self.assertCoincide(Vector(-x, y, 0), edge.Vertexes[0].Point)
|
||||
self.assertCoincide(Vector(+x, y, 0), edge.Vertexes[1].Point)
|
||||
|
||||
wire = PathGeomOp.offsetWire(Part.Wire([edge]), obj.Shape, 2, True)
|
||||
self.assertEqual(1, len(wire.Edges))
|
||||
|
||||
y = y + 2
|
||||
self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
||||
self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[1].Point)
|
||||
|
||||
# make sure we get the same result even if the edge is oriented the other way
|
||||
edge = PathGeom.flipEdge(edge)
|
||||
wire = PathGeomOp.offsetWire(Part.Wire([edge]), obj.Shape, 2, True)
|
||||
self.assertEqual(1, len(wire.Edges))
|
||||
|
||||
self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
||||
self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[1].Point)
|
||||
|
||||
def test35(self):
|
||||
'''Check offsetting a single inside edge not forward.'''
|
||||
obj = doc.getObjectsByLabel('offset-edge')[0]
|
||||
|
||||
w = getWireInside(obj)
|
||||
length = 20 * math.cos(math.pi/6)
|
||||
for e in w.Edges:
|
||||
self.assertRoughly(length, e.Length)
|
||||
|
||||
# let's offset the horizontal edge for starters
|
||||
hEdges = [e for e in w.Edges if PathGeom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y)]
|
||||
|
||||
x = length / 2
|
||||
y = -5
|
||||
self.assertEqual(1, len(hEdges))
|
||||
edge = hEdges[0]
|
||||
|
||||
self.assertCoincide(Vector(-x, y, 0), edge.Vertexes[0].Point)
|
||||
self.assertCoincide(Vector(+x, y, 0), edge.Vertexes[1].Point)
|
||||
|
||||
wire = PathGeomOp.offsetWire(Part.Wire([edge]), obj.Shape, 2, False)
|
||||
self.assertEqual(1, len(wire.Edges))
|
||||
|
||||
y = y + 2
|
||||
self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
||||
self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[1].Point)
|
||||
|
||||
# make sure we get the same result even if the edge is oriented the other way
|
||||
edge = PathGeom.flipEdge(edge)
|
||||
wire = PathGeomOp.offsetWire(Part.Wire([edge]), obj.Shape, 2, False)
|
||||
self.assertEqual(1, len(wire.Edges))
|
||||
|
||||
self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
||||
self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[1].Point)
|
||||
|
||||
def test36(self):
|
||||
'''Check offsetting multiple inside edges.'''
|
||||
obj = doc.getObjectsByLabel('offset-edge')[0]
|
||||
|
||||
w = getWireInside(obj)
|
||||
length = 20 * math.cos(math.pi/6)
|
||||
|
||||
# let's offset the other two legs
|
||||
lEdges = [e for e in w.Edges if not PathGeom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y)]
|
||||
self.assertEqual(2, len(lEdges))
|
||||
|
||||
wire = PathGeomOp.offsetWire(Part.Wire(lEdges), obj.Shape, 2, True)
|
||||
|
||||
x = length/2 - 2 * math.cos(math.pi/6)
|
||||
y = -5 - 2 * math.sin(math.pi/6)
|
||||
|
||||
self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
||||
self.assertCoincide(Vector(-x, y, 0), wire.Edges[-1].Vertexes[1].Point)
|
||||
|
||||
rEdges = [e for e in wire.Edges if Part.Circle == type(e.Curve)]
|
||||
self.assertEqual(0, len(rEdges))
|
||||
|
||||
#offset the other way
|
||||
wire = PathGeomOp.offsetWire(Part.Wire(lEdges), obj.Shape, 2, False)
|
||||
|
||||
self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
||||
self.assertCoincide(Vector(+x, y, 0), wire.Edges[-1].Vertexes[1].Point)
|
||||
|
||||
rEdges = [e for e in wire.Edges if Part.Circle == type(e.Curve)]
|
||||
self.assertEqual(0, len(rEdges))
|
||||
|
||||
def test37(self):
|
||||
'''Check offsetting multiple backwards inside edges.'''
|
||||
# This is exactly the same as test36 except that the wire is flipped to make
|
||||
# sure it's orientation doesn't matter
|
||||
obj = doc.getObjectsByLabel('offset-edge')[0]
|
||||
|
||||
w = getWireInside(obj)
|
||||
length = 20 * math.cos(math.pi/6)
|
||||
|
||||
# let's offset the other two legs
|
||||
lEdges = [e for e in w.Edges if not PathGeom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y)]
|
||||
self.assertEqual(2, len(lEdges))
|
||||
|
||||
w = PathGeom.flipWire(Part.Wire(lEdges))
|
||||
wire = PathGeomOp.offsetWire(w, obj.Shape, 2, True)
|
||||
|
||||
x = length/2 - 2 * math.cos(math.pi/6)
|
||||
y = -5 - 2 * math.sin(math.pi/6)
|
||||
|
||||
self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
||||
self.assertCoincide(Vector(-x, y, 0), wire.Edges[-1].Vertexes[1].Point)
|
||||
|
||||
rEdges = [e for e in wire.Edges if Part.Circle == type(e.Curve)]
|
||||
self.assertEqual(0, len(rEdges))
|
||||
|
||||
#offset the other way
|
||||
wire = PathGeomOp.offsetWire(Part.Wire(lEdges), obj.Shape, 2, False)
|
||||
|
||||
self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
||||
self.assertCoincide(Vector(+x, y, 0), wire.Edges[-1].Vertexes[1].Point)
|
||||
|
||||
rEdges = [e for e in wire.Edges if Part.Circle == type(e.Curve)]
|
||||
self.assertEqual(0, len(rEdges))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user