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
* added support for importing contour paths from SVG files (requires Inkscape and pstoedit)
* added basic support for importing simple DXF contour files
......
This diff is collapsed.
......@@ -94,6 +94,9 @@ class Point:
def dot(self, p):
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):
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)
......
......@@ -36,7 +36,7 @@ class Polygon(TransformableContainer):
# TODO: derive the plane from the appended points
if plane is None:
plane = Plane(Point(0, 0, 0), Point(0, 0, 1))
self._plane = plane
self.plane = plane
self._points = []
self._is_closed = False
self.maxx = None
......@@ -102,10 +102,10 @@ class Polygon(TransformableContainer):
def next(self):
for point in self._points:
yield point
yield self._plane
yield self.plane
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):
""" calculate the area covered by a line group
......@@ -126,6 +126,27 @@ class Polygon(TransformableContainer):
value += p1.x * p2.y - p2.x * p1.y
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):
""" calculate the maximum distance between two points of the polygon
"""
......@@ -243,10 +264,10 @@ class Polygon(TransformableContainer):
skel_dir = d1.add(d2).normalized()
if skel_dir is None:
# the two vectors pointed to opposite directions
skel_dir = d1.cross(self._plane.n).normalized()
skel_dir = d1.cross(self.plane.n).normalized()
else:
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
if offset_up_vector.z * skel_up_vector.z < 0:
# reverse the skeleton vector to point outwards
......@@ -257,7 +278,7 @@ class Polygon(TransformableContainer):
def get_shifted_vertex(index, offset):
p1 = self._points[index]
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)
factor = cross_offset.dot(bisector_normalized)
if factor != 0:
......@@ -375,7 +396,7 @@ class Polygon(TransformableContainer):
self_is_outer = self.is_outer()
groups = []
for lines in cleaned_line_groups:
group = Polygon(self._plane)
group = Polygon(self.plane)
for line in lines:
group.append(line)
if group.is_outer() != self_is_outer:
......@@ -408,7 +429,7 @@ class Polygon(TransformableContainer):
if offset == 0:
return Line(line.p1, line.p2)
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
# overlaps. Use factor "2" to take care for star-like structure
# where a complete convex triangle would get cropped (two lines
......@@ -581,7 +602,7 @@ class Polygon(TransformableContainer):
self_is_outer = self.is_outer()
groups = []
for lines in cleaned_line_groups:
group = Polygon(self._plane)
group = Polygon(self.plane)
for line in lines:
group.append(line)
if group.is_outer() == self_is_outer:
......@@ -619,7 +640,7 @@ class Polygon(TransformableContainer):
pass
else:
# 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_groups.append(new_group)
if len(new_groups) > 0:
......
......@@ -97,6 +97,8 @@ PREFERENCES_DEFAULTS = {
""" the listed items will be loaded/saved via the preferences file in the
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
GTK_COLOR_MAX = 65535.0
......@@ -324,7 +326,7 @@ class ProjectGui:
scale_dimension_control.connect("focus-out-event",
lambda widget, data: self.window.set_default(None))
# support grid
self.gui.get_object("SupportGridEnable").connect("clicked",
self.gui.get_object("SupportGridTypesControl").connect("changed",
self.update_support_grid_controls)
grid_distance_x = self.gui.get_object("SupportGridDistanceX")
grid_distance_x.connect("value-changed",
......@@ -354,14 +356,25 @@ class ProjectGui:
grid_height.get_value, grid_height.set_value)
grid_offset_x = self.gui.get_object("SupportGridOffsetX")
grid_offset_x.connect("value-changed",
self.update_support_grid_controls)
self.update_support_grid_model)
self.settings.add_item("support_grid_offset_x",
grid_offset_x.get_value, grid_offset_x.set_value)
grid_offset_y = self.gui.get_object("SupportGridOffsetY")
grid_offset_y.connect("value-changed",
self.update_support_grid_controls)
self.update_support_grid_model)
self.settings.add_item("support_grid_offset_y",
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
self.grid_adjustment_axis_x = self.gui.get_object("SupportGridPositionManualAxisX")
self.grid_adjustment_axis_x.connect("toggled",
......@@ -405,9 +418,11 @@ class ProjectGui:
get_set_grid_adjustment_value, get_set_grid_adjustment_value)
# support grid defaults
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_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
# visual and general settings
for name, objname in (("show_model", "ShowModelCheckBox"),
......@@ -720,28 +735,45 @@ class ProjectGui:
@gui_activity_guard
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")
distance_y = self.gui.get_object("SupportGridDistanceYControl")
if self.gui.get_object("SupportGridEnable").get_active():
distance_y.set_sensitive(not grid_square.get_active())
details_box.show()
if grid_square.get_active():
# We let "distance_y" track the value of "distance_x".
self.settings.set("support_grid_distance_y",
self.settings.get("support_grid_distance_x"))
self.update_support_grid_manual_model()
self.switch_support_grid_manual_selector()
elif grid_type == GRID_TYPES["automatic"]:
pass
elif grid_type == GRID_TYPES["none"]:
pass
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_view()
def update_support_grid_model(self):
is_enabled = self.gui.get_object("SupportGridEnable").get_active()
def update_support_grid_model(self, widget=None):
grid_type = self.gui.get_object("SupportGridTypesControl").get_active()
s = self.settings
if is_enabled \
and (s.get("support_grid_thickness") > 0) \
support_grid = None
if grid_type == GRID_TYPES["grid"]:
if (s.get("support_grid_thickness") > 0) \
and ((s.get("support_grid_distance_x") > 0) \
or (s.get("support_grid_distance_y") > 0)) \
and ((s.get("support_grid_distance_x") == 0) \
......@@ -761,8 +793,19 @@ class ProjectGui:
offset_y=s.get("support_grid_offset_y"),
adjustments_x=self.grid_adjustments_x,
adjustments_y=self.grid_adjustments_y)
else:
support_grid = None
elif grid_type == GRID_TYPES["automatic"]:
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)
def switch_support_grid_manual_selector(self, widget=None):
......@@ -2528,7 +2571,8 @@ class ProjectGui:
tool_settings["speed"], tool_settings["feedrate"])
# 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(
self.settings.get("support_grid_distance_x"),
self.settings.get("support_grid_distance_y"),
......@@ -2538,6 +2582,16 @@ class ProjectGui:
offset_y=self.settings.get("support_grid_offset_y"),
adjustments_x=self.grid_adjustments_x,
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
if self.settings.get("enable_ode"):
......
......@@ -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/>.
"""
from pycam.Geometry.Point import Point
from pycam.Geometry.Point import Point, Vector
from pycam.Geometry.Line import Line
from pycam.Geometry.Triangle import Triangle
from pycam.Geometry.Model import Model
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 get_triangles_for_face(pts):
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,
line_y + thick_half, z_plane, z_plane + height)
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