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 ...@@ -5,6 +5,7 @@ Version 0.4.1 - UNRELEASED
* added support for single-line fonts text (based on fonts from QCAD) * added support for single-line fonts text (based on fonts from QCAD)
* parallel and distributed processing is configurable in a dialog * parallel and distributed processing is configurable in a dialog
* visualize movements up to safety height properly * visualize movements up to safety height properly
* changed "simulation mode" for visualizing the machine moves
* added support for DXF feature "LWPOLYLINE" * added support for DXF feature "LWPOLYLINE"
* unify DropCutter behaviour for models that are higher than the defined bounding box * unify DropCutter behaviour for models that are higher than the defined bounding box
* always move up to safety height in this case * always move up to safety height in this case
......
This diff is collapsed.
...@@ -777,7 +777,9 @@ def draw_complete_model_view(settings): ...@@ -777,7 +777,9 @@ def draw_complete_model_view(settings):
GL.glColor4f(*settings.get("color_material")) GL.glColor4f(*settings.get("color_material"))
obj.to_OpenGL() obj.to_OpenGL()
# draw the model # 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")) GL.glColor4f(*settings.get("color_model"))
model = settings.get("model") model = settings.get("model")
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
...@@ -801,12 +803,21 @@ def draw_complete_model_view(settings): ...@@ -801,12 +803,21 @@ def draw_complete_model_view(settings):
if settings.get("show_support_grid") and settings.get("support_grid"): if settings.get("show_support_grid") and settings.get("support_grid"):
GL.glColor4f(*settings.get("color_support_grid")) GL.glColor4f(*settings.get("color_support_grid"))
settings.get("support_grid").to_OpenGL() 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 # draw the toolpath
# don't do it, if a new toolpath is just being calculated # don't do it, if a new toolpath is just being calculated
safety_height = settings.get("gcode_safety_height") safety_height = settings.get("gcode_safety_height")
if settings.get("show_toolpath") \ if settings.get("show_toolpath") \
and not (settings.get("show_drill_progress") \ 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"): for toolpath_obj in settings.get("toolpath"):
if toolpath_obj.visible: if toolpath_obj.visible:
draw_toolpath(toolpath_obj.get_moves(safety_height), draw_toolpath(toolpath_obj.get_moves(safety_height),
......
...@@ -756,6 +756,17 @@ class ProjectGui: ...@@ -756,6 +756,17 @@ class ProjectGui:
self.gui.get_object("toolpath_simulate").connect("clicked", self.toolpath_table_event, "simulate") 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("ExitSimulationButton").connect("clicked", self.finish_toolpath_simulation)
self.gui.get_object("UpdateSimulationButton").connect("clicked", self.update_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") # 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("ToolPathTabLabel").get_text()
# tool editor # tool editor
...@@ -2974,7 +2985,7 @@ class ProjectGui: ...@@ -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_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_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_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 @gui_activity_guard
def save_task_settings_file(self, widget=None, filename=None): def save_task_settings_file(self, widget=None, filename=None):
...@@ -3082,15 +3093,47 @@ class ProjectGui: ...@@ -3082,15 +3093,47 @@ class ProjectGui:
def finish_toolpath_simulation(self, widget=None): def finish_toolpath_simulation(self, widget=None):
# hide the simulation tab # hide the simulation tab
self.gui.get_object("SimulationTab").hide() self.simulation_window.hide()
# enable all other tabs again # enable all other tabs again
self.toggle_tabs_for_simulation(True) 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.settings.set("show_simulation", False)
self.update_view() self.update_view()
@progress_activity_guard @progress_activity_guard
def update_toolpath_simulation(self, widget=None, toolpath=None): 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 import pycam.Simulation.ODEBlocks as ODEBlocks
# get the currently selected toolpath, if none is give # get the currently selected toolpath, if none is give
if toolpath is None: if toolpath is None:
...@@ -3153,9 +3196,7 @@ class ProjectGui: ...@@ -3153,9 +3196,7 @@ class ProjectGui:
# disable the main controls # disable the main controls
self.toggle_tabs_for_simulation(False) self.toggle_tabs_for_simulation(False)
# show the simulation controls # show the simulation controls
self.gui.get_object("SimulationTab").show() self.simulation_window.show()
# switch to the simulation tab
self.gui.get_object("MainTabs").set_current_page(3)
# start the simulation # start the simulation
self.settings.set("show_simulation", True) self.settings.set("show_simulation", True)
self.update_toolpath_simulation(toolpath=toolpath) self.update_toolpath_simulation(toolpath=toolpath)
......
...@@ -101,20 +101,54 @@ class Toolpath(object): ...@@ -101,20 +101,54 @@ class Toolpath(object):
else: else:
self.color = color self.color = color
def get_moves(self, safety_height): def get_moves(self, safety_height, max_movement=None):
result = [] class move_container(object):
paths = self.get_paths() 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 p_last = None
max_safe_distance = 2 * self.toolpath_settings.get_tool().radius \ max_safe_distance = 2 * self.toolpath_settings.get_tool().radius \
+ epsilon + epsilon
for path in paths: result = move_container(max_movement)
for path in self.get_paths():
if not path: if not path:
# ignore empty paths # ignore empty paths
continue continue
p_next = path.points[0] p_next = path.points[0]
if p_last is None: if p_last is None:
p_last = Point(p_next.x, p_next.y, safety_height) 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) \ if ((abs(p_last.x - p_next.x) > epsilon) \
or (abs(p_last.y - p_next.y) > epsilon)): or (abs(p_last.y - p_next.y) > epsilon)):
# Draw the connection between the last and the next path. # Draw the connection between the last and the next path.
...@@ -126,15 +160,18 @@ class Toolpath(object): ...@@ -126,15 +160,18 @@ class Toolpath(object):
# adjacent lines. # adjacent lines.
safety_last = Point(p_last.x, p_last.y, safety_height) safety_last = Point(p_last.x, p_last.y, safety_height)
safety_next = Point(p_next.x, p_next.y, safety_height) safety_next = Point(p_next.x, p_next.y, safety_height)
result.append((safety_last, True)) if not result.append(safety_last, True):
result.append((safety_next, True)) return result.moves
if not result.append(safety_next, True):
return result.moves
for p in path.points: for p in path.points:
result.append((p, False)) if not result.append(p, False):
return result.moves
p_last = path.points[-1] p_last = path.points[-1]
if not p_last is None: if not p_last is None:
p_last_safety = Point(p_last.x, p_last.y, safety_height) p_last_safety = Point(p_last.x, p_last.y, safety_height)
result.append((p_last_safety, True)) result.append(p_last_safety, True)
return result return result.moves
def get_machine_time(self, safety_height=0.0): def get_machine_time(self, safety_height=0.0):
""" calculate an estimation of the time required for processing the """ calculate an estimation of the time required for processing the
...@@ -157,6 +194,17 @@ class Toolpath(object): ...@@ -157,6 +194,17 @@ class Toolpath(object):
current_position = new_pos current_position = new_pos
return result 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: 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