Commit 50e2f2ba authored by sumpfralle's avatar sumpfralle

separated handling of LineGroups (for contour classes)

skip unnecessary double-check of line collisions
moved "MODEL_TRANSFORMATIONS" from pycam.Geometry.Model to pycam.Geometry.Matrix (as "TRANSFORMATIONS")
fixed accidental reversion of contour model direction caused by some model transformations


git-svn-id: https://pycam.svn.sourceforge.net/svnroot/pycam/trunk@523 bbaffbd6-741e-11dd-a85d-61de82d9cad9
parent b829ff3e
...@@ -21,6 +21,9 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -21,6 +21,9 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
""" """
from pycam.Geometry import TransformableContainer from pycam.Geometry import TransformableContainer
from pycam.Geometry.Point import Point
from pycam.Geometry.utils import epsilon
import pycam.Geometry.Matrix as Matrix
import math import math
...@@ -67,8 +70,8 @@ class Line(TransformableContainer): ...@@ -67,8 +70,8 @@ class Line(TransformableContainer):
yield self.p2 yield self.p2
def reset_cache(self): def reset_cache(self):
self._dir is None self._dir = None
self._len is None self._len = None
def dir(self): def dir(self):
if self._dir is None: if self._dir is None:
...@@ -95,6 +98,9 @@ class Line(TransformableContainer): ...@@ -95,6 +98,9 @@ class Line(TransformableContainer):
def dist_to_point(self, p): def dist_to_point(self, p):
return math.sqrt(self.dist_to_point_sq(p)) return math.sqrt(self.dist_to_point_sq(p))
def is_point_in_line(self, p):
return abs(p.sub(self.p1).norm() + p.sub(self.p2).norm() - self.len()) < epsilon
def minx(self): def minx(self):
return min(self.p1.x, self.p2.x) return min(self.p1.x, self.p2.x)
...@@ -161,7 +167,21 @@ class Line(TransformableContainer): ...@@ -161,7 +167,21 @@ class Line(TransformableContainer):
factor = c.cross(b).dot(a.cross(b)) / a.cross(b).normsq() factor = c.cross(b).dot(a.cross(b)) / a.cross(b).normsq()
except ZeroDivisionError: except ZeroDivisionError:
# lines are parallel # lines are parallel
return None # check if they are _one_ line
if a.cross(c).normsq() != 0:
# the lines are parallel with a disctance
return None
# the lines are on one straight
if self.is_point_in_line(x3):
return x3
elif self.is_point_in_line(x4):
return x4
elif line.is_point_in_line(x1):
return x1
elif line.is_point_in_line(x2):
return x2
else:
return None
if 0 <= factor <= 1: if 0 <= factor <= 1:
intersection = x1.add(a.mul(factor)) intersection = x1.add(a.mul(factor))
# check if the intersection is between x3 and x4 # check if the intersection is between x3 and x4
...@@ -176,3 +196,242 @@ class Line(TransformableContainer): ...@@ -176,3 +196,242 @@ class Line(TransformableContainer):
# intersection outside of the length of line(x1, x2) # intersection outside of the length of line(x1, x2)
return None return None
class LineGroup(TransformableContainer):
def __init__(self, offset_matrix=None):
super(LineGroup, self).__init__()
self._offset_matrix = offset_matrix
self._lines = []
self._line_offsets = None
self._is_closed = False
self.maxx = None
self.minx = None
self.maxy = None
self.miny = None
self.maxz = None
self.minz = None
def append(self, line):
if not self.is_connectable(line):
raise ValueError("This line does not fit to the line group")
else:
if not self._lines or (self._lines[-1].p2 == line.p1):
self._lines.append(line)
else:
self._lines.insert(0, line)
self._update_limits(line)
self._is_closed = self._lines[0].p1 == self._lines[-1].p2
def is_connectable(self, line):
if self._is_closed:
return False
elif not self._lines:
# empty line groups can be connected with any line
return True
elif line.p1 == self._lines[-1].p2:
return True
elif line.p2 == self._lines[0].p1:
return True
else:
return False
def next(self):
for line in self._lines:
yield line
def _init_line_offsets(self):
if self._lines and self._line_offsets is None:
self._line_offsets = []
offset_matrix = self.get_offset_matrix()
# initialize all offset vectors (if necessary)
for line in self._lines:
line_dir = line.dir()
vector = (line_dir.x, line_dir.y, line_dir.z)
offset_vector = Matrix.multiply_vector_matrix(vector, offset_matrix)
offset_point = Point(offset_vector[0], offset_vector[1], offset_vector[2])
self._line_offsets.append(Line(line.p1, line.p1.add(offset_point)))
def transform_by_matrix(self, matrix, transformed_list):
if self._lines:
offset_matrix = self.get_offset_matrix()
# initialize all offset vectors (if necessary)
self._init_line_offsets()
super(LineGroup, self).transform_by_matrix(matrix, transformed_list)
# transform all offset vectors
if self._lines:
for offset in self._line_offsets:
if not id(offset) in transformed_list:
offset.transform_by_matrix(matrix, transformed_list)
# transform the offset vector of this line group
self._offset_matrix = Matrix.multiply_matrix_matrix(matrix, offset_matrix)
def get_lines(self):
return self._lines[:]
def to_OpenGL(self):
for line in self._lines:
line.to_OpenGL()
def get_offset_matrix(self):
if not self._offset_matrix is None:
return self._offset_matrix
elif not self._lines:
return None
else:
# assume that this line group forms a straight line
offset_matrix = None
# check if all lines are in one specific layer (z/y/x)
# return the respective axis rotation matrix
z_start_value = self._lines[0].minz()
on_z_level = [True for line in self._lines
if line.minz() == line.maxz() == z_start_value]
if len(on_z_level) == len(self._lines):
offset_matrix = Matrix.TRANSFORMATIONS["z"]
else:
y_start_value = self._lines[0].y
on_y_level = [True for line in self._lines
if line.miny() == line.maxy() == y_start_value]
if len(on_y_level) == len(self._lines):
offset_matrix = Matrix.TRANSFORMATIONS["y"]
else:
x_start_value = self._lines[0].x
on_x_level = [True for line in self._lines
if line.minx() == line.maxx() == x_start_value]
if len(on_x_level) == len(self._lines):
offset_matrix = Matrix.TRANSFORMATIONS["x"]
# store the result to avoid re-calculation
self._offset_matrix = offset_matrix
return offset_matrix
def _update_limits(self, line):
if self.minx is None:
self.minx = line.minx()
self.maxx = line.maxx()
self.miny = line.miny()
self.maxy = line.maxy()
self.minz = line.minz()
self.maxz = line.maxz()
else:
self.minx = min(self.minx, line.minx())
self.maxx = max(self.maxx, line.maxx())
self.miny = min(self.miny, line.miny())
self.maxy = max(self.maxy, line.maxy())
self.minz = min(self.minz, line.minz())
self.maxz = max(self.maxz, line.maxz())
def reset_cache(self):
if not self._lines:
self.minx, self.miny, self.minz = None, None, None
self.maxx, self.maxy, self.maxz = None, None, None
else:
first = self._lines[0]
# initialize the start limit with valid values
self.minx = first.minx()
self.maxx = first.maxx()
self.miny = first.miny()
self.maxy = first.maxy()
self.minz = first.minz()
self.maxz = first.maxz()
# update the limit for each line
for line in self._lines:
self._update_limits(line)
def get_offset_line_group(self, offset):
def get_parallel_line(line, line_offset, offset):
if offset == 0:
return Line(line.p1, line.p2)
else:
cross_offset = line_offset.dir().mul(offset)
# Prolong the line at the beginning and at the end - to allow
# overlaps.
in_line = line.dir().mul(offset)
return Line(line.p1.add(cross_offset).sub(in_line),
line.p2.add(cross_offset).add(in_line))
def do_lines_intersection(l1, l2):
""" calculate the new intersection between two neighbouring lines
"""
if l1.p2 == l2.p1:
# intersection is already fine
return
if (l1.p1 is None) or (l2.p1 is None):
# one line was already marked as obsolete
return
x1, x2, x3, x4 = l2.p1, l2.p2, l1.p1, l1.p2
a = x2.sub(x1)
b = x4.sub(x3)
c = x3.sub(x1)
# see http://mathworld.wolfram.com/Line-LineIntersection.html (24)
try:
factor = c.cross(b).dot(a.cross(b)) / a.cross(b).normsq()
except ZeroDivisionError:
l2.p1 = None
return
if not (0 <= factor < 1):
# The intersection is always supposed to be within p1 and p2.
l2.p1 = None
else:
intersection = x1.add(a.mul(factor))
if Line(l1.p1, intersection).dir() != l1.dir():
# Remove lines that would change their direction due to the
# new intersection. These are usually lines that become
# obsolete due to a more favourable intersection of the two
# neighbouring lines. This appears at small corners.
l1.p1 = None
elif Line(intersection, l2.p2).dir() != l2.dir():
# see comment above
l2.p1 = None
elif l1.p1 == intersection:
# remove invalid lines (zero length)
l1.p1 = None
elif l2.p2 == intersection:
# remove invalid lines (zero length)
l2.p1 = None
else:
# shorten both lines according to the new intersection
l1.p2 = intersection
l2.p1 = intersection
def simplify_line_group_intersections(lines):
finished = False
new_group = lines[:]
while not finished:
if len(new_group) > 1:
# Calculate new intersections for each pair of adjacent
# lines.
for index in range(len(new_group)):
if (index == 0) and (not self._is_closed):
# skip the first line if the group is not closed
continue
# this also works for index==0 (closed groups)
l1 = new_group[index - 1]
l2 = new_group[index]
do_lines_intersection(l1, l2)
# Remove all lines that were marked as obsolete during
# intersection calculation.
clean_group = [line for line in new_group
if not line.p1 is None]
finished = len(new_group) == len(clean_group)
if (len(clean_group) == 1) and self._is_closed:
new_group = []
finished = True
else:
new_group = clean_group
return new_group
if self.get_offset_matrix() is None:
# we can't get an offset line group if the normal is invalid
return self
else:
# initialize the line offsets if necessary
self._init_line_offsets()
new_lines = []
for line, line_offset in zip(self._lines, self._line_offsets):
new_lines.append(get_parallel_line(line, line_offset, offset))
cleaned_line_group = simplify_line_group_intersections(new_lines)
if len(cleaned_line_group) == 0:
return None
else:
group = LineGroup(self.get_offset_matrix())
for line in cleaned_line_group:
group.append(line)
return group
...@@ -27,6 +27,20 @@ from pycam.Geometry.Point import Point ...@@ -27,6 +27,20 @@ from pycam.Geometry.Point import Point
import math import math
TRANSFORMATIONS = {
"normal": ((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0)),
"x": ((1, 0, 0, 0), (0, 0, 1, 0), (0, -1, 0, 0)),
"y": ((0, 0, -1, 0), (0, 1, 0, 0), (1, 0, 0, 0)),
"z": ((0, 1, 0, 0), (-1, 0, 0, 0), (0, 0, 1, 0)),
"xy": ((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, -1, 0)),
"xz": ((1, 0, 0, 0), (0, -1, 0, 0), (0, 0, 1, 0)),
"yz": ((-1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0)),
"x_swap_y": ((0, 1, 0, 0), (1, 0, 0, 0), (0, 0, 1, 0)),
"x_swap_z": ((0, 0, 1, 0), (0, 1, 0, 0), (1, 0, 0, 0)),
"y_swap_z": ((1, 0, 0, 0), (0, 0, 1, 0), (0, 1, 0, 0)),
}
def get_dot_product(a, b): def get_dot_product(a, b):
""" calculate the dot product of two 3d vectors """ calculate the dot product of two 3d vectors
...@@ -158,7 +172,35 @@ def multiply_vector_matrix(v, m): ...@@ -158,7 +172,35 @@ def multiply_vector_matrix(v, m):
@rtype: tuple(float) @rtype: tuple(float)
@return: a tuple of 3 floats as the matrix product @return: a tuple of 3 floats as the matrix product
""" """
if len(m) == 9:
m = ((m[0], m[1], m[2]), (m[3], m[4], m[5]), (m[6], m[7], m[8]))
return (v[0] * m[0][0] + v[1] * m[0][1] + v[2] * m[0][2], return (v[0] * m[0][0] + v[1] * m[0][1] + v[2] * m[0][2],
v[0] * m[1][0] + v[1] * m[1][1] + v[2] * m[1][2], v[0] * m[1][0] + v[1] * m[1][1] + v[2] * m[1][2],
v[0] * m[2][0] + v[1] * m[2][1] + v[2] * m[2][2]) v[0] * m[2][0] + v[1] * m[2][1] + v[2] * m[2][2])
def multiply_matrix_matrix(m1, m2):
def multi(row1, col2):
return (m1[row1][0] * m2[0][col2] + m1[row1][1] * m2[1][col2] \
+ m1[row1][2] * m2[2][col2])
return ((multi(0, 0), multi(0, 1), multi(0, 2)),
(multi(1, 0), multi(1, 1), multi(1, 2)),
(multi(2, 0), multi(2, 1), multi(2, 2)))
def get_inverse_matrix(m):
_a = m[1][1] * m[2][2] - m[1][2] * m[2][1]
_b = m[0][2] * m[2][1] - m[0][1] * m[2][2]
_c = m[0][1] * m[1][2] - m[0][2] * m[1][1]
_d = m[1][2] * m[2][0] - m[1][0] * m[2][2]
_e = m[0][0] * m[2][2] - m[0][2] * m[2][0]
_f = m[0][2] * m[1][0] - m[0][0] * m[1][2]
_g = m[1][0] * m[2][1] - m[1][1] * m[2][0]
_h = m[0][1] * m[2][0] - m[0][0] * m[2][1]
_k = m[0][0] * m[1][1] - m[0][1] * m[1][0]
det = m[0][0] * _a + m[0][1] * _d + m[0][2] * _g
if det == 0:
return None
else:
return ((_a / det, _b / det, _c / det),
(_d / det, _e / det, _f / det),
(_g / det, _h / det, _k / det))
...@@ -23,27 +23,15 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -23,27 +23,15 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
import pycam.Exporters.STLExporter import pycam.Exporters.STLExporter
from pycam.Geometry.Triangle import Triangle from pycam.Geometry.Triangle import Triangle
from pycam.Geometry.Line import Line from pycam.Geometry.Line import Line, LineGroup
from pycam.Geometry.Point import Point from pycam.Geometry.Point import Point
from pycam.Geometry.TriangleKdtree import TriangleKdtree from pycam.Geometry.TriangleKdtree import TriangleKdtree
from pycam.Geometry.Matrix import TRANSFORMATIONS
from pycam.Toolpath import Bounds from pycam.Toolpath import Bounds
from pycam.Geometry.utils import INFINITE from pycam.Geometry.utils import INFINITE
from pycam.Geometry import TransformableContainer from pycam.Geometry import TransformableContainer
MODEL_TRANSFORMATIONS = {
"normal": ((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0)),
"x": ((1, 0, 0, 0), (0, 0, 1, 0), (0, -1, 0, 0)),
"y": ((0, 0, -1, 0), (0, 1, 0, 0), (1, 0, 0, 0)),
"z": ((0, 1, 0, 0), (-1, 0, 0, 0), (0, 0, 1, 0)),
"xy": ((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, -1, 0)),
"xz": ((1, 0, 0, 0), (0, -1, 0, 0), (0, 0, 1, 0)),
"yz": ((-1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0)),
"x_swap_y": ((0, 1, 0, 0), (1, 0, 0, 0), (0, 0, 1, 0)),
"x_swap_z": ((0, 0, 1, 0), (0, 1, 0, 0), (1, 0, 0, 0)),
"y_swap_z": ((1, 0, 0, 0), (0, 0, 1, 0), (0, 1, 0, 0)),
}
class BaseModel(TransformableContainer): class BaseModel(TransformableContainer):
id = 0 id = 0
...@@ -95,20 +83,26 @@ class BaseModel(TransformableContainer): ...@@ -95,20 +83,26 @@ class BaseModel(TransformableContainer):
+ "support the 'export' function.") % str(type(self))) + "support the 'export' function.") % str(type(self)))
def _update_limits(self, item): def _update_limits(self, item):
if callable(item.minx):
minx, miny, minz = item.minx(), item.miny(), item.minz()
maxx, maxy, maxz = item.maxx(), item.maxy(), item.maxz()
else:
minx, miny, minz = item.minx, item.miny, item.minz
maxx, maxy, maxz = item.maxx, item.maxy, item.maxz
if self.minx is None: if self.minx is None:
self.minx = item.minx() self.minx = minx
self.miny = item.miny() self.miny = miny
self.minz = item.minz() self.minz = minz
self.maxx = item.maxx() self.maxx = maxx
self.maxy = item.maxy() self.maxy = maxy
self.maxz = item.maxz() self.maxz = maxz
else: else:
self.minx = min(self.minx, item.minx()) self.minx = min(self.minx, minx)
self.miny = min(self.miny, item.miny()) self.miny = min(self.miny, miny)
self.minz = min(self.minz, item.minz()) self.minz = min(self.minz, minz)
self.maxx = max(self.maxx, item.maxx()) self.maxx = max(self.maxx, maxx)
self.maxy = max(self.maxy, item.maxy()) self.maxy = max(self.maxy, maxy)
self.maxz = max(self.maxz, item.maxz()) self.maxz = max(self.maxz, maxz)
def append(self, item): def append(self, item):
self._update_limits(item) self._update_limits(item)
...@@ -135,8 +129,8 @@ class BaseModel(TransformableContainer): ...@@ -135,8 +129,8 @@ class BaseModel(TransformableContainer):
self._update_limits(item) self._update_limits(item)
def transform_by_template(self, direction="normal"): def transform_by_template(self, direction="normal"):
if direction in MODEL_TRANSFORMATIONS.keys(): if direction in TRANSFORMATIONS.keys():
self.transform_by_matrix(MODEL_TRANSFORMATIONS[direction]) self.transform_by_matrix(TRANSFORMATIONS[direction])
def shift(self, shift_x, shift_y, shift_z): def shift(self, shift_x, shift_y, shift_z):
matrix = ((1, 0, 0, shift_x), (0, 1, 0, shift_y), (0, 0, 1, shift_z)) matrix = ((1, 0, 0, shift_x), (0, 1, 0, shift_y), (0, 0, 1, shift_z))
...@@ -217,20 +211,22 @@ class ContourModel(BaseModel): ...@@ -217,20 +211,22 @@ class ContourModel(BaseModel):
super(ContourModel, self).append(item) super(ContourModel, self).append(item)
if isinstance(item, Line): if isinstance(item, Line):
for line_group in self._line_groups: for line_group in self._line_groups:
if item.p2 == line_group[0].p1: if line_group.is_connectable(item):
# the line fits to the start of this group
line_group.insert(0, item)
break
elif item.p1 == line_group[-1].p2:
# the line fits to the end of this group
line_group.append(item) line_group.append(item)
break break
else: else:
# add a new group with this single item # add a single line as part of a new group
self._line_groups.append([item]) new_line_group = LineGroup()
new_line_group.append(item)
self._line_groups.append(new_line_group)
elif isinstance(item, LineGroup):
self._line_groups.append(item)
else:
# ignore any non-supported items
pass
def get_lines(self): def get_lines(self):
return sum(self._line_groups, []) return sum([group.get_lines() for group in self._line_groups], [])
def get_line_groups(self): def get_line_groups(self):
return self._line_groups return self._line_groups
...@@ -248,108 +244,16 @@ class ContourModel(BaseModel): ...@@ -248,108 +244,16 @@ class ContourModel(BaseModel):
@returns: the new shifted model @returns: the new shifted model
@rtype: pycam.Geometry.Model.Model @rtype: pycam.Geometry.Model.Model
""" """
def get_parallel_line(line, offset):
if offset == 0:
return Line(line.p1, line.p2)
else:
cross = line.p2.sub(line.p1).cross(Point(0, 0, 1))
cross_offset = cross.mul(offset / cross.norm())
in_line = line.p2.sub(line.p1).normalize().mul(offset)
return Line(line.p1.add(cross_offset).sub(in_line),
line.p2.add(cross_offset).add(in_line))
def do_lines_intersection(l1, l2):
""" calculate the new intersection between two neighbouring lines
"""
if l1.p2 == l2.p1:
# intersection is already fine
return
if (l1.p1 is None) or (l2.p1 is None):
# one line was already marked as obsolete
return
x1, x2, x3, x4 = l2.p1, l2.p2, l1.p1, l1.p2
a = x2.sub(x1)
b = x4.sub(x3)
c = x3.sub(x1)
# see http://mathworld.wolfram.com/Line-LineIntersection.html (24)
try:
factor = c.cross(b).dot(a.cross(b)) / a.cross(b).normsq()
except ZeroDivisionError:
l2.p1 = None
return
if not (0 <= factor < 1):
# The intersection is always supposed to be within p1 and p2.
l2.p1 = None
else:
intersection = x1.add(a.mul(factor))
if Line(l1.p1, intersection).dir() != l1.dir():
# Remove lines that would change their direction due to the
# new intersection. These are usually lines that become
# obsolete due to a more favourable intersection of the two
# neighbouring lines. This appears at small corners.
l1.p1 = None
elif Line(intersection, l2.p2).dir() != l2.dir():
# see comment above
l2.p1 = None
elif l1.p1 == intersection:
# remove invalid lines (zero length)
l1.p1 = None
elif l2.p2 == intersection:
# remove invalid lines (zero length)
l2.p1 = None
else:
# shorten both lines according to the new intersection
l1.p2 = intersection
l2.p1 = intersection
# use a cached offset model if it exists # use a cached offset model if it exists
if offset in self._cached_offset_models: if offset in self._cached_offset_models:
return self._cached_offset_models[offset] return self._cached_offset_models[offset]
result = ContourModel() result = ContourModel()
for group in self._line_groups: for group in self._line_groups:
closed_group = (len(group) > 1) and (group[-1].p2 == group[0].p1) new_group = group.get_offset_line_group(offset)
new_group = [] if not new_group is None:
for line in group: result.append(new_group)
new_group.append(get_parallel_line(line, offset)) if callback and callback():
# counter for the progress callback return None
lines_to_be_processed = len(new_group)
finished = False
while not finished:
if len(new_group) > 1:
# Calculate new intersections for each pair of adjacent
# lines.
for index in range(len(new_group)):
if (index == 0) and (not closed_group):
# skip the first line if the group is not closed
continue
# this also works for index==0 (closed groups)
l1 = new_group[index - 1]
l2 = new_group[index]
do_lines_intersection(l1, l2)
# Don't call the "progress" callback more times than the
# number of lines in this group.
if (lines_to_be_processed > 0) \
and callback and callback():
# the user requested "quit"
return None
lines_to_be_processed -= 1
# Remove all lines that were marked as obsolete during
# intersection calculation.
clean_group = [line for line in new_group
if not line.p1 is None]
finished = len(new_group) == len(clean_group)
if (len(clean_group) == 1) and closed_group:
new_group = []
finished = True
else:
new_group = clean_group
# "fix" the progress counter (it expected as many lines as there
# are items in the group. This causes small progress jumps.
if callback:
while lines_to_be_processed > 0:
if callback():
return None
lines_to_be_processed -= 1
for line in new_group:
result.append(line)
# cache the result # cache the result
self._cached_offset_models[offset] = result self._cached_offset_models[offset] = result
return result return result
...@@ -363,52 +267,32 @@ class ContourModel(BaseModel): ...@@ -363,52 +267,32 @@ class ContourModel(BaseModel):
interrupted the operation). interrupted the operation).
Otherwise it returns False if no intersections were found. Otherwise it returns False if no intersections were found.
""" """
def get_bounds_of_group(group): def check_bounds_of_groups(g1, g2):
minx, maxx, miny, maxy = None, None, None, None if (g1.minx <= g2.minx <= g1.maxx) \
for line in group: or (g1.minx <= g2.maxx <= g1.maxx) \
lminx = min(line.p1.x, line.p2.x) or (g2.minx <= g1.minx <= g2.maxx) \
lmaxx = max(line.p1.x, line.p2.x) or (g2.minx <= g1.maxx <= g2.maxx):
lminy = min(line.p1.y, line.p2.y)
lmaxy = max(line.p1.y, line.p2.y)
if (minx is None) or (minx > lminx):
minx = lminx
if (maxx is None) or (maxx > lmaxx):
maxx = lmaxx
if (miny is None) or (miny > lminy):
miny = lminy
if (maxy is None) or (maxy > lmaxy):
maxy = lmaxy
return (minx, maxx, miny, maxy)
def check_bounds_of_groups(bound1, bound2):
if ((bound1[0] <= bound2[0] <= bound1[1]) \
or (bound1[0] <= bound2[1] <= bound1[1]) \
or (bound2[0] <= bound1[0] <= bound2[1]) \
or (bound2[0] <= bound1[1] <= bound2[1])):
# the x boundaries overlap # the x boundaries overlap
if ((bound1[2] <= bound2[2] <= bound1[3]) \ if (g1.miny <= g2.miny <= g1.maxy) \
or (bound1[2] <= bound2[3] <= bound1[3]) \ or (g1.miny <= g2.maxy <= g1.maxy) \
or (bound2[2] <= bound1[2] <= bound2[3]) \ or (g2.miny <= g1.miny <= g2.maxy) \
or (bound2[2] <= bound1[3] <= bound2[3])): or (g2.miny <= g1.maxy <= g2.maxy):
# also the y boundaries overlap # also the y boundaries overlap
return True if (g1.minz <= g2.minz <= g1.maxz) \
or (g1.minz <= g2.maxz <= g1.maxz) \
or (g2.minz <= g1.minz <= g2.maxz) \
or (g2.minz <= g1.maxz <= g2.maxz):
# z overlaps as well
return True
return False return False
# check each pair of line groups for intersections # check each pair of line groups for intersections
# first: cache the bounds of each group for index, group1 in enumerate(self._line_groups[:-1]):
bounds = {} for group2 in self._line_groups[index+1:]:
for group in self._line_groups:
bounds[id(group)] = get_bounds_of_group(group)
# now start to look for intersections
for group1 in self._line_groups:
for group2 in self._line_groups:
# don't compare a group with itself
if group1 is group2:
continue
# check if both groups overlap - otherwise skip this pair # check if both groups overlap - otherwise skip this pair
if check_bounds_of_groups(bounds[id(group1)], if check_bounds_of_groups(group1, group2):
bounds[id(group2)]):
# check each pair of lines for intersections # check each pair of lines for intersections
for line1 in group1: for line1 in group1.next():
for line2 in group2: for line2 in group2.next():
intersection = line1.get_intersection(line2) intersection = line1.get_intersection(line2)
if intersection: if intersection:
return intersection return intersection
......
...@@ -78,7 +78,7 @@ class EngraveCutter: ...@@ -78,7 +78,7 @@ class EngraveCutter:
break break
for line_group in line_groups: for line_group in line_groups:
for line in line_group: for line in line_group.next():
self.GenerateToolPathLinePush(self.pa_push, line, z, self.GenerateToolPathLinePush(self.pa_push, line, z,
draw_callback) draw_callback)
if progress_counter.increment(): if progress_counter.increment():
...@@ -107,7 +107,7 @@ class EngraveCutter: ...@@ -107,7 +107,7 @@ class EngraveCutter:
for line_group in self.contour_model.get_line_groups(): for line_group in self.contour_model.get_line_groups():
self.pa_drop.new_direction(0) self.pa_drop.new_direction(0)
self.pa_drop.new_scanline() self.pa_drop.new_scanline()
for line in line_group: for line in line_group.get_lines():
self.GenerateToolPathLineDrop(self.pa_drop, line, minz, maxz, self.GenerateToolPathLineDrop(self.pa_drop, line, minz, maxz,
horiz_step, draw_callback=draw_callback) horiz_step, draw_callback=draw_callback)
if progress_counter.increment(): if progress_counter.increment():
......
...@@ -157,8 +157,8 @@ def generate_toolpath(model, tool_settings=None, ...@@ -157,8 +157,8 @@ def generate_toolpath(model, tool_settings=None,
if (not contour_model is None) and (engrave_offset > 0): if (not contour_model is None) and (engrave_offset > 0):
if not callback is None: if not callback is None:
callback(text="Preparing contour model with offset ...") callback(text="Preparing contour model with offset ...")
progress_callback = ProgressCounter(len(contour_model.get_lines()), progress_callback = ProgressCounter(
callback).increment len(contour_model.get_line_groups()), callback).increment
else: else:
progress_callback = None progress_callback = None
contour_model = contour_model.get_offset_model(engrave_offset, contour_model = contour_model.get_offset_model(engrave_offset,
......
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