Wed, 20 Jan 2021 10:15:13 +0100
updated and added new files for printrun
46 | 1 | #!/usr/bin/env python3 |
15 | 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 | ||
46 | 39 | glview = '--no-gl' not in sys.argv |
40 | if glview: | |
15 | 41 | try: |
42 | from printrun import stlview | |
43 | except: | |
46 | 44 | glview = False |
15 | 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): | |
46 | 64 | super().__init__(parent, size = size, pos = pos) |
15 | 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): | |
46 | 77 | m.bitmap = wx.Bitmap(800, 800, 32) |
15 | 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: | |
46 | 108 | currentpos = event.GetPosition() |
15 | 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: | |
46 | 119 | self.initpos = event.GetPosition() |
15 | 120 | self.Refresh() |
121 | dc = wx.ClientDC(self) | |
46 | 122 | p = event.GetPosition() |
15 | 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 | |
46 | 184 | self.rotate_shape(-1 if z < 0 else 1) #TEST |
15 | 185 | |
186 | def repaint(self, event): | |
187 | dc = wx.PaintDC(self) | |
188 | self.paint(dc = dc) | |
189 | ||
190 | def paint(self, coord1 = "x", coord2 = "y", dc = None): | |
191 | if dc is None: | |
192 | dc = wx.ClientDC(self) | |
193 | scale = 2 | |
194 | dc.SetPen(wx.Pen(wx.Colour(100, 100, 100))) | |
46 | 195 | for i in range(20): |
15 | 196 | dc.DrawLine(0, i * scale * 10, 400, i * scale * 10) |
197 | dc.DrawLine(i * scale * 10, 0, i * scale * 10, 400) | |
198 | dc.SetPen(wx.Pen(wx.Colour(0, 0, 0))) | |
46 | 199 | for i in range(4): |
15 | 200 | dc.DrawLine(0, i * scale * 50, 400, i * scale * 50) |
201 | dc.DrawLine(i * scale * 50, 0, i * scale * 50, 400) | |
202 | dc.SetBrush(wx.Brush(wx.Colour(128, 255, 128))) | |
203 | dc.SetPen(wx.Pen(wx.Colour(128, 128, 128))) | |
204 | dcs = wx.MemoryDC() | |
205 | for m in self.parent.models.values(): | |
206 | b = m.bitmap | |
207 | im = b.ConvertToImage() | |
208 | imgc = wx.Point(im.GetWidth() / 2, im.GetHeight() / 2) | |
209 | im = im.Rotate(math.radians(m.rot), imgc, 0) | |
210 | bm = wx.BitmapFromImage(im) | |
211 | dcs.SelectObject(bm) | |
212 | bsz = bm.GetSize() | |
213 | 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) | |
214 | del dc | |
215 | ||
216 | class StlPlaterPanel(PlaterPanel): | |
217 | ||
218 | load_wildcard = _("STL files (*.stl;*.STL)|*.stl;*.STL|OpenSCAD files (*.scad)|*.scad") | |
219 | save_wildcard = _("STL files (*.stl;*.STL)|*.stl;*.STL") | |
220 | ||
221 | def prepare_ui(self, filenames = [], callback = None, | |
222 | parent = None, build_dimensions = None, circular_platform = False, | |
223 | simarrange_path = None, antialias_samples = 0): | |
46 | 224 | super().prepare_ui(filenames, callback, parent, build_dimensions) |
15 | 225 | self.cutting = False |
226 | self.cutting_axis = None | |
227 | self.cutting_dist = None | |
228 | if glview: | |
46 | 229 | viewer = stlview.StlViewPanel(self, wx.DefaultSize, |
15 | 230 | build_dimensions = self.build_dimensions, |
231 | circular = circular_platform, | |
232 | antialias_samples = antialias_samples) | |
233 | # Cutting tool | |
234 | nrows = self.menusizer.GetRows() | |
235 | self.menusizer.Add(wx.StaticText(self.menupanel, -1, _("Cut along:")), | |
236 | pos = (nrows, 0), span = (1, 1), flag = wx.ALIGN_CENTER) | |
237 | cutconfirmbutton = wx.Button(self.menupanel, label = _("Confirm cut")) | |
238 | cutconfirmbutton.Bind(wx.EVT_BUTTON, self.cut_confirm) | |
239 | cutconfirmbutton.Disable() | |
240 | self.cutconfirmbutton = cutconfirmbutton | |
241 | self.menusizer.Add(cutconfirmbutton, pos = (nrows, 1), span = (1, 1), flag = wx.EXPAND) | |
46 | 242 | cutpanel = wx.Panel(self.menupanel) |
15 | 243 | cutsizer = self.cutsizer = wx.BoxSizer(wx.HORIZONTAL) |
244 | cutpanel.SetSizer(cutsizer) | |
245 | cutxplusbutton = wx.ToggleButton(cutpanel, label = _(">X"), style = wx.BU_EXACTFIT) | |
246 | cutxplusbutton.Bind(wx.EVT_TOGGLEBUTTON, lambda event: self.start_cutting_tool(event, "x", 1)) | |
247 | cutsizer.Add(cutxplusbutton, 1, flag = wx.EXPAND) | |
248 | cutzplusbutton = wx.ToggleButton(cutpanel, label = _(">Y"), style = wx.BU_EXACTFIT) | |
249 | cutzplusbutton.Bind(wx.EVT_TOGGLEBUTTON, lambda event: self.start_cutting_tool(event, "y", 1)) | |
250 | cutsizer.Add(cutzplusbutton, 1, flag = wx.EXPAND) | |
251 | cutzplusbutton = wx.ToggleButton(cutpanel, label = _(">Z"), style = wx.BU_EXACTFIT) | |
252 | cutzplusbutton.Bind(wx.EVT_TOGGLEBUTTON, lambda event: self.start_cutting_tool(event, "z", 1)) | |
253 | cutsizer.Add(cutzplusbutton, 1, flag = wx.EXPAND) | |
254 | cutxminusbutton = wx.ToggleButton(cutpanel, label = _("<X"), style = wx.BU_EXACTFIT) | |
255 | cutxminusbutton.Bind(wx.EVT_TOGGLEBUTTON, lambda event: self.start_cutting_tool(event, "x", -1)) | |
256 | cutsizer.Add(cutxminusbutton, 1, flag = wx.EXPAND) | |
257 | cutzminusbutton = wx.ToggleButton(cutpanel, label = _("<Y"), style = wx.BU_EXACTFIT) | |
258 | cutzminusbutton.Bind(wx.EVT_TOGGLEBUTTON, lambda event: self.start_cutting_tool(event, "y", -1)) | |
259 | cutsizer.Add(cutzminusbutton, 1, flag = wx.EXPAND) | |
260 | cutzminusbutton = wx.ToggleButton(cutpanel, label = _("<Z"), style = wx.BU_EXACTFIT) | |
261 | cutzminusbutton.Bind(wx.EVT_TOGGLEBUTTON, lambda event: self.start_cutting_tool(event, "z", -1)) | |
262 | cutsizer.Add(cutzminusbutton, 1, flag = wx.EXPAND) | |
263 | self.menusizer.Add(cutpanel, pos = (nrows + 1, 0), span = (1, 2), flag = wx.EXPAND) | |
264 | else: | |
265 | viewer = showstl(self, (580, 580), (0, 0)) | |
266 | self.simarrange_path = simarrange_path | |
267 | self.set_viewer(viewer) | |
268 | ||
269 | def start_cutting_tool(self, event, axis, direction): | |
46 | 270 | toggle = event.EventObject |
271 | self.cutting = toggle.Value | |
272 | if toggle.Value: | |
15 | 273 | # Disable the other toggles |
46 | 274 | for child in self.cutsizer.Children: |
275 | child = child.Window | |
15 | 276 | if child != toggle: |
46 | 277 | child.Value = False |
15 | 278 | self.cutting_axis = axis |
279 | self.cutting_direction = direction | |
280 | else: | |
281 | self.cutting_axis = None | |
282 | self.cutting_direction = None | |
46 | 283 | self.cutting_dist = None |
15 | 284 | |
285 | def cut_confirm(self, event): | |
286 | name = self.l.GetSelection() | |
287 | name = self.l.GetString(name) | |
288 | model = self.models[name] | |
289 | transformation = transformation_matrix(model) | |
290 | transformed = model.transform(transformation) | |
291 | logging.info(_("Cutting %s alongside %s axis") % (name, self.cutting_axis)) | |
292 | axes = ["x", "y", "z"] | |
293 | cut = transformed.cut(axes.index(self.cutting_axis), | |
294 | self.cutting_direction, | |
295 | self.cutting_dist) | |
296 | cut.offsets = [0, 0, 0] | |
297 | cut.rot = 0 | |
298 | cut.scale = model.scale | |
299 | cut.filename = model.filename | |
300 | cut.centeroffset = [0, 0, 0] | |
301 | self.s.prepare_model(cut, 2) | |
302 | self.models[name] = cut | |
303 | self.cutconfirmbutton.Disable() | |
304 | self.cutting = False | |
305 | self.cutting_axis = None | |
306 | self.cutting_dist = None | |
307 | self.cutting_direction = None | |
308 | for child in self.cutsizer.GetChildren(): | |
309 | child = child.GetWindow() | |
310 | child.SetValue(False) | |
311 | ||
312 | def clickcb(self, event, single = False): | |
313 | if not isinstance(self.s, stlview.StlViewPanel): | |
314 | return | |
315 | if self.cutting: | |
316 | self.clickcb_cut(event) | |
317 | else: | |
318 | self.clickcb_rebase(event) | |
319 | ||
320 | def clickcb_cut(self, event): | |
321 | axis = self.cutting_axis | |
322 | self.cutting_dist, _, _ = self.s.get_cutting_plane(axis, None, | |
323 | local_transform = True) | |
324 | if self.cutting_dist is not None: | |
325 | self.cutconfirmbutton.Enable() | |
326 | ||
327 | def clickcb_rebase(self, event): | |
328 | x, y = event.GetPosition() | |
329 | ray_near, ray_far = self.s.mouse_to_ray(x, y, local_transform = True) | |
330 | best_match = None | |
331 | best_facet = None | |
332 | best_dist = float("inf") | |
46 | 333 | for key, model in self.models.items(): |
15 | 334 | transformation = transformation_matrix(model) |
335 | transformed = model.transform(transformation) | |
336 | if not transformed.intersect_box(ray_near, ray_far): | |
337 | logging.debug("Skipping %s for rebase search" % key) | |
338 | continue | |
339 | facet, facet_dist = transformed.intersect(ray_near, ray_far) | |
340 | if facet is not None and facet_dist < best_dist: | |
341 | best_match = key | |
342 | best_facet = facet | |
343 | best_dist = facet_dist | |
344 | if best_match is not None: | |
345 | logging.info("Rebasing %s" % best_match) | |
346 | model = self.models[best_match] | |
347 | newmodel = model.rebase(best_facet) | |
348 | newmodel.offsets = list(model.offsets) | |
349 | newmodel.rot = 0 | |
350 | newmodel.scale = model.scale | |
351 | newmodel.filename = model.filename | |
352 | newmodel.centeroffset = [-(newmodel.dims[1] + newmodel.dims[0]) / 2, | |
353 | -(newmodel.dims[3] + newmodel.dims[2]) / 2, | |
354 | 0] | |
355 | self.s.prepare_model(newmodel, 2) | |
356 | self.models[best_match] = newmodel | |
357 | wx.CallAfter(self.Refresh) | |
358 | ||
359 | def done(self, event, cb): | |
360 | if not os.path.exists("tempstl"): | |
361 | os.mkdir("tempstl") | |
362 | name = "tempstl/" + str(int(time.time()) % 10000) + ".stl" | |
363 | self.export_to(name) | |
364 | if cb is not None: | |
365 | cb(name) | |
366 | if self.destroy_on_done: | |
367 | self.Destroy() | |
368 | ||
369 | def load_file(self, filename): | |
370 | if filename.lower().endswith(".stl"): | |
371 | try: | |
372 | self.load_stl(filename) | |
373 | except: | |
374 | dlg = wx.MessageDialog(self, _("Loading STL file failed"), | |
375 | _("Error:") + traceback.format_exc(), | |
376 | wx.OK) | |
377 | dlg.ShowModal() | |
378 | logging.error(_("Loading STL file failed:") | |
379 | + "\n" + traceback.format_exc()) | |
380 | elif filename.lower().endswith(".scad"): | |
381 | try: | |
382 | self.load_scad(filename) | |
383 | except: | |
384 | dlg = wx.MessageDialog(self, _("Loading OpenSCAD file failed"), | |
385 | _("Error:") + traceback.format_exc(), | |
386 | wx.OK) | |
387 | dlg.ShowModal() | |
388 | logging.error(_("Loading OpenSCAD file failed:") | |
389 | + "\n" + traceback.format_exc()) | |
390 | ||
391 | def load_scad(self, name): | |
392 | lf = open(name) | |
393 | s = [i.replace("\n", "").replace("\r", "").replace(";", "") for i in lf if "stl" in i] | |
394 | lf.close() | |
395 | ||
396 | for i in s: | |
397 | parts = i.split() | |
398 | for part in parts: | |
399 | if 'translate' in part: | |
400 | translate_list = evalme(part) | |
401 | for part in parts: | |
402 | if 'rotate' in part: | |
403 | rotate_list = evalme(part) | |
404 | for part in parts: | |
405 | if 'import' in part: | |
406 | stl_file = evalme(part) | |
407 | ||
408 | newname = os.path.split(stl_file.lower())[1] | |
409 | c = 1 | |
410 | while newname in self.models: | |
411 | newname = os.path.split(stl_file.lower())[1] | |
412 | newname = newname + "(%d)" % c | |
413 | c += 1 | |
414 | stl_path = os.path.join(os.path.split(name)[0:len(os.path.split(stl_file)) - 1]) | |
415 | stl_full_path = os.path.join(stl_path[0], str(stl_file)) | |
416 | self.load_stl_into_model(stl_full_path, stl_file, translate_list, rotate_list[2]) | |
417 | ||
418 | def load_stl(self, name): | |
419 | if not os.path.exists(name): | |
420 | logging.error(_("Couldn't load non-existing file %s") % name) | |
421 | return | |
422 | path = os.path.split(name)[0] | |
423 | self.basedir = path | |
424 | if name.lower().endswith(".stl"): | |
425 | for model in self.models.values(): | |
426 | if model.filename == name: | |
427 | newmodel = copy(model) | |
428 | newmodel.offsets = list(model.offsets) | |
429 | newmodel.rot = model.rot | |
430 | newmodel.scale = list(model.scale) | |
431 | self.add_model(name, newmodel) | |
432 | self.s.prepare_model(newmodel, 2) | |
433 | break | |
434 | else: | |
435 | # Filter out the path, just show the STL filename. | |
436 | self.load_stl_into_model(name, name) | |
437 | wx.CallAfter(self.Refresh) | |
438 | ||
439 | def load_stl_into_model(self, path, name, offset = None, rotation = 0, scale = [1.0, 1.0, 1.0]): | |
440 | model = stltool.stl(path) | |
441 | if offset is None: | |
442 | offset = [self.build_dimensions[3], self.build_dimensions[4], 0] | |
443 | model.offsets = list(offset) | |
444 | model.rot = rotation | |
445 | model.scale = list(scale) | |
446 | model.filename = name | |
447 | self.add_model(name, model) | |
448 | model.centeroffset = [-(model.dims[1] + model.dims[0]) / 2, | |
449 | -(model.dims[3] + model.dims[2]) / 2, | |
450 | 0] | |
451 | self.s.prepare_model(model, 2) | |
452 | ||
453 | def export_to(self, name): | |
454 | with open(name.replace(".", "_") + ".scad", "w") as sf: | |
455 | facets = [] | |
456 | for model in self.models.values(): | |
457 | r = model.rot | |
458 | o = model.offsets | |
459 | co = model.centeroffset | |
460 | sf.write("translate([%s, %s, %s])" | |
461 | "rotate([0, 0, %s])" | |
462 | "translate([%s, %s, %s])" | |
463 | "import(\"%s\");\n" % (o[0], o[1], o[2], | |
464 | r, | |
465 | co[0], co[1], co[2], | |
466 | model.filename)) | |
467 | model = model.transform(transformation_matrix(model)) | |
468 | facets += model.facets | |
469 | stltool.emitstl(name, facets, "plater_export") | |
470 | logging.info(_("Wrote plate to %s") % name) | |
471 | ||
472 | def autoplate(self, event = None): | |
473 | if self.simarrange_path: | |
474 | try: | |
475 | self.autoplate_simarrange() | |
46 | 476 | except Exception as e: |
15 | 477 | logging.warning(_("Failed to use simarrange for plating, " |
478 | "falling back to the standard method. " | |
479 | "The error was: ") + e) | |
480 | super(StlPlaterPanel, self).autoplate() | |
481 | else: | |
482 | super(StlPlaterPanel, self).autoplate() | |
483 | ||
484 | def autoplate_simarrange(self): | |
485 | logging.info(_("Autoplating using simarrange")) | |
486 | models = dict(self.models) | |
487 | files = [model.filename for model in models.values()] | |
488 | command = [self.simarrange_path, "--dryrun", | |
489 | "-m", # Pack around center | |
490 | "-x", str(int(self.build_dimensions[0])), | |
491 | "-y", str(int(self.build_dimensions[1]))] + files | |
46 | 492 | p = subprocess.Popen(command, stdout = subprocess.PIPE, universal_newlines = True) |
15 | 493 | |
494 | pos_regexp = re.compile("File: (.*) minx: ([0-9]+), miny: ([0-9]+), minrot: ([0-9]+)") | |
495 | for line in p.stdout: | |
496 | line = line.rstrip() | |
497 | if "Generating plate" in line: | |
498 | plateid = int(line.split()[-1]) | |
499 | if plateid > 0: | |
500 | logging.error(_("Plate full, please remove some objects")) | |
501 | break | |
502 | if "File:" in line: | |
503 | bits = pos_regexp.match(line).groups() | |
504 | filename = bits[0] | |
505 | x = float(bits[1]) | |
506 | y = float(bits[2]) | |
507 | rot = -float(bits[3]) | |
46 | 508 | for name, model in list(models.items()): |
15 | 509 | # FIXME: not sure this is going to work superwell with utf8 |
510 | if model.filename == filename: | |
511 | model.offsets[0] = x + self.build_dimensions[3] | |
512 | model.offsets[1] = y + self.build_dimensions[4] | |
513 | model.rot = rot | |
514 | del models[name] | |
515 | break | |
516 | if p.wait() != 0: | |
517 | raise RuntimeError(_("simarrange failed")) | |
518 | ||
519 | StlPlater = make_plater(StlPlaterPanel) |