Commit 118c3b49 authored by Lars Kruse's avatar Lars Kruse

moved partial toolpath reading (for simulation) to a separate filter

* additional TimeLimit filter
* read feedrate and safety height from configured toolpath filters
parent 7aa44e0a
......@@ -79,7 +79,6 @@ class OpenGLViewToolpath(pycam.Plugins.PluginBase):
# TODO: enable the VBO code for speedup!
#moves = toolpath.get_moves_for_opengl(self.core.get("gcode_safety_height"))
#self._draw_toolpath_moves2(moves)
toolpath._update_safety_height(self.core.get("gcode_safety_height"))
moves = toolpath.get_basic_moves()
self._draw_toolpath_moves(moves)
......
......@@ -171,7 +171,7 @@ class ToolpathExport(pycam.Plugins.PluginBase):
machine_time = 0
# calculate the machine time and store it in the GCode header
for toolpath in toolpaths:
machine_time += toolpath.get_machine_time(safety_height)
machine_time += toolpath.get_machine_time()
all_info = meta_data + os.linesep \
+ "Estimated machine time: %.0f minutes" % machine_time
minimum_steps = [self.core.get("gcode_minimum_step_x"),
......@@ -219,7 +219,6 @@ class ToolpathExport(pycam.Plugins.PluginBase):
spindle_speed = params.get("spindle_speed", 1000)
generator.set_speed(feedrate, spindle_speed)
# TODO: implement toolpath.get_meta_data()
toolpath._update_safety_height(safety_height)
generator.add_moves(toolpath.get_basic_moves(),
tool_id=tool_id, comment="")
generator.finish()
......
......@@ -94,14 +94,9 @@ class ToolpathSimulation(pycam.Plugins.PluginBase):
# we use only one toolpath
self._toolpath = toolpaths[0]
# calculate steps
self._safety_height = self.core.get("gcode_safety_height")
self._progress.set_upper(self._toolpath.get_machine_time(
safety_height=self._safety_height))
self._duration = self._toolpath.get_machine_move_distance_and_time()[1]
self._progress.set_upper(self._duration)
self._progress.set_value(0)
self._distance = self._toolpath.get_machine_move_distance(
safety_height=self._safety_height)
self._feedrate = self._toolpath.get_params().get("tool_feedrate",
300)
self._toolpath_moves = None
self.core.set("show_simulation", True)
self._running = True
......@@ -157,8 +152,7 @@ class ToolpathSimulation(pycam.Plugins.PluginBase):
seconds=int(self._progress.get_upper()))
self._timer_widget.set_label("%s / %s" % (current, complete))
self._toolpath_moves = self._toolpath.get_moves(
safety_height=self._safety_height,
max_movement=self._distance * fraction)
max_time=self._duration * fraction)
self.core.emit_event("visual-item-updated")
def show_simulation(self):
......@@ -174,7 +168,7 @@ class ToolpathSimulation(pycam.Plugins.PluginBase):
return
else:
toolpath = self.toolpath[toolpath_index]
paths = toolpath.paths
paths = toolpath.path
# set the current cutter
self.cutter = pycam.Cutters.get_tool_from_settings(
toolpath.get_tool_settings())
......
......@@ -21,8 +21,10 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
"""
from pycam.Toolpath import MOVE_STRAIGHT, MOVE_STRAIGHT_RAPID, MOVE_SAFETY, MACHINE_SETTING
from pycam.Geometry.PointUtils import psub, pdist, ptransform_by_matrix
from pycam.Toolpath import MOVE_STRAIGHT, MOVE_STRAIGHT_RAPID, MOVE_SAFETY, \
MACHINE_SETTING
from pycam.Geometry.PointUtils import padd, psub, pmul, pdist, \
ptransform_by_matrix
from pycam.Geometry.Line import Line
from pycam.Geometry.utils import epsilon
import pycam.Utils.log
......@@ -192,3 +194,39 @@ class TransformPosition(BaseFilter):
new_path.append((move_type, args))
return new_path
class TimeLimit(BaseFilter):
""" This filter is used for the toolpath simulation. It returns only a
partial toolpath within a given duration limit.
"""
PARAMS = ("timelimit", )
def filter_toolpath(self, toolpath):
feedrate = min_feedrate = 1
new_path = []
last_pos = None
limit = self.settings["timelimit"]
duration = 0
for move_type, args in toolpath:
if move_type in (MOVE_STRAIGHT, MOVE_STRAIGHT_RAPID):
if last_pos:
new_distance = pdist(args, last_pos)
new_duration = new_distance / max(feedrate, min_feedrate)
if (new_duration > 0) and (duration + new_duration > limit):
partial = (limit - duration) / new_duration
destination = padd(last_pos, pmul(psub(args, last_pos), partial))
duration = limit
else:
destination = args
duration += new_duration
else:
destination = args
new_path.append((move_type, destination))
last_pos = args
if (move_type == MACHINE_SETTING) and (args[0] == "feedrate"):
feedrate = args[1]
if duration >= limit:
break
return new_path
......@@ -155,74 +155,8 @@ class Toolpath(object):
end_marker = self.toolpath_settings.META_MARKER_END
return os.linesep.join((start_marker, meta, end_marker))
def get_moves(self, safety_height, max_movement=None):
self._update_safety_height(safety_height)
class MoveContainer(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 move with unknown start position - ignore it
self.moves.append((new_position, rapid))
self.last_pos = new_position
return True
else:
distance = pdist(new_position, self.last_pos)
if self.moved_distance + distance > self.max_movement:
partial = (self.max_movement - self.moved_distance) / \
distance
partial_dest = padd(self.last_pos, pmul(psub(new_position, self.last_pos), 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
result = MoveContainer(max_movement)
for path in self.path:
if not path:
# ignore empty paths
continue
p_next = path[0]
if p_last is None:
p_last = (p_next[0], p_next[1], safety_height)
if not result.append(p_last, True):
return result.moves
if ((abs(p_last[0] - p_next[0]) > epsilon) or (abs(p_last[1] - p_next[1]) > epsilon)):
# Draw the connection between the last and the next path.
# Respect the safety height.
if (abs(p_last[2] - p_next[2]) > epsilon) or (pdist(p_last, p_next) > self._max_safe_distance + epsilon):
# The distance between these two points is too far.
# This condition helps to prevent moves up/down for
# adjacent lines.
safety_last = (p_last[0], p_last[1], safety_height)
safety_next = (p_next[0], p_next[1], safety_height)
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:
if not result.append(p, False):
return result.moves
p_last = path.points[-1]
if not p_last is None:
p_last_safety = (p_last[0], p_last[1], safety_height)
result.append(p_last_safety, True)
return result.moves
def get_moves(self, max_time=None):
return self.get_basic_moves() | pycam.Toolpath.Filters.TimeLimit(max_time)
def _rotate_point(self, rp, sp, v, angle):
vx = v[0]
......@@ -339,38 +273,42 @@ class Toolpath(object):
self.opengl_safety_height = safety_height
self.opengl_lines = outpaths
def get_machine_setting(self, key, default=None):
""" look for the first appearance of a machine setting (e.g. feedrate,
safety height, metric/imperial, ...). Additional occourences of this
setting are ignored.
"""
for move_type, args in self.path:
if (move_type == MACHINE_SETTING) and (key == args[0]):
return args[1]
return default
def get_machine_time(self, safety_height=0.0):
""" calculate an estimation of the time required for processing the
toolpath with the machine
@value safety_height: the safety height configured for this toolpath
@type safety_height: float
@rtype: float
@returns: the machine time used for processing the toolpath in minutes
"""
return self.get_machine_move_distance(safety_height) / self._feedrate
def _update_safety_height(self, safety_height):
# TODO: remove this ugly hack!
from pycam.Toolpath.Filters import SafetyHeightFilter
for index in range(len(self.filters)):
if isinstance(self.filters[index], SafetyHeightFilter) and \
(self.filters[index].settings["safety_height"] != safety_height):
self.filters[index] = SafetyHeightFilter(safety_height)
self.get_basic_moves(reset_cache=True)
break
return self.get_machine_move_distance_and_time()[1]
def get_machine_move_distance(self, safety_height):
result = 0
def get_machine_move_distance_and_time(self):
min_feedrate = 1
length = 0
duration = 0
feed_rate = min_feedrate
current_position = None
self._update_safety_height(safety_height)
# go through all points of the path
for move_type, args in self.get_basic_moves():
if move_type in (MOVE_STRAIGHT, MOVE_STRAIGHT_RAPID):
if (move_type == MACHINE_SETTING) and (args[0] == "feedrate"):
feedrate = args[1]
elif move_type in (MOVE_STRAIGHT, MOVE_STRAIGHT_RAPID):
if not current_position is None:
result += pdist(args, current_position)
distance = pdist(args, current_position)
duration += distance / max(feedrate, min_feedrate)
length += distance
current_position = args
return result
return length, duration
def get_basic_moves(self, reset_cache=False):
if reset_cache or not self._cache_basic_moves:
......
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