Commit 801573d8 authored by sumpfralle's avatar sumpfralle

stabilized the waterline (push->follow) approach

 * cleanup is necessary


git-svn-id: https://pycam.svn.sourceforge.net/svnroot/pycam/trunk@681 bbaffbd6-741e-11dd-a85d-61de82d9cad9
parent 6262797f
...@@ -31,6 +31,129 @@ import random ...@@ -31,6 +31,129 @@ import random
import math import math
class WaterlineTriangles:
def __init__(self):
self.triangles = []
self.waterlines = []
self.shifted_lines = []
self.left = []
self.right = []
def __str__(self):
lines = []
for index, t in enumerate(self.triangles):
lines.append("%d - %s" % (index, t))
left_index = self.left[index] and self.triangles.index(self.left[index])
right_index = self.right[index] and self.triangles.index(self.right[index])
lines.append("\t%s / %s" % (left_index, right_index))
lines.append("\t%s" % str(self.waterlines[index]))
lines.append("\t%s" % str(self.shifted_lines[index]))
return "\n".join(lines)
def add(self, triangle, waterline, shifted_line):
if triangle in self.triangles:
raise ValueError("Processing a triangle twice: %s" % str(triangle))
if waterline in self.waterlines:
# ignore this triangle
return
left = None
right = None
for index, wl in enumerate(self.waterlines):
if waterline.p2 == wl.p1:
if not right is None:
raise ValueError("Too many right neighbours:\n%s\n%s\n%s" % (triangle, right, self.triangles[index]))
right = self.triangles[index]
if not self.left[index] is None:
raise ValueError("Too many previous right neighbours:\n%s\n%s\n%s" % (triangle, self.left[index], self.triangles[index]))
self.left[index] = triangle
elif waterline.p1 == wl.p2:
if not left is None:
raise ValueError("Too many left neighbours:\n%s\n%s\n%s" % (triangle, left, self.triangles[index]))
left = self.triangles[index]
if not self.right[index] is None:
raise ValueError("Too many previous left neighbours:\n%s\n%s\n%s" % (triangle, self.right[index], self.triangles[index]))
self.right[index] = triangle
else:
# no neighbour found
pass
self.triangles.append(triangle)
self.waterlines.append(waterline)
self.shifted_lines.append(shifted_line)
self.left.append(left)
self.right.append(right)
def extend_waterlines(self):
index = 0
while index < len(self.triangles):
if self.right[index] is None:
index += 1
continue
shifted_line = self.shifted_lines[index]
right_index = self.triangles.index(self.right[index])
right_shifted_line = self.shifted_lines[right_index]
if shifted_line.dir == right_shifted_line.dir:
# straight lines - combine these lines
self.shifted_lines[index] = Line(shifted_line.p1, right_shifted_line.p2)
self.remove(right_index)
index = 0
continue
if shifted_line.p2 == right_shifted_line.p1:
# the lines intersect properly
index += 1
continue
cp, dist = shifted_line.get_intersection(right_shifted_line, infinite_lines=True)
cp2, dist2 = right_shifted_line.get_intersection(shifted_line, infinite_lines=True)
if cp is None:
raise ValueError("Missing intersection: %s / %s" % (shifted_line, right_shifted_line))
if dist < epsilon:
# remove the current triangle
self.remove(index)
index = 0
elif dist2 > 1 - epsilon:
# remove the other triangle
self.remove(right_index)
index = 0
else:
# introduce the new intersection point
self.shifted_lines[index] = Line(shifted_line.p1, cp)
self.shifted_lines[right_index] = Line(cp, right_shifted_line.p2)
index += 1
def remove(self, index):
# fix the connection to the left
if not self.left[index] is None:
left_index = self.triangles.index(self.left[index])
self.right[left_index] = self.right[index]
# fix the connection to the right
if not self.right[index] is None:
right_index = self.triangles.index(self.right[index])
self.left[right_index] = self.left[index]
# remove the item
self.triangles.pop(index)
self.waterlines.pop(index)
self.shifted_lines.pop(index)
self.left.pop(index)
self.right.pop(index)
def get_shifted_lines(self):
finished = []
queue = self.shifted_lines
while len(queue) > 0:
current = queue.pop()
finished.append(current)
match_found = True
while match_found:
match_found = False
for other_index, other in enumerate(queue):
if current.p2 == other.p1:
finished.append(other)
queue.pop(other_index)
current = other
match_found = True
return finished
class Waterline: class Waterline:
def __init__(self, cutter, model, path_processor, physics=None): def __init__(self, cutter, model, path_processor, physics=None):
...@@ -105,44 +228,27 @@ class Waterline: ...@@ -105,44 +228,27 @@ class Waterline:
def GenerateToolPathSlice(self, minx, maxx, miny, maxy, z, def GenerateToolPathSlice(self, minx, maxx, miny, maxy, z,
draw_callback=None, progress_counter=None): draw_callback=None, progress_counter=None):
#print "**** Starting new slice at %f ****" % z #print "**** Starting new slice at %f ****" % z
triangle_queue = self.model.triangles()[:] plane = Plane(Point(0, 0, z), self._up_vector)
visited_triangles = [] visited_triangles = []
while len(triangle_queue) > 0: lines = []
first_skipped = False waterline_triangles = WaterlineTriangles()
#print "Triangles left: %d" % len(triangle_queue) for triangle in self.model.triangles():
triangle = triangle_queue.pop()
# ignore triangles below the z level # ignore triangles below the z level
if triangle.maxz < z: if (triangle.maxz < z) or (triangle in visited_triangles):
continue continue
cutter_location, ct, ctp, waterline = self.get_collision_waterline_of_triangle(triangle, z) cutter_location, ct, ctp, waterline = self.get_collision_waterline_of_triangle(triangle, z)
if ct is None: if ct is None:
continue continue
if ct in visited_triangles: shifted_waterline = self.get_waterline_extended(ct, waterline, cutter_location)
continue projected_waterline = plane.get_line_projection(waterline)
print "%s / %s / %s / %s" % (cutter_location, ct, ctp, waterline) waterline_triangles.add(triangle, projected_waterline, shifted_waterline)
waterline_triangles.extend_waterlines()
for wl in waterline_triangles.get_shifted_lines():
self.pa.new_scanline() self.pa.new_scanline()
#print "**** new scanline ****" self.pa.append(wl.p1)
# start from the middle of the triangle to the end of the waterline self.pa.append(wl.p2)
waterline = Line(ctp, waterline.p2)
cutter_location = waterline.p2
cutter_location, ct, ctp, waterline = \
self.go_to_next_collision(ct, ctp, cutter_location, waterline)
self.pa.append(cutter_location)
self.cutter.moveto(cutter_location)
while (not ct in visited_triangles) and (not math.isnan(cutter_location.x)):
if first_skipped:
#print "New cutter location: %s" % str(cutter_location)
self.pa.append(cutter_location)
visited_triangles.append(ct)
self.cutter.moveto(cutter_location)
else:
first_skipped = True
if draw_callback:
draw_callback(tool_position=cutter_location, toolpath=self.pa.paths)
cutter_location, ct, ctp, waterline = \
self.go_to_next_collision(ct, ctp, cutter_location, waterline)
#print "cl, ct, ctp, waterline: %s\n\t%s\n\t%s\n\t%s" % (cutter_location, ct, ctp, waterline)
self.pa.end_scanline() self.pa.end_scanline()
#print l
return self.pa.paths return self.pa.paths
def get_collision_waterline_of_triangle(self, triangle, z): def get_collision_waterline_of_triangle(self, triangle, z):
...@@ -168,10 +274,14 @@ class Waterline: ...@@ -168,10 +274,14 @@ class Waterline:
return None, None, None, None return None, None, None, None
# this vector is guaranteed to reach the outer limits # this vector is guaranteed to reach the outer limits
direction_sized = direction_xy.mul(self.get_max_length()) direction_sized = direction_xy.mul(self.get_max_length())
cutter_location, triangle_collisions = self.find_next_outer_collision(start, direction_sized) if str(triangle).startswith("Triangle8<"):
debug = True
else:
debug = False
cutter_location, triangle_collisions = self.find_next_outer_collision(start, direction_sized, preferred_triangle=triangle)
if cutter_location is None: if cutter_location is None:
# no collision starting from this point # no collision starting from this point
#print "Unexpected: missing collision" #print "Unexpected: missing collision (%s)" % str(direction_sized)
return None, None, None, None return None, None, None, None
# ct: colliding triangle # ct: colliding triangle
# ctp: collision point within colliding triangle # ctp: collision point within colliding triangle
...@@ -226,7 +336,7 @@ class Waterline: ...@@ -226,7 +336,7 @@ class Waterline:
new_list.sort(key=line_distance) new_list.sort(key=line_distance)
return new_list[0] return new_list[0]
def find_next_outer_collision(self, point, direction): def find_next_outer_collision(self, point, direction, preferred_triangle=None):
collisions = get_free_paths_triangles(self.model, self.cutter, collisions = get_free_paths_triangles(self.model, self.cutter,
point, point.add(direction), return_triangles=True) point, point.add(direction), return_triangles=True)
# remove leading "None" # remove leading "None"
...@@ -237,31 +347,44 @@ class Waterline: ...@@ -237,31 +347,44 @@ class Waterline:
# with an even index satisfies this condition. # with an even index satisfies this condition.
# Additionally we don't want to care for "not-real" collisions (e.g. # Additionally we don't want to care for "not-real" collisions (e.g.
# on the edge of the bounding box. # on the edge of the bounding box.
if (index % 2 == 0) and (not coll[1] is None): if (index % 2 == 0) and (not coll[1] is None) \
and (not coll[2] is None):
coll_outer.append(coll) coll_outer.append(coll)
#print "Outer collision candidates: %s" % str(coll_outer) #print "Outer collision candidates: (%d) %s" % (len(coll_outer), collisions)
if not coll_outer: if not coll_outer:
return None, None return None, None
# find all triangles that cause the collision # find all triangles that cause the collision
cutter_location = coll_outer[0][0] cutter_location = coll_outer[0][0]
closest_triangles = [] closest_triangles = []
# check the collision with the preferred triangle
if not preferred_triangle is None:
self.cutter.moveto(point)
pt_cl, pt_d, pt_cp = self.cutter.intersect(direction, preferred_triangle)
if pt_cl != cutter_location:
# try the reverse direction
pt_cl, pt_d, pt_cp = self.cutter.intersect(direction.mul(-1), preferred_triangle)
if pt_cl == cutter_location:
raw_waterline = self.get_raw_triangle_waterline(preferred_triangle, pt_cp, cutter_location)
return cutter_location, [(preferred_triangle, pt_cp, raw_waterline)]
while coll_outer and (abs(coll_outer[0][0].sub(cutter_location).norm) < epsilon): while coll_outer and (abs(coll_outer[0][0].sub(cutter_location).norm) < epsilon):
current_collision, t, cp = coll_outer.pop() current_collision, t, cp = coll_outer.pop()
raw_waterline = self.get_raw_triangle_waterline(t, cp, cutter_location) raw_waterline = self.get_raw_triangle_waterline(t, cp, cutter_location)
if (not raw_waterline is None) and (raw_waterline.len != 0): if (not raw_waterline is None) and (raw_waterline.len != 0):
closest_triangles.append((t, cp, raw_waterline)) closest_triangles.append((t, cp, raw_waterline))
else:
print "No waterline found: %s / %s / %s" % (t, cp, cutter_location)
#print "Outer collision selection: %s" % str(closest_triangles) #print "Outer collision selection: %s" % str(closest_triangles)
if len(closest_triangles) > 0: if len(closest_triangles) > 0:
return cutter_location, closest_triangles return cutter_location, closest_triangles
else: else:
return None, None return None, None
def get_closest_adjacent_waterline(self, triangle, waterline, reference_point): def get_closest_adjacent_waterline(self, triangle, waterline, wl_point, reference_point):
# Find the adjacent triangles that share vertices with the end of the # Find the adjacent triangles that share vertices with the end of the
# waterline of the original triangle. # waterline of the original triangle.
edges = [] edges = []
for edge in (triangle.e1, triangle.e2, triangle.e3): for edge in (triangle.e1, triangle.e2, triangle.e3):
if edge.is_point_in_line(waterline.p2): if edge.is_point_in_line(wl_point):
edges.append(edge) edges.append(edge)
# also add the reverse to speed up comparisons # also add the reverse to speed up comparisons
edges.append(Line(edge.p2, edge.p1)) edges.append(Line(edge.p2, edge.p1))
...@@ -269,7 +392,7 @@ class Waterline: ...@@ -269,7 +392,7 @@ class Waterline:
for t in self.model.triangles(): for t in self.model.triangles():
if (not t is triangle) and ((t.e1 in edges) or (t.e2 in edges) \ if (not t is triangle) and ((t.e1 in edges) or (t.e2 in edges) \
or (t.e3 in edges)): or (t.e3 in edges)):
t_waterline = self.get_raw_triangle_waterline(t, waterline.p2, t_waterline = self.get_raw_triangle_waterline(t, wl_point,
reference_point) reference_point)
if t_waterline is None: if t_waterline is None:
# no waterline through this triangle # no waterline through this triangle
...@@ -278,13 +401,16 @@ class Waterline: ...@@ -278,13 +401,16 @@ class Waterline:
# Ignore zero-length waterlines - there should be other - # Ignore zero-length waterlines - there should be other -
# non-point-like waterlines in other triangles. # non-point-like waterlines in other triangles.
continue continue
if t_waterline.p1 != waterline.p2: if t_waterline.p1 != wl_point:
if t_waterline.p2 == waterline.p2: if t_waterline.p2 == wl_point:
t_waterline = Line(t_waterline.p2, t_waterline.p1) t_waterline = Line(t_waterline.p2, t_waterline.p1)
else: else:
raise ValueError("get_waterline_endpoint: invalid " \ raise ValueError("get_waterline_endpoint: invalid " \
+ ("neighbouring waterline: %s (orig) / %s " \ + ("neighbouring waterline: %s (orig) / %s " \
+ "(neighbour)") % (waterline, t_waterline)) + "(neighbour)") % (waterline, t_waterline))
angle = get_angle_pi(waterline.p2, waterline.p1,
t_waterline.p1, self._up_vector)
else:
angle = get_angle_pi(t_waterline.p2, t_waterline.p1, angle = get_angle_pi(t_waterline.p2, t_waterline.p1,
waterline.p1, self._up_vector) waterline.p1, self._up_vector)
triangles.append((t, t_waterline, angle)) triangles.append((t, t_waterline, angle))
...@@ -294,91 +420,52 @@ class Waterline: ...@@ -294,91 +420,52 @@ class Waterline:
t, t_waterline, angle = triangles[0] t, t_waterline, angle = triangles[0]
return t, t_waterline, angle return t, t_waterline, angle
def get_waterline_endpoint(self, triangle, waterline, cutter_location): def get_waterline_extended(self, triangle, waterline, cutter_location):
strategy = "waterline"
reference_point = cutter_location.add(waterline.p2.sub(waterline.p1))
# Project the waterline and the cutter location down to the slice plane. # Project the waterline and the cutter location down to the slice plane.
# This is necessary for calculating the horizontal distance between the # This is necessary for calculating the horizontal distance between the
# cutter and the triangle waterline. # cutter and the triangle waterline.
plane = Plane(cutter_location, self._up_vector) plane = Plane(cutter_location, self._up_vector)
wl_proj_p1 = plane.get_point_projection(waterline.p1) wl_proj = plane.get_line_projection(waterline)
wl_proj_p2 = plane.get_point_projection(waterline.p2)
wl_proj = Line(wl_proj_p1, wl_proj_p2)
if wl_proj.len < epsilon: if wl_proj.len < epsilon:
#print "Waterline endpoint for zero sized line requested: %s" % str(waterline) #print "Waterline endpoint for zero sized line requested: %s" % str(waterline)
return reference_point return cutter_location
offset = wl_proj.dist_to_point(cutter_location) offset = wl_proj.dist_to_point(cutter_location)
if offset < epsilon: if offset < epsilon:
return reference_point return cutter_location
# shift both ends of the waterline towards the cutter location
shift = cutter_location.sub(wl_proj.closest_point(cutter_location))
shifted_waterline = Line(wl_proj.p1.add(shift), wl_proj.p2.add(shift))
return shifted_waterline
# Calculate the length of the vector change. # Calculate the length of the vector change.
t, t_waterline, angle = self.get_closest_adjacent_waterline(triangle, waterline, reference_point) t1, t1_waterline, angle1 = self.get_closest_adjacent_waterline(triangle, waterline, waterline.p1, cutter_location)
def get_waterline_collision(): t2, t2_waterline, angle2 = self.get_closest_adjacent_waterline(triangle, waterline, waterline.p2, cutter_location)
# TODO: same height as before? #TODO1: check "get_closest_adjacent_waterline" for edge=waterline (don't take the triangle above/below)
next_cl, dummy1, dummy2, next_waterline = self.get_collision_waterline_of_triangle(t, reference_point.z) #TODO2: the extension does not work, if the "hit" triangles are above the cutter (e.g. lower sphere-half)
#TODO3: same shift for both waterlines?
def get_waterline_collision(t, default_location):
next_cl, dummy1, dummy2, next_waterline = self.get_collision_waterline_of_triangle(t, cutter_location.z)
if next_cl is None: if next_cl is None:
raise ValueError("Failed collision: %s / %s" % (t, reference_point)) print "Failed collision: %s / %s" % (t, cutter_location)
line1 = Line(cutter_location, cutter_location.add(waterline.p2.sub(waterline.p1))) return default_location
line2 = Line(next_cl, next_cl.add(next_waterline.p2.sub(next_waterline.p1))) next_wl_proj = plane.get_line_projection(next_waterline)
if line1.dir == line2.dir: next_shift = next_cl.sub(next_wl_proj.closest_point(next_cl))
line = Line(next_wl_proj.p1.add(next_shift), next_wl_proj.p2.add(next_shift))
if waterline.dir == line.dir:
# both are on a straight line # both are on a straight line
#print "STRAIGHT: %s / %s" % (line1, line2) #print "STRAIGHT: %s / %s" % (line1, line2)
return line1.p2 return default_location
cp, dist = line1.get_intersection(line2, infinite_lines=True) cp, dist = shifted_waterline.get_intersection(line, infinite_lines=True)
if cp is None: if cp is None:
raise ValueError("Line going backward: %s / %s" % (waterline, t_waterline)) raise ValueError("Line going backward: %s / %s / %s / %s" % (triangle, t, waterline, next_waterline))
else: else:
if abs(dist) < epsilon: if abs(dist) < epsilon:
# "dist" is almost zero: # "dist" is almost zero:
return cutter_location return default_location
elif dist < 0:
raise ValueError("Waterline endpoint backwards: %s / %s" % (cp, dist))
else:
# safety distance is required to avoid any collision due to float inaccuracies
safety_distance = cp.sub(cutter_location).normalized().mul(epsilon)
return cp.add(safety_distance)
def get_bisector_collision():
# bisector based code
bisector_dir = get_bisector(waterline.p1, waterline.p2, t_waterline.p2, self._up_vector)
bisector = Line(waterline.p2, waterline.p2.add(bisector_dir))
waterline_dir = waterline.p2.sub(waterline.p1)
cp, dist = Line(cutter_location, cutter_location.add(
waterline_dir)).get_intersection(bisector, infinite_lines=True)
if cp is None:
raise ValueError("Line going backward: %s / %s" % (waterline, t_waterline))
else:
if abs(dist) < epsilon:
# "dist" is almost zero:
return cutter_location
elif dist < 0:
raise ValueError("Waterline endpoint backwards: %s / %s" % (cp, dist))
else: else:
return cp return cp
def get_angle_collision():
# angle based code
if abs(angle - math.pi) < epsilon:
# The two waterlines (waterline and t_waterline) are on a straight
# line. Thus we need to add the length of the t_waterline (incl.
# its extension) as the extension of "waterline".
# This sounds like a possible recursion trap, but it should be
# of finite depth.
#print "Waterline extension: straight line (%s / %s)" % (waterline, t_waterline)
endpoint = t_waterline.p2.sub(t_waterline.p1).add(
self.get_waterline_endpoint(t, t_waterline,
reference_point))
if abs(angle - 2 * math.pi) < epsilon:
raise ValueError("Line going backward: %s / %s" % (waterline, t_waterline))
else:
# this waterline and the next are _not_ on a straight line
change_vector_length = offset / math.tan(angle / 2)
change_vector = waterline.dir.mul(change_vector_length)
endpoint = cutter_location.add(waterline.p2.sub(waterline.p1)).add(change_vector)
#print "Waterline extension: %s (%f)" % (change_vector, change_vector.norm)
return endpoint
# alternative calculations - "waterline_collision" seems to be the best # alternative calculations - "waterline_collision" seems to be the best
waterline_collision = get_waterline_collision() collision1 = get_waterline_collision(t1, shifted_waterline.p1)
#bisector_collision = get_bisector_collision() collision2 = get_waterline_collision(t2, shifted_waterline.p2)
#angle_collision = get_angle_collision() return Line(collision1, collision2)
#print "Endpoints: %s / %s / %s" % (waterline_collision, bisector_collision, angle_collision)
collision = waterline_collision
return Point(collision.x, collision.y, collision.z)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment