Commit 8d170a56 authored by sumpfralle's avatar sumpfralle

finally a quite stable and reliable push->follow implementation


git-svn-id: https://pycam.svn.sourceforge.net/svnroot/pycam/trunk@694 bbaffbd6-741e-11dd-a85d-61de82d9cad9
parent 97224254
...@@ -34,167 +34,104 @@ import math ...@@ -34,167 +34,104 @@ import math
class WaterlineTriangles: class WaterlineTriangles:
def __init__(self): def __init__(self):
self.triangles = []
self.waterlines = [] self.waterlines = []
self.shifted_lines = [] self.shifted_lines = []
self.left = []
self.right = []
def __str__(self): def __str__(self):
lines = [] lines = []
for index, t in enumerate(self.triangles): for index, t in enumerate(self.triangles):
lines.append("%d - %s" % (index, t)) lines.append("%d - %s" % (index, t))
if self.left[index] is None: if not self.left[index]:
left_index = None left_index = None
else: else:
try: left_index = []
left_index = self.triangles.index(self.left[index]) for left in self.left[index]:
except ValueError: left_index.append(self.triangles.index(left))
left_index = "%s not found" % str(self.left[index]) if not self.right[index]:
if self.right[index] is None:
right_index = None right_index = None
else: else:
try: right_index = []
right_index = self.triangles.index(self.right[index]) for right in self.right[index]:
except ValueError: right_index.append(self.triangles.index(right))
right_index = "%s not found" % str(self.right[index])
lines.append("\t%s / %s" % (left_index, 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.waterlines[index]))
lines.append("\t%s" % str(self.shifted_lines[index])) lines.append("\t%s" % str(self.shifted_lines[index]))
return "\n".join(lines) return "\n".join(lines)
def add(self, triangle, waterline, shifted_line): def add(self, waterline, shifted_line):
if triangle in self.triangles:
raise ValueError("Processing a triangle twice: %s" % str(triangle))
if waterline in self.waterlines: if waterline in self.waterlines:
# ignore this triangle # ignore this triangle
return return
left = []
right = []
removal_list = []
# Try to combine the new waterline with all currently existing ones.
# The three input parameters may be changed in this process.
for index, wl in enumerate(self.waterlines):
if waterline.dir == wl.dir:
if wl.is_point_in_line(waterline.p1):
if wl.is_point_in_line(waterline.p2):
# waterline is completely within wl - ignore it
return
else:
# waterline is longer than wl (on the right side)
waterline = Line(wl.p1, waterline.p2)
old_shifted_line = self.shifted_lines[index]
shifted_line = Line(old_shifted_line.p1, shifted_line.p2)
# remove the item later
removal_list.append(index)
elif waterline.is_point_in_line(wl.p1):
if waterline.is_point_in_line(wl.p2):
# wl is completely within waterline
removal_list.append(index)
else:
# wl is longer than wl (on the right side)
waterline = Line(waterline.p1, wl.p2)
old_shifted_line = self.shifted_lines[index]
shifted_line = Line(shifted_line.p1, old_shifted_line.p2)
removal_list.append(index)
# remove all triangles that were scheduled for removal
removal_list.reverse()
for index in removal_list:
# don't connect the possible left/right neighbours
self.remove(index)
for index, wl in enumerate(self.waterlines):
if (waterline.p2 == wl.p1) and (waterline.p1 != wl.p2):
right.append(self.triangles[index])
self.left[index].append(triangle)
elif (waterline.p1 == wl.p2) and (waterline.p2 != wl.p1):
left.append(self.triangles[index])
self.right[index].append(triangle)
else:
# no neighbour found
pass
self.triangles.append(triangle)
self.waterlines.append(waterline) self.waterlines.append(waterline)
self.shifted_lines.append(shifted_line) self.shifted_lines.append(shifted_line)
self.left.append(left)
self.right.append(right)
def extend_waterlines(self): def _get_groups(self):
index = 0 if len(self.waterlines) == 0:
while index < len(self.triangles): return []
if not self.right[index]: queue = range(len(self.waterlines))
index += 1 current_group = [0]
continue queue.pop(0)
shifted_line = self.shifted_lines[index] groups = [current_group]
potential_endings = [] while queue:
for right_triangle in self.right[index]: for index in queue:
right_index = self.triangles.index(right_triangle) if self.waterlines[index].p2 == self.waterlines[current_group[0]].p1:
right_shifted_line = self.shifted_lines[right_index] current_group.insert(0, index)
if shifted_line.dir == right_shifted_line.dir: queue.remove(index)
# straight lines - extend to the start of the next one break
potential_endings.append((right_shifted_line.p1, 1.0, right_index, 0.0)) else:
continue # no new members added to this group - start a new one
if shifted_line.p2 == right_shifted_line.p1: current_group = [queue[0]]
# the lines intersect properly queue.pop(0)
potential_endings.append((shifted_line.p2, 1.0, right_index, 0.0)) groups.append(current_group)
return groups
def extend_shifted_lines(self):
# TODO: improve the code below to handle "holes" properly (neighbours that disappear due to a negative collision distance - use the example "SampleScene.stl" as a reference)
def get_right_neighbour(group, ref):
group_len = len(group)
for index in range(1, group_len):
group_index = (ref + index) % group_len
if not self.shifted_lines[group[group_index]] is None:
return group[group_index]
else:
return None
groups = self._get_groups()
for group in groups:
index = 0
while index < len(group):
current = group[index]
current_shifted = self.shifted_lines[current]
if current_shifted is None:
index += 1
continue continue
cp, dist = shifted_line.get_intersection(right_shifted_line, neighbour = get_right_neighbour(group, index)
infinite_lines=True) if neighbour is None:
cp2, dist2 = right_shifted_line.get_intersection(shifted_line, # no right neighbour available
infinite_lines=True) break
if cp is None: neighbour_shifted = self.shifted_lines[neighbour]
raise ValueError("Missing intersection:%d / %d\n\t%s\n\t%s\n\t%s\n\t%s" \ if current_shifted.p2 == neighbour_shifted.p1:
% (index, right_index, shifted_line, index += 1
right_shifted_line, self.waterlines[index],
self.waterlines[right_index]))
potential_endings.append((cp, dist, right_index, dist2))
# adjust all connected lines
potential_endings.sort(key=lambda (cp, dist, other_index, dist2): dist)
# the point with the greatest distance will be the final end of this line
if potential_endings[-1][1] > 0:
self.shifted_lines[index] = Line(self.shifted_lines[index].p1, potential_endings[-1][0])
final_end = potential_endings[-1][0]
for cp, dist, other_index, dist2 in potential_endings:
other_line = self.shifted_lines[other_index]
new_line = Line(cp, other_line.p2)
if new_line.dir != other_line.dir:
# don't reverse the line
continue continue
self.shifted_lines[other_index] = new_line cp, dist = current_shifted.get_intersection(neighbour_shifted, infinite_lines=True)
index += 1 cp2, dist2 = neighbour_shifted.get_intersection(current_shifted, infinite_lines=True)
if dist < epsilon:
def remove(self, index): self.shifted_lines[current] = None
triangle = self.triangles[index] index -= 1
# fix the connection to the left elif dist2 > 1 - epsilon:
for left_triangle in self.left[index]: self.shifted_lines[neighbour] = None
left_index = self.triangles.index(left_triangle) else:
self.right[left_index].remove(triangle) self.shifted_lines[current] = Line(current_shifted.p1, cp)
# fix the connection to the right self.shifted_lines[neighbour] = Line(cp, neighbour_shifted.p2)
for right_triangle in self.right[index]: index += 1
right_index = self.triangles.index(right_triangle)
self.left[right_index].remove(triangle)
# 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): def get_shifted_lines(self):
finished = [] result = []
queue = self.shifted_lines groups = self._get_groups()
while len(queue) > 0: for group in groups:
current = queue.pop() for index in group:
finished.append(current) if not self.shifted_lines[index] is None:
match_found = True result.append(self.shifted_lines[index])
while match_found: return result
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:
...@@ -205,6 +142,20 @@ class Waterline: ...@@ -205,6 +142,20 @@ class Waterline:
self.pa = path_processor self.pa = path_processor
self._up_vector = Vector(0, 0, 1) self._up_vector = Vector(0, 0, 1)
self.physics = physics self.physics = physics
if self.physics:
accuracy = 20
max_depth = 16
model_dim = max(abs(self.model.maxx - self.model.minx),
abs(self.model.maxy - self.model.miny))
depth = math.log(accuracy * model_dim / self.cutter.radius) / math.log(2)
self._physics_maxdepth = min(max_depth, max(ceil(depth_x), 4))
def _get_free_paths(self, p1, p2):
if self.physics:
return get_free_paths_ode(self.physics, p1, p2,
depth=self._physics_maxdepth)
else:
return get_free_paths_triangles(self.model, self.cutter, p1, p2)
def GenerateToolPath(self, minx, maxx, miny, maxy, minz, maxz, dz, def GenerateToolPath(self, minx, maxx, miny, maxy, minz, maxz, dz,
draw_callback=None): draw_callback=None):
...@@ -218,41 +169,37 @@ class Waterline: ...@@ -218,41 +169,37 @@ class Waterline:
num_of_layers = 1 + ceil(diff_z / dz) num_of_layers = 1 + ceil(diff_z / dz)
z_step = diff_z / max(1, (num_of_layers - 1)) z_step = diff_z / max(1, (num_of_layers - 1))
progress_counter = ProgressCounter(num_of_layers \ num_of_triangles = len(self.model.triangles(minx=minx, miny=miny, maxx=maxx, maxy=maxy))
* len(self.model.triangles()), draw_callback) progress_counter = ProgressCounter(2 * num_of_layers * num_of_triangles,
draw_callback)
current_layer = 0 current_layer = 0
z_steps = [(maxz - i * z_step) for i in range(num_of_layers)] z_steps = [(maxz - i * z_step) for i in range(num_of_layers)]
# collision handling function
for z in z_steps: for z in z_steps:
# update the progress bar and check, if we should cancel the process # update the progress bar and check, if we should cancel the process
if draw_callback and draw_callback(text="PushCutter: processing" \ if draw_callback and draw_callback(text="PushCutter: processing" \
+ " layer %d/%d" % (current_layer + 1, num_of_layers)): + " layer %d/%d" % (current_layer + 1, num_of_layers)):
# cancel immediately # cancel immediately
break break
self.pa.new_direction(0) self.pa.new_direction(0)
self.GenerateToolPathSlice(minx, maxx, miny, maxy, z, self.GenerateToolPathSlice(minx, maxx, miny, maxy, z,
draw_callback, progress_counter) draw_callback, progress_counter)
self.pa.end_direction() self.pa.end_direction()
self.pa.finish() self.pa.finish()
current_layer += 1 current_layer += 1
return self.pa.paths return self.pa.paths
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):
shifted_lines = self.get_potential_contour_lines(minx, maxx, miny, maxy, z) shifted_lines = self.get_potential_contour_lines(minx, maxx, miny, maxy,
z, progress_counter=progress_counter)
last_position = None last_position = None
self.pa.new_scanline() self.pa.new_scanline()
for line in shifted_lines: for line in shifted_lines:
if self.physics: points = self._get_free_paths(line.p1, line.p2)
points = get_free_paths_ode(self.physics, line.p1, line.p2,
depth=depth_x)
else:
points = get_free_paths_triangles(self.model, self.cutter,
line.p1, line.p2)
if points: if points:
if (not last_position is None) and (len(points) > 0) \ if (not last_position is None) and (len(points) > 0) \
and (last_position != points[0]): and (last_position != points[0]):
...@@ -275,27 +222,32 @@ class Waterline: ...@@ -275,27 +222,32 @@ class Waterline:
self.pa.end_scanline() self.pa.end_scanline()
return self.pa.paths return self.pa.paths
def get_potential_contour_lines(self, minx, maxx, miny, maxy, z): def get_potential_contour_lines(self, minx, maxx, miny, maxy, z,
progress_counter=None):
plane = Plane(Point(0, 0, z), self._up_vector) plane = Plane(Point(0, 0, z), self._up_vector)
visited_triangles = []
lines = [] lines = []
waterline_triangles = WaterlineTriangles() waterline_triangles = WaterlineTriangles()
projected_waterlines = []
for triangle in self.model.triangles(minx=minx, miny=miny, maxx=maxx, maxy=maxy): for triangle in self.model.triangles(minx=minx, miny=miny, maxx=maxx, maxy=maxy):
if not progress_counter is None:
if progress_counter.increment():
# quit requested
break
# ignore triangles below the z level # ignore triangles below the z level
if (triangle.maxz < z) or (triangle in visited_triangles): if triangle.maxz < z:
continue continue
# ignore triangles pointing upwards or downwards # ignore triangles pointing upwards or downwards
if triangle.normal.dot(self._up_vector) == 0: if triangle.normal.cross(self._up_vector).norm == 0:
continue
cutter_location, waterline = self.get_collision_waterline_of_triangle(triangle, z)
if cutter_location is None:
continue continue
shifted_waterline = self.get_shifted_waterline(triangle, waterline, cutter_location) edge_collisions = self.get_collision_waterline_of_triangle(triangle, z)
if shifted_waterline is None: if not edge_collisions:
continue continue
projected_waterline = plane.get_line_projection(waterline) for cutter_location, edge in edge_collisions:
waterline_triangles.add(triangle, projected_waterline, shifted_waterline) shifted_edge = self.get_shifted_waterline(edge, cutter_location)
waterline_triangles.extend_waterlines() if shifted_edge is None:
continue
waterline_triangles.add(edge, shifted_edge)
waterline_triangles.extend_shifted_lines()
result = [] result = []
for line in waterline_triangles.get_shifted_lines(): for line in waterline_triangles.get_shifted_lines():
cropped_line = line.get_cropped_line(minx, maxx, miny, maxy, z, z) cropped_line = line.get_cropped_line(minx, maxx, miny, maxy, z, z)
...@@ -313,49 +265,137 @@ class Waterline: ...@@ -313,49 +265,137 @@ class Waterline:
return self._max_length_cache return self._max_length_cache
def get_collision_waterline_of_triangle(self, triangle, z): def get_collision_waterline_of_triangle(self, triangle, z):
points = [] # TODO: there are problems with "material allowance > 0"
for edge in (triangle.e1, triangle.e2, triangle.e3): plane = Plane(Point(0, 0, z), self._up_vector)
if edge.p1.z < z < edge.p2.z: if triangle.minz > z:
points.append(edge.p1.add(edge.p2.sub(edge.p1).mul((z - edge.p1.z) / (edge.p2.z - edge.p1.z)))) # the triangle is completely above z
elif edge.p2.z < z < edge.p1.z: # try all edges
points.append(edge.p2.add(edge.p1.sub(edge.p2).mul((z - edge.p2.z) / (edge.p1.z - edge.p2.z)))) proj_points = []
sums = [0, 0, 0] for p in triangle.get_points():
for p in points: proj_p = plane.get_point_projection(p)
sums[0] += p.x if not proj_p in proj_points:
sums[1] += p.y proj_points.append(proj_p)
sums[2] += p.z if len(proj_points) == 3:
if len(points) > 0: edges = []
start = Point(sums[0] / len(points), sums[1] / len(points), sums[2] / len(points)) for index in range(3):
else: edge = Line(proj_points[index - 1], proj_points[index])
start = Point(triangle.center.x, triangle.center.y, z) # the edge should be clockwise around the model
# use a projection upon a plane trough (0, 0, 0) if edge.dir.cross(triangle.normal).dot(self._up_vector) < 0:
direction_xy = Plane(Point(0, 0, 0), self._up_vector).get_point_projection(triangle.normal).normalized() edge = Line(edge.p2, edge.p1)
# ignore triangles pointing upward or downward edges.append((edge, proj_points[index - 2]))
if direction_xy is None: outer_edges = []
return None, None for edge, other_point in edges:
# this vector is guaranteed to reach the outer limits # pick only edges, where the other point is on the right side
direction_sized = direction_xy.mul(self.get_max_length()) if other_point.sub(edge.p1).cross(edge.dir).dot(self._up_vector) > 0:
# calculate the collision point outer_edges.append(edge)
self.cutter.moveto(start) if len(outer_edges) == 0:
# We are looking for the collision of the "back" of the cutter with the # the points seem to be an one line
# triangle. # pick the longest edge
cl, d, cp = self.cutter.intersect(direction_xy.mul(-1), triangle) long_edge = edges[0][0]
if cl is None: for edge, other_point in edges[1:]:
return None, None if edge.len > long_edge.len:
long_edge = edge
outer_edges = long_edge
else:
edge = Line(proj_points[0], proj_points[1])
if edge.dir.cross(triangle.normal).dot(self._up_vector) < 0:
edge = Line(edge.p2, edge.p1)
outer_edges = [edge]
else: else:
plane = Plane(cp, self._up_vector) points_above = [plane.get_point_projection(p) for p in triangle.get_points() if p.z > z]
waterline = plane.intersect_triangle(triangle) waterline = plane.intersect_triangle(triangle)
if waterline is None: if waterline is None:
# calculate the waterline based on the z height if len(points_above) == 2:
plane = Plane(Point(cp.x, cp.y, z), self._up_vector) edge = Line(points_above[0], points_above[1])
waterline = plane.intersect_triangle(triangle) if edge.dir.cross(triangle.normal).dot(self._up_vector) < 0:
# shift the waterline to the outside outer_edges = [Line(edge.p2, edge.p1)]
offset = waterline.closest_point(cp).sub(plane.get_point_projection(cp)) else:
return cl, Line(waterline.p1.add(offset), waterline.p2.add(offset)) outer_edges = [edge]
else:
outer_edges = []
else: else:
return cl, waterline # remove points that are not part of the waterline
points_above = [p for p in points_above
if (p != waterline.p1) and (p != waterline.p2)]
potential_edges = []
if len(points_above) == 0:
outer_edges = [waterline]
elif len(points_above) == 1:
other_point = points_above[0]
dot = other_point.sub(waterline.p1).cross(waterline.dir).dot(self._up_vector)
if dot > 0:
outer_edges = [waterline]
elif dot < 0:
edges = []
edges.append(Line(waterline.p1, other_point))
edges.append(Line(waterline.p2, other_point))
outer_edges = []
for edge in edges:
if edge.dir.cross(triangle.normal).dot(self._up_vector) < 0:
outer_edges.append(Line(edge.p2, edge.p1))
else:
outer_edges.append(edge)
else:
# the three points are on one line
edges = []
edges.append(waterline)
edges.append(Line(waterline.p1, other_point))
edges.append(Line(waterline.p2, other_point))
edges.sort(key=lambda x: x.len)
outer_edges = [edges[-1]]
else:
# two points above
other_point = points_above[0]
dot = other_point.sub(waterline.p1).cross(waterline.dir).dot(self._up_vector)
if dot > 0:
# the other two points are on the right side
outer_edges = [waterline]
elif dot < 0:
edge = Line(points_above[0], points_above[1])
if edge.dir.cross(triangle.normal).dot(self._up_vector) < 0:
outer_edges = [Line(edge.p2, edge.p1)]
else:
outer_edges = [edge]
else:
edges = []
# pick the longest combination of two of these points
points = [waterline.p1, waterline.p2] + points_above
for p1 in points:
for p2 in points:
if not p1 is p2:
edges.append(Line(p1, p2))
edges.sort(key=lambda x: x.len)
edge = edges[-1]
if edge.dir.cross(triangle.normal).dot(self._up_vector) < 0:
outer_edges = [Line(edge.p2, edge.p1)]
else:
outer_edges = [edge]
result = []
for edge in outer_edges:
start = edge.p1.add(edge.p2).div(2)
direction = self._up_vector.cross(edge.dir).normalized()
if direction is None:
continue
direction = direction.mul(self.get_max_length())
# We need to use the triangle collision algorithm here - because we
# need the point of collision in the triangle.
collisions = get_free_paths_triangles(self.model, self.cutter, start,
start.add(direction), return_triangles=True)
for index, coll in enumerate(collisions):
if (index % 2 == 0) and (not coll[1] is None) \
and (not coll[2] is None) \
and (coll[0].sub(start).dot(direction) > 0):
cl, hit_t, cp = coll
break
else:
raise ValueError("Failed to detect any collision: " \
+ "%s / %s -> %s" % (edge, start, direction))
proj_cp = plane.get_point_projection(cp)
if edge.is_point_inside(proj_cp):
result.append((cl, edge))
return result
def get_shifted_waterline(self, triangle, waterline, cutter_location): def get_shifted_waterline(self, waterline, cutter_location):
# 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.
...@@ -368,10 +408,6 @@ class Waterline: ...@@ -368,10 +408,6 @@ class Waterline:
return wl_proj return wl_proj
# shift both ends of the waterline towards the cutter location # shift both ends of the waterline towards the cutter location
shift = cutter_location.sub(wl_proj.closest_point(cutter_location)) shift = cutter_location.sub(wl_proj.closest_point(cutter_location))
extra_shift = Plane(Point(0, 0, 0), self._up_vector).get_point_projection(shift)
# TODO: check if we need this extra distance to avoid collisions (resp. "touching")
if not extra_shift is None:
shift = shift.add(extra_shift.mul(epsilon))
shifted_waterline = Line(wl_proj.p1.add(shift), wl_proj.p2.add(shift)) shifted_waterline = Line(wl_proj.p1.add(shift), wl_proj.p2.add(shift))
return shifted_waterline return shifted_waterline
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