Wed, 20 Jan 2021 10:15:13 +0100
updated and added new files for printrun
15 | 1 | # -*- coding: utf-8 -*- |
2 | # Copyright (C) 2013 Guillaume Seguin | |
3 | # Copyright (C) 2011 Denis Kobozev | |
4 | # | |
5 | # This program 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 2 of the License, or | |
8 | # (at your option) any later version. | |
9 | # | |
10 | # This program 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 this program; if not, write to the Free Software Foundation, | |
17 | # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
18 | ||
19 | import time | |
20 | import numpy | |
21 | import array | |
22 | import math | |
23 | import logging | |
24 | import threading | |
25 | ||
26 | from ctypes import sizeof | |
27 | ||
28 | from pyglet.gl import glPushMatrix, glPopMatrix, glTranslatef, \ | |
29 | glGenLists, glNewList, GL_COMPILE, glEndList, glCallList, \ | |
30 | GL_ELEMENT_ARRAY_BUFFER, GL_UNSIGNED_INT, GL_TRIANGLES, GL_LINE_LOOP, \ | |
31 | GL_ARRAY_BUFFER, GL_STATIC_DRAW, glColor4f, glVertex3f, \ | |
32 | glBegin, glEnd, GL_LINES, glEnable, glDisable, glGetFloatv, \ | |
33 | GL_LINE_SMOOTH, glLineWidth, GL_LINE_WIDTH, GLfloat, GL_FLOAT, GLuint, \ | |
34 | glVertexPointer, glColorPointer, glDrawArrays, glDrawRangeElements, \ | |
35 | glEnableClientState, glDisableClientState, GL_VERTEX_ARRAY, GL_COLOR_ARRAY, \ | |
36 | GL_FRONT_AND_BACK, GL_FRONT, glMaterialfv, GL_SPECULAR, GL_EMISSION, \ | |
37 | glColorMaterial, GL_AMBIENT_AND_DIFFUSE, glMaterialf, GL_SHININESS, \ | |
38 | GL_NORMAL_ARRAY, glNormalPointer, GL_LIGHTING, glColor3f | |
39 | from pyglet.graphics.vertexbuffer import create_buffer, VertexBufferObject | |
40 | ||
41 | from printrun.utils import install_locale | |
42 | install_locale('pronterface') | |
43 | ||
44 | def vec(*args): | |
45 | return (GLfloat * len(args))(*args) | |
46 | ||
47 | def compile_display_list(func, *options): | |
48 | display_list = glGenLists(1) | |
49 | glNewList(display_list, GL_COMPILE) | |
50 | func(*options) | |
51 | glEndList() | |
52 | return display_list | |
53 | ||
54 | def numpy2vbo(nparray, target = GL_ARRAY_BUFFER, usage = GL_STATIC_DRAW, use_vbos = True): | |
55 | vbo = create_buffer(nparray.nbytes, target = target, usage = usage, vbo = use_vbos) | |
56 | vbo.bind() | |
57 | vbo.set_data(nparray.ctypes.data) | |
58 | return vbo | |
59 | ||
60 | def triangulate_rectangle(i1, i2, i3, i4): | |
61 | return [i1, i4, i3, i3, i2, i1] | |
62 | ||
63 | def triangulate_box(i1, i2, i3, i4, | |
64 | j1, j2, j3, j4): | |
65 | return [i1, i2, j2, j2, j1, i1, i2, i3, j3, j3, j2, i2, | |
66 | i3, i4, j4, j4, j3, i3, i4, i1, j1, j1, j4, i4] | |
67 | ||
46 | 68 | class BoundingBox: |
15 | 69 | """ |
70 | A rectangular box (cuboid) enclosing a 3D model, defined by lower and upper corners. | |
71 | """ | |
72 | def __init__(self, upper_corner, lower_corner): | |
73 | self.upper_corner = upper_corner | |
74 | self.lower_corner = lower_corner | |
75 | ||
76 | @property | |
77 | def width(self): | |
78 | width = abs(self.upper_corner[0] - self.lower_corner[0]) | |
79 | return round(width, 2) | |
80 | ||
81 | @property | |
82 | def depth(self): | |
83 | depth = abs(self.upper_corner[1] - self.lower_corner[1]) | |
84 | return round(depth, 2) | |
85 | ||
86 | @property | |
87 | def height(self): | |
88 | height = abs(self.upper_corner[2] - self.lower_corner[2]) | |
89 | return round(height, 2) | |
90 | ||
91 | ||
46 | 92 | class Platform: |
15 | 93 | """ |
94 | Platform on which models are placed. | |
95 | """ | |
96 | ||
46 | 97 | def __init__(self, build_dimensions, light = False, circular = False, grid = (1, 10)): |
15 | 98 | self.light = light |
99 | self.circular = circular | |
100 | self.width = build_dimensions[0] | |
101 | self.depth = build_dimensions[1] | |
102 | self.height = build_dimensions[2] | |
103 | self.xoffset = build_dimensions[3] | |
104 | self.yoffset = build_dimensions[4] | |
105 | self.zoffset = build_dimensions[5] | |
46 | 106 | self.grid = grid |
15 | 107 | |
108 | self.color_grads_minor = (0xaf / 255, 0xdf / 255, 0x5f / 255, 0.1) | |
109 | self.color_grads_interm = (0xaf / 255, 0xdf / 255, 0x5f / 255, 0.2) | |
110 | self.color_grads_major = (0xaf / 255, 0xdf / 255, 0x5f / 255, 0.33) | |
111 | ||
112 | self.initialized = False | |
113 | self.loaded = True | |
114 | ||
115 | def init(self): | |
116 | self.display_list = compile_display_list(self.draw) | |
117 | self.initialized = True | |
118 | ||
119 | def draw(self): | |
120 | glPushMatrix() | |
121 | ||
122 | glTranslatef(self.xoffset, self.yoffset, self.zoffset) | |
123 | ||
124 | def color(i): | |
46 | 125 | if i % self.grid[1] == 0: |
15 | 126 | glColor4f(*self.color_grads_major) |
46 | 127 | elif i % (self.grid[1] // 2) == 0: |
15 | 128 | glColor4f(*self.color_grads_interm) |
129 | else: | |
130 | if self.light: return False | |
131 | glColor4f(*self.color_grads_minor) | |
132 | return True | |
133 | ||
134 | # draw the grid | |
135 | glBegin(GL_LINES) | |
136 | if self.circular: # Draw a circular grid | |
46 | 137 | for i in numpy.arange(0, int(math.ceil(self.width + 1)), self.grid[0]): |
15 | 138 | angle = math.asin(2 * float(i) / self.width - 1) |
139 | x = (math.cos(angle) + 1) * self.depth / 2 | |
140 | if color(i): | |
141 | glVertex3f(float(i), self.depth - x, 0.0) | |
142 | glVertex3f(float(i), x, 0.0) | |
143 | ||
46 | 144 | for i in numpy.arange(0, int(math.ceil(self.depth + 1)), self.grid[0]): |
15 | 145 | angle = math.acos(2 * float(i) / self.depth - 1) |
146 | x = (math.sin(angle) + 1) * self.width / 2 | |
147 | if color(i): | |
148 | glVertex3f(self.width - x, float(i), 0.0) | |
149 | glVertex3f(x, float(i), 0.0) | |
150 | else: # Draw a rectangular grid | |
46 | 151 | for i in numpy.arange(0, int(math.ceil(self.width + 1)), self.grid[0]): |
15 | 152 | if color(i): |
153 | glVertex3f(float(i), 0.0, 0.0) | |
154 | glVertex3f(float(i), self.depth, 0.0) | |
155 | ||
46 | 156 | for i in numpy.arange(0, int(math.ceil(self.depth + 1)), self.grid[0]): |
15 | 157 | if color(i): |
158 | glVertex3f(0, float(i), 0.0) | |
159 | glVertex3f(self.width, float(i), 0.0) | |
160 | glEnd() | |
161 | ||
162 | if self.circular: | |
163 | glBegin(GL_LINE_LOOP) | |
164 | for i in range(0, 360): | |
165 | angle = math.radians(i) | |
166 | glVertex3f((math.cos(angle) + 1) * self.width / 2, | |
167 | (math.sin(angle) + 1) * self.depth / 2, 0.0) | |
168 | glEnd() | |
169 | ||
170 | glPopMatrix() | |
171 | ||
172 | def display(self, mode_2d=False): | |
173 | # FIXME: using the list sometimes results in graphical corruptions | |
174 | # glCallList(self.display_list) | |
175 | self.draw() | |
176 | ||
46 | 177 | class PrintHead: |
15 | 178 | def __init__(self): |
179 | self.color = (43. / 255, 0., 175. / 255, 1.0) | |
180 | self.scale = 5 | |
181 | self.height = 5 | |
182 | ||
183 | self.initialized = False | |
184 | self.loaded = True | |
185 | ||
186 | def init(self): | |
187 | self.display_list = compile_display_list(self.draw) | |
188 | self.initialized = True | |
189 | ||
190 | def draw(self): | |
191 | glPushMatrix() | |
192 | ||
193 | glBegin(GL_LINES) | |
194 | glColor4f(*self.color) | |
195 | for di in [-1, 1]: | |
196 | for dj in [-1, 1]: | |
197 | glVertex3f(0, 0, 0) | |
198 | glVertex3f(self.scale * di, self.scale * dj, self.height) | |
199 | glEnd() | |
200 | ||
201 | glPopMatrix() | |
202 | ||
203 | def display(self, mode_2d=False): | |
204 | glEnable(GL_LINE_SMOOTH) | |
205 | orig_linewidth = (GLfloat)() | |
206 | glGetFloatv(GL_LINE_WIDTH, orig_linewidth) | |
207 | glLineWidth(3.0) | |
208 | glCallList(self.display_list) | |
209 | glLineWidth(orig_linewidth) | |
210 | glDisable(GL_LINE_SMOOTH) | |
211 | ||
46 | 212 | class Model: |
15 | 213 | """ |
214 | Parent class for models that provides common functionality. | |
215 | """ | |
216 | AXIS_X = (1, 0, 0) | |
217 | AXIS_Y = (0, 1, 0) | |
218 | AXIS_Z = (0, 0, 1) | |
219 | ||
220 | letter_axis_map = { | |
221 | 'x': AXIS_X, | |
222 | 'y': AXIS_Y, | |
223 | 'z': AXIS_Z, | |
224 | } | |
225 | ||
226 | axis_letter_map = dict([(v, k) for k, v in letter_axis_map.items()]) | |
227 | ||
228 | lock = None | |
229 | ||
230 | def __init__(self, offset_x=0, offset_y=0): | |
231 | self.offset_x = offset_x | |
232 | self.offset_y = offset_y | |
233 | ||
234 | self.lock = threading.Lock() | |
235 | ||
236 | self.init_model_attributes() | |
237 | ||
238 | def init_model_attributes(self): | |
239 | """ | |
240 | Set/reset saved properties. | |
241 | """ | |
242 | self.invalidate_bounding_box() | |
243 | self.modified = False | |
244 | ||
245 | def invalidate_bounding_box(self): | |
246 | self._bounding_box = None | |
247 | ||
248 | @property | |
249 | def bounding_box(self): | |
250 | """ | |
251 | Get a bounding box for the model. | |
252 | """ | |
253 | if self._bounding_box is None: | |
254 | self._bounding_box = self._calculate_bounding_box() | |
255 | return self._bounding_box | |
256 | ||
257 | def _calculate_bounding_box(self): | |
258 | """ | |
259 | Calculate an axis-aligned box enclosing the model. | |
260 | """ | |
261 | # swap rows and columns in our vertex arrays so that we can do max and | |
262 | # min on axis 1 | |
263 | xyz_rows = self.vertices.reshape(-1, order='F').reshape(3, -1) | |
264 | lower_corner = xyz_rows.min(1) | |
265 | upper_corner = xyz_rows.max(1) | |
266 | box = BoundingBox(upper_corner, lower_corner) | |
267 | return box | |
268 | ||
269 | @property | |
270 | def width(self): | |
271 | return self.bounding_box.width | |
272 | ||
273 | @property | |
274 | def depth(self): | |
275 | return self.bounding_box.depth | |
276 | ||
277 | @property | |
278 | def height(self): | |
279 | return self.bounding_box.height | |
280 | ||
281 | def movement_color(self, move): | |
282 | """ | |
283 | Return the color to use for particular type of movement. | |
284 | """ | |
285 | if move.extruding: | |
286 | if move.current_tool == 0: | |
287 | return self.color_tool0 | |
288 | elif move.current_tool == 1: | |
289 | return self.color_tool1 | |
290 | elif move.current_tool == 2: | |
291 | return self.color_tool2 | |
292 | elif move.current_tool == 3: | |
293 | return self.color_tool3 | |
294 | else: | |
295 | return self.color_tool4 | |
296 | ||
297 | return self.color_travel | |
298 | ||
299 | def movement_angle(src, dst, precision=0): | |
300 | x = dst[0] - src[0] | |
301 | y = dst[1] - src[1] | |
302 | angle = math.degrees(math.atan2(y, -x)) # negate x for clockwise rotation angle | |
303 | return round(angle, precision) | |
304 | ||
305 | def get_next_move(gcode, layer_idx, gline_idx): | |
306 | gline_idx += 1 | |
307 | while layer_idx < len(gcode.all_layers): | |
308 | layer = gcode.all_layers[layer_idx] | |
309 | while gline_idx < len(layer): | |
310 | gline = layer[gline_idx] | |
311 | if gline.is_move: | |
312 | return gline | |
313 | gline_idx += 1 | |
314 | layer_idx += 1 | |
315 | gline_idx = 0 | |
316 | return None | |
317 | ||
46 | 318 | def interpolate_arcs(gline, prev_gline): |
319 | if gline.command == "G2" or gline.command == "G3": | |
320 | rx = gline.i if gline.i is not None else 0 | |
321 | ry = gline.j if gline.j is not None else 0 | |
322 | r = math.sqrt(rx*rx + ry*ry) | |
323 | ||
324 | cx = prev_gline.current_x + rx | |
325 | cy = prev_gline.current_y + ry | |
326 | ||
327 | a_start = math.atan2(-ry, -rx) | |
328 | dx = gline.current_x - cx | |
329 | dy = gline.current_y - cy | |
330 | a_end = math.atan2(dy, dx) | |
331 | a_delta = a_end - a_start | |
332 | ||
333 | if gline.command == "G3" and a_delta <= 0: | |
334 | a_delta += math.pi * 2 | |
335 | elif gline.command == "G2" and a_delta >= 0: | |
336 | a_delta -= math.pi * 2 | |
337 | ||
338 | z0 = prev_gline.current_z | |
339 | dz = gline.current_z - z0 | |
340 | ||
341 | # max segment size: 0.5mm, max num of segments: 100 | |
342 | segments = math.ceil(abs(a_delta) * r * 2 / 0.5) | |
343 | if segments > 100: | |
344 | segments = 100 | |
345 | ||
346 | for t in range(segments): | |
347 | a = t / segments * a_delta + a_start | |
348 | ||
349 | mid = ( | |
350 | cx + math.cos(a) * r, | |
351 | cy + math.sin(a) * r, | |
352 | z0 + t / segments * dz | |
353 | ) | |
354 | yield mid | |
355 | ||
356 | yield (gline.current_x, gline.current_y, gline.current_z) | |
357 | ||
358 | ||
15 | 359 | class GcodeModel(Model): |
360 | """ | |
361 | Model for displaying Gcode data. | |
362 | """ | |
363 | ||
364 | color_travel = (0.6, 0.6, 0.6, 0.6) | |
365 | color_tool0 = (1.0, 0.0, 0.0, 1.0) | |
366 | color_tool1 = (0.67, 0.05, 0.9, 1.0) | |
367 | color_tool2 = (1.0, 0.8, 0., 1.0) | |
368 | color_tool3 = (1.0, 0., 0.62, 1.0) | |
369 | color_tool4 = (0., 1.0, 0.58, 1.0) | |
370 | color_printed = (0.2, 0.75, 0, 1.0) | |
371 | color_current = (0, 0.9, 1.0, 1.0) | |
372 | color_current_printed = (0.1, 0.4, 0, 1.0) | |
373 | ||
374 | display_travels = True | |
375 | ||
376 | buffers_created = False | |
377 | use_vbos = True | |
378 | loaded = False | |
379 | fully_loaded = False | |
380 | ||
381 | gcode = None | |
382 | ||
383 | path_halfwidth = 0.2 | |
384 | path_halfheight = 0.2 | |
385 | ||
386 | def set_path_size(self, path_halfwidth, path_halfheight): | |
387 | with self.lock: | |
388 | self.path_halfwidth = path_halfwidth | |
389 | self.path_halfheight = path_halfheight | |
390 | ||
391 | def load_data(self, model_data, callback=None): | |
392 | t_start = time.time() | |
393 | self.gcode = model_data | |
394 | ||
395 | self.count_travel_indices = count_travel_indices = [0] | |
396 | self.count_print_indices = count_print_indices = [0] | |
397 | self.count_print_vertices = count_print_vertices = [0] | |
398 | ||
399 | # Some trivial computations, but that's mostly for documentation :) | |
400 | # Not like 10 multiplications are going to cost much time vs what's | |
401 | # about to happen :) | |
402 | ||
403 | # Max number of values which can be generated per gline | |
404 | # to store coordinates/colors/normals. | |
405 | # Nicely enough we have 3 per kind of thing for all kinds. | |
406 | coordspervertex = 3 | |
46 | 407 | buffered_color_len = 3 # 4th color component (alpha) is ignored |
15 | 408 | verticesperline = 8 |
409 | coordsperline = coordspervertex * verticesperline | |
410 | coords_count = lambda nlines: nlines * coordsperline | |
411 | ||
412 | travelverticesperline = 2 | |
413 | travelcoordsperline = coordspervertex * travelverticesperline | |
414 | travel_coords_count = lambda nlines: nlines * travelcoordsperline | |
415 | ||
416 | trianglesperface = 2 | |
417 | facesperbox = 4 | |
418 | trianglesperbox = trianglesperface * facesperbox | |
419 | verticespertriangle = 3 | |
420 | indicesperbox = verticespertriangle * trianglesperbox | |
421 | boxperline = 2 | |
422 | indicesperline = indicesperbox * boxperline | |
423 | indices_count = lambda nlines: nlines * indicesperline | |
424 | ||
425 | nlines = len(model_data) | |
426 | ntravelcoords = travel_coords_count(nlines) | |
427 | ncoords = coords_count(nlines) | |
428 | nindices = indices_count(nlines) | |
429 | travel_vertices = self.travels = numpy.zeros(ntravelcoords, dtype = GLfloat) | |
430 | travel_vertex_k = 0 | |
431 | vertices = self.vertices = numpy.zeros(ncoords, dtype = GLfloat) | |
432 | vertex_k = 0 | |
433 | colors = self.colors = numpy.zeros(ncoords, dtype = GLfloat) | |
46 | 434 | |
15 | 435 | color_k = 0 |
436 | normals = self.normals = numpy.zeros(ncoords, dtype = GLfloat) | |
437 | indices = self.indices = numpy.zeros(nindices, dtype = GLuint) | |
438 | index_k = 0 | |
439 | self.layer_idxs_map = {} | |
440 | self.layer_stops = [0] | |
441 | ||
442 | prev_move_normal_x = None | |
443 | prev_move_normal_y = None | |
444 | prev_move_angle = None | |
445 | prev_pos = (0, 0, 0) | |
46 | 446 | prev_gline = None |
15 | 447 | layer_idx = 0 |
448 | ||
449 | self.printed_until = 0 | |
450 | self.only_current = False | |
451 | ||
452 | twopi = 2 * math.pi | |
453 | ||
454 | processed_lines = 0 | |
455 | ||
456 | while layer_idx < len(model_data.all_layers): | |
457 | with self.lock: | |
458 | nlines = len(model_data) | |
459 | remaining_lines = nlines - processed_lines | |
460 | # Only reallocate memory which might be needed, not memory | |
461 | # for everything | |
462 | ntravelcoords = coords_count(remaining_lines) + travel_vertex_k | |
463 | ncoords = coords_count(remaining_lines) + vertex_k | |
464 | nindices = indices_count(remaining_lines) + index_k | |
465 | if ncoords > vertices.size: | |
466 | self.travels.resize(ntravelcoords, refcheck = False) | |
467 | self.vertices.resize(ncoords, refcheck = False) | |
468 | self.colors.resize(ncoords, refcheck = False) | |
469 | self.normals.resize(ncoords, refcheck = False) | |
470 | self.indices.resize(nindices, refcheck = False) | |
471 | layer = model_data.all_layers[layer_idx] | |
472 | has_movement = False | |
473 | for gline_idx, gline in enumerate(layer): | |
474 | if not gline.is_move: | |
475 | continue | |
476 | if gline.x is None and gline.y is None and gline.z is None: | |
477 | continue | |
478 | has_movement = True | |
46 | 479 | for current_pos in interpolate_arcs(gline, prev_gline): |
480 | if not gline.extruding: | |
481 | if self.travels.size < (travel_vertex_k + 100 * 6): | |
482 | # arc interpolation extra points allocation | |
483 | # if not enough room for another 100 points now, | |
484 | # allocate enough and 50% extra to minimize separate allocations | |
485 | ratio = (travel_vertex_k + 100 * 6) / self.travels.size * 1.5 | |
486 | # print(f"gl realloc travel {self.travels.size} -> {int(self.travels.size * ratio)}") | |
487 | self.travels.resize(int(self.travels.size * ratio), refcheck = False) | |
15 | 488 | |
46 | 489 | travel_vertices[travel_vertex_k:travel_vertex_k+3] = prev_pos |
490 | travel_vertices[travel_vertex_k + 3:travel_vertex_k + 6] = current_pos | |
491 | travel_vertex_k += 6 | |
492 | else: | |
493 | delta_x = current_pos[0] - prev_pos[0] | |
494 | delta_y = current_pos[1] - prev_pos[1] | |
495 | norm = delta_x * delta_x + delta_y * delta_y | |
496 | if norm == 0: # Don't draw anything if this move is Z+E only | |
497 | continue | |
498 | norm = math.sqrt(norm) | |
499 | move_normal_x = - delta_y / norm | |
500 | move_normal_y = delta_x / norm | |
501 | move_angle = math.atan2(delta_y, delta_x) | |
15 | 502 | |
46 | 503 | # FIXME: compute these dynamically |
504 | path_halfwidth = self.path_halfwidth * 1.2 | |
505 | path_halfheight = self.path_halfheight * 1.2 | |
15 | 506 | |
46 | 507 | new_indices = [] |
508 | new_vertices = [] | |
509 | new_normals = [] | |
510 | if prev_gline and prev_gline.extruding: | |
511 | # Store previous vertices indices | |
512 | prev_id = vertex_k // 3 - 4 | |
513 | avg_move_normal_x = (prev_move_normal_x + move_normal_x) / 2 | |
514 | avg_move_normal_y = (prev_move_normal_y + move_normal_y) / 2 | |
515 | norm = avg_move_normal_x * avg_move_normal_x + avg_move_normal_y * avg_move_normal_y | |
516 | if norm == 0: | |
517 | avg_move_normal_x = move_normal_x | |
518 | avg_move_normal_y = move_normal_y | |
519 | else: | |
520 | norm = math.sqrt(norm) | |
521 | avg_move_normal_x /= norm | |
522 | avg_move_normal_y /= norm | |
523 | delta_angle = move_angle - prev_move_angle | |
524 | delta_angle = (delta_angle + twopi) % twopi | |
525 | fact = abs(math.cos(delta_angle / 2)) | |
526 | # If move is turning too much, avoid creating a big peak | |
527 | # by adding an intermediate box | |
528 | if fact < 0.5: | |
529 | # FIXME: It looks like there's some heavy code duplication here... | |
530 | hw = path_halfwidth | |
531 | p1x = prev_pos[0] - hw * prev_move_normal_x | |
532 | p2x = prev_pos[0] + hw * prev_move_normal_x | |
533 | p1y = prev_pos[1] - hw * prev_move_normal_y | |
534 | p2y = prev_pos[1] + hw * prev_move_normal_y | |
535 | new_vertices.extend((prev_pos[0], prev_pos[1], prev_pos[2] + path_halfheight)) | |
536 | new_vertices.extend((p1x, p1y, prev_pos[2])) | |
537 | new_vertices.extend((prev_pos[0], prev_pos[1], prev_pos[2] - path_halfheight)) | |
538 | new_vertices.extend((p2x, p2y, prev_pos[2])) | |
539 | new_normals.extend((0, 0, 1)) | |
540 | new_normals.extend((-prev_move_normal_x, -prev_move_normal_y, 0)) | |
541 | new_normals.extend((0, 0, -1)) | |
542 | new_normals.extend((prev_move_normal_x, prev_move_normal_y, 0)) | |
543 | first = vertex_k // 3 | |
544 | # Link to previous | |
545 | new_indices += triangulate_box(prev_id, prev_id + 1, | |
546 | prev_id + 2, prev_id + 3, | |
547 | first, first + 1, | |
548 | first + 2, first + 3) | |
549 | p1x = prev_pos[0] - hw * move_normal_x | |
550 | p2x = prev_pos[0] + hw * move_normal_x | |
551 | p1y = prev_pos[1] - hw * move_normal_y | |
552 | p2y = prev_pos[1] + hw * move_normal_y | |
553 | new_vertices.extend((prev_pos[0], prev_pos[1], prev_pos[2] + path_halfheight)) | |
554 | new_vertices.extend((p1x, p1y, prev_pos[2])) | |
555 | new_vertices.extend((prev_pos[0], prev_pos[1], prev_pos[2] - path_halfheight)) | |
556 | new_vertices.extend((p2x, p2y, prev_pos[2])) | |
557 | new_normals.extend((0, 0, 1)) | |
558 | new_normals.extend((-move_normal_x, -move_normal_y, 0)) | |
559 | new_normals.extend((0, 0, -1)) | |
560 | new_normals.extend((move_normal_x, move_normal_y, 0)) | |
561 | prev_id += 4 | |
562 | first += 4 | |
563 | # Link to previous | |
564 | new_indices += triangulate_box(prev_id, prev_id + 1, | |
565 | prev_id + 2, prev_id + 3, | |
566 | first, first + 1, | |
567 | first + 2, first + 3) | |
568 | else: | |
569 | hw = path_halfwidth / fact | |
570 | # Compute vertices | |
571 | p1x = prev_pos[0] - hw * avg_move_normal_x | |
572 | p2x = prev_pos[0] + hw * avg_move_normal_x | |
573 | p1y = prev_pos[1] - hw * avg_move_normal_y | |
574 | p2y = prev_pos[1] + hw * avg_move_normal_y | |
575 | new_vertices.extend((prev_pos[0], prev_pos[1], prev_pos[2] + path_halfheight)) | |
576 | new_vertices.extend((p1x, p1y, prev_pos[2])) | |
577 | new_vertices.extend((prev_pos[0], prev_pos[1], prev_pos[2] - path_halfheight)) | |
578 | new_vertices.extend((p2x, p2y, prev_pos[2])) | |
579 | new_normals.extend((0, 0, 1)) | |
580 | new_normals.extend((-avg_move_normal_x, -avg_move_normal_y, 0)) | |
581 | new_normals.extend((0, 0, -1)) | |
582 | new_normals.extend((avg_move_normal_x, avg_move_normal_y, 0)) | |
583 | first = vertex_k // 3 | |
584 | # Link to previous | |
585 | new_indices += triangulate_box(prev_id, prev_id + 1, | |
586 | prev_id + 2, prev_id + 3, | |
587 | first, first + 1, | |
588 | first + 2, first + 3) | |
15 | 589 | else: |
46 | 590 | # Compute vertices normal to the current move and cap it |
591 | p1x = prev_pos[0] - path_halfwidth * move_normal_x | |
592 | p2x = prev_pos[0] + path_halfwidth * move_normal_x | |
593 | p1y = prev_pos[1] - path_halfwidth * move_normal_y | |
594 | p2y = prev_pos[1] + path_halfwidth * move_normal_y | |
15 | 595 | new_vertices.extend((prev_pos[0], prev_pos[1], prev_pos[2] + path_halfheight)) |
596 | new_vertices.extend((p1x, p1y, prev_pos[2])) | |
597 | new_vertices.extend((prev_pos[0], prev_pos[1], prev_pos[2] - path_halfheight)) | |
598 | new_vertices.extend((p2x, p2y, prev_pos[2])) | |
599 | new_normals.extend((0, 0, 1)) | |
600 | new_normals.extend((-move_normal_x, -move_normal_y, 0)) | |
601 | new_normals.extend((0, 0, -1)) | |
602 | new_normals.extend((move_normal_x, move_normal_y, 0)) | |
46 | 603 | first = vertex_k // 3 |
604 | new_indices = triangulate_rectangle(first, first + 1, | |
605 | first + 2, first + 3) | |
606 | ||
607 | next_move = get_next_move(model_data, layer_idx, gline_idx) | |
608 | next_is_extruding = next_move and next_move.extruding | |
609 | if not next_is_extruding: | |
610 | # Compute caps and link everything | |
611 | p1x = current_pos[0] - path_halfwidth * move_normal_x | |
612 | p2x = current_pos[0] + path_halfwidth * move_normal_x | |
613 | p1y = current_pos[1] - path_halfwidth * move_normal_y | |
614 | p2y = current_pos[1] + path_halfwidth * move_normal_y | |
615 | new_vertices.extend((current_pos[0], current_pos[1], current_pos[2] + path_halfheight)) | |
616 | new_vertices.extend((p1x, p1y, current_pos[2])) | |
617 | new_vertices.extend((current_pos[0], current_pos[1], current_pos[2] - path_halfheight)) | |
618 | new_vertices.extend((p2x, p2y, current_pos[2])) | |
15 | 619 | new_normals.extend((0, 0, 1)) |
46 | 620 | new_normals.extend((-move_normal_x, -move_normal_y, 0)) |
15 | 621 | new_normals.extend((0, 0, -1)) |
46 | 622 | new_normals.extend((move_normal_x, move_normal_y, 0)) |
623 | end_first = vertex_k // 3 + len(new_vertices) // 3 - 4 | |
624 | new_indices += triangulate_rectangle(end_first + 3, end_first + 2, | |
625 | end_first + 1, end_first) | |
626 | new_indices += triangulate_box(first, first + 1, | |
627 | first + 2, first + 3, | |
628 | end_first, end_first + 1, | |
629 | end_first + 2, end_first + 3) | |
15 | 630 | |
46 | 631 | if self.indices.size < (index_k + len(new_indices) + 100 * indicesperline): |
632 | # arc interpolation extra points allocation | |
633 | ratio = (index_k + len(new_indices) + 100 * indicesperline) / self.indices.size * 1.5 | |
634 | # print(f"gl realloc print {self.vertices.size} -> {int(self.vertices.size * ratio)}") | |
635 | self.vertices.resize(int(self.vertices.size * ratio), refcheck = False) | |
636 | self.colors.resize(int(self.colors.size * ratio), refcheck = False) | |
637 | self.normals.resize(int(self.normals.size * ratio), refcheck = False) | |
638 | self.indices.resize(int(self.indices.size * ratio), refcheck = False) | |
639 | ||
640 | for new_i, item in enumerate(new_indices): | |
641 | indices[index_k + new_i] = item | |
642 | index_k += len(new_indices) | |
15 | 643 | |
46 | 644 | new_vertices_len = len(new_vertices) |
645 | vertices[vertex_k:vertex_k+new_vertices_len] = new_vertices | |
646 | normals[vertex_k:vertex_k+new_vertices_len] = new_normals | |
647 | vertex_k += new_vertices_len | |
15 | 648 | |
46 | 649 | new_vertices_count = new_vertices_len//coordspervertex |
650 | # settings support alpha (transperancy), but it is ignored here | |
651 | gline_color = self.movement_color(gline)[:buffered_color_len] | |
652 | for vi in range(new_vertices_count): | |
653 | colors[color_k:color_k+buffered_color_len] = gline_color | |
654 | color_k += buffered_color_len | |
15 | 655 | |
46 | 656 | prev_move_normal_x = move_normal_x |
657 | prev_move_normal_y = move_normal_y | |
658 | prev_move_angle = move_angle | |
659 | ||
660 | prev_pos = current_pos | |
661 | prev_gline = gline | |
662 | count_travel_indices.append(travel_vertex_k // 3) | |
15 | 663 | count_print_indices.append(index_k) |
46 | 664 | count_print_vertices.append(vertex_k // 3) |
15 | 665 | gline.gcview_end_vertex = len(count_print_indices) - 1 |
666 | ||
667 | if has_movement: | |
668 | self.layer_stops.append(len(count_print_indices) - 1) | |
669 | self.layer_idxs_map[layer_idx] = len(self.layer_stops) - 1 | |
670 | self.max_layers = len(self.layer_stops) - 1 | |
671 | self.num_layers_to_draw = self.max_layers + 1 | |
672 | self.initialized = False | |
673 | self.loaded = True | |
674 | ||
675 | processed_lines += len(layer) | |
676 | ||
677 | if callback: | |
678 | callback(layer_idx + 1) | |
679 | ||
680 | yield layer_idx | |
681 | layer_idx += 1 | |
682 | ||
683 | with self.lock: | |
684 | self.dims = ((model_data.xmin, model_data.xmax, model_data.width), | |
685 | (model_data.ymin, model_data.ymax, model_data.depth), | |
686 | (model_data.zmin, model_data.zmax, model_data.height)) | |
687 | ||
688 | self.travels.resize(travel_vertex_k, refcheck = False) | |
689 | self.vertices.resize(vertex_k, refcheck = False) | |
690 | self.colors.resize(color_k, refcheck = False) | |
46 | 691 | self.normals.resize(vertex_k, refcheck = False) |
15 | 692 | self.indices.resize(index_k, refcheck = False) |
693 | ||
694 | self.layer_stops = array.array('L', self.layer_stops) | |
695 | self.count_travel_indices = array.array('L', count_travel_indices) | |
696 | self.count_print_indices = array.array('L', count_print_indices) | |
697 | self.count_print_vertices = array.array('L', count_print_vertices) | |
698 | ||
699 | self.max_layers = len(self.layer_stops) - 1 | |
700 | self.num_layers_to_draw = self.max_layers + 1 | |
701 | self.loaded = True | |
702 | self.initialized = False | |
703 | self.loaded = True | |
704 | self.fully_loaded = True | |
705 | ||
706 | t_end = time.time() | |
707 | ||
708 | logging.debug(_('Initialized 3D visualization in %.2f seconds') % (t_end - t_start)) | |
46 | 709 | logging.debug(_('Vertex count: %d') % ((len(self.vertices) + len(self.travels)) // 3)) |
15 | 710 | yield None |
711 | ||
712 | def copy(self): | |
713 | copy = GcodeModel() | |
714 | for var in ["vertices", "colors", "travels", "indices", "normals", | |
715 | "max_layers", "num_layers_to_draw", "printed_until", | |
716 | "layer_stops", "dims", "only_current", | |
717 | "layer_idxs_map", "count_travel_indices", | |
718 | "count_print_indices", "count_print_vertices", | |
719 | "path_halfwidth", "path_halfheight", | |
720 | "gcode"]: | |
721 | setattr(copy, var, getattr(self, var)) | |
722 | copy.loaded = True | |
723 | copy.fully_loaded = True | |
724 | copy.initialized = False | |
725 | return copy | |
726 | ||
46 | 727 | def update_colors(self): |
728 | """Rebuild gl color buffer without loading. Used after color settings edit""" | |
729 | ncoords = self.count_print_vertices[-1] | |
730 | colors = numpy.empty(ncoords*3, dtype = GLfloat) | |
731 | cur_vertex = 0 | |
732 | gline_i = 1 | |
733 | for gline in self.gcode.lines: | |
734 | if gline.gcview_end_vertex: | |
735 | gline_color = self.movement_color(gline)[:3] | |
736 | last_vertex = self.count_print_vertices[gline_i] | |
737 | gline_i += 1 | |
738 | while cur_vertex < last_vertex: | |
739 | colors[cur_vertex*3:cur_vertex*3+3] = gline_color | |
740 | cur_vertex += 1 | |
741 | if self.vertex_color_buffer: | |
742 | self.vertex_color_buffer.delete() | |
743 | self.vertex_color_buffer = numpy2vbo(colors, use_vbos = self.use_vbos) | |
744 | ||
15 | 745 | # ------------------------------------------------------------------------ |
746 | # DRAWING | |
747 | # ------------------------------------------------------------------------ | |
748 | ||
749 | def init(self): | |
750 | with self.lock: | |
751 | self.layers_loaded = self.max_layers | |
752 | self.initialized = True | |
753 | if self.buffers_created: | |
754 | self.travel_buffer.delete() | |
755 | self.index_buffer.delete() | |
756 | self.vertex_buffer.delete() | |
757 | self.vertex_color_buffer.delete() | |
758 | self.vertex_normal_buffer.delete() | |
759 | self.travel_buffer = numpy2vbo(self.travels, use_vbos = self.use_vbos) | |
760 | self.index_buffer = numpy2vbo(self.indices, use_vbos = self.use_vbos, | |
761 | target = GL_ELEMENT_ARRAY_BUFFER) | |
762 | self.vertex_buffer = numpy2vbo(self.vertices, use_vbos = self.use_vbos) | |
763 | self.vertex_color_buffer = numpy2vbo(self.colors, use_vbos = self.use_vbos) | |
764 | self.vertex_normal_buffer = numpy2vbo(self.normals, use_vbos = self.use_vbos) | |
765 | if self.fully_loaded: | |
766 | # Delete numpy arrays after creating VBOs after full load | |
767 | self.travels = None | |
768 | self.indices = None | |
769 | self.vertices = None | |
770 | self.colors = None | |
771 | self.normals = None | |
772 | self.buffers_created = True | |
773 | ||
774 | def display(self, mode_2d=False): | |
775 | with self.lock: | |
776 | glPushMatrix() | |
777 | glTranslatef(self.offset_x, self.offset_y, 0) | |
778 | glEnableClientState(GL_VERTEX_ARRAY) | |
779 | ||
780 | has_vbo = isinstance(self.vertex_buffer, VertexBufferObject) | |
781 | if self.display_travels: | |
782 | self._display_travels(has_vbo) | |
783 | ||
784 | glEnable(GL_LIGHTING) | |
785 | glEnableClientState(GL_NORMAL_ARRAY) | |
786 | glEnableClientState(GL_COLOR_ARRAY) | |
787 | glMaterialfv(GL_FRONT, GL_SPECULAR, vec(1, 1, 1, 1)) | |
788 | glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, vec(0, 0, 0, 0)) | |
789 | glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 50) | |
790 | ||
791 | glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE) | |
792 | self._display_movements(has_vbo) | |
793 | ||
794 | glDisable(GL_LIGHTING) | |
795 | ||
796 | glDisableClientState(GL_COLOR_ARRAY) | |
797 | glDisableClientState(GL_VERTEX_ARRAY) | |
798 | glDisableClientState(GL_NORMAL_ARRAY) | |
799 | ||
800 | glPopMatrix() | |
801 | ||
802 | def _display_travels(self, has_vbo): | |
803 | self.travel_buffer.bind() | |
804 | glVertexPointer(3, GL_FLOAT, 0, self.travel_buffer.ptr) | |
805 | ||
806 | # Prevent race condition by using the number of currently loaded layers | |
807 | max_layers = self.layers_loaded | |
808 | # TODO: show current layer travels in a different color | |
809 | end = self.layer_stops[min(self.num_layers_to_draw, max_layers)] | |
810 | end_index = self.count_travel_indices[end] | |
811 | glColor4f(*self.color_travel) | |
812 | if self.only_current: | |
813 | if self.num_layers_to_draw < max_layers: | |
814 | end_prev_layer = self.layer_stops[self.num_layers_to_draw - 1] | |
815 | start_index = self.count_travel_indices[end_prev_layer + 1] | |
816 | glDrawArrays(GL_LINES, start_index, end_index - start_index + 1) | |
817 | else: | |
818 | glDrawArrays(GL_LINES, 0, end_index) | |
819 | ||
820 | self.travel_buffer.unbind() | |
821 | ||
822 | def _draw_elements(self, start, end, draw_type = GL_TRIANGLES): | |
823 | # Don't attempt printing empty layer | |
824 | if self.count_print_indices[end] == self.count_print_indices[start - 1]: | |
825 | return | |
826 | glDrawRangeElements(draw_type, | |
827 | self.count_print_vertices[start - 1], | |
828 | self.count_print_vertices[end] - 1, | |
829 | self.count_print_indices[end] - self.count_print_indices[start - 1], | |
830 | GL_UNSIGNED_INT, | |
831 | sizeof(GLuint) * self.count_print_indices[start - 1]) | |
832 | ||
833 | def _display_movements(self, has_vbo): | |
834 | self.vertex_buffer.bind() | |
835 | glVertexPointer(3, GL_FLOAT, 0, self.vertex_buffer.ptr) | |
836 | ||
837 | self.vertex_color_buffer.bind() | |
838 | glColorPointer(3, GL_FLOAT, 0, self.vertex_color_buffer.ptr) | |
839 | ||
840 | self.vertex_normal_buffer.bind() | |
841 | glNormalPointer(GL_FLOAT, 0, self.vertex_normal_buffer.ptr) | |
842 | ||
843 | self.index_buffer.bind() | |
844 | ||
845 | # Prevent race condition by using the number of currently loaded layers | |
846 | max_layers = self.layers_loaded | |
847 | ||
848 | start = 1 | |
849 | layer_selected = self.num_layers_to_draw <= max_layers | |
850 | if layer_selected: | |
851 | end_prev_layer = self.layer_stops[self.num_layers_to_draw - 1] | |
852 | else: | |
853 | end_prev_layer = 0 | |
854 | end = self.layer_stops[min(self.num_layers_to_draw, max_layers)] | |
855 | ||
856 | glDisableClientState(GL_COLOR_ARRAY) | |
857 | ||
858 | glColor3f(*self.color_printed[:-1]) | |
859 | ||
860 | # Draw printed stuff until end or end_prev_layer | |
861 | cur_end = min(self.printed_until, end) | |
862 | if not self.only_current: | |
863 | if 1 <= end_prev_layer <= cur_end: | |
864 | self._draw_elements(1, end_prev_layer) | |
865 | elif cur_end >= 1: | |
866 | self._draw_elements(1, cur_end) | |
867 | ||
868 | glEnableClientState(GL_COLOR_ARRAY) | |
869 | ||
870 | # Draw nonprinted stuff until end_prev_layer | |
871 | start = max(cur_end, 1) | |
872 | if end_prev_layer >= start: | |
873 | if not self.only_current: | |
874 | self._draw_elements(start, end_prev_layer) | |
875 | cur_end = end_prev_layer | |
876 | ||
877 | # Draw current layer | |
878 | if layer_selected: | |
879 | glDisableClientState(GL_COLOR_ARRAY) | |
880 | ||
881 | glColor3f(*self.color_current_printed[:-1]) | |
882 | ||
883 | if cur_end > end_prev_layer: | |
884 | self._draw_elements(end_prev_layer + 1, cur_end) | |
885 | ||
886 | glColor3f(*self.color_current[:-1]) | |
887 | ||
888 | if end > cur_end: | |
889 | self._draw_elements(cur_end + 1, end) | |
890 | ||
891 | glEnableClientState(GL_COLOR_ARRAY) | |
892 | ||
893 | # Draw non printed stuff until end (if not ending at a given layer) | |
894 | start = max(self.printed_until, 1) | |
895 | if not layer_selected and end >= start: | |
896 | self._draw_elements(start, end) | |
897 | ||
898 | self.index_buffer.unbind() | |
899 | self.vertex_buffer.unbind() | |
900 | self.vertex_color_buffer.unbind() | |
901 | self.vertex_normal_buffer.unbind() | |
902 | ||
903 | class GcodeModelLight(Model): | |
904 | """ | |
905 | Model for displaying Gcode data. | |
906 | """ | |
907 | ||
908 | color_travel = (0.6, 0.6, 0.6, 0.6) | |
909 | color_tool0 = (1.0, 0.0, 0.0, 0.6) | |
910 | color_tool1 = (0.67, 0.05, 0.9, 0.6) | |
911 | color_tool2 = (1.0, 0.8, 0., 0.6) | |
912 | color_tool3 = (1.0, 0., 0.62, 0.6) | |
913 | color_tool4 = (0., 1.0, 0.58, 0.6) | |
914 | color_printed = (0.2, 0.75, 0, 0.6) | |
915 | color_current = (0, 0.9, 1.0, 0.8) | |
916 | color_current_printed = (0.1, 0.4, 0, 0.8) | |
917 | ||
918 | buffers_created = False | |
919 | use_vbos = True | |
920 | loaded = False | |
921 | fully_loaded = False | |
922 | ||
923 | gcode = None | |
924 | ||
925 | def load_data(self, model_data, callback=None): | |
926 | t_start = time.time() | |
927 | self.gcode = model_data | |
928 | ||
929 | self.layer_idxs_map = {} | |
930 | self.layer_stops = [0] | |
931 | ||
932 | prev_pos = (0, 0, 0) | |
933 | layer_idx = 0 | |
934 | nlines = len(model_data) | |
935 | vertices = self.vertices = numpy.zeros(nlines * 6, dtype = GLfloat) | |
936 | vertex_k = 0 | |
937 | colors = self.colors = numpy.zeros(nlines * 8, dtype = GLfloat) | |
938 | color_k = 0 | |
939 | self.printed_until = -1 | |
940 | self.only_current = False | |
46 | 941 | prev_gline = None |
15 | 942 | while layer_idx < len(model_data.all_layers): |
943 | with self.lock: | |
944 | nlines = len(model_data) | |
46 | 945 | if nlines * 6 > vertices.size: |
15 | 946 | self.vertices.resize(nlines * 6, refcheck = False) |
947 | self.colors.resize(nlines * 8, refcheck = False) | |
948 | layer = model_data.all_layers[layer_idx] | |
949 | has_movement = False | |
950 | for gline in layer: | |
951 | if not gline.is_move: | |
952 | continue | |
953 | if gline.x is None and gline.y is None and gline.z is None: | |
954 | continue | |
46 | 955 | |
15 | 956 | has_movement = True |
46 | 957 | for current_pos in interpolate_arcs(gline, prev_gline): |
958 | ||
959 | if self.vertices.size < (vertex_k + 100 * 6): | |
960 | # arc interpolation extra points allocation | |
961 | ratio = (vertex_k + 100 * 6) / self.vertices.size * 1.5 | |
962 | # print(f"gl realloc lite {self.vertices.size} -> {int(self.vertices.size * ratio)}") | |
963 | self.vertices.resize(int(self.vertices.size * ratio), refcheck = False) | |
964 | self.colors.resize(int(self.colors.size * ratio), refcheck = False) | |
965 | ||
15 | 966 | |
46 | 967 | vertices[vertex_k] = prev_pos[0] |
968 | vertices[vertex_k + 1] = prev_pos[1] | |
969 | vertices[vertex_k + 2] = prev_pos[2] | |
970 | vertices[vertex_k + 3] = current_pos[0] | |
971 | vertices[vertex_k + 4] = current_pos[1] | |
972 | vertices[vertex_k + 5] = current_pos[2] | |
973 | vertex_k += 6 | |
15 | 974 | |
46 | 975 | vertex_color = self.movement_color(gline) |
976 | colors[color_k] = vertex_color[0] | |
977 | colors[color_k + 1] = vertex_color[1] | |
978 | colors[color_k + 2] = vertex_color[2] | |
979 | colors[color_k + 3] = vertex_color[3] | |
980 | colors[color_k + 4] = vertex_color[0] | |
981 | colors[color_k + 5] = vertex_color[1] | |
982 | colors[color_k + 6] = vertex_color[2] | |
983 | colors[color_k + 7] = vertex_color[3] | |
984 | color_k += 8 | |
985 | ||
986 | prev_pos = current_pos | |
987 | prev_gline = gline | |
988 | gline.gcview_end_vertex = vertex_k // 3 | |
15 | 989 | |
990 | if has_movement: | |
46 | 991 | self.layer_stops.append(vertex_k // 3) |
15 | 992 | self.layer_idxs_map[layer_idx] = len(self.layer_stops) - 1 |
993 | self.max_layers = len(self.layer_stops) - 1 | |
994 | self.num_layers_to_draw = self.max_layers + 1 | |
995 | self.initialized = False | |
996 | self.loaded = True | |
997 | ||
998 | if callback: | |
999 | callback(layer_idx + 1) | |
1000 | ||
1001 | yield layer_idx | |
1002 | layer_idx += 1 | |
1003 | ||
1004 | with self.lock: | |
1005 | self.dims = ((model_data.xmin, model_data.xmax, model_data.width), | |
1006 | (model_data.ymin, model_data.ymax, model_data.depth), | |
1007 | (model_data.zmin, model_data.zmax, model_data.height)) | |
1008 | ||
1009 | self.vertices.resize(vertex_k, refcheck = False) | |
1010 | self.colors.resize(color_k, refcheck = False) | |
1011 | self.max_layers = len(self.layer_stops) - 1 | |
1012 | self.num_layers_to_draw = self.max_layers + 1 | |
1013 | self.initialized = False | |
1014 | self.loaded = True | |
1015 | self.fully_loaded = True | |
1016 | ||
1017 | t_end = time.time() | |
1018 | ||
1019 | logging.debug(_('Initialized 3D visualization in %.2f seconds') % (t_end - t_start)) | |
46 | 1020 | logging.debug(_('Vertex count: %d') % (len(self.vertices) // 3)) |
15 | 1021 | yield None |
1022 | ||
1023 | def copy(self): | |
1024 | copy = GcodeModelLight() | |
1025 | for var in ["vertices", "colors", "max_layers", | |
1026 | "num_layers_to_draw", "printed_until", | |
1027 | "layer_stops", "dims", "only_current", | |
1028 | "layer_idxs_map", "gcode"]: | |
1029 | setattr(copy, var, getattr(self, var)) | |
1030 | copy.loaded = True | |
1031 | copy.fully_loaded = True | |
1032 | copy.initialized = False | |
1033 | return copy | |
1034 | ||
1035 | # ------------------------------------------------------------------------ | |
1036 | # DRAWING | |
1037 | # ------------------------------------------------------------------------ | |
1038 | ||
1039 | def init(self): | |
1040 | with self.lock: | |
1041 | self.layers_loaded = self.max_layers | |
1042 | self.initialized = True | |
1043 | if self.buffers_created: | |
1044 | self.vertex_buffer.delete() | |
1045 | self.vertex_color_buffer.delete() | |
1046 | self.vertex_buffer = numpy2vbo(self.vertices, use_vbos = self.use_vbos) | |
1047 | self.vertex_color_buffer = numpy2vbo(self.colors, use_vbos = self.use_vbos) # each pair of vertices shares the color | |
1048 | if self.fully_loaded: | |
1049 | # Delete numpy arrays after creating VBOs after full load | |
1050 | self.vertices = None | |
1051 | self.colors = None | |
1052 | self.buffers_created = True | |
1053 | ||
1054 | def display(self, mode_2d=False): | |
1055 | with self.lock: | |
1056 | glPushMatrix() | |
1057 | glTranslatef(self.offset_x, self.offset_y, 0) | |
1058 | glEnableClientState(GL_VERTEX_ARRAY) | |
1059 | glEnableClientState(GL_COLOR_ARRAY) | |
1060 | ||
1061 | self._display_movements(mode_2d) | |
1062 | ||
1063 | glDisableClientState(GL_COLOR_ARRAY) | |
1064 | glDisableClientState(GL_VERTEX_ARRAY) | |
1065 | glPopMatrix() | |
1066 | ||
1067 | def _display_movements(self, mode_2d=False): | |
1068 | self.vertex_buffer.bind() | |
1069 | has_vbo = isinstance(self.vertex_buffer, VertexBufferObject) | |
1070 | if has_vbo: | |
1071 | glVertexPointer(3, GL_FLOAT, 0, None) | |
1072 | else: | |
1073 | glVertexPointer(3, GL_FLOAT, 0, self.vertex_buffer.ptr) | |
1074 | ||
1075 | self.vertex_color_buffer.bind() | |
1076 | if has_vbo: | |
1077 | glColorPointer(4, GL_FLOAT, 0, None) | |
1078 | else: | |
1079 | glColorPointer(4, GL_FLOAT, 0, self.vertex_color_buffer.ptr) | |
1080 | ||
1081 | # Prevent race condition by using the number of currently loaded layers | |
1082 | max_layers = self.layers_loaded | |
1083 | ||
1084 | start = 0 | |
1085 | if self.num_layers_to_draw <= max_layers: | |
1086 | end_prev_layer = self.layer_stops[self.num_layers_to_draw - 1] | |
1087 | else: | |
1088 | end_prev_layer = -1 | |
1089 | end = self.layer_stops[min(self.num_layers_to_draw, max_layers)] | |
1090 | ||
1091 | glDisableClientState(GL_COLOR_ARRAY) | |
1092 | ||
1093 | glColor4f(*self.color_printed) | |
1094 | ||
1095 | # Draw printed stuff until end or end_prev_layer | |
1096 | cur_end = min(self.printed_until, end) | |
1097 | if not self.only_current: | |
1098 | if 0 <= end_prev_layer <= cur_end: | |
1099 | glDrawArrays(GL_LINES, start, end_prev_layer) | |
1100 | elif cur_end >= 0: | |
1101 | glDrawArrays(GL_LINES, start, cur_end) | |
1102 | ||
1103 | glEnableClientState(GL_COLOR_ARRAY) | |
1104 | ||
1105 | # Draw nonprinted stuff until end_prev_layer | |
1106 | start = max(cur_end, 0) | |
1107 | if end_prev_layer >= start: | |
1108 | if not self.only_current: | |
1109 | glDrawArrays(GL_LINES, start, end_prev_layer - start) | |
1110 | cur_end = end_prev_layer | |
1111 | ||
1112 | # Draw current layer | |
1113 | if end_prev_layer >= 0: | |
1114 | glDisableClientState(GL_COLOR_ARRAY) | |
1115 | ||
1116 | # Backup & increase line width | |
1117 | orig_linewidth = (GLfloat)() | |
1118 | glGetFloatv(GL_LINE_WIDTH, orig_linewidth) | |
1119 | glLineWidth(2.0) | |
1120 | ||
1121 | glColor4f(*self.color_current_printed) | |
1122 | ||
1123 | if cur_end > end_prev_layer: | |
1124 | glDrawArrays(GL_LINES, end_prev_layer, cur_end - end_prev_layer) | |
1125 | ||
1126 | glColor4f(*self.color_current) | |
1127 | ||
1128 | if end > cur_end: | |
1129 | glDrawArrays(GL_LINES, cur_end, end - cur_end) | |
1130 | ||
1131 | # Restore line width | |
1132 | glLineWidth(orig_linewidth) | |
1133 | ||
1134 | glEnableClientState(GL_COLOR_ARRAY) | |
1135 | ||
1136 | # Draw non printed stuff until end (if not ending at a given layer) | |
1137 | start = max(self.printed_until, 0) | |
1138 | end = end - start | |
1139 | if end_prev_layer < 0 and end > 0 and not self.only_current: | |
1140 | glDrawArrays(GL_LINES, start, end) | |
1141 | ||
1142 | self.vertex_buffer.unbind() | |
1143 | self.vertex_color_buffer.unbind() |