Commit fcad5299 authored by Lars Kruse's avatar Lars Kruse

added tool-based toolpath filters

* SelectTool
* TriggerSpindle
* StepWidth
parent 559d0962
......@@ -21,8 +21,10 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
"""
import decimal
from pycam.Toolpath import MOVE_STRAIGHT, MOVE_STRAIGHT_RAPID, MOVE_SAFETY, \
MACHINE_SETTING
MOVES_LIST, MACHINE_SETTING
from pycam.Geometry.PointUtils import padd, psub, pmul, pdist, \
ptransform_by_matrix
from pycam.Geometry.Line import Line
......@@ -32,6 +34,19 @@ import pycam.Utils.log
log = pycam.Utils.log.get_logger()
def toolpath_filter(our_category, key):
""" decorator for toolpath filter functions
"""
def toolpath_filter_inner(func):
def get_filter_func(self, category, parameters, previous_filters):
if (category == our_category) and (key in parameters):
result = func(self, parameters[key])
if result:
previous_filters.extend(result)
return get_filter_func
return toolpath_filter_inner
class BaseFilter(object):
PARAMS = []
......@@ -59,7 +74,8 @@ class BaseFilter(object):
# allow to use pycam.Toolpath.Toolpath instances (instead of a list)
if hasattr(toolpath, "path") and hasattr(toolpath, "get_params"):
toolpath = toolpath.path
return self.filter_toolpath(toolpath)
# use a copy of the list -> changes will be permitted
return self.filter_toolpath(list(toolpath))
def filter_toolpath(self, toolpath):
raise NotImplementedError("The filter class %s failed to " + \
......@@ -136,8 +152,6 @@ class MachineSetting(BaseFilter):
def filter_toolpath(self, toolpath):
result = []
# prepare a copy
toolpath = list(toolpath)
# move all previous machine settings
while toolpath and toolpath[0][0] == MACHINE_SETTING:
result.append(toolpath.pop(0))
......@@ -146,6 +160,54 @@ class MachineSetting(BaseFilter):
return result + toolpath
class SelectTool(BaseFilter):
PARAMS = ("tool_id", )
def filter_toolpath(self, toolpath):
result = []
# move all previous machine settings
while toolpath and \
not toolpath[0][0] in MOVES_LIST:
result.append(toolpath.pop(0))
result.append((MACHINE_SETTING,
("select_tool", self.settings["tool_id"])))
return result + toolpath
class TriggerSpindle(BaseFilter):
PARAMS = ("delay", )
def filter_toolpath(self, toolpath):
def enable_spindle(path, index):
path.insert(index, (MACHINE_SETTING, ("spindle_enabled", True)))
if self.settings["delay"]:
path.insert(index + 1,
(MACHINE_SETTING, ("delay", self.settings["delay"])))
def disable_spindle(path, index):
path.insert(index, (MACHINE_SETTING, ("spindle_enabled", True)))
# move all previous machine settings
tool_changes = [index for index, (move, args) in enumerate(toolpath)
if (move == MACHINE_SETTING) and (args[0] == "select_tool")]
if tool_changes:
tool_changes.reverse()
for index in tool_changes:
enable_spindle(toolpath, index + 1)
else:
for index, (move, args) in enumerate(toolpath):
if move_type in MOVES_LIST:
enable_spindle(toolpath, index)
break
# add "stop spindle" just after the last move
index = len(result) - 1
while (not result[-index][0] in MOVES_LIST) and (index > 0):
index -= 1
if result[index][0] in MOVES_LIST:
disable_spindle(toolpath, index + 1)
return toolpath
class Crop(BaseFilter):
PARAMS = ("polygons", )
......@@ -239,6 +301,7 @@ class TimeLimit(BaseFilter):
break
return new_path
class MovesOnly(BaseFilter):
""" Use this filter for checking if a given toolpath is empty/useless
(only machine settings, safety moves, ...).
......@@ -251,5 +314,68 @@ class MovesOnly(BaseFilter):
class Copy(BaseFilter):
def filter_toolpath(self, toolpath):
return list(toolpath)
return toolpath
def _get_num_of_significant_digits(number):
""" Determine the number of significant digits of a float number. """
# use only positive numbers
number = abs(number)
max_diff = 0.1 ** MAX_DIGITS
if number <= max_diff:
# input value is smaller than the smallest usable number
return MAX_DIGITS
elif number >= 1:
# no negative number of significant digits
return 0
else:
for digit in range(1, MAX_DIGITS):
shifted = number * (10 ** digit)
if shifted - int(shifted) < max_diff:
return digit
else:
return MAX_DIGITS
def _get_num_converter(step_width):
""" Return a float-to-decimal conversion function with a prevision suitable
for the given step width.
"""
digits = _get_num_of_significant_digits(step_width)
format_string = "%%.%df" % digits
conv_func = lambda number: decimal.Decimal(format_string % number)
return conv_func, format_string
class StepWidth(BaseFilter):
PARAMS = ("step_width_x", "step_width_y", "step_width_z")
NUM_OF_AXES = 3
def filter_toolpath(self, toolpath):
minimum_steps = []
axes_formatter = []
for key in "xyz":
minimum_steps.append(self.settings["step_width_%s" % key])
for step_width in minimum_steps:
conv, format_string = _get_num_converter(step_width)
axes_formatter.append((conv(step_width), conv, format_string))
last_pos = None
path = []
for move_type, args in toolpath:
if move_type in (MOVE_STRAIGHT, MOVE_STRAIGHT_RAPID):
if last_pos:
diff = [(abs(conv[i](last_pos[i]) - conv[i](args[i])))
for i in range(3)]
if all([d < lim for d, lim in zip(diff, minimum_steps)]):
# too close: ignore this move
continue
destination = [conv[i](args[i]) for i in range(3)]
path.append((move_type, destination))
last_pos = args
else:
# forget "last_pos" - we don't know what happened in between
last_pos = None
path.append((move_type, args))
return path
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