# HG changeset patch # User mbayer # Date 1446907889 -3600 # Node ID b41cdab37aab6599738d66895140d8bcab056222 # Parent 234ad2069fddd1368985f5c13199a08a419b800d added generic infill support, alpha stage diff -r 234ad2069fdd -r b41cdab37aab svg2gcode/svg2gcode.py --- a/svg2gcode/svg2gcode.py Sat Nov 07 13:37:23 2015 +0100 +++ b/svg2gcode/svg2gcode.py Sat Nov 07 15:51:29 2015 +0100 @@ -1,8 +1,81 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- + import svg, sys from gcode import Gcode from optparse import OptionParser +from shapely.geometry import box, MultiLineString, Point, Polygon +from shapely.affinity import rotate +from shapely import speedups +from math import sqrt + +# enable Shapely speedups, if possible +if speedups.available: + speedups.enable() + +def hatchbox(rect, angle, spacing): + """ + returns a Shapely geometry (MULTILINESTRING, or more rarely, + GEOMETRYCOLLECTION) for a simple hatched rectangle. + + args: + rect - a Shapely geometry for the outer boundary of the hatch + Likely most useful if it really is a rectangle + + angle - angle of hatch lines, conventional anticlockwise -ve + + spacing - spacing between hatch lines + + GEOMETRYCOLLECTION case occurs when a hatch line intersects with + the corner of the clipping rectangle, which produces a point + along with the usual lines. + """ + + (llx, lly, urx, ury) = rect.bounds + centre_x = (urx + llx) / 2 + centre_y = (ury + lly) / 2 + diagonal_length = sqrt((urx - llx) ** 2 + (ury - lly) ** 2) + number_of_lines = 2 + int(diagonal_length / spacing) + hatch_length = spacing * (number_of_lines - 1) + + # build a square (of side hatch_length) horizontal lines + # centred on centroid of the bounding box, 'spacing' units apart + coords = [] + for i in range(number_of_lines): + # alternate lines l2r and r2l to keep HP-7470A plotter happy ☺ + if i % 2: + coords.extend([((centre_x - hatch_length / 2, centre_y + - hatch_length / 2 + i * spacing), (centre_x + + hatch_length / 2, centre_y - hatch_length + / 2 + i * spacing))]) + else: + coords.extend([((centre_x + hatch_length / 2, centre_y + - hatch_length / 2 + i * spacing), (centre_x + - hatch_length / 2, centre_y - hatch_length + / 2 + i * spacing))]) + # turn array into Shapely object + lines = MultiLineString(coords) + # Rotate by angle around box centre + lines = rotate(lines, angle, origin='centroid', use_radians=False) + # return clipped array + return rect.intersection(lines) + +def infill(bbox, polygon, angle=0, spacing=10): + b1, b2 = bbox + x1, y1 = b1.coord() + x2, y2 = b2.coord() + page = box(x1, y1, x2, y2) + hatching = hatchbox(page, angle, spacing) + # create shape from polygon: + segments = [] + for pnt in polygon: + x, y = pnt.coord() + segments.append((x, y)) + + shape = Polygon(segments) + return shape.intersection(hatching) + parser = OptionParser() parser.add_option("-f", "--file", dest="filename", default=None, help="Load SVG file", metavar="FILE") @@ -15,6 +88,9 @@ parser.add_option("-t", "", dest="travel_speed", type="float", default=130, help="travel speed mm/sec (default 130)") +parser.add_option("-o", "--outline", action="store_true", + dest="outline", default=False, + help="no infill, only outlines") (options, args) = parser.parse_args() @@ -45,11 +121,20 @@ for d in data: if hasattr(d, "segments"): for l in d.segments(1): + # THE OUTLINE x, y = normalize(l[0].coord()) gcode.move(x, y) for pt in l[1:]: x, y = normalize(pt.coord()) gcode.engrave(x, y) + if not options.outline: + # THE INFILL + for line in infill(im.bbox(), l, spacing=2): + start = normalize((line.coords[0][0], line.coords[0][1])) + end = normalize((line.coords[1][0], line.coords[1][1])) + gcode.move(start[0], start[1]) + gcode.engrave(end[0], end[1]) + # write gcode file gcode.write(options.filename + ".g")