Commit 57667344 authored by sumpfralle's avatar sumpfralle

unified the generation of a grid of movement positions for DropCutter/PushCutter


git-svn-id: https://pycam.svn.sourceforge.net/svnroot/pycam/trunk@759 bbaffbd6-741e-11dd-a85d-61de82d9cad9
parent 0bf77da9
...@@ -36,6 +36,7 @@ from pycam.Gui.OpenGLTools import ModelViewWindowGL ...@@ -36,6 +36,7 @@ from pycam.Gui.OpenGLTools import ModelViewWindowGL
from pycam.Toolpath import Bounds from pycam.Toolpath import Bounds
from pycam import VERSION from pycam import VERSION
import pycam.Physics.ode_physics import pycam.Physics.ode_physics
import pycam.Toolpath.MotionGrid
# this requires ODE - we import it later, if necessary # this requires ODE - we import it later, if necessary
#import pycam.Simulation.ODEBlocks #import pycam.Simulation.ODEBlocks
import gtk import gtk
...@@ -547,8 +548,9 @@ class ProjectGui: ...@@ -547,8 +548,9 @@ class ProjectGui:
# connect the "consistency check" and the update-handler with all toolpath settings # connect the "consistency check" and the update-handler with all toolpath settings
for objname in ("PushRemoveStrategy", "ContourPolygonStrategy", for objname in ("PushRemoveStrategy", "ContourPolygonStrategy",
"ContourFollowStrategy", "SurfaceStrategy", "ContourFollowStrategy", "SurfaceStrategy",
"EngraveStrategy", "GridDirectionX", "EngraveStrategy", "GridDirectionX", "GridDirectionY",
"GridDirectionY", "GridDirectionXY"): "GridDirectionXY", "MillingStyleConventional",
"MillingStyleClimb", "MillingStyleIgnore"):
self.gui.get_object(objname).connect("toggled", self.gui.get_object(objname).connect("toggled",
self.update_process_controls) self.update_process_controls)
self.gui.get_object(objname).connect("toggled", self.gui.get_object(objname).connect("toggled",
...@@ -1318,8 +1320,10 @@ class ProjectGui: ...@@ -1318,8 +1320,10 @@ class ProjectGui:
"EngraveOffsetControl") "EngraveOffsetControl")
active_controls = { active_controls = {
"PushRemoveStrategy": ("GridDirectionX", "GridDirectionY", "PushRemoveStrategy": ("GridDirectionX", "GridDirectionY",
"GridDirectionXY", "MaxStepDownControl", "GridDirectionXY", "MillingStyleConventional",
"MaterialAllowanceControl", "OverlapPercentControl"), "MillingStyleClimb", "MillingStyleIgnore",
"MaxStepDownControl", "MaterialAllowanceControl",
"OverlapPercentControl"),
"ContourPolygonStrategy": ("GridDirectionX", "GridDirectionY", "ContourPolygonStrategy": ("GridDirectionX", "GridDirectionY",
"GridDirectionXY", "MillingStyleConventional", "GridDirectionXY", "MillingStyleConventional",
"MillingStyleClimb", "MillingStyleIgnore", "MillingStyleClimb", "MillingStyleIgnore",
...@@ -2303,44 +2307,6 @@ class ProjectGui: ...@@ -2303,44 +2307,6 @@ class ProjectGui:
elif action == "delete": elif action == "delete":
self.append_to_queue(self.switch_bounds_table_selection) self.append_to_queue(self.switch_bounds_table_selection)
def _get_process_details(self, strategy, direction, milling_style):
STRATEGY_GENERATORS = {
"PushRemoveStrategy": "PushCutter",
"ContourPolygonStrategy": "PushCutter",
"ContourFollowStrategy": "ContourFollow",
"SurfaceStrategy": "DropCutter",
"EngraveStrategy": "EngraveCutter"}
generator = STRATEGY_GENERATORS[strategy]
if strategy in ("PushRemoveStrategy", "SurfaceStrategy"):
if strategy == "PushRemoveStrategy":
# TODO: implement "conventional/climb" for PolygonCutter
processor = "PolygonCutter"
elif milling_style in ("conventional", "climb"):
processor = "PathAccumulator"
else:
processor = "ZigZagCutter"
if milling_style == "conventional":
reverse = False
else:
reverse_counter = 0
if direction == "y":
reverse_counter += 1
if milling_style == "conventional":
reverse_counter += 1
reverse = (reverse_counter % 1) == 1
elif strategy in ("ContourPolygonStrategy", "ContourFollowStrategy"):
if strategy == "ContourPolygonStrategy":
processor = "ContourCutter"
else:
processor = "SimpleCutter"
reverse = milling_style in ("climb", "ignore")
elif strategy == "EngraveStrategy":
processor = "SimpleCutter"
reverse = False
else:
pass
return generator, processor, reverse
def _load_process_settings_from_gui(self, settings=None): def _load_process_settings_from_gui(self, settings=None):
if settings is None: if settings is None:
settings = {} settings = {}
...@@ -2860,18 +2826,23 @@ class ProjectGui: ...@@ -2860,18 +2826,23 @@ class ProjectGui:
# unit size # unit size
toolpath_settings.set_unit_size(self.settings.get("unit")) toolpath_settings.set_unit_size(self.settings.get("unit"))
generator, postprocessor, reverse = self._get_process_details( STRATEGY_GENERATORS = {
process_settings["path_strategy"], "PushRemoveStrategy": ("PushCutter", "SimpleCutter"),
process_settings["path_direction"], "ContourPolygonStrategy": ("PushCutter", "ContourPolygonStrategy"),
process_settings["milling_style"]) "ContourFollowStrategy": ("ContourFollow", "SimpleCutter"),
"SurfaceStrategy": ("DropCutter", "PathAccumulator"),
"EngraveStrategy": ("EngraveCutter", "SimpleCutter")}
generator, postprocessor = STRATEGY_GENERATORS[
process_settings["path_strategy"]]
# process settings # process settings
toolpath_settings.set_process_settings( toolpath_settings.set_process_settings(
generator, postprocessor, process_settings["path_direction"], generator, postprocessor, process_settings["path_direction"],
reverse, process_settings["material_allowance"], process_settings["material_allowance"],
process_settings["overlap_percent"] / 100.0, process_settings["overlap_percent"] / 100.0,
process_settings["step_down"], process_settings["step_down"],
process_settings["engrave_offset"]) process_settings["engrave_offset"],
process_settings["milling_style"])
return toolpath_settings return toolpath_settings
......
...@@ -586,11 +586,11 @@ class ToolpathSettings: ...@@ -586,11 +586,11 @@ class ToolpathSettings:
"generator": str, "generator": str,
"postprocessor": str, "postprocessor": str,
"path_direction": str, "path_direction": str,
"reverse": bool,
"material_allowance": float, "material_allowance": float,
"overlap": float, "overlap": float,
"step_down": float, "step_down": float,
"engrave_offset": float, "engrave_offset": float,
"milling_style": str,
}, },
} }
...@@ -698,20 +698,21 @@ class ToolpathSettings: ...@@ -698,20 +698,21 @@ class ToolpathSettings:
return "mm" return "mm"
def set_process_settings(self, generator, postprocessor, path_direction, def set_process_settings(self, generator, postprocessor, path_direction,
reverse=False, material_allowance=0.0, overlap=0.0, material_allowance=0.0, overlap=0.0, step_down=1.0,
step_down=1.0, engrave_offset=0.0): engrave_offset=0.0, milling_style="ignore"):
# TODO: this hack should be somewhere else, I guess # TODO: this hack should be somewhere else, I guess
if generator in ("ContourFollow", "EngraveCutter"): if generator in ("ContourFollow", "EngraveCutter"):
material_allowance = 0.0 material_allowance = 0.0
milling_style = "ignore"
self.process_settings = { self.process_settings = {
"generator": generator, "generator": generator,
"postprocessor": postprocessor, "postprocessor": postprocessor,
"path_direction": path_direction, "path_direction": path_direction,
"reverse": reverse,
"material_allowance": material_allowance, "material_allowance": material_allowance,
"overlap": overlap, "overlap": overlap,
"step_down": step_down, "step_down": step_down,
"engrave_offset": engrave_offset, "engrave_offset": engrave_offset,
"milling_style": milling_style,
} }
def get_process_settings(self): def get_process_settings(self):
......
...@@ -33,7 +33,7 @@ log = pycam.Utils.log.get_logger() ...@@ -33,7 +33,7 @@ log = pycam.Utils.log.get_logger()
# We need to use a global function here - otherwise it does not work with # We need to use a global function here - otherwise it does not work with
# the multiprocessing Pool. # the multiprocessing Pool.
def _process_one_grid_line((positions, minz, maxz, dim_attrs, model, cutter, def _process_one_grid_line((positions, minz, maxz, model, cutter,
physics, safety_height)): physics, safety_height)):
# for now only used for triangular collision detection # for now only used for triangular collision detection
last_position = None last_position = None
...@@ -41,11 +41,10 @@ def _process_one_grid_line((positions, minz, maxz, dim_attrs, model, cutter, ...@@ -41,11 +41,10 @@ def _process_one_grid_line((positions, minz, maxz, dim_attrs, model, cutter,
height_exceeded = False height_exceeded = False
for x, y in positions: for x, y in positions:
if physics: if physics:
result = get_max_height_ode(physics, x, y, minz, maxz, result = get_max_height_ode(physics, x, y, minz, maxz)
order=dim_attrs[:])
else: else:
result = get_max_height_triangles(model, cutter, x, y, minz, maxz, result = get_max_height_triangles(model, cutter, x, y, minz, maxz,
order=dim_attrs[:], last_pos=last_position) last_pos=last_position)
if result: if result:
points.extend(result) points.extend(result)
else: else:
...@@ -95,43 +94,35 @@ class DropCutter: ...@@ -95,43 +94,35 @@ class DropCutter:
# remember if we already reported an invalid boundary # remember if we already reported an invalid boundary
self._boundary_warning_already_shown = False self._boundary_warning_already_shown = False
def GenerateToolPath(self, minx, maxx, miny, maxy, minz, maxz, d0, d1, def GenerateToolPath(self, motion_grid, minz, maxz, draw_callback=None):
direction, draw_callback=None):
quit_requested = False quit_requested = False
# determine step size
num_of_x_lines = 1 + ceil(abs(maxx - minx) / d0)
num_of_y_lines = 1 + ceil(abs(maxy - miny) / d1)
x_step = abs(maxx - minx) / max(1, (num_of_x_lines - 1))
y_step = abs(maxy - miny) / max(1, (num_of_y_lines - 1))
x_steps = [(minx + i * x_step) for i in range(num_of_x_lines)]
y_steps = [(miny + i * y_step) for i in range(num_of_y_lines)]
# map the scales according to the order of direction
grid = []
if direction == 0:
# first x, then y
for x in x_steps:
grid.append(zip([x] * (len(y_steps) + 1), y_steps))
dim_attrs = ["x", "y"]
else:
# first y, then x
for y in y_steps:
grid.append(zip(x_steps, [y] * (len(x_steps) + 1)))
dim_attrs = ["y", "x"]
num_of_lines = len(grid) # Transfer the grid (a generator) into a list of lists and count the
num_of_grid_positions = num_of_x_lines * num_of_y_lines # items.
num_of_grid_positions = 0
lines = []
# there should be only one layer for DropCutter
for layer in motion_grid:
for line in layer:
lines.append(list(line))
num_of_grid_positions += len(lines[-1])
# ignore any other layers
break
num_of_lines = len(lines)
progress_counter = ProgressCounter(num_of_grid_positions, draw_callback) progress_counter = ProgressCounter(num_of_grid_positions, draw_callback)
current_line = 0 current_line = 0
self.pa.new_direction(direction) self.pa.new_direction(0)
self._boundary_warning_already_shown = False self._boundary_warning_already_shown = False
args = [] args = []
for one_grid_line in grid: for one_grid_line in lines:
args.append((one_grid_line, minz, maxz, dim_attrs, self.model, # simplify the data (useful for remote processing)
self.cutter, self.physics, self.model.maxz)) xy_coords = [(pos.x, pos.y) for pos in one_grid_line]
args.append((xy_coords, minz, maxz, self.model, self.cutter,
self.physics, self.model.maxz))
# ODE does not work with multi-threading # ODE does not work with multi-threading
disable_multiprocessing = not self.physics is None disable_multiprocessing = not self.physics is None
for points, height_exceeded in run_in_parallel(_process_one_grid_line, for points, height_exceeded in run_in_parallel(_process_one_grid_line,
......
...@@ -47,50 +47,34 @@ class PushCutter: ...@@ -47,50 +47,34 @@ class PushCutter:
self.pa = path_processor self.pa = path_processor
self.physics = physics self.physics = physics
def GenerateToolPath(self, minx, maxx, miny, maxy, minz, maxz, dx, dy, dz, def GenerateToolPath(self, motion_grid, draw_callback=None):
draw_callback=None):
# calculate the number of steps # calculate the number of steps
# Sometimes there is a floating point accuracy issue: make sure
# that only one layer is drawn, if maxz and minz are almost the same.
if abs(maxz - minz) < epsilon:
diff_z = 0
else:
diff_z = abs(maxz - minz)
num_of_layers = 1 + ceil(diff_z / dz)
z_step = diff_z / max(1, (num_of_layers - 1))
lines_per_layer = 0
if dx != 0:
x_lines_per_layer = 1 + ceil(abs(maxx - minx) / dx)
x_step = abs(maxx - minx) / max(1, (x_lines_per_layer - 1))
lines_per_layer += x_lines_per_layer
if dy != 0:
y_lines_per_layer = 1 + ceil(abs(maxy - miny) / dy)
y_step = abs(maxy - miny) / max(1, (y_lines_per_layer - 1))
lines_per_layer += y_lines_per_layer
progress_counter = ProgressCounter(num_of_layers * lines_per_layer,
draw_callback)
current_layer = 0 # Transfer the grid (a generator) into a list of lists and count the
# items.
grid = []
num_of_grid_positions = 0
for layer in motion_grid:
lines = []
for line in layer:
lines.append(list(line))
num_of_grid_positions += len(lines[-1])
grid.append(lines)
num_of_layers = len(grid)
z_steps = [(maxz - i * z_step) for i in range(num_of_layers)] progress_counter = ProgressCounter(num_of_grid_positions, draw_callback)
for z in z_steps:
current_layer = 0
for layer_grid in grid:
# 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
if dy > 0:
self.pa.new_direction(0) self.pa.new_direction(0)
self.GenerateToolPathSlice(minx, maxx, miny, maxy, z, 0, y_step, self.GenerateToolPathSlice(layer_grid, draw_callback, progress_counter)
draw_callback, progress_counter)
self.pa.end_direction()
if dx > 0:
self.pa.new_direction(1)
self.GenerateToolPathSlice(minx, maxx, miny, maxy, z, x_step, 0,
draw_callback, progress_counter)
self.pa.end_direction() self.pa.end_direction()
self.pa.finish() self.pa.finish()
...@@ -98,8 +82,8 @@ class PushCutter: ...@@ -98,8 +82,8 @@ class PushCutter:
return self.pa.paths return self.pa.paths
def GenerateToolPathSlice(self, minx, maxx, miny, maxy, z, dx, dy, def GenerateToolPathSlice(self, layer_grid, draw_callback=None,
draw_callback=None, progress_counter=None): progress_counter=None):
""" only dx or (exclusive!) dy may be bigger than zero """ only dx or (exclusive!) dy may be bigger than zero
""" """
# max_deviation_x = dx/accuracy # max_deviation_x = dx/accuracy
...@@ -107,34 +91,15 @@ class PushCutter: ...@@ -107,34 +91,15 @@ class PushCutter:
max_depth = 20 max_depth = 20
# calculate the required number of steps in each direction # calculate the required number of steps in each direction
if dx > 0: distance = layer_grid[0][-1].sub(layer_grid[0][0]).norm
depth = math.log(accuracy * abs(maxx - minx) / dx) / math.log(2) step_width = distance / len(layer_grid[0])
depth = max(ceil(depth), 4) depth = math.log(accuracy * distance / step_width) / math.log(2)
depth = min(depth, max_depth)
num_of_x_lines = 1 + ceil(abs(maxx - minx) / dx)
x_step = abs(maxx - minx) / max(1, (num_of_x_lines - 1))
x_steps = [minx + i * x_step for i in range(num_of_x_lines)]
y_steps = [None] * num_of_x_lines
elif dy != 0:
depth = math.log(accuracy * abs(maxy - miny) / dy) / math.log(2)
depth = max(ceil(depth), 4) depth = max(ceil(depth), 4)
depth = min(depth, max_depth) depth = min(depth, max_depth)
num_of_y_lines = 1 + ceil(abs(maxy - miny) / dy)
y_step = abs(maxy - miny) / max(1, (num_of_y_lines - 1))
y_steps = [miny + i * y_step for i in range(num_of_y_lines)]
x_steps = [None] * num_of_y_lines
else:
# nothing to be done
return
args = [] args = []
if dx > 0: for line in layer_grid:
depth = depth_ p1, p2 = line
for x, y in zip(x_steps, y_steps):
if dx > 0:
p1, p2 = Point(x, miny, z), Point(x, maxy, z)
else:
p1, p2 = Point(minx, y, z), Point(maxx, y, z)
args.append((p1, p2, depth, self.model, self.cutter, self.physics)) args.append((p1, p2, depth, self.model, self.cutter, self.physics))
# ODE does not work with multi-threading # ODE does not work with multi-threading
......
...@@ -159,7 +159,7 @@ def get_free_paths_ode(physics, p1, p2, depth=8): ...@@ -159,7 +159,7 @@ def get_free_paths_ode(physics, p1, p2, depth=8):
physics.reset_drill() physics.reset_drill()
return points return points
def get_max_height_ode(physics, x, y, minz, maxz, order=None): def get_max_height_ode(physics, x, y, minz, maxz):
low, high = minz, maxz low, high = minz, maxz
trip_start = 20 trip_start = 20
safe_z = None safe_z = None
...@@ -196,17 +196,13 @@ def get_max_height_ode(physics, x, y, minz, maxz, order=None): ...@@ -196,17 +196,13 @@ def get_max_height_ode(physics, x, y, minz, maxz, order=None):
else: else:
return [Point(x, y, safe_z)] return [Point(x, y, safe_z)]
def get_max_height_triangles(model, cutter, x, y, minz, maxz, order=None, def get_max_height_triangles(model, cutter, x, y, minz, maxz, last_pos=None):
last_pos=None):
# TODO: "order" should be replaced with a direction vector
result = [] result = []
if last_pos is None: if last_pos is None:
last_pos = {} last_pos = {}
for key in ("triangle", "cut"): for key in ("triangle", "cut"):
if not key in last_pos: if not key in last_pos:
last_pos[key] = None last_pos[key] = None
if order is None:
order = ["x", "y"]
p = Point(x, y, maxz) p = Point(x, y, maxz)
height_max = None height_max = None
cut_max = None cut_max = None
......
...@@ -26,6 +26,7 @@ from pycam.Geometry.utils import number ...@@ -26,6 +26,7 @@ from pycam.Geometry.utils import number
import pycam.PathProcessors import pycam.PathProcessors
import pycam.Cutters import pycam.Cutters
import pycam.Toolpath.SupportGrid import pycam.Toolpath.SupportGrid
import pycam.Toolpath.MotionGrid
import pycam.Geometry.Model import pycam.Geometry.Model
from pycam.Utils import ProgressCounter from pycam.Utils import ProgressCounter
import pycam.Utils.log import pycam.Utils.log
...@@ -45,14 +46,12 @@ def generate_toolpath_from_settings(model, tp_settings, callback=None): ...@@ -45,14 +46,12 @@ def generate_toolpath_from_settings(model, tp_settings, callback=None):
process = tp_settings.get_process_settings() process = tp_settings.get_process_settings()
grid = tp_settings.get_support_grid() grid = tp_settings.get_support_grid()
backend = tp_settings.get_calculation_backend() backend = tp_settings.get_calculation_backend()
bounds_obj = tp_settings.get_bounds()
bounds_low, bounds_high = bounds_obj.get_absolute_limits()
return generate_toolpath(model, tp_settings.get_tool_settings(), return generate_toolpath(model, tp_settings.get_tool_settings(),
bounds_low, bounds_high, process["path_direction"], tp_settings.get_bounds(), process["path_direction"],
process["generator"], process["postprocessor"], process["generator"], process["postprocessor"],
process["reverse"],
process["material_allowance"], process["overlap"], process["material_allowance"], process["overlap"],
process["step_down"], process["engrave_offset"], process["step_down"], process["engrave_offset"],
process["milling_style"],
grid["type"], grid["distance_x"], grid["distance_y"], grid["type"], grid["distance_x"], grid["distance_y"],
grid["thickness"], grid["height"], grid["offset_x"], grid["thickness"], grid["height"], grid["offset_x"],
grid["offset_y"], grid["adjustments_x"], grid["adjustments_y"], grid["offset_y"], grid["adjustments_x"], grid["adjustments_y"],
...@@ -60,10 +59,10 @@ def generate_toolpath_from_settings(model, tp_settings, callback=None): ...@@ -60,10 +59,10 @@ def generate_toolpath_from_settings(model, tp_settings, callback=None):
backend, callback) backend, callback)
def generate_toolpath(model, tool_settings=None, def generate_toolpath(model, tool_settings=None,
bounds_low=None, bounds_high=None, direction="x", bounds=None, direction="x",
path_generator="DropCutter", path_postprocessor="ZigZagCutter", path_generator="DropCutter", path_postprocessor="ZigZagCutter",
reverse=False,
material_allowance=0, overlap=0, step_down=0, engrave_offset=0, material_allowance=0, overlap=0, step_down=0, engrave_offset=0,
milling_style="ignore",
support_grid_type=None, support_grid_distance_x=None, support_grid_type=None, support_grid_distance_x=None,
support_grid_distance_y=None, support_grid_thickness=None, support_grid_distance_y=None, support_grid_thickness=None,
support_grid_height=None, support_grid_offset_x=None, support_grid_height=None, support_grid_offset_x=None,
...@@ -127,15 +126,13 @@ def generate_toolpath(model, tool_settings=None, ...@@ -127,15 +126,13 @@ def generate_toolpath(model, tool_settings=None,
overlap = number(overlap) overlap = number(overlap)
step_down = number(step_down) step_down = number(step_down)
engrave_offset = number(engrave_offset) engrave_offset = number(engrave_offset)
if bounds_low is None: if bounds is None:
# no bounds were given - we use the boundaries of the model # no bounds were given - we use the boundaries of the model
minx, miny, minz = (model.minx, model.miny, model.minz) minx, miny, minz = (model.minx, model.miny, model.minz)
else:
minx, miny, minz = [number(value) for value in bounds_low]
if bounds_high is None:
# no bounds were given - we use the boundaries of the model
maxx, maxy, maxz = (model.maxx, model.maxy, model.maxz) maxx, maxy, maxz = (model.maxx, model.maxy, model.maxz)
else: else:
bounds_low, bounds_high = bounds.get_absolute_limits()
minx, miny, minz = [number(value) for value in bounds_low]
maxx, maxy, maxz = [number(value) for value in bounds_high] maxx, maxy, maxz = [number(value) for value in bounds_high]
# trimesh model or contour model? # trimesh model or contour model?
if isinstance(model, pycam.Geometry.Model.Model): if isinstance(model, pycam.Geometry.Model.Model):
...@@ -259,42 +256,38 @@ def generate_toolpath(model, tool_settings=None, ...@@ -259,42 +256,38 @@ def generate_toolpath(model, tool_settings=None,
for next_model in trimesh_models[1:]: for next_model in trimesh_models[1:]:
combined_models += next_model combined_models += next_model
generator = _get_pathgenerator_instance(combined_models, contour_model, generator = _get_pathgenerator_instance(combined_models, contour_model,
cutter, path_generator, path_postprocessor, reverse, physics) cutter, path_generator, path_postprocessor, milling_style, physics)
if isinstance(generator, basestring): if isinstance(generator, basestring):
return generator return generator
if (overlap < 0) or (overlap >= 1): if (overlap < 0) or (overlap >= 1):
return "Invalid overlap value (%f): should be greater or equal 0 " \ return "Invalid overlap value (%f): should be greater or equal 0 " \
+ "and lower than 1" + "and lower than 1"
# factor "2" since we are based on radius instead of diameter # factor "2" since we are based on radius instead of diameter
stepping = 2 * number(tool_settings["tool_radius"]) * (1 - overlap) line_stepping = 2 * number(tool_settings["tool_radius"]) * (1 - overlap)
if path_generator == "PushCutter":
step_width = None
else:
# TODO: the step_width should be configurable
step_width = tool_settings["tool_radius"] / 10.0
if path_generator == "DropCutter": if path_generator == "DropCutter":
if direction == "x": layer_distance = None
direction_param = 0
elif direction == "y":
direction_param = 1
else: else:
return "Invalid direction value (%s): not one of %s" \ layer_distance = step_down
% (direction, DIRECTIONS) direction_dict = {"x": pycam.Toolpath.MotionGrid.GRID_DIRECTION_X,
toolpath = generator.GenerateToolPath(minx, maxx, miny, maxy, minz, "y": pycam.Toolpath.MotionGrid.GRID_DIRECTION_Y,
maxz, stepping, stepping, direction_param, callback) "xy": pycam.Toolpath.MotionGrid.GRID_DIRECTION_XY}
milling_style_grid = {
"ignore": pycam.Toolpath.MotionGrid.MILLING_STYLE_IGNORE,
"conventional": pycam.Toolpath.MotionGrid.MILLING_STYLE_CONVENTIONAL,
"climb": pycam.Toolpath.MotionGrid.MILLING_STYLE_CLIMB}
motion_grid = pycam.Toolpath.MotionGrid.get_fixed_grid(bounds,
layer_distance, line_stepping, step_width=step_width,
grid_direction=direction_dict[direction],
milling_style=milling_style_grid[milling_style])
if path_generator == "DropCutter":
toolpath = generator.GenerateToolPath(motion_grid, minz, maxz, callback)
elif path_generator == "PushCutter": elif path_generator == "PushCutter":
if step_down > 0: toolpath = generator.GenerateToolPath(motion_grid, callback)
dz = step_down
else:
dz = maxz - minz
if dz <= 0:
dz = 1
if direction == "x":
dx, dy = 0, stepping
elif direction == "y":
dx, dy = stepping, 0
elif direction == "xy":
dx, dy = stepping, stepping
else:
return "Invalid direction (%s): not one of %s" \
% (direction, DIRECTIONS)
toolpath = generator.GenerateToolPath(minx, maxx, miny, maxy, minz,
maxz, dx, dy, dz, callback)
elif path_generator == "EngraveCutter": elif path_generator == "EngraveCutter":
if step_down > 0: if step_down > 0:
dz = step_down dz = step_down
......
# -*- coding: utf-8 -*-
"""
$Id$
Copyright 2010 Lars Kruse <devel@sumpfralle.de>
This file is part of PyCAM.
PyCAM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
PyCAM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
"""
from pycam.Geometry.Point import Point
from pycam.Geometry.utils import epsilon
import math
GRID_DIRECTION_X = 0
GRID_DIRECTION_Y = 1
GRID_DIRECTION_XY = 2
MILLING_STYLE_IGNORE = 0
MILLING_STYLE_CONVENTIONAL = 1
MILLING_STYLE_CLIMB = 2
START_X = 0x1
START_Y = 0x2
START_Z = 0x4
def isiterable(obj):
try:
iter(obj)
return True
except TypeError:
return False
def floatrange(start, end, inc=None, steps=None, reverse=False):
if reverse:
start, end = end, start
# 'inc' will be adjusted below anyway
if abs(start - end) < epsilon:
yield start
elif inc is None and steps is None:
raise ValueError("floatrange: either 'inc' or 'steps' must be provided")
elif (not steps is None) and (steps < 2):
raise ValueError("floatrange: 'steps' must be greater than 1")
else:
# the input is fine
# reverse increment, if it does not suit start/end
if steps is None:
if ((end - start) > 0) != (inc > 0):
inc = -inc
steps = int(math.ceil(float(end - start) / inc) + 1)
inc = float(end - start) / (steps - 1)
for index in range(steps):
yield start + inc * index
def get_fixed_grid_line(start, end, line_pos, z, step_width=None,
grid_direction=GRID_DIRECTION_X):
if step_width is None:
# useful for PushCutter operations
steps = (start, end)
elif isiterable(step_width):
steps = step_width
else:
steps = floatrange(start, end, inc=step_width)
if grid_direction == GRID_DIRECTION_X:
get_point = lambda pos: Point(pos, line_pos, z)
else:
get_point = lambda pos: Point(line_pos, pos, z)
for pos in steps:
yield get_point(pos)
def get_fixed_grid_layer(minx, maxx, miny, maxy, z, line_distance,
step_width=None, grid_direction=GRID_DIRECTION_X,
milling_style=MILLING_STYLE_IGNORE, start_position=0):
if grid_direction == GRID_DIRECTION_XY:
raise ValueError("'get_one_layer_fixed_grid' does not accept XY " \
+ "direction")
# zigzag is only available if the milling
zigzag = (milling_style == MILLING_STYLE_IGNORE)
# If we happen to start at a position that collides with the milling style,
# then we need to move to the closest other corner. Here we decide, which
# would be the best alternative.
def get_alternative_start_position(start):
if (maxx - minx) <= (maxy - miny):
# toggle the X position bit
return start ^ START_X
else:
# toggle the Y position bit
return start ^ START_Y
if grid_direction == GRID_DIRECTION_X:
primary_dir = START_X
secondary_dir = START_Y
else:
primary_dir = START_Y
secondary_dir = START_X
# Determine the starting direction (assuming we begin at the lower x/y
# coordinates.
if milling_style == MILLING_STYLE_IGNORE:
# just move forward - milling style is not important
pass
elif (milling_style == MILLING_STYLE_CLIMB) == (grid_direction == GRID_DIRECTION_X):
if bool(start_position & START_X) == bool(start_position & START_Y):
# we can't start from here - choose an alternative
start_position = get_alternative_start_position(start_position)
elif (milling_style == MILLING_STYLE_CONVENTIONAL) == (grid_direction == GRID_DIRECTION_X):
if bool(start_position & START_X) != bool(start_position & START_Y):
# we can't start from here - choose an alternative
start_position = get_alternative_start_position(start_position)
else:
raise ValueError("Invalid milling style given: %s" % str(milling_style))
# sort out the coordinates (primary/secondary)
if grid_direction == GRID_DIRECTION_X:
start, end = minx, maxx
line_start, line_end = miny, maxy
else:
start, end = miny, maxy
line_start, line_end = minx, maxx
# switch start/end if we move from high to low
if start_position & primary_dir:
start, end = end, start
if start_position & secondary_dir:
line_start, line_end = line_end, line_start
# calculate the line positions
if isiterable(line_distance):
lines = line_distance
else:
lines = floatrange(line_start, line_end, inc=line_distance)
# at the end of the layer we will be on the other side of the 2nd direction
end_position = start_position ^ secondary_dir
# the final position will probably be on the other side (primary)
if not zigzag:
end_position ^= primary_dir
# calculate each line
def get_lines(start, end, end_position):
result = []
for line_pos in lines:
result.append(get_fixed_grid_line(start, end, line_pos, z,
step_width=step_width, grid_direction=grid_direction))
if zigzag:
start, end = end, start
end_position ^= primary_dir
return result, end_position
return get_lines(start, end, end_position)
def get_fixed_grid(bounds, layer_distance, line_distance, step_width=None,
grid_direction=GRID_DIRECTION_X, milling_style=MILLING_STYLE_IGNORE,
start_position=START_Z):
""" Calculate the grid positions for toolpath movements
"""
low, high = bounds.get_absolute_limits()
if isiterable(layer_distance):
layers = layer_distance
elif layer_distance is None:
# useful for DropCutter
layers = [low[2]]
else:
layers = floatrange(low[2], high[2], inc=layer_distance,
reverse=bool(start_position & START_Z))
def get_layers_with_direction(layers):
for layer in layers:
if grid_direction != GRID_DIRECTION_Y:
yield (layer, GRID_DIRECTION_X)
if grid_direction != GRID_DIRECTION_X:
yield (layer, GRID_DIRECTION_Y)
for z, direction in get_layers_with_direction(layers):
result, start_position = get_fixed_grid_layer(low[0], high[0],
low[1], high[1], z, line_distance, step_width=step_width,
grid_direction=direction, milling_style=milling_style,
start_position=start_position)
yield result
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