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): ...@@ -164,11 +164,11 @@ class ToolpathCrop(pycam.Plugins.PluginBase):
"ToolpathCropKeepOriginal").get_active() "ToolpathCropKeepOriginal").get_active()
for toolpath in self.core.get("toolpaths").get_selected(): for toolpath in self.core.get("toolpaths").get_selected():
new_tp = toolpath.get_cropped_copy(polygons) new_tp = toolpath.get_cropped_copy(polygons)
if new_tp.paths: if new_tp.path:
if keep_original: if keep_original:
self.core.get("toolpaths").append(new_tp) self.core.get("toolpaths").append(new_tp)
else: else:
toolpath.paths = new_tp.paths toolpath.path = new_tp.path
self.core.emit_event("toolpath-changed") self.core.emit_event("toolpath-changed")
else: else:
self.log.info("Toolpath cropping: the result is empty") self.log.info("Toolpath cropping: the result is empty")
......
...@@ -23,11 +23,36 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -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.Toolpath import MOVE_STRAIGHT, MOVE_STRAIGHT_RAPID, MOVE_SAFETY, MACHINE_SETTING
from pycam.Geometry.PointUtils import psub, pdist from pycam.Geometry.PointUtils import psub, pdist
from pycam.Geometry.Line import Line
from pycam.Geometry.utils import epsilon from pycam.Geometry.utils import epsilon
import pycam.Utils.log
log = pycam.Utils.log.get_logger()
class BaseFilter(object): 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): def __ror__(self, toolpath):
return self.filter_toolpath(toolpath) return self.filter_toolpath(toolpath)
...@@ -38,8 +63,7 @@ class BaseFilter(object): ...@@ -38,8 +63,7 @@ class BaseFilter(object):
class SafetyHeightFilter(BaseFilter): class SafetyHeightFilter(BaseFilter):
def __init__(self, safety_height): PARAMS = ("safety_height", )
self.safety_height = safety_height
def filter_toolpath(self, toolpath): def filter_toolpath(self, toolpath):
last_pos = None last_pos = None
...@@ -49,14 +73,16 @@ class SafetyHeightFilter(BaseFilter): ...@@ -49,14 +73,16 @@ class SafetyHeightFilter(BaseFilter):
if not last_pos: if not last_pos:
# there was a safety move (or no move at all) before # there was a safety move (or no move at all) before
# -> move sideways # -> 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)) new_path.append((MOVE_STRAIGHT_RAPID, safe_pos))
last_pos = args last_pos = args
new_path.append((move_type, args)) new_path.append((move_type, args))
elif move_type == MOVE_SAFETY: elif move_type == MOVE_SAFETY:
if last_pos: if last_pos:
# safety move -> move straight up to safety height # 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)) new_path.append((MOVE_STRAIGHT_RAPID, next_pos))
last_pos = None last_pos = None
else: else:
...@@ -70,8 +96,7 @@ class SafetyHeightFilter(BaseFilter): ...@@ -70,8 +96,7 @@ class SafetyHeightFilter(BaseFilter):
class TinySidewaysMovesFilter(BaseFilter): class TinySidewaysMovesFilter(BaseFilter):
def __init__(self, tolerance): PARAMS = ("tolerance", )
self.tolerance = tolerance
def filter_toolpath(self, toolpath): def filter_toolpath(self, toolpath):
new_path = [] new_path = []
...@@ -82,7 +107,7 @@ class TinySidewaysMovesFilter(BaseFilter): ...@@ -82,7 +107,7 @@ class TinySidewaysMovesFilter(BaseFilter):
if in_safety and last_pos: if in_safety and last_pos:
# check if the last position was very close and at the # check if the last position was very close and at the
# same height # 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): (abs(last_pos[2] - args[2]) < epsilon):
# within tolerance -> remove previous safety move # within tolerance -> remove previous safety move
new_path.pop(-1) new_path.pop(-1)
...@@ -98,10 +123,57 @@ class TinySidewaysMovesFilter(BaseFilter): ...@@ -98,10 +123,57 @@ class TinySidewaysMovesFilter(BaseFilter):
class MachineSetting(BaseFilter): class MachineSetting(BaseFilter):
def __init__(self, key, value): PARAMS = ("key", "value")
self.key = key
self.value = value 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))
# 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): def filter_toolpath(self, toolpath):
return [(MACHINE_SETTING, (self.key, self.value))] + 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 ...@@ -27,7 +27,6 @@ from OpenGL.arrays import vbo
import numpy import numpy
from numpy import array from numpy import array
from pycam.Geometry.PointUtils import * from pycam.Geometry.PointUtils import *
from pycam.Geometry.Path import Path
from pycam.Geometry.Line import Line from pycam.Geometry.Line import Line
from pycam.Geometry.utils import number, epsilon from pycam.Geometry.utils import number, epsilon
import pycam.Utils.log import pycam.Utils.log
...@@ -37,6 +36,8 @@ import os ...@@ -37,6 +36,8 @@ import os
import math import math
from itertools import groupby from itertools import groupby
log = pycam.Utils.log.get_logger()
MOVE_STRAIGHT, MOVE_STRAIGHT_RAPID, MOVE_ARC, MOVE_SAFETY, TOOL_CHANGE, \ MOVE_STRAIGHT, MOVE_STRAIGHT_RAPID, MOVE_ARC, MOVE_SAFETY, TOOL_CHANGE, \
MACHINE_SETTING = range(6) MACHINE_SETTING = range(6)
...@@ -57,13 +58,12 @@ def simplify_toolpath(path): ...@@ -57,13 +58,12 @@ def simplify_toolpath(path):
when moving along very small steps. when moving along very small steps.
The toolpath is simplified _in_place_. The toolpath is simplified _in_place_.
@value path: a single separate segment of a toolpath @value path: a single separate segment of a toolpath
@type path: pycam.Geometry.Path.Path @type path: list of points
""" """
index = 1 index = 1
points = path.points while index < len(path) - 1:
while index < len(points) - 1: if _check_colinearity(path[index-1], path[index], path[index+1]):
if _check_colinearity(points[index-1], points[index], points[index+1]): path.pop(index)
points.pop(index)
# don't increase the counter - otherwise we skip one point # don't increase the counter - otherwise we skip one point
else: else:
index += 1 index += 1
...@@ -91,6 +91,7 @@ class Toolpath(object): ...@@ -91,6 +91,7 @@ class Toolpath(object):
def clear_cache(self): def clear_cache(self):
self.opengl_safety_height = None self.opengl_safety_height = None
self._cache_basic_moves = None
self._minx = None self._minx = None
self._maxx = None self._maxx = None
self._miny = None self._miny = None
...@@ -102,13 +103,10 @@ class Toolpath(object): ...@@ -102,13 +103,10 @@ class Toolpath(object):
return dict(self.parameters) return dict(self.parameters)
def copy(self): def copy(self):
new_paths = [] new_path = list(self.path)
for path in self.path: new_tp = Toolpath(new_path, parameters=self.get_params())
new_path = Path() new_tp.filters = [f.clone() for f in self.filters]
for point in path: return new_tp
new_path.append(point)
new_paths.append(new_path)
return Toolpath(new_paths, parameters=self.get_params())
def _get_limit_generic(self, idx, func): def _get_limit_generic(self, idx, func):
values = [p[idx] for move_type, p in self.path values = [p[idx] for move_type, p in self.path
...@@ -262,7 +260,6 @@ class Toolpath(object): ...@@ -262,7 +260,6 @@ class Toolpath(object):
triangles = [(top, conepoints[idx], conepoints[idx + 1]) for idx in range ( len(conepoints) - 1)] triangles = [(top, conepoints[idx], conepoints[idx + 1]) for idx in range ( len(conepoints) - 1)]
return triangles return triangles
def get_moves_for_opengl(self, safety_height): def get_moves_for_opengl(self, safety_height):
if self.opengl_safety_height != safety_height: if self.opengl_safety_height != safety_height:
self.make_moves_for_opengl(safety_height) self.make_moves_for_opengl(safety_height)
...@@ -310,15 +307,13 @@ class Toolpath(object): ...@@ -310,15 +307,13 @@ class Toolpath(object):
self.opengl_coords = vbo.VBO(vertices) self.opengl_coords = vbo.VBO(vertices)
self.opengl_indices = output self.opengl_indices = output
#convert moves into lines for dispaly with opengl
def make_moves_for_opengl(self, safety_height): def make_moves_for_opengl(self, safety_height):
# convert moves into lines for display with opengl
working_path = [] working_path = []
outpaths = [] outpaths = []
for path in self.paths: for path in self.path:
if not path: if not path:
continue continue
path = path.points
if len(outpaths) != 0: if len(outpaths) != 0:
lastp = outpaths[-1][0][-1] lastp = outpaths[-1][0][-1]
...@@ -360,7 +355,7 @@ class Toolpath(object): ...@@ -360,7 +355,7 @@ class Toolpath(object):
from pycam.Toolpath.Filters import SafetyHeightFilter from pycam.Toolpath.Filters import SafetyHeightFilter
for index in range(len(self.filters)): for index in range(len(self.filters)):
if isinstance(self.filters[index], SafetyHeightFilter) and \ 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.filters[index] = SafetyHeightFilter(safety_height)
self.get_basic_moves(reset_cache=True) self.get_basic_moves(reset_cache=True)
break break
...@@ -376,8 +371,9 @@ class Toolpath(object): ...@@ -376,8 +371,9 @@ class Toolpath(object):
result += pdist(args, current_position) result += pdist(args, current_position)
current_position = args current_position = args
return result return result
def get_basic_moves(self, reset_cache=False): 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) result = list(self.path)
for move_filter in self.filters: for move_filter in self.filters:
result |= move_filter result |= move_filter
...@@ -391,11 +387,14 @@ class Toolpath(object): ...@@ -391,11 +387,14 @@ class Toolpath(object):
return tp return tp
def crop(self, polygons, callback=None): def crop(self, polygons, callback=None):
self.path |= pycam.Toolpath.Filters.Crop(polygons)
self.clear_cache()
return
# collect all existing toolpath lines # collect all existing toolpath lines
open_lines = [] open_lines = []
# TODO: migrate "crop" to the new toolpath structure for step in self.path:
for index in range(len(path) - 1): for index in range(len(path) - 1):
open_lines.append(Line(path[index], path[index + 1])) open_lines.append(Line(path[index], path[index + 1]))
# go through all polygons and add "inner" lines (or parts thereof) to # go through all polygons and add "inner" lines (or parts thereof) to
# the final list of remaining lines # the final list of remaining lines
inner_lines = [] inner_lines = []
...@@ -410,7 +409,7 @@ class Toolpath(object): ...@@ -410,7 +409,7 @@ class Toolpath(object):
open_lines = new_open_lines open_lines = new_open_lines
# turn all "inner_lines" into toolpath moves # turn all "inner_lines" into toolpath moves
new_paths = [] new_paths = []
current_path = Path() current_path = []
if inner_lines: if inner_lines:
line = inner_lines.pop(0) line = inner_lines.pop(0)
current_path.append(line.p1) current_path.append(line.p1)
......
...@@ -36,6 +36,29 @@ ...@@ -36,6 +36,29 @@
<property name="position">0</property> <property name="position">0</property>
</packing> </packing>
</child> </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> <child>
<object class="GtkTable" id="table1"> <object class="GtkTable" id="table1">
<property name="visible">True</property> <property name="visible">True</property>
...@@ -99,24 +122,6 @@ ...@@ -99,24 +122,6 @@
</packing> </packing>
</child> </child>
</object> </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> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="position">2</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