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]
......
This diff is collapsed.
...@@ -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