|
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() |