Commit c6f0edc9 authored by sumpfralle's avatar sumpfralle

moved the toolpath generation code to a separate module

renamed configuration setting "overlap" to "overlap_percent"


git-svn-id: https://pycam.svn.sourceforge.net/svnroot/pycam/trunk@326 bbaffbd6-741e-11dd-a85d-61de82d9cad9
parent 409a2478
Version 0.2.5 - NOT RELEASED
* changed name of configuration setting "overlap" to "overlap_percent"
(you may need to change this name in your custom config files)
Version 0.2.4 - 2010-04-12 Version 0.2.4 - 2010-04-12
* added a simple simulation mode for visualizing the material penetration of a toolpath * added a simple simulation mode for visualizing the material penetration of a toolpath
* join tangential moves (removes the inner points in a colinear set of adjacent path points) * join tangential moves (removes the inner points in a colinear set of adjacent path points)
......
...@@ -9,15 +9,17 @@ class BaseCutter: ...@@ -9,15 +9,17 @@ class BaseCutter:
id = 0 id = 0
vertical = Point(0,0,-1) vertical = Point(0,0,-1)
def __init__(self, radius, location=None, height=10): def __init__(self, radius, location=None, height=None):
if location is None: if location is None:
location = Point(0, 0, 0) location = Point(0, 0, 0)
self.location = location self.location = location
if height is None:
height = 10
self.height = height
self.id = BaseCutter.id self.id = BaseCutter.id
BaseCutter.id += 1 BaseCutter.id += 1
self.radius = radius self.radius = radius
self.radiussq = radius*radius self.radiussq = radius*radius
self.height = height
self.required_distance = 0 self.required_distance = 0
self.distance_radius = self.radius self.distance_radius = self.radius
self.distance_radiussq = self.distance_radius * self.distance_radius self.distance_radiussq = self.distance_radius * self.distance_radius
......
...@@ -5,3 +5,34 @@ from BaseCutter import BaseCutter ...@@ -5,3 +5,34 @@ from BaseCutter import BaseCutter
from SphericalCutter import SphericalCutter from SphericalCutter import SphericalCutter
from CylindricalCutter import CylindricalCutter from CylindricalCutter import CylindricalCutter
from ToroidalCutter import ToroidalCutter from ToroidalCutter import ToroidalCutter
def get_tool_from_settings(tool_settings, height=None):
""" get the tool specified by the relevant settings
The settings must include:
- "shape": one of "SphericalCutter", "CylindricalCutter" and
"ToroidalCutter"
- "radius": the tool radius
The following settings are optional or shape specific:
- "torus_radius": necessary for ToroidalCutter
@type tool_settings: dict
@value tool_settings: contains the attributes of the tool
@type height: float
@value height: the height of the tool
@rtype: BaseCutter | basestring
@return: a tool object or an error string
"""
cuttername = tool_settings["shape"]
radius = tool_settings["radius"]
if cuttername == "SphericalCutter":
return SphericalCutter(radius, height=height)
elif cuttername == "CylindricalCutter":
return CylindricalCutter(radius, height=height)
elif cuttername == "ToroidalCutter":
toroid = tool_settings["torus_radius"]
return ToroidalCutter(radius, toroid, height=height)
else:
return "Invalid cutter shape: '%s' is not one of %s" % (cuttername, TOOL_SHAPES)
...@@ -7,8 +7,7 @@ import pycam.Exporters.EMCToolExporter ...@@ -7,8 +7,7 @@ import pycam.Exporters.EMCToolExporter
import pycam.Gui.Settings import pycam.Gui.Settings
import pycam.Gui.common as GuiCommon import pycam.Gui.common as GuiCommon
import pycam.Cutters import pycam.Cutters
import pycam.PathGenerators import pycam.Toolpath.Generator
import pycam.PathProcessors
import pycam.Toolpath import pycam.Toolpath
import pycam.Geometry.utils as utils import pycam.Geometry.utils as utils
from pycam.Gui.OpenGLTools import ModelViewWindowGL from pycam.Gui.OpenGLTools import ModelViewWindowGL
...@@ -152,7 +151,6 @@ class ProjectGui: ...@@ -152,7 +151,6 @@ class ProjectGui:
# set defaults # set defaults
self.model = None self.model = None
self.toolpath = pycam.Toolpath.ToolPathList() self.toolpath = pycam.Toolpath.ToolPathList()
self._physics_cache = None
self.cutter = None self.cutter = None
self.process_list = [] self.process_list = []
self.tool_list = [] self.tool_list = []
...@@ -402,14 +400,6 @@ class ProjectGui: ...@@ -402,14 +400,6 @@ class ProjectGui:
self.view3d.glsetup() self.view3d.glsetup()
self.view3d.paint() self.view3d.paint()
def get_physics(self, cutter):
if self.settings.get("enable_ode"):
self._physics_cache = pycam.Physics.ode_physics.generate_physics(self.model,
cutter, self._physics_cache)
else:
self._physics_cache = None
return self._physics_cache
def update_save_actions(self): def update_save_actions(self):
self.gui.get_object("SaveTaskSettings").set_sensitive(not self.last_task_settings_file is None) self.gui.get_object("SaveTaskSettings").set_sensitive(not self.last_task_settings_file is None)
self.gui.get_object("SaveModel").set_sensitive(not self.last_model_file is None) self.gui.get_object("SaveModel").set_sensitive(not self.last_model_file is None)
...@@ -447,7 +437,7 @@ class ProjectGui: ...@@ -447,7 +437,7 @@ class ProjectGui:
lines.append(tool_desc) lines.append(tool_desc)
lines.append("Speed: %d/minute / Feedrate: %d%s/minute" % (tool["speed"], tool["feedrate"], unit)) lines.append("Speed: %d/minute / Feedrate: %d%s/minute" % (tool["speed"], tool["feedrate"], unit))
lines.append("Path: %s / %s" % (process["path_generator"], process["path_postprocessor"])) lines.append("Path: %s / %s" % (process["path_generator"], process["path_postprocessor"]))
lines.append("Overlap: %d%%" % process["overlap"]) lines.append("Overlap: %d%%" % process["overlap_percent"])
lines.append("Material allowance: %.2f%s" % (process["material_allowance"], unit)) lines.append("Material allowance: %.2f%s" % (process["material_allowance"], unit))
if process["path_generator"] == "PushCutter": if process["path_generator"] == "PushCutter":
lines.append("Maximum step down: %.2f%s" % (process["step_down"], unit)) lines.append("Maximum step down: %.2f%s" % (process["step_down"], unit))
...@@ -1174,7 +1164,7 @@ class ProjectGui: ...@@ -1174,7 +1164,7 @@ class ProjectGui:
return name return name
settings["path_postprocessor"] = get_path_postprocessor() settings["path_postprocessor"] = get_path_postprocessor()
for objname, key in (("SafetyHeightControl", "safety_height"), for objname, key in (("SafetyHeightControl", "safety_height"),
("OverlapPercentControl", "overlap"), ("OverlapPercentControl", "overlap_percent"),
("MaterialAllowanceControl", "material_allowance"), ("MaterialAllowanceControl", "material_allowance"),
("MaxStepDownControl", "step_down")): ("MaxStepDownControl", "step_down")):
settings[key] = self.gui.get_object(objname).get_value() settings[key] = self.gui.get_object(objname).get_value()
...@@ -1197,7 +1187,7 @@ class ProjectGui: ...@@ -1197,7 +1187,7 @@ class ProjectGui:
self.gui.get_object(value).set_active(True) self.gui.get_object(value).set_active(True)
set_path_postprocessor(settings["path_postprocessor"]) set_path_postprocessor(settings["path_postprocessor"])
for objname, key in (("SafetyHeightControl", "safety_height"), for objname, key in (("SafetyHeightControl", "safety_height"),
("OverlapPercentControl", "overlap"), ("OverlapPercentControl", "overlap_percent"),
("MaterialAllowanceControl", "material_allowance"), ("MaterialAllowanceControl", "material_allowance"),
("MaxStepDownControl", "step_down")): ("MaxStepDownControl", "step_down")):
self.gui.get_object(objname).set_value(settings[key]) self.gui.get_object(objname).set_value(settings[key])
...@@ -1314,8 +1304,8 @@ class ProjectGui: ...@@ -1314,8 +1304,8 @@ class ProjectGui:
# columns: name, visible, drill_size, drill_id, allowance, speed, feedrate # columns: name, visible, drill_size, drill_id, allowance, speed, feedrate
for index in range(len(self.toolpath)): for index in range(len(self.toolpath)):
tp = self.toolpath[index] tp = self.toolpath[index]
items = (index, tp.name, tp.visible, tp.drill_size, items = (index, tp.name, tp.visible, tp.tool_settings["radius"],
tp.drill_id, tp.material_allowance, tp.speed, tp.feedrate) tp.tool_id, tp.material_allowance, tp.speed, tp.feedrate)
model.append(items) model.append(items)
if not new_index is None: if not new_index is None:
self._treeview_set_active_index(self.toolpath_table, new_index) self._treeview_set_active_index(self.toolpath_table, new_index)
...@@ -1346,58 +1336,6 @@ class ProjectGui: ...@@ -1346,58 +1336,6 @@ class ProjectGui:
if not settings.write_to_file(filename, self.tool_list, self.process_list, self.task_list) and not no_dialog and not self.no_dialog: if not settings.write_to_file(filename, self.tool_list, self.process_list, self.task_list) and not no_dialog and not self.no_dialog:
show_error_dialog(self.window, "Failed to save settings file") show_error_dialog(self.window, "Failed to save settings file")
def get_tool_instance(self, tool_settings):
cutter_height = self.settings.get("maxz") - self.settings.get("minz")
if self.model:
cutter_height = max(cutter_height, self.model.maxz - self.model.minz)
# 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
cuttername = tool_settings["shape"]
radius = tool_settings["tool_radius"]
if cuttername == "SphericalCutter":
cutter = pycam.Cutters.SphericalCutter(radius, height=cutter_height)
elif cuttername == "CylindricalCutter":
cutter = pycam.Cutters.CylindricalCutter(radius, height=cutter_height)
elif cuttername == "ToroidalCutter":
toroid = tool_settings["torus_radius"]
cutter = pycam.Cutters.ToroidalCutter(radius, toroid, height=cutter_height)
else:
pass
return cutter
def get_pathgenerator_instance(self, cutter, process_settings):
pathgenerator = process_settings["path_generator"]
pathprocessor = process_settings["path_postprocessor"]
cutter.set_required_distance(process_settings["material_allowance"])
physics = self.get_physics(cutter)
if pathgenerator == "DropCutter":
if pathprocessor == "ZigZagCutter":
processor = pycam.PathProcessors.PathAccumulator(zigzag=True)
else:
processor = None
result = pycam.PathGenerators.DropCutter(cutter,
self.model, processor, physics=physics,
safety_height=self.settings.get("safety_height"))
elif pathgenerator == "PushCutter":
if pathprocessor == "PathAccumulator":
processor = pycam.PathProcessors.PathAccumulator()
elif pathprocessor == "SimpleCutter":
processor = pycam.PathProcessors.SimpleCutter()
elif pathprocessor == "ZigZagCutter":
processor = pycam.PathProcessors.ZigZagCutter()
elif pathprocessor == "PolygonCutter":
processor = pycam.PathProcessors.PolygonCutter()
elif pathprocessor == "ContourCutter":
processor = pycam.PathProcessors.ContourCutter()
else:
processor = None
result = pycam.PathGenerators.PushCutter(cutter,
self.model, processor, physics=physics)
else:
result = None
return result
def toggle_progress_bar(self, status): def toggle_progress_bar(self, status):
if status: if status:
self.task_pane.set_sensitive(False) self.task_pane.set_sensitive(False)
...@@ -1435,6 +1373,8 @@ class ProjectGui: ...@@ -1435,6 +1373,8 @@ class ProjectGui:
else: else:
toolpath = self.toolpath[toolpath_index] toolpath = self.toolpath[toolpath_index]
paths = toolpath.get_path() paths = toolpath.get_path()
# set the current cutter
self.cutter = pycam.Cutters.get_tool_from_settings(toolpath.tool_settings)
# calculate steps # calculate steps
detail_level = self.gui.get_object("SimulationDetailsValue").get_value() detail_level = self.gui.get_object("SimulationDetailsValue").get_value()
grid_size = 100 * pow(2, detail_level - 1) grid_size = 100 * pow(2, detail_level - 1)
...@@ -1443,7 +1383,7 @@ class ProjectGui: ...@@ -1443,7 +1383,7 @@ class ProjectGui:
x_steps = int(math.sqrt(grid_size) * proportion) x_steps = int(math.sqrt(grid_size) * proportion)
y_steps = int(math.sqrt(grid_size) / proportion) y_steps = int(math.sqrt(grid_size) / proportion)
simulation_backend = pycam.Simulation.ODEBlocks.ODEBlocks( simulation_backend = pycam.Simulation.ODEBlocks.ODEBlocks(
toolpath.drill, toolpath.bounding_box, toolpath.tool_settings, toolpath.bounding_box,
x_steps=x_steps, y_steps=y_steps) x_steps=x_steps, y_steps=y_steps)
self.settings.set("simulation_object", simulation_backend) self.settings.set("simulation_object", simulation_backend)
# disable the simulation widget (avoids confusion regarding "cancel") # disable the simulation widget (avoids confusion regarding "cancel")
...@@ -1516,10 +1456,13 @@ class ProjectGui: ...@@ -1516,10 +1456,13 @@ class ProjectGui:
callback = None callback = None
draw_callback = UpdateView(callback, draw_callback = UpdateView(callback,
max_fps=self.settings.get("drill_progress_max_fps")).update max_fps=self.settings.get("drill_progress_max_fps")).update
direction = process_settings["path_direction"]
self.update_progress_bar("Generating collision model") self.update_progress_bar("Generating collision model")
self.cutter = self.get_tool_instance(tool_settings)
if self.settings.get("enable_ode"):
calculation_backend = "ODE"
else:
calculation_backend = None
# this offset allows to cut a model with a minimal boundary box correctly # this offset allows to cut a model with a minimal boundary box correctly
offset = tool_settings["tool_radius"] / 2.0 offset = tool_settings["tool_radius"] / 2.0
...@@ -1543,6 +1486,7 @@ class ProjectGui: ...@@ -1543,6 +1486,7 @@ class ProjectGui:
maxy = float(self.settings.get("maxy"))+offset maxy = float(self.settings.get("maxy"))+offset
minz = float(self.settings.get("minz")) minz = float(self.settings.get("minz"))
maxz = float(self.settings.get("maxz")) maxz = float(self.settings.get("maxz"))
bounds = (minx, maxx, miny, maxy, minz, maxz)
# check if the boundary limits are valid # check if the boundary limits are valid
if (minx > maxx) or (miny > maxy) or (minz > maxz): if (minx > maxx) or (miny > maxy) or (minz > maxz):
...@@ -1551,70 +1495,57 @@ class ProjectGui: ...@@ -1551,70 +1495,57 @@ class ProjectGui:
show_error_dialog(self.window, "Processing boundaries are too small for this tool size.") show_error_dialog(self.window, "Processing boundaries are too small for this tool size.")
return True return True
effective_toolradius = tool_settings["tool_radius"] * (1.0 - process_settings["overlap"] / 100.0)
x_shift = effective_toolradius
y_shift = effective_toolradius
self.update_progress_bar("Starting the toolpath generation") self.update_progress_bar("Starting the toolpath generation")
# put the tool settings together
tool_dict = {"shape": tool_settings["shape"],
"radius": tool_settings["tool_radius"],
"torus_radius": tool_settings["torus_radius"],
}
# run the toolpath generation
toolpath = pycam.Toolpath.Generator.generate_toolpath(self.model,
tool_dict, bounds=bounds,
direction=process_settings["path_direction"],
path_generator=process_settings["path_generator"],
path_postprocessor=process_settings["path_postprocessor"],
material_allowance=process_settings["material_allowance"],
safety_height=process_settings["safety_height"],
overlap=process_settings["overlap_percent"] / 100.0,
step_down=process_settings["step_down"],
calculation_backend=calculation_backend, callback=draw_callback)
pathgenerator = self.get_pathgenerator_instance(self.cutter, process_settings) print "Time elapsed: %f" % (time.time() - start_time)
pathgenerator_name = process_settings["path_generator"]
if pathgenerator_name == "DropCutter":
dx = x_shift
dy = y_shift
if direction == "x":
toolpath = pathgenerator.GenerateToolPath(minx, maxx, miny, maxy, minz, maxz, dx, dy, 0, draw_callback)
elif direction == "y":
toolpath = pathgenerator.GenerateToolPath(minx, maxx, miny, maxy, minz, maxz, dy, dx, 1, draw_callback)
elif pathgenerator_name == "PushCutter": if isinstance(toolpath, basestring):
if process_settings["path_postprocessor"] == "ContourCutter": # an error occoured - "toolpath" contains the error message
dx = x_shift message = "Failed to generate toolpath: %s" % toolpath
else: if not self.no_dialog:
dx = utils.INFINITE show_error_dialog(self.window, message)
dy = y_shift
if process_settings["step_down"] > 0:
dz = process_settings["step_down"]
else: else:
dz = utils.INFINITE print >>sys.stderr, message
if direction == "x": # we were not successful (similar to a "cancel" request)
toolpath = pathgenerator.GenerateToolPath(minx, maxx, miny, maxy, minz, maxz, 0, dy, dz, draw_callback) return False
elif direction == "y":
toolpath = pathgenerator.GenerateToolPath(minx, maxx, miny, maxy, minz, maxz, dy, 0, dz, draw_callback)
elif direction == "xy":
toolpath = pathgenerator.GenerateToolPath(minx, maxx, miny, maxy, minz, maxz, dy, dy, dz, draw_callback)
print "Time elapsed: %f" % (time.time() - start_time)
# calculate the z offset for the starting position
# TODO: fix these hard-coded offsets; maybe use the safety height instead?
if self.settings.get("unit") == 'mm':
start_offset = 7.0
else: else:
start_offset = 0.25 # hide the previous toolpath if it is the only visible one (automatic mode)
# hide the previous toolpath if it is the only visible one (automatic mode) if (len([True for path in self.toolpath if path.visible]) == 1) \
if (len([True for path in self.toolpath if path.visible]) == 1) \ and self.toolpath[-1].visible:
and self.toolpath[-1].visible: self.toolpath[-1].visible = False
self.toolpath[-1].visible = False # add the new toolpath
# add the new toolpath description = "%s / %s" % (tool_settings["name"], process_settings["name"])
description = "%s / %s" % (tool_settings["name"], process_settings["name"]) # the tool id numbering should start with 1 instead of zero
# the tool id numbering should start with 1 instead of zero tool_id = self.tool_list.index(tool_settings) + 1
tool_id = self.tool_list.index(tool_settings) + 1 self.toolpath.add_toolpath(toolpath,
bounding_box = (self.settings.get("minx"), self.settings.get("maxx"), description, tool_dict, tool_id,
self.settings.get("miny"), self.settings.get("maxy"), tool_settings["speed"],
self.settings.get("minz"), self.settings.get("maxz")) tool_settings["feedrate"],
self.toolpath.add_toolpath(toolpath, process_settings["material_allowance"],
description, self.cutter, tool_id, process_settings["safety_height"],
tool_settings["speed"], self.settings.get("unit"),
tool_settings["feedrate"], minx, miny, process_settings["safety_height"],
process_settings["material_allowance"], bounds)
process_settings["safety_height"], self.update_toolpath_table()
self.settings.get("unit"), self.update_view()
minx, miny, maxz + start_offset, # return "False" if the action was cancelled
bounding_box) return not self._progress_cancel_requested
self.update_toolpath_table()
self.update_view()
# return "False" if the action was cancelled
return not self._progress_cancel_requested
def get_filename_via_dialog(self, title, mode_load=False, type_filter=None): def get_filename_via_dialog(self, title, mode_load=False, type_filter=None):
# we open a dialog # we open a dialog
...@@ -1727,7 +1658,7 @@ class ProjectGui: ...@@ -1727,7 +1658,7 @@ class ProjectGui:
pycam.Exporters.SimpleGCodeExporter.ExportPathList(destination, pycam.Exporters.SimpleGCodeExporter.ExportPathList(destination,
tp.toolpath, tp.unit, tp.toolpath, tp.unit,
tp.start_x, tp.start_y, tp.start_z, tp.start_x, tp.start_y, tp.start_z,
tp.feedrate, tp.speed, tp.safety_height, tp.drill_id, tp.feedrate, tp.speed, tp.safety_height, tp.tool_id,
finish_program=is_last_loop) finish_program=is_last_loop)
destination.close() destination.close()
if self.no_dialog: if self.no_dialog:
......
...@@ -120,7 +120,7 @@ path_generator: PushCutter ...@@ -120,7 +120,7 @@ path_generator: PushCutter
path_postprocessor: PolygonCutter path_postprocessor: PolygonCutter
material_allowance: 0.5 material_allowance: 0.5
step_down: 0.8 step_down: 0.8
overlap: 0 overlap_percent: 0
[Process1] [Process1]
name: Semi-finish name: Semi-finish
...@@ -128,7 +128,7 @@ path_generator: PushCutter ...@@ -128,7 +128,7 @@ path_generator: PushCutter
path_postprocessor: ContourCutter path_postprocessor: ContourCutter
material_allowance: 0.2 material_allowance: 0.2
step_down: 0.5 step_down: 0.5
overlap: 20 overlap_percent: 20
[Process2] [Process2]
name: Finish name: Finish
...@@ -136,7 +136,7 @@ path_generator: DropCutter ...@@ -136,7 +136,7 @@ path_generator: DropCutter
path_postprocessor: ZigZagCutter path_postprocessor: ZigZagCutter
material_allowance: 0.0 material_allowance: 0.0
step_down: 1.0 step_down: 1.0
overlap: 60 overlap_percent: 60
[TaskDefault] [TaskDefault]
enabled: 1 enabled: 1
...@@ -166,7 +166,7 @@ process: 2 ...@@ -166,7 +166,7 @@ process: 2
"path_postprocessor": str, "path_postprocessor": str,
"safety_height": float, "safety_height": float,
"material_allowance": float, "material_allowance": float,
"overlap": int, "overlap_percent": int,
"step_down": float, "step_down": float,
"tool": object, "tool": object,
"process": object, "process": object,
...@@ -182,7 +182,7 @@ process: 2 ...@@ -182,7 +182,7 @@ process: 2
CATEGORY_KEYS = { CATEGORY_KEYS = {
"tool": ("name", "shape", "tool_radius", "torus_radius", "feedrate", "speed"), "tool": ("name", "shape", "tool_radius", "torus_radius", "feedrate", "speed"),
"process": ("name", "path_generator", "path_postprocessor", "path_direction", "process": ("name", "path_generator", "path_postprocessor", "path_direction",
"safety_height", "material_allowance", "overlap", "step_down"), "safety_height", "material_allowance", "overlap_percent", "step_down"),
"task": ("tool", "process", "enabled"), "task": ("tool", "process", "enabled"),
} }
......
import pycam.Cutters
from pycam.Geometry.Point import Point from pycam.Geometry.Point import Point
import ode import ode
try: try:
...@@ -8,8 +9,8 @@ except: ...@@ -8,8 +9,8 @@ except:
class ODEBlocks: class ODEBlocks:
def __init__(self, cutter, (minx, maxx, miny, maxy, minz, maxz), x_steps=None, y_steps=None): def __init__(self, tool_settings, (minx, maxx, miny, maxy, minz, maxz), x_steps=None, y_steps=None):
self.cutter = cutter self.cutter = pycam.Cutters.get_tool_from_settings(tool_settings)
# we don't want to use the "material allowance" distance # we don't want to use the "material allowance" distance
self.cutter.set_required_distance(0) self.cutter.set_required_distance(0)
dimx = maxx - minx dimx = maxx - minx
......
import pycam.PathGenerators
import pycam.PathProcessors
import pycam.Cutters
import sys
DIRECTIONS = frozenset(("x", "y", "xy"))
PATH_GENERATORS = frozenset(("DropCutter", "PushCutter"))
PATH_POSTPROCESSORS = frozenset(("ContourCutter", "PathAccumulator", "PolygonCutter", "SimpleCutter", "ZigZagCutter"))
CALCULATION_BACKENDS = frozenset((None, "ODE"))
def generate_toolpath(model, tool_settings=None, bounds=None, direction="x",
path_generator="DropCutter", path_postprocessor="ZigZagCutter",
material_allowance=0.0, safety_height=None, overlap=0.0,
step_down=0.0, calculation_backend=None, callback=None):
""" abstract interface for generating a toolpath
@type model: pycam.Geometry.Model.Model
@value model: a model contains the surface triangles
@type direction: str
@value direction: any member of the DIRECTIONS set (e.g. "x", "y" or "xy")
@type bounds: tuple(float) | list(float)
@value bounds: the processing boundary (relative to the center of the tool)
(order: minx, maxx, miny, maxy, minz, maxz)
@type tool_settings: dict
@value tool_settings: contains at least the following keys (depending on
the tool type):
"shape": any of possible cutter shape (see "pycam.Cutters")
"radius": main radius of the tools
"torus_radius": (only for ToroidalCutter) second toroidal radius
@type path_generator: str
@value path_generator: any member of the PATH_GENERATORS set
@type path_postprocessor: str
@value path_postprocessor: any member of the PATH_POSTPROCESSORS set
@type material_allowance: float
@value material_allowance: the minimum distance between the tool and the model
@type overlap: float
@value overlap: the overlap between two adjacent tool paths (0 <= overlap < 1)
@type calculation_backend: str | None
@value calculation_backend: any member of the CALCULATION_BACKENDS set
The default is the triangular collision detection.
@rtype: pycam.Toolpath.ToolPath | str
@return: the resulting toolpath object or an error string in case of invalid
arguments
"""
if bounds is None:
# no bounds were given - we use the boundaries of the model
minx, maxx = model.minx, model.maxx
miny, maxy = model.miny, model.maxy
minz, maxz = model.minz, model.maxz
else:
minx, maxx, miny, maxy, minz, maxz = bounds
# 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 = pycam.Cutters.get_tool_from_settings(tool_settings, cutter_height)
if isinstance(cutter, basestring):
return cutter
cutter.set_required_distance(material_allowance)
physics = _get_physics(cutter, calculation_backend)
if isinstance(physics, basestring):
return physics
generator = _get_pathgenerator_instance(model, cutter, path_generator, path_postprocessor, material_allowance, safety_height, 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"
effective_toolradius = tool_settings["radius"] * (1.0 - overlap)
if path_generator == "DropCutter":
if direction == "x":
direction_param = 0
elif direction == "y":
direction_param = 1
else:
return "Invalid direction value (%s): not one of %s" % (direction, DIRECTIONS)
toolpath = generator.GenerateToolPath(minx, maxx, miny, maxy, minz, maxz,
effective_toolradius, effective_toolradius, direction_param, callback)
else:
if step_down > 0:
dz = step_down
else:
dz = maxz - minz
if direction == "x":
dx, dy = 0, effective_toolradius
elif direction == "y":
dx, dy = effective_toolradius, 0
elif direction == "xy":
dx, dy = effective_toolradius, effective_toolradius
else:
return "Invalid direction (%s): not one of %s" % (direction, DIRECTIONS)
toolpath = generator.GenerateToolPath(minx, maxx, miny, maxy, minz, maxz, dx, dy, dz, callback)
return toolpath
def _get_pathgenerator_instance(model, cutter, pathgenerator, pathprocessor,
material_allowance, safety_height, physics):
if pathgenerator == "DropCutter":
if pathprocessor == "ZigZagCutter":
processor = pycam.PathProcessors.PathAccumulator(zigzag=True)
elif pathprocessor == "PathAccumulator":
processor = pycam.PathProcessors.PathAccumulator()
else:
return "Invalid postprocessor (%s) for 'DropCutter': only 'ZigZagCutter' or 'PathAccumulator' are allowed" % str(pathprocessor)
return pycam.PathGenerators.DropCutter(cutter,
model, processor, physics=physics,
safety_height=safety_height)
elif pathgenerator == "PushCutter":
if pathprocessor == "PathAccumulator":
processor = pycam.PathProcessors.PathAccumulator()
elif pathprocessor == "SimpleCutter":
processor = pycam.PathProcessors.SimpleCutter()
elif pathprocessor == "ZigZagCutter":
processor = pycam.PathProcessors.ZigZagCutter()
elif pathprocessor == "PolygonCutter":
processor = pycam.PathProcessors.PolygonCutter()
elif pathprocessor == "ContourCutter":
processor = pycam.PathProcessors.ContourCutter()
else:
return "Invalid postprocessor (%s) for 'PushCutter' - it should be one of these: %s" % (processor, PATH_POSTPROCESSORS)
return pycam.PathGenerators.PushCutter(cutter,
model, processor, physics=physics)
else:
return "Invalid path generator (%s): not one of %s" % (pathgenerator, PATH_GENERATORS)
def _get_physics(cutter, calculation_backend):
if calculation_backend is None:
# triangular collision detection does not need any physical model
return None
elif calculation_backend == "ODE":
import pycam.Physics.ode_physics
return pycam.Physics.ode_physics.generate_physics(model, cutter)
else:
return "Invalid calculation backend (%s): not one of %s" % (calculation_backend, CALCULATION_BACKENDS)
__all__ = ["ToolPathList", "ToolPath", "Generator"]
import random import random
class ToolPathList(list): class ToolPathList(list):
def add_toolpath(self, toolpath, name, cutter, *args): def add_toolpath(self, toolpath, name, tool_settings, *args):
self.append(ToolPath(toolpath, name, cutter, *args)) self.append(ToolPath(toolpath, name, tool_settings, *args))
class ToolPath: class ToolPath:
def __init__(self, toolpath, name, cutter, drill_id, speed, feedrate, def __init__(self, toolpath, name, tool_settings, tool_id, speed,
material_allowance, safety_height, unit, start_x, start_y, start_z, bounding_box): feedrate, material_allowance, safety_height, unit, start_x,
start_y, start_z, bounding_box):
self.toolpath = toolpath self.toolpath = toolpath
self.name = name self.name = name
self.visible = True self.visible = True
self.drill_id = drill_id self.tool_id = tool_id
self.drill = cutter self.tool_settings = tool_settings
self.drill_size = cutter.radius
self.speed = speed self.speed = speed
self.feedrate = feedrate self.feedrate = feedrate
self.material_allowance = material_allowance self.material_allowance = material_allowance
......
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