Commit 02f33aa7 authored by Lars Kruse's avatar Lars Kruse

turn toolpath objects into children of ObjectWithAttributes

* unify the handling of models, tools, processes, tasks and (finally) toolpaths
* this required a change of the interfaces of
  pycam.Plugins.ObjectWithAttributes and pycam.Toolpath.Toolpath
  (use only named parameters - otherwise multiple inheritence fails)
* turn Toolpath.path into a property - now we can detect changes and clear the
  cache
* remove the Toolpath methods copy/crop/get_cropped_copy - now you can
  use the filter instead:
    toolpath | pycam.Toolpath.Filters.Crop(polygons)
* simplified the toolpath plugin - now the implementation is similar to
  models/tools/processes/tasks (no more information storage in gtk models)
parent 9ad232c1
...@@ -22,13 +22,13 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -22,13 +22,13 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
import pycam.Plugins import pycam.Plugins
import pycam.Toolpath
class Toolpaths(pycam.Plugins.ListPluginBase): class Toolpaths(pycam.Plugins.ListPluginBase):
UI_FILE = "toolpaths.ui" UI_FILE = "toolpaths.ui"
CATEGORIES = ["Toolpath"] CATEGORIES = ["Toolpath"]
COLUMN_REF, COLUMN_NAME, COLUMN_VISIBLE = range(3)
LIST_ATTRIBUTE_MAP = {"name": COLUMN_NAME, "visible": COLUMN_VISIBLE}
ICONS = {"visible": "visible.svg", "hidden": "visible_off.svg"} ICONS = {"visible": "visible.svg", "hidden": "visible_off.svg"}
def setup(self): def setup(self):
...@@ -63,14 +63,10 @@ class Toolpaths(pycam.Plugins.ListPluginBase): ...@@ -63,14 +63,10 @@ class Toolpaths(pycam.Plugins.ListPluginBase):
add_toolpath_handling_item, clear_toolpath_handling_obj) add_toolpath_handling_item, clear_toolpath_handling_obj)
# handle table changes # handle table changes
self._gtk_handlers.extend(( self._gtk_handlers.extend((
(self._modelview, "row-activated", (self._modelview, "row-activated", self._toggle_visibility),
self._list_action_toggle_custom, self.COLUMN_VISIBLE),
(self._modelview, "row-activated", "toolpath-changed"), (self._modelview, "row-activated", "toolpath-changed"),
(self.gui.get_object("ToolpathNameCell"), "edited", (self.gui.get_object("ToolpathNameCell"), "edited",
self._edit_toolpath_name))) self._edit_toolpath_name)))
self.gui.get_object("ToolpathVisibleColumn").set_cell_data_func(
self.gui.get_object("ToolpathVisibleSymbol"),
self._visualize_visible_state)
# handle selection changes # handle selection changes
selection = self._modelview.get_selection() selection = self._modelview.get_selection()
self._gtk_handlers.append((selection, "changed", self._gtk_handlers.append((selection, "changed",
...@@ -83,7 +79,7 @@ class Toolpaths(pycam.Plugins.ListPluginBase): ...@@ -83,7 +79,7 @@ class Toolpaths(pycam.Plugins.ListPluginBase):
("toolpath-list-changed", "visual-item-updated")) ("toolpath-list-changed", "visual-item-updated"))
self.register_gtk_handlers(self._gtk_handlers) self.register_gtk_handlers(self._gtk_handlers)
self.register_event_handlers(self._event_handlers) self.register_event_handlers(self._event_handlers)
self._trigger_toolpath_time_update() self._trigger_table_update()
self._update_widgets() self._update_widgets()
self.core.set("toolpaths", self) self.core.set("toolpaths", self)
self.core.register_namespace("toolpaths", self.core.register_namespace("toolpaths",
...@@ -99,20 +95,7 @@ class Toolpaths(pycam.Plugins.ListPluginBase): ...@@ -99,20 +95,7 @@ class Toolpaths(pycam.Plugins.ListPluginBase):
self.core.set("toolpaths", None) self.core.set("toolpaths", None)
def get_visible(self): def get_visible(self):
return [self[index] for index, item in enumerate(self._treemodel) return [tp for tp in self if tp["visible"]]
if item[self.COLUMN_VISIBLE]]
def select(self, toolpaths):
selection = self._modelview.get_selection()
model = self._modelview.get_model()
if not isinstance(toolpaths, (list, tuple)):
toolpaths = [toolpaths]
tp_refs = [id(tp) for tp in toolpaths]
for index, row in enumerate(model):
if row[self.COLUMN_REF] in tp_refs:
selection.select_path((index,))
else:
selection.unselect_path((index,))
def _update_widgets(self): def _update_widgets(self):
toolpaths = self toolpaths = self
...@@ -120,43 +103,43 @@ class Toolpaths(pycam.Plugins.ListPluginBase): ...@@ -120,43 +103,43 @@ class Toolpaths(pycam.Plugins.ListPluginBase):
self.tp_box.hide() self.tp_box.hide()
else: else:
self.tp_box.show() self.tp_box.show()
self._trigger_toolpath_time_update() self._trigger_table_update()
def _trigger_toolpath_time_update(self): def _trigger_table_update(self):
self.gui.get_object("ToolpathNameColumn").set_cell_data_func(
self.gui.get_object("ToolpathNameCell"),
self._visualize_toolpath_name)
self.gui.get_object("ToolpathTimeColumn").set_cell_data_func( self.gui.get_object("ToolpathTimeColumn").set_cell_data_func(
self.gui.get_object("ToolpathTimeCell"), self.gui.get_object("ToolpathTimeCell"),
self._visualize_machine_time) self._visualize_machine_time)
self.gui.get_object("ToolpathVisibleColumn").set_cell_data_func(
self.gui.get_object("ToolpathVisibleSymbol"),
self._visualize_visible_state)
def _list_action_toggle_custom(self, treeview, path, clicked_column, def _toggle_visibility(self, treeview, path, column):
force_column=None): toolpath = self.get_by_path(path)
if force_column is None: if toolpath:
column = self._modelview.get_columns().index(clicked_column) toolpath["visible"] = not toolpath["visible"]
else:
column = force_column
self._list_action_toggle(clicked_column, str(path[0]), column)
def _list_action_toggle(self, widget, path, column):
path = int(path)
model = self._treemodel
model[path][column] = not model[path][column]
self.core.emit_event("visual-item-updated") self.core.emit_event("visual-item-updated")
def _edit_toolpath_name(self, cell, path, new_text): def _edit_toolpath_name(self, cell, path, new_text):
path = int(path) toolpath = self.get_by_path(path)
if (new_text != self._treemodel[path][self.COLUMN_NAME]) and \ if toolpath and (new_text != toolpath["name"]) and new_text:
new_text: toolpath["name"] = new_text
self._treemodel[path][self.COLUMN_NAME] = new_text
def _visualize_toolpath_name(self, column, cell, model, m_iter):
toolpath = self.get_by_path(model.get_path(m_iter))
cell.set_property("text", toolpath["name"])
def _visualize_visible_state(self, column, cell, model, m_iter): def _visualize_visible_state(self, column, cell, model, m_iter):
visible = model.get_value(m_iter, self.COLUMN_VISIBLE) toolpath = self.get_by_path(model.get_path(m_iter))
if visible: if toolpath["visible"]:
cell.set_property("pixbuf", self.ICONS["visible"]) cell.set_property("pixbuf", self.ICONS["visible"])
else: else:
cell.set_property("pixbuf", self.ICONS["hidden"]) cell.set_property("pixbuf", self.ICONS["hidden"])
def _visualize_machine_time(self, column, cell, model, m_iter): def _visualize_machine_time(self, column, cell, model, m_iter):
path = model.get_path(m_iter) toolpath = self.get_by_path(model.get_path(m_iter))
toolpath = self[path[0]]
def get_time_string(minutes): def get_time_string(minutes):
if minutes > 180: if minutes > 180:
return "%d hours" % int(round(minutes / 60)) return "%d hours" % int(round(minutes / 60))
...@@ -164,7 +147,28 @@ class Toolpaths(pycam.Plugins.ListPluginBase): ...@@ -164,7 +147,28 @@ class Toolpaths(pycam.Plugins.ListPluginBase):
return "%d minutes" % int(round(minutes)) return "%d minutes" % int(round(minutes))
else: else:
return "%d seconds" % int(round(minutes * 60)) return "%d seconds" % int(round(minutes * 60))
text = get_time_string(toolpath.get_machine_time( text = get_time_string(toolpath.get_machine_time())
self.core.get("gcode_safety_height")))
cell.set_property("text", text) cell.set_property("text", text)
def add_new(self, new_tp):
if isinstance(new_tp, pycam.Toolpath.Toolpath):
moves = new_tp.path
parameters = new_tp.get_params()
else:
moves, parameters = new_tp
toolpath_id = len(self) + 1
name_template = "Toolpath #%d"
while (name_template % toolpath_id) in [tp["name"] for tp in self]:
toolpath_id += 1
attributes= {"visible": True, "name": name_template % toolpath_id}
new_tp = ToolpathEntity(toolpath_path=moves,
toolpath_parameters=parameters, attributes=attributes)
self.append(new_tp)
class ToolpathEntity(pycam.Toolpath.Toolpath,
pycam.Plugins.ObjectWithAttributes):
def __init__(self, **kwargs):
super(ToolpathEntity, self).__init__(node_key="toolpath", **kwargs)
...@@ -353,7 +353,6 @@ class PluginManager(object): ...@@ -353,7 +353,6 @@ class PluginManager(object):
class ListPluginBase(PluginBase, list): class ListPluginBase(PluginBase, list):
ACTION_UP, ACTION_DOWN, ACTION_DELETE, ACTION_CLEAR = range(4) ACTION_UP, ACTION_DOWN, ACTION_DELETE, ACTION_CLEAR = range(4)
LIST_ATTRIBUTE_MAP = {}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(ListPluginBase, self).__init__(*args, **kwargs) super(ListPluginBase, self).__init__(*args, **kwargs)
...@@ -557,11 +556,12 @@ class ListPluginBase(PluginBase, list): ...@@ -557,11 +556,12 @@ class ListPluginBase(PluginBase, list):
class ObjectWithAttributes(dict): class ObjectWithAttributes(dict):
def __init__(self, key, params=None): def __init__(self, node_key=None, attributes=None, **kwargs):
if not params is None: super(ObjectWithAttributes, self).__init__(**kwargs)
self.update(params) if not attributes is None:
self.update(attributes)
self["uuid"] = str(uuid.uuid4()) self["uuid"] = str(uuid.uuid4())
self.node_key = key self.node_key = node_key
def filter_list(items, *args, **kwargs): def filter_list(items, *args, **kwargs):
......
...@@ -71,24 +71,39 @@ def simplify_toolpath(path): ...@@ -71,24 +71,39 @@ def simplify_toolpath(path):
class Toolpath(object): class Toolpath(object):
def __init__(self, path, parameters=None): def __init__(self, toolpath_path=None, toolpath_parameters=None, **kwargs):
self.path = path super(Toolpath, self).__init__(**kwargs)
if not parameters: if toolpath_path is None:
parameters = {} toolpath_path = []
self.parameters = parameters self.__path = toolpath_path
if toolpath_parameters is None:
toolpath_parameters = {}
self.parameters = toolpath_parameters
# TODO: remove this hidden import (currently necessary to avoid dependency loop) # TODO: remove this hidden import (currently necessary to avoid dependency loop)
from pycam.Toolpath.Filters import TinySidewaysMovesFilter, MachineSetting, \ from pycam.Toolpath.Filters import TinySidewaysMovesFilter, MachineSetting, \
SafetyHeightFilter SafetyHeightFilter
self.filters = [] self.filters = []
self.filters.append(MachineSetting("metric", True)) self.filters.append(MachineSetting("metric", True))
self.filters.append(MachineSetting("feedrate", self.filters.append(MachineSetting("feedrate",
parameters.get("tool_feedrate", 300))) self.parameters.get("tool_feedrate", 300)))
self.filters.append(TinySidewaysMovesFilter( self.filters.append(TinySidewaysMovesFilter(
2 * parameters.get("tool_radius", 0))) 2 * self.parameters.get("tool_radius", 0)))
self.filters.append(SafetyHeightFilter(20)) self.filters.append(SafetyHeightFilter(20))
self._feedrate = parameters.get("tool_feedrate", 300) self._feedrate = self.parameters.get("tool_feedrate", 300)
self.clear_cache() self.clear_cache()
def __get_path(self):
return self.__path
def __set_path(self, new_path):
# use a read-only tuple instead of a list
# (otherwise we can't detect changes)
self.__path = tuple(new_path)
self.clear_cache()
# use a property in order to trigger "clear_cache" whenever the path changes
path = property(__get_path, __set_path)
def clear_cache(self): def clear_cache(self):
self.opengl_safety_height = None self.opengl_safety_height = None
self._cache_basic_moves = None self._cache_basic_moves = None
...@@ -102,12 +117,6 @@ class Toolpath(object): ...@@ -102,12 +117,6 @@ class Toolpath(object):
def get_params(self): def get_params(self):
return dict(self.parameters) return dict(self.parameters)
def copy(self):
new_path = list(self.path)
new_tp = Toolpath(new_path, parameters=self.get_params())
new_tp.filters = [f.clone() for f in self.filters]
return new_tp
def _get_limit_generic(self, idx, func): def _get_limit_generic(self, idx, func):
values = [p[idx] for move_type, p in self.path values = [p[idx] for move_type, p in self.path
if move_type in (MOVE_STRAIGHT, MOVE_STRAIGHT_RAPID)] if move_type in (MOVE_STRAIGHT, MOVE_STRAIGHT_RAPID)]
...@@ -156,7 +165,11 @@ class Toolpath(object): ...@@ -156,7 +165,11 @@ class Toolpath(object):
return os.linesep.join((start_marker, meta, end_marker)) return os.linesep.join((start_marker, meta, end_marker))
def get_moves(self, max_time=None): def get_moves(self, max_time=None):
return self.get_basic_moves() | pycam.Toolpath.Filters.TimeLimit(max_time) moves = self.get_basic_moves()
if max_time is None:
return moves
else:
return moves | pycam.Toolpath.Filters.TimeLimit(max_time)
def _rotate_point(self, rp, sp, v, angle): def _rotate_point(self, rp, sp, v, angle):
vx = v[0] vx = v[0]
...@@ -318,60 +331,6 @@ class Toolpath(object): ...@@ -318,60 +331,6 @@ class Toolpath(object):
self._cache_basic_moves = result self._cache_basic_moves = result
return self._cache_basic_moves return self._cache_basic_moves
def get_cropped_copy(self, polygons, callback=None):
# create a deep copy of the current toolpath
tp = self.copy()
tp.crop(polygons, callback=callback)
return tp
def crop(self, polygons, callback=None):
self.path |= pycam.Toolpath.Filters.Crop(polygons)
self.clear_cache()
return
# collect all existing toolpath lines
open_lines = []
for step in self.path:
for index in range(len(path) - 1):
open_lines.append(Line(path[index], path[index + 1]))
# go through all polygons and add "inner" lines (or parts thereof) to
# the final list of remaining lines
inner_lines = []
for polygon in polygons:
new_open_lines = []
for line in open_lines:
if callback and callback():
return
inner, outer = polygon.split_line(line)
inner_lines.extend(inner)
new_open_lines.extend(outer)
open_lines = new_open_lines
# turn all "inner_lines" into toolpath moves
new_paths = []
current_path = []
if inner_lines:
line = inner_lines.pop(0)
current_path.append(line.p1)
current_path.append(line.p2)
while inner_lines:
if callback and callback():
return
end = current_path[-1]
# look for the next connected point
for line in inner_lines:
if line.p1 == end:
inner_lines.remove(line)
current_path.append(line.p2)
break
else:
new_paths.append(current_path)
current_path = Path()
line = inner_lines.pop(0)
current_path.append(line.p1)
current_path.append(line.p2)
if current_path:
new_paths.append(current_path)
self.path = new_path
class Bounds(object): class Bounds(object):
......
...@@ -5,24 +5,8 @@ ...@@ -5,24 +5,8 @@
<object class="GtkListStore" id="ToolpathListModel"> <object class="GtkListStore" id="ToolpathListModel">
<columns> <columns>
<!-- column-name ref --> <!-- column-name ref -->
<column type="gulong"/>
<!-- column-name name -->
<column type="gchararray"/> <column type="gchararray"/>
<!-- column-name visible -->
<column type="gboolean"/>
</columns> </columns>
<data>
<row>
<col id="0">0</col>
<col id="1" translatable="yes">#1</col>
<col id="2">True</col>
</row>
<row>
<col id="0">0</col>
<col id="1" translatable="yes">#2</col>
<col id="2">False</col>
</row>
</data>
</object> </object>
<object class="GtkVPaned" id="ToolpathsBox"> <object class="GtkVPaned" id="ToolpathsBox">
<property name="visible">True</property> <property name="visible">True</property>
...@@ -60,10 +44,9 @@ ...@@ -60,10 +44,9 @@
<object class="GtkTreeViewColumn" id="ToolpathNameColumn"> <object class="GtkTreeViewColumn" id="ToolpathNameColumn">
<property name="title">Name</property> <property name="title">Name</property>
<child> <child>
<object class="GtkCellRendererText" id="ToolpathNameCell"/> <object class="GtkCellRendererText" id="ToolpathNameCell">
<attributes> <property name="editable">True</property>
<attribute name="text">1</attribute> </object>
</attributes>
</child> </child>
</object> </object>
</child> </child>
......
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