Thu, 07 Jul 2016 11:58:38 +0200
svg crowbar fixes
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 | import os | |
19 | ||
20 | # Set up Internationalization using gettext | |
21 | # searching for installed locales on /usr/share; uses relative folder if not found (windows) | |
22 | from .utils import install_locale | |
23 | install_locale('pronterface') | |
24 | ||
25 | import wx | |
26 | import time | |
27 | import logging | |
28 | import threading | |
29 | import math | |
30 | import sys | |
31 | import re | |
32 | import traceback | |
33 | import subprocess | |
34 | from copy import copy | |
35 | ||
36 | from printrun import stltool | |
37 | from printrun.objectplater import make_plater, PlaterPanel | |
38 | ||
39 | glview = False | |
40 | if "-nogl" not in sys.argv: | |
41 | try: | |
42 | from printrun import stlview | |
43 | glview = True | |
44 | except: | |
45 | logging.warning("Could not load 3D viewer for plater:" | |
46 | + "\n" + traceback.format_exc()) | |
47 | ||
48 | ||
49 | def evalme(s): | |
50 | return eval(s[s.find("(") + 1:s.find(")")]) | |
51 | ||
52 | def transformation_matrix(model): | |
53 | matrix = stltool.I | |
54 | if any(model.centeroffset): | |
55 | matrix = model.translation_matrix(model.centeroffset).dot(matrix) | |
56 | if model.rot: | |
57 | matrix = model.rotation_matrix([0, 0, model.rot]).dot(matrix) | |
58 | if any(model.offsets): | |
59 | matrix = model.translation_matrix(model.offsets).dot(matrix) | |
60 | return matrix | |
61 | ||
62 | class showstl(wx.Window): | |
63 | def __init__(self, parent, size, pos): | |
64 | wx.Window.__init__(self, parent, size = size, pos = pos) | |
65 | self.i = 0 | |
66 | self.parent = parent | |
67 | self.previ = 0 | |
68 | self.Bind(wx.EVT_MOUSEWHEEL, self.rot) | |
69 | self.Bind(wx.EVT_MOUSE_EVENTS, self.move) | |
70 | self.Bind(wx.EVT_PAINT, self.repaint) | |
71 | self.Bind(wx.EVT_KEY_DOWN, self.keypress) | |
72 | self.triggered = 0 | |
73 | self.initpos = None | |
74 | self.prevsel = -1 | |
75 | ||
76 | def prepare_model(self, m, scale): | |
77 | m.bitmap = wx.EmptyBitmap(800, 800, 32) | |
78 | dc = wx.MemoryDC() | |
79 | dc.SelectObject(m.bitmap) | |
80 | dc.SetBackground(wx.Brush((0, 0, 0, 0))) | |
81 | dc.SetBrush(wx.Brush((0, 0, 0, 255))) | |
82 | dc.SetBrush(wx.Brush(wx.Colour(128, 255, 128))) | |
83 | dc.SetPen(wx.Pen(wx.Colour(128, 128, 128))) | |
84 | for i in m.facets: | |
85 | dc.DrawPolygon([wx.Point(400 + scale * p[0], (400 - scale * p[1])) for p in i[1]]) | |
86 | dc.SelectObject(wx.NullBitmap) | |
87 | m.bitmap.SetMask(wx.Mask(m.bitmap, wx.Colour(0, 0, 0, 255))) | |
88 | ||
89 | def move_shape(self, delta): | |
90 | """moves shape (selected in l, which is list ListBox of shapes) | |
91 | by an offset specified in tuple delta. | |
92 | Positive numbers move to (rigt, down)""" | |
93 | name = self.parent.l.GetSelection() | |
94 | if name == wx.NOT_FOUND: | |
95 | return False | |
96 | name = self.parent.l.GetString(name) | |
97 | model = self.parent.models[name] | |
98 | model.offsets = [model.offsets[0] + delta[0], | |
99 | model.offsets[1] + delta[1], | |
100 | model.offsets[2] | |
101 | ] | |
102 | self.Refresh() | |
103 | return True | |
104 | ||
105 | def move(self, event): | |
106 | if event.ButtonUp(wx.MOUSE_BTN_LEFT): | |
107 | if self.initpos is not None: | |
108 | currentpos = event.GetPositionTuple() | |
109 | delta = (0.5 * (currentpos[0] - self.initpos[0]), | |
110 | -0.5 * (currentpos[1] - self.initpos[1]) | |
111 | ) | |
112 | self.move_shape(delta) | |
113 | self.Refresh() | |
114 | self.initpos = None | |
115 | elif event.ButtonDown(wx.MOUSE_BTN_RIGHT): | |
116 | self.parent.right(event) | |
117 | elif event.Dragging(): | |
118 | if self.initpos is None: | |
119 | self.initpos = event.GetPositionTuple() | |
120 | self.Refresh() | |
121 | dc = wx.ClientDC(self) | |
122 | p = event.GetPositionTuple() | |
123 | dc.DrawLine(self.initpos[0], self.initpos[1], p[0], p[1]) | |
124 | del dc | |
125 | else: | |
126 | event.Skip() | |
127 | ||
128 | def rotate_shape(self, angle): | |
129 | """rotates acive shape | |
130 | positive angle is clockwise | |
131 | """ | |
132 | self.i += angle | |
133 | if not self.triggered: | |
134 | self.triggered = 1 | |
135 | threading.Thread(target = self.cr).start() | |
136 | ||
137 | def keypress(self, event): | |
138 | """gets keypress events and moves/rotates acive shape""" | |
139 | keycode = event.GetKeyCode() | |
140 | step = 5 | |
141 | angle = 18 | |
142 | if event.ControlDown(): | |
143 | step = 1 | |
144 | angle = 1 | |
145 | # h | |
146 | if keycode == 72: | |
147 | self.move_shape((-step, 0)) | |
148 | # l | |
149 | if keycode == 76: | |
150 | self.move_shape((step, 0)) | |
151 | # j | |
152 | if keycode == 75: | |
153 | self.move_shape((0, step)) | |
154 | # k | |
155 | if keycode == 74: | |
156 | self.move_shape((0, -step)) | |
157 | # [ | |
158 | if keycode == 91: | |
159 | self.rotate_shape(-angle) | |
160 | # ] | |
161 | if keycode == 93: | |
162 | self.rotate_shape(angle) | |
163 | event.Skip() | |
164 | ||
165 | def rotateafter(self): | |
166 | if self.i != self.previ: | |
167 | i = self.parent.l.GetSelection() | |
168 | if i != wx.NOT_FOUND: | |
169 | self.parent.models[self.parent.l.GetString(i)].rot -= 5 * (self.i - self.previ) | |
170 | self.previ = self.i | |
171 | self.Refresh() | |
172 | ||
173 | def cr(self): | |
174 | time.sleep(0.01) | |
175 | wx.CallAfter(self.rotateafter) | |
176 | self.triggered = 0 | |
177 | ||
178 | def rot(self, event): | |
179 | z = event.GetWheelRotation() | |
180 | s = self.parent.l.GetSelection() | |
181 | if self.prevsel != s: | |
182 | self.i = 0 | |
183 | self.prevsel = s | |
184 | if z < 0: | |
185 | self.rotate_shape(-1) | |
186 | else: | |
187 | self.rotate_shape(1) | |
188 | ||
189 | def repaint(self, event): | |
190 | dc = wx.PaintDC(self) | |
191 | self.paint(dc = dc) | |
192 | ||
193 | def paint(self, coord1 = "x", coord2 = "y", dc = None): | |
194 | if dc is None: | |
195 | dc = wx.ClientDC(self) | |
196 | scale = 2 | |
197 | dc.SetPen(wx.Pen(wx.Colour(100, 100, 100))) | |
198 | for i in xrange(20): | |
199 | dc.DrawLine(0, i * scale * 10, 400, i * scale * 10) | |
200 | dc.DrawLine(i * scale * 10, 0, i * scale * 10, 400) | |
201 | dc.SetPen(wx.Pen(wx.Colour(0, 0, 0))) | |
202 | for i in xrange(4): | |
203 | dc.DrawLine(0, i * scale * 50, 400, i * scale * 50) | |
204 | dc.DrawLine(i * scale * 50, 0, i * scale * 50, 400) | |
205 | dc.SetBrush(wx.Brush(wx.Colour(128, 255, 128))) | |
206 | dc.SetPen(wx.Pen(wx.Colour(128, 128, 128))) | |
207 | dcs = wx.MemoryDC() | |
208 | for m in self.parent.models.values(): | |
209 | b = m.bitmap | |
210 | im = b.ConvertToImage() | |
211 | imgc = wx.Point(im.GetWidth() / 2, im.GetHeight() / 2) | |
212 | im = im.Rotate(math.radians(m.rot), imgc, 0) | |
213 | bm = wx.BitmapFromImage(im) | |
214 | dcs.SelectObject(bm) | |
215 | bsz = bm.GetSize() | |
216 | dc.Blit(scale * m.offsets[0] - bsz[0] / 2, 400 - (scale * m.offsets[1] + bsz[1] / 2), bsz[0], bsz[1], dcs, 0, 0, useMask = 1) | |
217 | del dc | |
218 | ||
219 | class StlPlaterPanel(PlaterPanel): | |
220 | ||
221 | load_wildcard = _("STL files (*.stl;*.STL)|*.stl;*.STL|OpenSCAD files (*.scad)|*.scad") | |
222 | save_wildcard = _("STL files (*.stl;*.STL)|*.stl;*.STL") | |
223 | ||
224 | def prepare_ui(self, filenames = [], callback = None, | |
225 | parent = None, build_dimensions = None, circular_platform = False, | |
226 | simarrange_path = None, antialias_samples = 0): | |
227 | super(StlPlaterPanel, self).prepare_ui(filenames, callback, parent, build_dimensions) | |
228 | self.cutting = False | |
229 | self.cutting_axis = None | |
230 | self.cutting_dist = None | |
231 | if glview: | |
232 | viewer = stlview.StlViewPanel(self, (580, 580), | |
233 | build_dimensions = self.build_dimensions, | |
234 | circular = circular_platform, | |
235 | antialias_samples = antialias_samples) | |
236 | # Cutting tool | |
237 | nrows = self.menusizer.GetRows() | |
238 | self.menusizer.Add(wx.StaticText(self.menupanel, -1, _("Cut along:")), | |
239 | pos = (nrows, 0), span = (1, 1), flag = wx.ALIGN_CENTER) | |
240 | cutconfirmbutton = wx.Button(self.menupanel, label = _("Confirm cut")) | |
241 | cutconfirmbutton.Bind(wx.EVT_BUTTON, self.cut_confirm) | |
242 | cutconfirmbutton.Disable() | |
243 | self.cutconfirmbutton = cutconfirmbutton | |
244 | self.menusizer.Add(cutconfirmbutton, pos = (nrows, 1), span = (1, 1), flag = wx.EXPAND) | |
245 | cutpanel = wx.Panel(self.menupanel, -1) | |
246 | cutsizer = self.cutsizer = wx.BoxSizer(wx.HORIZONTAL) | |
247 | cutpanel.SetSizer(cutsizer) | |
248 | cutxplusbutton = wx.ToggleButton(cutpanel, label = _(">X"), style = wx.BU_EXACTFIT) | |
249 | cutxplusbutton.Bind(wx.EVT_TOGGLEBUTTON, lambda event: self.start_cutting_tool(event, "x", 1)) | |
250 | cutsizer.Add(cutxplusbutton, 1, flag = wx.EXPAND) | |
251 | cutzplusbutton = wx.ToggleButton(cutpanel, label = _(">Y"), style = wx.BU_EXACTFIT) | |
252 | cutzplusbutton.Bind(wx.EVT_TOGGLEBUTTON, lambda event: self.start_cutting_tool(event, "y", 1)) | |
253 | cutsizer.Add(cutzplusbutton, 1, flag = wx.EXPAND) | |
254 | cutzplusbutton = wx.ToggleButton(cutpanel, label = _(">Z"), style = wx.BU_EXACTFIT) | |
255 | cutzplusbutton.Bind(wx.EVT_TOGGLEBUTTON, lambda event: self.start_cutting_tool(event, "z", 1)) | |
256 | cutsizer.Add(cutzplusbutton, 1, flag = wx.EXPAND) | |
257 | cutxminusbutton = wx.ToggleButton(cutpanel, label = _("<X"), style = wx.BU_EXACTFIT) | |
258 | cutxminusbutton.Bind(wx.EVT_TOGGLEBUTTON, lambda event: self.start_cutting_tool(event, "x", -1)) | |
259 | cutsizer.Add(cutxminusbutton, 1, flag = wx.EXPAND) | |
260 | cutzminusbutton = wx.ToggleButton(cutpanel, label = _("<Y"), style = wx.BU_EXACTFIT) | |
261 | cutzminusbutton.Bind(wx.EVT_TOGGLEBUTTON, lambda event: self.start_cutting_tool(event, "y", -1)) | |
262 | cutsizer.Add(cutzminusbutton, 1, flag = wx.EXPAND) | |
263 | cutzminusbutton = wx.ToggleButton(cutpanel, label = _("<Z"), style = wx.BU_EXACTFIT) | |
264 | cutzminusbutton.Bind(wx.EVT_TOGGLEBUTTON, lambda event: self.start_cutting_tool(event, "z", -1)) | |
265 | cutsizer.Add(cutzminusbutton, 1, flag = wx.EXPAND) | |
266 | self.menusizer.Add(cutpanel, pos = (nrows + 1, 0), span = (1, 2), flag = wx.EXPAND) | |
267 | else: | |
268 | viewer = showstl(self, (580, 580), (0, 0)) | |
269 | self.simarrange_path = simarrange_path | |
270 | self.set_viewer(viewer) | |
271 | ||
272 | def start_cutting_tool(self, event, axis, direction): | |
273 | toggle = event.GetEventObject() | |
274 | if toggle.GetValue(): | |
275 | # Disable the other toggles | |
276 | for child in self.cutsizer.GetChildren(): | |
277 | child = child.GetWindow() | |
278 | if child != toggle: | |
279 | child.SetValue(False) | |
280 | self.cutting = True | |
281 | self.cutting_axis = axis | |
282 | self.cutting_dist = None | |
283 | self.cutting_direction = direction | |
284 | else: | |
285 | self.cutting = False | |
286 | self.cutting_axis = None | |
287 | self.cutting_dist = None | |
288 | self.cutting_direction = None | |
289 | ||
290 | def cut_confirm(self, event): | |
291 | name = self.l.GetSelection() | |
292 | name = self.l.GetString(name) | |
293 | model = self.models[name] | |
294 | transformation = transformation_matrix(model) | |
295 | transformed = model.transform(transformation) | |
296 | logging.info(_("Cutting %s alongside %s axis") % (name, self.cutting_axis)) | |
297 | axes = ["x", "y", "z"] | |
298 | cut = transformed.cut(axes.index(self.cutting_axis), | |
299 | self.cutting_direction, | |
300 | self.cutting_dist) | |
301 | cut.offsets = [0, 0, 0] | |
302 | cut.rot = 0 | |
303 | cut.scale = model.scale | |
304 | cut.filename = model.filename | |
305 | cut.centeroffset = [0, 0, 0] | |
306 | self.s.prepare_model(cut, 2) | |
307 | self.models[name] = cut | |
308 | self.cutconfirmbutton.Disable() | |
309 | self.cutting = False | |
310 | self.cutting_axis = None | |
311 | self.cutting_dist = None | |
312 | self.cutting_direction = None | |
313 | for child in self.cutsizer.GetChildren(): | |
314 | child = child.GetWindow() | |
315 | child.SetValue(False) | |
316 | ||
317 | def clickcb(self, event, single = False): | |
318 | if not isinstance(self.s, stlview.StlViewPanel): | |
319 | return | |
320 | if self.cutting: | |
321 | self.clickcb_cut(event) | |
322 | else: | |
323 | self.clickcb_rebase(event) | |
324 | ||
325 | def clickcb_cut(self, event): | |
326 | axis = self.cutting_axis | |
327 | self.cutting_dist, _, _ = self.s.get_cutting_plane(axis, None, | |
328 | local_transform = True) | |
329 | if self.cutting_dist is not None: | |
330 | self.cutconfirmbutton.Enable() | |
331 | ||
332 | def clickcb_rebase(self, event): | |
333 | x, y = event.GetPosition() | |
334 | ray_near, ray_far = self.s.mouse_to_ray(x, y, local_transform = True) | |
335 | best_match = None | |
336 | best_facet = None | |
337 | best_dist = float("inf") | |
338 | for key, model in self.models.iteritems(): | |
339 | transformation = transformation_matrix(model) | |
340 | transformed = model.transform(transformation) | |
341 | if not transformed.intersect_box(ray_near, ray_far): | |
342 | logging.debug("Skipping %s for rebase search" % key) | |
343 | continue | |
344 | facet, facet_dist = transformed.intersect(ray_near, ray_far) | |
345 | if facet is not None and facet_dist < best_dist: | |
346 | best_match = key | |
347 | best_facet = facet | |
348 | best_dist = facet_dist | |
349 | if best_match is not None: | |
350 | logging.info("Rebasing %s" % best_match) | |
351 | model = self.models[best_match] | |
352 | newmodel = model.rebase(best_facet) | |
353 | newmodel.offsets = list(model.offsets) | |
354 | newmodel.rot = 0 | |
355 | newmodel.scale = model.scale | |
356 | newmodel.filename = model.filename | |
357 | newmodel.centeroffset = [-(newmodel.dims[1] + newmodel.dims[0]) / 2, | |
358 | -(newmodel.dims[3] + newmodel.dims[2]) / 2, | |
359 | 0] | |
360 | self.s.prepare_model(newmodel, 2) | |
361 | self.models[best_match] = newmodel | |
362 | wx.CallAfter(self.Refresh) | |
363 | ||
364 | def done(self, event, cb): | |
365 | if not os.path.exists("tempstl"): | |
366 | os.mkdir("tempstl") | |
367 | name = "tempstl/" + str(int(time.time()) % 10000) + ".stl" | |
368 | self.export_to(name) | |
369 | if cb is not None: | |
370 | cb(name) | |
371 | if self.destroy_on_done: | |
372 | self.Destroy() | |
373 | ||
374 | def load_file(self, filename): | |
375 | if filename.lower().endswith(".stl"): | |
376 | try: | |
377 | self.load_stl(filename) | |
378 | except: | |
379 | dlg = wx.MessageDialog(self, _("Loading STL file failed"), | |
380 | _("Error:") + traceback.format_exc(), | |
381 | wx.OK) | |
382 | dlg.ShowModal() | |
383 | logging.error(_("Loading STL file failed:") | |
384 | + "\n" + traceback.format_exc()) | |
385 | elif filename.lower().endswith(".scad"): | |
386 | try: | |
387 | self.load_scad(filename) | |
388 | except: | |
389 | dlg = wx.MessageDialog(self, _("Loading OpenSCAD file failed"), | |
390 | _("Error:") + traceback.format_exc(), | |
391 | wx.OK) | |
392 | dlg.ShowModal() | |
393 | logging.error(_("Loading OpenSCAD file failed:") | |
394 | + "\n" + traceback.format_exc()) | |
395 | ||
396 | def load_scad(self, name): | |
397 | lf = open(name) | |
398 | s = [i.replace("\n", "").replace("\r", "").replace(";", "") for i in lf if "stl" in i] | |
399 | lf.close() | |
400 | ||
401 | for i in s: | |
402 | parts = i.split() | |
403 | for part in parts: | |
404 | if 'translate' in part: | |
405 | translate_list = evalme(part) | |
406 | for part in parts: | |
407 | if 'rotate' in part: | |
408 | rotate_list = evalme(part) | |
409 | for part in parts: | |
410 | if 'import' in part: | |
411 | stl_file = evalme(part) | |
412 | ||
413 | newname = os.path.split(stl_file.lower())[1] | |
414 | c = 1 | |
415 | while newname in self.models: | |
416 | newname = os.path.split(stl_file.lower())[1] | |
417 | newname = newname + "(%d)" % c | |
418 | c += 1 | |
419 | stl_path = os.path.join(os.path.split(name)[0:len(os.path.split(stl_file)) - 1]) | |
420 | stl_full_path = os.path.join(stl_path[0], str(stl_file)) | |
421 | self.load_stl_into_model(stl_full_path, stl_file, translate_list, rotate_list[2]) | |
422 | ||
423 | def load_stl(self, name): | |
424 | if not os.path.exists(name): | |
425 | logging.error(_("Couldn't load non-existing file %s") % name) | |
426 | return | |
427 | path = os.path.split(name)[0] | |
428 | self.basedir = path | |
429 | if name.lower().endswith(".stl"): | |
430 | for model in self.models.values(): | |
431 | if model.filename == name: | |
432 | newmodel = copy(model) | |
433 | newmodel.offsets = list(model.offsets) | |
434 | newmodel.rot = model.rot | |
435 | newmodel.scale = list(model.scale) | |
436 | self.add_model(name, newmodel) | |
437 | self.s.prepare_model(newmodel, 2) | |
438 | break | |
439 | else: | |
440 | # Filter out the path, just show the STL filename. | |
441 | self.load_stl_into_model(name, name) | |
442 | wx.CallAfter(self.Refresh) | |
443 | ||
444 | def load_stl_into_model(self, path, name, offset = None, rotation = 0, scale = [1.0, 1.0, 1.0]): | |
445 | model = stltool.stl(path) | |
446 | if offset is None: | |
447 | offset = [self.build_dimensions[3], self.build_dimensions[4], 0] | |
448 | model.offsets = list(offset) | |
449 | model.rot = rotation | |
450 | model.scale = list(scale) | |
451 | model.filename = name | |
452 | self.add_model(name, model) | |
453 | model.centeroffset = [-(model.dims[1] + model.dims[0]) / 2, | |
454 | -(model.dims[3] + model.dims[2]) / 2, | |
455 | 0] | |
456 | self.s.prepare_model(model, 2) | |
457 | ||
458 | def export_to(self, name): | |
459 | with open(name.replace(".", "_") + ".scad", "w") as sf: | |
460 | facets = [] | |
461 | for model in self.models.values(): | |
462 | r = model.rot | |
463 | o = model.offsets | |
464 | co = model.centeroffset | |
465 | sf.write("translate([%s, %s, %s])" | |
466 | "rotate([0, 0, %s])" | |
467 | "translate([%s, %s, %s])" | |
468 | "import(\"%s\");\n" % (o[0], o[1], o[2], | |
469 | r, | |
470 | co[0], co[1], co[2], | |
471 | model.filename)) | |
472 | model = model.transform(transformation_matrix(model)) | |
473 | facets += model.facets | |
474 | stltool.emitstl(name, facets, "plater_export") | |
475 | logging.info(_("Wrote plate to %s") % name) | |
476 | ||
477 | def autoplate(self, event = None): | |
478 | if self.simarrange_path: | |
479 | try: | |
480 | self.autoplate_simarrange() | |
481 | except Exception, e: | |
482 | logging.warning(_("Failed to use simarrange for plating, " | |
483 | "falling back to the standard method. " | |
484 | "The error was: ") + e) | |
485 | super(StlPlaterPanel, self).autoplate() | |
486 | else: | |
487 | super(StlPlaterPanel, self).autoplate() | |
488 | ||
489 | def autoplate_simarrange(self): | |
490 | logging.info(_("Autoplating using simarrange")) | |
491 | models = dict(self.models) | |
492 | files = [model.filename for model in models.values()] | |
493 | command = [self.simarrange_path, "--dryrun", | |
494 | "-m", # Pack around center | |
495 | "-x", str(int(self.build_dimensions[0])), | |
496 | "-y", str(int(self.build_dimensions[1]))] + files | |
497 | p = subprocess.Popen(command, stdout = subprocess.PIPE) | |
498 | ||
499 | pos_regexp = re.compile("File: (.*) minx: ([0-9]+), miny: ([0-9]+), minrot: ([0-9]+)") | |
500 | for line in p.stdout: | |
501 | line = line.rstrip() | |
502 | if "Generating plate" in line: | |
503 | plateid = int(line.split()[-1]) | |
504 | if plateid > 0: | |
505 | logging.error(_("Plate full, please remove some objects")) | |
506 | break | |
507 | if "File:" in line: | |
508 | bits = pos_regexp.match(line).groups() | |
509 | filename = bits[0] | |
510 | x = float(bits[1]) | |
511 | y = float(bits[2]) | |
512 | rot = -float(bits[3]) | |
513 | for name, model in models.items(): | |
514 | # FIXME: not sure this is going to work superwell with utf8 | |
515 | if model.filename == filename: | |
516 | model.offsets[0] = x + self.build_dimensions[3] | |
517 | model.offsets[1] = y + self.build_dimensions[4] | |
518 | model.rot = rot | |
519 | del models[name] | |
520 | break | |
521 | if p.wait() != 0: | |
522 | raise RuntimeError(_("simarrange failed")) | |
523 | ||
524 | StlPlater = make_plater(StlPlaterPanel) |