Commit 6c161057 authored by Lars Kruse's avatar Lars Kruse

fixed toolpath cropping; implement filter cloning

* implement toolpath cropping via a filter
* highlighted warning label in toolpath cropping UI dialog
* filters gain a "clone" method (this allows deep copies of toolpaths)
* MachineSetting filter preserves the order of settings (before: last in first out)
parent a36ef40c
......@@ -164,11 +164,11 @@ class ToolpathCrop(pycam.Plugins.PluginBase):
"ToolpathCropKeepOriginal").get_active()
for toolpath in self.core.get("toolpaths").get_selected():
new_tp = toolpath.get_cropped_copy(polygons)
if new_tp.paths:
if new_tp.path:
if keep_original:
self.core.get("toolpaths").append(new_tp)
else:
toolpath.paths = new_tp.paths
toolpath.path = new_tp.path
self.core.emit_event("toolpath-changed")
else:
self.log.info("Toolpath cropping: the result is empty")
......
......@@ -23,11 +23,36 @@ 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
from pycam.Geometry.Line import Line
from pycam.Geometry.utils import epsilon
import pycam.Utils.log
log = pycam.Utils.log.get_logger()
class BaseFilter(object):
PARAMS = []
def __init__(self, *args, **kwargs):
self.settings = dict(kwargs)
# fail if too many arguments (without names) are given
if len(args) > len(self.PARAMS):
raise ValueError("Too many parameters: " + \
"%d (expected: %d)" % (len(args), len(self.PARAMS)))
# fail if too fee arguments (without names) are given
for index, key in enumerate(self.PARAMS):
if len(args) > index:
self.settings[key] = args[index]
elif key in self.settings:
# named parameter are ok, as well
pass
else:
raise ValueError("Missing parameter: %s" % str(key))
def clone(self):
return self.__class__(**self.settings)
def __ror__(self, toolpath):
return self.filter_toolpath(toolpath)
......@@ -38,8 +63,7 @@ class BaseFilter(object):
class SafetyHeightFilter(BaseFilter):
def __init__(self, safety_height):
self.safety_height = safety_height
PARAMS = ("safety_height", )
def filter_toolpath(self, toolpath):
last_pos = None
......@@ -49,14 +73,16 @@ class SafetyHeightFilter(BaseFilter):
if not last_pos:
# there was a safety move (or no move at all) before
# -> move sideways
safe_pos = (args[0], args[1], self.safety_height)
safe_pos = (args[0], args[1],
self.settings["safety_height"])
new_path.append((MOVE_STRAIGHT_RAPID, safe_pos))
last_pos = args
new_path.append((move_type, args))
elif move_type == MOVE_SAFETY:
if last_pos:
# safety move -> move straight up to safety height
next_pos = (last_pos[0], last_pos[1], self.safety_height)
next_pos = (last_pos[0], last_pos[1],
self.settings["safety_height"])
new_path.append((MOVE_STRAIGHT_RAPID, next_pos))
last_pos = None
else:
......@@ -70,8 +96,7 @@ class SafetyHeightFilter(BaseFilter):
class TinySidewaysMovesFilter(BaseFilter):
def __init__(self, tolerance):
self.tolerance = tolerance
PARAMS = ("tolerance", )
def filter_toolpath(self, toolpath):
new_path = []
......@@ -82,7 +107,7 @@ class TinySidewaysMovesFilter(BaseFilter):
if in_safety and last_pos:
# check if the last position was very close and at the
# same height
if (pdist(last_pos, args) < self.tolerance) and \
if (pdist(last_pos, args) < self.settings["tolerance"]) and \
(abs(last_pos[2] - args[2]) < epsilon):
# within tolerance -> remove previous safety move
new_path.pop(-1)
......@@ -98,10 +123,57 @@ class TinySidewaysMovesFilter(BaseFilter):
class MachineSetting(BaseFilter):
def __init__(self, key, value):
self.key = key
self.value = value
PARAMS = ("key", "value")
def filter_toolpath(self, toolpath):
return [(MACHINE_SETTING, (self.key, self.value))] + 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))
# add the new setting
result.append((MACHINE_SETTING, (self.settings["key"], self.settings["value"])))
return result + toolpath
class Crop(BaseFilter):
PARAMS = ("polygons", )
def filter_toolpath(self, toolpath):
new_path = []
last_pos = None
optional_moves = []
for move_type, args in toolpath:
if move_type in (MOVE_STRAIGHT, MOVE_STRAIGHT_RAPID):
if last_pos:
# find all remaining pieces of this line
inner_lines = []
for polygon in self.settings["polygons"]:
inner, outer = polygon.split_line(Line(last_pos, args))
inner_lines.extend(inner)
# turn these lines into moves
for line in inner_lines:
if pdist(line.p1, last_pos) > epsilon:
new_path.append((MOVE_SAFETY, None))
new_path.append((move_type, line.p1))
else:
# we continue were we left
if optional_moves:
new_path.extend(optional_moves)
optional_moves = []
new_path.append((move_type, line.p2))
last_pos = line.p2
optional_moves = []
# finish the line by moving to its end (if necessary)
if pdist(last_pos, args) > epsilon:
optional_moves.append((MOVE_SAFETY, None))
optional_moves.append((move_type, args))
last_pos = args
elif move_type == MOVE_SAFETY:
optional_moves = []
else:
new_path.append((move_type, args))
return new_path
......@@ -27,7 +27,6 @@ from OpenGL.arrays import vbo
import numpy
from numpy import array
from pycam.Geometry.PointUtils import *
from pycam.Geometry.Path import Path
from pycam.Geometry.Line import Line
from pycam.Geometry.utils import number, epsilon
import pycam.Utils.log
......@@ -37,6 +36,8 @@ import os
import math
from itertools import groupby
log = pycam.Utils.log.get_logger()
MOVE_STRAIGHT, MOVE_STRAIGHT_RAPID, MOVE_ARC, MOVE_SAFETY, TOOL_CHANGE, \
MACHINE_SETTING = range(6)
......@@ -57,13 +58,12 @@ def simplify_toolpath(path):
when moving along very small steps.
The toolpath is simplified _in_place_.
@value path: a single separate segment of a toolpath
@type path: pycam.Geometry.Path.Path
@type path: list of points
"""
index = 1
points = path.points
while index < len(points) - 1:
if _check_colinearity(points[index-1], points[index], points[index+1]):
points.pop(index)
while index < len(path) - 1:
if _check_colinearity(path[index-1], path[index], path[index+1]):
path.pop(index)
# don't increase the counter - otherwise we skip one point
else:
index += 1
......@@ -91,6 +91,7 @@ class Toolpath(object):
def clear_cache(self):
self.opengl_safety_height = None
self._cache_basic_moves = None
self._minx = None
self._maxx = None
self._miny = None
......@@ -102,13 +103,10 @@ class Toolpath(object):
return dict(self.parameters)
def copy(self):
new_paths = []
for path in self.path:
new_path = Path()
for point in path:
new_path.append(point)
new_paths.append(new_path)
return Toolpath(new_paths, parameters=self.get_params())
new_path = list(self.path)
new_tp = Toolpath(new_path, parameters=self.get_params())
new_tp.filters = [f.clone() for f in self.filters]
return new_tp
def _get_limit_generic(self, idx, func):
values = [p[idx] for move_type, p in self.path
......@@ -262,7 +260,6 @@ class Toolpath(object):
triangles = [(top, conepoints[idx], conepoints[idx + 1]) for idx in range ( len(conepoints) - 1)]
return triangles
def get_moves_for_opengl(self, safety_height):
if self.opengl_safety_height != safety_height:
self.make_moves_for_opengl(safety_height)
......@@ -310,15 +307,13 @@ class Toolpath(object):
self.opengl_coords = vbo.VBO(vertices)
self.opengl_indices = output
#convert moves into lines for dispaly with opengl
def make_moves_for_opengl(self, safety_height):
# convert moves into lines for display with opengl
working_path = []
outpaths = []
for path in self.paths:
for path in self.path:
if not path:
continue
path = path.points
if len(outpaths) != 0:
lastp = outpaths[-1][0][-1]
......@@ -360,7 +355,7 @@ class Toolpath(object):
from pycam.Toolpath.Filters import SafetyHeightFilter
for index in range(len(self.filters)):
if isinstance(self.filters[index], SafetyHeightFilter) and \
(self.filters[index].safety_height != safety_height):
(self.filters[index].settings["safety_height"] != safety_height):
self.filters[index] = SafetyHeightFilter(safety_height)
self.get_basic_moves(reset_cache=True)
break
......@@ -376,8 +371,9 @@ class Toolpath(object):
result += pdist(args, current_position)
current_position = args
return result
def get_basic_moves(self, reset_cache=False):
if reset_cache or not hasattr(self, "_cache_basic_moves"):
if reset_cache or not self._cache_basic_moves:
result = list(self.path)
for move_filter in self.filters:
result |= move_filter
......@@ -391,9 +387,12 @@ class Toolpath(object):
return tp
def crop(self, polygons, callback=None):
self.path |= pycam.Toolpath.Filters.Crop(polygons)
self.clear_cache()
return
# collect all existing toolpath lines
open_lines = []
# TODO: migrate "crop" to the new toolpath structure
for step in self.path:
for index in range(len(path) - 1):
open_lines.append(Line(path[index], path[index + 1]))
# go through all polygons and add "inner" lines (or parts thereof) to
......@@ -410,7 +409,7 @@ class Toolpath(object):
open_lines = new_open_lines
# turn all "inner_lines" into toolpath moves
new_paths = []
current_path = Path()
current_path = []
if inner_lines:
line = inner_lines.pop(0)
current_path.append(line.p1)
......
......@@ -36,6 +36,29 @@
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkViewport" id="ToolpathCropInfoBox">
<property name="visible">True</property>
<property name="resize_mode">queue</property>
<property name="shadow_type">etched-out</property>
<child>
<object class="GtkLabel" id="ToolpathCropInfo">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="ypad">1</property>
<property name="label" translatable="yes">label</property>
<attributes>
<attribute name="weight" value="semibold"/>
<attribute name="foreground" value="#ffff00000000"/>
</attributes>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkTable" id="table1">
<property name="visible">True</property>
......@@ -99,24 +122,6 @@
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkViewport" id="ToolpathCropInfoBox">
<property name="visible">True</property>
<property name="resize_mode">queue</property>
<property name="shadow_type">etched-out</property>
<child>
<object class="GtkLabel" id="ToolpathCropInfo">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">label</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">2</property>
......
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