def _fix_intersections(self, path): """Collapse self-intersecting loops. """ # See: https://en.wikipedia.org/wiki/Bentley-Ottmann_algorithm # for a more efficient sweepline method O(Nlog(N)). # This is the bonehead way: O(n**2), but it's fine for # reasonable path lengths... # See: winding numbers. intersecting loops should match # underlying toolpath winding. fixed_path = [] skip_ahead = 0 for i, line1 in enumerate(path): if i < skip_ahead: continue p = None for j, line2 in enumerate(path[(i + 2):]): p = line1.intersection(line2, segment=True) if p is not None: geom.debug.draw_point(p, color='#ffc000') fixed_path.append(geom.Line(line1.p1, p)) fixed_path.append(geom.Line(p, line2.p2)) skip_ahead = i + j + 3 break # fixed_path.append(line1) if p is None: fixed_path.append(line1) return fixed_path
def _make_outline_path(self, points): # Find all the adjacent tan line intersections if len(self.toolmarks) > 1: prev_line = self.toolmarks[0] for next_line in self.toolmarks[1:]: p = prev_line.intersection(next_line) if p is not None: geom.debug.draw_point(p, color="#ff0000") prev_line = next_line path = [] if len(points) > 1: prev_pt = points[0] for next_pt in points[1:]: if next_pt != prev_pt: # Simplify the outline by skipping inline nodes. if path and path[-1].which_side(next_pt, inline=True) == 0: prev_line = path.pop() next_line = geom.Line(prev_line.p1, next_pt) else: next_line = geom.Line(prev_pt, next_pt) # next_line = geom.Line(prev_pt, next_pt) path.append(next_line) prev_pt = next_pt path = self._fix_intersections(path) path = self._fix_reversals(path) return path
def convert_rect(element): """Convert an SVG rect shape element to four geom.Line segments. Args: element: An SVG 'rect' element of the form <rect x='X' y='Y' width='W' height='H'/> Returns: A clockwise wound polygon as a list of geom.Line segments. """ # Convert to a clockwise wound polygon x1 = float(element.get('x', 0)) y1 = float(element.get('y', 0)) x2 = x1 + float(element.get('width', 0)) y2 = y1 + float(element.get('height', 0)) p1 = (x1, y1) p2 = (x1, y2) p3 = (x2, y2) p4 = (x2, y1) return [ geom.Line(p1, p2), geom.Line(p2, p3), geom.Line(p3, p4), geom.Line(p4, p1) ]
def process_brush_path(self, path): """ """ start_angle = util.seg_start_angle(path[0]) # First prepend the landing strip if any. strip_dist = self.brush_landing_strip if strip_dist > self.gc.tolerance: delta = geom.P.from_polar(strip_dist, start_angle + math.pi) segment = geom.Line(path[0].p1 + delta, path[0].p1) if hasattr(path[0], 'inline_start_angle'): segment.inline_end_angle = path[0].inline_start_angle path.insert(0, segment) # Then prepend the soft landing segment. landing_dist = self.tool_trail_offset if self.brush_soft_landing and landing_dist > self.gc.tolerance: delta = geom.P.from_polar(landing_dist, start_angle + math.pi) segment = geom.Line(path[0].p1 + delta, path[0].p1) if hasattr(path[0], 'inline_start_angle'): segment.inline_end_angle = path[0].inline_start_angle path.insert(0, segment) # d = max(self.tool_trail_offset, 0.01) # if first_segment.length() > d: # # If the segment is longer than the brush trail # # cut it into two segments and use the first as the landing. # seg1, seg2 = first_segment.subdivide(d / first_segment.length()) # path[0] = seg1 # path.insert(1, seg2) path[0].inline_z = self.z_step # Append overshoot segments if brush overshoot is enabled if self.brush_overshoot_enable: if self.brush_overshoot_auto: overshoot_dist = self.tool_width / 2 else: overshoot_dist = self.brush_overshoot_distance # logger.debug('tw=%f, od=%f' % (self.tool_width, overshoot_dist)) segment = path[-1] brush_direction = util.seg_end_angle(segment) if overshoot_dist > self.gc.tolerance: delta = geom.P.from_polar(overshoot_dist, brush_direction) overshoot_endp = segment.p2 + delta overshoot_line = geom.Line(segment.p2, overshoot_endp) path.append(overshoot_line) if self.brush_soft_landing: liftoff_dist = self.tool_trail_offset if liftoff_dist > self.gc.tolerance: delta = geom.P.from_polar(liftoff_dist, brush_direction) liftoff_endp = overshoot_endp + delta liftoff_line = geom.Line(overshoot_endp, liftoff_endp) liftoff_line.inline_z = 0.0 path.append(liftoff_line) return path
def _get_polygon_rays(self, vertices, clip_rect): """Return rays that emanate from convex vertices to the outside clipping rectangle. """ rays = [] for A, B, C in vertices: # Calculate the interior angle bisector segment # using the angle bisector theorem: # https://en.wikipedia.org/wiki/Angle_bisector_theorem AC = geom.Line(A, C) d1 = B.distance(C) d2 = B.distance(A) mu = d2 / (d1 + d2) D = AC.point_at(mu) bisector = geom.Line(D, B) # find the intersection with the clip rectangle dx = bisector.p2.x - bisector.p1.x dy = bisector.p2.y - bisector.p1.y # if dx is zero the line is vertical if geom.float_eq(dx, 0.0): y = clip_rect.ymax if dy > 0 else clip_rect.ymin x = bisector.p1.x else: # if slope is zero the line is horizontal m = dy / dx b = (m * -bisector.p1.x) + bisector.p1.y if dx > 0: if geom.float_eq(m, 0.0): y = b x = clip_rect.xmax else: y = clip_rect.xmax * m + b if m > 0: y = min(clip_rect.ymax, y) else: y = max(clip_rect.ymin, y) x = (y - b) / m else: if geom.float_eq(m, 0.0): y = b x = self.clip_rect.xmin else: y = self.clip_rect.xmin * m + b if m < 0: y = min(clip_rect.ymax, y) else: y = max(clip_rect.ymin, y) x = (y - b) / m clip_pt = geom.P(x, y) rays.append(geom.Line(bisector.p2, clip_pt)) return rays
def _draw_tool_mark(self, segment, u, angle): # This will be the midpoint of the tool mark line. p = segment.point_at(u) if not self.gcgen.float_eq(self.tool_offset, 0.0): # Calculate and draw the tool offset mark. px = p + geom.P.from_polar(self.tool_offset, angle - math.pi) if self.show_toolmarks: self.svg.create_line(p, px, self._styles['tooloffset'], parent=self.tool_layer) else: # No tool offset px = p # Calculate the endpoints of the tool mark. r = self.tool_width / 2 p1 = px + geom.P.from_polar(r, angle + math.pi / 2) p2 = px + geom.P.from_polar(r, angle - math.pi / 2) toolmark_line = geom.Line(p1, p2) if (not self.toolmarks or not toolmark_line.is_coincident(self.toolmarks[-1])): if self.show_toolmarks: self.svg.create_line(p1, p2, self._styles['toolmark'], parent=self.tool_layer) # Save toolmarks for toolpath outline and sub-path creation self.toolmarks.append(toolmark_line)
def _concave_vertices(self, vertices, max_angle=math.pi): """ Args: vertices: the polygon vertices. An iterable of 2-tuple (x, y) points. max_angle: Maximum interior angle of the concave vertices. Only concave vertices with an interior angle less than this will be returned. Returns: A list of triplet vertices that are pointy towards the inside and a new, somewhat more convex, polygon with the concave vertices closed. """ concave_verts = [] new_polygon = [] clockwise = polygon.area(vertices) < 0 i = -3 if vertices[-1] == vertices[0] else -2 vert1 = vertices[i] vert2 = vertices[i + 1] for vert3 in vertices: seg = geom.Line(vert1, vert2) side = seg.which_side(vert3) angle = abs(vert2.angle2(vert1, vert3)) if angle < max_angle and ((clockwise and side < 0) or (not clockwise and side > 0)): concave_verts.append((vert1, vert2, vert3)) new_polygon.append(vert3) elif not new_polygon or vert2 != new_polygon[-1]: new_polygon.append(vert2) vert1 = vert2 vert2 = vert3 return (concave_verts, new_polygon)
def _linex_segment(self, ray, path1, path2): """ Get the segment from the starting point of the ray (on the first path) to its intersection with the second path. """ ilines = [] # d1 = self._pline_distance(ray.p1, path1) # logger.debug('ray d1: %.3f', d1) for seg in path2: px = ray.intersection(seg, segment=True) if px is not None: # geom.debug.draw_point(px) lx = geom.Line(ray.p1, px) # geom.debug.draw_line(lx, color='#ff00ff') ilines.append(lx) # If multiple intersections, return the shortest. if ilines: # logger.debug('intersects: %d', len(ilines)) ilines.sort(key=lambda l: l.length()) # ilines.sort(key=lambda lx: abs(d1 - self._pline_distance(lx.p2, path2))) for lx in ilines: if lx.length() > 0.001: break # logger.debug('l: %f', lx.length()) # lx = ilines[0] # geom.debug.draw_line(lx, color='#ff00ff', width=15, opacity=.5) # d = abs(d1 - self._pline_distance(lx.p2, path2)) # logger.debug('lx d: %.3f', d) return lx return None
def plot_feed(self, endp): """Plot G01 - linear feed from current position to :endp:(x,y,z,a).""" if self.show_toolmarks: self._draw_tool_marks(geom.Line(self._current_xy, endp), start_angle=self._current_a, end_angle=endp[3]) if self._current_xy.distance(endp) > geom.const.EPSILON: self.svg.create_line(self._current_xy, endp, self._styles['feedline']) self._update_location(endp)
def AlmostALine(self): mid_point = self.MidParam(0.5) if geom.Line(self.s, self.e - self.s).Dist(mid_point) <= geom.tolerance: return True max_arc_radius = 1.0 / geom.tolerance radius = self.c.Dist(self.s) if radius > max_arc_radius: return True return False
def _fix_reversals(self, path): """Collapse path reversals. This is when the next segment direction is more than 90deg from current segment direction... """ # This works in O(n) time... skip_ahead = 0 line1 = path[0] fixed_path = [] for i, line2 in enumerate(path[1:]): if i < skip_ahead: continue skip_ahead = 0 angle = line1.p2.angle2(line1.p1, line2.p2) if abs(angle) < math.pi / 2: if angle > 0: # right turn. corner is poking outwards. # geom.debug.draw_point(line1.p2, color='#0000ff') # Move forward until next reversal for j, line2 in enumerate(path[(i + 1):]): angle = line1.p2.angle2(line1.p1, line2.p2) if abs(angle) > math.pi / 2: # geom.debug.draw_line(line2, color='#ff0000') line2 = geom.Line(line1.p2, line2.p2) skip_ahead = i + j + 1 break else: # left turn. corner is poking inwards. # geom.debug.draw_point(line1.p2, color='#ff0000') # Move forward until next reversal for j, line2 in enumerate(path[(i + 1):]): line1 = geom.Line(line1.p1, line2.p1) angle = line1.p2.angle2(line1.p1, line2.p2) if abs(angle) > math.pi / 2: skip_ahead = i + j + 1 break fixed_path.append(line1) line1 = line2 fixed_path.append(line1) return fixed_path
def _simplify_path(self, path, tolerance): points1, points2 = zip(*path) points1 = list(points1) points2 = list(points2) points1.append(points2[-1]) points = polygon.simplify_polyline_rdp(points1, tolerance) new_path = [] prev_pt = points[0] for next_pt in points[1:]: next_line = geom.Line(prev_pt, next_pt) new_path.append(next_line) prev_pt = next_pt return new_path
def convert_line(element): """Convert an SVG line shape element to a geom.Line. Args: element: An SVG 'line' element of the form: <line x1='X1' y1='Y1' x2='X2' y2='Y2/> Returns: A line segment: geom.Line((x1, y1), (x2, y2)) """ x1 = float(element.get('x1', 0)) y1 = float(element.get('y1', 0)) x2 = float(element.get('x2', 0)) y2 = float(element.get('y2', 0)) return geom.Line((x1, y1), (x2, y2))
def convert_polygon(element): """Convert an SVG `polygon` shape element to a list line segments. Args: element: An SVG 'polygon' element of the form: <polygon points='x1,y1 x2,y2 x3,y3 [...]'/> Returns: A list of geom.Line segments. The polygon will be closed. """ segments = convert_polyline(element) # Close the polygon if not already so if len(segments) > 1 and segments[-1] != segments[0]: segments.append(geom.Line(segments[-1], segments[0])) return segments
def _convex_vertices(self, vertices): """ :param vertices: the polygon vertices. An iterable of 2-tuple (x, y) points. :return: A list of triplet vertices that are pointy towards the outside. """ pointy_verts = [] clockwise = polygon.area(vertices) < 0 i = -3 if vertices[-1] == vertices[0] else -2 vert1 = vertices[i] vert2 = vertices[i + 1] for vert3 in vertices: seg = geom.Line(vert1, vert2) side = seg.which_side(vert3, inline=True) if side != 0 and ((clockwise and side > 0) or (not clockwise and side < 0)): pointy_verts.append((vert1, vert2, vert3)) vert1 = vert2 vert2 = vert3 return pointy_verts
def convert_polyline(element): """Convert an SVG `polyline` shape element to a list of line segments. Args: element: An SVG 'polyline' element of the form: <polyline points='x1,y1 x2,y2 x3,y3 [...]'/> Returns: A list of geom.Line segments. """ segments = [] points = element.get('points', '').split() sx, sy = points[0].split(',') start_p = geom.P(float(sx), float(sy)) prev_p = start_p for point in points[1:]: sx, sy = point.split(',') p = geom.P(float(sx), float(sy)) segments.append(geom.Line(prev_p, p)) prev_p = p return segments
def __init__(self, verts, target, error): # There are three of everything here. # Image coordinates for the three vertices, in pixels, listed clockwise. self.verts = verts # Target world coordinates of vertices, in meters. self.target = target # Error vector in meters (difference from the target point to be corrected.) self.error = error # Construct edge lines from each vertex to its clockwise neighbor. self.edges = [ geom.Line(verts[(i + 1) % 3], verts[(i + 2) % 3]) for i in range(3) ] # Perpendicular distances from each vertex to the opposing edge line. self.dists = [e.signedDist(v) for v, e in zip(self.verts, self.edges)] pass
def parse_path_geom(path_data, ellipse_to_bezier=False): """ Parse SVG path data and convert to geometry objects. Args: path_data: The `d` attribute value of an SVG path element. ellipse_to_bezier: Convert elliptical arcs to bezier curves if True. Default is False. Returns: A list of zero or more subpaths. A subpath being a list of zero or more Line, Arc, EllipticalArc, or CubicBezier objects. """ subpath = [] subpath_list = [] p1 = (0.0, 0.0) for cmd, params in svg.parse_path(path_data): p2 = (params[-2], params[-1]) if cmd == 'M': # Start of path or sub-path if subpath: subpath_list.append(subpath) subpath = [] elif cmd == 'L': subpath.append(geom.Line(p1, p2)) elif cmd == 'A': rx = params[0] ry = params[1] phi = params[2] large_arc = params[3] sweep_flag = params[4] elliptical_arc = geom.ellipse.EllipticalArc.from_endpoints( p1, p2, rx, ry, large_arc, sweep_flag, phi) if elliptical_arc is None: # Parameters must be degenerate... # Try just making a line logger = logging.getLogger(__name__) logger.debug('Degenerate arc...') subpath.append(geom.Line(p1, p2)) elif geom.float_eq(rx, ry): # If it's a circular arc then create one using # the previously computed ellipse parameters. segment = geom.Arc(p1, p2, rx, elliptical_arc.sweep_angle, elliptical_arc.center) subpath.append(segment) elif ellipse_to_bezier: # Convert the elliptical arc to cubic Beziers subpath.extend(bezier.bezier_ellipse(elliptical_arc)) else: subpath.append(elliptical_arc) elif cmd == 'C': c1 = (params[0], params[1]) c2 = (params[2], params[3]) subpath.append(bezier.CubicBezier(p1, c1, c2, p2)) elif cmd == 'Q': c1 = (params[0], params[1]) subpath.append(bezier.CubicBezier.from_quadratic(p1, c1, p2)) p1 = p2 if subpath: subpath_list.append(subpath) return subpath_list
def _get_segments(self, path1, path2): """ This is ridiculously non-optimal, but who cares... """ bbox = polygon.bounding_box([p1 for p1, _p2 in path1] + [p1 for p1, _p2 in path2]) # Make sure both paths are going more or less in the same direction # by checking if the distance between the starting points is less # than the distance between one path starting point and the other # path ending point. d1 = path1[0].p1.distance(path2[0].p1) d2 = path1[0].p1.distance(path2[-1].p2) if d1 > d2: geom.util.reverse_path(path2) rays = [geom.Line(path1[0].p1, path2[0].p1)] rayside = path1[0].which_side(path2[0].p1) p0 = path1[0].p1 for p1, p2 in path1[1:]: if abs(p1.angle2(p0, p2)) > geom.const.EPSILON: # The angle bisector at each vertex bisector = geom.Line(p1, p1.bisector(p0, p2)) # Make sure it's pointing the right way if geom.Line(p0, p1).which_side(bisector.p2) != rayside: bisector = bisector.flipped() # geom.debug.draw_line(bisector, width=11) bisector = bisector.extend(bbox.diagonal()) # See if it intersects a segment on the other path lx = self._linex_segment(bisector, path1, path2) if lx is not None: rays.append(lx) p0 = p1 # Do the same for the other path rayside = -rayside p0 = path2[0].p1 for p1, p2 in path2[1:]: bisector = geom.Line(p1, p1.bisector(p0, p2)) if geom.Line(p0, p1).which_side(bisector.p2) != rayside: bisector = bisector.flipped() # geom.debug.draw_line(bisector, width=11) bisector = bisector.extend(bbox.diagonal()) lx = self._linex_segment(bisector, path2, path1) if lx is not None: rays.append(lx.reversed()) p0 = p1 # Last line joining the path endpoints rays.append(geom.Line(path1[-1].p2, path2[-1].p2)) # Sort the segment endpoints on each path by distance from first # path endpoint. Then connect the sorted segment endpoints. p1list = [seg.p1 for seg in rays] p2list = [seg.p2 for seg in rays] p1list.sort(key=lambda p: self._pline_distance(p, path1)) p2list.sort(key=lambda p: self._pline_distance(p, path2)) rays = [geom.Line(p1, p2) for p1, p2 in zip(p1list, p2list)] # Sort the segments by distance on first path # rays.sort(key=lambda seg: self._pline_distance(seg.p1, path1)) # for i, ray in enumerate(rays): # logger.debug('ray[%d]: %.3f, %.3f', i, ray.length(), self._pline_distance(ray.p1, path1)) # geom.debug.draw_point(ray.p1, radius=7, color='#ffff00') # geom.debug.draw_point(rays[8].p1, radius=7, color='#ffff00') # geom.debug.draw_line(path1[5], color='#ffff00', width=7) # if path1[5].point_on_line(rays[8].p1): # logger.debug('yep') # logger.debug('d: %.3f', self._pline_distance(rays[25].p1, path1)) # return self._elim_crossings(rays, path1, path2) return rays
def RenderGrid2(cad, view_point, max_number_across, in_between_spaces, miss_main_lines, bg, cc, brightness, plane_mode): zval = 0.5 size = view_point.viewport.GetViewportSize() sp = [] sp.append(geom.Point3D(0, 0, zval)) sp.append(geom.Point3D(size.GetWidth(), 0, zval)) sp.append(geom.Point3D(size.GetWidth(), size.GetHeight(), zval)) sp.append(geom.Point3D(0, size.GetHeight(), zval)) vx, vy, plane_mode2 = view_point.GetTwoAxes(False, plane_mode) datum = geom.Point3D(0, 0, 0) orimat = cad.GetDrawMatrix(False) datum.Transform(orimat) orimat = geom.Matrix(datum, vx, vy) unit_forward = view_point.forwards_vector().Normalized() plane_dp = math.fabs( geom.Point3D(0, 0, 1).Transformed(orimat) * unit_forward) if plane_dp < 0.3: return plane = geom.Plane( geom.Point3D(0, 0, 0).Transformed(orimat), geom.Point3D(0, 0, 1).Transformed(orimat)) for i in range(0, 4): p1 = view_point.glUnproject(sp[i]) sp[i].z = 0.0 p2 = view_point.glUnproject(sp[i]) if p1.Dist(p2) < 0.00000000001: return line = geom.Line(p1, p2) pnt = line.IntersectPlane(plane) if pnt != None: sp[i].x = (pnt * vx) - (datum * vx) sp[i].y = (pnt * vy) - (datum * vy) sp[i].z = 0.0 b = geom.Box3D() for i in range(0, 4): b.InsertPoint(sp[i].x, sp[i].y, sp[i].z) width = b.Width() height = b.Height() biggest_dimension = None if height > width: biggest_dimension = height else: biggest_dimension = width widest_spacing = biggest_dimension / max_number_across dimmer = False dimness_ratio = 1.0 spacing = None if cad.draw_to_grid: spacing = cad.digitizing_grid if miss_main_lines == False: spacing = spacing * 10 if spacing < 0.0000000001: return if biggest_dimension / spacing > max_number_across * 1.5: return if biggest_dimension / spacing > max_number_across: dimmer = True dimness_ratio = (max_number_across * 1.5 - biggest_dimension / spacing) / (max_number_across * 0.5) else: l = math.log10(widest_spacing / cad.view_units) intl = int(l) if l > 0: intl = intl + 1 spacing = math.pow(10.0, intl) * cad.view_units if cad.grid_mode == 3: dimmer = True dimness_ratio = dimness_ratio * plane_dp dimness_ratio = dimness_ratio * plane_dp ext2d = [b.x[0], b.x[1], b.x[3], b.x[4]] for i in range(0, 4): intval = int(ext2d[i] / spacing) if i < 2: if ext2d[i] < 0: intval -= 1 elif ext2d[i] > 0: intval += 1 ext2d[i] = intval * spacing if cc: col = HeeksColor(cc.red, cc.green, cc.blue) if cad.grid_mode == 3: if plane_mode2 == 0: col.green = int(0.6 * float(bg.green)) elif plane_mode2 == 1: col.red = int(0.9 * float(bg.red)) else: col.blue = bg.blue if dimmer: d_brightness = float(brightness) * dimness_ratio uc_brightness = int(d_brightness) glColor4ub(col.red, col.green, col.blue, uc_brightness) else: glColor4ub(col.red, col.green, col.blue, brightness) glBegin(GL_LINES) extra = 0.0 if in_between_spaces: extra = spacing * 0.5 x = ext2d[0] - extra while x < ext2d[2] + extra: if miss_main_lines: xr = x / spacing / 5 temp = xr if temp > 0: temp += 0.5 else: temp -= 0.5 temp = int(temp) temp = float(temp) if math.fabs(xr - temp) < 0.1: x += spacing continue temp = datum + (vx * x) + (vy * ext2d[1]) glVertex3d(temp.x, temp.y, temp.z) temp = datum + (vx * x) + (vy * ext2d[3]) glVertex3d(temp.x, temp.y, temp.z) x += spacing y = ext2d[1] - extra while y < ext2d[3] + extra: if miss_main_lines: yr = y / spacing / 5 temp = yr if temp > 0: temp += 0.5 else: temp -= 0.5 temp = int(temp) temp = float(temp) if math.fabs(yr - temp) < 0.1: y += spacing continue temp = datum + (vx * ext2d[0]) + (vy * y) glVertex3d(temp.x, temp.y, temp.z) temp = datum + (vx * ext2d[2]) + (vy * y) glVertex3d(temp.x, temp.y, temp.z) y += spacing glEnd()
def offset_path(path, offset, min_arc_dist, g1_tolerance=None): """Recalculate path to compensate for a trailing tangential offset. This will shift all of the segments by `offset` amount. Arcs will be recalculated to correct for the shift offset. Args: path: The path to recalculate. offset: The amount of tangential tool trail. min_arc_dist: The minimum distance between two connected segment end points that can be bridged with an arc. A line will be used if the distance is less than this. g1_tolerance: The angle tolerance to determine if two segments are g1 continuous. Returns: A new path Raises: :class:`cam.toolpath.ToolpathException`: if the path contains segment types other than Line or Arc. """ if geom.float_eq(offset, 0.0): return path offset_path = [] prev_seg = None prev_offset_seg = None for seg in path: if seg.p1 == seg.p2: # Skip zero length segments continue if isinstance(seg, geom.Line): # Line segments are easy - just shift them forward by offset offset_seg = seg.shift(offset) elif isinstance(seg, geom.Arc): offset_seg = offset_arc(seg, offset) else: raise toolpath.ToolpathException('Unrecognized path segment type.') # Fix discontinuities caused by offsetting non-G1 segments if prev_seg is not None: if prev_offset_seg.p2 != offset_seg.p1: seg_distance = prev_offset_seg.p2.distance(offset_seg.p1) # If the distance between the two segments is less than the # minimum arc distance or if the segments are G1 continuous # then just insert a connecting line. if (seg_distance < min_arc_dist or geom.segments_are_g1( prev_offset_seg, offset_seg, g1_tolerance)): connect_seg = geom.Line(prev_offset_seg.p2, offset_seg.p1) else: # Insert an arc in tool path to rotate the tool to the next # starting tangent when the segments are not G1 continuous. # TODO: avoid creating tiny segments by extending # offset segment. p1 = prev_offset_seg.p2 p2 = offset_seg.p1 angle = prev_seg.p2.angle2(p1, p2) # TODO: This should be a straight line if the arc is tiny connect_seg = geom.Arc(p1, p2, offset, angle, prev_seg.p2) # if connect_seg.length() < 0.01: # logger.debug('tiny arc! length= %f, radius=%f, angle=%f', connect_seg.length(), connect_seg.radius, connect_seg.angle) connect_seg.inline_start_angle = prev_seg.end_tangent_angle() connect_seg.inline_end_angle = seg.start_tangent_angle() offset_path.append(connect_seg) prev_offset_seg = connect_seg elif (geom.segments_are_g1(prev_seg, seg, g1_tolerance) and not hasattr(prev_seg, 'ignore_g1') and not hasattr(seg, 'ignore_g1')): # Add hint for smoothing pass prev_offset_seg.g1 = True prev_seg = seg prev_offset_seg = offset_seg offset_path.append(offset_seg) # Compensate for starting angle start_angle = (offset_path[0].p1 - path[0].p1).angle() offset_path[0].inline_start_angle = start_angle return offset_path