Commit 66fe3ab6 authored by sumpfralle's avatar sumpfralle

"simulation mode" now shows the toolpath over time (good for visualizing machine moves)


git-svn-id: https://pycam.svn.sourceforge.net/svnroot/pycam/trunk@885 bbaffbd6-741e-11dd-a85d-61de82d9cad9
parent 40f69b00
......@@ -5,6 +5,7 @@ Version 0.4.1 - UNRELEASED
* added support for single-line fonts text (based on fonts from QCAD)
* parallel and distributed processing is configurable in a dialog
* visualize movements up to safety height properly
* changed "simulation mode" for visualizing the machine moves
* added support for DXF feature "LWPOLYLINE"
* unify DropCutter behaviour for models that are higher than the defined bounding box
* always move up to safety height in this case
......
......@@ -4311,132 +4311,6 @@ This feature requires the Open Dynamics Engine (ODE).</property>
<property name="tab_fill">False</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="SimulationTab">
<property name="label_xalign">0</property>
<property name="shadow_type">none</property>
<child>
<object class="GtkAlignment" id="alignment29">
<property name="visible">True</property>
<property name="left_padding">12</property>
<child>
<object class="GtkVBox" id="vbox23">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkHBox" id="hbox20">
<property name="visible">True</property>
<property name="spacing">2</property>
<child>
<object class="GtkLabel" id="SimulationDetailsControlLabel">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="xpad">5</property>
<property name="label" translatable="yes">Simulation detail level (1..10):</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="SimulationDetailsControl">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="max_length">6</property>
<property name="invisible_char">&#x25CF;</property>
<property name="adjustment">SimulationDetailsValue</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">&lt;b&gt;Hint:&lt;/b&gt; take a look at the "show drill" and
"OpenGL: polygon" settings in preferences.
&lt;b&gt;Beware:&lt;/b&gt; the simulation feature is new and
experimental. Thus you will quite probably stumble
upon interesting bugs and weird results.</property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHButtonBox" id="hbuttonbox1">
<property name="visible">True</property>
<property name="spacing">3</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="UpdateSimulationButton">
<property name="label">gtk-refresh</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="ExitSimulationButton">
<property name="label">gtk-close</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="position">2</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="SimulationWidgetFrameLabel">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;b&gt;Simulation settings&lt;/b&gt;</property>
<property name="use_markup">True</property>
</object>
</child>
</object>
<packing>
<property name="position">6</property>
</packing>
</child>
<child type="tab">
<object class="GtkLabel" id="SimulationTabLabel">
<property name="visible">True</property>
<property name="label" translatable="yes">Simulation</property>
</object>
<packing>
<property name="position">6</property>
<property name="tab_fill">False</property>
</packing>
</child>
</object>
<packing>
<property name="position">0</property>
......@@ -7815,4 +7689,186 @@ Please read the description of the Server Mode (linked below) to understand the
<property name="upper">65535</property>
<property name="step_increment">1</property>
</object>
<object class="GtkAdjustment" id="SimulationSpeedFactorValueExponential">
<property name="value">1</property>
<property name="lower">-2</property>
<property name="upper">4</property>
<property name="step_increment">1</property>
</object>
<object class="GtkDialog" id="SimulationDialog">
<property name="border_width">5</property>
<property name="title" translatable="yes">PyCAM simulation</property>
<property name="modal">True</property>
<property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property>
<property name="has_separator">False</property>
<child internal-child="vbox">
<object class="GtkVBox" id="dialog-vbox9">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
<object class="GtkVBox" id="vbox23">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">3</property>
<child>
<object class="GtkHBox" id="hbox20">
<property name="spacing">2</property>
<child>
<object class="GtkLabel" id="SimulationDetailsControlLabel">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="xpad">5</property>
<property name="label" translatable="yes">Simulation detail level (1..10):</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="SimulationDetailsControl">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="max_length">6</property>
<property name="invisible_char">&#x25CF;</property>
<property name="adjustment">SimulationDetailsValue</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label2">
<property name="xalign">0</property>
<property name="label" translatable="yes">&lt;b&gt;Hint:&lt;/b&gt; take a look at the "show drill" and
"OpenGL: polygon" settings in preferences.
&lt;b&gt;Beware:&lt;/b&gt; the simulation feature is new and
experimental. Thus you will quite probably stumble
upon interesting bugs and weird results.</property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox36">
<property name="visible">True</property>
<property name="spacing">4</property>
<child>
<object class="GtkLabel" id="SimulationSpeedFactorLabel">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Simulation speed factor:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="SimulationSpeedFactorValueLabel">
<property name="visible">True</property>
<property name="label" translatable="yes">1</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkHScale" id="SimulationSpeedFactor">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">SimulationSpeedFactorValueExponential</property>
<property name="draw_value">False</property>
<property name="value_pos">bottom</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child internal-child="action_area">
<object class="GtkHButtonBox" id="dialog-action_area8">
<property name="visible">True</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="SimulationCancelButton">
<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="UpdateSimulationButton">
<property name="label">gtk-refresh</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="ExitSimulationButton">
<property name="label">gtk-close</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>
</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">SimulationCancelButton</action-widget>
<action-widget response="0">UpdateSimulationButton</action-widget>
<action-widget response="0">ExitSimulationButton</action-widget>
</action-widgets>
</object>
</interface>
......@@ -777,7 +777,9 @@ def draw_complete_model_view(settings):
GL.glColor4f(*settings.get("color_material"))
obj.to_OpenGL()
# draw the model
if settings.get("show_model"):
if settings.get("show_model") \
and not (settings.get("show_simulation") \
and settings.get("simulation_toolpath_moves")):
GL.glColor4f(*settings.get("color_model"))
model = settings.get("model")
min_area = abs(model.maxx - model.minx) * abs(model.maxy - model.miny) / 100
......@@ -801,12 +803,21 @@ def draw_complete_model_view(settings):
if settings.get("show_support_grid") and settings.get("support_grid"):
GL.glColor4f(*settings.get("color_support_grid"))
settings.get("support_grid").to_OpenGL()
# draw the toolpath simulation
if settings.get("show_simulation"):
moves = settings.get("simulation_toolpath_moves")
if not moves is None:
draw_toolpath(moves, settings.get("color_toolpath_cut"),
settings.get("color_toolpath_return"),
show_directions=settings.get("show_directions"))
# draw the toolpath
# don't do it, if a new toolpath is just being calculated
safety_height = settings.get("gcode_safety_height")
if settings.get("show_toolpath") \
and not (settings.get("show_drill_progress") \
and (not settings.get("toolpath_in_progress") is None)):
and (not settings.get("toolpath_in_progress") is None)) \
and not (settings.get("show_simulation") \
and settings.get("simulation_toolpath_moves")):
for toolpath_obj in settings.get("toolpath"):
if toolpath_obj.visible:
draw_toolpath(toolpath_obj.get_moves(safety_height),
......
......@@ -756,6 +756,17 @@ class ProjectGui:
self.gui.get_object("toolpath_simulate").connect("clicked", self.toolpath_table_event, "simulate")
self.gui.get_object("ExitSimulationButton").connect("clicked", self.finish_toolpath_simulation)
self.gui.get_object("UpdateSimulationButton").connect("clicked", self.update_toolpath_simulation)
speed_factor_widget = self.gui.get_object("SimulationSpeedFactor")
self.settings.add_item("simulation_speed_factor",
lambda: pow(10, speed_factor_widget.get_value()),
lambda value: speed_factor_widget.set_value(math.log10(max(0.001, value))))
# update the speed factor label
speed_factor_widget.connect("value-changed",
lambda widget: self.gui.get_object("SimulationSpeedFactorValueLabel").set_label(
"%g" % self.settings.get("simulation_speed_factor")))
self.simulation_window = self.gui.get_object("SimulationDialog")
self.simulation_window.connect("delete-event", self.toggle_about_window, False)
self.gui.get_object("SimulationCancelButton").connect("clicked", self.cancel_progress)
# 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()
# tool editor
......@@ -2974,7 +2985,7 @@ class ProjectGui:
self.gui.get_object("toolpath_up").set_sensitive((not new_index is None) and (new_index > 0))
self.gui.get_object("toolpath_delete").set_sensitive(not new_index is None)
self.gui.get_object("toolpath_down").set_sensitive((not new_index is None) and (new_index + 1 < len(self.toolpath)))
self.gui.get_object("toolpath_simulate").set_sensitive((not new_index is None) and pycam.Physics.ode_physics.is_ode_available())
self.gui.get_object("toolpath_simulate").set_sensitive(not new_index is None)
@gui_activity_guard
def save_task_settings_file(self, widget=None, filename=None):
......@@ -3082,15 +3093,47 @@ class ProjectGui:
def finish_toolpath_simulation(self, widget=None):
# hide the simulation tab
self.gui.get_object("SimulationTab").hide()
self.simulation_window.hide()
# enable all other tabs again
self.toggle_tabs_for_simulation(True)
self.settings.set("simulate_object", None)
self.settings.set("simulation_object", None)
self.settings.set("simulation_toolpath_moves", None)
self.settings.set("show_simulation", False)
self.update_view()
@progress_activity_guard
def update_toolpath_simulation(self, widget=None, toolpath=None):
# get the currently selected toolpath, if none is give
if toolpath is None:
toolpath_index = self._treeview_get_active_index(self.toolpath_table, self.toolpath)
if toolpath_index is None:
return
else:
toolpath = self.toolpath[toolpath_index]
# set the current cutter
self.cutter = toolpath.toolpath_settings.get_tool()
# calculate steps
safety_height = self.settings.get("gcode_safety_height")
machine_time = toolpath.get_machine_time(safety_height=safety_height)
complete_distance = toolpath.get_machine_movement_distance(
safety_height=safety_height)
current_distance = 0
time_step = 1.0 / self.settings.get("drill_progress_max_fps")
feedrate = toolpath.toolpath_settings.get_tool_settings()["feedrate"]
self.update_progress_bar("Simulating movements")
while current_distance <= complete_distance:
current_distance += self.settings.get("simulation_speed_factor") * time_step * feedrate / 60
progress_value_percent = 100.0 * current_distance / complete_distance
moves = toolpath.get_moves(safety_height=safety_height,
max_movement=current_distance)
self.settings.set("simulation_toolpath_moves", moves)
self.update_view()
if self.update_progress_bar(percent=progress_value_percent):
break
time.sleep(time_step)
@progress_activity_guard
def update_toolpath_simulation_ode(self, widget=None, toolpath=None):
import pycam.Simulation.ODEBlocks as ODEBlocks
# get the currently selected toolpath, if none is give
if toolpath is None:
......@@ -3153,9 +3196,7 @@ class ProjectGui:
# disable the main controls
self.toggle_tabs_for_simulation(False)
# show the simulation controls
self.gui.get_object("SimulationTab").show()
# switch to the simulation tab
self.gui.get_object("MainTabs").set_current_page(3)
self.simulation_window.show()
# start the simulation
self.settings.set("show_simulation", True)
self.update_toolpath_simulation(toolpath=toolpath)
......
......@@ -101,20 +101,54 @@ class Toolpath(object):
else:
self.color = color
def get_moves(self, safety_height):
result = []
paths = self.get_paths()
def get_moves(self, safety_height, max_movement=None):
class move_container(object):
def __init__(self, max_movement):
self.max_movement = max_movement
self.moved_distance = 0
self.moves = []
self.last_pos = None
if max_movement is None:
self.append = self.append_without_movement_limit
else:
self.append = self.append_with_movement_limit
def append_with_movement_limit(self, new_position, rapid):
if self.last_pos is None:
# first movement with unknown start position - thus we ignore it
self.moves.append((new_position, rapid))
self.last_pos = new_position
return True
else:
distance = new_position.sub(self.last_pos).norm
if self.moved_distance + distance > self.max_movement:
partial = (self.max_movement - self.moved_distance) / distance
partial_dest = p_last.add(new_position.sub(
self.last_pos).mul(partial))
self.moves.append((partial_dest, rapid))
self.last_pos = partial_dest
# we are finished
return False
else:
self.moves.append((new_position, rapid))
self.moved_distance += distance
self.last_pos = new_position
return True
def append_without_movement_limit(self, new_position, rapid):
self.moves.append((new_position, rapid))
return True
p_last = None
max_safe_distance = 2 * self.toolpath_settings.get_tool().radius \
+ epsilon
for path in paths:
result = move_container(max_movement)
for path in self.get_paths():
if not path:
# ignore empty paths
continue
p_next = path.points[0]
if p_last is None:
p_last = Point(p_next.x, p_next.y, safety_height)
result.append((p_last, True))
if not result.append(p_last, True):
return result.moves
if ((abs(p_last.x - p_next.x) > epsilon) \
or (abs(p_last.y - p_next.y) > epsilon)):
# Draw the connection between the last and the next path.
......@@ -126,15 +160,18 @@ class Toolpath(object):
# adjacent lines.
safety_last = Point(p_last.x, p_last.y, safety_height)
safety_next = Point(p_next.x, p_next.y, safety_height)
result.append((safety_last, True))
result.append((safety_next, True))
if not result.append(safety_last, True):
return result.moves
if not result.append(safety_next, True):
return result.moves
for p in path.points:
result.append((p, False))
if not result.append(p, False):
return result.moves
p_last = path.points[-1]
if not p_last is None:
p_last_safety = Point(p_last.x, p_last.y, safety_height)
result.append((p_last_safety, True))
return result
result.append(p_last_safety, True)
return result.moves
def get_machine_time(self, safety_height=0.0):
""" calculate an estimation of the time required for processing the
......@@ -157,6 +194,17 @@ class Toolpath(object):
current_position = new_pos
return result
def get_machine_movement_distance(self, safety_height=0.0):
result = 0
safety_height = number(safety_height)
current_position = None
# go through all points of the path
for new_pos, rapid in self.get_moves(safety_height):
if not current_position is None:
result += new_pos.sub(current_position).norm
current_position = new_pos
return result
class Bounds:
......
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