-
Notifications
You must be signed in to change notification settings - Fork 0
/
base_pieces.py
executable file
·1693 lines (1526 loc) · 68.2 KB
/
base_pieces.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""
Description
-----------
Crossbeams Modeller base_pieces library--a base library for piece
manipulation. Describes various routines for manipulating a fairly
arbitrary rod/connector system.
See cbmodel.py for a description of the package and its history.
Author
------
Charles Sharman
License
-------
Distributed under the GNU GENERAL PUBLIC LICENSE Version 3. View
LICENSE for details.
"""
from OpenGL.GL import *
from OpenGL.GLE import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
import numpy as np # The opengl number-crunching module is called np
import math
import vector_math
import string
import copy
# Global Variables
draw_outline = 0 # 0/1
draw_future_parts = 1 # 0/1 used in instructions
generate_pdf = 0 # 0/1
depth_scale = 1.0 # Used in draw_part_outlines
dim_scale = 1.0 # Multiplier for lines/text for print display
pixperunit = 10.0 # Set by cbmodel.py later
xabstol = 10e-3 # A non-zero number which is insignificant
# These colors are overwritten by cbmodel.py
colors = {'total': (1.0, 1.0, 1.0),
'straight': (1.0, 1.0, 1.0),
'angle': (1.0, 1.0, 1.0),
'arc': (1.0, 1.0, 1.0),
'part': (0.0, 1.0, 0.0),
'edit': (0.45, 0.675, 0.9),
'outline': (0.0, 0.0, 0.0),
'newpart': (0.45, 0.675, 0.9),
'futurepart': (0.33, 0.33, 0.33),
'tire': (0.33, 0.33, 0.33),
'wheel': (1.0, 1.0, 1.0),
'clip': (1.0, 1.0, 1.0),
'gear': (1.0, 1.0, 1.0),
'shaft': (1.0, 1.0, 1.0),
'region0': (1.0, 0.5, 0.5),
'region1': (0.5, 1.0, 0.5),
'region2': (0.5, 0.5, 1.0),
'region3': (0.8, 0.8, 0.4),
'region4': (0.8, 0.4, 0.8),
'region5': (0.4, 0.8, 0.8)}
def absinvert(x):
"""
Returns the number if >= 0. Otherwise, returns the inversion of
the number.
"""
if x >= 0:
return x
else:
return ~x
class piece(object):
"""
A base class for every piece
"""
detail1 = 'Yes'
query_options = [] # a piece which can be configured
unaligned_center = []
unaligned_ends = []
ends_types = [] # 's'/'j' (stick or joint)
combination = [] # a piece which is a combination of pieces
axis = [] # indicates which ends are on a rotating axis
def __init__(self):
self.port = 0 #port is an index
self.flip = 0
if len(self.unaligned_center) == 0:
self.unaligned_center = np.array([0.0, 0.0, 0.0])
count = 0
for count in range(len(self.unaligned_ends)):
self.unaligned_center = self.unaligned_center + self.unaligned_ends[count][0]
self.unaligned_center = self.unaligned_center/(count + 1.0)
self.configure = []
for query_option in self.query_options:
self.configure.append(query_option[1][0])
def configure_name(self, configure):
"""
Some configures are prefixed or postfixed with special
characters. Returns the base aliased name of a configure and
its extras.
"""
if configure[0] in ['-', '^']:
name = configure[1:]
extra = configure[0]
else:
name = configure
extra = ''
if pieces.configure_aliases.has_key(name):
name = pieces.configure_aliases[name]
return (name, extra)
def label(self):
"""
Returns the text label for this part
"""
return [self.name]
def inset_files(self):
"""
Returns the inset files for this part
"""
return ''
def help_text(self):
"""
Returns the instruction help text for this part
"""
return ''
def nextport(self, next = 1):
"""
Toggles ports, making the next one align with the module.
"""
end_type = self.ends_types[self.port]
if next == 1:
try:
self.port = self.ends_types.index(end_type, self.port + 1)
except ValueError:
self.port = self.ends_types.index(end_type)
else:
rev_types = self.ends_types[:]
rev_types.reverse()
try:
rev_port = rev_types.index(end_type, len(rev_types) - self.port)
except ValueError:
rev_port = rev_types.index(end_type)
self.port = len(rev_types) - rev_port - 1
def flipport(self, dir = 1):
"""
Rotates the port 90 degrees relative to the module.
"""
self.flip = (self.flip + dir*90) % 360
def shape_color(self, color = None):
"""
Sets the piece color
"""
if not color:
glColor3fv(colors['total'])
else:
glColor3fv(color)
def draw(self, color = None):
"""
Draws the piece
"""
glPushMatrix()
glMultMatrixf(self.matrix)
self.shape_color(color)
if pieces.detail == 2:
self.shape(color)
else: # pieces.detail == 1
if pieces.detail1_gllists.has_key(self.name):
glCallList(pieces.detail1_gllists[self.name])
else:
if self.detail1 != 'Separate':
gllist = glGenLists(1)
else:
gllist = 0
if gllist != 0:
glNewList(gllist, GL_COMPILE)
self.shape(color)
#for end, end_type in zip(self.unaligned_ends, self.ends_types):
# pieces.draw_end(end, end_type)
if gllist != 0:
glEndList()
pieces.detail1_gllists[self.name] = gllist
glCallList(gllist)
glPopMatrix()
def calc_draw(self, color):
"""
Aligns the piece to the module then draws the piece.
"""
global xabstol
to_end = self.to_end
from_end = copy.copy(self.unaligned_ends[self.port])
#print 'to_end+', to_end, from_end
# Rotate 1
vfrom = from_end[0] - from_end[1]
vto = to_end[0] - to_end[1]
vrot = np.array([0.0, 0.0, 1.0])
if not np.allclose(-vfrom, vto, atol=xabstol, rtol=0.0): # Not already aligned
if np.allclose(vfrom, vto, atol=xabstol, rtol=0.0): # 180 degrees apart
vrot = to_end[0] - to_end[2]
angle = 180.0
else:
vrot = vector_math.cross(vfrom, vto)
angle = 180.0 + math.degrees(vector_math.acos_care(np.dot(vto, vfrom)))
#print 'angle = ' + repr(angle) + ' vrot = ' + repr(vrot)
glPushMatrix()
glLoadIdentity()
glRotatef(angle, vrot[0], vrot[1], vrot[2])
m = glGetFloatv(GL_MODELVIEW_MATRIX)[:3,:3]
glPopMatrix()
rot_end = (np.dot(m, from_end[0]),
np.dot(m, from_end[1]),
np.dot(m, from_end[2]))
else:
angle = 0.0
rot_end = copy.copy(from_end)
#print 'rot_end = ' + repr(rot_end)
# Rotate 2
vfrom = rot_end[0] - rot_end[2]
vto = to_end[0] - to_end[2]
vrot2 = rot_end[0] - rot_end[1]
if not np.allclose(vfrom, vto, atol=xabstol, rtol=0.0): # Not aligned
if np.allclose(-vfrom, vto, atol=xabstol, rtol=0.0): # 180 degrees
angle2 = 180.0
else:
angle2 = 180.0 + math.degrees(vector_math.acos_care(np.dot(vto, vfrom)))
else:
angle2 = 0.0
if self.flip != 0:
if angle2 >= 180.0: angle2 = angle2 - float(self.flip)
else: angle2 = angle2 + float(self.flip)
# Do the transformation
glPushMatrix()
glLoadIdentity()
glTranslatef(to_end[0][0], to_end[0][1], to_end[0][2])
glRotatef(angle2, vrot2[0], vrot2[1], vrot2[2])
glRotatef(angle, vrot[0], vrot[1], vrot[2])
glTranslatef(-from_end[0][0], -from_end[0][1], -from_end[0][2])
# I need the 4x4 matrix, because translations are stored in
# the extra column
self.matrix = glGetFloatv(GL_MODELVIEW_MATRIX)
#print 'self.matrix', self.matrix
self.calc_ends()
glPopMatrix()
# Calculate new ends
self.draw(color)
def calc_ends(self):
"""
Calculates the absolute position of each end after an alignment.
"""
matrix = np.transpose(self.matrix)
self.ends = []
for count in range(len(self.unaligned_ends)):
self.ends.append((np.dot(matrix, np.concatenate((self.unaligned_ends[count][0], [1.0])))[:3],
np.dot(matrix, np.concatenate((self.unaligned_ends[count][1], [1.0])))[:3],
np.dot(matrix, np.concatenate((self.unaligned_ends[count][2], [1.0])))[:3]))
self.ends = np.array(self.ends)
self.center = np.dot(matrix, np.concatenate((self.unaligned_center, [1.0])))[:3]
self.center_save = self.center[:]
def align(self, to_end):
"""
Sets the end to align to
"""
self.to_end = to_end
class module(object):
"""
A base class for a collection of pieces, called a module.
"""
HISTORY_LENGTH = 10
def __init__(self):
self.netlist = []
self.port = 0
self.ends = []
self.ends_types = []
self.selected = []
# Instructions
self.frame = 0
# self.instructions format is:
# [{vcenter, vout, vup, pixperunit, title, author, date}, # Frame 0 (Title)
# {vcenter, vout, vup, pixperunit, new_parts}, # Frame 1
# {vcenter, vout, vup, pixperunit, new_parts}, # Frame 2
# etc.] (where new_pieces is a list of pieces to add for that frame)
self.individual_instructions = []
self.group_instructions = []
self.instructions = []
self.old_parts = []
self.hidden_parts = []
self.instruction_start = 1 # frame where instructions start; poses are before this
self.submodel = 0 # -1 pop, 0 none, 1 pop
self.submodel_stack = []
self.hold_pose = 0
self.group_index = -1
self.piece_groups = {} # A dictionary of piece_index:group_index
self.group_pieces = [[]] # A list of the pieces in each group
self.groups = [1]
# History
# Currently a crude form of undo/redo. Stores the module
# after every module change
self.history = []
self.history_index = -1
self.redraw_called = 0 # Signals redraw started and finished
def history_push(self):
"""
Saves the module onto the stack
"""
# disallow redos after a push
for count in range(self.history_index, -1):
del self.history[-1]
self.history_index = -1
if len(self.history) >= self.HISTORY_LENGTH:
del self.history[0]
self.history.append((copy.deepcopy(self.netlist), self.port, self.ends[:], self.ends_types[:], copy.deepcopy(self.individual_instructions), copy.deepcopy(self.group_instructions)))
def history_undo(self):
"""
Use the last module store
"""
new_index = self.history_index - 1
if len(self.history) >= -new_index:
self.history_index = new_index
self.netlist, self.port, self.ends, self.ends_types, self.individual_instructions, self.group_instructions = self.history[self.history_index]
self.selected = []
def history_redo(self):
"""
Use the next module store
"""
new_index = min(self.history_index + 1, -1)
if len(self.history) >= -new_index:
self.history_index = new_index
self.netlist, self.port, self.ends, self.ends_types, self.individual_instructions, self.group_instructions = self.history[self.history_index]
self.selected = []
def start_ends(self, end_type):
"""
The positions of the invisible cross hair before any piece
has been placed.
"""
e0 = pieces.join_len
if end_type == 's':
self.ends = [(np.array([e0, 0.0, 0.0]), np.array([e0-1.0, 0.0, 0.0]), np.array([e0, 0.0, -1.0])),
(np.array([0.0, e0, 0.0]), np.array([0.0, e0-1.0, 0.0]), np.array([0.0, e0, -1.0])),
(np.array([0.0, 0.0, e0]), np.array([0.0, 0.0, e0-1.0]), np.array([-1.0, 0.0, e0])),
(np.array([-e0, 0.0, 0.0]), np.array([-(e0-1.0), 0.0, 0.0]), np.array([-e0, 0.0, -1.0])),
(np.array([0.0, -e0, 0.0]), np.array([0.0, -(e0-1.0), 0.0]), np.array([0.0, -e0, -1.0])),
(np.array([0.0, 0.0, -e0]), np.array([0.0, 0.0, -(e0-1.0)]), np.array([-1.0, 0.0, -e0]))]
else: # 'j'
self.ends = [(np.array([e0, 0.0, 0.0]), np.array([e0+1.0, 0.0, 0.0]), np.array([e0, 0.0, -1.0])),
(np.array([0.0, e0, 0.0]), np.array([0.0, e0+1.0, 0.0]), np.array([0.0, e0, -1.0])),
(np.array([0.0, 0.0, e0]), np.array([0.0, 0.0, e0+1.0]), np.array([-1.0, 0.0, e0])),
(np.array([-e0, 0.0, 0.0]), np.array([-(e0+1.0), 0.0, 0.0]), np.array([-e0, 0.0, -1.0])),
(np.array([0.0, -e0, 0.0]), np.array([0.0, -(e0+1.0), 0.0]), np.array([0.0, -e0, -1.0])),
(np.array([0.0, 0.0, -e0]), np.array([0.0, 0.0, -(e0+1.0)]), np.array([-1.0, 0.0, -e0]))]
def draw_base(self):
"""
Draws the module by copying pixels from the back buffer to the
front buffer. Used in conjuction with capture_background.
"""
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
glDepthMask(GL_TRUE)
glClear(GL_DEPTH_BUFFER_BIT) # This should *not* be necessary
glDrawPixelsf(GL_DEPTH_COMPONENT, self.total_bmd)
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
glDepthMask(GL_FALSE)
glDrawPixelsf(GL_RGBA, self.total_rgb)
glDepthMask(GL_TRUE)
def capture_background(self):
"""
Captures the background image for rapid redisplay. Used in
conjuction with draw_base.
glReadPixels seems to be quite slow on many graphics hardware,
so this is probably a poor way to do things. Many claim
textures are better.
"""
viewport = glGetIntegerv(GL_VIEWPORT)
glReadBuffer(GL_BACK)
self.total_bmd = glReadPixelsf(viewport[0], viewport[1], viewport[2], viewport[3], GL_DEPTH_COMPONENT)
self.total_rgb = glReadPixelsf(viewport[0], viewport[1], viewport[2], viewport[3], GL_RGBA)
def draw_selected(self, vout, vup, creationmode):
"""
Draws selected pieces a different color by redrawing the piece
slightly in front of where it should be.
"""
global colors
# glScalef did not work because the center about which to
# scale was unclear
#glDepthFunc(GL_EQUAL) # fine, but z-fighting for def draw()
# When all parts are selected, doubles redraw time ***
voffset = vout*0.1
glPushMatrix()
glTranslatef(voffset[0], voffset[1], voffset[2])
for count in self.selected:
if self.hold_pose and (creationmode == 'instructions' or creationmode == 'group') and self.frame >= self.instruction_start:
matrix = self.region_matrices[self.region_lookups[count]]
glPushMatrix()
glMultMatrixf(matrix)
self.netlist[count].draw(colors['edit'])
glPopMatrix()
else:
self.netlist[count].draw(colors['edit'])
#self.draw_part_outline(self.netlist[count], vout, vup, colors['edit'], 2.0)
glPopMatrix()
#if self.newparts != []:
# for newpart in self.newparts:
# self.netlist[newpart].draw(colors['edit'])
def draw_part_outline(self, part, vout, vup, color, width_scale = 1.0):
"""
Draws an outline around a single part in the module.
Expensive and inaccurate. Currently unused.
"""
global colors
voffset = -vout*pieces.base_rad
width = width_scale * 2 * pieces.base_rad/10.0
vy = vup*width
vx = vector_math.cross(vout, vup)*width
va = vector_math.normalize(vx + vy)*width
vb = vector_math.normalize(-vx - vy)*width
glDisable(GL_LIGHTING)
#glScalef(1.1, 1.1, 1.1) # Scaling about (0,0,0) makes outline funny ***
#glTranslatef(voffset[0], voffset[1], voffset[2])
# The 4 draws make this very expensive, and a large vx/vy
# distorts the outline. ***
# Orthogonal Directions
glPushMatrix()
glTranslatef(voffset[0]+vy[0], voffset[1]+vy[1], voffset[2]+vy[2])
part.draw(color)
glPopMatrix()
glPushMatrix()
glTranslatef(voffset[0]-vy[0], voffset[1]-vy[1], voffset[2]-vy[2])
part.draw(color)
glPopMatrix()
glPushMatrix()
glTranslatef(voffset[0]+vx[0], voffset[1]+vx[1], voffset[2]+vx[2])
part.draw(color)
glPopMatrix()
glPushMatrix()
glTranslatef(voffset[0]-vx[0], voffset[1]-vx[1], voffset[2]-vx[2])
part.draw(color)
glPopMatrix()
if pieces.detail > 0:
glEnable(GL_LIGHTING)
def draw_part_outlines(self):
"""
Captures all object shadows with a depth buffer screen and
broadens their background footprint with a smearing algorithm.
(Could use scipy binary_erosion, but I didn't want to add yet
another special import.) By broadening the depth buffer along
with it, I can even create outlines with one piece overlapping
another.
Although more complicated than draw_part_outline, it is much
faster:
Corvette redraw times:
0.6s line (detail=0), screen colors
0.8s solid (detail=1), screen colors
3.9s rendered (detail=2), screen colors
10.8s rendered, print colors, draw_part_outline
4.2s rendered, print colors, draw_part_outlines
"""
global colors, depth_scale, pixperunit
model = glGetDoublev(GL_MODELVIEW_MATRIX)
projection = glGetDoublev(GL_PROJECTION_MATRIX)
viewport = glGetIntegerv(GL_VIEWPORT)
# reshape needed to circumvent pyopengl bug ***
total_bmd = np.reshape(self.total_bmd, (viewport[3], viewport[2]))
binary_depth = (total_bmd < 1.0).astype(np.uint8)
#iterations = 2*dim_scale # Increase for thicker outlines
#iterations = max(2, int(round(2*dim_scale*pixperunit/10.0))) # Increase for thicker outlines
iterations = min(int(2*dim_scale), max(2, int(round(2*dim_scale*pixperunit/10.0)))) # Increase for thicker outlines
outline = binary_depth.copy()
shadow_bmd = total_bmd.copy()+2*pieces.base_rad*depth_scale # The second term defines the shadow offset.
offsets = vector_math.offsets(iterations, iterations - 1) # outline pattern possible as long as distance is less than the minimum thickness of any feature
ol = np.zeros(outline.shape, np.uint8)
sh = np.ones(outline.shape, np.float32)
ymax, xmax = outline.shape
for x, y in offsets:
y1 = max(0, y)
y2 = min(ymax, ymax + y)
x1 = max(0, x)
x2 = min(xmax, xmax + x)
y3 = max(0, -y)
y4 = min(ymax, ymax - y)
x3 = max(0, -x)
x4 = min(xmax, xmax - x)
ol[y1:y2,x1:x2] = ol[y1:y2,x1:x2] | outline[y3:y4,x3:x4]
sh[y1:y2,x1:x2] = np.minimum(sh[y1:y2,x1:x2], shadow_bmd[y3:y4,x3:x4])
outline = ol & (sh < total_bmd) # Remove this check and all shadow_bmd lines if I only want background highlighted
column_adds = viewport[2] % 8
if column_adds > 0:
column_adds = 8 - column_adds
full_outline = np.zeros((viewport[3], viewport[2] + column_adds), np.uint8)
full_outline[:,:viewport[2]] = outline
outline = full_outline
outline = np.reshape(outline, (-1, 8)) * np.array([128, 64, 32, 16, 8, 4, 2, 1], np.uint8)
outline = np.sum(outline, 1).astype(np.uint8)
glColor3fv(colors['outline'])
#glDisable(GL_DEPTH_TEST)
glDisable(GL_LIGHTING)
p = gluUnProject(0.001, 0.001, 0.001, model, projection, viewport) # Set z to 0.999 (almost back) if I only want background highlighted
glRasterPos3fv(p)
glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
glBitmap(viewport[2], viewport[3], 0, 0, 0, 0, outline)
if pieces.detail > 0:
glEnable(GL_LIGHTING)
#glEnable(GL_DEPTH_TEST)
glPixelStorei(GL_UNPACK_ALIGNMENT, 4)
p = gluUnProject(0.001, 0.001, 0.001, model, projection, viewport)
glRasterPos3fv(p)
# Put outlines at the proper depth (can skip if I only want background highlighted)
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
glClear(GL_DEPTH_BUFFER_BIT)
glDrawPixelsf(GL_DEPTH_COMPONENT, self.total_bmd)
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
def draw(self, vout, vup, creationmode):
"""
Draws the module by using previous draws. Faster than redraw.
"""
#print 'draw'
self.draw_base()
self.draw_selected(vout, vup, creationmode)
def generate_submodel_stack(self):
"""
Generates the submodel stack
"""
self.submodel_stack = []
for frame_index in range(self.instruction_start, self.frame):
inst = self.instructions[frame_index]
if inst.has_key('submodel'):
if inst['submodel'] == 1:
self.submodel_stack.append(frame_index)
else:
del self.submodel_stack[-1]
# Add the current frame
if self.submodel == 1:
self.submodel_stack.append(self.frame)
elif self.submodel == -1:
del self.submodel_stack[-1]
def clean_submodel_stack(self):
"""
Removes any extra pops in a submodel stack after a delete or
end_submodel.
"""
depth = 0
for frame_index in range(self.instruction_start, len(self.instructions)):
inst = self.instructions[frame_index]
if inst.has_key('submodel'):
if inst['submodel'] == 1:
depth = depth + 1
else: # a pop
if depth > 0:
depth = depth - 1
else:
del self.instructions[frame_index]['submodel']
def calc_group_index(self):
return np.nonzero(np.array([0] + self.groups) <= self.frame)[0][-1] - 1
def generate_groups(self):
"""
Generates a list of the frame_indices where the group changes
"""
groups = [self.instruction_start]
group_pieces = [[]]
piece_groups = {}
group_index = 0
for frame_index in range(len(self.instructions)):
inst = self.instructions[frame_index]
if inst.has_key('group'):
groups.append(frame_index)
group_pieces.append([])
group_index = group_index + 1
group_pieces[-1] = group_pieces[-1] + inst['new_parts']
for part in inst['new_parts']:
piece_groups[part] = group_index
self.groups = groups
self.piece_groups = piece_groups
self.group_pieces = group_pieces
def pose_transform(self, path, axes, rotates):
"""
Determines a transformation for a region
"""
glPushMatrix()
glLoadIdentity()
# Determine the transformation
last_region = path[0]
for connecting_region in path[1:]:
if last_region > connecting_region:
key = (connecting_region, last_region)
else:
key = (last_region, connecting_region)
end = axes[key]
about = end[0] - end[1]
angle = rotates[connecting_region]
glTranslatef(end[0][0], end[0][1], end[0][2])
glRotatef(angle, about[0], about[1], about[2])
glTranslatef(-end[0][0], -end[0][1], -end[0][2])
last_region = connecting_region
# Calculate new matrix
matrix = glGetFloatv(GL_MODELVIEW_MATRIX)
glPopMatrix()
glMultMatrixf(matrix)
return matrix
def hold_pose_matrices(self):
self.region_matrices = []
for region_index, region in enumerate(self.regions):
glPushMatrix()
matrix = self.pose_transform(self.region_paths[region_index], self.region_axes, self.instructions[0]['rotates']) # Pose fixed at frame 0
self.region_matrices.append(matrix)
glPopMatrix()
def pose_centers(self):
for matrix, region in zip(self.region_matrices, self.regions):
for part_index in region:
if part_index >= 0:
center = self.netlist[part_index].center_save
new_center = np.dot(np.transpose(matrix), np.concatenate((center, [1.0])))[:3]
self.netlist[part_index].center = new_center
def restore_centers(self):
for part in self.netlist:
part.center = part.center_save
def redraw(self, vout, vup, creationmode):
"""
Draws the module from scratch
"""
global colors, draw_future_parts, generate_pdf, draw_outline
#print 'redraw'
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
if creationmode == 'modelling':
for p in self.netlist:
p.draw()
self.capture_background()
if draw_outline:
self.draw_part_outlines()
# Draw selected
self.draw_selected(vout, vup, creationmode)
elif (creationmode == 'instructions' or creationmode == 'group') and self.frame < self.instruction_start: # pose
# Used this to draw each region a different color
#for region_index, region in enumerate(self.regions):
# color = colors['region' + str(region_index % 6)]
# for part_index in region:
# if part_index >= 0:
# self.netlist[part_index].draw(color)
something_selected = len(self.selected)
color = None # Allow piece colors
for region_index, region in enumerate(self.regions):
glPushMatrix()
matrix = self.pose_transform(self.region_paths[region_index], self.region_axes, self.region_rotates)
self.region_centers[region_index] = np.dot(np.transpose(matrix), np.concatenate((self.region_fixed_centers[region_index], [1.0])))[:3]
if not generate_pdf:
if something_selected and region_index == self.selected[0]: # Moving Region
color = colors['edit']
elif region_index == self.region_fixed: # Fixed Region
color = colors['total']
else:
color = colors['futurepart']
for part_index in region:
if part_index >= 0:
if creationmode == 'group':
try:
group_index = self.piece_groups[part_index]
except:
group_index = -1
if (group_index >= 0) and ((self.group_index < 0) or (group_index == self.group_index)):
color = colors['region' + str(group_index % 6)]
else:
color = None
self.netlist[part_index].draw(color)
glPopMatrix()
self.capture_background()
if draw_outline:
self.draw_part_outlines()
elif (creationmode == 'instructions' or creationmode == 'group') and self.frame >= self.instruction_start:
len_self_netlist = len(self.netlist)
self.part_flags = 2*np.ones(len_self_netlist, np.uint8)
frame_inc = 0
if len(self.submodel_stack) > 0:
hide_old_index = reduce(lambda x, y: x + y, map(lambda z: len(z['new_parts']), self.instructions[:self.submodel_stack[-1]]))
elif creationmode == 'group' and len(self.groups) > 1:
hide_old_index = reduce(lambda x, y: x + y, map(lambda z: len(z['new_parts']), self.instructions[:self.groups[self.calc_group_index()]]))
else:
hide_old_index = 0
self.part_flags[self.old_parts[:hide_old_index]] = 3 # discard
self.part_flags[self.old_parts[hide_old_index:]] = 0
if self.submodel == -1: # Pop
# Find the start
submodel_level = -1
for frame_index in range(self.frame - 1, -1, -1):
submodel_level = submodel_level + self.instructions[frame_index].get('submodel', 0)
if submodel_level == 0:
break
show_old_index = reduce(lambda x, y: x + y, map(lambda z: len(z['new_parts']), self.instructions[:frame_index]))
self.part_flags[self.old_parts[show_old_index:]] = 1
if generate_pdf:
self.part_flags[self.selected] = 1
if not generate_pdf:
self.part_flags[self.hidden_parts] = 3
local_colors = [None, colors['newpart'], colors['futurepart']]
for part_index in range(len_self_netlist):
if self.part_flags[part_index] == 3:
pass
elif self.part_flags[part_index] == 2 and draw_future_parts == 0:
pass
else:
p = self.netlist[part_index]
color = local_colors[self.part_flags[part_index]]
if self.hold_pose:
matrix = self.region_matrices[self.region_lookups[part_index]]
glPushMatrix()
glMultMatrixf(matrix)
p.draw(color)
#if draw_outline:
# self.draw_part_outline(p, vout, vup)
glPopMatrix()
else:
p.draw(color)
#if draw_outline:
# self.draw_part_outline(p, vout, vup)
self.capture_background()
if draw_outline:
self.draw_part_outlines()
if not generate_pdf:
self.draw_selected(vout, vup, creationmode)
self.redraw_called = 1
def ends_aligned(self, end1, ends, same_dir = 0):
"""
Returns whether end1 is aligned with any end in ends
Returns the index+1 of only one of the ends that are aligned
Returns 0 if none are aligned
Returns -1 if there is an error
"""
if len(ends) <= 0:
return 0
#local_ends = np.array(ends)
sametips = vector_math.mag(end1[0] - ends[:,0]) < xabstol
sametip_indices = np.nonzero(sametips)[0]
for sametip_index in sametip_indices:
end2 = ends[sametip_index]
if ((same_dir == 0 and np.allclose(end1[0]-end1[1], end2[1]-end2[0], atol=xabstol, rtol=0.0)) or (same_dir == 1 and np.allclose(end1[0]-end1[1], end2[0]-end2[1], atol=xabstol, rtol=0.0))):
dp = np.dot(end1[0]-end1[2], end2[0]-end2[2])
if (-1.0 - xabstol < dp < -1.0 + xabstol) or \
(-xabstol < dp < xabstol) or \
(1.0 - xabstol < dp < 1.0 + xabstol):
return sametip_index+1
else:
print 'bad_angle', end1, end2
return -1 # aligned but bad angle
return 0
def write_netlist(self):
"""
Returns a string representation of the module
"""
netlist_version = 1.0
sf = 1.0/pieces.sf
# Write version
written = 'v' + str(netlist_version) + '\n'
# Write parts
for part in self.netlist:
if len(part.configure) > 0:
configs = reduce(lambda x, y: str(x) + ' ' + str(y), part.configure) + ' '
else:
configs = ''
# May want to change the round in the future depending on parts
written = written + part.name + ' ' + configs + reduce(lambda x, y: str(x) + ' ' + str(y), map(lambda z: round(z, 1), (sf*part.matrix.reshape(16)).tolist())) + '\n'
# Write ends
for end, end_type in zip(self.ends, self.ends_types):
# May want to change the round in the future depending on parts
written = written + 'end ' + end_type + ' ' + reduce(lambda x, y: str(x) + ' ' + str(y), map(lambda z: round(z, 1), (sf*np.reshape(end, (1, 9))[0]).tolist())) + '\n'
# Write instructions
sizes = {'fullr': 'r', 'full': 'f', 'quarter': 'q', 'halfh': 'h'}
# Add mass, price, dimensions for easy read-out later
if len(self.individual_instructions) > 0:
self.individual_instructions[0]['count'] = '%d' % self.total_inventory()
self.individual_instructions[0]['mass'] = '%.0f' % self.mass()
self.individual_instructions[0]['price'] = '%.2f' % self.price()
self.individual_instructions[0]['dims'] = '%.1f,%.1f,%.1f' % self.dimensions()
for c, itype in (('i', self.individual_instructions),
('g', self.group_instructions)):
for count, inst in enumerate(itype):
written = written + c + ' %.6e %.6e %.6e %.6e %.6e %.6e %.6e %.6e %.6e %.6e' % (inst['vcenter'][0], inst['vcenter'][1], inst['vcenter'][2], inst['vout'][0], inst['vout'][1], inst['vout'][2], inst['vup'][0], inst['vup'][1], inst['vup'][2], inst['pixperunit'])
if len(inst['new_parts']) > 1:
written = written + ' ' + reduce(lambda x, y: str(x) + ' ' + str(y), inst['new_parts'])
elif len(inst['new_parts']) == 1: # Some numpy bug couldn't reduce a len == 1 list
written = written + ' ' + str(inst['new_parts'][0])
written = written + ' ' + sizes[inst['size']]
remaining_keys = filter(lambda x: x not in ['vcenter', 'vout', 'vup', 'pixperunit', 'new_parts', 'size'], inst.keys())
for key in remaining_keys:
written = written + ' ' + key + ': ' + repr(inst[key])
written = written + '\n'
return written
def read_netlist(self, netlist):
"""
Reads a string representation of a module and converts it into
a module.
"""
# This newer version takes a few more lines, but it makes
# files about half-size.
global colors, xabstol
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
if netlist[0][0] == '[': # Prior to version 0.4 style netlist
netlist = eval(netlist[0]) # Convert to list
for part_line in netlist[0]:
part = eval('pieces.' + part_line[0] + '()')
part.name, angle, vrot, angle2, vrot2, delta, flip, part.ends, part.center = part_line
angle = float(angle)
vrot = np.array(vrot)/10.0
angle2 = float(angle2)
vrot2 = np.array(vrot2)/10.0
delta = np.array(delta)/10.0
flip = float(flip)
part.ends = map(lambda x: (np.array(x[0])/10.0, np.array(x[1])/10.0, np.array(x[2])/10.0), part.ends)
part.center = np.array(part.center)/10.0
glPushMatrix()
glLoadIdentity()
glRotatef(angle, vrot[0], vrot[1], vrot[2])
glRotatef(angle2, vrot2[0], vrot2[1], vrot2[2])
glTranslatef(-delta[0], -delta[1], -delta[2])
part.matrix = glGetFloatv(GL_MODELVIEW_MATRIX)
glPopMatrix()
part.draw()
self.netlist.append(part)
self.ends = copy.copy(netlist[1])
if len(netlist) > 2:
self.ends_types = copy.copy(netlist[2])
else:
self.ends_types = []
for end in self.ends:
done = 0
for count, part in enumerate(self.netlist):
for part_end in part.ends:
aligned_index = self.ends_aligned(part_end, np.array([end]), same_dir=1)
#if self.ends_aligned(part_end, end, same_dir=1):
if aligned_index > 0:
done = 1
self.ends_types.append(part.ends_types[count])
break
if done == 1:
break
elif netlist[0].strip() == 'v0.4': # prior to version 1.0 style netlist
#lines = netlist.split('\n')
line_number = 1
# Pieces
while line_number < len(netlist) and netlist[line_number][0:3] != 'end' and netlist[line_number][0:2] != 'i ':
part_line = netlist[line_number].split()
part = eval('pieces.' + part_line[0] + '()')
part.name = part_line[0]
len_configs = len(part_line) - 17 # 17 = name + 16 pos matrix
if len_configs > 0:
for config_index in range(len_configs):
part.configure[config_index] = part_line[1+config_index]
part.matrix = (np.array(map(lambda x: float(x), part_line[1+len_configs:]))/10.0).reshape((4,4))
part.calc_ends()
part.draw()
self.netlist.append(part)
line_number = line_number + 1
# Ends
self.ends = []
self.ends_types = []
while line_number < len(netlist) and netlist[line_number][0:2] != 'i ':
end_line = netlist[line_number].split()
self.ends_types.append(end_line[1])
num_end = np.array(map(lambda x: float(x), end_line[2:]))/10.0
self.ends.append((num_end[0:3], num_end[3:6], num_end[6:9]))
line_number = line_number + 1
# Instructions
sizes = {'f': 'full', 'r': 'fullr', 'q': 'quarter', 'h': 'halfh'}
while line_number < len(netlist):
line = netlist[line_number]
for size in sizes.keys():
sizei = line.find(' ' + size)
if line[sizei+2] != ' ' and line[sizei+2] != '\n':
sizei = -1
if sizei >= 0:
break
line_start = line[:sizei].split()[1:]
line_floats = map(lambda x: float(x), line_start[:10])
inst = {'vcenter': np.array(line_floats[0:3]),
'vout': np.array(line_floats[3:6]),
'vup': np.array(line_floats[6:9]),
'pixperunit': line_floats[9]}
inst['new_parts'] = map(lambda x: int(x), line_start[10:])
inst['size'] = sizes[size]
# Parse defaults
line = line[sizei+2:].strip()
if line:
line_split = line.split()
default_index = 0
while default_index < len(line_split):
ls_index = default_index + 1
while ls_index < len(line_split) and line_split[ls_index][-1] != ':':
ls_index = ls_index + 1
inst[line_split[default_index][:-1]] = eval(' '.join(line_split[default_index+1:ls_index]))
default_index = ls_index
# Convert draw_old_parts_from to submodel
if inst.has_key('draw_old_parts_from'):
if inst['draw_old_parts_from'] == 0: # a pop
inst['submodel'] = -1
else:
inst['submodel'] = 1
del inst['draw_old_parts_from']
self.instructions.append(inst)
line_number = line_number + 1
elif netlist[0].strip() == 'v1.0': # Latest style netlist
#lines = netlist.split('\n')
line_number = 1
sf = 1.0/pieces.sf
# Pieces
while line_number < len(netlist) and netlist[line_number][0:3] != 'end' and netlist[line_number][0:2] != 'i ':
part_line = netlist[line_number].split()
try:
part = eval('pieces.' + part_line[0] + '()')
except:
print 'Warning: ' + part_line[0] + ' missing'
part = None
if part:
part.name = part_line[0]