Commit 83a69d40 authored by sumpfralle's avatar sumpfralle

added a separate class for handling toolpath settings

 * this will ease the repeatition of previous toolpath generations
added meta data to the STL and GCode exporters


git-svn-id: https://pycam.svn.sourceforge.net/svnroot/pycam/trunk@394 bbaffbd6-741e-11dd-a85d-61de82d9cad9
parent aef44e26
...@@ -47,7 +47,7 @@ def get_tool_from_settings(tool_settings, height=None): ...@@ -47,7 +47,7 @@ def get_tool_from_settings(tool_settings, height=None):
@return: a tool object or an error string @return: a tool object or an error string
""" """
cuttername = tool_settings["shape"] cuttername = tool_settings["shape"]
radius = tool_settings["radius"] radius = tool_settings["tool_radius"]
if cuttername == "SphericalCutter": if cuttername == "SphericalCutter":
return SphericalCutter(radius, height=height) return SphericalCutter(radius, height=height)
elif cuttername == "CylindricalCutter": elif cuttername == "CylindricalCutter":
......
...@@ -26,10 +26,11 @@ import os ...@@ -26,10 +26,11 @@ import os
class STLExporter: class STLExporter:
def __init__(self, model, name="model", created_by="pycam", linesep=None): def __init__(self, model, name="model", created_by="pycam", linesep=None, comment=None):
self.model = model self.model = model
self.name = name self.name = name
self.created_by = created_by self.created_by = created_by
self.comment = comment
if linesep is None: if linesep is None:
self.linesep = os.linesep self.linesep = os.linesep
else: else:
...@@ -46,6 +47,9 @@ class STLExporter: ...@@ -46,6 +47,9 @@ class STLExporter:
def get_output_lines(self): def get_output_lines(self):
date = datetime.date.today().isoformat() date = datetime.date.today().isoformat()
yield """solid "%s"; Produced by %s, %s""" % (self.name, self.created_by, date) yield """solid "%s"; Produced by %s, %s""" % (self.name, self.created_by, date)
if self.comment:
for line in self.comment.split(self.linesep):
yield(";%s" % line)
for tr in self.model.triangles(): for tr in self.model.triangles():
norm = tr.normal().normalize() norm = tr.normal().normalize()
yield "facet normal %f %f %f" % (norm.x, norm.y, norm.z) yield "facet normal %f %f %f" % (norm.x, norm.y, norm.z)
......
...@@ -22,6 +22,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -22,6 +22,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
""" """
from gcode import gcode from gcode import gcode
import os
# simplistic GCode exporter # simplistic GCode exporter
# does each run, and moves the tool to the safetyheight in between # does each run, and moves the tool to the safetyheight in between
...@@ -30,7 +31,7 @@ class SimpleGCodeExporter: ...@@ -30,7 +31,7 @@ class SimpleGCodeExporter:
def __init__(self, destination, unit, startx, starty, startz, feedrate, def __init__(self, destination, unit, startx, starty, startz, feedrate,
speed, safety_height=None, tool_id=1, finish_program=False, speed, safety_height=None, tool_id=1, finish_program=False,
max_skip_safety_distance=None): max_skip_safety_distance=None, comment=None):
self._last_path_point = None self._last_path_point = None
self._max_skip_safety_distance = max_skip_safety_distance self._max_skip_safety_distance = max_skip_safety_distance
if isinstance(destination, basestring): if isinstance(destination, basestring):
...@@ -43,6 +44,8 @@ class SimpleGCodeExporter: ...@@ -43,6 +44,8 @@ class SimpleGCodeExporter:
self.destination = destination self.destination = destination
# don't close the stream if we did not open it on our own # don't close the stream if we did not open it on our own
self._close_stream_on_exit = False self._close_stream_on_exit = False
if comment:
self.add_comment(comment)
if unit == "mm": if unit == "mm":
self.destination.write("G21\n") self.destination.write("G21\n")
else: else:
...@@ -69,6 +72,10 @@ class SimpleGCodeExporter: ...@@ -69,6 +72,10 @@ class SimpleGCodeExporter:
distance = new_point.sub(self._last_path_point).norm() distance = new_point.sub(self._last_path_point).norm()
return distance <= self._max_skip_safety_distance return distance <= self._max_skip_safety_distance
def add_comment(self, comment):
for line in comment.split(os.linesep):
self.destination.write(";%s\n" % line)
def AddPath(self, path): def AddPath(self, path):
gc = self.gcode gc = self.gcode
point = path.points[0] point = path.points[0]
......
...@@ -32,6 +32,7 @@ import pycam.Toolpath.Generator ...@@ -32,6 +32,7 @@ import pycam.Toolpath.Generator
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
from pycam import VERSION
import pycam.Physics.ode_physics import pycam.Physics.ode_physics
# 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
...@@ -39,6 +40,7 @@ import gtk ...@@ -39,6 +40,7 @@ import gtk
import ConfigParser import ConfigParser
import math import math
import time import time
import datetime
import re import re
import os import os
import sys import sys
...@@ -112,6 +114,8 @@ class ProjectGui: ...@@ -112,6 +114,8 @@ class ProjectGui:
"along": 0, "along": 0,
"around": 1} "around": 1}
META_DATA_PREFIX = "PYCAM-META-DATA:"
def __init__(self, master=None, no_dialog=False): def __init__(self, master=None, no_dialog=False):
""" TODO: remove "master" above when the Tk interface is abandoned""" """ TODO: remove "master" above when the Tk interface is abandoned"""
self.settings = pycam.Gui.Settings.Settings() self.settings = pycam.Gui.Settings.Settings()
...@@ -1057,7 +1061,7 @@ class ProjectGui: ...@@ -1057,7 +1061,7 @@ class ProjectGui:
return return
try: try:
fi = open(filename, "w") fi = open(filename, "w")
pycam.Exporters.STLExporter.STLExporter(self.model).write(fi) pycam.Exporters.STLExporter.STLExporter(self.model, comment=self.get_meta_data()).write(fi)
fi.close() fi.close()
except IOError, err_msg: except IOError, err_msg:
if not no_dialog and not self.no_dialog: if not no_dialog and not self.no_dialog:
...@@ -1503,9 +1507,13 @@ class ProjectGui: ...@@ -1503,9 +1507,13 @@ 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.tool_settings["radius"], toolpath_settings = tp.get_toolpath_settings()
tp.tool_id, tp.material_allowance, tp.speed, tool = toolpath_settings.get_tool_settings()
tp.feedrate, get_time_string(tp.get_machine_time())) process = toolpath_settings.get_process_settings()
items = (index, tp.name, tp.visible, tool["tool_radius"],
tool["id"], process["material_allowance"],
tool["speed"], tool["feedrate"],
get_time_string(tp.get_machine_time()))
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)
...@@ -1577,7 +1585,8 @@ class ProjectGui: ...@@ -1577,7 +1585,8 @@ class ProjectGui:
toolpath = self.toolpath[toolpath_index] toolpath = self.toolpath[toolpath_index]
paths = toolpath.get_path() paths = toolpath.get_path()
# set the current cutter # set the current cutter
self.cutter = pycam.Cutters.get_tool_from_settings(toolpath.tool_settings) self.cutter = pycam.Cutters.get_tool_from_settings(
toolpath.get_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)
...@@ -1586,7 +1595,7 @@ class ProjectGui: ...@@ -1586,7 +1595,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.tool_settings, toolpath.bounding_box, toolpath.get_tool_settings(), toolpath.get_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")
...@@ -1666,10 +1675,47 @@ class ProjectGui: ...@@ -1666,10 +1675,47 @@ class ProjectGui:
self.update_progress_bar("Generating collision model") self.update_progress_bar("Generating collision model")
if self.settings.get("enable_ode"): # turn the toolpath settings into a dict
calculation_backend = "ODE" toolpath_settings = self.get_toolpath_settings(tool_settings, process_settings)
if toolpath_settings is None:
# behave as if "cancel" was requested
return True
self.cutter = toolpath_settings.get_tool()
# run the toolpath generation
self.update_progress_bar("Starting the toolpath generation")
toolpath = pycam.Toolpath.Generator.generate_toolpath_from_settings(
self.model, toolpath_settings, callback=draw_callback)
print "Time elapsed: %f" % (time.time() - start_time)
if isinstance(toolpath, basestring):
# an error occoured - "toolpath" contains the error message
message = "Failed to generate toolpath: %s" % toolpath
if not self.no_dialog:
show_error_dialog(self.window, message)
else:
print >>sys.stderr, message
# we were not successful (similar to a "cancel" request)
return False
else: else:
calculation_backend = None # 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) \
and self.toolpath[-1].visible:
self.toolpath[-1].visible = False
# add the new toolpath
description = "%s / %s" % (tool_settings["name"],
process_settings["name"])
# the tool id numbering should start with 1 instead of zero
self.toolpath.add_toolpath(toolpath, description, toolpath_settings)
self.update_toolpath_table()
self.update_view()
# return "False" if the action was cancelled
return not self._progress_cancel_requested
def get_toolpath_settings(self, tool_settings, process_settings):
toolpath_settings = pycam.Gui.Settings.ToolpathSettings()
# 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
...@@ -1693,79 +1739,46 @@ class ProjectGui: ...@@ -1693,79 +1739,46 @@ 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) toolpath_settings.set_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):
# don't generate a toolpath if the area is too small (e.g. due to the tool size) # don't generate a toolpath if the area is too small (e.g. due to the tool size)
if not self.no_dialog: if not self.no_dialog:
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 None
self.update_progress_bar("Starting the toolpath generation")
# put the tool settings together # put the tool settings together
tool_dict = {"shape": tool_settings["shape"], tool_id = self.tool_list.index(tool_settings) + 1
"radius": tool_settings["tool_radius"], toolpath_settings.set_tool(tool_id, tool_settings["shape"],
"torus_radius": tool_settings["torus_radius"], tool_settings["tool_radius"], tool_settings["torus_radius"],
} tool_settings["speed"], tool_settings["feedrate"])
self.cutter = pycam.Cutters.get_tool_from_settings(tool_dict)
# get the support grid options # get the support grid options
if self.gui.get_object("SupportGridEnable").get_active(): if self.gui.get_object("SupportGridEnable").get_active():
support_grid_distance = self.settings.get("support_grid_distance") toolpath_settings.set_support_grid(
support_grid_thickness = self.settings.get("support_grid_thickness") self.settings.get("support_grid_distance"),
support_grid_height = self.settings.get("support_grid_height") self.settings.get("support_grid_thickness"),
else: self.settings.get("support_grid_height"))
support_grid_distance = None
support_grid_thickness = None # calculation backend: ODE / None
support_grid_height = None if self.settings.get("enable_ode"):
# run the toolpath generation toolpath_settings.set_calculation_backend("ODE")
toolpath = pycam.Toolpath.Generator.generate_toolpath(self.model,
tool_settings=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"],
support_grid_distance=support_grid_distance,
support_grid_thickness=support_grid_thickness,
support_grid_height=support_grid_height,
calculation_backend=calculation_backend, callback=draw_callback)
print "Time elapsed: %f" % (time.time() - start_time) # unit size
toolpath_settings.set_unit_size(self.settings.get("unit"))
if isinstance(toolpath, basestring): # process settings
# an error occoured - "toolpath" contains the error message toolpath_settings.set_process_settings(
message = "Failed to generate toolpath: %s" % toolpath process_settings["path_generator"],
if not self.no_dialog: process_settings["path_postprocessor"],
show_error_dialog(self.window, message) process_settings["path_direction"],
else: process_settings["material_allowance"],
print >>sys.stderr, message process_settings["safety_height"],
# we were not successful (similar to a "cancel" request) process_settings["overlap_percent"] / 100.0,
return False process_settings["step_down"])
else:
# hide the previous toolpath if it is the only visible one (automatic mode) return toolpath_settings
if (len([True for path in self.toolpath if path.visible]) == 1) \
and self.toolpath[-1].visible:
self.toolpath[-1].visible = False
# add the new toolpath
description = "%s / %s" % (tool_settings["name"], process_settings["name"])
# the tool id numbering should start with 1 instead of zero
tool_id = self.tool_list.index(tool_settings) + 1
self.toolpath.add_toolpath(toolpath,
description, tool_dict, tool_id,
tool_settings["speed"],
tool_settings["feedrate"],
process_settings["material_allowance"],
process_settings["safety_height"],
self.settings.get("unit"),
minx, miny, process_settings["safety_height"],
bounds)
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
...@@ -1878,12 +1891,20 @@ class ProjectGui: ...@@ -1878,12 +1891,20 @@ class ProjectGui:
is_last_loop = True is_last_loop = True
else: else:
is_last_loop = False is_last_loop = False
start_pos = tp.get_start_position()
settings = tp.get_toolpath_settings()
process = settings.get_process_settings()
tool = settings.get_tool_settings()
meta_data = []
meta_data.append(self.get_meta_data())
meta_data.append(tp.get_meta_data())
pycam.Exporters.SimpleGCodeExporter.ExportPathList(destination, pycam.Exporters.SimpleGCodeExporter.ExportPathList(destination,
tp.toolpath, tp.unit, tp.get_path(), settings.get_unit_size(), start_pos.x,
tp.start_x, tp.start_y, tp.start_z, start_pos.y, start_pos.z, tool["feedrate"],
tp.feedrate, tp.speed, safety_height=tp.safety_height, tool_id=tp.tool_id, tool["speed"], safety_height=process["safety_height"],
finish_program=is_last_loop, tool_id=tool["id"], finish_program=is_last_loop,
max_skip_safety_distance=2*tp.tool_settings["radius"]) max_skip_safety_distance=2*tool["tool_radius"],
comment=os.linesep.join(meta_data))
destination.close() destination.close()
if self.no_dialog: if self.no_dialog:
print "GCode file successfully written: %s" % str(filename) print "GCode file successfully written: %s" % str(filename)
...@@ -1891,6 +1912,15 @@ class ProjectGui: ...@@ -1891,6 +1912,15 @@ class ProjectGui:
if not no_dialog and not self.no_dialog: if not no_dialog and not self.no_dialog:
show_error_dialog(self.window, "Failed to save toolpath file") show_error_dialog(self.window, "Failed to save toolpath file")
def get_meta_data(self):
filename = "Filename: %s" % str(self.last_model_file)
timestamp = "Timestamp: %s" % str(datetime.datetime.now())
version = "Version: %s" % VERSION
result = []
for text in (filename, timestamp, version):
result.append("%s %s" % (self.META_DATA_PREFIX, text))
return os.linesep.join(result)
def mainloop(self): def mainloop(self):
# run the mainloop only if a GUI was requested # run the mainloop only if a GUI was requested
if not self.no_dialog: if not self.no_dialog:
......
...@@ -20,6 +20,7 @@ You should have received a copy of the GNU General Public License ...@@ -20,6 +20,7 @@ You should have received a copy of the GNU General Public License
along with PyCAM. If not, see <http://www.gnu.org/licenses/>. along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
""" """
import pycam.Cutters
import ConfigParser import ConfigParser
import StringIO import StringIO
import sys import sys
...@@ -388,3 +389,157 @@ process: 3 ...@@ -388,3 +389,157 @@ process: 3
result.append("") result.append("")
return os.linesep.join(result) return os.linesep.join(result)
class ToolpathSettings:
SECTIONS = {
"Bounds": {
"minx": float,
"maxx": float,
"miny": float,
"maxy": float,
"minz": float,
"maxz": float,
},
"Tool": {
"shape": str,
"tool_radius": float,
"torus_radius": float,
"speed": float,
"feedrate": float,
},
"SupportGrid": {
"distance": float,
"thickness": float,
"height": float,
},
"General": {
"calculation_backend": str,
},
"ProcessSettings": {
"generator": str,
"postprocessor": str,
"path_direction": str,
"material_allowance": float,
"safety_height": float,
"overlap": float,
"step_down": float,
},
}
META_MARKER_START = "PYCAM_TOOLPATH_SETTINGS: START"
META_MARKER_END = "PYCAM_TOOLPATH_SETTINGS: END"
def __init__(self):
self.general = {}
self.bounds = {}
self.tool_settings = {}
self.support_grid = {}
self.process_settings = {}
def set_bounds(self, minx, maxx, miny, maxy, minz, maxz):
self.bounds = {
"minx": minx,
"maxx": maxx,
"miny": miny,
"maxy": maxy,
"minz": minz,
"maxz": maxz,
}
def get_bounds(self):
return self.bounds
def set_tool(self, index, shape, tool_radius, torus_radius=None, speed=0.0, feedrate=0.0):
self.tool_settings = {"id": index,
"shape": shape,
"tool_radius": tool_radius,
"torus_radius": torus_radius,
"speed": speed,
"feedrate": feedrate,
}
def get_tool(self):
return pycam.Cutters.get_tool_from_settings(self.tool_settings)
def get_tool_settings(self):
return self.tool_settings
def set_support_grid(self, distance, thickness, height):
self.support_grid["distance"] = distance
self.support_grid["thickness"] = thickness
self.support_grid["height"] = height
def get_support_grid(self):
if self.support_grid:
return self.support_grid
else:
return {"distance": None, "thickness": None, "height": None}
def set_calculation_backend(self, backend=None):
self.general["calculation_backend"] = None
def get_calculation_backend(self):
if self.general.has_key("calculation_backend"):
return self.general["calculation_backend"]
else:
return None
def set_unit_size(self, unit_size):
self.general["unit_size"] = unit_size
def get_unit_size(self):
if self.general.has_key("unit_size"):
return self.general["unit_size"]
else:
return "mm"
def set_process_settings(self, generator, postprocessor, path_direction,
material_allowance=0.0, safety_height=0.0, overlap=0.0,
step_down=1.0):
self.process_settings = {
"generator": generator,
"postprocessor": postprocessor,
"path_direction": path_direction,
"material_allowance": material_allowance,
"safety_height": safety_height,
"overlap": overlap,
"step_down": step_down,
}
def get_process_settings(self):
return self.process_settings
def parse(self, text):
text_stream = StringIO.StringIO(text)
config = ConfigParser.SafeConfigParser()
config.readfp(text_stream)
for config_dict, section in ((self.bounds, "Bounds"),
(self.tool_settings, "Tool"),
(self.support_grid, "SupportGrid"),
(self.process_settings, "ProcessSettings")):
for key, value_type in self.SECTIONS[section].items():
raw_value = config.get(section, key, None)
if not raw_value is None:
try:
value = value_type(raw_value)
config_dict[key] = value
except ValueError:
print >>sys.stderr, "Ignored invalid setting (%s -> %s): %s" % (section, key, value_raw)
def get_string(self):
result = []
for config_dict, section in ((self.bounds, "Bounds"),
(self.tool_settings, "Tool"),
(self.support_grid, "SupportGrid"),
(self.process_settings, "ProcessSettings")):
result.append("[%s]" % section)
for key, value_type in self.SECTIONS[section].items():
if config_dict.has_key(key):
value = config_dict[key]
if type(value) == value_type:
result.append("%s = %s" % (key, value))
# add one empty line after each section
result.append("")
return os.linesep.join(result)
...@@ -32,6 +32,22 @@ PATH_GENERATORS = frozenset(("DropCutter", "PushCutter", "EngraveCutter")) ...@@ -32,6 +32,22 @@ PATH_GENERATORS = frozenset(("DropCutter", "PushCutter", "EngraveCutter"))
PATH_POSTPROCESSORS = frozenset(("ContourCutter", "PathAccumulator", "PolygonCutter", "SimpleCutter", "ZigZagCutter")) PATH_POSTPROCESSORS = frozenset(("ContourCutter", "PathAccumulator", "PolygonCutter", "SimpleCutter", "ZigZagCutter"))
CALCULATION_BACKENDS = frozenset((None, "ODE")) CALCULATION_BACKENDS = frozenset((None, "ODE"))
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 = []
bounds_dict = tp_settings.get_bounds()
for key in ("minx", "maxx", "miny", "maxy", "minz", "maxz"):
bounds.append(bounds_dict[key])
return generate_toolpath(model, tp_settings.get_tool_settings(),
bounds, process["path_direction"], process["generator"],
process["postprocessor"], process["material_allowance"],
process["safety_height"], process["overlap"],
process["step_down"], grid["distance"], grid["thickness"],
grid["height"], backend, callback)
def generate_toolpath(model, tool_settings=None, def generate_toolpath(model, tool_settings=None,
bounds=None, direction="x", path_generator="DropCutter", bounds=None, direction="x", path_generator="DropCutter",
path_postprocessor="ZigZagCutter", material_allowance=0.0, path_postprocessor="ZigZagCutter", material_allowance=0.0,
...@@ -46,7 +62,7 @@ def generate_toolpath(model, tool_settings=None, ...@@ -46,7 +62,7 @@ def generate_toolpath(model, tool_settings=None,
@value tool_settings: contains at least the following keys (depending on @value tool_settings: contains at least the following keys (depending on
the tool type): the tool type):
"shape": any of possible cutter shape (see "pycam.Cutters") "shape": any of possible cutter shape (see "pycam.Cutters")
"radius": main radius of the tools "tool_radius": main radius of the tools
"torus_radius": (only for ToroidalCutter) second toroidal radius "torus_radius": (only for ToroidalCutter) second toroidal radius
@type bounds: tuple(float) | list(float) @type bounds: tuple(float) | list(float)
@value bounds: the processing boundary (relative to the center of the tool) @value bounds: the processing boundary (relative to the center of the tool)
...@@ -126,7 +142,7 @@ def generate_toolpath(model, tool_settings=None, ...@@ -126,7 +142,7 @@ def generate_toolpath(model, tool_settings=None,
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 and lower than 1" return "Invalid overlap value (%f): should be greater or equal 0 and lower than 1"
effective_toolradius = tool_settings["radius"] * (1.0 - overlap) effective_toolradius = tool_settings["tool_radius"] * (1.0 - overlap)
if path_generator == "DropCutter": if path_generator == "DropCutter":
if direction == "x": if direction == "x":
direction_param = 0 direction_param = 0
......
...@@ -23,32 +23,24 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -23,32 +23,24 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
__all__ = ["ToolPathList", "ToolPath", "Generator"] __all__ = ["ToolPathList", "ToolPath", "Generator"]
from pycam.Geometry.Point import Point from pycam.Geometry.Point import Point
import pycam.Gui.Settings
import random import random
import os
class ToolPathList(list): class ToolPathList(list):
def add_toolpath(self, toolpath, name, tool_settings, *args): def add_toolpath(self, toolpath, name, tool_settings):
self.append(ToolPath(toolpath, name, tool_settings, *args)) self.append(ToolPath(toolpath, name, tool_settings))
class ToolPath: class ToolPath:
def __init__(self, toolpath, name, tool_settings, tool_id, speed, def __init__(self, toolpath, name, toolpath_settings):
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.toolpath_settings = toolpath_settings
self.visible = True self.visible = True
self.tool_id = tool_id
self.tool_settings = tool_settings
self.speed = speed
self.feedrate = feedrate
self.material_allowance = material_allowance
self.safety_height = safety_height
self.unit = unit
self.start_x = start_x
self.start_y = start_y
self.start_z = start_z
self.bounding_box = bounding_box
self.color = None self.color = None
# generate random color # generate random color
self.set_color() self.set_color()
...@@ -56,6 +48,32 @@ class ToolPath: ...@@ -56,6 +48,32 @@ class ToolPath:
def get_path(self): def get_path(self):
return self.toolpath return self.toolpath
def get_start_position(self):
safety_height = self.toolpath_settings.get_process_settings()["safety_height"]
for path in self.toolpath:
if path.points:
p = path.points[0]
return Point(p.x, p.y, safety_height)
else:
return Point(0, 0, safety_height)
def get_bounding_box(self):
box = self.toolpath_settings.get_bounding_box()
return (box["minx"], box["maxx"], box["miny"], box["maxy"], box["minz"],
box["maxz"])
def get_tool_settings(self):
return self.toolpath_settings.get_tool_settings()
def get_toolpath_settings(self):
return self.toolpath_settings
def get_meta_data(self):
meta = self.toolpath_settings.get_string()
start_marker = self.toolpath_settings.META_MARKER_START
end_marker = self.toolpath_settings.META_MARKER_END
return os.linesep.join((start_marker, meta, end_marker))
def set_color(self, color=None): def set_color(self, color=None):
if color is None: if color is None:
self.color = (random.random(), random.random(), random.random()) self.color = (random.random(), random.random(), random.random())
...@@ -74,22 +92,24 @@ class ToolPath: ...@@ -74,22 +92,24 @@ class ToolPath:
""" """
if start_position is None: if start_position is None:
start_position = Point(0, 0, 0) start_position = Point(0, 0, 0)
feedrate = self.toolpath_settings.get_tool_settings()["feedrate"]
def move(new_pos): def move(new_pos):
move.result_time += new_pos.sub(move.current_position).norm() / self.feedrate move.result_time += new_pos.sub(move.current_position).norm() / feedrate
move.current_position = new_pos move.current_position = new_pos
move.current_position = start_position move.current_position = start_position
move.result_time = 0 move.result_time = 0
# move to safey height at the starting position # move to safey height at the starting position
move(Point(start_position.x, start_position.y, self.safety_height)) safety_height = self.toolpath_settings.get_process_settings()["safety_height"]
move(Point(start_position.x, start_position.y, safety_height))
for path in self.get_path(): for path in self.get_path():
# go to safety height (horizontally from the previous x/y location) # go to safety height (horizontally from the previous x/y location)
if len(path.points) > 0: if len(path.points) > 0:
move(Point(path.points[0].x, path.points[0].y, self.safety_height)) move(Point(path.points[0].x, path.points[0].y, safety_height))
# go through all points of the path # go through all points of the path
for point in path.points: for point in path.points:
move(point) move(point)
# go to safety height (vertically up from the current x/y location) # go to safety height (vertically up from the current x/y location)
if len(path.points) > 0: if len(path.points) > 0:
move(Point(path.points[-1].x, path.points[-1].y, self.safety_height)) move(Point(path.points[-1].x, path.points[-1].y, safety_height))
return move.result_time return move.result_time
...@@ -22,3 +22,5 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -22,3 +22,5 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
__all__=["Cutters","Exporters","Geometry","Gui","Importers","PathGenerators","PathProcessors","Utils"] __all__=["Cutters","Exporters","Geometry","Gui","Importers","PathGenerators","PathProcessors","Utils"]
VERSION = "0.2.5"
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
- in "pycam/Gui/gtk-interface/pycam-project.ui" ("version" in "GtkAboutDialog") - in "pycam/Gui/gtk-interface/pycam-project.ui" ("version" in "GtkAboutDialog")
- in "Changelog" - in "Changelog"
- in "setup.py" - in "setup.py"
- in "pycam/__init__.py"
- update the release date and the list of changes in "Changelog" - update the release date and the list of changes in "Changelog"
- commit the changes - commit the changes
......
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