1 #!/usr/bin/env python |
1 #!/usr/bin/env python |
|
2 # -*- coding: utf-8 -*- |
|
3 |
2 import svg, sys |
4 import svg, sys |
3 from gcode import Gcode |
5 from gcode import Gcode |
4 from optparse import OptionParser |
6 from optparse import OptionParser |
|
7 |
|
8 from shapely.geometry import box, MultiLineString, Point, Polygon |
|
9 from shapely.affinity import rotate |
|
10 from shapely import speedups |
|
11 from math import sqrt |
|
12 |
|
13 # enable Shapely speedups, if possible |
|
14 if speedups.available: |
|
15 speedups.enable() |
|
16 |
|
17 def hatchbox(rect, angle, spacing): |
|
18 """ |
|
19 returns a Shapely geometry (MULTILINESTRING, or more rarely, |
|
20 GEOMETRYCOLLECTION) for a simple hatched rectangle. |
|
21 |
|
22 args: |
|
23 rect - a Shapely geometry for the outer boundary of the hatch |
|
24 Likely most useful if it really is a rectangle |
|
25 |
|
26 angle - angle of hatch lines, conventional anticlockwise -ve |
|
27 |
|
28 spacing - spacing between hatch lines |
|
29 |
|
30 GEOMETRYCOLLECTION case occurs when a hatch line intersects with |
|
31 the corner of the clipping rectangle, which produces a point |
|
32 along with the usual lines. |
|
33 """ |
|
34 |
|
35 (llx, lly, urx, ury) = rect.bounds |
|
36 centre_x = (urx + llx) / 2 |
|
37 centre_y = (ury + lly) / 2 |
|
38 diagonal_length = sqrt((urx - llx) ** 2 + (ury - lly) ** 2) |
|
39 number_of_lines = 2 + int(diagonal_length / spacing) |
|
40 hatch_length = spacing * (number_of_lines - 1) |
|
41 |
|
42 # build a square (of side hatch_length) horizontal lines |
|
43 # centred on centroid of the bounding box, 'spacing' units apart |
|
44 coords = [] |
|
45 for i in range(number_of_lines): |
|
46 # alternate lines l2r and r2l to keep HP-7470A plotter happy ☺ |
|
47 if i % 2: |
|
48 coords.extend([((centre_x - hatch_length / 2, centre_y |
|
49 - hatch_length / 2 + i * spacing), (centre_x |
|
50 + hatch_length / 2, centre_y - hatch_length |
|
51 / 2 + i * spacing))]) |
|
52 else: |
|
53 coords.extend([((centre_x + hatch_length / 2, centre_y |
|
54 - hatch_length / 2 + i * spacing), (centre_x |
|
55 - hatch_length / 2, centre_y - hatch_length |
|
56 / 2 + i * spacing))]) |
|
57 # turn array into Shapely object |
|
58 lines = MultiLineString(coords) |
|
59 # Rotate by angle around box centre |
|
60 lines = rotate(lines, angle, origin='centroid', use_radians=False) |
|
61 # return clipped array |
|
62 return rect.intersection(lines) |
|
63 |
|
64 def infill(bbox, polygon, angle=0, spacing=10): |
|
65 b1, b2 = bbox |
|
66 x1, y1 = b1.coord() |
|
67 x2, y2 = b2.coord() |
|
68 page = box(x1, y1, x2, y2) |
|
69 hatching = hatchbox(page, angle, spacing) |
|
70 # create shape from polygon: |
|
71 segments = [] |
|
72 for pnt in polygon: |
|
73 x, y = pnt.coord() |
|
74 segments.append((x, y)) |
|
75 |
|
76 shape = Polygon(segments) |
|
77 return shape.intersection(hatching) |
5 |
78 |
6 parser = OptionParser() |
79 parser = OptionParser() |
7 parser.add_option("-f", "--file", dest="filename", default=None, |
80 parser.add_option("-f", "--file", dest="filename", default=None, |
8 help="Load SVG file", metavar="FILE") |
81 help="Load SVG file", metavar="FILE") |
9 parser.add_option("-s", "--scale", |
82 parser.add_option("-s", "--scale", |
13 dest="engrave_speed", type="float", default=20, |
86 dest="engrave_speed", type="float", default=20, |
14 help="engrave speed mm/sec (default 20)") |
87 help="engrave speed mm/sec (default 20)") |
15 parser.add_option("-t", "", |
88 parser.add_option("-t", "", |
16 dest="travel_speed", type="float", default=130, |
89 dest="travel_speed", type="float", default=130, |
17 help="travel speed mm/sec (default 130)") |
90 help="travel speed mm/sec (default 130)") |
|
91 parser.add_option("-o", "--outline", action="store_true", |
|
92 dest="outline", default=False, |
|
93 help="no infill, only outlines") |
18 |
94 |
19 |
95 |
20 (options, args) = parser.parse_args() |
96 (options, args) = parser.parse_args() |
21 |
97 |
22 |
98 |
43 |
119 |
44 data = im.flatten() |
120 data = im.flatten() |
45 for d in data: |
121 for d in data: |
46 if hasattr(d, "segments"): |
122 if hasattr(d, "segments"): |
47 for l in d.segments(1): |
123 for l in d.segments(1): |
|
124 # THE OUTLINE |
48 x, y = normalize(l[0].coord()) |
125 x, y = normalize(l[0].coord()) |
49 gcode.move(x, y) |
126 gcode.move(x, y) |
50 for pt in l[1:]: |
127 for pt in l[1:]: |
51 x, y = normalize(pt.coord()) |
128 x, y = normalize(pt.coord()) |
52 gcode.engrave(x, y) |
129 gcode.engrave(x, y) |
53 |
130 |
|
131 if not options.outline: |
|
132 # THE INFILL |
|
133 for line in infill(im.bbox(), l, spacing=2): |
|
134 start = normalize((line.coords[0][0], line.coords[0][1])) |
|
135 end = normalize((line.coords[1][0], line.coords[1][1])) |
|
136 gcode.move(start[0], start[1]) |
|
137 gcode.engrave(end[0], end[1]) |
|
138 |
54 # write gcode file |
139 # write gcode file |
55 gcode.write(options.filename + ".g") |
140 gcode.write(options.filename + ".g") |