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
from pycam.Toolpath import Bounds
from pycam import VERSION
import pycam.Physics.ode_physics
import pycam.Toolpath.MotionGrid
# this requires ODE - we import it later, if necessary
#import pycam.Simulation.ODEBlocks
import gtk
......@@ -547,8 +548,9 @@ class ProjectGui:
# connect the "consistency check" and the update-handler with all toolpath settings
for objname in ("PushRemoveStrategy", "ContourPolygonStrategy",
"ContourFollowStrategy", "SurfaceStrategy",
"EngraveStrategy", "GridDirectionX",
"GridDirectionY", "GridDirectionXY"):
"EngraveStrategy", "GridDirectionX", "GridDirectionY",
"GridDirectionXY", "MillingStyleConventional",
"MillingStyleClimb", "MillingStyleIgnore"):
self.gui.get_object(objname).connect("toggled",
self.update_process_controls)
self.gui.get_object(objname).connect("toggled",
......@@ -1318,8 +1320,10 @@ class ProjectGui:
"EngraveOffsetControl")
active_controls = {
"PushRemoveStrategy": ("GridDirectionX", "GridDirectionY",
"GridDirectionXY", "MaxStepDownControl",
"MaterialAllowanceControl", "OverlapPercentControl"),
"GridDirectionXY", "MillingStyleConventional",
"MillingStyleClimb", "MillingStyleIgnore",
"MaxStepDownControl", "MaterialAllowanceControl",
"OverlapPercentControl"),
"ContourPolygonStrategy": ("GridDirectionX", "GridDirectionY",
"GridDirectionXY", "MillingStyleConventional",
"MillingStyleClimb", "MillingStyleIgnore",
......@@ -2303,44 +2307,6 @@ class ProjectGui:
elif action == "delete":
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):
if settings is None:
settings = {}
......@@ -2860,18 +2826,23 @@ class ProjectGui:
# unit size
toolpath_settings.set_unit_size(self.settings.get("unit"))
generator, postprocessor, reverse = self._get_process_details(
process_settings["path_strategy"],
process_settings["path_direction"],
process_settings["milling_style"])
STRATEGY_GENERATORS = {
"PushRemoveStrategy": ("PushCutter", "SimpleCutter"),
"ContourPolygonStrategy": ("PushCutter", "ContourPolygonStrategy"),
"ContourFollowStrategy": ("ContourFollow", "SimpleCutter"),
"SurfaceStrategy": ("DropCutter", "PathAccumulator"),
"EngraveStrategy": ("EngraveCutter", "SimpleCutter")}
generator, postprocessor = STRATEGY_GENERATORS[
process_settings["path_strategy"]]
# process settings
toolpath_settings.set_process_settings(
generator, postprocessor, process_settings["path_direction"],
reverse, process_settings["material_allowance"],
process_settings["material_allowance"],
process_settings["overlap_percent"] / 100.0,
process_settings["step_down"],
process_settings["engrave_offset"])
process_settings["engrave_offset"],
process_settings["milling_style"])
return toolpath_settings
......
......@@ -586,11 +586,11 @@ class ToolpathSettings:
"generator": str,
"postprocessor": str,
"path_direction": str,
"reverse": bool,
"material_allowance": float,
"overlap": float,
"step_down": float,
"engrave_offset": float,
"milling_style": str,
},
}
......@@ -698,20 +698,21 @@ class ToolpathSettings:
return "mm"
def set_process_settings(self, generator, postprocessor, path_direction,
reverse=False, material_allowance=0.0, overlap=0.0,
step_down=1.0, engrave_offset=0.0):
material_allowance=0.0, overlap=0.0, step_down=1.0,
engrave_offset=0.0, milling_style="ignore"):
# TODO: this hack should be somewhere else, I guess
if generator in ("ContourFollow", "EngraveCutter"):
material_allowance = 0.0
milling_style = "ignore"
self.process_settings = {
"generator": generator,
"postprocessor": postprocessor,
"path_direction": path_direction,
"reverse": reverse,
"material_allowance": material_allowance,
"overlap": overlap,
"step_down": step_down,
"engrave_offset": engrave_offset,
"milling_style": milling_style,
}
def get_process_settings(self):
......
......@@ -33,7 +33,7 @@ log = pycam.Utils.log.get_logger()
# We need to use a global function here - otherwise it does not work with
# 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)):
# for now only used for triangular collision detection
last_position = None
......@@ -41,11 +41,10 @@ def _process_one_grid_line((positions, minz, maxz, dim_attrs, model, cutter,
height_exceeded = False
for x, y in positions:
if physics:
result = get_max_height_ode(physics, x, y, minz, maxz,
order=dim_attrs[:])
result = get_max_height_ode(physics, x, y, minz, maxz)
else:
result = get_max_height_triangles(model, cutter, x, y, minz, maxz,
order=dim_attrs[:], last_pos=last_position)
last_pos=last_position)
if result:
points.extend(result)
else:
......@@ -95,43 +94,35 @@ class DropCutter:
# remember if we already reported an invalid boundary
self._boundary_warning_already_shown = False
def GenerateToolPath(self, minx, maxx, miny, maxy, minz, maxz, d0, d1,
direction, draw_callback=None):
def GenerateToolPath(self, motion_grid, minz, maxz, draw_callback=None):
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)
num_of_grid_positions = num_of_x_lines * num_of_y_lines
# Transfer the grid (a generator) into a list of lists and count the
# 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)
current_line = 0
self.pa.new_direction(direction)
self.pa.new_direction(0)
self._boundary_warning_already_shown = False
args = []
for one_grid_line in grid:
args.append((one_grid_line, minz, maxz, dim_attrs, self.model,
self.cutter, self.physics, self.model.maxz))
for one_grid_line in lines:
# simplify the data (useful for remote processing)
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
disable_multiprocessing = not self.physics is None
for points, height_exceeded in run_in_parallel(_process_one_grid_line,
......
......@@ -47,50 +47,34 @@ class PushCutter:
self.pa = path_processor
self.physics = physics
def GenerateToolPath(self, minx, maxx, miny, maxy, minz, maxz, dx, dy, dz,
draw_callback=None):
def GenerateToolPath(self, motion_grid, draw_callback=None):
# 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)]
for z in z_steps:
progress_counter = ProgressCounter(num_of_grid_positions, draw_callback)
current_layer = 0
for layer_grid in grid:
# update the progress bar and check, if we should cancel the process
if draw_callback and draw_callback(text="PushCutter: processing" \
+ " layer %d/%d" % (current_layer + 1, num_of_layers)):
# cancel immediately
break
if dy > 0:
self.pa.new_direction(0)
self.GenerateToolPathSlice(minx, maxx, miny, maxy, z, 0, y_step,
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.GenerateToolPathSlice(layer_grid, draw_callback, progress_counter)
self.pa.end_direction()
self.pa.finish()
......@@ -98,8 +82,8 @@ class PushCutter:
return self.pa.paths
def GenerateToolPathSlice(self, minx, maxx, miny, maxy, z, dx, dy,
draw_callback=None, progress_counter=None):
def GenerateToolPathSlice(self, layer_grid, draw_callback=None,
progress_counter=None):
""" only dx or (exclusive!) dy may be bigger than zero
"""
# max_deviation_x = dx/accuracy
......@@ -107,34 +91,15 @@ class PushCutter:
max_depth = 20
# calculate the required number of steps in each direction
if dx > 0:
depth = math.log(accuracy * abs(maxx - minx) / dx) / math.log(2)
depth = max(ceil(depth), 4)
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)
distance = layer_grid[0][-1].sub(layer_grid[0][0]).norm
step_width = distance / len(layer_grid[0])
depth = math.log(accuracy * distance / step_width) / math.log(2)
depth = max(ceil(depth), 4)
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 = []
if dx > 0:
depth = depth_
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)
for line in layer_grid:
p1, p2 = line
args.append((p1, p2, depth, self.model, self.cutter, self.physics))
# ODE does not work with multi-threading
......
......@@ -159,7 +159,7 @@ def get_free_paths_ode(physics, p1, p2, depth=8):
physics.reset_drill()
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
trip_start = 20
safe_z = None
......@@ -196,17 +196,13 @@ def get_max_height_ode(physics, x, y, minz, maxz, order=None):
else:
return [Point(x, y, safe_z)]
def get_max_height_triangles(model, cutter, x, y, minz, maxz, order=None,
last_pos=None):
# TODO: "order" should be replaced with a direction vector
def get_max_height_triangles(model, cutter, x, y, minz, maxz, last_pos=None):
result = []
if last_pos is None:
last_pos = {}
for key in ("triangle", "cut"):
if not key in last_pos:
last_pos[key] = None
if order is None:
order = ["x", "y"]
p = Point(x, y, maxz)
height_max = None
cut_max = None
......
......@@ -26,6 +26,7 @@ from pycam.Geometry.utils import number
import pycam.PathProcessors
import pycam.Cutters
import pycam.Toolpath.SupportGrid
import pycam.Toolpath.MotionGrid
import pycam.Geometry.Model
from pycam.Utils import ProgressCounter
import pycam.Utils.log
......@@ -45,14 +46,12 @@ def generate_toolpath_from_settings(model, tp_settings, callback=None):
process = tp_settings.get_process_settings()
grid = tp_settings.get_support_grid()
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(),
bounds_low, bounds_high, process["path_direction"],
tp_settings.get_bounds(), process["path_direction"],
process["generator"], process["postprocessor"],
process["reverse"],
process["material_allowance"], process["overlap"],
process["step_down"], process["engrave_offset"],
process["milling_style"],
grid["type"], grid["distance_x"], grid["distance_y"],
grid["thickness"], grid["height"], grid["offset_x"],
grid["offset_y"], grid["adjustments_x"], grid["adjustments_y"],
......@@ -60,10 +59,10 @@ def generate_toolpath_from_settings(model, tp_settings, callback=None):
backend, callback)
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",
reverse=False,
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_distance_y=None, support_grid_thickness=None,
support_grid_height=None, support_grid_offset_x=None,
......@@ -127,15 +126,13 @@ def generate_toolpath(model, tool_settings=None,
overlap = number(overlap)
step_down = number(step_down)
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
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)
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]
# trimesh model or contour model?
if isinstance(model, pycam.Geometry.Model.Model):
......@@ -259,42 +256,38 @@ def generate_toolpath(model, tool_settings=None,
for next_model in trimesh_models[1:]:
combined_models += next_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):
return generator
if (overlap < 0) or (overlap >= 1):
return "Invalid overlap value (%f): should be greater or equal 0 " \
+ "and lower than 1"
# 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 direction == "x":
direction_param = 0
elif direction == "y":
direction_param = 1
layer_distance = None
else:
return "Invalid direction value (%s): not one of %s" \
% (direction, DIRECTIONS)
toolpath = generator.GenerateToolPath(minx, maxx, miny, maxy, minz,
maxz, stepping, stepping, direction_param, callback)
layer_distance = step_down
direction_dict = {"x": pycam.Toolpath.MotionGrid.GRID_DIRECTION_X,
"y": pycam.Toolpath.MotionGrid.GRID_DIRECTION_Y,
"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":
if step_down > 0:
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)
toolpath = generator.GenerateToolPath(motion_grid, callback)
elif path_generator == "EngraveCutter":
if step_down > 0:
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