Commit a19f544c authored by sumpfralle's avatar sumpfralle

mirgrated the path generators to a more uniform interface

added a motion-grid generator for engraving
separated the toolpath visualization


git-svn-id: https://pycam.svn.sourceforge.net/svnroot/pycam/trunk@1125 bbaffbd6-741e-11dd-a85d-61de82d9cad9
parent 16acdad6
......@@ -37,6 +37,14 @@
</row>
</data>
</object>
<object class="GtkListStore" id="ProcessList">
<columns>
<!-- column-name ref -->
<column type="gulong"/>
<!-- column-name name -->
<column type="gchararray"/>
</columns>
</object>
<object class="GtkWindow" id="window1">
<child>
<object class="GtkVPaned" id="ProcessBox">
......@@ -343,7 +351,7 @@
</packing>
</child>
<child>
<object class="GtkSpinButton" id="OverlapPercentControl">
<object class="GtkSpinButton" id="OverlapPercent">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x25CF;</property>
......@@ -370,7 +378,7 @@
</packing>
</child>
<child>
<object class="GtkSpinButton" id="MaterialAllowanceControl">
<object class="GtkSpinButton" id="MaterialAllowance">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x25CF;</property>
......@@ -387,7 +395,7 @@
</packing>
</child>
<child>
<object class="GtkSpinButton" id="MaxStepDownControl">
<object class="GtkSpinButton" id="MaxStepDown">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x25CF;</property>
......@@ -430,7 +438,7 @@
</packing>
</child>
<child>
<object class="GtkSpinButton" id="EngraveOffsetControl">
<object class="GtkSpinButton" id="EngraveOffset">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x25CF;</property>
......@@ -697,12 +705,4 @@
</object>
</child>
</object>
<object class="GtkListStore" id="ProcessList">
<columns>
<!-- column-name ref -->
<column type="gulong"/>
<!-- column-name name -->
<column type="gchararray"/>
</columns>
</object>
</interface>
......@@ -552,13 +552,14 @@
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">4</property>
</packing>
</child>
</object>
<packing>
<property name="resize">True</property>
<property name="shrink">True</property>
<property name="resize">False</property>
<property name="shrink">False</property>
</packing>
</child>
</object>
......
This diff is collapsed.
......@@ -47,6 +47,9 @@ class Line(TransformableContainer):
self.p2 = p2
self.reset_cache()
def copy(self):
return self.__class__(self.p1.copy(), self.p2.copy())
@property
def vector(self):
if self._vector is None:
......
......@@ -69,6 +69,14 @@ def get_combined_bounds(models):
high[2] = model.maxz
return low, high
def get_combined_model(models):
if not models:
return None
result = models.pop(0).copy()
while models:
result += models.pop(0)
return result
class BaseModel(TransformableContainer):
id = 0
......@@ -90,11 +98,9 @@ class BaseModel(TransformableContainer):
def __add__(self, other_model):
""" combine two models """
result = self.__class__()
for item in self.next():
result.append(item)
result = self.copy()
for item in other_model.next():
result.append(item)
result.append(item.copy())
return result
def __len__(self):
......@@ -307,6 +313,12 @@ class Model(BaseModel):
"""
return len(self._triangles)
def copy(self):
result = self.__class__(use_kdtree=self._use_kdtree)
for triangle in self.triangles():
result.append(triangle.copy())
return result
@property
def uuid(self):
if (self.__uuid is None) or self._dirty:
......@@ -421,7 +433,7 @@ class ContourModel(BaseModel):
self.name = "contourmodel%d" % self.id
if plane is None:
# the default plane points upwards along the z axis
plane = Plane(Point(0, 0, 0), Point(0, 0, 1))
plane = Plane(Point(0, 0, 0), Vector(0, 0, 1))
self._plane = plane
self._line_groups = []
self._item_groups.append(self._line_groups)
......@@ -438,6 +450,12 @@ class ContourModel(BaseModel):
"""
return len(self._line_groups)
def copy(self):
result = self.__class__(plane=self._plane.copy())
for polygon in self.get_polygons():
result.append(polygon.copy())
return result
def reset_cache(self):
super(ContourModel, self).reset_cache()
# reset the offset model cache
......
......@@ -52,6 +52,9 @@ class Plane(TransformableContainer):
else:
return cmp(str(self), str(other))
def copy(self):
return self.__class__(self.p.copy(), self.n.copy())
def next(self):
yield self.p
yield self.n
......
......@@ -50,6 +50,9 @@ class Point(object):
self._normsq = self.dot(self)
return self._normsq
def copy(self):
return self.__class__(float(self.x), float(self.y), float(self.z))
def __repr__(self):
return "Point%d<%g,%g,%g>" % (self.id, self.x, self.y, self.z)
......
......@@ -230,6 +230,9 @@ class Polygon(TransformableContainer):
self._area_cache = None
self._cached_offset_polygons = {}
def copy(self):
return self.__class__(plane=self.plane.copy())
def append(self, line):
if not self.is_connectable(line):
raise ValueError("This line does not fit to the polygon")
......
......@@ -91,6 +91,10 @@ class Triangle(TransformableContainer):
def __repr__(self):
return "Triangle%d<%s,%s,%s>" % (self.id, self.p1, self.p2, self.p3)
def copy(self):
return self.__class__(self.p1.copy(), self.p2.copy(), self.p3.copy(),
self.n.copy())
def next(self):
yield self.p1
yield self.p2
......
......@@ -22,8 +22,6 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
from pycam.Geometry.Point import Point
from pycam.Geometry.utils import sqrt
import pycam.Geometry.Model
import pycam.Utils.log
# careful import
try:
......@@ -32,32 +30,9 @@ try:
except (ImportError, RuntimeError):
pass
import gtk
import math
log = pycam.Utils.log.get_logger()
def connect_button_handlers(signal, original_button, derived_button):
""" Join two buttons (probably "toggle" buttons) to keep their values
synchronized.
"""
def derived_handler(widget, original_button=original_button):
original_button.set_active(not original_button.get_active())
derived_handler_id = derived_button.connect_object_after(
signal, derived_handler, derived_button)
def original_handler(original_button, derived_button=derived_button,
derived_handler_id=derived_handler_id):
derived_button.handler_block(derived_handler_id)
# prevent any recursive handler-triggering
if derived_button.get_active() != original_button.get_active():
derived_button.set_active(not derived_button.get_active())
derived_button.handler_unblock(derived_handler_id)
original_button.connect_object_after(signal, original_handler,
original_button)
def keep_gl_mode(func):
def keep_gl_mode_wrapper(*args, **kwargs):
prev_mode = GL.glGetIntegerv(GL.GL_MATRIX_MODE)
......@@ -143,20 +118,6 @@ def draw_complete_model_view(settings):
settings.get("color_toolpath_return"),
show_directions=settings.get("show_directions"),
lighting=settings.get("view_light"))
# draw the toolpath
# don't do it, if a new toolpath is just being calculated
safety_height = settings.get("gcode_safety_height")
if settings.get("toolpath") and settings.get("show_toolpath") \
and not settings.get("toolpath_in_progress") \
and not (settings.get("show_simulation") \
and settings.get("simulation_toolpath_moves")):
for toolpath_obj in settings.get("toolpath"):
if toolpath_obj.visible:
draw_toolpath(toolpath_obj.get_moves(safety_height),
settings.get("color_toolpath_cut"),
settings.get("color_toolpath_return"),
show_directions=settings.get("show_directions"),
lighting=settings.get("view_light"))
# draw the drill
if settings.get("show_drill"):
cutter = settings.get("cutter")
......@@ -179,37 +140,3 @@ def draw_complete_model_view(settings):
show_directions=settings.get("show_directions"),
lighting=settings.get("view_light"))
@keep_gl_mode
@keep_matrix
def draw_toolpath(moves, color_cut, color_rapid, show_directions=False, lighting=True):
GL.glMatrixMode(GL.GL_MODELVIEW)
GL.glLoadIdentity()
last_position = None
last_rapid = None
if lighting:
GL.glDisable(GL.GL_LIGHTING)
GL.glBegin(GL.GL_LINE_STRIP)
for position, rapid in moves:
if last_rapid != rapid:
GL.glEnd()
if rapid:
GL.glColor4f(*color_rapid)
else:
GL.glColor4f(*color_cut)
# we need to wait until the color change is active
GL.glFinish()
GL.glBegin(GL.GL_LINE_STRIP)
if not last_position is None:
GL.glVertex3f(last_position.x, last_position.y, last_position.z)
last_rapid = rapid
GL.glVertex3f(position.x, position.y, position.z)
last_position = position
GL.glEnd()
if lighting:
GL.glEnable(GL.GL_LIGHTING)
if show_directions:
for index in range(len(moves) - 1):
p1 = moves[index][0]
p2 = moves[index + 1][0]
draw_direction_cone(p1, p2)
This diff is collapsed.
......@@ -195,9 +195,7 @@ class CollisionPaths(object):
class ContourFollow(object):
def __init__(self, cutter, models, path_processor, physics=None):
self.cutter = cutter
self.models = models
def __init__(self, path_processor, physics=None):
self.pa = path_processor
self._up_vector = Vector(0, 0, 1)
self.physics = physics
......@@ -205,6 +203,7 @@ class ContourFollow(object):
if self.physics:
accuracy = 20
max_depth = 16
# TODO: migrate to new interface
maxx = max([m.maxx for m in self.models])
minx = max([m.minx for m in self.models])
maxy = max([m.maxy for m in self.models])
......@@ -214,15 +213,15 @@ class ContourFollow(object):
math.log(2)
self._physics_maxdepth = min(max_depth, max(ceil(depth), 4))
def _get_free_paths(self, p1, p2):
def _get_free_paths(self, cutter, models, p1, p2):
if self.physics:
return get_free_paths_ode(self.physics, p1, p2,
depth=self._physics_maxdepth)
else:
return get_free_paths_triangles(self.models, self.cutter, p1, p2)
return get_free_paths_triangles(models, cutter, p1, p2)
def GenerateToolPath(self, minx, maxx, miny, maxy, minz, maxz, dz,
draw_callback=None):
def GenerateToolPath(self, cutter, models, minx, maxx, miny, maxy, minz,
maxz, dz, draw_callback=None):
# reset the list of processed triangles
self._processed_triangles = []
# calculate the number of steps
......@@ -236,7 +235,8 @@ class ContourFollow(object):
z_step = diff_z / max(1, (num_of_layers - 1))
# only the first model is used for the contour-follow algorithm
num_of_triangles = len(self.models[0].triangles(minx=minx, miny=miny,
# TODO: should we combine all models?
num_of_triangles = len(models[0].triangles(minx=minx, miny=miny,
maxx=maxx, maxy=maxy))
progress_counter = ProgressCounter(2 * num_of_layers * num_of_triangles,
draw_callback)
......@@ -254,16 +254,16 @@ class ContourFollow(object):
# cancel immediately
break
self.pa.new_direction(0)
self.GenerateToolPathSlice(minx, maxx, miny, maxy, z,
self.GenerateToolPathSlice(cutter, models[0], minx, maxx, miny, maxy, z,
draw_callback, progress_counter, num_of_triangles)
self.pa.end_direction()
self.pa.finish()
current_layer += 1
return self.pa.paths
def GenerateToolPathSlice(self, minx, maxx, miny, maxy, z,
def GenerateToolPathSlice(self, cutter, model, minx, maxx, miny, maxy, z,
draw_callback=None, progress_counter=None, num_of_triangles=None):
shifted_lines = self.get_potential_contour_lines(minx, maxx, miny, maxy,
shifted_lines = self.get_potential_contour_lines(cutter, model, minx, maxx, miny, maxy,
z, progress_counter=progress_counter)
if num_of_triangles is None:
num_of_triangles = len(shifted_lines)
......@@ -296,14 +296,14 @@ class ContourFollow(object):
self.pa.end_scanline()
return self.pa.paths
def get_potential_contour_lines(self, minx, maxx, miny, maxy, z,
def get_potential_contour_lines(self, cutter, model, minx, maxx, miny, maxy, z,
progress_counter=None):
# use only the first model for the contour
follow_model = self.models[0]
follow_model = model
waterline_triangles = CollisionPaths()
triangles = follow_model.triangles(minx=minx, miny=miny, maxx=maxx,
maxy=maxy)
args = [(follow_model, self.cutter, self._up_vector, t, z)
args = [(follow_model, cutter, self._up_vector, t, z)
for t in triangles if not id(t) in self._processed_triangles]
results_iter = run_in_parallel(_process_one_triangle, args,
unordered=True, callback=progress_counter.update)
......
......@@ -24,6 +24,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
from pycam.PathGenerators import get_max_height_dynamic
from pycam.Utils import ProgressCounter
from pycam.Utils.threading import run_in_parallel
import pycam.Geometry.Model
import pycam.Utils.log
log = pycam.Utils.log.get_logger()
......@@ -39,51 +40,17 @@ def _process_one_grid_line((positions, minz, maxz, model, cutter, physics)):
return get_max_height_dynamic(model, cutter, positions, minz, maxz, physics)
class Dimension(object):
def __init__(self, start, end):
self.start = float(start)
self.end = float(end)
self.min = float(min(start, end))
self.max = float(max(start, end))
self.downward = start > end
self.value = 0.0
def check_bounds(self, value=None, tolerance=None):
if value is None:
value = self.value
if tolerance is None:
return (value >= self.min) and (value <= self.max)
else:
return (value > self.min - tolerance) \
and (value < self.max + tolerance)
def shift(self, distance):
if self.downward:
self.value -= distance
else:
self.value += distance
def set(self, value):
self.value = float(value)
def get(self):
return self.value
class DropCutter(object):
def __init__(self, cutter, models, path_processor, physics=None):
self.cutter = cutter
# combine the models (if there is more than one)
self.model = models[0]
for model in models[1:]:
self.model += model
def __init__(self, path_processor, physics=None):
self.pa = path_processor
self.physics = physics
# remember if we already reported an invalid boundary
def GenerateToolPath(self, motion_grid, minz, maxz, draw_callback=None):
def GenerateToolPath(self, cutter, models, motion_grid, minz=None, maxz=None, draw_callback=None):
quit_requested = False
model = pycam.Geometry.Model.get_combined_model(models)
if not model:
return
# Transfer the grid (a generator) into a list of lists and count the
# items.
......@@ -103,7 +70,7 @@ class DropCutter(object):
for one_grid_line in lines:
# simplify the data (useful for remote processing)
xy_coords = [(pos.x, pos.y) for pos in one_grid_line]
args.append((xy_coords, minz, maxz, self.model, self.cutter,
args.append((xy_coords, minz, maxz, model, cutter,
self.physics))
for points in run_in_parallel(_process_one_grid_line, args,
callback=progress_counter.update):
......
This diff is collapsed.
......@@ -45,21 +45,17 @@ def _process_one_line((p1, p2, depth, models, cutter, physics)):
class PushCutter(object):
def __init__(self, cutter, models, path_processor, physics=None):
def __init__(self, path_processor, physics=None):
if physics is None:
log.debug("Starting PushCutter (without ODE)")
else:
log.debug("Starting PushCutter (with ODE)")
self.cutter = cutter
self.models = models
self.pa = path_processor
self.physics = physics
# check if we use a PolygonExtractor
self._use_polygon_extractor = hasattr(self.pa, "pe")
def GenerateToolPath(self, motion_grid, draw_callback=None):
# calculate the number of steps
def GenerateToolPath(self, cutter, models, motion_grid, minz=None, maxz=None, draw_callback=None):
# Transfer the grid (a generator) into a list of lists and count the
# items.
grid = []
......@@ -85,15 +81,15 @@ class PushCutter(object):
break
self.pa.new_direction(0)
self.GenerateToolPathSlice(layer_grid, draw_callback,
self.GenerateToolPathSlice(cutter, models, layer_grid, draw_callback,
progress_counter)
self.pa.end_direction()
self.pa.finish()
current_layer += 1
if self._use_polygon_extractor and (len(self.models) > 1):
other_models = self.models[1:]
if self._use_polygon_extractor and (len(models) > 1):
other_models = models[1:]
# TODO: this is complicated and hacky :(
# we don't use parallelism or ODE (for the sake of simplicity)
final_pa = pycam.PathProcessors.SimpleCutter.SimpleCutter(
......@@ -105,7 +101,7 @@ class PushCutter(object):
pairs.append((path.points[index], path.points[index + 1]))
for p1, p2 in pairs:
free_points = get_free_paths_triangles(other_models,
self.cutter, p1, p2)
cutter, p1, p2)
for point in free_points:
final_pa.append(point)
final_pa.end_scanline()
......@@ -114,7 +110,7 @@ class PushCutter(object):
else:
return self.pa.paths
def GenerateToolPathSlice(self, layer_grid, draw_callback=None,
def GenerateToolPathSlice(self, cutter, models, layer_grid, draw_callback=None,
progress_counter=None):
""" only dx or (exclusive!) dy may be bigger than zero
"""
......@@ -131,14 +127,14 @@ class PushCutter(object):
# the ContourCutter pathprocessor does not work with combined models
if self._use_polygon_extractor:
models = self.models[:1]
models = models[:1]
else:
models = self.models
models = models
args = []
for line in layer_grid:
p1, p2 = line
args.append((p1, p2, depth, models, self.cutter, self.physics))
args.append((p1, p2, depth, models, cutter, self.physics))
for points in run_in_parallel(_process_one_line, args,
callback=progress_counter.update):
......
......@@ -25,6 +25,10 @@ import pycam.Plugins
import pycam.Toolpath
_RELATIVE_UNIT = ("%", "mm")
_BOUNDARY_MODES = ("inside", "along", "around")
class Bounds(pycam.Plugins.ListPluginBase):
UI_FILE = "bounds.ui"
......@@ -32,12 +36,10 @@ class Bounds(pycam.Plugins.ListPluginBase):
COLUMN_REF, COLUMN_NAME = range(2)
LIST_ATTRIBUTE_MAP = {"ref": COLUMN_REF, "name": COLUMN_NAME}
BOUNDARY_MODES = ("inside", "along", "around")
# mapping of boundary types and GUI control elements
BOUNDARY_TYPES = {
pycam.Toolpath.Bounds.TYPE_RELATIVE_MARGIN: "TypeRelativeMargin",
pycam.Toolpath.Bounds.TYPE_CUSTOM: "TypeCustom"}
RELATIVE_UNIT = ("%", "mm")
CONTROL_BUTTONS = ("TypeRelativeMargin", "TypeCustom",
"ToolLimit", "RelativeUnit", "BoundaryLowX",
"BoundaryLowY", "BoundaryLowZ", "BoundaryHighX",
......@@ -184,34 +186,6 @@ class Bounds(pycam.Plugins.ListPluginBase):
for not_found in remaining:
models.remove(not_found)
def get_bounds_limit(self, bounds):
default = (None, None, None), (None, None, None)
get_low_value = lambda axis: bounds["BoundaryLow%s" % "XYZ"[axis]]
get_high_value = lambda axis: bounds["BoundaryHigh%s" % "XYZ"[axis]]
if bounds["TypeRelativeMargin"]:
low_model, high_model = pycam.Geometry.Model.get_combined_bounds(
bounds["Models"])
if None in low_model or None in high_model:
# zero-sized models -> no action
return default
is_percent = self.RELATIVE_UNIT[bounds["RelativeUnit"]] == "%"
low, high = [], []
if is_percent:
for axis in range(3):
dim = high_model[axis] - low_model[axis]
low.append(low_model[axis] - (get_low_value(axis) / 100.0 * dim))
high.append(high_model[axis] + (get_high_value(axis) / 100.0 * dim))
else:
for axis in range(3):
low.append(low_model[axis] - get_low_value(axis))
high.append(high_model[axis] + get_high_value(axis))
else:
low, high = [], []
for axis in range(3):
low.append(get_low_value(axis))
high.append(get_high_value(axis))
return low, high
def _render_model_name(self, column, cell, model, m_iter):
path = model.get_path(m_iter)
all_models = self.core.get("models")
......@@ -386,7 +360,7 @@ class Bounds(pycam.Plugins.ListPluginBase):
self.core.emit_event("bounds-changed")
def _is_percent(self):
return self.RELATIVE_UNIT[self.gui.get_object("RelativeUnit").get_active()] == "%"
return _RELATIVE_UNIT[self.gui.get_object("RelativeUnit").get_active()] == "%"
def _update_controls(self):
bounds = self.get_selected()
......@@ -425,7 +399,22 @@ class Bounds(pycam.Plugins.ListPluginBase):
current_bounds_index = self.get_selected(index=True)
if current_bounds_index is None:
current_bounds_index = 0
new_bounds = {
new_bounds = BoundsDict()
self.append(new_bounds)
self.select(new_bounds)
def _edit_bounds_name(self, cell, path, new_text):
path = int(path)
if (new_text != self._treemodel[path][self.COLUMN_NAME]) and \
new_text:
self._treemodel[path][self.COLUMN_NAME] = new_text
class BoundsDict(dict):
def __init__(self, *args, **kwargs):
super(BoundsDict, self).__init__(*args, **kwargs)
self.update({
"BoundaryLowX": 0,
"BoundaryLowY": 0,
"BoundaryLowZ": 0,
......@@ -434,16 +423,36 @@ class Bounds(pycam.Plugins.ListPluginBase):
"BoundaryHighZ": 0,
"TypeRelativeMargin": True,
"TypeCustom": False,
"RelativeUnit": self.RELATIVE_UNIT.index("%"),
"ToolLimit": self.BOUNDARY_MODES.index("along"),
"RelativeUnit": _RELATIVE_UNIT.index("%"),
"ToolLimit": _BOUNDARY_MODES.index("along"),
"Models": [],
}
self.append(new_bounds)
self.select(new_bounds)
})
def _edit_bounds_name(self, cell, path, new_text):
path = int(path)
if (new_text != self._treemodel[path][self.COLUMN_NAME]) and \
new_text:
self._treemodel[path][self.COLUMN_NAME] = new_text
def get_absolute_limits(self):
default = (None, None, None), (None, None, None)
get_low_value = lambda axis: self["BoundaryLow%s" % "XYZ"[axis]]
get_high_value = lambda axis: self["BoundaryHigh%s" % "XYZ"[axis]]
if self["TypeRelativeMargin"]:
low_model, high_model = pycam.Geometry.Model.get_combined_bounds(
self["Models"])
if None in low_model or None in high_model:
# zero-sized models -> no action
return default
is_percent = _RELATIVE_UNIT[self["RelativeUnit"]] == "%"
low, high = [], []
if is_percent:
for axis in range(3):
dim = high_model[axis] - low_model[axis]
low.append(low_model[axis] - (get_low_value(axis) / 100.0 * dim))
high.append(high_model[axis] + (get_high_value(axis) / 100.0 * dim))
else:
for axis in range(3):
low.append(low_model[axis] - get_low_value(axis))
high.append(high_model[axis] + get_high_value(axis))
else:
low, high = [], []
for axis in range(3):
low.append(get_low_value(axis))
high.append(get_high_value(axis))
return low, high
......@@ -43,7 +43,7 @@ class OpenGLViewBounds(pycam.Plugins.PluginBase):
bounds = self.core.get("bounds").get_selected()
if not bounds:
return
low, high = self.core.get("bounds").get_bounds_limit(bounds)
low, high = bounds.get_absolute_limits()
if None in low or None in high:
return
minx, miny, minz = low[0], low[1], low[2]
......
......@@ -26,7 +26,7 @@ import pycam.Plugins
GTK_COLOR_MAX = 65535.0
class OpenGLWindow(pycam.Plugins.PluginBase):
class OpenGLViewModel(pycam.Plugins.PluginBase):
DEPENDS = ["OpenGLWindow", "Models"]
......
# -*- coding: utf-8 -*-
"""
$Id$
Copyright 2011 Lars Kruse <devel@sumpfralle.de>
This file is part of PyCAM.
PyCAM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
PyCAM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
"""
import pycam.Plugins
class OpenGLViewToolpath(pycam.Plugins.PluginBase):
DEPENDS = ["OpenGLWindow", "Toolpaths"]
def setup(self):
import OpenGL.GL
self._GL = OpenGL.GL
self.core.register_event("visualize-items", self.draw_toolpath)
return True
def teardown(self):
self.core.unregister_event("visualize-items", self.draw_toolpath)
return True
def draw_toolpath(self):
if self.core.get("show_toolpath") \
and not self.core.get("toolpath_in_progress") \
and not (self.core.get("show_simulation") \
and self.core.get("simulation_toolpath_moves")):
GL = self._GL
for toolpath in self.core.get("toolpaths").get_visible():
color_rapid = self.core.get("color_toolpath_return")
color_cut = self.core.get("color_toolpath_cut")
show_directions = self.core.get("show_directions")
lighting = self.core.get("view_light")
moves = toolpath.get_moves(self.core.get("gcode_safety_height"))
GL.glMatrixMode(GL.GL_MODELVIEW)
GL.glLoadIdentity()
last_position = None
last_rapid = None
if lighting:
GL.glDisable(GL.GL_LIGHTING)
GL.glBegin(GL.GL_LINE_STRIP)
for position, rapid in moves:
if last_rapid != rapid:
GL.glEnd()
if rapid:
GL.glColor4f(*color_rapid)
else:
GL.glColor4f(*color_cut)
# we need to wait until the color change is active
GL.glFinish()
GL.glBegin(GL.GL_LINE_STRIP)
if not last_position is None:
GL.glVertex3f(last_position.x, last_position.y, last_position.z)
last_rapid = rapid
GL.glVertex3f(position.x, position.y, position.z)
last_position = position
GL.glEnd()
if lighting:
GL.glEnable(GL.GL_LIGHTING)
if show_directions:
for index in range(len(moves) - 1):
p1 = moves[index][0]
p2 = moves[index + 1][0]
draw_direction_cone(p1, p2)
......@@ -30,8 +30,8 @@ class Processes(pycam.Plugins.ListPluginBase):
LIST_ATTRIBUTE_MAP = {"ref": COLUMN_REF, "name": COLUMN_NAME}
CONTROL_BUTTONS = ("PushRemoveStrategy", "ContourPolygonStrategy",
"ContourFollowStrategy", "SurfaceStrategy", "EngraveStrategy",
"OverlapPercentControl", "MaterialAllowanceControl",
"MaxStepDownControl", "EngraveOffsetControl",
"OverlapPercent", "MaterialAllowance",
"MaxStepDown", "EngraveOffset",
"PocketingControl", "GridDirectionX", "GridDirectionY",
"GridDirectionXY", "MillingStyleConventional", "MillingStyleClimb",
"MillingStyleIgnore")
......@@ -128,19 +128,19 @@ class Processes(pycam.Plugins.ListPluginBase):
strategy = key
break
if strategy == "PushRemoveStrategy":
text = "Slice %g%s %d%%" % (data["MaxStepDownControl"],
self.core.get("unit"), data["OverlapPercentControl"])
text = "Slice %g%s %d%%" % (data["MaxStepDown"],
self.core.get("unit"), data["OverlapPercent"])
elif strategy == "ContourPolygonStrategy":
text = "Contour (polygon) %g%s" % (data["MaxStepDownControl"],
text = "Contour (polygon) %g%s" % (data["MaxStepDown"],
self.core.get("unit"))
elif strategy == "ContourFollowStrategy":
text = "Contour (follow) %g%s" % (data["MaxStepDownControl"],
text = "Contour (follow) %g%s" % (data["MaxStepDown"],
self.core.get("unit"))
elif strategy == "SurfaceStrategy":
text = "Surface %d%%" % data["OverlapPercentControl"]
text = "Surface %d%%" % data["OverlapPercent"]
else:
# EngraveStrategy
text = "Engrave %g%s" % (data["EngraveOffsetControl"],
text = "Engrave %g%s" % (data["EngraveOffset"],
self.core.get("unit"))
cell.set_property("text", text)
......@@ -206,10 +206,10 @@ class Processes(pycam.Plugins.ListPluginBase):
"ContourFollowStrategy": False,
"SurfaceStrategy": False,
"EngraveStrategy": False,
"OverlapPercentControl": 10,
"MaterialAllowanceControl": 0,
"MaxStepDownControl": 1,
"EngraveOffsetControl": 0,
"OverlapPercent": 10,
"MaterialAllowance": 0,
"MaxStepDown": 1,
"EngraveOffset": 0,
"PocketingControl": self.POCKETING_TYPES.index("none"),
"GridDirectionX": True,
"GridDirectionY": False,
......@@ -250,26 +250,26 @@ class Processes(pycam.Plugins.ListPluginBase):
return False
all_controls = ("GridDirectionX", "GridDirectionY", "GridDirectionXY",
"MillingStyleConventional", "MillingStyleClimb",
"MillingStyleIgnore", "MaxStepDownControl",
"MaterialAllowanceControl", "OverlapPercentControl",
"EngraveOffsetControl", "PocketingControl")
"MillingStyleIgnore", "MaxStepDown",
"MaterialAllowance", "OverlapPercent",
"EngraveOffset", "PocketingControl")
active_controls = {
"PushRemoveStrategy": ("GridDirectionX", "GridDirectionY",
"GridDirectionXY", "MillingStyleConventional",
"MillingStyleClimb", "MillingStyleIgnore",
"MaxStepDownControl", "MaterialAllowanceControl",
"OverlapPercentControl"),
"MaxStepDown", "MaterialAllowance",
"OverlapPercent"),
# TODO: direction y and xy currently don't work for ContourPolygonStrategy
"ContourPolygonStrategy": ("GridDirectionX",
"MillingStyleIgnore", "MaxStepDownControl",
"MaterialAllowanceControl", "OverlapPercentControl"),
"MillingStyleIgnore", "MaxStepDown",
"MaterialAllowance", "OverlapPercent"),
"ContourFollowStrategy": ("MillingStyleConventional",
"MillingStyleClimb", "MaxStepDownControl"),
"MillingStyleClimb", "MaxStepDown"),
"SurfaceStrategy": ("GridDirectionX", "GridDirectionY",
"GridDirectionXY", "MillingStyleConventional",
"MillingStyleClimb", "MillingStyleIgnore",
"MaterialAllowanceControl", "OverlapPercentControl"),
"EngraveStrategy": ("MaxStepDownControl", "EngraveOffsetControl",
"MaterialAllowance", "OverlapPercent"),
"EngraveStrategy": ("MaxStepDown", "EngraveOffset",
"MillingStyleConventional", "MillingStyleClimb",
"PocketingControl"),
}
......
This diff is collapsed.
This diff is collapsed.
......@@ -87,9 +87,11 @@ class PluginBase(object):
except ImportError:
return
actiongroup = gtk.ActionGroup(groupname)
key, mod = gtk.accelerator_parse(accel_string)
accel_path = "<pycam>/%s" % accel_name
action.set_accel_path(accel_path)
# it is a bit pointless, but we allow an empty accel_string anyway ...
if accel_string:
key, mod = gtk.accelerator_parse(accel_string)
gtk.accel_map_change_entry(accel_path, key, mod, True)
actiongroup.add_action(action)
self.core.get("gtk-uimanager").insert_action_group(actiongroup, pos=-1)
......
......@@ -22,6 +22,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
from pycam.Geometry.Point import Point
from pycam.Geometry.utils import epsilon
from pycam.Geometry.Polygon import PolygonSorter
import math
......@@ -156,7 +157,7 @@ def get_fixed_grid_layer(minx, maxx, miny, maxy, z, line_distance,
return result, end_position
return get_lines(start, end, end_position)
def get_fixed_grid(bounds, layer_distance, line_distance, step_width=None,
def get_fixed_grid(bounds, layer_distance, line_distance=None, step_width=None,
grid_direction=GRID_DIRECTION_X, milling_style=MILLING_STYLE_IGNORE,
start_position=START_Z):
""" Calculate the grid positions for toolpath moves
......@@ -172,6 +173,7 @@ def get_fixed_grid(bounds, layer_distance, line_distance, step_width=None,
reverse=bool(start_position & START_Z))
def get_layers_with_direction(layers):
for layer in layers:
# this will produce a nice xy-grid, as well as simple x and y grids
if grid_direction != GRID_DIRECTION_Y:
yield (layer, GRID_DIRECTION_X)
if grid_direction != GRID_DIRECTION_X:
......@@ -183,3 +185,106 @@ def get_fixed_grid(bounds, layer_distance, line_distance, step_width=None,
start_position=start_position)
yield result
def get_lines_layer(lines, z, last_z=None, step_width=None,
milling_style=MILLING_STYLE_CONVENTIONAL):
get_proj_point = lambda proj_point: Point(proj_point.x, proj_point.y, z)
projected_lines = []
for line in lines:
if (not last_z is None) and (last_z < line.minz):
# the line was processed before
continue
elif line.minz < z < line.maxz:
# Split the line at the point at z level and do the calculation
# for both point pairs.
factor = (z - line.p1.z) / (line.p2.z - line.p1.z)
plane_point = line.p1.add(line.vector.mul(factor))
if line.p1.z < z:
p1 = get_proj_point(line.p1)
p2 = line.p2
else:
p1 = line.p1
p2 = get_proj_point(line.p2)
projected_lines.append(Line(p1, plane_point))
yield Line(plane_point, p2)
elif line.minz < last_z < line.maxz:
plane = Plane(Point(0, 0, last_z), Vector(0, 0, 1))
cp = plane.intersect_point(line.dir, line.p1)[0]
# we can be sure that there is an intersection
if line.p1.z > last_z:
p1, p2 = cp, line.p2
else:
p1, p2 = line.p1, cp
projected_lines.append(Line(p1, p2))
else:
if line.maxz <= z:
# the line is completely below z
projected_lines.append(Line(get_proj_point(line.p1),
get_proj_point(line.p2)))
elif line.minz >= z:
projected_lines.append(line)
else:
log.warn("Unexpected condition 'get_lines_layer': " + \
"%s / %s / %s / %s" % (line.p1, line.p2, z, last_z))
# process all projected lines
for line in projected_lines:
if step_width is None:
yield line.p1
yield line.p2
else:
if isiterable(step_width):
steps = step_width
else:
steps = floatrange(0.0, line.len, inc=step_width)
for step in steps:
yield line.p1.add(line.dir.mul(step))
def _get_sorted_polygons(models, callback=None):
# Sort the polygons according to their directions (first inside, then
# outside. This reduces the problem of break-away pieces.
inner_polys = []
outer_polys = []
for model in models:
for poly in model.get_polygons():
if poly.get_area() <= 0:
inner_polys.append(poly)
else:
outer_polys.append(poly)
inner_sorter = PolygonSorter(inner_polys, callback=callback)
outer_sorter = PolygonSorter(outer_polys, callback=callback)
return inner_sorter.get_polygons() + outer_sorter.get_polygons()
def get_lines_grid(models, bounds, layer_distance, line_distance=None,
step_width=None, grid_direction=None,
milling_style=MILLING_STYLE_CONVENTIONAL,
start_position=None, callback=None):
lines = []
for polygon in _get_sorted_polygons(models, callback=callback):
if polygon.is_closed and \
(milling_style == MILLING_STYLE_CONVENTIONAL):
polygon = polygon.copy()
polygon.reverse()
for line in polygon.get_lines():
lines.append(line)
low, high = bounds.get_absolute_limits()
# the lower limit is never below the model
low_limit_lines = min([line.minz for line in lines])
low[2] = max(low[2], low_limit_lines)
if isiterable(layer_distance):
layers = layer_distance
elif layer_distance is None:
# only one layer
layers = [low[2]]
else:
layers = floatrange(low[2], high[2], inc=layer_distance,
reverse=bool(start_position & START_Z))
last_z = None
if layers:
# the upper layers are used for PushCutter operations
for z in layers[:-1]:
yield get_lines_layer(lines, z, last_z=last_z,
step_width=None, milling_style=milling_style)
last_z = z
# the last layer is used for a DropCutter operation
yield get_lines_layer(lines, layers[-1], last_z=last_z,
step_width=step_width, milling_style=milling_style)
......@@ -22,7 +22,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
__all__ = ["iterators", "polynomials", "ProgressCounter", "threading",
"get_platform", "URIHandler", "PLATFORM_WINDOWS", "PLATFORM_MACOS",
"PLATFORM_LINUX", "PLATFORM_UNKNOWN"]
"PLATFORM_LINUX", "PLATFORM_UNKNOWN", "get_exception_report"]
import sys
import os
......@@ -30,6 +30,7 @@ import re
import socket
import urllib
import urlparse
import traceback
# this is imported below on demand
#import win32com
#import win32api
......@@ -39,6 +40,7 @@ PLATFORM_WINDOWS = 1
PLATFORM_MACOS = 2
PLATFORM_UNKNOWN = 3
# setproctitle is (optionally) imported
try:
from setproctitle import setproctitle
......@@ -191,6 +193,11 @@ def get_all_ips():
filtered_result.sort(cmp=sort_ip_by_relevance)
return filtered_result
def get_exception_report():
return "An unexpected exception occoured: please send the " \
+ "text below to the developers of PyCAM. Thanks a lot!" \
+ os.linesep + traceback.format_exc()
class ProgressCounter(object):
......
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