Sat, 07 Nov 2015 16:57:15 +0100
infill only on Polygon or Path which have fill style attribute
4 | 1 | #!/usr/bin/env python |
5 | 2 | # -*- coding: utf-8 -*- |
3 | ||
4 | 4 | import svg, sys |
5 | from gcode import Gcode | |
6 | from optparse import OptionParser | |
6
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
7 | from tinycss import CSS21Parser |
4 | 8 | |
5 | 9 | from shapely.geometry import box, MultiLineString, Point, Polygon |
10 | from shapely.affinity import rotate | |
11 | from shapely import speedups | |
12 | from math import sqrt | |
13 | ||
14 | # enable Shapely speedups, if possible | |
15 | if speedups.available: | |
16 | speedups.enable() | |
17 | ||
18 | def hatchbox(rect, angle, spacing): | |
19 | """ | |
20 | returns a Shapely geometry (MULTILINESTRING, or more rarely, | |
21 | GEOMETRYCOLLECTION) for a simple hatched rectangle. | |
22 | ||
23 | args: | |
24 | rect - a Shapely geometry for the outer boundary of the hatch | |
25 | Likely most useful if it really is a rectangle | |
26 | ||
27 | angle - angle of hatch lines, conventional anticlockwise -ve | |
28 | ||
29 | spacing - spacing between hatch lines | |
30 | ||
31 | GEOMETRYCOLLECTION case occurs when a hatch line intersects with | |
32 | the corner of the clipping rectangle, which produces a point | |
33 | along with the usual lines. | |
34 | """ | |
35 | ||
36 | (llx, lly, urx, ury) = rect.bounds | |
37 | centre_x = (urx + llx) / 2 | |
38 | centre_y = (ury + lly) / 2 | |
39 | diagonal_length = sqrt((urx - llx) ** 2 + (ury - lly) ** 2) | |
40 | number_of_lines = 2 + int(diagonal_length / spacing) | |
41 | hatch_length = spacing * (number_of_lines - 1) | |
42 | ||
43 | # build a square (of side hatch_length) horizontal lines | |
44 | # centred on centroid of the bounding box, 'spacing' units apart | |
45 | coords = [] | |
46 | for i in range(number_of_lines): | |
47 | # alternate lines l2r and r2l to keep HP-7470A plotter happy ☺ | |
48 | if i % 2: | |
49 | coords.extend([((centre_x - hatch_length / 2, centre_y | |
50 | - hatch_length / 2 + i * spacing), (centre_x | |
51 | + hatch_length / 2, centre_y - hatch_length | |
52 | / 2 + i * spacing))]) | |
53 | else: | |
54 | coords.extend([((centre_x + hatch_length / 2, centre_y | |
55 | - hatch_length / 2 + i * spacing), (centre_x | |
56 | - hatch_length / 2, centre_y - hatch_length | |
57 | / 2 + i * spacing))]) | |
58 | # turn array into Shapely object | |
59 | lines = MultiLineString(coords) | |
60 | # Rotate by angle around box centre | |
61 | lines = rotate(lines, angle, origin='centroid', use_radians=False) | |
62 | # return clipped array | |
63 | return rect.intersection(lines) | |
64 | ||
6
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
65 | def get_infill(polygon): |
5 | 66 | # create shape from polygon: |
67 | segments = [] | |
68 | for pnt in polygon: | |
69 | x, y = pnt.coord() | |
70 | segments.append((x, y)) | |
71 | ||
72 | shape = Polygon(segments) | |
6
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
73 | if shape.is_valid: |
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
74 | return shape.intersection(INFILL) |
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
75 | else: |
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
76 | return [] |
5 | 77 | |
4 | 78 | parser = OptionParser() |
79 | parser.add_option("-f", "--file", dest="filename", default=None, | |
80 | help="Load SVG file", metavar="FILE") | |
81 | parser.add_option("-s", "--scale", | |
82 | dest="scale", type="float", default=1.0, | |
83 | help="set scale factor (default 1.0)") | |
84 | parser.add_option("-e", "", | |
85 | dest="engrave_speed", type="float", default=20, | |
86 | help="engrave speed mm/sec (default 20)") | |
87 | parser.add_option("-t", "", | |
88 | dest="travel_speed", type="float", default=130, | |
89 | help="travel speed mm/sec (default 130)") | |
5 | 90 | parser.add_option("-o", "--outline", action="store_true", |
91 | dest="outline", default=False, | |
92 | help="no infill, only outlines") | |
4 | 93 | |
94 | ||
95 | (options, args) = parser.parse_args() | |
96 | ||
97 | ||
98 | if not options.filename: | |
99 | print "no filename given!" | |
100 | sys.exit(1) | |
101 | ||
102 | gcode = Gcode(scale=options.scale, travel_speed=options.travel_speed, engrave_speed=options.engrave_speed) | |
103 | ||
104 | im = svg.parse(options.filename) | |
105 | b1, b2 = im.bbox() | |
106 | width, height = b2.coord() | |
107 | print "Original dimension: %.2f x %.2f" % (width, height) | |
108 | width *= gcode.mm_pixel * options.scale | |
109 | height *= gcode.mm_pixel * options.scale | |
110 | print "Print dimension: %.2fmm x %.2fmm" % (width, height) | |
111 | ||
6
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
112 | x1, y1 = b1.coord() |
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
113 | x2, y2 = b2.coord() |
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
114 | page = box(x1, y1, x2, y2) |
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
115 | INFILL = hatchbox(page, 0, 2) |
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
116 | |
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
117 | def parse_style(stylestr): |
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
118 | if stylestr.strip() == '': |
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
119 | return None |
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
120 | parser = CSS21Parser() |
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
121 | style = parser.parse_style_attr(stylestr) |
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
122 | kv = {} |
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
123 | for obj in style[0]: |
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
124 | kv[obj.name] = obj.value[0].value |
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
125 | return kv |
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
126 | |
4 | 127 | def normalize(coord): |
128 | x = coord[0] | |
129 | y = coord[1] | |
130 | # flip y | |
131 | y = (b2.coord()[1] - y) | |
132 | return (x, y) | |
133 | ||
134 | data = im.flatten() | |
135 | for d in data: | |
136 | if hasattr(d, "segments"): | |
137 | for l in d.segments(1): | |
5 | 138 | # THE OUTLINE |
4 | 139 | x, y = normalize(l[0].coord()) |
140 | gcode.move(x, y) | |
141 | for pt in l[1:]: | |
142 | x, y = normalize(pt.coord()) | |
143 | gcode.engrave(x, y) | |
144 | ||
6
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
145 | if options.outline: |
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
146 | continue |
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
147 | |
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
148 | if (isinstance(d, svg.Polygon) or isinstance(d, svg.Path)): |
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
149 | #check if we shoild infill? |
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
150 | style = parse_style(d.style) |
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
151 | if not style: |
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
152 | continue |
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
153 | if not 'fill' in style.keys(): |
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
154 | continue |
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
155 | if style['fill'] == 'none': |
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
156 | continue |
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
157 | |
5 | 158 | # THE INFILL |
6
ff679c15cb0e
infill only on Polygon or Path which have fill style attribute
mbayer
parents:
5
diff
changeset
|
159 | for line in get_infill(l): |
5 | 160 | start = normalize((line.coords[0][0], line.coords[0][1])) |
161 | end = normalize((line.coords[1][0], line.coords[1][1])) | |
162 | gcode.move(start[0], start[1]) | |
163 | gcode.engrave(end[0], end[1]) | |
164 | ||
4 | 165 | # write gcode file |
166 | gcode.write(options.filename + ".g") |