Commit 572a93cc authored by sumpfralle's avatar sumpfralle

added support for multiple trimesh models to all path generators

* this allows a better handling of the support grid (especially for the ContourFollow strategy)
fixed an old bug regarding the "get_free_path_triangles" function: it returned a free path even if the start and end points were completely within the model


git-svn-id: https://pycam.svn.sourceforge.net/svnroot/pycam/trunk@770 bbaffbd6-741e-11dd-a85d-61de82d9cad9
parent ff46ead3
......@@ -189,9 +189,9 @@ class CollisionPaths:
class ContourFollow:
def __init__(self, cutter, model, path_processor, physics=None):
def __init__(self, cutter, models, path_processor, physics=None):
self.cutter = cutter
self.model = model
self.models = models
self.pa = path_processor
self._up_vector = Vector(0, 0, 1)
self.physics = physics
......@@ -199,8 +199,11 @@ class ContourFollow:
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))
maxx = max([m.maxx for m in self.models])
minx = max([m.minx for m in self.models])
maxy = max([m.maxy for m in self.models])
miny = max([m.miny for m in self.models])
model_dim = max(abs(maxx - minx), abs(maxy - miny))
depth = math.log(accuracy * model_dim / self.cutter.radius) / math.log(2)
self._physics_maxdepth = min(max_depth, max(ceil(depth_x), 4))
......@@ -209,7 +212,7 @@ class ContourFollow:
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)
return get_free_paths_triangles(self.models, self.cutter, p1, p2)
def GenerateToolPath(self, minx, maxx, miny, maxy, minz, maxz, dz,
draw_callback=None):
......@@ -225,7 +228,8 @@ class ContourFollow:
num_of_layers = 1 + ceil(diff_z / dz)
z_step = diff_z / max(1, (num_of_layers - 1))
num_of_triangles = len(self.model.triangles(minx=minx, miny=miny, maxx=maxx, maxy=maxy))
# only the first model is used for the contour-follow algorithm
num_of_triangles = len(self.models[0].triangles(minx=minx, miny=miny, maxx=maxx, maxy=maxy))
progress_counter = ProgressCounter(2 * num_of_layers * num_of_triangles,
draw_callback)
......@@ -242,16 +246,18 @@ class ContourFollow:
break
self.pa.new_direction(0)
self.GenerateToolPathSlice(minx, maxx, miny, maxy, z,
draw_callback, progress_counter)
draw_callback, progress_counter, num_of_triangles)
self.pa.end_direction()
self.pa.finish()
current_layer += 1
return self.pa.paths
def GenerateToolPathSlice(self, minx, maxx, miny, maxy, z,
draw_callback=None, progress_counter=None):
draw_callback=None, progress_counter=None, num_of_triangles=None):
shifted_lines = self.get_potential_contour_lines(minx, maxx, miny, maxy,
z, progress_counter=progress_counter)
if num_of_triangles is None:
num_of_triangles = len(shifted_lines)
last_position = None
self.pa.new_scanline()
for line in shifted_lines:
......@@ -275,19 +281,21 @@ class ContourFollow:
break
# the progress counter jumps up by the number of non directly processed triangles
if not progress_counter is None:
progress_counter.increment(len(self.model.triangles()) - len(shifted_lines))
progress_counter.increment(num_of_triangles - len(shifted_lines))
self.pa.end_scanline()
return self.pa.paths
def get_potential_contour_lines(self, minx, maxx, miny, maxy, z,
progress_counter=None):
# use only the first model for the contour
follow_model = self.models[0]
plane = Plane(Point(0, 0, z), self._up_vector)
lines = []
waterline_triangles = CollisionPaths()
projected_waterlines = []
triangles = self.model.triangles(minx=minx, miny=miny, maxx=maxx,
triangles = follow_model.triangles(minx=minx, miny=miny, maxx=maxx,
maxy=maxy)
args = [(self.model, self.cutter, self._up_vector, t, z)
args = [(follow_model, self.cutter, self._up_vector, t, z)
for t in triangles if not id(t) in self._processed_triangles]
results_iter = run_in_parallel(_process_one_triangle, args,
unordered=True)
......@@ -458,7 +466,7 @@ def get_collision_waterline_of_triangle(model, cutter, up_vector, triangle, z):
start = edge.p1.add(edge_dir.mul(factor))
# We need to use the triangle collision algorithm here - because we
# need the point of collision in the triangle.
collisions = get_free_paths_triangles(model, cutter, start,
collisions = get_free_paths_triangles([model], cutter, start,
start.add(direction), return_triangles=True)
for index, coll in enumerate(collisions):
if (index % 2 == 0) and (not coll[1] is None) \
......
......@@ -86,9 +86,12 @@ class Dimension:
class DropCutter:
def __init__(self, cutter, model, path_processor, physics=None):
def __init__(self, cutter, models, path_processor, physics=None):
self.cutter = cutter
self.model = model
# combine the models (if there is more than one)
self.model = models[0]
for model in models[1:]:
self.model += model
self.pa = path_processor
self.physics = physics
# remember if we already reported an invalid boundary
......
......@@ -34,10 +34,17 @@ log = pycam.Utils.log.get_logger()
class EngraveCutter:
def __init__(self, cutter, model, contour_model, path_processor,
def __init__(self, cutter, trimesh_models, contour_model, path_processor,
physics=None, safety_height=INFINITE):
self.cutter = cutter
self.model = model
self.models = trimesh_models
# combine the models (if there is more than one)
if self.models:
self.combined_model = self.models[0]
for model in self.models[1:]:
self.combined_model += model
else:
self.combined_models = []
self.contour_model = contour_model
self.pa_push = path_processor
# We use a separated path processor for the last "drop" layer.
......@@ -153,12 +160,13 @@ class EngraveCutter:
p2 = Point(line.p2.x, line.p2.y, z)
# no model -> no possible obstacles
# model is completely below z (e.g. support bridges) -> no obstacles
if not self.model or (self.model.maxz < z):
relevant_models = [m for m in self.models if m.maxz >= z]
if not relevant_models:
points = [p1, p2]
elif self.physics:
points = get_free_paths_ode(self.physics, p1, p2)
else:
points = get_free_paths_triangles(self.model, self.cutter, p1, p2)
points = get_free_paths_triangles(relevant_models, self.cutter, p1, p2)
if points:
for p in points:
pa.append(p)
......@@ -185,13 +193,13 @@ class EngraveCutter:
last_position = None
for x, y in step_coords:
if not self.model:
if not self.combined_model:
# no obstacle -> minimum height
points = [Point(x, y, minz)]
elif self.physics:
points = get_max_height_ode(self.physics, x, y, minz, maxz)
else:
points = get_max_height_triangles(self.model, self.cutter,
points = get_max_height_triangles(self.combined_model, self.cutter,
x, y, minz, maxz, last_pos=last_position)
if points:
......
......@@ -31,19 +31,19 @@ import math
# We need to use a global function here - otherwise it does not work with
# the multiprocessing Pool.
def _process_one_line((p1, p2, depth, model, cutter, physics)):
def _process_one_line((p1, p2, depth, models, cutter, physics)):
if physics:
points = get_free_paths_ode(physics, p1, p2, depth=depth)
else:
points = get_free_paths_triangles(model, cutter, p1, p2)
points = get_free_paths_triangles(models, cutter, p1, p2)
return points
class PushCutter:
def __init__(self, cutter, model, path_processor, physics=None):
def __init__(self, cutter, models, path_processor, physics=None):
self.cutter = cutter
self.model = model
self.models = models
self.pa = path_processor
self.physics = physics
......@@ -100,7 +100,7 @@ class PushCutter:
args = []
for line in layer_grid:
p1, p2 = line
args.append((p1, p2, depth, self.model, self.cutter, self.physics))
args.append((p1, p2, depth, self.models, self.cutter, self.physics))
# ODE does not work with multi-threading
disable_multiprocessing = not self.physics is None
......
......@@ -41,7 +41,27 @@ class Hit:
return "%s - %s - %s - %s" % (self.d, self.cl, self.dir, self.cp)
def get_free_paths_triangles(model, cutter, p1, p2, return_triangles=False):
def get_free_paths_triangles(models, cutter, p1, p2, return_triangles=False):
if len(models) == 1:
# only one model is left - just continue
model = models[0]
else:
# multiple models were given - process them in layers
result = get_free_paths_triangles(models[:1], cutter, p1, p2,
return_triangles)
# group the result into pairs of two points (start/end)
point_pairs = []
while result:
pair1 = result.pop(0)
pair2 = result.pop(0)
point_pairs.append((pair1, pair2))
all_results = []
for pair in point_pairs:
one_result = get_free_paths_triangles(models[1:], cutter, pair[0],
pair[1], return_triangles)
all_results.extend(one_result)
return all_results
backward = p1.sub(p2).normalized()
forward = p2.sub(p1).normalized()
xyz_dist = p2.sub(p1).norm
......@@ -90,8 +110,20 @@ def get_free_paths_triangles(model, cutter, p1, p2, return_triangles=False):
points.append((p2, None, None))
if len(points) == 0:
points.append((p1, None, None))
points.append((p2, None, None))
# check if the path is completely free or if we are inside of the model
inside_counter = 0
for h in hits:
if -epsilon <= h.d:
# we reached the outer limit of the model
break
if h.dir == forward:
inside_counter += 1
else:
inside_counter -= 1
if inside_counter <= 0:
# we are not inside of the model
points.append((p1, None, None))
points.append((p2, None, None))
if return_triangles:
return points
......
......@@ -137,14 +137,13 @@ def generate_toolpath(model, tool_settings=None,
# trimesh model or contour model?
if isinstance(model, pycam.Geometry.Model.Model):
# trimesh model
trimesh_model = model
trimesh_models = [model]
contour_model = None
else:
# contour model
trimesh_model = pycam.Geometry.Model.Model()
trimesh_models = []
contour_model = model
# create the grid model if requested
trimesh_models = [trimesh_model]
if (support_grid_type == "grid") \
and (((not support_grid_distance_x is None) \
or (not support_grid_distance_y is None)) \
......@@ -194,7 +193,7 @@ def generate_toolpath(model, tool_settings=None,
if not contour_model is None:
model = contour_model
else:
model = trimesh_model
model = trimesh_models[0]
support_grid_model = pycam.Toolpath.SupportGrid.get_support_distributed(
model, minz, support_grid_average_distance,
support_grid_minimum_bridges, support_grid_thickness,
......@@ -242,7 +241,7 @@ def generate_toolpath(model, tool_settings=None,
return "No part of the contour model is within the bounding box."
# Due to some weirdness the height of the drill must be bigger than the
# object's size. Otherwise some collisions are not detected.
cutter_height = 4 * (maxy - miny)
cutter_height = 4 * abs(maxz - minz)
cutter = pycam.Cutters.get_tool_from_settings(tool_settings, cutter_height)
if isinstance(cutter, basestring):
return cutter
......@@ -252,10 +251,7 @@ def generate_toolpath(model, tool_settings=None,
physics = _get_physics(trimesh_models, cutter, calculation_backend)
if isinstance(physics, basestring):
return physics
combined_models = trimesh_models[0]
for next_model in trimesh_models[1:]:
combined_models += next_model
generator = _get_pathgenerator_instance(combined_models, contour_model,
generator = _get_pathgenerator_instance(trimesh_models, contour_model,
cutter, path_generator, path_postprocessor, milling_style, physics)
if isinstance(generator, basestring):
return generator
......@@ -309,9 +305,9 @@ def generate_toolpath(model, tool_settings=None,
% (path_generator, PATH_GENERATORS)
return toolpath
def _get_pathgenerator_instance(trimesh_model, contour_model, cutter,
def _get_pathgenerator_instance(trimesh_models, contour_model, cutter,
pathgenerator, pathprocessor, reverse, physics):
if pathgenerator != "EngraveCutter" and not trimesh_model.triangles():
if pathgenerator != "EngraveCutter" and contour_model:
return ("The only available toolpath strategy for 2D contour models " \
+ "is 'Engraving'.")
if pathgenerator == "DropCutter":
......@@ -324,7 +320,7 @@ def _get_pathgenerator_instance(trimesh_model, contour_model, cutter,
return ("Invalid postprocessor (%s) for 'DropCutter': only " \
+ "'ZigZagCutter' or 'PathAccumulator' are allowed") \
% str(pathprocessor)
return DropCutter.DropCutter(cutter, trimesh_model, processor,
return DropCutter.DropCutter(cutter, trimesh_models, processor,
physics=physics)
elif pathgenerator == "PushCutter":
if pathprocessor == "PathAccumulator":
......@@ -340,7 +336,7 @@ def _get_pathgenerator_instance(trimesh_model, contour_model, cutter,
else:
return ("Invalid postprocessor (%s) for 'PushCutter' - it should " \
+ "be one of these: %s") % (processor, PATH_POSTPROCESSORS)
return PushCutter.PushCutter(cutter, trimesh_model, processor,
return PushCutter.PushCutter(cutter, trimesh_models, processor,
physics=physics)
elif pathgenerator == "EngraveCutter":
if pathprocessor == "SimpleCutter":
......@@ -351,7 +347,7 @@ def _get_pathgenerator_instance(trimesh_model, contour_model, cutter,
if not contour_model:
return "The 'Engraving' toolpath strategy requires a 2D contour " \
+ "model (e.g. from a DXF or SVG file)."
return EngraveCutter.EngraveCutter(cutter, trimesh_model,
return EngraveCutter.EngraveCutter(cutter, trimesh_models,
contour_model, processor, physics=physics)
elif pathgenerator == "ContourFollow":
if pathprocessor == "SimpleCutter":
......@@ -359,7 +355,7 @@ def _get_pathgenerator_instance(trimesh_model, contour_model, cutter,
else:
return ("Invalid postprocessor (%s) for 'ContourFollow' - it " \
+ "should be: SimpleCutter") % str(processor)
return ContourFollow.ContourFollow(cutter, trimesh_model, processor,
return ContourFollow.ContourFollow(cutter, trimesh_models, processor,
physics=physics)
else:
return "Invalid path generator (%s): not one of %s" \
......
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