Tue, 19 Jan 2021 20:25:47 +0100
NeoCube laser cutting improvements
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 | ||
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() |