Tue, 26 Sep 2017 19:08:02 +0200
SVG options: offset, original scale
SVG Bugfix: "scale" does now really fit to dimensions
15 | 1 | #!/usr/bin/env python |
2 | ||
3 | # This file is part of the Printrun suite. | |
4 | # | |
5 | # Printrun is free software: you can redistribute it and/or modify | |
6 | # it under the terms of the GNU General Public License as published by | |
7 | # the Free Software Foundation, either version 3 of the License, or | |
8 | # (at your option) any later version. | |
9 | # | |
10 | # Printrun is distributed in the hope that it will be useful, | |
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | # GNU General Public License for more details. | |
14 | # | |
15 | # You should have received a copy of the GNU General Public License | |
16 | # along with Printrun. If not, see <http://www.gnu.org/licenses/>. | |
17 | ||
18 | # Set up Internationalization using gettext | |
19 | # searching for installed locales on /usr/share; uses relative folder if not found (windows) | |
20 | from .utils import install_locale, get_home_pos | |
21 | install_locale('pronterface') | |
22 | ||
23 | import wx | |
24 | import sys | |
25 | import os | |
26 | import time | |
27 | import types | |
28 | import re | |
29 | import math | |
30 | import logging | |
31 | ||
32 | from printrun import gcoder | |
33 | from printrun.objectplater import make_plater, PlaterPanel | |
34 | from printrun.gl.libtatlin import actors | |
35 | import printrun.gui.viz # NOQA | |
36 | from printrun import gcview | |
37 | ||
38 | def extrusion_only(gline): | |
39 | return gline.e is not None \ | |
40 | and (gline.x, gline.y, gline.z) == (None, None, None) | |
41 | ||
42 | # Custom method for gcoder.GCode to analyze & output gcode in a single call | |
43 | def gcoder_write(self, f, line, store = False): | |
44 | f.write(line) | |
45 | self.append(line, store = store) | |
46 | ||
47 | rewrite_exp = re.compile("(%s)" % "|".join(["X([-+]?[0-9]*\.?[0-9]*)", | |
48 | "Y([-+]?[0-9]*\.?[0-9]*)"])) | |
49 | ||
50 | def rewrite_gline(centeroffset, gline, cosr, sinr): | |
51 | if gline.is_move and (gline.x is not None or gline.y is not None): | |
52 | if gline.relative: | |
53 | xc = yc = 0 | |
54 | cox = coy = 0 | |
55 | if gline.x is not None: | |
56 | xc = gline.x | |
57 | if gline.y is not None: | |
58 | yc = gline.y | |
59 | else: | |
60 | xc = gline.current_x + centeroffset[0] | |
61 | yc = gline.current_y + centeroffset[1] | |
62 | cox = centeroffset[0] | |
63 | coy = centeroffset[1] | |
64 | new_x = "X%.04f" % (xc * cosr - yc * sinr - cox) | |
65 | new_y = "Y%.04f" % (xc * sinr + yc * cosr - coy) | |
66 | new = {"X": new_x, "Y": new_y} | |
67 | new_line = rewrite_exp.sub(lambda ax: new[ax.group()[0]], gline.raw) | |
68 | new_line = new_line.split(";")[0] | |
69 | if gline.x is None: new_line += " " + new_x | |
70 | if gline.y is None: new_line += " " + new_y | |
71 | return new_line | |
72 | else: | |
73 | return gline.raw | |
74 | ||
75 | class GcodePlaterPanel(PlaterPanel): | |
76 | ||
77 | load_wildcard = _("GCODE files (*.gcode;*.GCODE;*.g)") + "|*.gcode;*.gco;*.g" | |
78 | save_wildcard = _("GCODE files (*.gcode;*.GCODE;*.g)") + "|*.gcode;*.gco;*.g" | |
79 | ||
80 | def prepare_ui(self, filenames = [], callback = None, | |
81 | parent = None, build_dimensions = None, | |
82 | circular_platform = False, antialias_samples = 0): | |
83 | super(GcodePlaterPanel, self).prepare_ui(filenames, callback, parent, build_dimensions) | |
84 | viewer = gcview.GcodeViewPanel(self, build_dimensions = self.build_dimensions, | |
85 | antialias_samples = antialias_samples) | |
86 | self.set_viewer(viewer) | |
87 | self.platform = actors.Platform(self.build_dimensions, | |
88 | circular = circular_platform) | |
89 | self.platform_object = gcview.GCObject(self.platform) | |
90 | ||
91 | def get_objects(self): | |
92 | return [self.platform_object] + self.models.values() | |
93 | objects = property(get_objects) | |
94 | ||
95 | def load_file(self, filename): | |
96 | gcode = gcoder.GCode(open(filename, "rU"), | |
97 | get_home_pos(self.build_dimensions)) | |
98 | model = actors.GcodeModel() | |
99 | if gcode.filament_length > 0: | |
100 | model.display_travels = False | |
101 | generator = model.load_data(gcode) | |
102 | generator_output = generator.next() | |
103 | while generator_output is not None: | |
104 | generator_output = generator.next() | |
105 | obj = gcview.GCObject(model) | |
106 | obj.offsets = [self.build_dimensions[3], self.build_dimensions[4], 0] | |
107 | obj.gcode = gcode | |
108 | obj.dims = [gcode.xmin, gcode.xmax, | |
109 | gcode.ymin, gcode.ymax, | |
110 | gcode.zmin, gcode.zmax] | |
111 | obj.centeroffset = [-(obj.dims[1] + obj.dims[0]) / 2, | |
112 | -(obj.dims[3] + obj.dims[2]) / 2, | |
113 | 0] | |
114 | self.add_model(filename, obj) | |
115 | wx.CallAfter(self.Refresh) | |
116 | ||
117 | def done(self, event, cb): | |
118 | if not os.path.exists("tempgcode"): | |
119 | os.mkdir("tempgcode") | |
120 | name = "tempgcode/" + str(int(time.time()) % 10000) + ".gcode" | |
121 | self.export_to(name) | |
122 | if cb is not None: | |
123 | cb(name) | |
124 | if self.destroy_on_done: | |
125 | self.Destroy() | |
126 | ||
127 | # What's hard in there ? | |
128 | # 1) [x] finding the order in which the objects are printed | |
129 | # 2) [x] handling layers correctly | |
130 | # 3) [x] handling E correctly | |
131 | # 4) [x] handling position shifts: should we either reset absolute 0 using | |
132 | # G92 or should we rewrite all positions ? => we use G92s | |
133 | # 5) [ ] handling the start & end gcode properly ? | |
134 | # 6) [x] handling of current tool | |
135 | # 7) [x] handling of Z moves for sequential printing (don't lower Z before | |
136 | # reaching the next object print area) | |
137 | # 8) [x] handling of absolute/relative status | |
138 | # Initial implementation should just print the objects sequentially, | |
139 | # but the end goal is to have a clean per-layer merge | |
140 | def export_to(self, name): | |
141 | return self.export_combined(name) | |
142 | return self.export_sequential(name) | |
143 | ||
144 | def export_combined(self, name): | |
145 | models = self.models.values() | |
146 | last_real_position = None | |
147 | # Sort models by Z max to print smaller objects first | |
148 | models.sort(key = lambda x: x.dims[-1]) | |
149 | alllayers = [] | |
150 | for (model_i, model) in enumerate(models): | |
151 | def add_offset(layer): | |
152 | return layer.z + model.offsets[2] if layer.z is not None else layer.z | |
153 | alllayers += [(add_offset(layer), model_i, layer_i) | |
154 | for (layer_i, layer) in enumerate(model.gcode.all_layers) if layer] | |
155 | alllayers.sort() | |
156 | laste = [0] * len(models) | |
157 | lasttool = [0] * len(models) | |
158 | lastrelative = [False] * len(models) | |
159 | with open(name, "w") as f: | |
160 | analyzer = gcoder.GCode(None, get_home_pos(self.build_dimensions)) | |
161 | analyzer.write = types.MethodType(lambda self, line: gcoder_write(self, f, line), analyzer) | |
162 | for (layer_z, model_i, layer_i) in alllayers: | |
163 | model = models[model_i] | |
164 | layer = model.gcode.all_layers[layer_i] | |
165 | r = math.radians(model.rot) | |
166 | o = model.offsets | |
167 | co = model.centeroffset | |
168 | offset_pos = last_real_position if last_real_position is not None else (0, 0, 0) | |
169 | analyzer.write("; %f %f %f\n" % offset_pos) | |
170 | trans = (- (o[0] + co[0]), | |
171 | - (o[1] + co[1]), | |
172 | - (o[2] + co[2])) | |
173 | trans_wpos = (offset_pos[0] + trans[0], | |
174 | offset_pos[1] + trans[1], | |
175 | offset_pos[2] + trans[2]) | |
176 | analyzer.write("; GCodePlater: Model %d Layer %d at Z = %s\n" % (model_i, layer_i, layer_z)) | |
177 | if lastrelative[model_i]: | |
178 | analyzer.write("G91\n") | |
179 | else: | |
180 | analyzer.write("G90\n") | |
181 | if analyzer.current_tool != lasttool[model_i]: | |
182 | analyzer.write("T%d\n" % lasttool[model_i]) | |
183 | analyzer.write("G92 X%.5f Y%.5f Z%.5f\n" % trans_wpos) | |
184 | analyzer.write("G92 E%.5f\n" % laste[model_i]) | |
185 | for l in layer: | |
186 | if l.command != "G28" and (l.command != "G92" or extrusion_only(l)): | |
187 | if r == 0: | |
188 | analyzer.write(l.raw + "\n") | |
189 | else: | |
190 | analyzer.write(rewrite_gline(co, l, math.cos(r), math.sin(r)) + "\n") | |
191 | # Find the current real position & E | |
192 | last_real_position = analyzer.current_pos | |
193 | laste[model_i] = analyzer.current_e | |
194 | lastrelative[model_i] = analyzer.relative | |
195 | lasttool[model_i] = analyzer.current_tool | |
196 | logging.info(_("Exported merged G-Codes to %s") % name) | |
197 | ||
198 | def export_sequential(self, name): | |
199 | models = self.models.values() | |
200 | last_real_position = None | |
201 | # Sort models by Z max to print smaller objects first | |
202 | models.sort(key = lambda x: x.dims[-1]) | |
203 | with open(name, "w") as f: | |
204 | for model_i, model in enumerate(models): | |
205 | r = math.radians(model.rot) | |
206 | o = model.offsets | |
207 | co = model.centeroffset | |
208 | offset_pos = last_real_position if last_real_position is not None else (0, 0, 0) | |
209 | trans = (- (o[0] + co[0]), | |
210 | - (o[1] + co[1]), | |
211 | - (o[2] + co[2])) | |
212 | trans_wpos = (offset_pos[0] + trans[0], | |
213 | offset_pos[1] + trans[1], | |
214 | offset_pos[2] + trans[2]) | |
215 | f.write("; GCodePlater: Model %d\n" % model_i) | |
216 | f.write("G90\n") | |
217 | f.write("G92 X%.5f Y%.5f Z%.5f E0\n" % trans_wpos) | |
218 | f.write("G1 X%.5f Y%.5f" % (-co[0], -co[1])) | |
219 | for l in model.gcode: | |
220 | if l.command != "G28" and (l.command != "G92" or extrusion_only(l)): | |
221 | if r == 0: | |
222 | f.write(l.raw + "\n") | |
223 | else: | |
224 | f.write(rewrite_gline(co, l, math.cos(r), math.sin(r)) + "\n") | |
225 | # Find the current real position | |
226 | for i in xrange(len(model.gcode) - 1, -1, -1): | |
227 | gline = model.gcode.lines[i] | |
228 | if gline.is_move: | |
229 | last_real_position = (- trans[0] + gline.current_x, | |
230 | - trans[1] + gline.current_y, | |
231 | - trans[2] + gline.current_z) | |
232 | break | |
233 | logging.info(_("Exported merged G-Codes to %s") % name) | |
234 | ||
235 | GcodePlater = make_plater(GcodePlaterPanel) | |
236 | ||
237 | if __name__ == '__main__': | |
238 | app = wx.App(False) | |
239 | main = GcodePlater(filenames = sys.argv[1:]) | |
240 | for fn in main.filenames: | |
241 | main.load_file(fn) | |
242 | main.filenames = None | |
243 | main.autoplate() | |
244 | main.export_to("gcodeplate___test.gcode") | |
245 | raise SystemExit | |
246 | main.Show() | |
247 | app.MainLoop() |