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/>.
import pycam.Plugins
import pycam.Toolpath
class Toolpaths(pycam.Plugins.ListPluginBase):
UI_FILE = "toolpaths.ui"
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"}
def setup(self):
......@@ -63,14 +63,10 @@ class Toolpaths(pycam.Plugins.ListPluginBase):
add_toolpath_handling_item, clear_toolpath_handling_obj)
# handle table changes
self._gtk_handlers.extend((
(self._modelview, "row-activated",
self._list_action_toggle_custom, self.COLUMN_VISIBLE),
(self._modelview, "row-activated", self._toggle_visibility),
(self._modelview, "row-activated", "toolpath-changed"),
(self.gui.get_object("ToolpathNameCell"), "edited",
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
selection = self._modelview.get_selection()
self._gtk_handlers.append((selection, "changed",
......@@ -83,7 +79,7 @@ class Toolpaths(pycam.Plugins.ListPluginBase):
("toolpath-list-changed", "visual-item-updated"))
self.register_gtk_handlers(self._gtk_handlers)
self.register_event_handlers(self._event_handlers)
self._trigger_toolpath_time_update()
self._trigger_table_update()
self._update_widgets()
self.core.set("toolpaths", self)
self.core.register_namespace("toolpaths",
......@@ -99,20 +95,7 @@ class Toolpaths(pycam.Plugins.ListPluginBase):
self.core.set("toolpaths", None)
def get_visible(self):
return [self[index] for index, item in enumerate(self._treemodel)
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,))
return [tp for tp in self if tp["visible"]]
def _update_widgets(self):
toolpaths = self
......@@ -120,43 +103,43 @@ class Toolpaths(pycam.Plugins.ListPluginBase):
self.tp_box.hide()
else:
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("ToolpathTimeCell"),
self._visualize_machine_time)
def _list_action_toggle_custom(self, treeview, path, clicked_column,
force_column=None):
if force_column is None:
column = self._modelview.get_columns().index(clicked_column)
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.gui.get_object("ToolpathVisibleColumn").set_cell_data_func(
self.gui.get_object("ToolpathVisibleSymbol"),
self._visualize_visible_state)
def _toggle_visibility(self, treeview, path, column):
toolpath = self.get_by_path(path)
if toolpath:
toolpath["visible"] = not toolpath["visible"]
self.core.emit_event("visual-item-updated")
def _edit_toolpath_name(self, cell, path, new_text):
path = int(path)
if (new_text != self._treemodel[path][self.COLUMN_NAME]) and \
new_text:
self._treemodel[path][self.COLUMN_NAME] = new_text
toolpath = self.get_by_path(path)
if toolpath and (new_text != toolpath["name"]) and new_text:
toolpath["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):
visible = model.get_value(m_iter, self.COLUMN_VISIBLE)
if visible:
toolpath = self.get_by_path(model.get_path(m_iter))
if toolpath["visible"]:
cell.set_property("pixbuf", self.ICONS["visible"])
else:
cell.set_property("pixbuf", self.ICONS["hidden"])
def _visualize_machine_time(self, column, cell, model, m_iter):
path = model.get_path(m_iter)
toolpath = self[path[0]]
toolpath = self.get_by_path(model.get_path(m_iter))
def get_time_string(minutes):
if minutes > 180:
return "%d hours" % int(round(minutes / 60))
......@@ -164,7 +147,28 @@ class Toolpaths(pycam.Plugins.ListPluginBase):
return "%d minutes" % int(round(minutes))
else:
return "%d seconds" % int(round(minutes * 60))
text = get_time_string(toolpath.get_machine_time(
self.core.get("gcode_safety_height")))
text = get_time_string(toolpath.get_machine_time())
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):
class ListPluginBase(PluginBase, list):
ACTION_UP, ACTION_DOWN, ACTION_DELETE, ACTION_CLEAR = range(4)
LIST_ATTRIBUTE_MAP = {}
def __init__(self, *args, **kwargs):
super(ListPluginBase, self).__init__(*args, **kwargs)
......@@ -557,11 +556,12 @@ class ListPluginBase(PluginBase, list):
class ObjectWithAttributes(dict):
def __init__(self, key, params=None):
if not params is None:
self.update(params)
def __init__(self, node_key=None, attributes=None, **kwargs):
super(ObjectWithAttributes, self).__init__(**kwargs)
if not attributes is None:
self.update(attributes)
self["uuid"] = str(uuid.uuid4())
self.node_key = key
self.node_key = node_key
def filter_list(items, *args, **kwargs):
......
......@@ -71,24 +71,39 @@ def simplify_toolpath(path):
class Toolpath(object):
def __init__(self, path, parameters=None):
self.path = path
if not parameters:
parameters = {}
self.parameters = parameters
def __init__(self, toolpath_path=None, toolpath_parameters=None, **kwargs):
super(Toolpath, self).__init__(**kwargs)
if toolpath_path is None:
toolpath_path = []
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)
from pycam.Toolpath.Filters import TinySidewaysMovesFilter, MachineSetting, \
SafetyHeightFilter
self.filters = []
self.filters.append(MachineSetting("metric", True))
self.filters.append(MachineSetting("feedrate",
parameters.get("tool_feedrate", 300)))
self.parameters.get("tool_feedrate", 300)))
self.filters.append(TinySidewaysMovesFilter(
2 * parameters.get("tool_radius", 0)))
2 * self.parameters.get("tool_radius", 0)))
self.filters.append(SafetyHeightFilter(20))
self._feedrate = parameters.get("tool_feedrate", 300)
self._feedrate = self.parameters.get("tool_feedrate", 300)
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):
self.opengl_safety_height = None
self._cache_basic_moves = None
......@@ -102,12 +117,6 @@ class Toolpath(object):
def get_params(self):
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):
values = [p[idx] for move_type, p in self.path
if move_type in (MOVE_STRAIGHT, MOVE_STRAIGHT_RAPID)]
......@@ -156,7 +165,11 @@ class Toolpath(object):
return os.linesep.join((start_marker, meta, end_marker))
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):
vx = v[0]
......@@ -318,60 +331,6 @@ class Toolpath(object):
self._cache_basic_moves = result
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):
......
......@@ -5,24 +5,8 @@
<object class="GtkListStore" id="ToolpathListModel">
<columns>
<!-- column-name ref -->
<column type="gulong"/>
<!-- column-name name -->
<column type="gchararray"/>
<!-- column-name visible -->
<column type="gboolean"/>
</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 class="GtkVPaned" id="ToolpathsBox">
<property name="visible">True</property>
......@@ -60,10 +44,9 @@
<object class="GtkTreeViewColumn" id="ToolpathNameColumn">
<property name="title">Name</property>
<child>
<object class="GtkCellRendererText" id="ToolpathNameCell"/>
<attributes>
<attribute name="text">1</attribute>
</attributes>
<object class="GtkCellRendererText" id="ToolpathNameCell">
<property name="editable">True</property>
</object>
</child>
</object>
</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