Commit e7fab8b1 authored by sumpfralle's avatar sumpfralle

moved toolpath cropping to a separate plugin

added a basic model manager
added dependency checks for plugins


git-svn-id: https://pycam.svn.sourceforge.net/svnroot/pycam/trunk@1104 bbaffbd6-741e-11dd-a85d-61de82d9cad9
parent 7e9cdffe
<?xml version="1.0"?>
<interface>
<!-- interface-requires gtk+ 2.12 -->
<!-- interface-naming-policy project-wide -->
<object class="GtkWindow" id="window1">
<child>
<object class="GtkVPaned" id="ModelBox">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkHBox" id="hbox1">
<property name="visible">True</property>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
<property name="shadow_type">etched-out</property>
<child>
<object class="GtkIconView" id="ModelView">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="selection_mode">multiple</property>
<property name="model">ModelList</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext1"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
</object>
<packing>
<property name="padding">5</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkVButtonBox" id="vbuttonbox1">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="layout_style">center</property>
<child>
<object class="GtkButton" id="ModelDelete">
<property name="label">gtk-delete</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="ModelDeleteAll">
<property name="label">gtk-clear</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="ModelMoveUp">
<property name="label">gtk-go-up</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="ModelMoveDown">
<property name="label">gtk-go-down</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkLinkButton" id="ModelTransformationsHelp">
<property name="label">gtk-help</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="relief">none</property>
<property name="use_stock">True</property>
<property name="uri">http://sourceforge.net/apps/mediawiki/pycam/index.php?title=ModelTransformations</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">4</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="resize">False</property>
<property name="shrink">False</property>
</packing>
</child>
<child>
<object class="GtkNotebook" id="ModelHandlingNotebook">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tab_pos">left</property>
</object>
<packing>
<property name="resize">True</property>
<property name="shrink">True</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkListStore" id="ModelList">
<columns>
<!-- column-name name -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">Model 1</col>
</row>
</data>
</object>
</interface>
This source diff could not be displayed because it is too large. You can view the blob instead.
<?xml version="1.0"?>
<interface>
<!-- interface-requires gtk+ 2.12 -->
<!-- interface-naming-policy project-wide -->
<object class="GtkDialog" id="ToolpathCropDialog">
<property name="border_width">5</property>
<property name="title" translatable="yes">Crop toolpath</property>
<property name="type_hint">normal</property>
<child internal-child="vbox">
<object class="GtkVBox" id="dialog-vbox1">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
<object class="GtkVBox" id="vbox1">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">3</property>
<child>
<object class="GtkTable" id="table1">
<property name="visible">True</property>
<property name="n_rows">2</property>
<property name="n_columns">2</property>
<property name="column_spacing">3</property>
<property name="row_spacing">3</property>
<child>
<object class="GtkLabel" id="ToolpathCropMarginLabel">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Margin:</property>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="ToolpathCropMargin">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x25CF;</property>
<property name="width_chars">5</property>
<property name="adjustment">ToolpathCropMarginValue</property>
<property name="digits">2</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="ToolpathCropZSliceLabel">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Slicing z-level:</property>
</object>
<packing>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="ToolpathCropZSlice">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x25CF;</property>
<property name="width_chars">5</property>
<property name="adjustment">ToolpathCropZSliceValue</property>
<property name="digits">2</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="ToolpathCropKeepOriginal">
<property name="label" translatable="yes">Keep original toolpath</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkViewport" id="ToolpathCropInfoBox">
<property name="no_show_all">True</property>
<property name="resize_mode">queue</property>
<child>
<object class="GtkLabel" id="ToolpathCropInfo">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">label</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
<child internal-child="action_area">
<object class="GtkHButtonBox" id="dialog-action_area1">
<property name="visible">True</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="ToolpathCropCancel">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="ToolpathCropOK">
<property name="label">gtk-ok</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
<action-widgets>
<action-widget response="0">ToolpathCropCancel</action-widget>
<action-widget response="0">ToolpathCropOK</action-widget>
</action-widgets>
</object>
<object class="GtkAdjustment" id="ToolpathCropMarginValue">
<property name="lower">-1000</property>
<property name="upper">1000</property>
<property name="step_increment">1</property>
</object>
<object class="GtkAdjustment" id="ToolpathCropZSliceValue">
<property name="lower">-1000</property>
<property name="upper">1000</property>
<property name="step_increment">1</property>
</object>
<object class="GtkWindow" id="window1">
<child>
<object class="GtkButton" id="ToolpathCropButton">
<property name="label" translatable="yes">Crop</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
</object>
</child>
</object>
</interface>
...@@ -29,10 +29,12 @@ from pycam.Geometry.Point import Vector ...@@ -29,10 +29,12 @@ from pycam.Geometry.Point import Vector
class Plane(TransformableContainer): class Plane(TransformableContainer):
id = 0 id = 0
def __init__(self, point, normal): def __init__(self, point, normal=None):
super(Plane, self).__init__() super(Plane, self).__init__()
self.id = Plane.id self.id = Plane.id
Plane.id += 1 Plane.id += 1
if normal is None:
normal = Vector(0, 0, 1)
self.p = point self.p = point
self.n = normal self.n = normal
if not isinstance(self.n, Vector): if not isinstance(self.n, Vector):
......
...@@ -216,10 +216,15 @@ class Camera(object): ...@@ -216,10 +216,15 @@ class Camera(object):
v = self.view v = self.view
# position the light according to the current bounding box # position the light according to the current bounding box
light_pos = range(3) light_pos = range(3)
model = self.settings.get("model") values = {}
light_pos[0] = 2 * model.maxx - model.minx for model in self.settings.get("models"):
light_pos[1] = 2 * model.maxy - model.miny for key in ("minx", "miny", "minz", "maxx", "maxy", "maxz"):
light_pos[2] = 2 * model.maxz - model.minz if not key in values:
values[key] = []
values[key].append(getattr(model, key))
light_pos[0] = 2 * max(values["maxx"]) - min(values["minx"])
light_pos[1] = 2 * max(values["maxy"]) - min(values["miny"])
light_pos[2] = 2 * max(values["maxz"]) - min(values["minz"])
GL.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, (light_pos[0], light_pos[1], GL.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, (light_pos[0], light_pos[1],
light_pos[2], 1.0)) light_pos[2], 1.0))
# position the camera # position the camera
...@@ -907,24 +912,24 @@ def draw_complete_model_view(settings): ...@@ -907,24 +912,24 @@ def draw_complete_model_view(settings):
and not (settings.get("show_simulation") \ and not (settings.get("show_simulation") \
and settings.get("simulation_toolpath_moves")): and settings.get("simulation_toolpath_moves")):
GL.glColor4f(*settings.get("color_model")) GL.glColor4f(*settings.get("color_model"))
model = settings.get("model") for model in settings.get("models"):
""" """
min_area = abs(model.maxx - model.minx) * abs(model.maxy - model.miny) / 100 min_area = abs(model.maxx - model.minx) * abs(model.maxy - model.miny) / 100
# example for coloring specific triangles # example for coloring specific triangles
groups = model.get_flat_areas(min_area) groups = model.get_flat_areas(min_area)
all_flat_ids = [] all_flat_ids = []
for group in groups: for group in groups:
all_flat_ids.extend([t.id for t in group]) all_flat_ids.extend([t.id for t in group])
flat_color = (1.0, 0.0, 0.0, 1.0) flat_color = (1.0, 0.0, 0.0, 1.0)
normal_color = settings.get("color_model") normal_color = settings.get("color_model")
def check_triangle_draw(triangle): def check_triangle_draw(triangle):
if triangle.id in all_flat_ids: if triangle.id in all_flat_ids:
return True, flat_color return True, flat_color
else: else:
return True, normal_color return True, normal_color
model.to_OpenGL(visible_filter=check_triangle_draw) model.to_OpenGL(visible_filter=check_triangle_draw)
""" """
model.to_OpenGL(show_directions=settings.get("show_directions")) model.to_OpenGL(show_directions=settings.get("show_directions"))
# draw the support grid # draw the support grid
if settings.get("show_support_grid") and settings.get("current_support_model"): if settings.get("show_support_grid") and settings.get("current_support_model"):
GL.glColor4f(*settings.get("color_support_grid")) GL.glColor4f(*settings.get("color_support_grid"))
......
...@@ -192,15 +192,11 @@ def get_icons_pixbuffers(): ...@@ -192,15 +192,11 @@ def get_icons_pixbuffers():
return result return result
UI_FUNC_INDEX = 0 UI_FUNC_INDEX, UI_WIDGET_INDEX = range(2)
UI_WIDGET_INDEX = 1 WIDGET_NAME_INDEX, WIDGET_OBJ_INDEX, WIDGET_WEIGHT_INDEX = range(3)
WIDGET_NAME_INDEX = 0 HANDLER_FUNC_INDEX, HANDLER_ARG_INDEX = range(2)
WIDGET_OBJ_INDEX = 1 EVENT_HANDLER_INDEX, EVENT_BLOCKER_INDEX = range(2)
WIDGET_WEIGHT_INDEX = 2
HANDLER_FUNC_INDEX = 0
HANDLER_ARG_INDEX = 1
EVENT_HANDLER_INDEX = 0
EVENT_BLOCKER_INDEX = 1
class EventCore(pycam.Gui.Settings.Settings): class EventCore(pycam.Gui.Settings.Settings):
...@@ -526,7 +522,8 @@ class ProjectGui(object): ...@@ -526,7 +522,8 @@ class ProjectGui(object):
self._font_dialog_window_position = None self._font_dialog_window_position = None
# set defaults # set defaults
# fallback - in case of a failure when opening a model file # fallback - in case of a failure when opening a model file
self.model = pycam.Importers.TestModel.get_test_model() model = pycam.Importers.TestModel.get_test_model()
self.settings.get("models").append(model)
self.toolpath = pycam.Toolpath.ToolpathList() self.toolpath = pycam.Toolpath.ToolpathList()
self.cutter = None self.cutter = None
self.tool_list = [] self.tool_list = []
...@@ -537,17 +534,24 @@ class ProjectGui(object): ...@@ -537,17 +534,24 @@ class ProjectGui(object):
self._last_unit = None self._last_unit = None
self._toolpath_for_grid_data = {} self._toolpath_for_grid_data = {}
# add some dummies - to be implemented later ... # add some dummies - to be implemented later ...
self.settings.add_item("model", lambda: self.model)
self.settings.add_item("toolpath", lambda: self.toolpath) self.settings.add_item("toolpath", lambda: self.toolpath)
self.settings.add_item("cutter", lambda: self.cutter) self.settings.add_item("cutter", lambda: self.cutter)
model_handling_obj = self.gui.get_object("ModelHandlingNotebook") main_tab = self.gui.get_object("MainTabs")
def clear_model_handling_obj(): def clear_main_tab():
for index in range(model_handling_obj.get_n_pages()): for index in range(main_tab.get_n_pages()):
model_handling_obj.remove_page(0) main_tab.remove_page(0)
def add_model_handling_item(item, name): def add_main_tab_item(item, name):
model_handling_obj.append_page(item, gtk.Label(name)) main_tab.append_page(item, gtk.Label(name))
self.settings.register_ui_section("model_handling", # TODO: move these to plugins, as well
add_model_handling_item, clear_model_handling_obj) tab_names = ("Tools", "Processes", "Bounds", "Tasks", "Toolpaths")
for name in tab_names:
item = self.gui.get_object(name + "Tab")
item.unparent()
self.settings.register_ui_section("main", add_main_tab_item,
clear_main_tab)
for name in tab_names:
item = self.gui.get_object(name + "Tab")
self.settings.register_ui("main", name, item, tab_names.index(name))
# unit control (mm/inch) # unit control (mm/inch)
unit_field = self.gui.get_object("unit_control") unit_field = self.gui.get_object("unit_control")
unit_field.connect("changed", self.change_unit_init) unit_field.connect("changed", self.change_unit_init)
...@@ -606,18 +610,26 @@ class ProjectGui(object): ...@@ -606,18 +610,26 @@ class ProjectGui(object):
# Calculate the "minx, ..." settings based on a (potentially) selected # Calculate the "minx, ..." settings based on a (potentially) selected
# bounds setting. # bounds setting.
def get_absolute_limit(key): def get_absolute_limit(key):
if self.model is None: if not self.settings.get("models"):
# avoid problems if no model is loaded # avoid problems if no model is loaded
return 0 return 0
bounds = self.settings.get("current_bounds") bounds = self.settings.get("current_bounds")
if key.startswith("min"):
func = min
else:
func = max
if bounds is None: if bounds is None:
return getattr(self.model, key) return func([getattr(model, key) for model in self.settings.get("models")])
low, high = bounds.get_absolute_limits(reference=self.model.get_bounds()) lows, highs = [], []
index = "xyz".index(key[-1]) index = "xyz".index(key[-1])
for model in self.settings.get("models"):
low, high = bounds.get_absolute_limits(reference=model.get_bounds())
lows.append(low[index])
highs.append(high[index])
if key.startswith("min"): if key.startswith("min"):
return low[index] return func(lows)
else: else:
return high[index] return func(highs)
for key in ("minx", "maxx", "miny", "maxy", "minz", "maxz"): for key in ("minx", "maxx", "miny", "maxy", "minz", "maxz"):
# create a new variable "key" to avoid re-using the same object "key" # create a new variable "key" to avoid re-using the same object "key"
# (due to the lambda name scope) # (due to the lambda name scope)
...@@ -826,7 +838,7 @@ class ProjectGui(object): ...@@ -826,7 +838,7 @@ class ProjectGui(object):
self.simulation_window = self.gui.get_object("SimulationDialog") self.simulation_window = self.gui.get_object("SimulationDialog")
self.simulation_window.connect("delete-event", self.finish_toolpath_simulation) self.simulation_window.connect("delete-event", self.finish_toolpath_simulation)
# store the original content (for adding the number of current toolpaths in "update_toolpath_table") # store the original content (for adding the number of current toolpaths in "update_toolpath_table")
self._original_toolpath_tab_label = self.gui.get_object("ToolPathTabLabel").get_text() self._original_toolpath_tab_label = self.gui.get_object("ToolpathsTabLabel").get_text()
# tool editor # tool editor
self.settings.add_item("current_tool", self.settings.add_item("current_tool",
lambda: get_current_item(self.tool_editor_table, self.tool_list), lambda: get_current_item(self.tool_editor_table, self.tool_list),
...@@ -1130,7 +1142,11 @@ class ProjectGui(object): ...@@ -1130,7 +1142,11 @@ class ProjectGui(object):
def update_model_type_related_controls(self): def update_model_type_related_controls(self):
# disable the lower boundary for contour models # disable the lower boundary for contour models
is_contour = isinstance(self.model, pycam.Geometry.Model.ContourModel) models = self.settings.get("models")
if not models:
return
model = models[0]
is_contour = isinstance(model, pycam.Geometry.Model.ContourModel)
margin_type = self._load_bounds_settings_from_gui().get_type() margin_type = self._load_bounds_settings_from_gui().get_type()
z_low_control = self.gui.get_object("boundary_z_low") z_low_control = self.gui.get_object("boundary_z_low")
if is_contour and (margin_type != Bounds.TYPE_CUSTOM): if is_contour and (margin_type != Bounds.TYPE_CUSTOM):
...@@ -1139,7 +1155,7 @@ class ProjectGui(object): ...@@ -1139,7 +1155,7 @@ class ProjectGui(object):
z_low_control.set_sensitive(True) z_low_control.set_sensitive(True)
# copy button # copy button
self.gui.get_object("CopyModelToClipboard").set_sensitive( self.gui.get_object("CopyModelToClipboard").set_sensitive(
bool(self.model and self.model.is_export_supported())) bool(model and model.is_export_supported()))
def update_ode_settings(self, widget=None): def update_ode_settings(self, widget=None):
if pycam.Utils.threading.is_multiprocessing_enabled() \ if pycam.Utils.threading.is_multiprocessing_enabled() \
...@@ -1184,9 +1200,9 @@ class ProjectGui(object): ...@@ -1184,9 +1200,9 @@ class ProjectGui(object):
def _store_undo_state(self): def _store_undo_state(self):
# for now we only store the model # for now we only store the model
if not self.model: if not self.settings.get("models"):
return return
self._undo_states.append(pickle.dumps(self.model)) self._undo_states.append(pickle.dumps(self.settings.get("models")[0]))
log.debug("Stored the current state of the model for undo") log.debug("Stored the current state of the model for undo")
while len(self._undo_states) > MAX_UNDO_STATES: while len(self._undo_states) > MAX_UNDO_STATES:
self._undo_states.pop(0) self._undo_states.pop(0)
...@@ -1195,7 +1211,8 @@ class ProjectGui(object): ...@@ -1195,7 +1211,8 @@ class ProjectGui(object):
def _restore_undo_state(self, widget=None, event=None): def _restore_undo_state(self, widget=None, event=None):
if len(self._undo_states) > 0: if len(self._undo_states) > 0:
latest = StringIO.StringIO(self._undo_states.pop(-1)) latest = StringIO.StringIO(self._undo_states.pop(-1))
self.model = pickle.Unpickler(latest).load() model = pickle.Unpickler(latest).load()
self.load_model(model)
self.gui.get_object("UndoButton").set_sensitive( self.gui.get_object("UndoButton").set_sensitive(
len(self._undo_states) > 0) len(self._undo_states) > 0)
log.info("Restored the previous state of the model") log.info("Restored the previous state of the model")
...@@ -1234,8 +1251,8 @@ class ProjectGui(object): ...@@ -1234,8 +1251,8 @@ class ProjectGui(object):
self.gui.get_object("SaveTaskSettings").set_sensitive( self.gui.get_object("SaveTaskSettings").set_sensitive(
bool(self.last_task_settings_uri and \ bool(self.last_task_settings_uri and \
self.last_task_settings_uri.is_writable())) self.last_task_settings_uri.is_writable()))
save_as_possible = (not self.model is None) \ model = self.settings.get("models") and self.settings.get("models")[0]
and self.model.is_export_supported() save_as_possible = (not model is None) and model.is_export_supported()
self.gui.get_object("SaveAsModel").set_sensitive(save_as_possible) self.gui.get_object("SaveAsModel").set_sensitive(save_as_possible)
save_possible = bool(self.last_model_uri and save_as_possible and \ save_possible = bool(self.last_model_uri and save_as_possible and \
self.last_model_uri.is_writable()) self.last_model_uri.is_writable())
...@@ -1371,25 +1388,26 @@ class ProjectGui(object): ...@@ -1371,25 +1388,26 @@ class ProjectGui(object):
@gui_activity_guard @gui_activity_guard
def adjust_bounds(self, widget, axis, change): def adjust_bounds(self, widget, axis, change):
bounds = self.settings.get("current_bounds") bounds = self.settings.get("current_bounds")
model = self.settings.get("models")[0]
abs_bounds_low, abs_bounds_high = bounds.get_absolute_limits( abs_bounds_low, abs_bounds_high = bounds.get_absolute_limits(
reference=self.model.get_bounds()) reference=model.get_bounds())
# calculate the "change" for +/- (10% of this axis' model dimension) # calculate the "change" for +/- (10% of this axis' model dimension)
if bounds is None: if bounds is None:
return return
if axis == "x": if axis == "x":
change_value = (self.model.maxx - self.model.minx) * 0.1 change_value = (model.maxx - model.minx) * 0.1
elif axis == "y": elif axis == "y":
change_value = (self.model.maxy - self.model.miny) * 0.1 change_value = (model.maxy - model.miny) * 0.1
elif axis == "z": elif axis == "z":
change_value = (self.model.maxz - self.model.minz) * 0.1 change_value = (model.maxz - model.minz) * 0.1
else: else:
# not allowed # not allowed
return return
# calculate the new bounds # calculate the new bounds
axis_index = "xyz".index(axis) axis_index = "xyz".index(axis)
if change == "0": if change == "0":
abs_bounds_low[axis_index] = getattr(self.model, "min%s" % axis) abs_bounds_low[axis_index] = getattr(model, "min%s" % axis)
abs_bounds_high[axis_index] = getattr(self.model, "max%s" % axis) abs_bounds_high[axis_index] = getattr(model, "max%s" % axis)
elif change == "+": elif change == "+":
abs_bounds_low[axis_index] -= change_value abs_bounds_low[axis_index] -= change_value
abs_bounds_high[axis_index] += change_value abs_bounds_high[axis_index] += change_value
...@@ -1401,7 +1419,7 @@ class ProjectGui(object): ...@@ -1401,7 +1419,7 @@ class ProjectGui(object):
return return
# transfer the new bounds values to the old settings # transfer the new bounds values to the old settings
bounds.adjust_bounds_to_absolute_limits(abs_bounds_low, abs_bounds_high, bounds.adjust_bounds_to_absolute_limits(abs_bounds_low, abs_bounds_high,
reference=self.model.get_bounds()) reference=model.get_bounds())
# update the controls # update the controls
self._put_bounds_settings_to_gui(bounds) self._put_bounds_settings_to_gui(bounds)
# update the visualization # update the visualization
...@@ -1410,16 +1428,17 @@ class ProjectGui(object): ...@@ -1410,16 +1428,17 @@ class ProjectGui(object):
@gui_activity_guard @gui_activity_guard
def switch_bounds_type(self, widget=None): def switch_bounds_type(self, widget=None):
bounds = self.settings.get("current_bounds") bounds = self.settings.get("current_bounds")
model = self.settings.get("models")[0]
new_type = self._load_bounds_settings_from_gui().get_type() new_type = self._load_bounds_settings_from_gui().get_type()
if new_type == bounds.get_type(): if new_type == bounds.get_type():
# no change # no change
return return
# calculate the absolute bounds of the previous configuration # calculate the absolute bounds of the previous configuration
abs_bounds_low, abs_bounds_high = bounds.get_absolute_limits( abs_bounds_low, abs_bounds_high = bounds.get_absolute_limits(
reference=self.model.get_bounds()) reference=model.get_bounds())
bounds.set_type(new_type) bounds.set_type(new_type)
bounds.adjust_bounds_to_absolute_limits(abs_bounds_low, abs_bounds_high, bounds.adjust_bounds_to_absolute_limits(abs_bounds_low, abs_bounds_high,
reference=self.model.get_bounds()) reference=model.get_bounds())
self._put_bounds_settings_to_gui(bounds) self._put_bounds_settings_to_gui(bounds)
# update the descriptive label for each margin type # update the descriptive label for each margin type
self.update_bounds_controls() self.update_bounds_controls()
...@@ -1801,13 +1820,14 @@ class ProjectGui(object): ...@@ -1801,13 +1820,14 @@ class ProjectGui(object):
self.clipboard.store() self.clipboard.store()
def copy_model_to_clipboard(self, widget=None): def copy_model_to_clipboard(self, widget=None):
if not self.model.is_export_supported(): model = self.settings.get("models")[0]
if not model.is_export_supported():
return return
text_buffer = StringIO.StringIO() text_buffer = StringIO.StringIO()
self.model.export(comment=self.get_meta_data(), model.export(comment=self.get_meta_data(),
unit=self.settings.get("unit")).write(text_buffer) unit=self.settings.get("unit")).write(text_buffer)
text_buffer.seek(0) text_buffer.seek(0)
is_contour = isinstance(self.model, pycam.Geometry.Model.ContourModel) is_contour = isinstance(model, pycam.Geometry.Model.ContourModel)
# TODO: this should not be decided here # TODO: this should not be decided here
if is_contour: if is_contour:
targets = CLIPBOARD_TARGETS["svg"] targets = CLIPBOARD_TARGETS["svg"]
...@@ -2074,7 +2094,7 @@ class ProjectGui(object): ...@@ -2074,7 +2094,7 @@ class ProjectGui(object):
if self.view3d and not self.view3d.enabled: if self.view3d and not self.view3d.enabled:
# initialization failed - don't do anything # initialization failed - don't do anything
return return
if not self.model: if not self.settings.get("models"):
# no model loaded - don't enable the window # no model loaded - don't enable the window
return return
current_state = not ((self.view3d is None) or (not self.view3d.is_visible)) current_state = not ((self.view3d is None) or (not self.view3d.is_visible))
...@@ -2098,7 +2118,7 @@ class ProjectGui(object): ...@@ -2098,7 +2118,7 @@ class ProjectGui(object):
item_buttons=item_buttons, item_buttons=item_buttons,
context_menu_actions=[self.gui.get_object(name) context_menu_actions=[self.gui.get_object(name)
for name in ("GeneralSettings", "Help3DView")]) for name in ("GeneralSettings", "Help3DView")])
if self.model and self.view3d.enabled: if self.view3d.enabled:
self.view3d.reset_view() self.view3d.reset_view()
# configure drag-and-drop for the 3D window # configure drag-and-drop for the 3D window
self.configure_drag_and_drop(self.view3d.window) self.configure_drag_and_drop(self.view3d.window)
...@@ -2355,10 +2375,17 @@ class ProjectGui(object): ...@@ -2355,10 +2375,17 @@ class ProjectGui(object):
if self.gui.get_object("UnitChangeModel").get_active(): if self.gui.get_object("UnitChangeModel").get_active():
# transform the model if it is selected # transform the model if it is selected
# keep the original center of the model # keep the original center of the model
old_center = self._get_model_center()
self.settings.emit_event("model-change-before") self.settings.emit_event("model-change-before")
self.model.scale(factor) for model in self.settings.get("models").get_selected():
self._set_model_center(old_center) new_x, new_y, new_z = ((model.maxx + model.minx) / 2,
(model.maxy + model.miny) / 2,
(model.maxz + model.minz) / 2)
model.scale(factor)
cur_x, cur_y, cur_z = self._get_model_center()
self.update_progress_bar("Centering model")
model.shift(new_x - cur_x, new_y - cur_y,
new_z - cur_z,
callback=self.update_progress_bar)
if self.gui.get_object("UnitChangeProcesses").get_active(): if self.gui.get_object("UnitChangeProcesses").get_active():
# scale the process settings # scale the process settings
for process in self.process_list: for process in self.process_list:
...@@ -2444,7 +2471,9 @@ class ProjectGui(object): ...@@ -2444,7 +2471,9 @@ class ProjectGui(object):
def save_model(self, widget=None, filename=None, model=None, def save_model(self, widget=None, filename=None, model=None,
store_filename=True): store_filename=True):
if model is None: if model is None:
model = self.model models = self.settings.get("models").get_selected()
# TODO: merge multiple models
model = models[0]
if not model.is_export_supported(): if not model.is_export_supported():
log.warn(("Saving this type of model (%s) is currently not " \ log.warn(("Saving this type of model (%s) is currently not " \
+ "implemented!") % str(type(model))) + "implemented!") % str(type(model)))
...@@ -2552,32 +2581,27 @@ class ProjectGui(object): ...@@ -2552,32 +2581,27 @@ class ProjectGui(object):
except IOError, err_msg: except IOError, err_msg:
log.warn("Failed to write preferences file (%s): %s" % (config_filename, err_msg)) log.warn("Failed to write preferences file (%s): %s" % (config_filename, err_msg))
def _get_model_center(self):
if self.model is None:
return None
else:
return ((self.model.maxx + self.model.minx) / 2,
(self.model.maxy + self.model.miny) / 2,
(self.model.maxz + self.model.minz) / 2)
def _set_model_center(self, center):
new_x, new_y, new_z = center
old_x, old_y, old_z = self._get_model_center()
self.update_progress_bar("Centering model")
# undo state should be stored in the caller function
self.model.shift(new_x - old_x, new_y - old_y, new_z - old_z,
callback=self.update_progress_bar)
@gui_activity_guard @gui_activity_guard
def update_model_dimensions(self, widget=None): def update_model_dimensions(self, widget=None):
if self.model is None: models = self.settings.get("models").get_selected()
if not models:
return return
# model corners in 3D view # model corners in 3D view
values = {}
for model in models:
for coord in ("minx", "miny", "minz", "maxx", "maxy", "maxz"):
if not coord in values:
values[coord] = []
values[coord].append(getattr(model, coord))
for attr, label_suffix in (("minx", "XMin"), ("miny", "YMin"), for attr, label_suffix in (("minx", "XMin"), ("miny", "YMin"),
("minz", "ZMin"), ("maxx", "XMax"), ("maxy", "YMax"), ("minz", "ZMin"), ("maxx", "XMax"), ("maxy", "YMax"),
("maxz", "ZMax")): ("maxz", "ZMax")):
if attr.startswith("min"):
func = min
else:
func = max
label_name = "ModelCorner%s" % label_suffix label_name = "ModelCorner%s" % label_suffix
value = "%.3f" % getattr(self.model, attr) value = "%.3f" % func(values[attr])
self.gui.get_object(label_name).set_label(value) self.gui.get_object(label_name).set_label(value)
def destroy(self, widget=None, data=None): def destroy(self, widget=None, data=None):
...@@ -2744,11 +2768,11 @@ class ProjectGui(object): ...@@ -2744,11 +2768,11 @@ class ProjectGui(object):
# load the new model only if the import worked # load the new model only if the import worked
if model: if model:
self.settings.emit_event("model-change-before") self.settings.emit_event("model-change-before")
self.model = model self.settings.get("models").append(model)
self.last_model_uri = None self.last_model_uri = None
# do some initialization # do some initialization
self.settings.emit_event("model-change-after") self.settings.emit_event("model-change-after")
if self.model and self.view3d and self.view3d.enabled: if self.view3d and self.view3d.enabled:
self.append_to_queue(self.view3d.reset_view) self.append_to_queue(self.view3d.reset_view)
self.append_to_queue(self.toggle_3d_view, value=True) self.append_to_queue(self.toggle_3d_view, value=True)
return True return True
...@@ -2842,16 +2866,17 @@ class ProjectGui(object): ...@@ -2842,16 +2866,17 @@ class ProjectGui(object):
def get_control(index, side): def get_control(index, side):
return self.gui.get_object("boundary_%s_%s" % ("xyz"[index], side)) return self.gui.get_object("boundary_%s_%s" % ("xyz"[index], side))
# disable each zero-dimension in relative margin mode # disable each zero-dimension in relative margin mode
model = self.settings.get("models")[0]
if current_type == Bounds.TYPE_RELATIVE_MARGIN: if current_type == Bounds.TYPE_RELATIVE_MARGIN:
model_dims = (self.model.maxx - self.model.minx, model_dims = (model.maxx - model.minx,
self.model.maxy - self.model.miny, model.maxy - model.miny,
self.model.maxz - self.model.minz) model.maxz - model.minz)
# disable the low/high controls for each zero-dimension # disable the low/high controls for each zero-dimension
for index in range(3): for index in range(3):
# enabled, if dimension is non-zero # enabled, if dimension is non-zero
state = model_dims[index] != 0 state = model_dims[index] != 0
get_control(index, "high").set_sensitive(state) get_control(index, "high").set_sensitive(state)
if (index == 2) and isinstance(self.model, if (index == 2) and isinstance(model,
pycam.Geometry.Model.ContourModel): pycam.Geometry.Model.ContourModel):
# disable lower z for contour models # disable lower z for contour models
state = False state = False
...@@ -2860,7 +2885,7 @@ class ProjectGui(object): ...@@ -2860,7 +2885,7 @@ class ProjectGui(object):
# non-relative margins: enable all controls # non-relative margins: enable all controls
for index in range(3): for index in range(3):
get_control(index, "high").set_sensitive(True) get_control(index, "high").set_sensitive(True)
if (index == 2) and isinstance(self.model, if (index == 2) and isinstance(model,
pycam.Geometry.Model.ContourModel) and \ pycam.Geometry.Model.ContourModel) and \
(current_type != Bounds.TYPE_CUSTOM): (current_type != Bounds.TYPE_CUSTOM):
# disable lower z for contour models # disable lower z for contour models
...@@ -3158,40 +3183,13 @@ class ProjectGui(object): ...@@ -3158,40 +3183,13 @@ class ProjectGui(object):
self.update_toolpath_table() self.update_toolpath_table()
dialog.hide() dialog.hide()
def _get_projection_plane(self):
# determine projection plane
if (self.model.maxz < 0) or (self.model.minz > 0):
# completely above or below zero
plane_z = self.model.minz
else:
plane_z = 0
return Plane(Point(0, 0, plane_z), Vector(0, 0, 1))
@progress_activity_guard
def crop_toolpath(self, toolpath):
if hasattr(self.model, "get_polygons"):
contour = self.model
elif hasattr(self.model, "get_waterline_contour"):
plane = self._get_projection_plane()
self.update_progress_bar("Calculating the 2D projection")
contour = self.model.get_waterline_contour(plane)
self.update_progress_bar("Applying the tool diameter offset")
contour = contour.get_offset_model(
2 * toolpath.get_tool_settings()["tool_radius"])
else:
log.warn(("The current model (%s) does not support " \
+ "projections") % str(type(self.model)))
return
self.update_progress_bar("Cropping the toolpath")
toolpath.crop(contour.get_polygons(), callback=self.update_progress_bar)
def update_toolpath_related_controls(self): def update_toolpath_related_controls(self):
# show or hide the "toolpath" tab # show or hide the "toolpath" tab
toolpath_tab = self.gui.get_object("ToolPathTab") toolpath_tab = self.gui.get_object("ToolpathsTab")
if not self.toolpath: if not self.toolpath:
toolpath_tab.hide() toolpath_tab.hide()
else: else:
self.gui.get_object("ToolPathTabLabel").set_text( self.gui.get_object("ToolpathsTabLabel").set_text(
"%s (%d)" % (self._original_toolpath_tab_label, len(self.toolpath))) "%s (%d)" % (self._original_toolpath_tab_label, len(self.toolpath)))
toolpath_tab.show() toolpath_tab.show()
# enable/disable the export menu item # enable/disable the export menu item
...@@ -3476,11 +3474,7 @@ class ProjectGui(object): ...@@ -3476,11 +3474,7 @@ class ProjectGui(object):
self.gui.get_object("SimulationTab").set_sensitive(True) self.gui.get_object("SimulationTab").set_sensitive(True)
def toggle_tabs_for_simulation(self, new_state): def toggle_tabs_for_simulation(self, new_state):
for objname in ("ModelTab", "ModelTabLabel", "TasksTab", self.gui.get_object("MainTabs").set_sensitive(new_state)
"TasksTabLabel", "ToolPathTab", "ToolPathTabLabel", "ToolTab",
"ToolTabLabel", "ProcessTab", "ProcessTabLabel", "BoundsTab",
"BoundsTabLabel"):
self.gui.get_object(objname).set_sensitive(new_state)
def show_toolpath_simulation(self, toolpath=None): def show_toolpath_simulation(self, toolpath=None):
# disable the main controls # disable the main controls
...@@ -3531,11 +3525,14 @@ class ProjectGui(object): ...@@ -3531,11 +3525,14 @@ class ProjectGui(object):
self.cutter = toolpath_settings.get_tool() self.cutter = toolpath_settings.get_tool()
# TODO: find the right model
model = self.settings.get("models")[0]
# run the toolpath generation # run the toolpath generation
self.update_progress_bar("Starting the toolpath generation") self.update_progress_bar("Starting the toolpath generation")
try: try:
toolpath = pycam.Toolpath.Generator.generate_toolpath_from_settings( toolpath = pycam.Toolpath.Generator.generate_toolpath_from_settings(
self.model, toolpath_settings, callback=draw_callback) model, toolpath_settings, callback=draw_callback)
except Exception: except Exception:
# catch all non-system-exiting exceptions # catch all non-system-exiting exceptions
report_exception() report_exception()
...@@ -3588,8 +3585,10 @@ class ProjectGui(object): ...@@ -3588,8 +3585,10 @@ class ProjectGui(object):
# this should never happen # this should never happen
log.error("Assertion failed: invalid boundary_mode (%s)" % str(self.settings.get("boundary_mode"))) log.error("Assertion failed: invalid boundary_mode (%s)" % str(self.settings.get("boundary_mode")))
# TODO: find the right model
model = self.settings.get("models")[0]
border = (offset, offset, 0) border = (offset, offset, 0)
bounds.set_reference(self.model.get_bounds()) bounds.set_reference(model.get_bounds())
processing_bounds = Bounds(Bounds.TYPE_FIXED_MARGIN, border, border, processing_bounds = Bounds(Bounds.TYPE_FIXED_MARGIN, border, border,
reference=bounds) reference=bounds)
......
...@@ -24,11 +24,8 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -24,11 +24,8 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
import math import math
import pycam.Plugins import pycam.Plugins
import pycam.Utils.log
log = pycam.Utils.log.get_logger()
EXTRUSION_TYPES = (("radius_up", "Radius (bulge)", "ExtrusionRadiusUpIcon"), EXTRUSION_TYPES = (("radius_up", "Radius (bulge)", "ExtrusionRadiusUpIcon"),
("radius_down", "Radius (valley)", "ExtrusionRadiusDownIcon"), ("radius_down", "Radius (valley)", "ExtrusionRadiusDownIcon"),
("skewed", "Chamfer", "ExtrusionChamferIcon"), ("skewed", "Chamfer", "ExtrusionChamferIcon"),
...@@ -40,6 +37,7 @@ EXTRUSION_TYPES = (("radius_up", "Radius (bulge)", "ExtrusionRadiusUpIcon"), ...@@ -40,6 +37,7 @@ EXTRUSION_TYPES = (("radius_up", "Radius (bulge)", "ExtrusionRadiusUpIcon"),
class ModelExtrusion(pycam.Plugins.PluginBase): class ModelExtrusion(pycam.Plugins.PluginBase):
UI_FILE = "model_extrusion.ui" UI_FILE = "model_extrusion.ui"
DEPENDS = ["Models"]
def setup(self): def setup(self):
if self.gui: if self.gui:
...@@ -102,7 +100,7 @@ class ModelExtrusion(pycam.Plugins.PluginBase): ...@@ -102,7 +100,7 @@ class ModelExtrusion(pycam.Plugins.PluginBase):
elif type_string == "sigmoid": elif type_string == "sigmoid":
func = lambda x: height * ((math.sin(((min(x, width) / width) - 0.5) * math.pi) + 1) / 2) func = lambda x: height * ((math.sin(((min(x, width) / width) - 0.5) * math.pi) + 1) / 2)
else: else:
log.error("Unknown extrusion type selected: %s" % type_string) self.log.error("Unknown extrusion type selected: %s" % type_string)
return return
new_model = model.extrude(stepping=grid_size, func=func, new_model = model.extrude(stepping=grid_size, func=func,
callback=self.core.get("update_progress")) callback=self.core.get("update_progress"))
......
...@@ -27,6 +27,7 @@ import pycam.Plugins ...@@ -27,6 +27,7 @@ import pycam.Plugins
class ModelPlaneMirror(pycam.Plugins.PluginBase): class ModelPlaneMirror(pycam.Plugins.PluginBase):
UI_FILE = "model_plane_mirror.ui" UI_FILE = "model_plane_mirror.ui"
DEPENDS = ["Models"]
def setup(self): def setup(self):
if self.gui: if self.gui:
......
...@@ -27,6 +27,7 @@ import pycam.Plugins ...@@ -27,6 +27,7 @@ import pycam.Plugins
class ModelPolygons(pycam.Plugins.PluginBase): class ModelPolygons(pycam.Plugins.PluginBase):
UI_FILE = "model_polygons.ui" UI_FILE = "model_polygons.ui"
DEPENDS = ["Models"]
def setup(self): def setup(self):
if self.gui: if self.gui:
......
...@@ -27,6 +27,7 @@ import pycam.Plugins ...@@ -27,6 +27,7 @@ import pycam.Plugins
class ModelPosition(pycam.Plugins.PluginBase): class ModelPosition(pycam.Plugins.PluginBase):
UI_FILE = "model_position.ui" UI_FILE = "model_position.ui"
DEPENDS = ["Models"]
def setup(self): def setup(self):
if self.gui: if self.gui:
......
...@@ -24,14 +24,12 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -24,14 +24,12 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
import pycam.Plugins import pycam.Plugins
from pycam.Geometry.Plane import Plane from pycam.Geometry.Plane import Plane
from pycam.Geometry.Point import Point, Vector from pycam.Geometry.Point import Point, Vector
import pycam.Utils.log
log = pycam.Utils.log.get_logger()
class ModelProjection(pycam.Plugins.PluginBase): class ModelProjection(pycam.Plugins.PluginBase):
UI_FILE = "model_projection.ui" UI_FILE = "model_projection.ui"
DEPENDS = ["Models"]
def setup(self): def setup(self):
if self.gui: if self.gui:
...@@ -73,12 +71,12 @@ class ModelProjection(pycam.Plugins.PluginBase): ...@@ -73,12 +71,12 @@ class ModelProjection(pycam.Plugins.PluginBase):
self.gui.get_object("ProjectionZLevel").get_value())): self.gui.get_object("ProjectionZLevel").get_value())):
if self.gui.get_object(objname).get_active(): if self.gui.get_object(objname).get_active():
plane = Plane(Point(0, 0, z_level), Vector(0, 0, 1)) plane = Plane(Point(0, 0, z_level), Vector(0, 0, 1))
log.info("Projecting 3D model at level z=%g" % plane.p.z) self.log.info("Projecting 3D model at level z=%g" % plane.p.z)
projection = model.get_waterline_contour(plane) projection = model.get_waterline_contour(plane)
if projection: if projection:
self.core.get("load_model")(projection) self.core.get("load_model")(projection)
else: else:
log.warn("The 2D projection at z=%g is empty. Aborted." % \ self.log.warn("The 2D projection at z=%g is empty. Aborted." % \
plane.p.z) plane.p.z)
break break
...@@ -28,6 +28,7 @@ import pycam.Geometry.Matrix ...@@ -28,6 +28,7 @@ import pycam.Geometry.Matrix
class ModelRotation(pycam.Plugins.PluginBase): class ModelRotation(pycam.Plugins.PluginBase):
UI_FILE = "model_rotation.ui" UI_FILE = "model_rotation.ui"
DEPENDS = ["Models"]
def setup(self): def setup(self):
if self.gui: if self.gui:
......
...@@ -27,6 +27,7 @@ import pycam.Plugins ...@@ -27,6 +27,7 @@ import pycam.Plugins
class ModelScaling(pycam.Plugins.PluginBase): class ModelScaling(pycam.Plugins.PluginBase):
UI_FILE = "model_scaling.ui" UI_FILE = "model_scaling.ui"
DEPENDS = ["Models"]
def setup(self): def setup(self):
if self.gui: if self.gui:
......
...@@ -27,6 +27,7 @@ import pycam.Plugins ...@@ -27,6 +27,7 @@ import pycam.Plugins
class ModelSupport(pycam.Plugins.PluginBase): class ModelSupport(pycam.Plugins.PluginBase):
UI_FILE = "model_support.ui" UI_FILE = "model_support.ui"
DEPENDS = ["Models"]
def setup(self): def setup(self):
if self.gui: if self.gui:
......
...@@ -27,6 +27,7 @@ import pycam.Plugins ...@@ -27,6 +27,7 @@ import pycam.Plugins
class ModelSupportDistributed(pycam.Plugins.PluginBase): class ModelSupportDistributed(pycam.Plugins.PluginBase):
UI_FILE = "model_support_distributed.ui" UI_FILE = "model_support_distributed.ui"
DEPENDS = ["Models"]
def setup(self): def setup(self):
if self.gui: if self.gui:
......
...@@ -20,15 +20,16 @@ You should have received a copy of the GNU General Public License ...@@ -20,15 +20,16 @@ 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/>.
""" """
# gtk is imported later (on demand)
#import gtk
import pycam.Plugins import pycam.Plugins
# gtk is imported later
#import gtk
class ModelSupportGrid(pycam.Plugins.PluginBase): class ModelSupportGrid(pycam.Plugins.PluginBase):
UI_FILE = "model_support_grid.ui" UI_FILE = "model_support_grid.ui"
DEPENDS = ["Models"]
def setup(self): def setup(self):
if self.gui: if self.gui:
......
...@@ -27,6 +27,7 @@ import pycam.Plugins ...@@ -27,6 +27,7 @@ import pycam.Plugins
class ModelSwapAxes(pycam.Plugins.PluginBase): class ModelSwapAxes(pycam.Plugins.PluginBase):
UI_FILE = "model_swap_axes.ui" UI_FILE = "model_swap_axes.ui"
DEPENDS = ["Models"]
def setup(self): def setup(self):
if self.gui: if self.gui:
......
# -*- coding: utf-8 -*-
"""
$Id: __init__.py 1061 2011-04-12 13:14:12Z sumpfralle $
Copyright 2011 Lars Kruse <devel@sumpfralle.de>
This file is part of PyCAM.
PyCAM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
PyCAM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
"""
# imported later (on demand)
#import gtk
import pycam.Plugins
class Models(pycam.Plugins.ListPluginBase):
UI_FILE = "models.ui"
def setup(self):
if self.gui:
import gtk
model_frame = self.gui.get_object("ModelBox")
model_frame.unparent()
self.core.register_ui("main", "Models", model_frame, -50)
model_handling_obj = self.gui.get_object("ModelHandlingNotebook")
def clear_model_handling_obj():
for index in range(model_handling_obj.get_n_pages()):
model_handling_obj.remove_page(0)
def add_model_handling_item(item, name):
model_handling_obj.append_page(item, gtk.Label(name))
self.core.register_ui_section("model_handling",
add_model_handling_item, clear_model_handling_obj)
self._modelview = self.gui.get_object("ModelView")
for action, obj_name in ((self.ACTION_UP, "ModelMoveUp"),
(self.ACTION_DOWN, "ModelMoveDown"),
(self.ACTION_DELETE, "ModelDelete"),
(self.ACTION_CLEAR, "ModelDeleteAll")):
self.register_list_action_button(action, self._modelview,
self.gui.get_object(obj_name))
treemodel = self.gui.get_object("ModelList")
def update_model():
treemodel.clear()
for index in range(len(self)):
treemodel.append((self[index], ))
# clean the model now
self.register_model_update(update_model)
#update_model()
self.core.add_item("models", lambda: self)
return True
def get_selected(self):
return self._get_selected(self._modelview, force_list=True)
def teardown(self):
if self.gui:
self.core.unregister_ui("main", self.gui.get_object("ModelBox"))
self.core.unregister_ui_section("main", "model_handling")
self.core.set("models", None)
return True
# -*- coding: utf-8 -*-
"""
$Id$
Copyright 2011 Lars Kruse <devel@sumpfralle.de>
This file is part of PyCAM.
PyCAM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
PyCAM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
"""
import pycam.Plugins
from pycam.Geometry.Point import Point
from pycam.Geometry.Plane import Plane
"""
TODO:
- get the currently selected toolpath (from the table)
- update the current crop-polygons instantly (3D)
- update the ToolpathCropInfoLabel content if no polygons are found
"""
class ToolpathCrop(pycam.Plugins.PluginBase):
UI_FILE = "toolpath_crop.ui"
def setup(self):
if self.gui:
action_button = self.gui.get_object("ToolpathCropButton")
action_button.unparent()
self.core.register_ui("toolpath_crop", "Crop", action_button, -3)
self.core.register_event("model-change-after",
self._update_model_type_controls)
return True
def teardown(self):
if self.gui:
self.core.unregister_ui("toolpath__crop",
self.gui.get_object("ToolpathCropButton"))
def _update_model_type_controls(self):
model = self.core.get("model")
if not model:
return
# show or hide z-slice controls
can_slice = hasattr(model, "get_waterline_contour")
for name in "ToolpathCropZSliceLabel", "ToolpathCropZSlice":
if can_slice:
self.gui.get_object(name).show()
else:
self.gui.get_object(name).hide()
# set lower and upper limit for z-slice
z_slice_value = self.gui.get_object("ToolpathCropZSliceValue")
z_slice_value.set_lower(model.minz)
z_slice_value.set_upper(model.maxz)
def crop_toolpath(self, widget=None):
# TODO: how to get the currently selected toolpath???
toolpath = self.core.get("toolpath")[0]
model = self.core.get("model")
if not model:
return
if hasattr(model, "get_polygons"):
contour = model
elif hasattr(model, "get_waterline_contour"):
z_slice = self.gui.get_object("ToolpathCropZSlice").get_value()
plane = Plane(Point(0, 0, z_slice))
#self.update_progress_bar("Calculating the 2D projection")
contour = model.get_waterline_contour(plane)
else:
self.log.warn(("The current model (%s) does not support " \
+ "projections") % str(type(model)))
return
#self.update_progress_bar("Applying the tool diameter offset")
margin = self.gui.get_object("ToolpathCropMargin").get_value()
if margin:
contour = contour.get_offset_model(margin)
#self.update_progress_bar("Cropping the toolpath")
#toolpath.crop(contour.get_polygons(), callback=self.update_progress_bar)
if self.gui.get_object("ToolpathCropKeepOriginal").get_active():
new_tp = toolpath.get_cropped_copy(contour.get_polygons(),
callback=self.update_progress_bar)
new_tp.visible = True
old_index = self.core.get("toolpath").index(toolpath)
self.core.get("toolpath").insert(old_index + 1, new_tp)
else:
toolpath.crop(contour.get_polygons(), callback=self.update_progress_bar)
# -*- coding: utf-8 -*-
"""
$Id: __init__.py 1061 2011-04-12 13:14:12Z sumpfralle $
Copyright 2011 Lars Kruse <devel@sumpfralle.de>
This file is part of PyCAM.
PyCAM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
PyCAM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
"""
import pycam.Plugins
class Toolpaths(pycam.Plugins.ListPluginBase):
def setup(self):
self.core.add_item("toolpaths", self)
return True
def teardown(self):
self.core.set("toolpaths", None)
return True
...@@ -35,12 +35,14 @@ _log = pycam.Utils.log.get_logger() ...@@ -35,12 +35,14 @@ _log = pycam.Utils.log.get_logger()
class PluginBase(object): class PluginBase(object):
UI_FILE = None UI_FILE = None
DEPENDS = []
def __init__(self, core, name): def __init__(self, core, name):
self.enabled = True self.enabled = True
self.name = name self.name = name
self.core = core self.core = core
self.gui = None self.gui = None
self.log = _log
if self.UI_FILE: if self.UI_FILE:
gtk_build_file = pycam.Utils.locations.get_ui_file_location( gtk_build_file = pycam.Utils.locations.get_ui_file_location(
self.UI_FILE) self.UI_FILE)
...@@ -50,12 +52,10 @@ class PluginBase(object): ...@@ -50,12 +52,10 @@ class PluginBase(object):
self.gui.add_from_file(gtk_build_file) self.gui.add_from_file(gtk_build_file)
except RuntimeError: except RuntimeError:
self.gui = None self.gui = None
if not self.setup():
raise RuntimeError("Failed to load plugin '%s'" % str(name))
def setup(self): def setup(self):
raise NotImplementedError("Module %s (%s) does not implement " + \ raise NotImplementedError(("Module %s (%s) does not implement " + \
"'setup'" % (self.name, __file__)) "'setup'") % (self.name, __file__))
def teardown(self): def teardown(self):
raise NotImplementedError("Module %s (%s) does not implement " + \ raise NotImplementedError("Module %s (%s) does not implement " + \
...@@ -75,8 +75,10 @@ class PluginManager(object): ...@@ -75,8 +75,10 @@ class PluginManager(object):
files = os.listdir(directory) files = os.listdir(directory)
except OSError: except OSError:
return return
plugins = []
for filename in files: for filename in files:
if filename.endswith(".py") and (filename != "__init__.py") and \ if filename.endswith(".py") and \
(filename.lower() != "__init__.py") and \
os.path.isfile(os.path.join(directory, filename)): os.path.isfile(os.path.join(directory, filename)):
mod_name = filename[0:-(len(".py"))] mod_name = filename[0:-(len(".py"))]
try: try:
...@@ -86,19 +88,176 @@ class PluginManager(object): ...@@ -86,19 +88,176 @@ class PluginManager(object):
mod = imp.load_module(full_mod_name, mod_file, mod = imp.load_module(full_mod_name, mod_file,
mod_filename, mod_desc) mod_filename, mod_desc)
except ImportError: except ImportError:
_log.debug("Skipping broken plugin %s" % os.path.join( _log.info("Skipping broken plugin %s" % os.path.join(
directory, filename)) directory, filename))
continue continue
for attr in dir(mod): for attr in dir(mod):
item = getattr(mod, attr) item = getattr(mod, attr)
if inspect.isclass(item) and hasattr(item, "setup"): if inspect.isclass(item) and hasattr(item, "setup"):
self._load_plugin(item, mod_filename, attr) plugin_name = "%s.%s" % (os.path.basename(
mod_filename)[0:-len(".py")], attr)
def _load_plugin(self, obj, filename, local_name): plugins.append((item, mod_filename, attr))
name = "%s.%s" % (os.path.basename(filename)[0:-len(".py")], local_name) try_again = True
if name in self.modules: while try_again:
_log.debug("Cleaning up module %s" % name) try_again = False
self.modules[name].teardown() postponed_plugins = []
_log.debug("Initializing module %s (%s)" % (name, filename)) for plugin, filename, name in plugins:
self.modules[name] = obj(self.core, name) for dep in plugin.DEPENDS:
if not dep in self.modules and \
not "%s.%s" % (dep, dep) in self.modules:
# dependency not loaded, yet
postponed_plugins.append((plugin, filename, name))
break
else:
self._load_plugin(plugin, filename, name)
try_again = True
plugins = postponed_plugins
for plugin, filename, name in plugins:
# module failed to load due to missing dependencies
_log.info("Skipping plugin '%s' due to missing dependencies: %s" % \
(name, ", ".join(plugin.DEPENDS)))
def _load_plugin(self, obj, filename, plugin_name):
if plugin_name in self.modules:
_log.debug("Cleaning up module %s" % plugin_name)
self.modules[plugin_name].teardown()
_log.debug("Initializing module %s (%s)" % (plugin_name, filename))
new_plugin = obj(self.core, plugin_name)
if not new_plugin.setup():
raise RuntimeError("Failed to load plugin '%s'" % str(name))
else:
self.modules[plugin_name] = new_plugin
class ListPluginBase(PluginBase, list):
ACTION_UP, ACTION_DOWN, ACTION_DELETE, ACTION_CLEAR = range(4)
def __init__(self, *args, **kwargs):
super(ListPluginBase, self).__init__(*args, **kwargs)
self._update_model_funcs = []
def get_function(func_name):
return lambda *args, **kwargs: self._change_wrapper(func_name, *args, **kwargs)
for name in "append", "insert", "pop", "reverse", "sort":
setattr(self, name, get_function(name))
def _change_wrapper(self, func_name, *args, **kwargs):
value = getattr(super(ListPluginBase, self), func_name)(*args, **kwargs)
self._update_model()
return value
def _get_selected(self, modelview, index=False, force_list=False):
if hasattr(modelview, "get_selection"):
# a treeview selection
selection = modelview.get_selection()
selection_mode = selection.get_mode()
paths = selection.get_selected_rows()[1]
else:
# an iconview
selection_mode = modelview.get_selection_mode()
paths = modelview.get_selected_items()
if index:
get_result = lambda path: path[0]
else:
get_result = lambda path: self[path[0]]
if (selection_mode == gtk.SELECTION_MULTIPLE) or force_list:
result = []
for path in paths:
result.append(get_result(path))
else:
if not paths:
return None
else:
result = get_result(paths[0])
return result
def _update_model(self):
for update_func in self._update_model_funcs:
update_func()
def register_model_update(self, func):
self._update_model_funcs.append(func)
def unregister_model_update(self, func):
if func in self._update_model_funcs:
self._update_model_funcs.remove(func)
def _list_action(self, *args):
# the second-to-last paramater should be the model view
modelview = args[-2]
# the last parameter should be the action (ACTION_UP|DOWN|DELETE|CLEAR)
action = args[-1]
if not action in (self.ACTION_UP, self.ACTION_DOWN,
self.ACTION_DELETE, self.ACTION_CLEAR):
self.log.info("Invalid action for ListPluginBase.list_action: " + \
str(action))
return
selected_items = self._get_selected(modelview, index=True,
force_list=True)
selected_items.sort()
if action in (self.ACTION_DOWN, self.ACTION_DELETE):
selected_items.sort(reverse=True)
new_selection = []
if action == self.ACTION_CLEAR:
while len(self) > 0:
self.pop(0)
else:
for index in selected_items:
if action == self.ACTION_UP:
if index > 0:
item = self.pop(index)
self.insert(index - 1, item)
new_selection.append(index - 1)
elif action == self.ACTION_DOWN:
if index < len(self) - 1:
item = self.pop(index)
self.insert(index + 1, item)
new_selection.append(index + 1)
elif action == self.ACTION_DELETE:
self.pop(index)
new_selection.append(min(index, len(self) - 1))
else:
pass
self._update_model()
if hasattr(modelview, "get_selection"):
selection = modelview.get_selection()
else:
selection = modelview
selection.unselect_all()
for index in new_selection:
selection.select_path((index,))
def _update_list_action_button_state(self, *args):
modelview = args[-3]
action = args[-2]
button = args[-1]
paths = self._get_selected(modelview, index=True, force_list=True)
if action == self.ACTION_CLEAR:
button.set_sensitive(len(self) > 0)
elif not paths:
button.set_sensitive(False)
else:
if action == self.ACTION_UP:
button.set_sensitive(not 0 in paths)
elif action == self.ACTION_DOWN:
button.set_sensitive(not (len(self) - 1) in paths)
else:
button.set_sensitive(True)
def register_list_action_button(self, action, modelview, button):
if hasattr(modelview, "get_selection"):
# a treeview
selection = modelview.get_selection()
selection.connect("changed", self._update_list_action_button_state,
modelview, action, button)
else:
modelview.connect("selection-changed",
self._update_list_action_button_state, modelview, action,
button)
model = modelview.get_model()
for signal in ("row-changed", "row-deleted",
"row-has-child-toggled", "row-inserted", "rows-reordered"):
model.connect(signal, self._update_list_action_button_state,
modelview, action, button)
button.connect("clicked", self._list_action, modelview, action)
...@@ -233,6 +233,21 @@ class Toolpath(object): ...@@ -233,6 +233,21 @@ class Toolpath(object):
current_position = new_pos current_position = new_pos
return result return result
def get_cropped_copy(self, polygons, callback=None):
# create a deep copy of the current toolpath
new_paths = []
for path in self.toolpath:
if path:
new_path = Path()
for point in path.points:
new_path.append(point)
new_paths.append(new_path)
tp = Toolpath(new_paths, "%s (cropped)" % self.name,
self.toolpath_settings)
tp.visible = self.visible
tp.crop(polygons, callback=callback)
return tp
def crop(self, polygons, callback=None): def crop(self, polygons, callback=None):
# collect all existing toolpath lines # collect all existing toolpath lines
open_lines = [] open_lines = []
......
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