45 # centred on centroid of the bounding box, 'spacing' units apart |
44 # centred on centroid of the bounding box, 'spacing' units apart |
46 coords = [] |
45 coords = [] |
47 for i in range(number_of_lines): |
46 for i in range(number_of_lines): |
48 # alternate lines l2r and r2l to keep HP-7470A plotter happy ☺ |
47 # alternate lines l2r and r2l to keep HP-7470A plotter happy ☺ |
49 if i % 2: |
48 if i % 2: |
50 coords.extend([((centre_x - hatch_length / 2, centre_y |
49 coords.extend([(( |
51 - hatch_length / 2 + i * spacing), (centre_x |
50 centre_x - hatch_length / 2, \ |
52 + hatch_length / 2, centre_y - hatch_length |
51 centre_y - hatch_length / 2 + i * spacing), ( |
53 / 2 + i * spacing))]) |
52 centre_x + hatch_length / 2, \ |
|
53 centre_y - hatch_length / 2 + i * spacing))]) |
54 else: |
54 else: |
55 coords.extend([((centre_x + hatch_length / 2, centre_y |
55 coords.extend([(( \ |
56 - hatch_length / 2 + i * spacing), (centre_x |
56 centre_x + hatch_length / 2, \ |
57 - hatch_length / 2, centre_y - hatch_length |
57 centre_y - hatch_length / 2 + i * spacing), ( |
58 / 2 + i * spacing))]) |
58 centre_x - hatch_length / 2, \ |
|
59 centre_y - hatch_length / 2 + i * spacing))]) |
59 # turn array into Shapely object |
60 # turn array into Shapely object |
60 lines = MultiLineString(coords) |
61 lines = MultiLineString(coords) |
61 # Rotate by angle around box centre |
62 # Rotate by angle around box centre |
62 lines = rotate(lines, angle, origin='centroid', use_radians=False) |
63 lines = rotate(lines, angle, origin='centroid', use_radians=False) |
63 # return clipped array |
64 # return clipped array |
64 return rect.intersection(lines) |
65 return rect.intersection(lines) |
65 |
|
66 |
|
67 parser = OptionParser() |
|
68 parser.add_option("-f", "--file", dest="filename", default=None, |
|
69 help="Load SVG file", metavar="FILE") |
|
70 parser.add_option("-s", "--scale", |
|
71 dest="scale", type="float", default=1.0, |
|
72 help="set scale factor (default 1.0)") |
|
73 parser.add_option("-e", "", |
|
74 dest="engrave_speed", type="float", default=20, |
|
75 help="engrave speed mm/sec (default 20)") |
|
76 parser.add_option("-t", "", |
|
77 dest="travel_speed", type="float", default=130, |
|
78 help="travel speed mm/sec (default 130)") |
|
79 parser.add_option("-o", "--outline", action="store_true", |
|
80 dest="outline", default=False, |
|
81 help="no infill, only outlines") |
|
82 |
|
83 |
|
84 (options, args) = parser.parse_args() |
|
85 |
|
86 |
|
87 if not options.filename: |
|
88 print "no filename given!" |
|
89 sys.exit(1) |
|
90 |
|
91 gcode = Gcode(scale=options.scale, travel_speed=options.travel_speed, engrave_speed=options.engrave_speed) |
|
92 |
|
93 im = svg.parse(options.filename) |
|
94 b1, b2 = im.bbox() |
|
95 width, height = b2.coord() |
|
96 print "Original dimension: %.2f x %.2f" % (width, height) |
|
97 width *= gcode.mm_pixel * options.scale |
|
98 height *= gcode.mm_pixel * options.scale |
|
99 print "Print dimension: %.2fmm x %.2fmm" % (width, height) |
|
100 |
|
101 x1, y1 = b1.coord() |
|
102 x2, y2 = b2.coord() |
|
103 page = box(x1, y1, x2, y2) |
|
104 INFILL = hatchbox(page, 0, 2) |
|
105 |
66 |
106 def parse_style(stylestr): |
67 def parse_style(stylestr): |
107 if stylestr.strip() == '': |
68 if stylestr.strip() == '': |
108 return None |
69 return None |
109 parser = CSS21Parser() |
70 parser = CSS21Parser() |
111 kv = {} |
72 kv = {} |
112 for obj in style[0]: |
73 for obj in style[0]: |
113 kv[obj.name] = obj.value[0].value |
74 kv[obj.name] = obj.value[0].value |
114 return kv |
75 return kv |
115 |
76 |
116 def normalize(coord): |
|
117 x = coord[0] |
|
118 y = coord[1] |
|
119 # flip y |
|
120 y = (b2.coord()[1] - y) |
|
121 return (x, y) |
|
122 |
77 |
123 data = im.flatten() |
78 def svg2gcode(options, gcode): |
124 for d in data: |
|
125 if hasattr(d, "segments"): |
|
126 for l in d.segments(1): |
|
127 # THE OUTLINE |
|
128 x, y = normalize(l[0].coord()) |
|
129 gcode.move(x, y) |
|
130 for pt in l[1:]: |
|
131 x, y = normalize(pt.coord()) |
|
132 gcode.engrave(x, y) |
|
133 |
79 |
134 if options.outline: |
80 def normalize(coord): |
135 continue |
81 x = coord[0] |
|
82 y = coord[1] |
|
83 # flip y |
|
84 y = (b2.coord()[1] - y) |
|
85 return (x, y) |
136 |
86 |
137 if (isinstance(d, svg.Polygon) or isinstance(d, svg.Path)): |
87 im = svg.parse(options.filename) |
138 #check if we shoild infill? |
88 b1, b2 = im.bbox() |
139 style = parse_style(d.style) |
89 width, height = b2.coord() |
140 if not style: |
90 msg = "Original dimension: %.2f x %.2f" % (width, height) |
141 continue |
91 print msg |
142 if not 'fill' in style.keys(): |
92 gcode.comment(msg) |
143 continue |
93 width *= gcode.mm_pixel * options.scale |
144 if style['fill'] == 'none': |
94 height *= gcode.mm_pixel * options.scale |
145 continue |
95 msg = "Print dimension: %.2fmm x %.2fmm" % (width, height) |
|
96 print msg |
|
97 gcode.comment(msg) |
146 |
98 |
147 poly = None |
99 x1, y1 = b1.coord() |
|
100 x2, y2 = b2.coord() |
|
101 page = box(x1, y1, x2, y2) |
|
102 # TODO: Infill spacing needs to be calculated with proper scaling and gcode MM dimensions |
|
103 # TODO: Make infill angle 0, 45 or 90 degrees configurable to options parser (0° = X, 90° = Y, 45° = X and Y but half the speed/accel needed!) |
|
104 INFILL = hatchbox(page, 0, 2) |
|
105 |
|
106 data = im.flatten() |
|
107 for d in data: |
|
108 if hasattr(d, "segments"): |
148 for l in d.segments(1): |
109 for l in d.segments(1): |
149 # create shape from polygon: |
110 # THE OUTLINE |
150 segments = [] |
111 x, y = normalize(l[0].coord()) |
151 for pnt in l: |
112 gcode.move(x, y) |
152 x, y = pnt.coord() |
113 for pt in l[1:]: |
153 segments.append((x, y)) |
114 x, y = normalize(pt.coord()) |
154 shape = Polygon(segments) |
115 gcode.engrave(x, y) |
155 if shape.is_valid: |
116 |
156 if not poly: |
117 if options.outline: |
157 poly = shape |
118 continue |
158 else: |
119 |
159 if shape.within(poly): |
120 if isinstance(d, svg.Polygon) or isinstance(d, svg.Path): |
160 poly = poly.difference(shape) |
121 #check if we should infill? |
|
122 style = parse_style(d.style) |
|
123 if not style: |
|
124 continue |
|
125 if not 'fill' in style.keys(): |
|
126 continue |
|
127 if style['fill'] == 'none': |
|
128 continue |
|
129 |
|
130 # try to generate the infill poly complex |
|
131 poly = None |
|
132 for l in d.segments(1): |
|
133 segments = [] |
|
134 for pnt in l: |
|
135 x, y = pnt.coord() |
|
136 segments.append((x, y)) |
|
137 shape = Polygon(segments) |
|
138 if shape.is_valid: |
|
139 if not poly: |
|
140 poly = shape |
161 else: |
141 else: |
162 poly = poly.union(shape) |
142 if shape.within(poly): |
|
143 poly = poly.difference(shape) |
|
144 else: |
|
145 poly = poly.union(shape) |
163 |
146 |
164 lines = poly.intersection(INFILL) |
147 lines = poly.intersection(INFILL) |
165 if lines: |
148 if lines: |
166 # THE INFILL |
149 # THE INFILL |
167 for line in lines: |
150 for line in lines: |
168 start = normalize((line.coords[0][0], line.coords[0][1])) |
151 # TODO: swap start/end to nearest move! |
169 end = normalize((line.coords[1][0], line.coords[1][1])) |
152 start = normalize((line.coords[0][0], line.coords[0][1])) |
170 gcode.move(start[0], start[1]) |
153 end = normalize((line.coords[1][0], line.coords[1][1])) |
171 gcode.engrave(end[0], end[1]) |
154 gcode.move(start[0], start[1]) |
|
155 gcode.engrave(end[0], end[1]) |
172 |
156 |
173 # write gcode file |
157 if __name__ == "__main__": |
174 gcode.write(options.filename + ".g") |
158 parser = OptionParser() |
|
159 parser.add_option("-f", "--file", dest="filename", default=None, |
|
160 help="Load SVG file", metavar="FILE") |
|
161 parser.add_option("-s", "--scale", |
|
162 dest="scale", type="float", default=1.0, |
|
163 help="set scale factor (default 1.0)") |
|
164 parser.add_option("-e", "", |
|
165 dest="engrave_speed", type="float", default=20, |
|
166 help="engrave speed mm/sec (default 20)") |
|
167 parser.add_option("-t", "", |
|
168 dest="travel_speed", type="float", default=130, |
|
169 help="travel speed mm/sec (default 130)") |
|
170 parser.add_option("-o", "--outline", action="store_true", |
|
171 dest="outline", default=False, |
|
172 help="no infill, only outlines") |
|
173 |
|
174 (options, args) = parser.parse_args() |
|
175 |
|
176 if not options.filename: |
|
177 print "no filename given!" |
|
178 sys.exit(1) |
|
179 |
|
180 # initialize gcode worker |
|
181 gcode = Gcode(scale=options.scale, travel_speed=options.travel_speed, engrave_speed=options.engrave_speed) |
|
182 |
|
183 # processing |
|
184 svg2gcode(options, gcode) |
|
185 |
|
186 # write gcode file |
|
187 gcode.write(options.filename + ".g") |