Commit 0e85bcfe authored by sumpfralle's avatar sumpfralle

implemented progress bar for model transformations (useful for huge models)


git-svn-id: https://pycam.svn.sourceforge.net/svnroot/pycam/trunk@538 bbaffbd6-741e-11dd-a85d-61de82d9cad9
parent ad50d9d1
...@@ -70,6 +70,10 @@ class Line(TransformableContainer): ...@@ -70,6 +70,10 @@ class Line(TransformableContainer):
yield self.p1 yield self.p1
yield self.p2 yield self.p2
def get_children_count(self):
# a line always contains two points
return 2
def reset_cache(self): def reset_cache(self):
self._dir = None self._dir = None
self._len = None self._len = None
...@@ -295,6 +299,10 @@ class LineGroup(TransformableContainer): ...@@ -295,6 +299,10 @@ class LineGroup(TransformableContainer):
for line in self._lines: for line in self._lines:
yield line yield line
def get_children_count(self):
# two children per line -> 3 times the number of lines
return 3 * len(self._lines)
def _init_line_offsets(self): def _init_line_offsets(self):
if self._lines and self._line_offsets is None: if self._lines and self._line_offsets is None:
self._line_offsets = [] self._line_offsets = []
...@@ -303,23 +311,29 @@ class LineGroup(TransformableContainer): ...@@ -303,23 +311,29 @@ class LineGroup(TransformableContainer):
for line in self._lines: for line in self._lines:
line_dir = line.dir() line_dir = line.dir()
vector = (line_dir.x, line_dir.y, line_dir.z) vector = (line_dir.x, line_dir.y, line_dir.z)
offset_vector = Matrix.multiply_vector_matrix(vector, offset_matrix) offset_vector = Matrix.multiply_vector_matrix(vector,
offset_point = Point(offset_vector[0], offset_vector[1], offset_vector[2]) offset_matrix)
self._line_offsets.append(Line(line.p1, line.p1.add(offset_point))) offset_point = Point(offset_vector[0], offset_vector[1],
offset_vector[2])
def transform_by_matrix(self, matrix, transformed_list): self._line_offsets.append(Line(line.p1,
line.p1.add(offset_point)))
def transform_by_matrix(self, matrix, transformed_list, **kwargs):
if self._lines: if self._lines:
offset_matrix = self.get_offset_matrix() offset_matrix = self.get_offset_matrix()
# initialize all offset vectors (if necessary) # initialize all offset vectors (if necessary)
self._init_line_offsets() self._init_line_offsets()
super(LineGroup, self).transform_by_matrix(matrix, transformed_list) super(LineGroup, self).transform_by_matrix(matrix, transformed_list,
**kwargs)
# transform all offset vectors # transform all offset vectors
if self._lines: if self._lines:
for offset in self._line_offsets: for offset in self._line_offsets:
if not id(offset) in transformed_list: if not id(offset) in transformed_list:
offset.transform_by_matrix(matrix, transformed_list) offset.transform_by_matrix(matrix, transformed_list,
**kwargs)
# transform the offset vector of this line group # transform the offset vector of this line group
self._offset_matrix = Matrix.multiply_matrix_matrix(matrix, offset_matrix) self._offset_matrix = Matrix.multiply_matrix_matrix(matrix,
offset_matrix)
def get_lines(self): def get_lines(self):
return self._lines[:] return self._lines[:]
......
...@@ -30,7 +30,7 @@ from pycam.Geometry.Matrix import TRANSFORMATIONS ...@@ -30,7 +30,7 @@ from pycam.Geometry.Matrix import TRANSFORMATIONS
from pycam.Toolpath import Bounds from pycam.Toolpath import Bounds
from pycam.Geometry.utils import INFINITE from pycam.Geometry.utils import INFINITE
from pycam.Geometry import TransformableContainer from pycam.Geometry import TransformableContainer
from pycam.Utils import ProgressCounter
class BaseModel(TransformableContainer): class BaseModel(TransformableContainer):
...@@ -68,6 +68,15 @@ class BaseModel(TransformableContainer): ...@@ -68,6 +68,15 @@ class BaseModel(TransformableContainer):
else: else:
yield item yield item
def get_children_count(self):
result = 0
for item_group in self._item_groups:
for item in item_group:
result += 1
if hasattr(item, "get_children_count"):
result += item.get_children_count()
return result
def to_OpenGL(self): def to_OpenGL(self):
for item in self.next(): for item in self.next():
item.to_OpenGL() item.to_OpenGL()
...@@ -128,21 +137,31 @@ class BaseModel(TransformableContainer): ...@@ -128,21 +137,31 @@ class BaseModel(TransformableContainer):
for item in self.next(): for item in self.next():
self._update_limits(item) self._update_limits(item)
def transform_by_template(self, direction="normal"): def _get_progress_callback(self, update_callback):
if update_callback:
return ProgressCounter(self.get_children_count(),
update_callback=update_callback).increment
else:
return None
def transform_by_template(self, direction="normal", callback=None):
if direction in TRANSFORMATIONS.keys(): if direction in TRANSFORMATIONS.keys():
self.transform_by_matrix(TRANSFORMATIONS[direction]) self.transform_by_matrix(TRANSFORMATIONS[direction],
callback=self._get_progress_callback(callback))
def shift(self, shift_x, shift_y, shift_z): def shift(self, shift_x, shift_y, shift_z, callback=None):
matrix = ((1, 0, 0, shift_x), (0, 1, 0, shift_y), (0, 0, 1, shift_z)) matrix = ((1, 0, 0, shift_x), (0, 1, 0, shift_y), (0, 0, 1, shift_z))
self.transform_by_matrix(matrix) self.transform_by_matrix(matrix,
callback=self._get_progress_callback(callback))
def scale(self, scale_x, scale_y=None, scale_z=None): def scale(self, scale_x, scale_y=None, scale_z=None, callback=None):
if scale_y is None: if scale_y is None:
scale_y = scale_x scale_y = scale_x
if scale_z is None: if scale_z is None:
scale_z = scale_x scale_z = scale_x
matrix = ((scale_x, 0, 0, 0), (0, scale_y, 0, 0), (0, 0, scale_z, 0)) matrix = ((scale_x, 0, 0, 0), (0, scale_y, 0, 0), (0, 0, scale_z, 0))
self.transform_by_matrix(matrix) self.transform_by_matrix(matrix,
callback=self._get_progress_callback(callback))
def get_bounds(self): def get_bounds(self):
return Bounds(Bounds.TYPE_CUSTOM, (self.minx, self.miny, self.minz), return Bounds(Bounds.TYPE_CUSTOM, (self.minx, self.miny, self.minz),
......
...@@ -59,7 +59,7 @@ class Point: ...@@ -59,7 +59,7 @@ class Point:
else: else:
return cmp(str(self), str(other)) return cmp(str(self), str(other))
def transform_by_matrix(self, matrix, transformed_list=None): def transform_by_matrix(self, matrix, transformed_list=None, callback=None):
x = self.x * matrix[0][0] + self.y * matrix[0][1] \ x = self.x * matrix[0][0] + self.y * matrix[0][1] \
+ self.z * matrix[0][2] + matrix[0][3] + self.z * matrix[0][2] + matrix[0][3]
y = self.x * matrix[1][0] + self.y * matrix[1][1] \ y = self.x * matrix[1][0] + self.y * matrix[1][1] \
...@@ -69,6 +69,8 @@ class Point: ...@@ -69,6 +69,8 @@ class Point:
self.x = x self.x = x
self.y = y self.y = y
self.z = z self.z = z
if callback:
callback()
self.reset_cache() self.reset_cache()
def reset_cache(self): def reset_cache(self):
......
...@@ -80,12 +80,17 @@ class Triangle(TransformableContainer): ...@@ -80,12 +80,17 @@ class Triangle(TransformableContainer):
yield self.p2 yield self.p2
yield self.p3 yield self.p3
def transform_by_matrix(self, matrix, transformed_list=None): def get_children_count(self):
# tree points per triangle
return 3
def transform_by_matrix(self, matrix, transformed_list=None, **kwargs):
previous_normal = self._normal previous_normal = self._normal
super(Triangle, self).transform_by_matrix(matrix, transformed_list) super(Triangle, self).transform_by_matrix(matrix, transformed_list,
**kwargs)
# try to keep the original normal vector (transform it manually) # try to keep the original normal vector (transform it manually)
if not previous_normal is None: if not previous_normal is None:
previous_normal.transform_by_matrix(matrix) previous_normal.transform_by_matrix(matrix, **kwargs)
self._normal = previous_normal self._normal = previous_normal
def name(self): def name(self):
......
...@@ -38,6 +38,10 @@ class TransformableContainer(object): ...@@ -38,6 +38,10 @@ class TransformableContainer(object):
be provided. This method is called when all children of the object were be provided. This method is called when all children of the object were
successfully transformed. successfully transformed.
A method 'get_children_count' for calculating the number of children
(recursively) is necessary for the "callback" parameter of
"transform_by_matrix".
Optionally the method 'transform_by_matrix' may be used to perform Optionally the method 'transform_by_matrix' may be used to perform
object-specific calculations (e.g. retaining the 'normal' vector of a object-specific calculations (e.g. retaining the 'normal' vector of a
triangle). triangle).
...@@ -47,7 +51,7 @@ class TransformableContainer(object): ...@@ -47,7 +51,7 @@ class TransformableContainer(object):
not required to be a subclass of TransformableContainer. not required to be a subclass of TransformableContainer.
""" """
def transform_by_matrix(self, matrix, transformed_list=None): def transform_by_matrix(self, matrix, transformed_list=None, callback=None):
if transformed_list is None: if transformed_list is None:
transformed_list = [] transformed_list = []
# Prevent any kind of loops or double transformations (e.g. Points in # Prevent any kind of loops or double transformations (e.g. Points in
...@@ -57,7 +61,8 @@ class TransformableContainer(object): ...@@ -57,7 +61,8 @@ class TransformableContainer(object):
for item in self.next(): for item in self.next():
if not id(item) in transformed_list: if not id(item) in transformed_list:
if isinstance(item, TransformableContainer): if isinstance(item, TransformableContainer):
item.transform_by_matrix(matrix, transformed_list) item.transform_by_matrix(matrix, transformed_list,
callback=callback)
else: else:
# non-TransformableContainer do not care to update the # non-TransformableContainer do not care to update the
# 'transformed_list'. Thus we need to do it. # 'transformed_list'. Thus we need to do it.
...@@ -65,7 +70,11 @@ class TransformableContainer(object): ...@@ -65,7 +70,11 @@ class TransformableContainer(object):
# Don't transmit the 'transformed_list' if the object is # Don't transmit the 'transformed_list' if the object is
# not a TransformableContainer. It is not necessary and it # not a TransformableContainer. It is not necessary and it
# is hard to understand on the lowest level (e.g. Point). # is hard to understand on the lowest level (e.g. Point).
item.transform_by_matrix(matrix) item.transform_by_matrix(matrix, callback=callback)
# run the callback - e.g. for a progress counter
if callback and callback():
# user requesteded abort
break
self.reset_cache() self.reset_cache()
def __iter__(self): def __iter__(self):
...@@ -76,6 +85,11 @@ class TransformableContainer(object): ...@@ -76,6 +85,11 @@ class TransformableContainer(object):
+ "'TransformableContainer' but it fails to implement the " \ + "'TransformableContainer' but it fails to implement the " \
+ "'next' generator") % str(type(self))) + "'next' generator") % str(type(self)))
def get_children_count(self):
raise NotImplementedError(("'%s' is a subclass of " \
+ "'TransformableContainer' but it fails to implement the " \
+ "'get_children_count' method") % str(type(self)))
def reset_cache(self): def reset_cache(self):
raise NotImplementedError(("'%s' is a subclass of " \ raise NotImplementedError(("'%s' is a subclass of " \
+ "'TransformableContainer' but it fails to implement the " \ + "'TransformableContainer' but it fails to implement the " \
......
...@@ -512,7 +512,8 @@ class ProjectGui: ...@@ -512,7 +512,8 @@ class ProjectGui:
self.progress_bar = self.gui.get_object("ProgressBar") self.progress_bar = self.gui.get_object("ProgressBar")
self.progress_widget = self.gui.get_object("ProgressWidget") self.progress_widget = self.gui.get_object("ProgressWidget")
self.task_pane = self.gui.get_object("MainTabs") self.task_pane = self.gui.get_object("MainTabs")
self.gui.get_object("ProgressCancelButton").connect("clicked", self.cancel_progress) self.progress_cancel_button = self.gui.get_object("ProgressCancelButton")
self.progress_cancel_button.connect("clicked", self.cancel_progress)
# make sure that the toolpath settings are consistent # make sure that the toolpath settings are consistent
self.toolpath_table = self.gui.get_object("ToolPathTable") self.toolpath_table = self.gui.get_object("ToolPathTable")
self.toolpath_table.get_selection().connect("changed", self.toolpath_table_event, "update_buttons") self.toolpath_table.get_selection().connect("changed", self.toolpath_table_event, "update_buttons")
...@@ -1195,6 +1196,7 @@ class ProjectGui: ...@@ -1195,6 +1196,7 @@ class ProjectGui:
# (e.g. disabled if no OpenGL support is available) # (e.g. disabled if no OpenGL support is available)
toggle_3d_checkbox.set_active(self.view3d.enabled and new_state) toggle_3d_checkbox.set_active(self.view3d.enabled and new_state)
@progress_activity_guard
@gui_activity_guard @gui_activity_guard
def transform_model(self, widget): def transform_model(self, widget):
if widget is self.gui.get_object("Rotate"): if widget is self.gui.get_object("Rotate"):
...@@ -1209,7 +1211,10 @@ class ProjectGui: ...@@ -1209,7 +1211,10 @@ class ProjectGui:
return return
for obj, value in controls: for obj, value in controls:
if self.gui.get_object(obj).get_active(): if self.gui.get_object(obj).get_active():
self.model.transform_by_template(value) self.disable_progress_cancel_button()
self.update_progress_bar("Transforming model")
self.model.transform_by_template(value,
callback=self.update_progress_bar)
self.update_view() self.update_view()
def _treeview_get_active_index(self, table, datalist): def _treeview_get_active_index(self, table, datalist):
...@@ -1604,6 +1609,7 @@ class ProjectGui: ...@@ -1604,6 +1609,7 @@ class ProjectGui:
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))
@progress_activity_guard
@gui_activity_guard @gui_activity_guard
def shift_model(self, widget, use_form_values=True): def shift_model(self, widget, use_form_values=True):
if use_form_values: if use_form_values:
...@@ -1614,7 +1620,10 @@ class ProjectGui: ...@@ -1614,7 +1620,10 @@ class ProjectGui:
shift_x = -self.model.minx shift_x = -self.model.minx
shift_y = -self.model.miny shift_y = -self.model.miny
shift_z = -self.model.minz shift_z = -self.model.minz
self.model.shift(shift_x, shift_y, shift_z) self.update_progress_bar("Shifting model")
self.disable_progress_cancel_button()
self.model.shift(shift_x, shift_y, shift_z,
callback=self.update_progress_bar)
self.update_support_grid_model() self.update_support_grid_model()
self.update_view() self.update_view()
...@@ -1629,8 +1638,11 @@ class ProjectGui: ...@@ -1629,8 +1638,11 @@ class ProjectGui:
def _set_model_center(self, center): def _set_model_center(self, center):
new_x, new_y, new_z = center new_x, new_y, new_z = center
old_x, old_y, old_z = self._get_model_center() old_x, old_y, old_z = self._get_model_center()
self.model.shift(new_x - old_x, new_y - old_y, new_z - old_z) self.update_progress_bar("Centering model")
self.model.shift(new_x - old_x, new_y - old_y, new_z - old_z,
callback=self.update_progress_bar)
@progress_activity_guard
@gui_activity_guard @gui_activity_guard
def scale_model(self, widget=None, percent=None): def scale_model(self, widget=None, percent=None):
if percent is None: if percent is None:
...@@ -1639,7 +1651,9 @@ class ProjectGui: ...@@ -1639,7 +1651,9 @@ class ProjectGui:
if (factor <= 0) or (factor == 1): if (factor <= 0) or (factor == 1):
return return
old_center = self._get_model_center() old_center = self._get_model_center()
self.model.scale(factor) self.update_progress_bar("Scaling model")
self.disable_progress_cancel_button()
self.model.scale(factor, callback=self.update_progress_bar)
self._set_model_center(old_center) self._set_model_center(old_center)
self.update_support_grid_model() self.update_support_grid_model()
self.update_view() self.update_view()
...@@ -1662,6 +1676,7 @@ class ProjectGui: ...@@ -1662,6 +1676,7 @@ class ProjectGui:
scale_value.set_sensitive(enable_controls) scale_value.set_sensitive(enable_controls)
scale_value.set_value(value) scale_value.set_value(value)
@progress_activity_guard
@gui_activity_guard @gui_activity_guard
def scale_model_axis_fit(self, widget): def scale_model_axis_fit(self, widget):
proportionally = self.gui.get_object("ScaleDimensionsProportionally").get_active() proportionally = self.gui.get_object("ScaleDimensionsProportionally").get_active()
...@@ -1672,8 +1687,10 @@ class ProjectGui: ...@@ -1672,8 +1687,10 @@ class ProjectGui:
factor = value / (getattr(self.model, "max" + axis_suffix) - getattr(self.model, "min" + axis_suffix)) factor = value / (getattr(self.model, "max" + axis_suffix) - getattr(self.model, "min" + axis_suffix))
# store the original center of the model # store the original center of the model
old_center = self._get_model_center() old_center = self._get_model_center()
self.update_progress_bar("Scaling model")
self.disable_progress_cancel_button()
if proportionally: if proportionally:
self.model.scale(factor) self.model.scale(factor, callback=self.update_progress_bar)
else: else:
factor_x, factor_y, factor_z = (1, 1, 1) factor_x, factor_y, factor_z = (1, 1, 1)
if index == 0: if index == 0:
...@@ -1684,7 +1701,8 @@ class ProjectGui: ...@@ -1684,7 +1701,8 @@ class ProjectGui:
factor_z = factor factor_z = factor
else: else:
return return
self.model.scale(factor_x, factor_y, factor_z) self.model.scale(factor_x, factor_y, factor_z,
callback=self.update_progress_bar)
# move the model to its previous center # move the model to its previous center
self._set_model_center(old_center) self._set_model_center(old_center)
self.update_support_grid_model() self.update_support_grid_model()
...@@ -2162,18 +2180,30 @@ class ProjectGui: ...@@ -2162,18 +2180,30 @@ class ProjectGui:
self.menubar.set_sensitive(False) self.menubar.set_sensitive(False)
self.task_pane.set_sensitive(False) self.task_pane.set_sensitive(False)
self.update_progress_bar("", 0) self.update_progress_bar("", 0)
self.progress_cancel_button.set_sensitive(True)
self.progress_widget.show() self.progress_widget.show()
else: else:
self.progress_widget.hide() self.progress_widget.hide()
self.task_pane.set_sensitive(True) self.task_pane.set_sensitive(True)
self.menubar.set_sensitive(True) self.menubar.set_sensitive(True)
def disable_progress_cancel_button(self):
""" mainly useful for non-interruptable operations (e.g. model
transformations)
"""
self.progress_cancel_button.set_sensitive(False)
def update_progress_bar(self, text=None, percent=None): def update_progress_bar(self, text=None, percent=None):
if not percent is None: if not percent is None:
percent = min(max(percent, 0.0), 100.0) percent = min(max(percent, 0.0), 100.0)
self.progress_bar.set_fraction(percent/100.0) self.progress_bar.set_fraction(percent/100.0)
if not text is None: if not text is None:
self.progress_bar.set_text(text) self.progress_bar.set_text(text)
# update the GUI
while gtk.events_pending():
gtk.main_iteration()
# return if the user requested a break
return self._progress_cancel_requested
def cancel_progress(self, widget=None): def cancel_progress(self, widget=None):
self._progress_cancel_requested = True self._progress_cancel_requested = True
...@@ -2223,7 +2253,9 @@ class ProjectGui: ...@@ -2223,7 +2253,9 @@ class ProjectGui:
for path_index, path in enumerate(paths): for path_index, path in enumerate(paths):
progress_text = "Simulating path %d/%d" % (path_index, len(paths)) progress_text = "Simulating path %d/%d" % (path_index, len(paths))
progress_value_percent = 100.0 * path_index / len(paths) progress_value_percent = 100.0 * path_index / len(paths)
self.update_progress_bar(progress_text, progress_value_percent) if self.update_progress_bar(progress_text, progress_value_percent):
# break if the user pressed the "cancel" button
break
for index in range(len(path.points)): for index in range(len(path.points)):
self.cutter.moveto(path.points[index]) self.cutter.moveto(path.points[index])
if index != 0: if index != 0:
...@@ -2232,13 +2264,8 @@ class ProjectGui: ...@@ -2232,13 +2264,8 @@ class ProjectGui:
if start != end: if start != end:
simulation_backend.process_cutter_movement(start, end) simulation_backend.process_cutter_movement(start, end)
self.update_view() self.update_view()
# update the GUI
while gtk.events_pending():
gtk.main_iteration()
# break the loop if someone clicked the "cancel" button # break the loop if someone clicked the "cancel" button
if self._progress_cancel_requested: if self.update_progress_bar():
break
if self._progress_cancel_requested:
break break
# enable the simulation widget again (if we were started from the GUI) # enable the simulation widget again (if we were started from the GUI)
if not widget is None: if not widget is None:
...@@ -2256,7 +2283,7 @@ class ProjectGui: ...@@ -2256,7 +2283,7 @@ class ProjectGui:
self.settings.set("show_simulation", True) self.settings.set("show_simulation", True)
self.update_toolpath_simulation(toolpath=toolpath) self.update_toolpath_simulation(toolpath=toolpath)
# hide the controls immediately, if the simulation was cancelled # hide the controls immediately, if the simulation was cancelled
if self._progress_cancel_requested: if self.update_progress_bar():
self.finish_toolpath_simulation() self.finish_toolpath_simulation()
@progress_activity_guard @progress_activity_guard
...@@ -2272,16 +2299,12 @@ class ProjectGui: ...@@ -2272,16 +2299,12 @@ class ProjectGui:
def update(self, text=None, percent=None, tool_position=None): def update(self, text=None, percent=None, tool_position=None):
if not tool_position is None: if not tool_position is None:
parent.cutter.moveto(tool_position) parent.cutter.moveto(tool_position)
parent.update_progress_bar(text, percent)
if (time.time() - self.last_update) > 1.0/self.max_fps: if (time.time() - self.last_update) > 1.0/self.max_fps:
self.last_update = time.time() self.last_update = time.time()
if self.func: if self.func:
self.func() self.func()
# update the GUI
while gtk.events_pending():
gtk.main_iteration()
# break the loop if someone clicked the "cancel" button # break the loop if someone clicked the "cancel" button
return parent._progress_cancel_requested return parent.update_progress_bar(text, percent)
if self.settings.get("show_drill_progress"): if self.settings.get("show_drill_progress"):
callback = self.update_view callback = self.update_view
else: else:
...@@ -2310,7 +2333,7 @@ class ProjectGui: ...@@ -2310,7 +2333,7 @@ class ProjectGui:
if toolpath is None: if toolpath is None:
# user interruption # user interruption
# return "False" if the action was cancelled # return "False" if the action was cancelled
return not self._progress_cancel_requested return not self.update_progress_bar()
elif isinstance(toolpath, basestring): elif isinstance(toolpath, basestring):
# an error occoured - "toolpath" contains the error message # an error occoured - "toolpath" contains the error message
log.error("Failed to generate toolpath: %s" % toolpath) log.error("Failed to generate toolpath: %s" % toolpath)
...@@ -2329,7 +2352,7 @@ class ProjectGui: ...@@ -2329,7 +2352,7 @@ class ProjectGui:
self.update_toolpath_table() self.update_toolpath_table()
self.update_view() self.update_view()
# return "False" if the action was cancelled # return "False" if the action was cancelled
return not self._progress_cancel_requested return not self.update_progress_bar()
def get_toolpath_settings(self, tool_settings, process_settings, bounds): def get_toolpath_settings(self, tool_settings, process_settings, bounds):
toolpath_settings = pycam.Gui.Settings.ToolpathSettings() toolpath_settings = pycam.Gui.Settings.ToolpathSettings()
......
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