printrun-src/printrun/gl/libtatlin/actors.py

changeset 15
0bbb006204fc
child 46
cce0af6351f0
equal deleted inserted replaced
14:51bf56ba3c10 15:0bbb006204fc
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
68 class BoundingBox(object):
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
92 class Platform(object):
93 """
94 Platform on which models are placed.
95 """
96 graduations_major = 10
97
98 def __init__(self, build_dimensions, light = False, circular = False):
99 self.light = light
100 self.circular = circular
101 self.width = build_dimensions[0]
102 self.depth = build_dimensions[1]
103 self.height = build_dimensions[2]
104 self.xoffset = build_dimensions[3]
105 self.yoffset = build_dimensions[4]
106 self.zoffset = build_dimensions[5]
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):
125 if i % self.graduations_major == 0:
126 glColor4f(*self.color_grads_major)
127 elif i % (self.graduations_major / 2) == 0:
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
137 for i in range(0, int(math.ceil(self.width + 1))):
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
144 for i in range(0, int(math.ceil(self.depth + 1))):
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
151 for i in range(0, int(math.ceil(self.width + 1))):
152 if color(i):
153 glVertex3f(float(i), 0.0, 0.0)
154 glVertex3f(float(i), self.depth, 0.0)
155
156 for i in range(0, int(math.ceil(self.depth + 1))):
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
177 class PrintHead(object):
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
212 class Model(object):
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
318 class GcodeModel(Model):
319 """
320 Model for displaying Gcode data.
321 """
322
323 color_travel = (0.6, 0.6, 0.6, 0.6)
324 color_tool0 = (1.0, 0.0, 0.0, 1.0)
325 color_tool1 = (0.67, 0.05, 0.9, 1.0)
326 color_tool2 = (1.0, 0.8, 0., 1.0)
327 color_tool3 = (1.0, 0., 0.62, 1.0)
328 color_tool4 = (0., 1.0, 0.58, 1.0)
329 color_printed = (0.2, 0.75, 0, 1.0)
330 color_current = (0, 0.9, 1.0, 1.0)
331 color_current_printed = (0.1, 0.4, 0, 1.0)
332
333 display_travels = True
334
335 buffers_created = False
336 use_vbos = True
337 loaded = False
338 fully_loaded = False
339
340 gcode = None
341
342 path_halfwidth = 0.2
343 path_halfheight = 0.2
344
345 def set_path_size(self, path_halfwidth, path_halfheight):
346 with self.lock:
347 self.path_halfwidth = path_halfwidth
348 self.path_halfheight = path_halfheight
349
350 def load_data(self, model_data, callback=None):
351 t_start = time.time()
352 self.gcode = model_data
353
354 self.count_travel_indices = count_travel_indices = [0]
355 self.count_print_indices = count_print_indices = [0]
356 self.count_print_vertices = count_print_vertices = [0]
357
358 # Some trivial computations, but that's mostly for documentation :)
359 # Not like 10 multiplications are going to cost much time vs what's
360 # about to happen :)
361
362 # Max number of values which can be generated per gline
363 # to store coordinates/colors/normals.
364 # Nicely enough we have 3 per kind of thing for all kinds.
365 coordspervertex = 3
366 verticesperline = 8
367 coordsperline = coordspervertex * verticesperline
368 coords_count = lambda nlines: nlines * coordsperline
369
370 travelverticesperline = 2
371 travelcoordsperline = coordspervertex * travelverticesperline
372 travel_coords_count = lambda nlines: nlines * travelcoordsperline
373
374 trianglesperface = 2
375 facesperbox = 4
376 trianglesperbox = trianglesperface * facesperbox
377 verticespertriangle = 3
378 indicesperbox = verticespertriangle * trianglesperbox
379 boxperline = 2
380 indicesperline = indicesperbox * boxperline
381 indices_count = lambda nlines: nlines * indicesperline
382
383 nlines = len(model_data)
384 ntravelcoords = travel_coords_count(nlines)
385 ncoords = coords_count(nlines)
386 nindices = indices_count(nlines)
387 travel_vertices = self.travels = numpy.zeros(ntravelcoords, dtype = GLfloat)
388 travel_vertex_k = 0
389 vertices = self.vertices = numpy.zeros(ncoords, dtype = GLfloat)
390 vertex_k = 0
391 colors = self.colors = numpy.zeros(ncoords, dtype = GLfloat)
392 color_k = 0
393 normals = self.normals = numpy.zeros(ncoords, dtype = GLfloat)
394 normal_k = 0
395 indices = self.indices = numpy.zeros(nindices, dtype = GLuint)
396 index_k = 0
397 self.layer_idxs_map = {}
398 self.layer_stops = [0]
399
400 prev_is_extruding = False
401 prev_move_normal_x = None
402 prev_move_normal_y = None
403 prev_move_angle = None
404
405 prev_pos = (0, 0, 0)
406 layer_idx = 0
407
408 self.printed_until = 0
409 self.only_current = False
410
411 twopi = 2 * math.pi
412
413 processed_lines = 0
414
415 while layer_idx < len(model_data.all_layers):
416 with self.lock:
417 nlines = len(model_data)
418 remaining_lines = nlines - processed_lines
419 # Only reallocate memory which might be needed, not memory
420 # for everything
421 ntravelcoords = coords_count(remaining_lines) + travel_vertex_k
422 ncoords = coords_count(remaining_lines) + vertex_k
423 nindices = indices_count(remaining_lines) + index_k
424 if ncoords > vertices.size:
425 self.travels.resize(ntravelcoords, refcheck = False)
426 self.vertices.resize(ncoords, refcheck = False)
427 self.colors.resize(ncoords, refcheck = False)
428 self.normals.resize(ncoords, refcheck = False)
429 self.indices.resize(nindices, refcheck = False)
430 layer = model_data.all_layers[layer_idx]
431 has_movement = False
432 for gline_idx, gline in enumerate(layer):
433 if not gline.is_move:
434 continue
435 if gline.x is None and gline.y is None and gline.z is None:
436 continue
437 has_movement = True
438 current_pos = (gline.current_x, gline.current_y, gline.current_z)
439 if not gline.extruding:
440 travel_vertices[travel_vertex_k] = prev_pos[0]
441 travel_vertices[travel_vertex_k + 1] = prev_pos[1]
442 travel_vertices[travel_vertex_k + 2] = prev_pos[2]
443 travel_vertices[travel_vertex_k + 3] = current_pos[0]
444 travel_vertices[travel_vertex_k + 4] = current_pos[1]
445 travel_vertices[travel_vertex_k + 5] = current_pos[2]
446 travel_vertex_k += 6
447 prev_is_extruding = False
448 else:
449 gline_color = self.movement_color(gline)
450
451 next_move = get_next_move(model_data, layer_idx, gline_idx)
452 next_is_extruding = (next_move.extruding
453 if next_move is not None else False)
454
455 delta_x = current_pos[0] - prev_pos[0]
456 delta_y = current_pos[1] - prev_pos[1]
457 norm = delta_x * delta_x + delta_y * delta_y
458 if norm == 0: # Don't draw anything if this move is Z+E only
459 continue
460 norm = math.sqrt(norm)
461 move_normal_x = - delta_y / norm
462 move_normal_y = delta_x / norm
463 move_angle = math.atan2(delta_y, delta_x)
464
465 # FIXME: compute these dynamically
466 path_halfwidth = self.path_halfwidth * 1.2
467 path_halfheight = self.path_halfheight * 1.2
468
469 new_indices = []
470 new_vertices = []
471 new_normals = []
472 if prev_is_extruding:
473 # Store previous vertices indices
474 prev_id = vertex_k / 3 - 4
475 avg_move_normal_x = (prev_move_normal_x + move_normal_x) / 2
476 avg_move_normal_y = (prev_move_normal_y + move_normal_y) / 2
477 norm = avg_move_normal_x * avg_move_normal_x + avg_move_normal_y * avg_move_normal_y
478 if norm == 0:
479 avg_move_normal_x = move_normal_x
480 avg_move_normal_y = move_normal_y
481 else:
482 norm = math.sqrt(norm)
483 avg_move_normal_x /= norm
484 avg_move_normal_y /= norm
485 delta_angle = move_angle - prev_move_angle
486 delta_angle = (delta_angle + twopi) % twopi
487 fact = abs(math.cos(delta_angle / 2))
488 # If move is turning too much, avoid creating a big peak
489 # by adding an intermediate box
490 if fact < 0.5:
491 # FIXME: It looks like there's some heavy code duplication here...
492 hw = path_halfwidth
493 p1x = prev_pos[0] - hw * prev_move_normal_x
494 p2x = prev_pos[0] + hw * prev_move_normal_x
495 p1y = prev_pos[1] - hw * prev_move_normal_y
496 p2y = prev_pos[1] + hw * prev_move_normal_y
497 new_vertices.extend((prev_pos[0], prev_pos[1], prev_pos[2] + path_halfheight))
498 new_vertices.extend((p1x, p1y, prev_pos[2]))
499 new_vertices.extend((prev_pos[0], prev_pos[1], prev_pos[2] - path_halfheight))
500 new_vertices.extend((p2x, p2y, prev_pos[2]))
501 new_normals.extend((0, 0, 1))
502 new_normals.extend((-prev_move_normal_x, -prev_move_normal_y, 0))
503 new_normals.extend((0, 0, -1))
504 new_normals.extend((prev_move_normal_x, prev_move_normal_y, 0))
505 first = vertex_k / 3
506 # Link to previous
507 new_indices += triangulate_box(prev_id, prev_id + 1,
508 prev_id + 2, prev_id + 3,
509 first, first + 1,
510 first + 2, first + 3)
511 p1x = prev_pos[0] - hw * move_normal_x
512 p2x = prev_pos[0] + hw * move_normal_x
513 p1y = prev_pos[1] - hw * move_normal_y
514 p2y = prev_pos[1] + hw * move_normal_y
515 new_vertices.extend((prev_pos[0], prev_pos[1], prev_pos[2] + path_halfheight))
516 new_vertices.extend((p1x, p1y, prev_pos[2]))
517 new_vertices.extend((prev_pos[0], prev_pos[1], prev_pos[2] - path_halfheight))
518 new_vertices.extend((p2x, p2y, prev_pos[2]))
519 new_normals.extend((0, 0, 1))
520 new_normals.extend((-move_normal_x, -move_normal_y, 0))
521 new_normals.extend((0, 0, -1))
522 new_normals.extend((move_normal_x, move_normal_y, 0))
523 prev_id += 4
524 first += 4
525 # Link to previous
526 new_indices += triangulate_box(prev_id, prev_id + 1,
527 prev_id + 2, prev_id + 3,
528 first, first + 1,
529 first + 2, first + 3)
530 else:
531 hw = path_halfwidth / fact
532 # Compute vertices
533 p1x = prev_pos[0] - hw * avg_move_normal_x
534 p2x = prev_pos[0] + hw * avg_move_normal_x
535 p1y = prev_pos[1] - hw * avg_move_normal_y
536 p2y = prev_pos[1] + hw * avg_move_normal_y
537 new_vertices.extend((prev_pos[0], prev_pos[1], prev_pos[2] + path_halfheight))
538 new_vertices.extend((p1x, p1y, prev_pos[2]))
539 new_vertices.extend((prev_pos[0], prev_pos[1], prev_pos[2] - path_halfheight))
540 new_vertices.extend((p2x, p2y, prev_pos[2]))
541 new_normals.extend((0, 0, 1))
542 new_normals.extend((-avg_move_normal_x, -avg_move_normal_y, 0))
543 new_normals.extend((0, 0, -1))
544 new_normals.extend((avg_move_normal_x, avg_move_normal_y, 0))
545 first = vertex_k / 3
546 # Link to previous
547 new_indices += triangulate_box(prev_id, prev_id + 1,
548 prev_id + 2, prev_id + 3,
549 first, first + 1,
550 first + 2, first + 3)
551 else:
552 # Compute vertices normal to the current move and cap it
553 p1x = prev_pos[0] - path_halfwidth * move_normal_x
554 p2x = prev_pos[0] + path_halfwidth * move_normal_x
555 p1y = prev_pos[1] - path_halfwidth * move_normal_y
556 p2y = prev_pos[1] + path_halfwidth * move_normal_y
557 new_vertices.extend((prev_pos[0], prev_pos[1], prev_pos[2] + path_halfheight))
558 new_vertices.extend((p1x, p1y, prev_pos[2]))
559 new_vertices.extend((prev_pos[0], prev_pos[1], prev_pos[2] - path_halfheight))
560 new_vertices.extend((p2x, p2y, prev_pos[2]))
561 new_normals.extend((0, 0, 1))
562 new_normals.extend((-move_normal_x, -move_normal_y, 0))
563 new_normals.extend((0, 0, -1))
564 new_normals.extend((move_normal_x, move_normal_y, 0))
565 first = vertex_k / 3
566 new_indices = triangulate_rectangle(first, first + 1,
567 first + 2, first + 3)
568
569 if not next_is_extruding:
570 # Compute caps and link everything
571 p1x = current_pos[0] - path_halfwidth * move_normal_x
572 p2x = current_pos[0] + path_halfwidth * move_normal_x
573 p1y = current_pos[1] - path_halfwidth * move_normal_y
574 p2y = current_pos[1] + path_halfwidth * move_normal_y
575 new_vertices.extend((current_pos[0], current_pos[1], current_pos[2] + path_halfheight))
576 new_vertices.extend((p1x, p1y, current_pos[2]))
577 new_vertices.extend((current_pos[0], current_pos[1], current_pos[2] - path_halfheight))
578 new_vertices.extend((p2x, p2y, current_pos[2]))
579 new_normals.extend((0, 0, 1))
580 new_normals.extend((-move_normal_x, -move_normal_y, 0))
581 new_normals.extend((0, 0, -1))
582 new_normals.extend((move_normal_x, move_normal_y, 0))
583 end_first = vertex_k / 3 + len(new_vertices) / 3 - 4
584 new_indices += triangulate_rectangle(end_first + 3, end_first + 2,
585 end_first + 1, end_first)
586 new_indices += triangulate_box(first, first + 1,
587 first + 2, first + 3,
588 end_first, end_first + 1,
589 end_first + 2, end_first + 3)
590
591 for new_i, item in enumerate(new_indices):
592 indices[index_k + new_i] = item
593 index_k += len(new_indices)
594 for new_i, item in enumerate(new_vertices):
595 vertices[vertex_k + new_i] = item
596 vertex_k += len(new_vertices)
597 for new_i, item in enumerate(new_normals):
598 normals[normal_k + new_i] = item
599 normal_k += len(new_normals)
600 new_colors = list(gline_color)[:-1] * (len(new_vertices) / 3)
601 for new_i, item in enumerate(new_colors):
602 colors[color_k + new_i] = item
603 color_k += len(new_colors)
604
605 prev_is_extruding = True
606 prev_move_normal_x = move_normal_x
607 prev_move_normal_y = move_normal_y
608 prev_move_angle = move_angle
609
610 prev_pos = current_pos
611 count_travel_indices.append(travel_vertex_k / 3)
612 count_print_indices.append(index_k)
613 count_print_vertices.append(vertex_k / 3)
614 gline.gcview_end_vertex = len(count_print_indices) - 1
615
616 if has_movement:
617 self.layer_stops.append(len(count_print_indices) - 1)
618 self.layer_idxs_map[layer_idx] = len(self.layer_stops) - 1
619 self.max_layers = len(self.layer_stops) - 1
620 self.num_layers_to_draw = self.max_layers + 1
621 self.initialized = False
622 self.loaded = True
623
624 processed_lines += len(layer)
625
626 if callback:
627 callback(layer_idx + 1)
628
629 yield layer_idx
630 layer_idx += 1
631
632 with self.lock:
633 self.dims = ((model_data.xmin, model_data.xmax, model_data.width),
634 (model_data.ymin, model_data.ymax, model_data.depth),
635 (model_data.zmin, model_data.zmax, model_data.height))
636
637 self.travels.resize(travel_vertex_k, refcheck = False)
638 self.vertices.resize(vertex_k, refcheck = False)
639 self.colors.resize(color_k, refcheck = False)
640 self.normals.resize(normal_k, refcheck = False)
641 self.indices.resize(index_k, refcheck = False)
642
643 self.layer_stops = array.array('L', self.layer_stops)
644 self.count_travel_indices = array.array('L', count_travel_indices)
645 self.count_print_indices = array.array('L', count_print_indices)
646 self.count_print_vertices = array.array('L', count_print_vertices)
647
648 self.max_layers = len(self.layer_stops) - 1
649 self.num_layers_to_draw = self.max_layers + 1
650 self.loaded = True
651 self.initialized = False
652 self.loaded = True
653 self.fully_loaded = True
654
655 t_end = time.time()
656
657 logging.debug(_('Initialized 3D visualization in %.2f seconds') % (t_end - t_start))
658 logging.debug(_('Vertex count: %d') % ((len(self.vertices) + len(self.travels)) / 3))
659 yield None
660
661 def copy(self):
662 copy = GcodeModel()
663 for var in ["vertices", "colors", "travels", "indices", "normals",
664 "max_layers", "num_layers_to_draw", "printed_until",
665 "layer_stops", "dims", "only_current",
666 "layer_idxs_map", "count_travel_indices",
667 "count_print_indices", "count_print_vertices",
668 "path_halfwidth", "path_halfheight",
669 "gcode"]:
670 setattr(copy, var, getattr(self, var))
671 copy.loaded = True
672 copy.fully_loaded = True
673 copy.initialized = False
674 return copy
675
676 # ------------------------------------------------------------------------
677 # DRAWING
678 # ------------------------------------------------------------------------
679
680 def init(self):
681 with self.lock:
682 self.layers_loaded = self.max_layers
683 self.initialized = True
684 if self.buffers_created:
685 self.travel_buffer.delete()
686 self.index_buffer.delete()
687 self.vertex_buffer.delete()
688 self.vertex_color_buffer.delete()
689 self.vertex_normal_buffer.delete()
690 self.travel_buffer = numpy2vbo(self.travels, use_vbos = self.use_vbos)
691 self.index_buffer = numpy2vbo(self.indices, use_vbos = self.use_vbos,
692 target = GL_ELEMENT_ARRAY_BUFFER)
693 self.vertex_buffer = numpy2vbo(self.vertices, use_vbos = self.use_vbos)
694 self.vertex_color_buffer = numpy2vbo(self.colors, use_vbos = self.use_vbos)
695 self.vertex_normal_buffer = numpy2vbo(self.normals, use_vbos = self.use_vbos)
696 if self.fully_loaded:
697 # Delete numpy arrays after creating VBOs after full load
698 self.travels = None
699 self.indices = None
700 self.vertices = None
701 self.colors = None
702 self.normals = None
703 self.buffers_created = True
704
705 def display(self, mode_2d=False):
706 with self.lock:
707 glPushMatrix()
708 glTranslatef(self.offset_x, self.offset_y, 0)
709 glEnableClientState(GL_VERTEX_ARRAY)
710
711 has_vbo = isinstance(self.vertex_buffer, VertexBufferObject)
712 if self.display_travels:
713 self._display_travels(has_vbo)
714
715 glEnable(GL_LIGHTING)
716 glEnableClientState(GL_NORMAL_ARRAY)
717 glEnableClientState(GL_COLOR_ARRAY)
718 glMaterialfv(GL_FRONT, GL_SPECULAR, vec(1, 1, 1, 1))
719 glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, vec(0, 0, 0, 0))
720 glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 50)
721
722 glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)
723 self._display_movements(has_vbo)
724
725 glDisable(GL_LIGHTING)
726
727 glDisableClientState(GL_COLOR_ARRAY)
728 glDisableClientState(GL_VERTEX_ARRAY)
729 glDisableClientState(GL_NORMAL_ARRAY)
730
731 glPopMatrix()
732
733 def _display_travels(self, has_vbo):
734 self.travel_buffer.bind()
735 glVertexPointer(3, GL_FLOAT, 0, self.travel_buffer.ptr)
736
737 # Prevent race condition by using the number of currently loaded layers
738 max_layers = self.layers_loaded
739 # TODO: show current layer travels in a different color
740 end = self.layer_stops[min(self.num_layers_to_draw, max_layers)]
741 end_index = self.count_travel_indices[end]
742 glColor4f(*self.color_travel)
743 if self.only_current:
744 if self.num_layers_to_draw < max_layers:
745 end_prev_layer = self.layer_stops[self.num_layers_to_draw - 1]
746 start_index = self.count_travel_indices[end_prev_layer + 1]
747 glDrawArrays(GL_LINES, start_index, end_index - start_index + 1)
748 else:
749 glDrawArrays(GL_LINES, 0, end_index)
750
751 self.travel_buffer.unbind()
752
753 def _draw_elements(self, start, end, draw_type = GL_TRIANGLES):
754 # Don't attempt printing empty layer
755 if self.count_print_indices[end] == self.count_print_indices[start - 1]:
756 return
757 glDrawRangeElements(draw_type,
758 self.count_print_vertices[start - 1],
759 self.count_print_vertices[end] - 1,
760 self.count_print_indices[end] - self.count_print_indices[start - 1],
761 GL_UNSIGNED_INT,
762 sizeof(GLuint) * self.count_print_indices[start - 1])
763
764 def _display_movements(self, has_vbo):
765 self.vertex_buffer.bind()
766 glVertexPointer(3, GL_FLOAT, 0, self.vertex_buffer.ptr)
767
768 self.vertex_color_buffer.bind()
769 glColorPointer(3, GL_FLOAT, 0, self.vertex_color_buffer.ptr)
770
771 self.vertex_normal_buffer.bind()
772 glNormalPointer(GL_FLOAT, 0, self.vertex_normal_buffer.ptr)
773
774 self.index_buffer.bind()
775
776 # Prevent race condition by using the number of currently loaded layers
777 max_layers = self.layers_loaded
778
779 start = 1
780 layer_selected = self.num_layers_to_draw <= max_layers
781 if layer_selected:
782 end_prev_layer = self.layer_stops[self.num_layers_to_draw - 1]
783 else:
784 end_prev_layer = 0
785 end = self.layer_stops[min(self.num_layers_to_draw, max_layers)]
786
787 glDisableClientState(GL_COLOR_ARRAY)
788
789 glColor3f(*self.color_printed[:-1])
790
791 # Draw printed stuff until end or end_prev_layer
792 cur_end = min(self.printed_until, end)
793 if not self.only_current:
794 if 1 <= end_prev_layer <= cur_end:
795 self._draw_elements(1, end_prev_layer)
796 elif cur_end >= 1:
797 self._draw_elements(1, cur_end)
798
799 glEnableClientState(GL_COLOR_ARRAY)
800
801 # Draw nonprinted stuff until end_prev_layer
802 start = max(cur_end, 1)
803 if end_prev_layer >= start:
804 if not self.only_current:
805 self._draw_elements(start, end_prev_layer)
806 cur_end = end_prev_layer
807
808 # Draw current layer
809 if layer_selected:
810 glDisableClientState(GL_COLOR_ARRAY)
811
812 glColor3f(*self.color_current_printed[:-1])
813
814 if cur_end > end_prev_layer:
815 self._draw_elements(end_prev_layer + 1, cur_end)
816
817 glColor3f(*self.color_current[:-1])
818
819 if end > cur_end:
820 self._draw_elements(cur_end + 1, end)
821
822 glEnableClientState(GL_COLOR_ARRAY)
823
824 # Draw non printed stuff until end (if not ending at a given layer)
825 start = max(self.printed_until, 1)
826 if not layer_selected and end >= start:
827 self._draw_elements(start, end)
828
829 self.index_buffer.unbind()
830 self.vertex_buffer.unbind()
831 self.vertex_color_buffer.unbind()
832 self.vertex_normal_buffer.unbind()
833
834 class GcodeModelLight(Model):
835 """
836 Model for displaying Gcode data.
837 """
838
839 color_travel = (0.6, 0.6, 0.6, 0.6)
840 color_tool0 = (1.0, 0.0, 0.0, 0.6)
841 color_tool1 = (0.67, 0.05, 0.9, 0.6)
842 color_tool2 = (1.0, 0.8, 0., 0.6)
843 color_tool3 = (1.0, 0., 0.62, 0.6)
844 color_tool4 = (0., 1.0, 0.58, 0.6)
845 color_printed = (0.2, 0.75, 0, 0.6)
846 color_current = (0, 0.9, 1.0, 0.8)
847 color_current_printed = (0.1, 0.4, 0, 0.8)
848
849 buffers_created = False
850 use_vbos = True
851 loaded = False
852 fully_loaded = False
853
854 gcode = None
855
856 def load_data(self, model_data, callback=None):
857 t_start = time.time()
858 self.gcode = model_data
859
860 self.layer_idxs_map = {}
861 self.layer_stops = [0]
862
863 prev_pos = (0, 0, 0)
864 layer_idx = 0
865 nlines = len(model_data)
866 vertices = self.vertices = numpy.zeros(nlines * 6, dtype = GLfloat)
867 vertex_k = 0
868 colors = self.colors = numpy.zeros(nlines * 8, dtype = GLfloat)
869 color_k = 0
870 self.printed_until = -1
871 self.only_current = False
872 while layer_idx < len(model_data.all_layers):
873 with self.lock:
874 nlines = len(model_data)
875 if nlines * 6 != vertices.size:
876 self.vertices.resize(nlines * 6, refcheck = False)
877 self.colors.resize(nlines * 8, refcheck = False)
878 layer = model_data.all_layers[layer_idx]
879 has_movement = False
880 for gline in layer:
881 if not gline.is_move:
882 continue
883 if gline.x is None and gline.y is None and gline.z is None:
884 continue
885 has_movement = True
886 vertices[vertex_k] = prev_pos[0]
887 vertices[vertex_k + 1] = prev_pos[1]
888 vertices[vertex_k + 2] = prev_pos[2]
889 current_pos = (gline.current_x, gline.current_y, gline.current_z)
890 vertices[vertex_k + 3] = current_pos[0]
891 vertices[vertex_k + 4] = current_pos[1]
892 vertices[vertex_k + 5] = current_pos[2]
893 vertex_k += 6
894
895 vertex_color = self.movement_color(gline)
896 colors[color_k] = vertex_color[0]
897 colors[color_k + 1] = vertex_color[1]
898 colors[color_k + 2] = vertex_color[2]
899 colors[color_k + 3] = vertex_color[3]
900 colors[color_k + 4] = vertex_color[0]
901 colors[color_k + 5] = vertex_color[1]
902 colors[color_k + 6] = vertex_color[2]
903 colors[color_k + 7] = vertex_color[3]
904 color_k += 8
905
906 prev_pos = current_pos
907 gline.gcview_end_vertex = vertex_k / 3
908
909 if has_movement:
910 self.layer_stops.append(vertex_k / 3)
911 self.layer_idxs_map[layer_idx] = len(self.layer_stops) - 1
912 self.max_layers = len(self.layer_stops) - 1
913 self.num_layers_to_draw = self.max_layers + 1
914 self.initialized = False
915 self.loaded = True
916
917 if callback:
918 callback(layer_idx + 1)
919
920 yield layer_idx
921 layer_idx += 1
922
923 with self.lock:
924 self.dims = ((model_data.xmin, model_data.xmax, model_data.width),
925 (model_data.ymin, model_data.ymax, model_data.depth),
926 (model_data.zmin, model_data.zmax, model_data.height))
927
928 self.vertices.resize(vertex_k, refcheck = False)
929 self.colors.resize(color_k, refcheck = False)
930 self.max_layers = len(self.layer_stops) - 1
931 self.num_layers_to_draw = self.max_layers + 1
932 self.initialized = False
933 self.loaded = True
934 self.fully_loaded = True
935
936 t_end = time.time()
937
938 logging.debug(_('Initialized 3D visualization in %.2f seconds') % (t_end - t_start))
939 logging.debug(_('Vertex count: %d') % (len(self.vertices) / 3))
940 yield None
941
942 def copy(self):
943 copy = GcodeModelLight()
944 for var in ["vertices", "colors", "max_layers",
945 "num_layers_to_draw", "printed_until",
946 "layer_stops", "dims", "only_current",
947 "layer_idxs_map", "gcode"]:
948 setattr(copy, var, getattr(self, var))
949 copy.loaded = True
950 copy.fully_loaded = True
951 copy.initialized = False
952 return copy
953
954 # ------------------------------------------------------------------------
955 # DRAWING
956 # ------------------------------------------------------------------------
957
958 def init(self):
959 with self.lock:
960 self.layers_loaded = self.max_layers
961 self.initialized = True
962 if self.buffers_created:
963 self.vertex_buffer.delete()
964 self.vertex_color_buffer.delete()
965 self.vertex_buffer = numpy2vbo(self.vertices, use_vbos = self.use_vbos)
966 self.vertex_color_buffer = numpy2vbo(self.colors, use_vbos = self.use_vbos) # each pair of vertices shares the color
967 if self.fully_loaded:
968 # Delete numpy arrays after creating VBOs after full load
969 self.vertices = None
970 self.colors = None
971 self.buffers_created = True
972
973 def display(self, mode_2d=False):
974 with self.lock:
975 glPushMatrix()
976 glTranslatef(self.offset_x, self.offset_y, 0)
977 glEnableClientState(GL_VERTEX_ARRAY)
978 glEnableClientState(GL_COLOR_ARRAY)
979
980 self._display_movements(mode_2d)
981
982 glDisableClientState(GL_COLOR_ARRAY)
983 glDisableClientState(GL_VERTEX_ARRAY)
984 glPopMatrix()
985
986 def _display_movements(self, mode_2d=False):
987 self.vertex_buffer.bind()
988 has_vbo = isinstance(self.vertex_buffer, VertexBufferObject)
989 if has_vbo:
990 glVertexPointer(3, GL_FLOAT, 0, None)
991 else:
992 glVertexPointer(3, GL_FLOAT, 0, self.vertex_buffer.ptr)
993
994 self.vertex_color_buffer.bind()
995 if has_vbo:
996 glColorPointer(4, GL_FLOAT, 0, None)
997 else:
998 glColorPointer(4, GL_FLOAT, 0, self.vertex_color_buffer.ptr)
999
1000 # Prevent race condition by using the number of currently loaded layers
1001 max_layers = self.layers_loaded
1002
1003 start = 0
1004 if self.num_layers_to_draw <= max_layers:
1005 end_prev_layer = self.layer_stops[self.num_layers_to_draw - 1]
1006 else:
1007 end_prev_layer = -1
1008 end = self.layer_stops[min(self.num_layers_to_draw, max_layers)]
1009
1010 glDisableClientState(GL_COLOR_ARRAY)
1011
1012 glColor4f(*self.color_printed)
1013
1014 # Draw printed stuff until end or end_prev_layer
1015 cur_end = min(self.printed_until, end)
1016 if not self.only_current:
1017 if 0 <= end_prev_layer <= cur_end:
1018 glDrawArrays(GL_LINES, start, end_prev_layer)
1019 elif cur_end >= 0:
1020 glDrawArrays(GL_LINES, start, cur_end)
1021
1022 glEnableClientState(GL_COLOR_ARRAY)
1023
1024 # Draw nonprinted stuff until end_prev_layer
1025 start = max(cur_end, 0)
1026 if end_prev_layer >= start:
1027 if not self.only_current:
1028 glDrawArrays(GL_LINES, start, end_prev_layer - start)
1029 cur_end = end_prev_layer
1030
1031 # Draw current layer
1032 if end_prev_layer >= 0:
1033 glDisableClientState(GL_COLOR_ARRAY)
1034
1035 # Backup & increase line width
1036 orig_linewidth = (GLfloat)()
1037 glGetFloatv(GL_LINE_WIDTH, orig_linewidth)
1038 glLineWidth(2.0)
1039
1040 glColor4f(*self.color_current_printed)
1041
1042 if cur_end > end_prev_layer:
1043 glDrawArrays(GL_LINES, end_prev_layer, cur_end - end_prev_layer)
1044
1045 glColor4f(*self.color_current)
1046
1047 if end > cur_end:
1048 glDrawArrays(GL_LINES, cur_end, end - cur_end)
1049
1050 # Restore line width
1051 glLineWidth(orig_linewidth)
1052
1053 glEnableClientState(GL_COLOR_ARRAY)
1054
1055 # Draw non printed stuff until end (if not ending at a given layer)
1056 start = max(self.printed_until, 0)
1057 end = end - start
1058 if end_prev_layer < 0 and end > 0 and not self.only_current:
1059 glDrawArrays(GL_LINES, start, end)
1060
1061 self.vertex_buffer.unbind()
1062 self.vertex_color_buffer.unbind()

mercurial