def fromShapelyLineString(cls, prefix: str, shapely_path: shapely.geometry.LineString, safeToClose: bool) -> 'SvgPath': ''' ''' pts = list(shapely_path.coords) discretized_svg_path: List[complex] = [ complex(pt[0], pt[1]) for pt in pts ] svg_path = svgpathtools.Path() for i in range(len(discretized_svg_path) - 1): start = discretized_svg_path[i] end = discretized_svg_path[i + 1] svg_path.append(svgpathtools.Line(start, end)) # last one : from end point to start point if safeToClose: start = discretized_svg_path[-1] end = discretized_svg_path[0] svg_path.append(svgpathtools.Line(start, end)) return SvgPath(prefix, {'d': svg_path.d(), 'fill-rule': 'nonzero'})
def rectangle(h, w) : rect = [] rect.append(svg.Line(0 + 0j, w + 0j)) rect.append(svg.Line(w + 0j, w + h * 1j)) rect.append(svg.Line(w + h * 1j, 0 + h * 1j)) rect.append(svg.Line(0 + h * 1j, 0 + 0j)) return svg.Path(*rect)
def make_rectangle(cls, left, top, bottom, right, width, color='black'): path = cls( svgpathtools.Path( svgpathtools.Line(left + top * 1j, right + top * 1j), svgpathtools.Line(right + top * 1j, right + bottom * 1j), svgpathtools.Line(right + bottom * 1j, left + bottom * 1j), svgpathtools.Line(left + bottom * 1j, left + top * 1j), ), {'stroke': color, 'stroke-width': f'{width}', 'fill': 'none'} ) path.svg_attributes['stroke'] = color if isinstance(width, units.GraphicUnits): path.width = width return path
def visualize_pen_transits(paths, svg_file): # We will construct a new image by storing (path, attribute) # pairs in this list. parts = list() last_end = None for i, path in enumerate(paths): start = path.start end = path.end # Generate a color based on how far along in the plot we are. frac = i / (len(paths) - 1) color = generate_color(frac, 1.0, 1.0) if last_end is not None: # If this isn't our first path, add a line between the end of # the last path and the start of this one. parts.append( ( svgpathtools.Line(last_end, start), { "stroke": "black", "fill": "none", }, ) ) last_end = end # Also draw a faded, colorized version of this path. parts.append((path, {"stroke": color, "fill": "none", "opacity": "0.5"})) new_paths, new_attrs = zip(*parts) svgpathtools.wsvg(new_paths, attributes=new_attrs, filename=svg_file)
def add_quadratic(q): paths.append(q) q_ctrl = svgpathtools.Path(svgpathtools.Line(q.start, q.control), svgpathtools.Line(q.control, q.end)) paths.append(q_ctrl) colors.append((200, 50, 50)) # q_color colors.append((150, 150, 150)) # q_ctrl_color dots.append(q.start) dots.append(q.control) dots.append(q.end) ncols.append('purple') ncols.append('purple') ncols.append('purple') nradii.append(r) nradii.append(r) nradii.append(r) stroke_widths.append(3.0) stroke_widths.append(1.5)
def get_path(points): acc = [] start = complex(*points[0]) points = points[1:] + points[:1] for p in points: end = complex(*p) line = svg.Line(start, end) acc.append(line) start = end return svg.Path(*acc)
def make_line(cls, p1, p2, width, color='black'): path = cls( svgpathtools.Path( svgpathtools.Line(p1[0] + p1[1] * 1j, p2[0] + p2[1] * 1j), ), {'stroke': color, 'stroke-width': f'{width}', 'fill': 'none'} ) path.svg_attributes['stroke'] = color if isinstance(width, units.GraphicUnits): path.width = width return path
def save_svgpathtools(self, filename): lines = [] for x, y, _ in self.lines: for i in range(len(x) - 1): lines.append( svg.Line(x[i] + y[i] * -1j, x[i + 1] + y[i + 1] * -1j)) line_colors = [ color for x, _, color in self.lines for _ in range(len(x)) ] text_path = [] for x, y, _, _, _ in self.texts: for i in range(len(x)): text_path.append( svg.Line(x[i] + y[i] * -1j, x[i] + 1.0 + y[i] * -1j)) text = [text for x, _, text, _, _ in self.texts for _ in range(len(x))] svg.wsvg(lines, line_colors, stroke_widths=[0.01] * len(lines), text=text, text_path=text_path, font_size=[0.1] * len(text), filename=filename)
def approximate_with_line_segs(seg, tol, min_length): # Potential line approximation. line = svgpathtools.Line(seg.start, seg.end) # Check if the line segment is already super short. if len(line) > min_length: # Check if the line segment is within the tolerance at the quartiles. for p in [.25, .5, .75]: if np.abs(seg.point(p) - line.point(p)) > tol: left_seg, right_seg = seg.split(.5) return (approximate_with_line_segs(left_seg, tol, min_length) + approximate_with_line_segs(right_seg, tol, min_length)) # Return the line if it's already short or sufficiently close to the curve. return [line]
def save_svg(self, filename): lines = [ svg.Line(start[0] + start[1] * 1j, end[0] + end[1] * 1j) for [start, end], _ in self.lines ] line_colors = [color for [_, _], color in self.lines] nodes = [point[0] + point[1] * 1j for point, _ in self.nodes] node_colors = [color for _, color in self.nodes] text_path = [ svg.Line(coordinate[0] + coordinate[1] * 1j, coordinate[0] + 100.0 + coordinate[1] * 1j) for coordinate, _, _, _ in self.texts ] text = [text for _, text, _, _ in self.texts] svg.wsvg(lines, line_colors, stroke_widths=[1.0] * len(lines), nodes=nodes, node_colors=node_colors, node_radii=[2.5] * len(nodes), text=text, text_path=text_path, font_size=[5] * len(text), filename=filename)
def segment_svg(svg_file, output_file, segment_size=8): paths, _ = svgpathtools.svg2paths(svg_file) lines = [] for path in paths: for cp in path.continuous_subpaths(): last_pos = None segments = int(cp.length() / segment_size) for frac in np.linspace(0., 1., segments): pos = cp.point(frac) if last_pos: lines.append(svgpathtools.Line(last_pos, pos)) last_pos = pos svgpathtools.wsvg(lines, filename=output_file)
def from_primitive(cls, primitive_type, primitive): if primitive_type == PT_LINE: start = primitive[0] + primitive[1] * 1j end = primitive[2] + primitive[3] * 1j width = primitive[4] primitive = svgpathtools.Line(start, end) elif primitive_type == PT_QBEZIER: p1 = primitive[0] + primitive[1] * 1j p2 = primitive[2] + primitive[3] * 1j p3 = primitive[4] + primitive[5] * 1j width = primitive[6] primitive = svgpathtools.QuadraticBezier(p1, p2, p3) else: raise NotImplementedError path = svgpathtools.Path(primitive) attributes = {'stroke': 'black', 'stroke-width': f'{width}', 'fill': 'none'} return cls(path, attributes)
def point_inside_curve(c, p, s=0): crv = np.array(c) center = np.array([np.average(crv[:, 0]), np.average(crv[:, 1])]) new_crv = [] for pt in crv: if s == 0: new_crv.append(pt) else: new_crv.append(explode(pt, center, (s / 2))) new_crv = np.array(new_crv) pth = svgpathtools.parse_path(get_path_for_curve(new_crv)) bbox = pth.bbox() end = p[0] + 1j * bbox[3] p = p[0] + 1j * p[1] curve = svgpathtools.Path(svgpathtools.Line(p, end)) intersection_count = len(curve.intersect(pth)) return intersection_count % 2 == 1
def _bezier_to_svg_arc(segment, max_dist, min_radius): p1 = segment.start p2 = segment.point(0.5) p3 = segment.end p = [] # if p1, p2, p3 are collinear, then # (p1-p3)/(p3-p2) is real tol = 1.e-5 _ratio = (p1-p3)/(p3-p2) if np.abs(np.imag(_ratio)) < tol: a = spt.Line(start=p1, end=p3) p.append(a) else: t_start = 0 t_end = 1 while t_start < t_end: a, t_arc = fit_arc(segment, t_start, t_end, max_dist, min_radius) p.append(a) t_start += t_arc return p
def join_close_paths(paths, threshold): new_paths = [] current_path = [] for path in paths: start = path[0].start end = path[-1].end if current_path: last_point = current_path[-1].end start_point = path[0].start if dist(last_point, start_point) < threshold: current_path.append(svgpathtools.Line(last_point, start_point)) current_path.extend(path) continue else: new_paths.append(svgpathtools.Path(*current_path)) current_path = list(path) if current_path: new_paths.append(svgpathtools.Path(*current_path)) return new_paths
def fromCircleDef(cls, center: float, radius: float) -> 'SvgPath': ''' PyCut Tab import in svg viewer ''' NB_SEGMENTS = 12 angles = [ float(k * M_PI) / (NB_SEGMENTS) for k in range(NB_SEGMENTS * 2 + 1) ] discretized_svg_path : List[complex] = [ complex( \ center[0] + radius*math.cos(angle), center[1] + radius*math.sin(angle) ) for angle in angles] svg_path = svgpathtools.Path() for i in range(len(discretized_svg_path) - 1): start = discretized_svg_path[i] end = discretized_svg_path[i + 1] svg_path.append(svgpathtools.Line(start, end)) return SvgPath("pycut_tab", {'d': svg_path.d()})
def approximate_with_line_segs(seg, tol, min_length, depth, max_depth): # Potential line approximation. line = svgpathtools.Line(seg.start, seg.end) done = True # Check if the line segment is already super short if line.length() > min_length: # Check if the line segment is within the tolerance at the quartiles for p in [.25, .5, .75]: if np.abs(seg.point(p) - line.point(p)) > tol: done = False break if depth < max_depth and not done: left_seg, right_seg = seg.split(.5) path_left, done_left = approximate_with_line_segs( left_seg, tol, min_length, depth + 1, max_depth) path_right, done_right = approximate_with_line_segs( right_seg, tol, min_length, depth + 1, max_depth) return (path_left + path_right), (done_left and done_right) # Return the line if it's already short or sufficiently close to the curve return [line], done
def find_path_between_points(p1, p2): # temp if ruv.distance(p1, p2) < 100: edist = 100 - ruv.distance(p1, p2) mid = np.array([ (p1[0] + p1[1]) / 2, (p2[0] + p2[1]) / 2 ]) dir = p1[0] % 2 == 0 vec = (p2 - p1) / ruv.distance(p1, p2) a = vec[0] b = vec[1] if dir: a = -a else: b = -b vec[0] = b vec[1] = a p0 = mid + (vec * edist) pth = svgpathtools.Path(svgpathtools.QuadraticBezier(complex_as_point(p1), complex_as_point(ruv.cp_for(p1, p0, p2)), complex_as_point(p2))) else: pth = svgpathtools.Path(svgpathtools.Line(complex_as_point(p1), complex_as_point(p2))) return pth.d()
def from_svg_path(path_str, shape_to_canvas=torch.eye(3), force_close=False): path = svgpathtools.parse_path(path_str) if len(path) == 0: return [] ret_paths = [] subpaths = path.continuous_subpaths() for subpath in subpaths: if subpath.isclosed(): if len(subpath) > 1 and isinstance( subpath[-1], svgpathtools.Line) and subpath[-1].length() < 1e-5: subpath.remove(subpath[-1]) subpath[-1].end = subpath[0].start # Force closing the path subpath.end = subpath[-1].end assert (subpath.isclosed()) else: beg = subpath[0].start end = subpath[-1].end if abs(end - beg) < 1e-5: subpath[-1].end = beg # Force closing the path subpath.end = subpath[-1].end assert (subpath.isclosed()) elif force_close: subpath.append(svgpathtools.Line(end, beg)) subpath.end = subpath[-1].end assert (subpath.isclosed()) num_control_points = [] points = [] for i, e in enumerate(subpath): if i == 0: points.append((e.start.real, e.start.imag)) else: # Must begin from the end of previous segment assert (e.start.real == points[-1][0]) assert (e.start.imag == points[-1][1]) if isinstance(e, svgpathtools.Line): num_control_points.append(0) elif isinstance(e, svgpathtools.QuadraticBezier): num_control_points.append(1) points.append((e.control.real, e.control.imag)) elif isinstance(e, svgpathtools.CubicBezier): num_control_points.append(2) points.append((e.control1.real, e.control1.imag)) points.append((e.control2.real, e.control2.imag)) elif isinstance(e, svgpathtools.Arc): # Convert to Cubic curves # https://www.joecridge.me/content/pdf/bezier-arcs.pdf start = e.theta * math.pi / 180.0 stop = (e.theta + e.delta) * math.pi / 180.0 sign = 1.0 if stop < start: sign = -1.0 epsilon = 0.00001 # debug = abs(e.delta) >= 90.0 while (sign * (stop - start) > epsilon): arc_to_draw = stop - start if arc_to_draw > 0.0: arc_to_draw = min(arc_to_draw, 0.5 * math.pi) else: arc_to_draw = max(arc_to_draw, -0.5 * math.pi) alpha = arc_to_draw / 2.0 cos_alpha = math.cos(alpha) sin_alpha = math.sin(alpha) cot_alpha = 1.0 / math.tan(alpha) phi = start + alpha cos_phi = math.cos(phi) sin_phi = math.sin(phi) lambda_ = (4.0 - cos_alpha) / 3.0 mu = sin_alpha + (cos_alpha - lambda_) * cot_alpha last = sign * (stop - (start + arc_to_draw)) <= epsilon num_control_points.append(2) rx = e.radius.real ry = e.radius.imag cx = e.center.real cy = e.center.imag rot = e.phi * math.pi / 180.0 cos_rot = math.cos(rot) sin_rot = math.sin(rot) x = lambda_ * cos_phi + mu * sin_phi y = lambda_ * sin_phi - mu * cos_phi xx = x * cos_rot - y * sin_rot yy = x * sin_rot + y * cos_rot points.append((cx + rx * xx, cy + ry * yy)) x = lambda_ * cos_phi - mu * sin_phi y = lambda_ * sin_phi + mu * cos_phi xx = x * cos_rot - y * sin_rot yy = x * sin_rot + y * cos_rot points.append((cx + rx * xx, cy + ry * yy)) if not last: points.append( (cx + rx * math.cos(rot + start + arc_to_draw), cy + ry * math.sin(rot + start + arc_to_draw))) start += arc_to_draw # first = False if i != len(subpath) - 1: points.append((e.end.real, e.end.imag)) else: if subpath.isclosed(): # Must end at the beginning of first segment assert (e.end.real == points[0][0]) assert (e.end.imag == points[0][1]) else: points.append((e.end.real, e.end.imag)) points = torch.tensor(points) points = torch.cat((points, torch.ones([points.shape[0], 1])), dim=1) @ torch.transpose(shape_to_canvas, 0, 1) points = points / points[:, 2:3] points = points[:, :2].contiguous() ret_paths.append( Path(torch.tensor(num_control_points), points, subpath.isclosed())) return ret_paths
def convert_and_write_svg(cubic, filename): cubic_path = svgpathtools.Path(cubic) cubic_ctrl = svgpathtools.Path( svgpathtools.Line(cubic.start, cubic.control1), svgpathtools.Line(cubic.control1, cubic.control2), svgpathtools.Line(cubic.control2, cubic.end)) cubic_color = (50, 50, 200) cubic_ctrl_color = (150, 150, 150) r = 4.0 paths = [cubic_path, cubic_ctrl] colors = [cubic_color, cubic_ctrl_color] dots = [ cubic_path[0].start, cubic_path[0].control1, cubic_path[0].control2, cubic_path[0].end ] ncols = ['green', 'green', 'green', 'green'] nradii = [r, r, r, r] stroke_widths = [3.0, 1.5] def add_quadratic(q): paths.append(q) q_ctrl = svgpathtools.Path(svgpathtools.Line(q.start, q.control), svgpathtools.Line(q.control, q.end)) paths.append(q_ctrl) colors.append((200, 50, 50)) # q_color colors.append((150, 150, 150)) # q_ctrl_color dots.append(q.start) dots.append(q.control) dots.append(q.end) ncols.append('purple') ncols.append('purple') ncols.append('purple') nradii.append(r) nradii.append(r) nradii.append(r) stroke_widths.append(3.0) stroke_widths.append(1.5) prec = 1.0 queue = [cubic] num_quadratics = 0 while len(queue) > 0: c = queue[-1] queue = queue[:-1] # Criteria for conversion # http://caffeineowl.com/graphics/2d/vectorial/cubic2quad01.html p = c.end - 3 * c.control2 + 3 * c.control1 - c.start d = math.sqrt(p.real * p.real + p.imag * p.imag) * math.sqrt(3.0) / 36 t = math.pow(1.0 / d, 1.0 / 3.0) if t < 1.0: c0, c1 = split_cubic(c, 0.5) queue.append(c0) queue.append(c1) else: quadratic = cubic_to_quadratic(c) print(quadratic) add_quadratic(quadratic) num_quadratics += 1 print('num_quadratics:', num_quadratics) svgpathtools.wsvg(paths, colors=colors, stroke_widths=stroke_widths, nodes=dots, node_colors=ncols, node_radii=nradii, filename=filename)
def _line_from_points(points): return svgpathtools.Line(*(complex(*p) for p in points))
segments = numpy.linspace(0, 1, args.approximate + 1) for path in paths: for element in path: if args.verbose: print('Plotting', str(type(element)).split('.')[-1], end='') if type(element) == svgpathtools.path.Line or element.length( ) * args.scale < args.min_length: line_list = [element] if args.verbose: print(' as line: (', element.start, ',', element.end, ')') else: # adapted from svgpathtools issue #61 pts = [element.point(t) for t in segments] line_list = [ svgpathtools.Line(pts[i - 1], pts[i]) for i in range(1, len(pts)) ] if args.verbose: print(' as lines:') for line in line_list: print(' (', element.start, ',', element.end, ')') for line in line_list: if robot.stop_project_flag.is_set(): raise RuntimeError('Robot requested stop.') start = invert_y(line.start) * args.scale end = invert_y(line.end) * args.scale # if the robot's last known position isn't close enough to the start, lift the pen if numpy.linalg.norm(robot._last_coord - start) > args.epsilon:
def offset_paths(path, offset_distance, steps=100, debug=False): """Takes an svgpathtools.path.Path object, `path`, and a float distance, `offset_distance`, and returns the parallel offset curves (in the form of a list of svgpathtools.path.Path objects).""" def is_enclosed(path, check_paths): """`path` is an svgpathtools.path.Path object, `check_paths` is a list of svgpath.path.Path objects. This function returns True if `path` lies inside any of the paths in `check_paths`, and returns False if it lies outside all of them.""" seg = path[0] point = seg.point(0.5) for i in range(len(check_paths)): test_path = check_paths[i] if path == test_path: continue # find outside_point, which lies outside other_path (xmin, xmax, ymin, ymax) = test_path.bbox() outside_point = complex(xmax+100, ymax+100) if svgpathtools.path_encloses_pt(point, outside_point, test_path): if debug: print("point is within path", i, file=sys.stderr) return True return False # This only works on closed paths. if debug: print("input path:", file=sys.stderr) if debug: print(path, file=sys.stderr) if debug: print("offset:", offset_distance, file=sys.stderr) assert(path.isclosed()) # # First generate a list of Path elements (Lines and Arcs), # corresponding to the offset versions of the Path elements in the # input path. # if debug: print("generating offset segments...", file=sys.stderr) offset_path_list = [] for seg in path: if type(seg) == svgpathtools.path.Line: start = seg.point(0) + (offset_distance * seg.normal(0)) end = seg.point(1) + (offset_distance * seg.normal(1)) offset_path_list.append(svgpathtools.Line(start, end)) if debug: print(" ", offset_path_list[-1], file=sys.stderr) elif type(seg) == svgpathtools.path.Arc and (seg.radius.real == seg.radius.imag): # Circular arcs remain arcs, elliptical arcs become linear # approximations below. # # Polygons (input paths) are counter-clockwise. # # Positive offsets are to the inside of the polygon, negative # offsets are to the outside. # # If this arc is counter-clockwise (sweep == False), # *subtract* the `offset_distance` from its radius, so # insetting makes the arc smaller and outsetting makes # it larger. # # If this arc is clockwise (sweep == True), *add* the # `offset_distance` from its radius, so insetting makes the # arc larger and outsetting makes it smaller. # # If the radius of the offset arc is negative, use its # absolute value and invert the sweep. if seg.sweep == False: new_radius = seg.radius.real - offset_distance else: new_radius = seg.radius.real + offset_distance start = seg.point(0) + (offset_distance * seg.normal(0)) end = seg.point(1) + (offset_distance * seg.normal(1)) sweep = seg.sweep flipped = False if new_radius < 0.0: if debug: print(" inverting Arc!", file=sys.stderr) flipped = True new_radius = abs(new_radius) sweep = not sweep if new_radius > 0.002: radius = complex(new_radius, new_radius) offset_arc = svgpathtools.path.Arc( start = start, end = end, radius = radius, rotation = seg.rotation, large_arc = seg.large_arc, sweep = sweep ) offset_path_list.append(offset_arc) elif new_radius > epsilon: # Offset Arc radius is smaller than the minimum that # LinuxCNC accepts, replace with a Line. if debug: print(" arc too small, replacing with a line", file=sys.stderr) if flipped: old_start = start start = end end = old_start offset_arc = svgpathtools.path.Line(start = start, end = end) offset_path_list.append(offset_arc) else: # Zero-radius Arc, it disappeared. if debug: print(" arc way too small, removing", file=sys.stderr) continue if debug: print(" ", offset_path_list[-1], file=sys.stderr) else: # Deal with any segment that's not a line or a circular arc. # This includes elliptic arcs and bezier curves. Use linear # approximation. # # FIXME: Steps should probably be computed dynamically to make # the length of the *offset* line segments manageable. points = [] for k in range(steps+1): t = k / float(steps) normal = seg.normal(t) offset_vector = offset_distance * normal points.append(seg.point(t) + offset_vector) for k in range(len(points)-1): start = points[k] end = points[k+1] offset_path_list.append(svgpathtools.Line(start, end)) if debug: print(" (long list of short lines)", file=sys.stderr) # # Find all the places where one segment intersects the next, and # trim to the intersection. # if debug: print("trimming intersecting segments...", file=sys.stderr) for i in range(len(offset_path_list)): this_seg = offset_path_list[i] if (i+1) < len(offset_path_list): next_seg = offset_path_list[i+1] else: next_seg = offset_path_list[0] # FIXME: I'm not sure about this part. if debug: print("intersecting", file=sys.stderr) if debug: print(" this", this_seg, file=sys.stderr) if debug: print(" next", next_seg, file=sys.stderr) intersections = this_seg.intersect(next_seg) if debug: print(" intersections:", intersections, file=sys.stderr) if len(intersections) > 0: intersection = intersections[0] point = this_seg.point(intersection[0]) if debug: print(" intersection point:", point, file=sys.stderr) if not complex_close_enough(point, this_seg.end): this_seg.end = this_seg.point(intersection[0]) next_seg.start = this_seg.end # # Find all the places where adjacent segments do not end/start close # to each other, and join them with Arcs. # if debug: print("joining non-connecting segments with arcs...", file=sys.stderr) joined_offset_path_list = [] for i in range(len(offset_path_list)): this_seg = offset_path_list[i] if (i+1) < len(offset_path_list): next_seg = offset_path_list[i+1] else: next_seg = offset_path_list[0] if complex_close_enough(this_seg.end, next_seg.start): joined_offset_path_list.append(this_seg) continue if debug: print("these segments don't touch end to end:", file=sys.stderr) if debug: print(this_seg, file=sys.stderr) if debug: print(next_seg, file=sys.stderr) if debug: print(" error:", this_seg.end-next_seg.start, file=sys.stderr) # FIXME: Choose values for `large_arc` and `sweep` correctly here. # I think the goal is to make the joining arc tangent to the segments it joins. # large_arc should always be False # sweep means "clockwise" (but +Y is down) if debug: print("determining joining arc:", file=sys.stderr) if debug: print(" this_seg ending normal:", this_seg.normal(1), file=sys.stderr) if debug: print(" next_seg starting normal:", next_seg.normal(0), file=sys.stderr) sweep_arc = svgpathtools.path.Arc( start = this_seg.end, end = next_seg.start, radius = complex(offset_distance, offset_distance), rotation = 0, large_arc = False, sweep = True ) sweep_start_error = this_seg.normal(1) - sweep_arc.normal(0) sweep_end_error = next_seg.normal(0) - sweep_arc.normal(1) sweep_error = pow(abs(sweep_start_error), 2) + pow(abs(sweep_end_error), 2) if debug: print(" sweep arc starting normal:", sweep_arc.normal(0), file=sys.stderr) if debug: print(" sweep arc ending normal:", sweep_arc.normal(1), file=sys.stderr) if debug: print(" sweep starting error:", sweep_start_error, file=sys.stderr) if debug: print(" sweep end error:", sweep_end_error, file=sys.stderr) if debug: print(" sweep error:", sweep_error, file=sys.stderr) antisweep_arc = svgpathtools.path.Arc( start = this_seg.end, end = next_seg.start, radius = complex(offset_distance, offset_distance), rotation = 0, large_arc = False, sweep = False ) antisweep_start_error = this_seg.normal(1) - antisweep_arc.normal(0) antisweep_end_error = next_seg.normal(0) - antisweep_arc.normal(1) antisweep_error = pow(abs(antisweep_start_error), 2) + pow(abs(antisweep_end_error), 2) if debug: print(" antisweep arc starting normal:", antisweep_arc.normal(0), file=sys.stderr) if debug: print(" antisweep arc ending normal:", antisweep_arc.normal(1), file=sys.stderr) if debug: print(" antisweep starting error:", antisweep_start_error, file=sys.stderr) if debug: print(" antisweep end error:", antisweep_end_error, file=sys.stderr) if debug: print(" antisweep error:", antisweep_error, file=sys.stderr) joining_arc = None if sweep_error < antisweep_error: if debug: print("joining arc is sweep", file=sys.stderr) joining_arc = sweep_arc else: if debug: print("joining arc is antisweep", file=sys.stderr) joining_arc = antisweep_arc if debug: print("joining arc:", file=sys.stderr) if debug: print(joining_arc, file=sys.stderr) if debug: print(" length:", joining_arc.length(), file=sys.stderr) if debug: print(" start-end distance:", joining_arc.start-joining_arc.end, file=sys.stderr) # FIXME: this is kind of arbitrary joining_seg = joining_arc if joining_arc.length() < 1e-4: joining_seg = svgpathtools.path.Line(joining_arc.start, joining_arc.end) if debug: print(" too short! replacing with a line:", joining_seg, file=sys.stderr) joined_offset_path_list.append(this_seg) joined_offset_path_list.append(joining_seg) offset_path_list = joined_offset_path_list # # Find the places where the path intersects itself, split into # multiple separate paths in those places. # if debug: print("splitting path at intersections...", file=sys.stderr) offset_paths_list = split_path_at_intersections(offset_path_list) # # Smooth the path: adjacent segments whose start/end points are # "close enough" to each other are adjusted so they actually touch. # # FIXME: is this still needed? # if debug: print("smoothing paths...", file=sys.stderr) for path_list in offset_paths_list: for i in range(len(path_list)): this_seg = path_list[i] if (i+1) < len(path_list): next_seg = path_list[i+1] else: next_seg = path_list[0] if complex_close_enough(this_seg.end, next_seg.start): next_seg.start = this_seg.end else: if debug: print("gap in the path (seg %d and following):" % i, file=sys.stderr) if debug: print(" this_seg.end:", this_seg.end, file=sys.stderr) if debug: print(" next_seg.start:", next_seg.start, file=sys.stderr) # # Convert each path list to a Path object and sanity check. # if debug: print("converting path lists to paths...", file=sys.stderr) offset_paths = [] for path_list in offset_paths_list: offset_path = svgpathtools.Path(*path_list) if debug: print("offset path:", file=sys.stderr) if debug: print(offset_path, file=sys.stderr) assert(offset_path.isclosed()) offset_paths.append(offset_path) # # The set of paths we got from split_path_at_intersections() has # zero or more 'true paths' that we actually want to return, plus # zero or more 'false paths' that should be discarded. # # When offsetting a path to the inside, the false paths will be # outside the true path and will wind in the opposite direction of # the input path. # # When offsetting a path to the outside, the false paths will be # inside the true paths, and will wind in the same direction as the # input path. # # [citation needed] # if debug: print("pruning false paths...", file=sys.stderr) path_area = approximate_path_area(path) if debug: print("input path area:", path_area, file=sys.stderr) keepers = [] if offset_distance > 0: # The offset is positive (inwards), discard paths with opposite # direction from input path. for offset_path in offset_paths: if debug: print("checking path:", offset_path, file=sys.stderr) offset_path_area = approximate_path_area(offset_path) if debug: print("offset path area:", offset_path_area, file=sys.stderr) if path_area * offset_path_area < 0.0: # Input path and offset path go in the opposite directions, # drop offset path. if debug: print("wrong direction, dropping", file=sys.stderr) continue keepers.append(offset_path) else: # The offset is negative (outwards), discard paths that lie # inside any other path and have the same winding direction as # the input path. for offset_path in offset_paths: if debug: print("checking path:", offset_path, file=sys.stderr) if is_enclosed(offset_path, offset_paths): if debug: print(" enclosed", file=sys.stderr) # This path is enclosed, check the winding direction. offset_path_area = approximate_path_area(offset_path) if debug: print("offset path area:", offset_path_area, file=sys.stderr) if path_area * offset_path_area > 0.0: if debug: print(" winding is the same as input, dropping", file=sys.stderr) continue else: if debug: print(" winding is opposite input", file=sys.stderr) else: if debug: print(" not enclosed", file=sys.stderr) if debug: print(" keeping", file=sys.stderr) keepers.append(offset_path) offset_paths = keepers return offset_paths
def __init__ (self, h1, w1, h2, w2) : self.top = rectangle(h1, w1) self.joint = (h1/2) * 1j self.joint2 = w1 + (h1/2) * 1j self.bottom = rectangle(h2, w2).translated(self.joint2 - (h2/2) * 1j) self.pseudoLine = svg.Line(start=self.joint, end=self.joint2)
def from_points(cls, start, end): return cls(svgpathtools.Line(complex(*start), complex(*end)))
def __init__(self, segment): self.segment = svgpathtools.Line(*(np.round(p, INTERNAL_PRECISION) for p in segment))
def _calc_fillet_for_joint(self, p, i): seg1 = p[(i) % len(p)] seg2 = p[(i + 1) % len(p)] ori_p = svgpathtools.Path(seg1, seg2) new_p = svgpathtools.Path() # ignore the node if G1 continuity tg1 = seg1.unit_tangent(1.0) tg2 = seg2.unit_tangent(0.0) cosA = abs(tg1.real * tg2.real + tg1.imag * tg2.imag) if abs(cosA - 1.0) < 1e-6: new_p.append(seg1.cropped(self._prev_t, 1.0)) self._prev_t = 0.0 if self._very_first_t is None: self._very_first_t = 1.0 if not isclosedac(p) and i == len(p) - 2: new_p.append(seg2.cropped( 0.0, 1.0)) # add last segment if not closed else: cir = self.circle(seg1.end, self.options.radius) # new_p.extend(cir) intersects = ori_p.intersect(cir) if len(intersects) != 2: inkex.errormsg( "Some fillet or chamfer may not be drawn: %d intersections!" % len(intersects)) new_p.append(seg1.cropped(self._prev_t, 1.0)) self._prev_t = 0.0 if self._very_first_t is None: self._very_first_t = 1.0 if not isclosedac(p) and i == len(p) - 2: new_p.append(seg2.cropped( 0.0, 1.0)) # add last segment if not closed else: cb = [] segs = [] ts = [] for (T1, seg1, t1), (T2, seg2, t2) in intersects: c1 = seg1.point(t1) tg1 = seg1.unit_tangent(t1) * (self.options.radius * KAPPA) cb.extend([c1, tg1]) segs.append(seg1) ts.append(t1) # cir1 = self.circle(c1, self.options.radius * KAPPA) # new_p.extend(cir1) # new_p.append(svgpathtools.Line(c1, c1+tg1)) assert len(cb) == 4 new_p.append(segs[0].cropped(self._prev_t, ts[0])) if self.options.fillet_type == 'fillet': fillet = svgpathtools.CubicBezier(cb[0], cb[0] + cb[1], cb[2] - cb[3], cb[2]) else: fillet = svgpathtools.Line(cb[0], cb[2]) new_p.append(fillet) self._prev_t = ts[1] if self._very_first_t is None: self._very_first_t = ts[0] if isclosedac(p) and i == len(p) - 1: new_p.append(segs[1].cropped( ts[1], self._very_first_t)) # update first segment if closed elif not isclosedac(p) and i == len(p) - 2: new_p.append(segs[1].cropped( ts[1], 1.0)) # add last segment if not closed # # fix for the first segment # if p.isclosed(): # new_p[0] = p[0].cropped(ts[1], self._very_first_t) # new_p.append(segs[0].cropped(ts[0], 1.0)) # new_p.append(segs[1].cropped(0.0, ts[1])) # if self.options.fillet_type == 'fillet': # fillet = svgpathtools.CubicBezier(cb[0], cb[0]+cb[1], cb[2]-cb[3], cb[2]) # else: # fillet = svgpathtools.Line(cb[0], cb[2]) # new_p.append(fillet.reversed()) return new_p