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/>.
import pycam.PathProcessors.PathAccumulator
from pycam.Geometry import Point
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 sys
......@@ -129,11 +129,9 @@ class EngraveCutter:
if not self.model or (self.model.maxz < z):
points = [p1, p2]
elif self.physics:
points = get_free_horizontal_paths_ode(self.physics, p1.x, p2.x,
p1.y, p2.y, z)
points = get_free_paths_ode(self.physics, p1, p2)
else:
points = get_free_horizontal_paths_triangles(self.model,
self.cutter, p1.x, p2.x, p1.y, p2.y, z)
points = get_free_paths_triangles(self.model, self.cutter, p1, p2)
if points:
for p in points:
pa.append(p)
......
......@@ -23,7 +23,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
from pycam.Geometry import Point
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
......@@ -38,16 +38,19 @@ class PushCutter:
def GenerateToolPath(self, minx, maxx, miny, maxy, minz, maxz, dx, dy, dz, draw_callback=None):
# calculate the number of steps
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
if dx != 0:
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
if dy != 0:
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
progress_counter = ProgressCounter(num_of_layers * lines_per_layer,
......@@ -57,7 +60,7 @@ class PushCutter:
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:
# update the progress bar and check, if we should cancel the process
if draw_callback and draw_callback(text="PushCutter: processing" \
......@@ -65,14 +68,14 @@ class PushCutter:
# cancel immediately
break
if dy > 0:
if y_step > 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)
self.pa.end_direction()
if dx > 0:
if x_step > 0:
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)
self.pa.end_direction()
self.pa.finish()
......@@ -95,35 +98,37 @@ class PushCutter:
# calculate the required number of steps in each direction
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 = min(depth_x, max_depth)
num_of_x_lines = 1 + int(math.ceil(abs(maxx - minx) / dx))
x_step = abs(maxx - minx) / (num_of_x_lines - 1)
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:
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 = min(depth_y, max_depth)
num_of_y_lines = 1 + int(math.ceil(abs(maxy - miny) / dy))
y_step = abs(maxy - miny) / (num_of_y_lines - 1)
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):
self.pa.new_scanline()
if dx > 0:
p1, p2 = Point(x, miny, z), Point(x, maxy, z)
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:
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:
p1, p2 = Point(minx, y, z), Point(maxx, y, z)
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:
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:
for p in points:
......
......@@ -57,30 +57,38 @@ class Hit:
def cmp(a,b):
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 = []
x_dist = abs(maxx - minx)
y_dist = abs(maxy - miny)
xy_dist = math.sqrt(x_dist * x_dist + y_dist * y_dist)
x_frac = x_dist / xy_dist
y_frac = y_dist / xy_dist
forward = Point(x_frac, y_frac, 0)
backward = Point(-x_frac, -y_frac, 0)
forward_small = Point(epsilon * x_frac, epsilon * y_frac, 0)
backward_small = Point(-epsilon * x_frac, -epsilon * y_frac, 0)
x_dist = p2.x - p1.x
y_dist = p2.y - p1.y
z_dist = p2.z - p1.z
xyz_dist = math.sqrt(x_dist * x_dist + y_dist * y_dist + z_dist * z_dist)
x_frac = x_dist / xyz_dist
y_frac = y_dist / xyz_dist
z_frac = z_dist / xyz_dist
forward = Point(x_frac, y_frac, z_frac)
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
hits = []
prev = Point(minx, miny, z)
hits.append(Hit(prev, None, 0, None))
hits.append(Hit(p1, 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)
for t in triangles:
# normals point outward... and we want to approach the model from the outside!
n = t.normal().dot(forward)
cutter.moveto(prev)
cutter.moveto(p1)
if n >= 0:
(cl, d) = cutter.intersect(backward, t)
if cl:
......@@ -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.sub(forward_small), t, d - epsilon, forward))
next = Point(maxx, maxy, z)
hits.append(Hit(next, None, xy_dist, None))
hits.append(Hit(p2, None, xyz_dist, None))
# sort along the scan direction
hits.sort(Hit.cmp)
# remove duplicates (typically shared edges)
i = 1
while i < len(hits):
while i<len(hits) and abs(hits[i].d - hits[i-1].d)<epsilon/2:
del hits[i]
i += 1
# Remove duplicates (typically shared edges)
# Remove hits outside the min/max area of x/y/z (especially useful for the
# short-line cuts of the EngraveCutter
filtered_hits = []
previous_hit = None
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
for h in hits:
......@@ -117,13 +136,13 @@ def get_free_horizontal_paths_triangles(model, cutter, minx, maxx, miny, maxy, z
begin = hits[0].cl
end = None
for h in hits:
if h.z >= z - epsilon/10:
if h.z >= minz - epsilon / 10:
if begin and end:
points.append(begin)
points.append(end)
begin = None
end = None
if h.z <= z + epsilon/10:
if h.z <= maxz + epsilon / 10:
if not begin:
begin = h.cl
else:
......@@ -137,7 +156,7 @@ def get_free_horizontal_paths_triangles(model, cutter, minx, maxx, miny, maxy, z
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
small pieces to gather connected paths for the PushCutter.
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):
"""
points = []
# "resize" the drill along the while x/y range and check for a collision
physics.extend_drill(maxx-minx, maxy-miny, 0.0)
physics.set_drill_position((minx, miny, z))
physics.extend_drill(p2.x - p1.x, p2.y - p1.y, p2.z - p1.z)
physics.set_drill_position((p1.x, p1.y, p1.z))
if physics.check_collision():
# collision detected
if depth > 0:
middle_x = (minx + maxx)/2.0
middle_y = (miny + maxy)/2.0
group1 = get_free_horizontal_paths_ode(physics, minx, middle_x,
miny, middle_y, z, depth-1)
group2 = get_free_horizontal_paths_ode(physics, middle_x, maxx,
middle_y, maxy, z, depth-1)
if group1 and group2 and (group1[-1].x == group2[0].x) and (group1[-1].y == group2[0].y):
middle_x = (p1.x + p2.x) / 2.0
middle_y = (p1.y + p2.y) / 2.0
middle_z = (p1.z + p2.z) / 2.0
p_middle = Point(middle_x, middle_y, middle_z)
group1 = get_free_paths_ode(physics, p1, p_middle, depth - 1)
group2 = get_free_paths_ode(physics, p_middle, p2, depth - 1)
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
# we will combine them into one couple
last = group1[-2]
first = group2[1]
combined = [last, first]
points.extend(group1[:-2])
points.extend(combined)
points.extend(group2[2:])
points.extend(group1[:-1])
points.extend(group2[1:])
else:
# the two groups are not connected - just add both
points.extend(group1)
......@@ -194,8 +209,8 @@ def get_free_horizontal_paths_ode(physics, minx, maxx, miny, maxy, z, depth=8):
pass
else:
# no collision - the line is free
points.append(Point(minx, miny, z))
points.append(Point(maxx, maxy, z))
points.append(p1)
points.append(p2)
physics.reset_drill()
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