Commit 9b5a670d authored by sumpfralle's avatar sumpfralle

added cropping of toolpath (reduces machine time)


git-svn-id: https://pycam.svn.sourceforge.net/svnroot/pycam/trunk@920 bbaffbd6-741e-11dd-a85d-61de82d9cad9
parent 6fb115db
......@@ -8,6 +8,7 @@ Version 0.4.1 - UNRELEASED
* changed "simulation mode" for visualizing the machine moves
* added a very simple "pocketing" mode for 2D models
* added 2D projection of 3D models
* added toolpath cropping
* added support for DXF feature "LWPOLYLINE"
* added a configuration setting for automatically loading a custom task settings file on startup
* added a simple "undo" feature for reversing model manipulations
......
......@@ -4282,19 +4282,34 @@ Usually you will want to use the cutter radius here to cut around the outline.</
<property name="spacing">2</property>
<child>
<object class="GtkButton" id="toolpath_simulate">
<property name="label" translatable="yes">Simulation
(experimental)</property>
<property name="label" translatable="yes">Simulate</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Show a simple simulation of the generated toolpath.</property>
<property name="image">ShowToolPathSimulation</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="toolpath_crop">
<property name="label" translatable="yes">Crop</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Crop the selected toolpath to the current 2D model (or alternatively the 2D projection of the current 3D model) with a positive offset of the tool diameter. </property>
<property name="image">CropToolpath</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="toolpath_delete">
<property name="label">gtk-delete</property>
......@@ -4306,7 +4321,7 @@ Usually you will want to use the cutter radius here to cut around the outline.</
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
<property name="position">2</property>
</packing>
</child>
<child>
......@@ -4320,7 +4335,7 @@ Usually you will want to use the cutter radius here to cut around the outline.</
</object>
<packing>
<property name="expand">False</property>
<property name="position">2</property>
<property name="position">3</property>
</packing>
</child>
<child>
......@@ -4334,7 +4349,7 @@ Usually you will want to use the cutter radius here to cut around the outline.</
</object>
<packing>
<property name="expand">False</property>
<property name="position">3</property>
<property name="position">4</property>
</packing>
</child>
</object>
......@@ -8019,4 +8034,8 @@ upon interesting bugs and weird results.</property>
<property name="upper">100</property>
<property name="step_increment">1</property>
</object>
<object class="GtkImage" id="CropToolpath">
<property name="visible">True</property>
<property name="stock">gtk-zoom-fit</property>
</object>
</interface>
......@@ -1067,3 +1067,30 @@ class Polygon(TransformableContainer):
result.append(poly)
return result
def split_line(self, line):
outer = []
inner = []
# project the line onto the polygon's plane
proj_line = self.plane.get_line_projection(line)
intersections = []
for pline in self.get_lines():
cp, d = proj_line.get_intersection(pline)
if cp:
intersections.append((cp, d))
# sort the intersections
intersections.sort(key=lambda (cp, d): d)
intersections.insert(0, (proj_line.p1, 0))
intersections.append((proj_line.p2, 1))
get_original_point = lambda d: line.p1.add(line.vector.mul(d))
for index in range(len(intersections) - 1):
p1, d1 = intersections[index]
p2, d2 = intersections[index + 1]
if p1 != p2:
middle = p1.add(p2).div(2)
new_line = Line(get_original_point(d1), get_original_point(d2))
if self.is_point_inside(middle):
inner.append(new_line)
else:
outer.append(new_line)
return (inner, outer)
......@@ -829,6 +829,7 @@ class ProjectGui:
self.gui.get_object("toolpath_down").connect("clicked", self.toolpath_table_event, "move_down")
self.gui.get_object("toolpath_delete").connect("clicked", self.toolpath_table_event, "delete")
self.gui.get_object("toolpath_simulate").connect("clicked", self.toolpath_table_event, "simulate")
self.gui.get_object("toolpath_crop").connect("clicked", self.toolpath_table_event, "crop")
self.gui.get_object("ExitSimulationButton").connect("clicked", self.finish_toolpath_simulation)
speed_factor_widget = self.gui.get_object("SimulationSpeedFactor")
self.settings.add_item("simulation_speed_factor",
......@@ -1094,8 +1095,8 @@ class ProjectGui:
# for now we only store the model
if not self.model:
return
log.debug("Storing the current state of the model for undo")
self._undo_states.append(pickle.dumps(self.model))
log.debug("Stored the current state of the model for undo")
while len(self._undo_states) > MAX_UNDO_STATES:
self._undo_states.pop(0)
self.gui.get_object("UndoButton").set_sensitive(True)
......@@ -1103,13 +1104,13 @@ class ProjectGui:
def _restore_undo_state(self, widget=None, event=None):
if len(self._undo_states) > 0:
latest = StringIO.StringIO(self._undo_states.pop(-1))
log.info("Restoring the previous state of the model")
self.model = pickle.Unpickler(latest).load()
self.gui.get_object("UndoButton").set_sensitive(
len(self._undo_states) > 0)
log.info("Restored the previous state of the model")
self._update_all_model_attributes()
else:
log.info("No previous undo state available - ignoring request")
log.info("No previous undo state available - request ignored")
def show_help(self, widget=None, page="Main_Page"):
if not page.startswith("http"):
......@@ -2536,23 +2537,27 @@ class ProjectGui:
self.model.shift(new_x - old_x, new_y - old_y, new_z - old_z,
callback=self.update_progress_bar)
@progress_activity_guard
@gui_activity_guard
def projection_2d(self, widget=None):
self.update_progress_bar("Calculating 2D projection")
def _get_projection_plane(self):
# determine projection plane
if (self.model.maxz < 0) or (self.model.minz > 0):
# completely above or below zero
plane_z = self.model.minz
else:
plane_z = 0
plane = Plane(Point(0, 0, plane_z), Vector(0, 0, 1))
log.info("Projecting 3D model at level z=%g" % plane_z)
return Plane(Point(0, 0, plane_z), Vector(0, 0, 1))
@progress_activity_guard
@gui_activity_guard
def projection_2d(self, widget=None):
self.update_progress_bar("Calculating 2D projection")
plane = self._get_projection_plane()
log.info("Projecting 3D model at level z=%g" % plane.p.z)
projection = self.model.get_waterline_contour(plane)
if projection.get_num_of_lines() > 0:
self.load_model(projection)
else:
log.warn("The 2D projection at z=%g is empty. Aborted." % plane_z)
log.warn("The 2D projection at z=%g is empty. Aborted." % plane.p.z)
@progress_activity_guard
@gui_activity_guard
......@@ -3089,12 +3094,36 @@ class ProjectGui:
index = self._treeview_get_active_index(self.toolpath_table, self.toolpath)
if not index is None:
self.show_toolpath_simulation(self.toolpath[index])
self._treeview_button_event(self.toolpath_table, self.toolpath, action, self.update_toolpath_table)
elif action == "crop":
index = self._treeview_get_active_index(self.toolpath_table, self.toolpath)
if not index is None:
self.crop_toolpath(self.toolpath[index])
# process the default operations (move, delete)
self._treeview_button_event(self.toolpath_table, self.toolpath, action,
self.update_toolpath_table)
# do some post-processing ...
if action == "delete":
if action in ("delete", "crop"):
# hide the deleted toolpath immediately
self.update_view()
@progress_activity_guard
def crop_toolpath(self, tp):
if hasattr(self.model, "get_polygons"):
contour = self.model
elif hasattr(self.model, "get_waterline_contour"):
plane = self._get_projection_plane()
self.update_progress_bar("Calculating the 2D projection")
contour = self.model.get_waterline_contour(plane)
else:
log.warn(("The current model (%s) does not support " \
+ "projections") % str(type(self.model)))
return
self.update_progress_bar("Applying the tool diameter offset")
offset_model = contour.get_offset_model(
2 * tp.get_tool_settings()["tool_radius"])
self.update_progress_bar("Cropping the toolpath")
tp.crop(offset_model.get_polygons(), callback=self.update_progress_bar)
def update_toolpath_table(self, new_index=None, skip_model_update=False):
def get_time_string(minutes):
if minutes > 180:
......@@ -3142,6 +3171,7 @@ class ProjectGui:
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_simulate").set_sensitive(not new_index is None)
self.gui.get_object("toolpath_crop").set_sensitive(not new_index is None)
@gui_activity_guard
def save_task_settings_file(self, widget=None, filename=None):
......
......@@ -23,6 +23,8 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
__all__ = ["simplify_toolpath", "ToolpathList", "Toolpath", "Generator"]
from pycam.Geometry.Point import Point
from pycam.Geometry.Path import Path
from pycam.Geometry.Line import Line
from pycam.Geometry.utils import number, epsilon
import pycam.Utils.log
import random
......@@ -205,6 +207,52 @@ class Toolpath(object):
current_position = new_pos
return result
def crop(self, polygons, callback=None):
# collect all existing toolpath lines
open_lines = []
for path in self.toolpath:
if path:
for index in range(len(path.points) - 1):
open_lines.append(Line(path.points[index], path.points[index + 1]))
# go through all polygons and add "inner" lines (or parts thereof) to
# the final list of remaining lines
inner_lines = []
for polygon in polygons:
new_open_lines = []
for line in open_lines:
if callback and callback():
return
inner, outer = polygon.split_line(line)
inner_lines.extend(inner)
new_open_lines.extend(outer)
open_lines = new_open_lines
# turn all "inner_lines" into toolpath movements
new_paths = []
current_path = Path()
if inner_lines:
line = inner_lines.pop(0)
current_path.append(line.p1)
current_path.append(line.p2)
while inner_lines:
if callback and callback():
return
end = current_path.points[-1]
# look for the next connected point
for line in inner_lines:
if line.p1 == end:
inner_lines.remove(line)
current_path.append(line.p2)
break
else:
new_paths.append(current_path)
current_path = Path()
line = inner_lines.pop(0)
current_path.append(line.p1)
current_path.append(line.p2)
if current_path.points:
new_paths.append(current_path)
self.toolpath = new_paths
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