Commit 0385eb03 authored by sumpfralle's avatar sumpfralle

added proof-of-concept for automatic support grid positioning


git-svn-id: https://pycam.svn.sourceforge.net/svnroot/pycam/trunk@622 bbaffbd6-741e-11dd-a85d-61de82d9cad9
parent 2a4bdb52
Version 0.3.1 - UNRELEASED
* added automatic support grid positioning for contour models
Version 0.3.0 - 2010-08-16 Version 0.3.0 - 2010-08-16
* added support for importing contour paths from SVG files (requires Inkscape and pstoedit) * added support for importing contour paths from SVG files (requires Inkscape and pstoedit)
* added basic support for importing simple DXF contour files * added basic support for importing simple DXF contour files
......
This diff is collapsed.
...@@ -94,6 +94,9 @@ class Point: ...@@ -94,6 +94,9 @@ class Point:
def dot(self, p): def dot(self, p):
return self.x * p.x + self.y * p.y + self.z * p.z return self.x * p.x + self.y * p.y + self.z * p.z
def size(self):
return sqrt(self.dot(self))
def cross(self, p): def cross(self, p):
return Point(self.y * p.z - p.y * self.z, p.x * self.z - self.x * p.z, return Point(self.y * p.z - p.y * self.z, p.x * self.z - self.x * p.z,
self.x * p.y - p.x * self.y) self.x * p.y - p.x * self.y)
......
...@@ -36,7 +36,7 @@ class Polygon(TransformableContainer): ...@@ -36,7 +36,7 @@ class Polygon(TransformableContainer):
# TODO: derive the plane from the appended points # TODO: derive the plane from the appended points
if plane is None: if plane is None:
plane = Plane(Point(0, 0, 0), Point(0, 0, 1)) plane = Plane(Point(0, 0, 0), Point(0, 0, 1))
self._plane = plane self.plane = plane
self._points = [] self._points = []
self._is_closed = False self._is_closed = False
self.maxx = None self.maxx = None
...@@ -102,10 +102,10 @@ class Polygon(TransformableContainer): ...@@ -102,10 +102,10 @@ class Polygon(TransformableContainer):
def next(self): def next(self):
for point in self._points: for point in self._points:
yield point yield point
yield self._plane yield self.plane
def get_children_count(self): def get_children_count(self):
return len(self._points) + self._plane.get_children_count() return len(self._points) + self.plane.get_children_count()
def get_area(self): def get_area(self):
""" calculate the area covered by a line group """ calculate the area covered by a line group
...@@ -126,6 +126,27 @@ class Polygon(TransformableContainer): ...@@ -126,6 +126,27 @@ class Polygon(TransformableContainer):
value += p1.x * p2.y - p2.x * p1.y value += p1.x * p2.y - p2.x * p1.y
return value / 2 return value / 2
def get_length(self):
""" add the length of all lines within the polygon
"""
return sum(self.get_lengths())
def get_middle_of_line(self, index):
if (index >= len(self._points)) \
or (not self._is_closed and index == len(self._points) - 1):
return None
else:
return self._points[index].add(self._points[(index + 1) % len(self._points)]).div(2)
def get_lengths(self):
result = []
for index in range(len(self._points) - 1):
result.append(self._points[index + 1].sub(
self._points[index]).size())
if self._is_closed:
result.append(self._points[0].sub(self._points[-1]).size())
return result
def get_max_inside_distance(self): def get_max_inside_distance(self):
""" calculate the maximum distance between two points of the polygon """ calculate the maximum distance between two points of the polygon
""" """
...@@ -243,10 +264,10 @@ class Polygon(TransformableContainer): ...@@ -243,10 +264,10 @@ class Polygon(TransformableContainer):
skel_dir = d1.add(d2).normalized() skel_dir = d1.add(d2).normalized()
if skel_dir is None: if skel_dir is None:
# the two vectors pointed to opposite directions # the two vectors pointed to opposite directions
skel_dir = d1.cross(self._plane.n).normalized() skel_dir = d1.cross(self.plane.n).normalized()
else: else:
skel_up_vector = skel_dir.cross(p2.sub(p1)) skel_up_vector = skel_dir.cross(p2.sub(p1))
offset_up_vector = self._plane.n offset_up_vector = self.plane.n
# TODO: check for other axis as well # TODO: check for other axis as well
if offset_up_vector.z * skel_up_vector.z < 0: if offset_up_vector.z * skel_up_vector.z < 0:
# reverse the skeleton vector to point outwards # reverse the skeleton vector to point outwards
...@@ -257,7 +278,7 @@ class Polygon(TransformableContainer): ...@@ -257,7 +278,7 @@ class Polygon(TransformableContainer):
def get_shifted_vertex(index, offset): def get_shifted_vertex(index, offset):
p1 = self._points[index] p1 = self._points[index]
p2 = self._points[(index + 1) % len(self._points)] p2 = self._points[(index + 1) % len(self._points)]
cross_offset = p2.sub(p1).cross(self._plane.n).normalized() cross_offset = p2.sub(p1).cross(self.plane.n).normalized()
bisector_normalized = self.get_bisector(index) bisector_normalized = self.get_bisector(index)
factor = cross_offset.dot(bisector_normalized) factor = cross_offset.dot(bisector_normalized)
if factor != 0: if factor != 0:
...@@ -375,7 +396,7 @@ class Polygon(TransformableContainer): ...@@ -375,7 +396,7 @@ class Polygon(TransformableContainer):
self_is_outer = self.is_outer() self_is_outer = self.is_outer()
groups = [] groups = []
for lines in cleaned_line_groups: for lines in cleaned_line_groups:
group = Polygon(self._plane) group = Polygon(self.plane)
for line in lines: for line in lines:
group.append(line) group.append(line)
if group.is_outer() != self_is_outer: if group.is_outer() != self_is_outer:
...@@ -408,7 +429,7 @@ class Polygon(TransformableContainer): ...@@ -408,7 +429,7 @@ class Polygon(TransformableContainer):
if offset == 0: if offset == 0:
return Line(line.p1, line.p2) return Line(line.p1, line.p2)
else: else:
cross_offset = line.dir.cross(self._plane.n).normalized().mul(offset) cross_offset = line.dir.cross(self.plane.n).normalized().mul(offset)
# Prolong the line at the beginning and at the end - to allow # Prolong the line at the beginning and at the end - to allow
# overlaps. Use factor "2" to take care for star-like structure # overlaps. Use factor "2" to take care for star-like structure
# where a complete convex triangle would get cropped (two lines # where a complete convex triangle would get cropped (two lines
...@@ -581,7 +602,7 @@ class Polygon(TransformableContainer): ...@@ -581,7 +602,7 @@ class Polygon(TransformableContainer):
self_is_outer = self.is_outer() self_is_outer = self.is_outer()
groups = [] groups = []
for lines in cleaned_line_groups: for lines in cleaned_line_groups:
group = Polygon(self._plane) group = Polygon(self.plane)
for line in lines: for line in lines:
group.append(line) group.append(line)
if group.is_outer() == self_is_outer: if group.is_outer() == self_is_outer:
...@@ -619,7 +640,7 @@ class Polygon(TransformableContainer): ...@@ -619,7 +640,7 @@ class Polygon(TransformableContainer):
pass pass
else: else:
# no suitable group was found - we create a new one # no suitable group was found - we create a new one
new_group = Polygon(self._plane) new_group = Polygon(self.plane)
new_group.append(new_line) new_group.append(new_line)
new_groups.append(new_group) new_groups.append(new_group)
if len(new_groups) > 0: if len(new_groups) > 0:
......
...@@ -97,6 +97,8 @@ PREFERENCES_DEFAULTS = { ...@@ -97,6 +97,8 @@ PREFERENCES_DEFAULTS = {
""" the listed items will be loaded/saved via the preferences file in the """ the listed items will be loaded/saved via the preferences file in the
user's home directory on startup/shutdown""" user's home directory on startup/shutdown"""
GRID_TYPES = {"none": 0, "grid": 1, "automatic": 2}
# floating point color values are only available since gtk 2.16 # floating point color values are only available since gtk 2.16
GTK_COLOR_MAX = 65535.0 GTK_COLOR_MAX = 65535.0
...@@ -324,7 +326,7 @@ class ProjectGui: ...@@ -324,7 +326,7 @@ class ProjectGui:
scale_dimension_control.connect("focus-out-event", scale_dimension_control.connect("focus-out-event",
lambda widget, data: self.window.set_default(None)) lambda widget, data: self.window.set_default(None))
# support grid # support grid
self.gui.get_object("SupportGridEnable").connect("clicked", self.gui.get_object("SupportGridTypesControl").connect("changed",
self.update_support_grid_controls) self.update_support_grid_controls)
grid_distance_x = self.gui.get_object("SupportGridDistanceX") grid_distance_x = self.gui.get_object("SupportGridDistanceX")
grid_distance_x.connect("value-changed", grid_distance_x.connect("value-changed",
...@@ -354,14 +356,25 @@ class ProjectGui: ...@@ -354,14 +356,25 @@ class ProjectGui:
grid_height.get_value, grid_height.set_value) grid_height.get_value, grid_height.set_value)
grid_offset_x = self.gui.get_object("SupportGridOffsetX") grid_offset_x = self.gui.get_object("SupportGridOffsetX")
grid_offset_x.connect("value-changed", grid_offset_x.connect("value-changed",
self.update_support_grid_controls) self.update_support_grid_model)
self.settings.add_item("support_grid_offset_x", self.settings.add_item("support_grid_offset_x",
grid_offset_x.get_value, grid_offset_x.set_value) grid_offset_x.get_value, grid_offset_x.set_value)
grid_offset_y = self.gui.get_object("SupportGridOffsetY") grid_offset_y = self.gui.get_object("SupportGridOffsetY")
grid_offset_y.connect("value-changed", grid_offset_y.connect("value-changed",
self.update_support_grid_controls) self.update_support_grid_model)
self.settings.add_item("support_grid_offset_y", self.settings.add_item("support_grid_offset_y",
grid_offset_y.get_value, grid_offset_y.set_value) grid_offset_y.get_value, grid_offset_y.set_value)
grid_average_distance = self.gui.get_object("GridAverageDistance")
grid_average_distance.connect("value-changed",
self.update_support_grid_model)
self.settings.add_item("support_grid_average_distance",
grid_average_distance.get_value,
grid_average_distance.set_value)
grid_minimum_bridges = self.gui.get_object("GridMinBridgesPerPolygon")
grid_minimum_bridges.connect("value-changed",
self.update_support_grid_model)
self.settings.add_item("support_grid_minimum_bridges",
grid_minimum_bridges.get_value, grid_minimum_bridges.set_value)
# manual grid adjustments # manual grid adjustments
self.grid_adjustment_axis_x = self.gui.get_object("SupportGridPositionManualAxisX") self.grid_adjustment_axis_x = self.gui.get_object("SupportGridPositionManualAxisX")
self.grid_adjustment_axis_x.connect("toggled", self.grid_adjustment_axis_x.connect("toggled",
...@@ -405,9 +418,11 @@ class ProjectGui: ...@@ -405,9 +418,11 @@ class ProjectGui:
get_set_grid_adjustment_value, get_set_grid_adjustment_value) get_set_grid_adjustment_value, get_set_grid_adjustment_value)
# support grid defaults # support grid defaults
grid_distance_square.set_active(True) grid_distance_square.set_active(True)
self.settings.set("support_grid_distance_x", 5.0) self.settings.set("support_grid_distance_x", 10.0)
self.settings.set("support_grid_thickness", 0.5) self.settings.set("support_grid_thickness", 0.5)
self.settings.set("support_grid_height", 0.5) self.settings.set("support_grid_height", 0.5)
self.settings.set("support_grid_average_distance", 30)
self.settings.set("support_grid_minimum_bridges", 2)
self.grid_adjustment_axis_x_last = True self.grid_adjustment_axis_x_last = True
# visual and general settings # visual and general settings
for name, objname in (("show_model", "ShowModelCheckBox"), for name, objname in (("show_model", "ShowModelCheckBox"),
...@@ -720,28 +735,45 @@ class ProjectGui: ...@@ -720,28 +735,45 @@ class ProjectGui:
@gui_activity_guard @gui_activity_guard
def update_support_grid_controls(self, widget=None): def update_support_grid_controls(self, widget=None):
details_box = self.gui.get_object("SupportGridDetailsBox") controls = {"GridProfileExpander": ("grid", "automatic"),
"GridPatternExpander": ("grid", ),
"GridPositionExpander": ("grid", ),
"GridManualShiftExpander": ("grid", ),
"GridAverageDistanceExpander": ("automatic", ),
}
grid_type = self.gui.get_object("SupportGridTypesControl").get_active()
if grid_type == GRID_TYPES["grid"]:
grid_square = self.gui.get_object("SupportGridDistanceSquare") grid_square = self.gui.get_object("SupportGridDistanceSquare")
distance_y = self.gui.get_object("SupportGridDistanceYControl") distance_y = self.gui.get_object("SupportGridDistanceYControl")
if self.gui.get_object("SupportGridEnable").get_active():
distance_y.set_sensitive(not grid_square.get_active()) distance_y.set_sensitive(not grid_square.get_active())
details_box.show()
if grid_square.get_active(): if grid_square.get_active():
# We let "distance_y" track the value of "distance_x". # We let "distance_y" track the value of "distance_x".
self.settings.set("support_grid_distance_y", self.settings.set("support_grid_distance_y",
self.settings.get("support_grid_distance_x")) self.settings.get("support_grid_distance_x"))
self.update_support_grid_manual_model() self.update_support_grid_manual_model()
self.switch_support_grid_manual_selector() self.switch_support_grid_manual_selector()
elif grid_type == GRID_TYPES["automatic"]:
pass
elif grid_type == GRID_TYPES["none"]:
pass
else: else:
details_box.hide() raise ValueError("Invalid grid type: %d" % grid_type)
# show and hide all controls according to the current type
for key, grid_types in controls.iteritems():
obj = self.gui.get_object(key)
if grid_type in [GRID_TYPES[allowed] for allowed in grid_types]:
obj.show()
else:
obj.hide()
self.update_support_grid_model() self.update_support_grid_model()
self.update_view() self.update_view()
def update_support_grid_model(self): def update_support_grid_model(self, widget=None):
is_enabled = self.gui.get_object("SupportGridEnable").get_active() grid_type = self.gui.get_object("SupportGridTypesControl").get_active()
s = self.settings s = self.settings
if is_enabled \ support_grid = None
and (s.get("support_grid_thickness") > 0) \ if grid_type == GRID_TYPES["grid"]:
if (s.get("support_grid_thickness") > 0) \
and ((s.get("support_grid_distance_x") > 0) \ and ((s.get("support_grid_distance_x") > 0) \
or (s.get("support_grid_distance_y") > 0)) \ or (s.get("support_grid_distance_y") > 0)) \
and ((s.get("support_grid_distance_x") == 0) \ and ((s.get("support_grid_distance_x") == 0) \
...@@ -761,8 +793,19 @@ class ProjectGui: ...@@ -761,8 +793,19 @@ class ProjectGui:
offset_y=s.get("support_grid_offset_y"), offset_y=s.get("support_grid_offset_y"),
adjustments_x=self.grid_adjustments_x, adjustments_x=self.grid_adjustments_x,
adjustments_y=self.grid_adjustments_y) adjustments_y=self.grid_adjustments_y)
else: elif grid_type == GRID_TYPES["automatic"]:
support_grid = None if (s.get("support_grid_thickness") > 0) \
and (s.get("support_grid_height") > 0) \
and (s.get("support_grid_average_distance") > 0) \
and (s.get("support_grid_minimum_bridges") > 0):
support_grid = pycam.Toolpath.SupportGrid.get_distributed_support_bridges(
s.get("model"), s.get("minz"),
s.get("support_grid_average_distance"),
s.get("support_grid_minimum_bridges"),
s.get("support_grid_thickness"),
s.get("support_grid_height"))
elif grid_type == GRID_TYPES["none"]:
pass
s.set("support_grid", support_grid) s.set("support_grid", support_grid)
def switch_support_grid_manual_selector(self, widget=None): def switch_support_grid_manual_selector(self, widget=None):
...@@ -2528,7 +2571,8 @@ class ProjectGui: ...@@ -2528,7 +2571,8 @@ class ProjectGui:
tool_settings["speed"], tool_settings["feedrate"]) tool_settings["speed"], tool_settings["feedrate"])
# get the support grid options # get the support grid options
if self.gui.get_object("SupportGridEnable").get_active(): grid_type = self.gui.get_object("SupportGridTypesControl").get_active()
if grid_type == GRID_TYPES["grid"]:
toolpath_settings.set_support_grid( toolpath_settings.set_support_grid(
self.settings.get("support_grid_distance_x"), self.settings.get("support_grid_distance_x"),
self.settings.get("support_grid_distance_y"), self.settings.get("support_grid_distance_y"),
...@@ -2538,6 +2582,16 @@ class ProjectGui: ...@@ -2538,6 +2582,16 @@ class ProjectGui:
offset_y=self.settings.get("support_grid_offset_y"), offset_y=self.settings.get("support_grid_offset_y"),
adjustments_x=self.grid_adjustments_x, adjustments_x=self.grid_adjustments_x,
adjustments_y=self.grid_adjustments_y) adjustments_y=self.grid_adjustments_y)
elif grid_type == GRID_TYPES["automatic"]:
toolpath_settings.set_support_automatic(
self.settings.get("support_grid_average_distance"),
self.settings.get("support_grid_minimum_bridges"),
self.settings.get("support_grid_thickness"),
self.settings.get("support_grid_height"))
elif grid_type == GRID_TYPES["none"]:
pass
else:
raise ValueError("Invalid support grid type: %d" % grid_type)
# calculation backend: ODE / None # calculation backend: ODE / None
if self.settings.get("enable_ode"): if self.settings.get("enable_ode"):
......
...@@ -20,13 +20,23 @@ You should have received a copy of the GNU General Public License ...@@ -20,13 +20,23 @@ You should have received a copy of the GNU General Public License
along with PyCAM. If not, see <http://www.gnu.org/licenses/>. along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
""" """
from pycam.Geometry.Point import Point from pycam.Geometry.Point import Point, Vector
from pycam.Geometry.Line import Line from pycam.Geometry.Line import Line
from pycam.Geometry.Triangle import Triangle from pycam.Geometry.Triangle import Triangle
from pycam.Geometry.Model import Model from pycam.Geometry.Model import Model
from pycam.Geometry.utils import number from pycam.Geometry.utils import number
def _add_pyramid_to_model(model, start, direction, height, width):
up = Vector(0, 0, 1)
top = start.add(direction).add(up.mul(height))
middle_end = start.add(direction)
end_right = middle_end.add(direction.cross(up).normalized().mul(width / 2))
end_left = middle_end.add(direction.cross(up).normalized().mul(-width / 2))
for points in ((start, top, end_right), (start, end_right, top),
(start, end_right, end_left), (top, end_left, end_right)):
model.append(Triangle(points[0], points[1], points[2]))
def _add_cuboid_to_model(minx, maxx, miny, maxy, minz, maxz): def _add_cuboid_to_model(minx, maxx, miny, maxy, minz, maxz):
def get_triangles_for_face(pts): def get_triangles_for_face(pts):
t1 = Triangle(pts[0], pts[1], pts[2], Line(pts[0], pts[1]), t1 = Triangle(pts[0], pts[1], pts[2], Line(pts[0], pts[1]),
...@@ -131,3 +141,38 @@ def get_support_grid(minx, maxx, miny, maxy, z_plane, dist_x, dist_y, thickness, ...@@ -131,3 +141,38 @@ def get_support_grid(minx, maxx, miny, maxy, z_plane, dist_x, dist_y, thickness,
line_y + thick_half, z_plane, z_plane + height) line_y + thick_half, z_plane, z_plane + height)
return grid_model return grid_model
def get_distributed_support_bridges(model, z_plane, average_distance,
min_bridges_per_polygon, thickness, height):
result = Model()
for polygon in model.get_polygons():
if not polygon.is_outer():
continue
lines = polygon.get_lines()
poly_lengths = polygon.get_lengths()
num_of_bridges = max(min_bridges_per_polygon,
int(round(sum(poly_lengths) / average_distance)))
real_average_distance = sum(poly_lengths) / num_of_bridges
max_line_index = poly_lengths.index(max(poly_lengths))
positions = []
current_line_index = max_line_index
distance_processed = poly_lengths[current_line_index] / 2
positions.append(current_line_index)
while len(positions) < num_of_bridges:
current_line_index += 1
current_line_index %= len(poly_lengths)
while distance_processed + poly_lengths[current_line_index] < real_average_distance:
distance_processed += poly_lengths[current_line_index]
current_line_index += 1
current_line_index %= len(poly_lengths)
positions.append(current_line_index)
distance_processed += poly_lengths[current_line_index]
distance_processed %= real_average_distance
for line_index in positions:
bridge_dir = lines[line_index].dir.cross(polygon.plane.n)
# TODO: should we ask for a length?
length = average_distance / 4
_add_pyramid_to_model(result,
polygon.get_middle_of_line(line_index), bridge_dir,
height, thickness)
return result
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