Commit 1472fc88 authored by sumpfralle's avatar sumpfralle

generalized the triangular collision detection to allow non-horizontal moves

(btw: this allows engraving with triangular collision detection)


git-svn-id: https://pycam.svn.sourceforge.net/svnroot/pycam/trunk@387 bbaffbd6-741e-11dd-a85d-61de82d9cad9
parent 581d290e
...@@ -24,7 +24,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -24,7 +24,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
import pycam.PathProcessors.PathAccumulator import pycam.PathProcessors.PathAccumulator
from pycam.Geometry import Point from pycam.Geometry import Point
from pycam.Geometry.utils import INFINITE from pycam.Geometry.utils import INFINITE
from pycam.PathGenerators import get_max_height_triangles, get_max_height_ode, get_free_horizontal_paths_ode, get_free_horizontal_paths_triangles, ProgressCounter from pycam.PathGenerators import get_max_height_triangles, get_max_height_ode, get_free_paths_ode, get_free_paths_triangles, ProgressCounter
import math import math
import sys import sys
...@@ -129,11 +129,9 @@ class EngraveCutter: ...@@ -129,11 +129,9 @@ class EngraveCutter:
if not self.model or (self.model.maxz < z): if not self.model or (self.model.maxz < z):
points = [p1, p2] points = [p1, p2]
elif self.physics: elif self.physics:
points = get_free_horizontal_paths_ode(self.physics, p1.x, p2.x, points = get_free_paths_ode(self.physics, p1, p2)
p1.y, p2.y, z)
else: else:
points = get_free_horizontal_paths_triangles(self.model, points = get_free_paths_triangles(self.model, self.cutter, p1, p2)
self.cutter, p1.x, p2.x, p1.y, p2.y, z)
if points: if points:
for p in points: for p in points:
pa.append(p) pa.append(p)
......
...@@ -23,7 +23,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -23,7 +23,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
from pycam.Geometry import Point from pycam.Geometry import Point
from pycam.Geometry.utils import INFINITE, epsilon from pycam.Geometry.utils import INFINITE, epsilon
from pycam.PathGenerators import drop_cutter_test, get_free_horizontal_paths_ode, get_free_horizontal_paths_triangles, ProgressCounter from pycam.PathGenerators import drop_cutter_test, get_free_paths_ode, get_free_paths_triangles, ProgressCounter
import math import math
...@@ -38,16 +38,19 @@ class PushCutter: ...@@ -38,16 +38,19 @@ class PushCutter:
def GenerateToolPath(self, minx, maxx, miny, maxy, minz, maxz, dx, dy, dz, draw_callback=None): def GenerateToolPath(self, minx, maxx, miny, maxy, minz, maxz, dx, dy, dz, draw_callback=None):
# calculate the number of steps # calculate the number of steps
num_of_layers = 1 + int(math.ceil(abs(maxz - minz) / dz)) num_of_layers = 1 + int(math.ceil(abs(maxz - minz) / dz))
dz = abs(maxz - minz) / (num_of_layers - 1) if num_of_layers > 1:
z_step = abs(maxz - minz) / (num_of_layers - 1)
else:
z_step = 0
lines_per_layer = 0 lines_per_layer = 0
if dx != 0: if dx != 0:
x_lines_per_layer = 1 + int(math.ceil(abs(maxx - minx) / dx)) x_lines_per_layer = 1 + int(math.ceil(abs(maxx - minx) / dx))
dx = abs(maxx - minx) / (x_lines_per_layer - 1) x_step = abs(maxx - minx) / (x_lines_per_layer - 1)
lines_per_layer += x_lines_per_layer lines_per_layer += x_lines_per_layer
if dy != 0: if dy != 0:
y_lines_per_layer = 1 + int(math.ceil(abs(maxy - miny) / dy)) y_lines_per_layer = 1 + int(math.ceil(abs(maxy - miny) / dy))
dy = abs(maxy - miny) / (y_lines_per_layer - 1) y_step = abs(maxy - miny) / (y_lines_per_layer - 1)
lines_per_layer += y_lines_per_layer lines_per_layer += y_lines_per_layer
progress_counter = ProgressCounter(num_of_layers * lines_per_layer, progress_counter = ProgressCounter(num_of_layers * lines_per_layer,
...@@ -57,7 +60,7 @@ class PushCutter: ...@@ -57,7 +60,7 @@ class PushCutter:
current_layer = 0 current_layer = 0
z_steps = [(maxz - i * dz) for i in range(num_of_layers)] z_steps = [(maxz - i * z_step) for i in range(num_of_layers)]
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" \
...@@ -65,14 +68,14 @@ class PushCutter: ...@@ -65,14 +68,14 @@ class PushCutter:
# cancel immediately # cancel immediately
break break
if dy > 0: if y_step > 0:
self.pa.new_direction(0) self.pa.new_direction(0)
self.GenerateToolPathSlice(minx, maxx, miny, maxy, z, 0, dy, self.GenerateToolPathSlice(minx, maxx, miny, maxy, z, 0, y_step,
draw_callback, progress_counter) draw_callback, progress_counter)
self.pa.end_direction() self.pa.end_direction()
if dx > 0: if x_step > 0:
self.pa.new_direction(1) self.pa.new_direction(1)
self.GenerateToolPathSlice(minx, maxx, miny, maxy, z, dx, 0, self.GenerateToolPathSlice(minx, maxx, miny, maxy, z, x_step, 0,
draw_callback, progress_counter) draw_callback, progress_counter)
self.pa.end_direction() self.pa.end_direction()
self.pa.finish() self.pa.finish()
...@@ -95,35 +98,37 @@ class PushCutter: ...@@ -95,35 +98,37 @@ class PushCutter:
# calculate the required number of steps in each direction # calculate the required number of steps in each direction
if dx > 0: if dx > 0:
depth_x = math.log(accuracy * abs(maxx-minx) / dx) / math.log(2) depth_x = math.log(accuracy * abs(maxx - minx) / dx) / math.log(2)
depth_x = max(int(math.ceil(depth_x)), 4) depth_x = max(int(math.ceil(depth_x)), 4)
depth_x = min(depth_x, max_depth) depth_x = min(depth_x, max_depth)
num_of_x_lines = 1 + int(math.ceil(abs(maxx - minx) / dx)) num_of_x_lines = 1 + int(math.ceil(abs(maxx - minx) / dx))
x_step = abs(maxx - minx) / (num_of_x_lines - 1) x_step = abs(maxx - minx) / (num_of_x_lines - 1)
x_steps = [minx + i * x_step for i in range(num_of_x_lines)] x_steps = [minx + i * x_step for i in range(num_of_x_lines)]
y_steps = [miny] * num_of_x_lines y_steps = [None] * num_of_x_lines
else: else:
depth_y = math.log(accuracy * (maxy-miny) / dy) / math.log(2) depth_y = math.log(accuracy * abs(maxy - miny) / dy) / math.log(2)
depth_y = max(int(math.ceil(depth_y)), 4) depth_y = max(int(math.ceil(depth_y)), 4)
depth_y = min(depth_y, max_depth) depth_y = min(depth_y, max_depth)
num_of_y_lines = 1 + int(math.ceil(abs(maxy - miny) / dy)) num_of_y_lines = 1 + int(math.ceil(abs(maxy - miny) / dy))
y_step = abs(maxy - miny) / (num_of_y_lines - 1) y_step = abs(maxy - miny) / (num_of_y_lines - 1)
y_steps = [miny + i * y_step for i in range(num_of_y_lines)] y_steps = [miny + i * y_step for i in range(num_of_y_lines)]
x_steps = [minx] * num_of_y_lines x_steps = [None] * num_of_y_lines
for x, y in zip(x_steps, y_steps): for x, y in zip(x_steps, y_steps):
self.pa.new_scanline() self.pa.new_scanline()
if dx > 0: if dx > 0:
p1, p2 = Point(x, miny, z), Point(x, maxy, z)
if self.physics: if self.physics:
points = get_free_horizontal_paths_ode(self.physics, x, x, miny, maxy, z, depth=depth_x) points = get_free_paths_ode(self.physics, p1, p2, depth=depth_x)
else: else:
points = get_free_horizontal_paths_triangles(self.model, self.cutter, x, x, miny, maxy, z) points = get_free_paths_triangles(self.model, self.cutter, p1, p2)
else: else:
p1, p2 = Point(minx, y, z), Point(maxx, y, z)
if self.physics: if self.physics:
points = get_free_horizontal_paths_ode(self.physics, minx, maxx, y, y, z, depth=depth_y) points = get_free_paths_ode(self.physics, p1, p2, depth=depth_y)
else: else:
points = get_free_horizontal_paths_triangles(self.model, self.cutter, minx, maxx, y, y, z) points = get_free_paths_triangles(self.model, self.cutter, p1, p2)
if points: if points:
for p in points: for p in points:
......
...@@ -57,30 +57,38 @@ class Hit: ...@@ -57,30 +57,38 @@ class Hit:
def cmp(a,b): def cmp(a,b):
return cmp(a.d, b.d) return cmp(a.d, b.d)
def get_free_horizontal_paths_triangles(model, cutter, minx, maxx, miny, maxy, z): def get_free_paths_triangles(model, cutter, p1, p2):
points = [] points = []
x_dist = abs(maxx - minx) x_dist = p2.x - p1.x
y_dist = abs(maxy - miny) y_dist = p2.y - p1.y
xy_dist = math.sqrt(x_dist * x_dist + y_dist * y_dist) z_dist = p2.z - p1.z
x_frac = x_dist / xy_dist xyz_dist = math.sqrt(x_dist * x_dist + y_dist * y_dist + z_dist * z_dist)
y_frac = y_dist / xy_dist x_frac = x_dist / xyz_dist
forward = Point(x_frac, y_frac, 0) y_frac = y_dist / xyz_dist
backward = Point(-x_frac, -y_frac, 0) z_frac = z_dist / xyz_dist
forward_small = Point(epsilon * x_frac, epsilon * y_frac, 0) forward = Point(x_frac, y_frac, z_frac)
backward_small = Point(-epsilon * x_frac, -epsilon * y_frac, 0) backward = Point(-x_frac, -y_frac, -z_frac)
forward_small = Point(epsilon * x_frac, epsilon * y_frac, epsilon * z_frac)
backward_small = Point(-epsilon * x_frac, -epsilon * y_frac, -epsilon * z_frac)
minx = min(p1.x, p2.x)
maxx = max(p1.x, p2.x)
miny = min(p1.y, p2.y)
maxy = max(p1.y, p2.y)
minz = min(p1.z, p2.z)
maxz = max(p1.z, p2.z)
# find all hits along scan line # find all hits along scan line
hits = [] hits = []
prev = Point(minx, miny, z) hits.append(Hit(p1, None, 0, None))
hits.append(Hit(prev, None, 0, None))
triangles = model.triangles(minx - cutter.radius, miny - cutter.radius, z, triangles = model.triangles(minx - cutter.radius, miny - cutter.radius, minz,
maxx + cutter.radius, maxy + cutter.radius, INFINITE) maxx + cutter.radius, maxy + cutter.radius, INFINITE)
for t in triangles: for t in triangles:
# normals point outward... and we want to approach the model from the outside! # normals point outward... and we want to approach the model from the outside!
n = t.normal().dot(forward) n = t.normal().dot(forward)
cutter.moveto(prev) cutter.moveto(p1)
if n >= 0: if n >= 0:
(cl, d) = cutter.intersect(backward, t) (cl, d) = cutter.intersect(backward, t)
if cl: if cl:
...@@ -94,19 +102,30 @@ def get_free_horizontal_paths_triangles(model, cutter, minx, maxx, miny, maxy, z ...@@ -94,19 +102,30 @@ def get_free_horizontal_paths_triangles(model, cutter, minx, maxx, miny, maxy, z
hits.append(Hit(cl.add(forward_small), t, d + epsilon, forward)) hits.append(Hit(cl.add(forward_small), t, d + epsilon, forward))
hits.append(Hit(cl.sub(forward_small), t, d - epsilon, forward)) hits.append(Hit(cl.sub(forward_small), t, d - epsilon, forward))
next = Point(maxx, maxy, z) hits.append(Hit(p2, None, xyz_dist, None))
hits.append(Hit(next, None, xy_dist, None))
# sort along the scan direction # sort along the scan direction
hits.sort(Hit.cmp) hits.sort(Hit.cmp)
# remove duplicates (typically shared edges) # Remove duplicates (typically shared edges)
i = 1 # Remove hits outside the min/max area of x/y/z (especially useful for the
while i < len(hits): # short-line cuts of the EngraveCutter
while i<len(hits) and abs(hits[i].d - hits[i-1].d)<epsilon/2: filtered_hits = []
del hits[i] previous_hit = None
i += 1 for one_hit in hits:
if not ((minx - epsilon) < one_hit.cl.x < (maxx + epsilon)):
continue
elif not ((miny - epsilon) < one_hit.cl.y < (maxy + epsilon)):
continue
elif not ((minz - epsilon) < one_hit.cl.z < (maxz + epsilon)):
continue
elif previous_hit and (abs(previous_hit.d - one_hit.d) < epsilon / 2):
continue
else:
previous_hit = one_hit
filtered_hits.append(one_hit)
hits = filtered_hits
# determine height at each interesting point # determine height at each interesting point
for h in hits: for h in hits:
...@@ -117,13 +136,13 @@ def get_free_horizontal_paths_triangles(model, cutter, minx, maxx, miny, maxy, z ...@@ -117,13 +136,13 @@ def get_free_horizontal_paths_triangles(model, cutter, minx, maxx, miny, maxy, z
begin = hits[0].cl begin = hits[0].cl
end = None end = None
for h in hits: for h in hits:
if h.z >= z - epsilon/10: if h.z >= minz - epsilon / 10:
if begin and end: if begin and end:
points.append(begin) points.append(begin)
points.append(end) points.append(end)
begin = None begin = None
end = None end = None
if h.z <= z + epsilon/10: if h.z <= maxz + epsilon / 10:
if not begin: if not begin:
begin = h.cl begin = h.cl
else: else:
...@@ -137,7 +156,7 @@ def get_free_horizontal_paths_triangles(model, cutter, minx, maxx, miny, maxy, z ...@@ -137,7 +156,7 @@ def get_free_horizontal_paths_triangles(model, cutter, minx, maxx, miny, maxy, z
return points return points
def get_free_horizontal_paths_ode(physics, minx, maxx, miny, maxy, z, depth=8): def get_free_paths_ode(physics, p1, p2, depth=8):
""" Recursive function for splitting a line (usually along x or y) into """ Recursive function for splitting a line (usually along x or y) into
small pieces to gather connected paths for the PushCutter. small pieces to gather connected paths for the PushCutter.
Strategy: check if the whole line is free (without collisions). Do a Strategy: check if the whole line is free (without collisions). Do a
...@@ -165,26 +184,22 @@ def get_free_horizontal_paths_ode(physics, minx, maxx, miny, maxy, z, depth=8): ...@@ -165,26 +184,22 @@ def get_free_horizontal_paths_ode(physics, minx, maxx, miny, maxy, z, depth=8):
""" """
points = [] points = []
# "resize" the drill along the while x/y range and check for a collision # "resize" the drill along the while x/y range and check for a collision
physics.extend_drill(maxx-minx, maxy-miny, 0.0) physics.extend_drill(p2.x - p1.x, p2.y - p1.y, p2.z - p1.z)
physics.set_drill_position((minx, miny, z)) physics.set_drill_position((p1.x, p1.y, p1.z))
if physics.check_collision(): if physics.check_collision():
# collision detected # collision detected
if depth > 0: if depth > 0:
middle_x = (minx + maxx)/2.0 middle_x = (p1.x + p2.x) / 2.0
middle_y = (miny + maxy)/2.0 middle_y = (p1.y + p2.y) / 2.0
group1 = get_free_horizontal_paths_ode(physics, minx, middle_x, middle_z = (p1.z + p2.z) / 2.0
miny, middle_y, z, depth-1) p_middle = Point(middle_x, middle_y, middle_z)
group2 = get_free_horizontal_paths_ode(physics, middle_x, maxx, group1 = get_free_paths_ode(physics, p1, p_middle, depth - 1)
middle_y, maxy, z, depth-1) group2 = get_free_paths_ode(physics, p_middle, p2, depth - 1)
if group1 and group2 and (group1[-1].x == group2[0].x) and (group1[-1].y == group2[0].y): if group1 and group2 and (group1[-1] == group2[0]):
# the last couple of the first group ends where the first couple of the second group starts # the last couple of the first group ends where the first couple of the second group starts
# we will combine them into one couple # we will combine them into one couple
last = group1[-2] points.extend(group1[:-1])
first = group2[1] points.extend(group2[1:])
combined = [last, first]
points.extend(group1[:-2])
points.extend(combined)
points.extend(group2[2:])
else: else:
# the two groups are not connected - just add both # the two groups are not connected - just add both
points.extend(group1) points.extend(group1)
...@@ -194,8 +209,8 @@ def get_free_horizontal_paths_ode(physics, minx, maxx, miny, maxy, z, depth=8): ...@@ -194,8 +209,8 @@ def get_free_horizontal_paths_ode(physics, minx, maxx, miny, maxy, z, depth=8):
pass pass
else: else:
# no collision - the line is free # no collision - the line is free
points.append(Point(minx, miny, z)) points.append(p1)
points.append(Point(maxx, maxy, z)) points.append(p2)
physics.reset_drill() physics.reset_drill()
return points return points
......
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