45:c82943fb205f | 46:cce0af6351f0 |
---|---|
63 def triangulate_box(i1, i2, i3, i4, | 63 def triangulate_box(i1, i2, i3, i4, |
64 j1, j2, j3, j4): | 64 j1, j2, j3, j4): |
65 return [i1, i2, j2, j2, j1, i1, i2, i3, j3, j3, j2, i2, | 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] | 66 i3, i4, j4, j4, j3, i3, i4, i1, j1, j1, j4, i4] |
67 | 67 |
68 class BoundingBox(object): | 68 class BoundingBox: |
69 """ | 69 """ |
70 A rectangular box (cuboid) enclosing a 3D model, defined by lower and upper corners. | 70 A rectangular box (cuboid) enclosing a 3D model, defined by lower and upper corners. |
71 """ | 71 """ |
72 def __init__(self, upper_corner, lower_corner): | 72 def __init__(self, upper_corner, lower_corner): |
73 self.upper_corner = upper_corner | 73 self.upper_corner = upper_corner |
87 def height(self): | 87 def height(self): |
88 height = abs(self.upper_corner[2] - self.lower_corner[2]) | 88 height = abs(self.upper_corner[2] - self.lower_corner[2]) |
89 return round(height, 2) | 89 return round(height, 2) |
90 | 90 |
91 | 91 |
92 class Platform(object): | 92 class Platform: |
93 """ | 93 """ |
94 Platform on which models are placed. | 94 Platform on which models are placed. |
95 """ | 95 """ |
96 graduations_major = 10 | 96 |
97 | 97 def __init__(self, build_dimensions, light = False, circular = False, grid = (1, 10)): |
98 def __init__(self, build_dimensions, light = False, circular = False): | |
99 self.light = light | 98 self.light = light |
100 self.circular = circular | 99 self.circular = circular |
101 self.width = build_dimensions[0] | 100 self.width = build_dimensions[0] |
102 self.depth = build_dimensions[1] | 101 self.depth = build_dimensions[1] |
103 self.height = build_dimensions[2] | 102 self.height = build_dimensions[2] |
104 self.xoffset = build_dimensions[3] | 103 self.xoffset = build_dimensions[3] |
105 self.yoffset = build_dimensions[4] | 104 self.yoffset = build_dimensions[4] |
106 self.zoffset = build_dimensions[5] | 105 self.zoffset = build_dimensions[5] |
106 self.grid = grid | |
107 | 107 |
108 self.color_grads_minor = (0xaf / 255, 0xdf / 255, 0x5f / 255, 0.1) | 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) | 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) | 110 self.color_grads_major = (0xaf / 255, 0xdf / 255, 0x5f / 255, 0.33) |
111 | 111 |
120 glPushMatrix() | 120 glPushMatrix() |
121 | 121 |
122 glTranslatef(self.xoffset, self.yoffset, self.zoffset) | 122 glTranslatef(self.xoffset, self.yoffset, self.zoffset) |
123 | 123 |
124 def color(i): | 124 def color(i): |
125 if i % self.graduations_major == 0: | 125 if i % self.grid[1] == 0: |
126 glColor4f(*self.color_grads_major) | 126 glColor4f(*self.color_grads_major) |
127 elif i % (self.graduations_major / 2) == 0: | 127 elif i % (self.grid[1] // 2) == 0: |
128 glColor4f(*self.color_grads_interm) | 128 glColor4f(*self.color_grads_interm) |
129 else: | 129 else: |
130 if self.light: return False | 130 if self.light: return False |
131 glColor4f(*self.color_grads_minor) | 131 glColor4f(*self.color_grads_minor) |
132 return True | 132 return True |
133 | 133 |
134 # draw the grid | 134 # draw the grid |
135 glBegin(GL_LINES) | 135 glBegin(GL_LINES) |
136 if self.circular: # Draw a circular grid | 136 if self.circular: # Draw a circular grid |
137 for i in range(0, int(math.ceil(self.width + 1))): | 137 for i in numpy.arange(0, int(math.ceil(self.width + 1)), self.grid[0]): |
138 angle = math.asin(2 * float(i) / self.width - 1) | 138 angle = math.asin(2 * float(i) / self.width - 1) |
139 x = (math.cos(angle) + 1) * self.depth / 2 | 139 x = (math.cos(angle) + 1) * self.depth / 2 |
140 if color(i): | 140 if color(i): |
141 glVertex3f(float(i), self.depth - x, 0.0) | 141 glVertex3f(float(i), self.depth - x, 0.0) |
142 glVertex3f(float(i), x, 0.0) | 142 glVertex3f(float(i), x, 0.0) |
143 | 143 |
144 for i in range(0, int(math.ceil(self.depth + 1))): | 144 for i in numpy.arange(0, int(math.ceil(self.depth + 1)), self.grid[0]): |
145 angle = math.acos(2 * float(i) / self.depth - 1) | 145 angle = math.acos(2 * float(i) / self.depth - 1) |
146 x = (math.sin(angle) + 1) * self.width / 2 | 146 x = (math.sin(angle) + 1) * self.width / 2 |
147 if color(i): | 147 if color(i): |
148 glVertex3f(self.width - x, float(i), 0.0) | 148 glVertex3f(self.width - x, float(i), 0.0) |
149 glVertex3f(x, float(i), 0.0) | 149 glVertex3f(x, float(i), 0.0) |
150 else: # Draw a rectangular grid | 150 else: # Draw a rectangular grid |
151 for i in range(0, int(math.ceil(self.width + 1))): | 151 for i in numpy.arange(0, int(math.ceil(self.width + 1)), self.grid[0]): |
152 if color(i): | 152 if color(i): |
153 glVertex3f(float(i), 0.0, 0.0) | 153 glVertex3f(float(i), 0.0, 0.0) |
154 glVertex3f(float(i), self.depth, 0.0) | 154 glVertex3f(float(i), self.depth, 0.0) |
155 | 155 |
156 for i in range(0, int(math.ceil(self.depth + 1))): | 156 for i in numpy.arange(0, int(math.ceil(self.depth + 1)), self.grid[0]): |
157 if color(i): | 157 if color(i): |
158 glVertex3f(0, float(i), 0.0) | 158 glVertex3f(0, float(i), 0.0) |
159 glVertex3f(self.width, float(i), 0.0) | 159 glVertex3f(self.width, float(i), 0.0) |
160 glEnd() | 160 glEnd() |
161 | 161 |
172 def display(self, mode_2d=False): | 172 def display(self, mode_2d=False): |
173 # FIXME: using the list sometimes results in graphical corruptions | 173 # FIXME: using the list sometimes results in graphical corruptions |
174 # glCallList(self.display_list) | 174 # glCallList(self.display_list) |
175 self.draw() | 175 self.draw() |
176 | 176 |
177 class PrintHead(object): | 177 class PrintHead: |
178 def __init__(self): | 178 def __init__(self): |
179 self.color = (43. / 255, 0., 175. / 255, 1.0) | 179 self.color = (43. / 255, 0., 175. / 255, 1.0) |
180 self.scale = 5 | 180 self.scale = 5 |
181 self.height = 5 | 181 self.height = 5 |
182 | 182 |
207 glLineWidth(3.0) | 207 glLineWidth(3.0) |
208 glCallList(self.display_list) | 208 glCallList(self.display_list) |
209 glLineWidth(orig_linewidth) | 209 glLineWidth(orig_linewidth) |
210 glDisable(GL_LINE_SMOOTH) | 210 glDisable(GL_LINE_SMOOTH) |
211 | 211 |
212 class Model(object): | 212 class Model: |
213 """ | 213 """ |
214 Parent class for models that provides common functionality. | 214 Parent class for models that provides common functionality. |
215 """ | 215 """ |
216 AXIS_X = (1, 0, 0) | 216 AXIS_X = (1, 0, 0) |
217 AXIS_Y = (0, 1, 0) | 217 AXIS_Y = (0, 1, 0) |
313 gline_idx += 1 | 313 gline_idx += 1 |
314 layer_idx += 1 | 314 layer_idx += 1 |
315 gline_idx = 0 | 315 gline_idx = 0 |
316 return None | 316 return None |
317 | 317 |
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 | |
318 class GcodeModel(Model): | 359 class GcodeModel(Model): |
319 """ | 360 """ |
320 Model for displaying Gcode data. | 361 Model for displaying Gcode data. |
321 """ | 362 """ |
322 | 363 |
361 | 402 |
362 # Max number of values which can be generated per gline | 403 # Max number of values which can be generated per gline |
363 # to store coordinates/colors/normals. | 404 # to store coordinates/colors/normals. |
364 # Nicely enough we have 3 per kind of thing for all kinds. | 405 # Nicely enough we have 3 per kind of thing for all kinds. |
365 coordspervertex = 3 | 406 coordspervertex = 3 |
407 buffered_color_len = 3 # 4th color component (alpha) is ignored | |
366 verticesperline = 8 | 408 verticesperline = 8 |
367 coordsperline = coordspervertex * verticesperline | 409 coordsperline = coordspervertex * verticesperline |
368 coords_count = lambda nlines: nlines * coordsperline | 410 coords_count = lambda nlines: nlines * coordsperline |
369 | 411 |
370 travelverticesperline = 2 | 412 travelverticesperline = 2 |
387 travel_vertices = self.travels = numpy.zeros(ntravelcoords, dtype = GLfloat) | 429 travel_vertices = self.travels = numpy.zeros(ntravelcoords, dtype = GLfloat) |
388 travel_vertex_k = 0 | 430 travel_vertex_k = 0 |
389 vertices = self.vertices = numpy.zeros(ncoords, dtype = GLfloat) | 431 vertices = self.vertices = numpy.zeros(ncoords, dtype = GLfloat) |
390 vertex_k = 0 | 432 vertex_k = 0 |
391 colors = self.colors = numpy.zeros(ncoords, dtype = GLfloat) | 433 colors = self.colors = numpy.zeros(ncoords, dtype = GLfloat) |
434 | |
392 color_k = 0 | 435 color_k = 0 |
393 normals = self.normals = numpy.zeros(ncoords, dtype = GLfloat) | 436 normals = self.normals = numpy.zeros(ncoords, dtype = GLfloat) |
394 normal_k = 0 | |
395 indices = self.indices = numpy.zeros(nindices, dtype = GLuint) | 437 indices = self.indices = numpy.zeros(nindices, dtype = GLuint) |
396 index_k = 0 | 438 index_k = 0 |
397 self.layer_idxs_map = {} | 439 self.layer_idxs_map = {} |
398 self.layer_stops = [0] | 440 self.layer_stops = [0] |
399 | 441 |
400 prev_is_extruding = False | |
401 prev_move_normal_x = None | 442 prev_move_normal_x = None |
402 prev_move_normal_y = None | 443 prev_move_normal_y = None |
403 prev_move_angle = None | 444 prev_move_angle = None |
404 | |
405 prev_pos = (0, 0, 0) | 445 prev_pos = (0, 0, 0) |
446 prev_gline = None | |
406 layer_idx = 0 | 447 layer_idx = 0 |
407 | 448 |
408 self.printed_until = 0 | 449 self.printed_until = 0 |
409 self.only_current = False | 450 self.only_current = False |
410 | 451 |
433 if not gline.is_move: | 474 if not gline.is_move: |
434 continue | 475 continue |
435 if gline.x is None and gline.y is None and gline.z is None: | 476 if gline.x is None and gline.y is None and gline.z is None: |
436 continue | 477 continue |
437 has_movement = True | 478 has_movement = True |
438 current_pos = (gline.current_x, gline.current_y, gline.current_z) | 479 for current_pos in interpolate_arcs(gline, prev_gline): |
439 if not gline.extruding: | 480 if not gline.extruding: |
440 travel_vertices[travel_vertex_k] = prev_pos[0] | 481 if self.travels.size < (travel_vertex_k + 100 * 6): |
441 travel_vertices[travel_vertex_k + 1] = prev_pos[1] | 482 # arc interpolation extra points allocation |
442 travel_vertices[travel_vertex_k + 2] = prev_pos[2] | 483 # if not enough room for another 100 points now, |
443 travel_vertices[travel_vertex_k + 3] = current_pos[0] | 484 # allocate enough and 50% extra to minimize separate allocations |
444 travel_vertices[travel_vertex_k + 4] = current_pos[1] | 485 ratio = (travel_vertex_k + 100 * 6) / self.travels.size * 1.5 |
445 travel_vertices[travel_vertex_k + 5] = current_pos[2] | 486 # print(f"gl realloc travel {self.travels.size} -> {int(self.travels.size * ratio)}") |
446 travel_vertex_k += 6 | 487 self.travels.resize(int(self.travels.size * ratio), refcheck = False) |
447 prev_is_extruding = False | 488 |
448 else: | 489 travel_vertices[travel_vertex_k:travel_vertex_k+3] = prev_pos |
449 gline_color = self.movement_color(gline) | 490 travel_vertices[travel_vertex_k + 3:travel_vertex_k + 6] = current_pos |
450 | 491 travel_vertex_k += 6 |
451 next_move = get_next_move(model_data, layer_idx, gline_idx) | 492 else: |
452 next_is_extruding = (next_move.extruding | 493 delta_x = current_pos[0] - prev_pos[0] |
453 if next_move is not None else False) | 494 delta_y = current_pos[1] - prev_pos[1] |
454 | 495 norm = delta_x * delta_x + delta_y * delta_y |
455 delta_x = current_pos[0] - prev_pos[0] | 496 if norm == 0: # Don't draw anything if this move is Z+E only |
456 delta_y = current_pos[1] - prev_pos[1] | 497 continue |
457 norm = delta_x * delta_x + delta_y * delta_y | 498 norm = math.sqrt(norm) |
458 if norm == 0: # Don't draw anything if this move is Z+E only | 499 move_normal_x = - delta_y / norm |
459 continue | 500 move_normal_y = delta_x / norm |
460 norm = math.sqrt(norm) | 501 move_angle = math.atan2(delta_y, delta_x) |
461 move_normal_x = - delta_y / norm | 502 |
462 move_normal_y = delta_x / norm | 503 # FIXME: compute these dynamically |
463 move_angle = math.atan2(delta_y, delta_x) | 504 path_halfwidth = self.path_halfwidth * 1.2 |
464 | 505 path_halfheight = self.path_halfheight * 1.2 |
465 # FIXME: compute these dynamically | 506 |
466 path_halfwidth = self.path_halfwidth * 1.2 | 507 new_indices = [] |
467 path_halfheight = self.path_halfheight * 1.2 | 508 new_vertices = [] |
468 | 509 new_normals = [] |
469 new_indices = [] | 510 if prev_gline and prev_gline.extruding: |
470 new_vertices = [] | 511 # Store previous vertices indices |
471 new_normals = [] | 512 prev_id = vertex_k // 3 - 4 |
472 if prev_is_extruding: | 513 avg_move_normal_x = (prev_move_normal_x + move_normal_x) / 2 |
473 # Store previous vertices indices | 514 avg_move_normal_y = (prev_move_normal_y + move_normal_y) / 2 |
474 prev_id = vertex_k / 3 - 4 | 515 norm = avg_move_normal_x * avg_move_normal_x + avg_move_normal_y * avg_move_normal_y |
475 avg_move_normal_x = (prev_move_normal_x + move_normal_x) / 2 | 516 if norm == 0: |
476 avg_move_normal_y = (prev_move_normal_y + move_normal_y) / 2 | 517 avg_move_normal_x = move_normal_x |
477 norm = avg_move_normal_x * avg_move_normal_x + avg_move_normal_y * avg_move_normal_y | 518 avg_move_normal_y = move_normal_y |
478 if norm == 0: | 519 else: |
479 avg_move_normal_x = move_normal_x | 520 norm = math.sqrt(norm) |
480 avg_move_normal_y = move_normal_y | 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) | |
481 else: | 589 else: |
482 norm = math.sqrt(norm) | 590 # Compute vertices normal to the current move and cap it |
483 avg_move_normal_x /= norm | 591 p1x = prev_pos[0] - path_halfwidth * move_normal_x |
484 avg_move_normal_y /= norm | 592 p2x = prev_pos[0] + path_halfwidth * move_normal_x |
485 delta_angle = move_angle - prev_move_angle | 593 p1y = prev_pos[1] - path_halfwidth * move_normal_y |
486 delta_angle = (delta_angle + twopi) % twopi | 594 p2y = prev_pos[1] + path_halfwidth * move_normal_y |
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)) | 595 new_vertices.extend((prev_pos[0], prev_pos[1], prev_pos[2] + path_halfheight)) |
516 new_vertices.extend((p1x, p1y, prev_pos[2])) | 596 new_vertices.extend((p1x, p1y, prev_pos[2])) |
517 new_vertices.extend((prev_pos[0], prev_pos[1], prev_pos[2] - path_halfheight)) | 597 new_vertices.extend((prev_pos[0], prev_pos[1], prev_pos[2] - path_halfheight)) |
518 new_vertices.extend((p2x, p2y, prev_pos[2])) | 598 new_vertices.extend((p2x, p2y, prev_pos[2])) |
519 new_normals.extend((0, 0, 1)) | 599 new_normals.extend((0, 0, 1)) |
520 new_normals.extend((-move_normal_x, -move_normal_y, 0)) | 600 new_normals.extend((-move_normal_x, -move_normal_y, 0)) |
521 new_normals.extend((0, 0, -1)) | 601 new_normals.extend((0, 0, -1)) |
522 new_normals.extend((move_normal_x, move_normal_y, 0)) | 602 new_normals.extend((move_normal_x, move_normal_y, 0)) |
523 prev_id += 4 | 603 first = vertex_k // 3 |
524 first += 4 | 604 new_indices = triangulate_rectangle(first, first + 1, |
525 # Link to previous | 605 first + 2, first + 3) |
526 new_indices += triangulate_box(prev_id, prev_id + 1, | 606 |
527 prev_id + 2, prev_id + 3, | 607 next_move = get_next_move(model_data, layer_idx, gline_idx) |
528 first, first + 1, | 608 next_is_extruding = next_move and next_move.extruding |
529 first + 2, first + 3) | 609 if not next_is_extruding: |
530 else: | 610 # Compute caps and link everything |
531 hw = path_halfwidth / fact | 611 p1x = current_pos[0] - path_halfwidth * move_normal_x |
532 # Compute vertices | 612 p2x = current_pos[0] + path_halfwidth * move_normal_x |
533 p1x = prev_pos[0] - hw * avg_move_normal_x | 613 p1y = current_pos[1] - path_halfwidth * move_normal_y |
534 p2x = prev_pos[0] + hw * avg_move_normal_x | 614 p2y = current_pos[1] + path_halfwidth * move_normal_y |
535 p1y = prev_pos[1] - hw * avg_move_normal_y | 615 new_vertices.extend((current_pos[0], current_pos[1], current_pos[2] + path_halfheight)) |
536 p2y = prev_pos[1] + hw * avg_move_normal_y | 616 new_vertices.extend((p1x, p1y, current_pos[2])) |
537 new_vertices.extend((prev_pos[0], prev_pos[1], prev_pos[2] + path_halfheight)) | 617 new_vertices.extend((current_pos[0], current_pos[1], current_pos[2] - path_halfheight)) |
538 new_vertices.extend((p1x, p1y, prev_pos[2])) | 618 new_vertices.extend((p2x, p2y, current_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)) | 619 new_normals.extend((0, 0, 1)) |
542 new_normals.extend((-avg_move_normal_x, -avg_move_normal_y, 0)) | 620 new_normals.extend((-move_normal_x, -move_normal_y, 0)) |
543 new_normals.extend((0, 0, -1)) | 621 new_normals.extend((0, 0, -1)) |
544 new_normals.extend((avg_move_normal_x, avg_move_normal_y, 0)) | 622 new_normals.extend((move_normal_x, move_normal_y, 0)) |
545 first = vertex_k / 3 | 623 end_first = vertex_k // 3 + len(new_vertices) // 3 - 4 |
546 # Link to previous | 624 new_indices += triangulate_rectangle(end_first + 3, end_first + 2, |
547 new_indices += triangulate_box(prev_id, prev_id + 1, | 625 end_first + 1, end_first) |
548 prev_id + 2, prev_id + 3, | 626 new_indices += triangulate_box(first, first + 1, |
549 first, first + 1, | 627 first + 2, first + 3, |
550 first + 2, first + 3) | 628 end_first, end_first + 1, |
551 else: | 629 end_first + 2, end_first + 3) |
552 # Compute vertices normal to the current move and cap it | 630 |
553 p1x = prev_pos[0] - path_halfwidth * move_normal_x | 631 if self.indices.size < (index_k + len(new_indices) + 100 * indicesperline): |
554 p2x = prev_pos[0] + path_halfwidth * move_normal_x | 632 # arc interpolation extra points allocation |
555 p1y = prev_pos[1] - path_halfwidth * move_normal_y | 633 ratio = (index_k + len(new_indices) + 100 * indicesperline) / self.indices.size * 1.5 |
556 p2y = prev_pos[1] + path_halfwidth * move_normal_y | 634 # print(f"gl realloc print {self.vertices.size} -> {int(self.vertices.size * ratio)}") |
557 new_vertices.extend((prev_pos[0], prev_pos[1], prev_pos[2] + path_halfheight)) | 635 self.vertices.resize(int(self.vertices.size * ratio), refcheck = False) |
558 new_vertices.extend((p1x, p1y, prev_pos[2])) | 636 self.colors.resize(int(self.colors.size * ratio), refcheck = False) |
559 new_vertices.extend((prev_pos[0], prev_pos[1], prev_pos[2] - path_halfheight)) | 637 self.normals.resize(int(self.normals.size * ratio), refcheck = False) |
560 new_vertices.extend((p2x, p2y, prev_pos[2])) | 638 self.indices.resize(int(self.indices.size * ratio), refcheck = False) |
561 new_normals.extend((0, 0, 1)) | 639 |
562 new_normals.extend((-move_normal_x, -move_normal_y, 0)) | 640 for new_i, item in enumerate(new_indices): |
563 new_normals.extend((0, 0, -1)) | 641 indices[index_k + new_i] = item |
564 new_normals.extend((move_normal_x, move_normal_y, 0)) | 642 index_k += len(new_indices) |
565 first = vertex_k / 3 | 643 |
566 new_indices = triangulate_rectangle(first, first + 1, | 644 new_vertices_len = len(new_vertices) |
567 first + 2, first + 3) | 645 vertices[vertex_k:vertex_k+new_vertices_len] = new_vertices |
568 | 646 normals[vertex_k:vertex_k+new_vertices_len] = new_normals |
569 if not next_is_extruding: | 647 vertex_k += new_vertices_len |
570 # Compute caps and link everything | 648 |
571 p1x = current_pos[0] - path_halfwidth * move_normal_x | 649 new_vertices_count = new_vertices_len//coordspervertex |
572 p2x = current_pos[0] + path_halfwidth * move_normal_x | 650 # settings support alpha (transperancy), but it is ignored here |
573 p1y = current_pos[1] - path_halfwidth * move_normal_y | 651 gline_color = self.movement_color(gline)[:buffered_color_len] |
574 p2y = current_pos[1] + path_halfwidth * move_normal_y | 652 for vi in range(new_vertices_count): |
575 new_vertices.extend((current_pos[0], current_pos[1], current_pos[2] + path_halfheight)) | 653 colors[color_k:color_k+buffered_color_len] = gline_color |
576 new_vertices.extend((p1x, p1y, current_pos[2])) | 654 color_k += buffered_color_len |
577 new_vertices.extend((current_pos[0], current_pos[1], current_pos[2] - path_halfheight)) | 655 |
578 new_vertices.extend((p2x, p2y, current_pos[2])) | 656 prev_move_normal_x = move_normal_x |
579 new_normals.extend((0, 0, 1)) | 657 prev_move_normal_y = move_normal_y |
580 new_normals.extend((-move_normal_x, -move_normal_y, 0)) | 658 prev_move_angle = move_angle |
581 new_normals.extend((0, 0, -1)) | 659 |
582 new_normals.extend((move_normal_x, move_normal_y, 0)) | 660 prev_pos = current_pos |
583 end_first = vertex_k / 3 + len(new_vertices) / 3 - 4 | 661 prev_gline = gline |
584 new_indices += triangulate_rectangle(end_first + 3, end_first + 2, | 662 count_travel_indices.append(travel_vertex_k // 3) |
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) | 663 count_print_indices.append(index_k) |
613 count_print_vertices.append(vertex_k / 3) | 664 count_print_vertices.append(vertex_k // 3) |
614 gline.gcview_end_vertex = len(count_print_indices) - 1 | 665 gline.gcview_end_vertex = len(count_print_indices) - 1 |
615 | 666 |
616 if has_movement: | 667 if has_movement: |
617 self.layer_stops.append(len(count_print_indices) - 1) | 668 self.layer_stops.append(len(count_print_indices) - 1) |
618 self.layer_idxs_map[layer_idx] = len(self.layer_stops) - 1 | 669 self.layer_idxs_map[layer_idx] = len(self.layer_stops) - 1 |
635 (model_data.zmin, model_data.zmax, model_data.height)) | 686 (model_data.zmin, model_data.zmax, model_data.height)) |
636 | 687 |
637 self.travels.resize(travel_vertex_k, refcheck = False) | 688 self.travels.resize(travel_vertex_k, refcheck = False) |
638 self.vertices.resize(vertex_k, refcheck = False) | 689 self.vertices.resize(vertex_k, refcheck = False) |
639 self.colors.resize(color_k, refcheck = False) | 690 self.colors.resize(color_k, refcheck = False) |
640 self.normals.resize(normal_k, refcheck = False) | 691 self.normals.resize(vertex_k, refcheck = False) |
641 self.indices.resize(index_k, refcheck = False) | 692 self.indices.resize(index_k, refcheck = False) |
642 | 693 |
643 self.layer_stops = array.array('L', self.layer_stops) | 694 self.layer_stops = array.array('L', self.layer_stops) |
644 self.count_travel_indices = array.array('L', count_travel_indices) | 695 self.count_travel_indices = array.array('L', count_travel_indices) |
645 self.count_print_indices = array.array('L', count_print_indices) | 696 self.count_print_indices = array.array('L', count_print_indices) |
653 self.fully_loaded = True | 704 self.fully_loaded = True |
654 | 705 |
655 t_end = time.time() | 706 t_end = time.time() |
656 | 707 |
657 logging.debug(_('Initialized 3D visualization in %.2f seconds') % (t_end - t_start)) | 708 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)) | 709 logging.debug(_('Vertex count: %d') % ((len(self.vertices) + len(self.travels)) // 3)) |
659 yield None | 710 yield None |
660 | 711 |
661 def copy(self): | 712 def copy(self): |
662 copy = GcodeModel() | 713 copy = GcodeModel() |
663 for var in ["vertices", "colors", "travels", "indices", "normals", | 714 for var in ["vertices", "colors", "travels", "indices", "normals", |
670 setattr(copy, var, getattr(self, var)) | 721 setattr(copy, var, getattr(self, var)) |
671 copy.loaded = True | 722 copy.loaded = True |
672 copy.fully_loaded = True | 723 copy.fully_loaded = True |
673 copy.initialized = False | 724 copy.initialized = False |
674 return copy | 725 return copy |
726 | |
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) | |
675 | 744 |
676 # ------------------------------------------------------------------------ | 745 # ------------------------------------------------------------------------ |
677 # DRAWING | 746 # DRAWING |
678 # ------------------------------------------------------------------------ | 747 # ------------------------------------------------------------------------ |
679 | 748 |
867 vertex_k = 0 | 936 vertex_k = 0 |
868 colors = self.colors = numpy.zeros(nlines * 8, dtype = GLfloat) | 937 colors = self.colors = numpy.zeros(nlines * 8, dtype = GLfloat) |
869 color_k = 0 | 938 color_k = 0 |
870 self.printed_until = -1 | 939 self.printed_until = -1 |
871 self.only_current = False | 940 self.only_current = False |
941 prev_gline = None | |
872 while layer_idx < len(model_data.all_layers): | 942 while layer_idx < len(model_data.all_layers): |
873 with self.lock: | 943 with self.lock: |
874 nlines = len(model_data) | 944 nlines = len(model_data) |
875 if nlines * 6 != vertices.size: | 945 if nlines * 6 > vertices.size: |
876 self.vertices.resize(nlines * 6, refcheck = False) | 946 self.vertices.resize(nlines * 6, refcheck = False) |
877 self.colors.resize(nlines * 8, refcheck = False) | 947 self.colors.resize(nlines * 8, refcheck = False) |
878 layer = model_data.all_layers[layer_idx] | 948 layer = model_data.all_layers[layer_idx] |
879 has_movement = False | 949 has_movement = False |
880 for gline in layer: | 950 for gline in layer: |
881 if not gline.is_move: | 951 if not gline.is_move: |
882 continue | 952 continue |
883 if gline.x is None and gline.y is None and gline.z is None: | 953 if gline.x is None and gline.y is None and gline.z is None: |
884 continue | 954 continue |
955 | |
885 has_movement = True | 956 has_movement = True |
886 vertices[vertex_k] = prev_pos[0] | 957 for current_pos in interpolate_arcs(gline, prev_gline): |
887 vertices[vertex_k + 1] = prev_pos[1] | 958 |
888 vertices[vertex_k + 2] = prev_pos[2] | 959 if self.vertices.size < (vertex_k + 100 * 6): |
889 current_pos = (gline.current_x, gline.current_y, gline.current_z) | 960 # arc interpolation extra points allocation |
890 vertices[vertex_k + 3] = current_pos[0] | 961 ratio = (vertex_k + 100 * 6) / self.vertices.size * 1.5 |
891 vertices[vertex_k + 4] = current_pos[1] | 962 # print(f"gl realloc lite {self.vertices.size} -> {int(self.vertices.size * ratio)}") |
892 vertices[vertex_k + 5] = current_pos[2] | 963 self.vertices.resize(int(self.vertices.size * ratio), refcheck = False) |
893 vertex_k += 6 | 964 self.colors.resize(int(self.colors.size * ratio), refcheck = False) |
894 | 965 |
895 vertex_color = self.movement_color(gline) | 966 |
896 colors[color_k] = vertex_color[0] | 967 vertices[vertex_k] = prev_pos[0] |
897 colors[color_k + 1] = vertex_color[1] | 968 vertices[vertex_k + 1] = prev_pos[1] |
898 colors[color_k + 2] = vertex_color[2] | 969 vertices[vertex_k + 2] = prev_pos[2] |
899 colors[color_k + 3] = vertex_color[3] | 970 vertices[vertex_k + 3] = current_pos[0] |
900 colors[color_k + 4] = vertex_color[0] | 971 vertices[vertex_k + 4] = current_pos[1] |
901 colors[color_k + 5] = vertex_color[1] | 972 vertices[vertex_k + 5] = current_pos[2] |
902 colors[color_k + 6] = vertex_color[2] | 973 vertex_k += 6 |
903 colors[color_k + 7] = vertex_color[3] | 974 |
904 color_k += 8 | 975 vertex_color = self.movement_color(gline) |
905 | 976 colors[color_k] = vertex_color[0] |
906 prev_pos = current_pos | 977 colors[color_k + 1] = vertex_color[1] |
907 gline.gcview_end_vertex = vertex_k / 3 | 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 | |
908 | 989 |
909 if has_movement: | 990 if has_movement: |
910 self.layer_stops.append(vertex_k / 3) | 991 self.layer_stops.append(vertex_k // 3) |
911 self.layer_idxs_map[layer_idx] = len(self.layer_stops) - 1 | 992 self.layer_idxs_map[layer_idx] = len(self.layer_stops) - 1 |
912 self.max_layers = len(self.layer_stops) - 1 | 993 self.max_layers = len(self.layer_stops) - 1 |
913 self.num_layers_to_draw = self.max_layers + 1 | 994 self.num_layers_to_draw = self.max_layers + 1 |
914 self.initialized = False | 995 self.initialized = False |
915 self.loaded = True | 996 self.loaded = True |
934 self.fully_loaded = True | 1015 self.fully_loaded = True |
935 | 1016 |
936 t_end = time.time() | 1017 t_end = time.time() |
937 | 1018 |
938 logging.debug(_('Initialized 3D visualization in %.2f seconds') % (t_end - t_start)) | 1019 logging.debug(_('Initialized 3D visualization in %.2f seconds') % (t_end - t_start)) |
939 logging.debug(_('Vertex count: %d') % (len(self.vertices) / 3)) | 1020 logging.debug(_('Vertex count: %d') % (len(self.vertices) // 3)) |
940 yield None | 1021 yield None |
941 | 1022 |
942 def copy(self): | 1023 def copy(self): |
943 copy = GcodeModelLight() | 1024 copy = GcodeModelLight() |
944 for var in ["vertices", "colors", "max_layers", | 1025 for var in ["vertices", "colors", "max_layers", |