Fixed open wire offsetting and orienting.

This commit is contained in:
Markus Lampert
2018-06-28 22:19:35 -07:00
parent 3798cf1d10
commit 7f38777377
2 changed files with 323 additions and 17 deletions

View File

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

View File

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