2
# -*- coding: utf-8 -*-
4
# Copyright © 2012-2013 B. Clausius <barcc@gmx.de>
6
# This program is free software: you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation, either version 3 of the License, or
9
# (at your option) any later version.
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
# GNU General Public License for more details.
16
# You should have received a copy of the GNU General Public License
17
# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
from __future__ import print_function, division, unicode_literals
22
from math import sin, cos, pi, sqrt
24
import cPickle as pickle
28
from .debug import DEBUG, DEBUG_NOLABEL, DEBUG_NOBEVEL, DEBUG_INNER
29
from .config import MODELS_DIR
42
def __init__(self, symbol, vindices=None, vertices=None):
44
self.vindices = vindices
45
self.vertices = vertices
47
self.scaled = None # Face with indices for beveled_vertices
48
self.faces = None # adjacent faces
51
return 'Face({}, {})'.format(self.symbol, self.vindices)
54
vertices = self.vertices if self.vindices is None else self.vindices
56
for v2 in vertices[1:]:
61
def center(self, vertices=None):
63
vertices = self.vertices
64
if self.vindices is not None:
65
vertices = [vertices[vi] for vi in self.vindices]
66
return [sum(_i)/len(_i) for _i in zip(*vertices)]
68
def create_scaled(self, vertices, factor, new_vertices):
70
for vi in self.vindices:
71
# Scale orthogonal to the center
72
bv = [(v-c)*factor+c for v, c in zip(vertices[vi], self.center(vertices))]
73
bf.append(len(new_vertices))
74
new_vertices.append(bv)
75
self.scaled = Face(self.symbol, bf)
77
def translated(self, vertices, offset, factor=1):
80
for v in self.vertices:
81
tv = [xv+xo*factor for xv, xo in zip(v, offset)]
84
for vi in self.vindices:
85
tv = [xv+xo*factor for xv, xo in zip(vertices[vi], offset)]
87
return Face(self.symbol, vertices=tf)
90
if self.vindices is not None:
91
vlen = len(self.vindices)
92
self.vindices = [self.vindices[(i-n)%vlen] for i in range(vlen)]
93
elif self.vertices is not None:
94
vlen = len(self.vertices)
95
self.vertices = [self.vertices[(i-n)%vlen] for i in range(vlen)]
97
def init_adjacent_faces(self, faces):
99
for edge in self.edges():
100
edge = tuple(reversed(edge))
107
self.faces.append(f.symbol)
110
assert False, 'No adjacent face for edge: ' + str((self, edge, faces))
111
assert len(self.faces) == len(self.vindices)
114
self.vertices.reverse()
117
class Block (object):
118
def __init__(self, model, index, data=None):
120
self.__dict__.update(data)
123
self.indices = (index % model.sizes[0],
124
index // model.sizes[0] % model.sizes[1],
125
index // model.sizes[0] // model.sizes[1])
126
self.coords = [float(2*idx - model.sizes[i] + 1) for i, idx in enumerate(self.indices)]
127
self.coords.append(1.)
128
self.symbol_to_move = {}
129
self.visible_faces = []
130
self.visible_glfaces = []
131
for symbol in model.faces:
132
mdir = symbol not in model.symbols
133
maxis = model.symbols.index(model.invers_symbols[symbol] if mdir else symbol)
134
mslice = self.axis_to_slice(maxis)
135
self.symbol_to_move[symbol] = (maxis, mslice, mdir)
136
if mslice == (model.sizes[maxis]-1 if mdir else 0):
137
self.visible_faces.append(symbol)
138
self.inplace_rotations = []
140
def init_inplace_rotations(self, model):
141
for (blocknum, rotsym), blocknum2 in model.rotated_position.items():
142
if blocknum == blocknum2 == self.index and rotsym:
143
self.inplace_rotations.append(rotsym)
144
self.inplace_rotations = sorted(self.inplace_rotations, key=len)[:2]
147
return 'Block({}, {}, {}, {})'.format(self.index, self.indices, self.coords, self.visible_faces)
149
def axis_to_slice(self, maxis):
150
return self.indices[maxis]
153
class Geometry (object):
154
def __init__(self, vertices=None, faces=None, bevel_factor=None):
155
self.vertices = vertices
157
self.beveled_vertices = [] # list of vertex positions
159
for f in self.faces.values():
160
f.normal = f.center(self.vertices)
161
f.create_scaled(self.vertices, bevel_factor, self.beveled_vertices)
162
f.init_adjacent_faces(self.faces.values())
164
def create_permutation(self, matrix):
165
m = np.matrix(matrix)
167
for i, v in enumerate(self.vertices):
168
rv = (m * np.matrix(v+[0]).T).T.tolist()[0][:-1]
169
ri = self.vertices.index(rv)
172
for sym, f in self.faces.items():
173
rvindices = [indices[i] for i in f.vindices]
174
for rsym, rf in self.faces.items():
175
if set(rvindices) == set(rf.vindices):
176
symbols[rsym.lower()] = sym.lower()
180
class EmptyModel (object):
189
bounding_sphere_radius = 1.
192
def __init__(self, *unused_args):
193
# The arrays should never be empty. For empty numpy-arrays it is
194
# not possible to get the C-address in module gldraw (compiled mode).
195
# The fields can only become empty when certain debugging options are active.
196
glvertices = np.array([0], dtype='f')
197
glnormals = np.array([0], dtype='f')
198
glcolors = np.array([0], dtype=np.ubyte)
199
gltexpostiled = np.array([0], dtype='f')
200
gltexposmosaic = np.array([0], dtype='f')
201
self.gldata = [glvertices, glnormals, glcolors, gltexpostiled, gltexposmosaic]
204
return tuple(self.gldata) + ([], 0, 0)
206
def gl_pick_data(self, unused_selection_mode):
207
# pylint: disable=W0201
208
self.gl_pick_vertices = np.array([0], dtype='f')
209
self.gl_pick_colors = np.array([0], dtype=np.ubyte)
210
return self.gl_pick_vertices, self.gl_pick_colors
213
class BrickModel (object):
215
mformat = _(u'{0}×{1}×{2}-Brick')
216
symmetry = 180., 180., 180.
218
axes = [(-1,0,0), (0,-1,0), (0,0,-1)] # pylint: disable=C0324
224
#### geometry of the cube ####
226
# indices orientation face symbols
229
# | 6------7 | +---+---+---+---+
230
# | | | | o--x | L | F | R | B |
231
# 0-|----1 | \ +---+---+---+---+
236
# vertex-positions, used for unbeveled faces and picking
237
vertices=[[-1,-1,-1], [1,-1,-1], [-1, 1,-1], [1, 1,-1],
238
[-1,-1, 1], [1,-1, 1], [-1, 1, 1], [1, 1, 1]],
239
# vertex-indices for unbeveled faces, used for picking
240
faces={b'U': Face(b'U', [2,6,7,3]), b'D': Face(b'D', [4,0,1,5]),
241
b'L': Face(b'L', [2,0,4,6]), b'R': Face(b'R', [7,5,1,3]),
242
b'F': Face(b'F', [6,4,5,7]), b'B': Face(b'B', [3,1,0,2])},
245
texpos_tiled = [(0, 0), (0, 1), (1, 1), (1, 0)]
246
face_axes = {b'U': (0, 2), b'D': (0, 2),
247
b'L': (2, 1), b'R': (2, 1),
248
b'F': (0, 1), b'B': (0, 1)}
250
def __init__(self, size, mirror_distance):
251
self.sizes = self.norm_sizes(size) # tuple of lenght 3
252
assert len(self.sizes) == 3
253
for _size in self.sizes:
254
assert 1 <= _size <= 10
255
self.size = size # derived classes can assign a different value
256
self.mirror_distance = mirror_distance and mirror_distance * max(self.sizes)
258
#TODO: The bounding_sphere_radius is optimised for the far clipping plane,
259
# for the near clipping plane the radius without the mirror_distance
260
# would be sufficient.
262
sm = s + (self.mirror_distance or 0)
263
self.bounding_sphere_radius = sqrt(2*s*s + sm*sm)
265
self.invers_symbols = {}
266
for sym, isym in zip(self.symbols, self.symbolsI):
267
self.invers_symbols[sym] = isym
268
self.invers_symbols[isym] = sym
269
self.axesI = [tuple(-x for x in a) for a in self.axes]
271
self.normal_rotation_symbols = {}
272
self.rotation_matrices = {}
273
self._init_rotations()
274
self.face_permutations = self._create_permutations()
277
global modeldata_cache
279
modeldata = modeldata_cache[(self.type, self.sizes)]
281
datafilename = self.get_datafilename(self.sizes)
282
datafilename = os.path.join(MODELS_DIR, datafilename)
283
with open(datafilename, 'rb') as datafile:
284
modeldata = pickle.load(datafile)
285
modeldata_cache = modeldata
286
modeldata = modeldata_cache[(self.type, self.sizes)]
287
self.blocks = [Block(None, None, data=data) for data in modeldata[b'blocks']]
288
self.rotated_position = modeldata[b'rotated_position']
289
self.gl_never_visible_face = self._gl_never_visible_face_cached
291
nblocks = self.sizes[0] * self.sizes[1] * self.sizes[2]
292
self.blocks = [Block(self, i) for i in range(nblocks)]
293
self.rotated_position = self._create_rotated_position()
294
for block in self.blocks:
295
block.init_inplace_rotations(self)
296
self.gl_never_visible_face = self._gl_never_visible_face_create_cache
298
self.pick_vector = {}
299
for maxis, axis in enumerate(self.axes):
300
for symbol, face in self.geom.faces.items():
301
self.pick_vector[maxis, False, symbol] = np.cross(axis, face.normal).tolist()
302
self.pick_vector[maxis, True, symbol] = np.cross(face.normal, axis).tolist()
304
self.pick_data = [()] # list of (maxis, mslice, mdir, face, center, block, symbol)
306
# List of numpy-arrays
307
# the gldraw module uses C-pointers to the data, so it is essential
308
# that the arrays do not get lost
312
def norm_sizes(cls, sizes):
316
def displaystring(cls, size):
317
return cls.mformat.format(*size)
319
def __unicode__(self):
320
return self.displaystring(self.sizes)
323
def _create_rotation(cls, axis, angle):
324
angle = angle / 180. * pi
332
[n1*n1*e_ca + ca, n1*n2*e_ca - n3*sa, n1*n3*e_ca + n2*sa, 0.],
333
[n2*n1*e_ca + n3*sa, n2*n2*e_ca + ca, n2*n3*e_ca - n1*sa, 0.],
334
[n3*n1*e_ca - n2*sa, n3*n2*e_ca + n1*sa, n3*n3*e_ca + ca, 0.],
337
#XXX: try to keep the matrix clean
341
if abs(m.A[y][x]) < cls.epsilon:
346
def _matrix_equal(cls, m1, m2):
347
assert m1.shape == m2.shape, (m1, m2)
351
if abs(m1.A[y][x] - m2.A[y][x]) > cls.epsilon:
355
def _init_rotations(self):
356
prim = [(sym, self._create_rotation(axis, angle))
357
for axis, sym, angle in zip(self.axes + self.axesI,
358
self.symbols + self.symbolsI,
359
self.symmetry + self.symmetry)]
360
self.normal_rotation_symbols[b''] = b''
361
transform = [(b'', np.matrix(np.identity(4)))]
363
transform.append((sp, p))
364
for sm, m in transform:
368
for st, t in transform:
369
if self._matrix_equal(t, n):
370
self.normal_rotation_symbols[sn] = st
373
self.normal_rotation_symbols[sn] = sn
374
transform.append((sn, n))
375
for sm, m in transform:
376
self.rotation_matrices[sm] = m.A.tolist()
378
def _create_permutations(self):
379
face_permutations = {}
380
for msym, matrix in self.rotation_matrices.items():
381
face_permutations[msym] = self.geom.create_permutation(matrix)
382
return face_permutations
384
def _create_rotated_position(self):
385
rotated_position = {}
386
for b, block in enumerate(self.blocks):
387
for sym, rotation in self.rotation_matrices.items():
388
coords = (np.matrix([block.coords]) * rotation).A.tolist()[0]
389
for p, pos in enumerate(self.blocks):
390
if pos.coords == coords:
391
rotated_position[b, sym] = p
394
assert False, 'not a permutation'
395
return rotated_position
398
def get_datafilename(cls, sizes):
399
x, y, unused_z = sizes
401
return b'mdata_01-02'
403
return b'mdata_{:02}_{}'.format(x, 0 if x<=5 else y%2 if x<=8 else y%3)
405
def get_savedata(self):
406
blocks = [block.__dict__ for block in self.blocks]
407
return {b'blocks': blocks, b'rotated_position': self.rotated_position}
409
def norm_symbol(self, sym):
411
return self.normal_rotation_symbols[sym]
416
new_sym = self.normal_rotation_symbols[new_sym+c]
418
raise ValueError('invalid symbol:', sym)
421
def block_indices_to_index(self, indices):
422
indices = tuple(indices)
423
for b, block in enumerate(self.blocks):
424
if block.indices == indices:
426
raise ValueError('Invalid block indices:', indices)
428
def rotation_symbolic_to_matrix(self, block, sym):
429
m = self.rotation_matrices[sym][:]
430
m[3] = self.blocks[block].coords
433
def block_symbolic_to_block_index(self, symblock):
434
indices = [1] * len(self.axes)
435
for match in re.finditer(r'(.)(\d*)', symblock):
436
blockface, blockslice = match.groups()
437
blockface = blockface.upper()
438
blockslicenum = int(blockslice)-1 if blockslice else 0
439
if blockface in self.symbolsI:
440
axis = self.symbolsI.index(blockface)
441
blockslicenum = self.sizes[axis]-1 - blockslicenum
442
blockface = self.invers_symbols[blockface]
444
axis = self.symbols.index(blockface)
445
indices[axis] = blockslicenum
446
return self.block_indices_to_index(indices)
448
def block_index_to_block_symbolic(self, blockpos, rotsym):
449
def axisidx_to_sym(axis, idx):
450
if idx <= self.sizes[axis] // 2:
451
sym = self.symbols[axis]
453
idx = self.sizes[axis]-1 - idx
454
sym = self.symbolsI[axis]
457
# skip idx for corners
458
return sym, self.face_symbolic_to_face_color(sym, rotsym)
459
elif idx == 1 and self.sizes[axis] == 3:
460
# for size == 3 there is only one edge
463
# symbol with index to distinguish edge, but no color because the face is not visible
464
return sym + str(idx+1), '?'
465
x, y, z = self.blocks[blockpos].indices
466
symx, colorsymx = axisidx_to_sym(0, x)
467
symy, colorsymy = axisidx_to_sym(1, y)
468
symz, colorsymz = axisidx_to_sym(2, z)
469
return symx + symy + symz, colorsymx + colorsymy + colorsymz
471
def face_symbolic_to_face_color(self, face, rot):
472
for k, v in self.face_permutations[rot].items():
476
assert False, (face, rot)
478
def rotate_symbolic(self, axis, rdir, block, sym):
479
rsym = (self.symbols if not rdir else self.symbolsI)[axis]
480
block = self.rotated_position[block, rsym]
481
sym = self.norm_symbol(sym + rsym)
484
def rotate_move(self, complete_move, move):
485
caxis, unused_cslice, cdir = complete_move
486
maxis, mslice, mdir = move
487
caxissym = (self.symbols if cdir else self.symbolsI)[caxis]
488
maxissym = (self.symbols if not mdir else self.symbolsI)[maxis]
489
raxissym = self.face_permutations[caxissym][maxissym.lower()].upper()
490
rdir = raxissym not in self.symbols
491
raxis = self.symbols.index(self.invers_symbols[raxissym] if rdir else raxissym)
493
mslice = self.sizes[raxis] - 1 - mslice
494
return raxis, mslice, rdir
497
def get_selected_move(block, face, edgeno):
498
'''block: a block in the rotation slice
499
face -> edgeno is the direction of slice rotation
501
edgeno = (edgeno - 1) % 4
502
rotation_symbol = face.faces[edgeno]
503
return block.symbol_to_move[rotation_symbol]
505
def compare_move_to_pick(self, maxis, unused_mslice, mdir, face, faceedge):
506
axis = self.axes[maxis]
508
vpick = np.cross(axis, face.normal).tolist()
510
vpick = np.cross(face.normal, axis).tolist()
511
#TODO: rather test whether face.center+pick_vector intersects with the edge
512
return vpick == faceedge.normal
515
def get_selected_move_center(block, face):
516
maxis, mslice, mdir = block.symbol_to_move[face.symbol]
517
return maxis, mslice, not mdir
519
def _gl_never_visible_face_cached(self, block, face): # pylint: disable=R0201
520
return face not in block.visible_glfaces
522
def _gl_never_visible_face_create_cache(self, block, face):
523
if not self._gl_never_visible_face_calculated(block, face):
524
block.visible_glfaces.append(face)
525
# create the cache and discard the real gl face
528
def _gl_never_visible_face_calculated(self, block, face):
529
if max(self.sizes) == 2 and self.sizes.count(2) >= 2:
530
#FIXME: the algorithm to detect invisible faces is wrong,
531
# deactivate the test for the cubes that are most affected.
533
vertices = self.geom.beveled_vertices
535
sqr0 = self.sizes[0] * self.sizes[0]
536
sqr1 = self.sizes[1] * self.sizes[1]
537
sqr2 = self.sizes[2] * self.sizes[2]
539
v = [vk+ck for vk, ck in zip(vertices[vi], c)]
540
if (v[0]*v[0] + v[1]*v[1] > min(sqr0, sqr1) or
541
v[0]*v[0] + v[2]*v[2] > min(sqr0, sqr2) or
542
v[1]*v[1] + v[2]*v[2] > min(sqr1, sqr2)):
546
def texpos_tiled_to_mosaic(self, block, face, texpos_tiled):
547
axisx, axisy = self.face_axes[face.symbol]
548
sizex, sizey = self.sizes[axisx], self.sizes[axisy]
549
tptx, tpty = texpos_tiled
551
vertices = [self.geom.vertices[vi] for vi in face.vindices]
552
subx = [(c1-c0)/2 for c0, c1 in zip(vertices[0], vertices[-1])]
553
suby = [(c1-c0)/2 for c0, c1 in zip(vertices[0], vertices[1])]
554
coords = block.coords[:3]
555
tpmx = sum(tc*bc for tc, bc in zip(subx, coords))
556
tpmy = sum(tc*bc for tc, bc in zip(suby, coords))
558
texx = ((sizex-1 + tpmx) / 2. + tptx) / sizex
559
texy = ((sizey-1 + tpmy) / 2. + tpty) / sizey
562
def gl_label_quads(self, block, visible):
566
if block.indices[i] in [0, self.sizes[i]-1]:
568
if DEBUG_NOLABEL and visible:
570
if DEBUG_NOBEVEL and not visible:
572
for faceno, symbol in enumerate(self.faces):
573
face = self.geom.faces[symbol]
574
if (symbol in block.visible_faces) != visible:
577
if self.gl_never_visible_face(block, f.vindices):
579
for i, vi in enumerate(f.vindices):
580
v = self.geom.beveled_vertices[vi]
581
texpos_tiled = self.texpos_tiled[i]
582
texpos_mosaic = self.texpos_tiled_to_mosaic(block, face, texpos_tiled)
583
yield (v, face.normal, faceno, texpos_tiled, texpos_mosaic)
584
if self.mirror_distance is not None and visible:
585
f = f.translated(self.geom.beveled_vertices, face.normal, self.mirror_distance)
587
for i, v in enumerate(f.vertices):
588
tptx, tpty = self.texpos_tiled[i]
589
texpos_tiled = 1. - tptx, tpty
590
texpos_mosaic = self.texpos_tiled_to_mosaic(block, face, texpos_tiled)
591
yield (v, face.normal, faceno, texpos_tiled, texpos_mosaic)
593
def gl_beveled_quads(self, block):
594
'''For every edge create a face'''
598
if block.indices[i] in [0, self.sizes[i]-1]:
602
for symbol, f in self.geom.faces.items():
603
for symbol2, (vi, vi2) in zip(f.faces, f.edges()):
604
if symbol >= symbol2: # find the corners only once, no matter in which order
606
f2 = self.geom.faces[symbol2]
607
# f and f2 have a common edge (vi,vi2)
608
# we now need the vertices and normals of the adjacent labels.
609
# remember, the face must be counterclockwise!
610
bvi1 = f.scaled.vindices[f.vindices.index(vi2)]
611
bvi2 = f.scaled.vindices[f.vindices.index(vi)]
612
bvi3 = f2.scaled.vindices[f2.vindices.index(vi)]
613
bvi4 = f2.scaled.vindices[f2.vindices.index(vi2)]
614
if self.gl_never_visible_face(block, [bvi1, bvi2, bvi3, bvi4]):
616
n = Face('', [bvi1, bvi2, bvi3, bvi4]).center(self.geom.beveled_vertices)
617
yield self.geom.beveled_vertices[bvi1], n
618
yield self.geom.beveled_vertices[bvi2], n
619
yield self.geom.beveled_vertices[bvi3], n
620
yield self.geom.beveled_vertices[bvi4], n
622
def gl_beveled_triangles(self, block):
623
'''For every corner create a face'''
627
if block.indices[i] in [0, self.sizes[i]-1]:
631
for vi in range(len(self.geom.vertices)):
632
bf = [] # one beveled face for each vertex
633
for face_first in self.geom.faces.values():
634
if vi in face_first.vindices:
635
# we now have one adjacent face, this code should be reached if the model is valid
642
ivi = face.vindices.index(vi)
643
bvi = face.scaled.vindices[ivi]
644
bf.insert(0, bvi) # we need counterclockwise order
645
symbol = face.faces[ivi]
646
face = self.geom.faces[symbol]
647
## we now have the clockwise next face
648
if face_first.symbol == face.symbol:
650
if self.gl_never_visible_face(block, bf):
652
n = Face('', bf).center(self.geom.beveled_vertices)
654
yield self.geom.beveled_vertices[vi], n
656
def gl_pick_triangles(self, selection_mode):
657
def edge_center(v1, v2):
658
return [(_v1 + _v2) / 2 for _v1, _v2 in zip(v1, v2)]
659
for block in self.blocks:
662
if block.indices[i] in [0, self.sizes[i]-1]:
664
for face, symbol in enumerate(self.faces):
665
f = self.geom.faces[symbol]
666
assert f.symbol == symbol
667
if symbol not in block.visible_faces:
671
if _sym in block.visible_faces:
673
ft = f.translated(self.geom.vertices, block.coords)
675
if self.mirror_distance is None:
678
ftt = ft.translated(None, f.normal, self.mirror_distance)
681
if selection_mode == 1 and cnt_n == 0:
682
maxis, mslice, mdir = self.get_selected_move_center(block, f)
683
color = len(self.pick_data)
684
self.pick_data.append((maxis, mslice, mdir, face, True, block, symbol, None, None))
685
for i in (0,1,2, 2,3,0): # pylint: disable=C0324
686
yield color, ft.vertices[i]
687
if self.mirror_distance is not None:
688
color = len(self.pick_data)
689
self.pick_data.append((maxis, mslice, not mdir, face, True, block, symbol, None, None))
690
for i in (0,2,1, 2,0,3): # pylint: disable=C0324
691
yield color, ftt.vertices[i]
692
elif selection_mode == 1 and cnt_n == 1 and cnt_faces == 2:
693
for edgeno, symboledge in enumerate(f.faces): # find the other face on the block
694
if symboledge in block.visible_faces:
697
edgeno = symboledge = None
699
maxis, mslice, mdir = self.get_selected_move(block, f, edgeno)
701
assert self.compare_move_to_pick(maxis, mslice, mdir, f, self.geom.faces[symboledge])
702
ec = edge_center(*list(ft.edges())[edgeno])
703
color = len(self.pick_data)
704
self.pick_data.append((maxis, mslice, mdir, face, False, block, symbol, vc, ec))
705
for i in (0,1,2, 2,3,0): # pylint: disable=C0324
706
yield color, ft.vertices[i]
707
if self.mirror_distance is not None:
708
for i in (0,2,1, 2,0,3): # pylint: disable=C0324
709
yield color, ftt.vertices[i]
710
elif selection_mode == 1 and cnt_n == 2 and cnt_faces == 3:
711
# find the two other faces on the block
712
for i1, symboledge1 in enumerate(f.faces): # find the other face on the block
714
symboledge2 = f.faces[i2]
715
visible_faces = block.visible_faces
716
if symboledge1 in visible_faces and symboledge2 in visible_faces:
719
i1 = symboledge1 = None
721
symboledges = (i1, symboledge1, i1-1), (i2, symboledge2, i2)
722
for edgeno, symboledge, offset in symboledges:
723
maxis, mslice, mdir = self.get_selected_move(block, f, edgeno)
725
assert self.compare_move_to_pick(maxis, mslice, mdir, f, self.geom.faces[symboledge])
726
ec = edge_center(*list(ft.edges())[edgeno])
727
color = len(self.pick_data)
728
self.pick_data.append((maxis, mslice, mdir, face, False, block, symbol, vc, ec))
730
yield color, ft.vertices[(i+offset)%4]
731
if self.mirror_distance is not None:
733
yield color, ftt.vertices[(i+offset)%4]
735
for edgeno, (edge, edget) in enumerate(zip(ft.edges(), ftt.edges())):
736
maxis, mslice, mdir = self.get_selected_move(block, f, edgeno)
737
ec = edge_center(*edge)
738
color = len(self.pick_data)
739
self.pick_data.append((maxis, mslice, mdir, face, False, block, symbol, vc, ec))
740
# pylint: disable=C0321
741
yield color, vc; yield color, edge[0]; yield color, edge[1]
742
if self.mirror_distance is not None:
743
yield color, vct; yield color, edget[1]; yield color, edget[0]
751
# list of (labelinfo, bevelqinfo, beveltinfo)
752
blockinfos = [[[], 0, 0] for unused_b in self.blocks]
755
for iblock, block in enumerate(self.blocks):
759
for v, n, f, tpt, tpm in self.gl_label_quads(block, True):
766
facesinfo.append((cnt, f))
770
facesinfo[-1] = (cnt, f)
771
blockinfos[iblock][0][:] = facesinfo
773
idx_vbevelq = len(vertices)
774
for iblock, block in enumerate(self.blocks):
776
for v, n in self.gl_beveled_quads(block):
780
for v, n, f, tpt, tpm in self.gl_label_quads(block, False):
784
blockinfos[iblock][1] = cnt
786
idx_vbevelt = len(vertices)
787
for iblock, block in enumerate(self.blocks):
789
for v, n in self.gl_beveled_triangles(block):
793
blockinfos[iblock][2] = cnt
795
# The arrays should never be empty. For empty numpy-arrays it is
796
# not possible to get the C-address in module gldraw (compiled mode).
797
# The fields can only become empty when certain debugging options are active.
798
glvertices = np.array(vertices or [0], dtype='f')
799
glnormals = np.array(normals or [0], dtype='f')
800
glcolors = np.array(colors or [0], dtype=np.ubyte)
801
gltexpostiled = np.array(texpost or [0], dtype='f')
802
gltexposmosaic = np.array(texposm or [0], dtype='f')
803
self.gldata.append(glvertices)
804
self.gldata.append(glnormals)
805
self.gldata.append(glcolors)
806
self.gldata.append(gltexpostiled)
807
self.gldata.append(gltexposmosaic)
809
assert idx_vbevelq * 2 == len(texpost) * 3 == len(texposm) * 3
810
assert sum([cnt for _bi in blockinfos for cnt, unused_faceno in _bi[0]]) * 3 == idx_vbevelq
812
return (glvertices, glnormals, glcolors, gltexpostiled, gltexposmosaic,
814
idx_vbevelq, idx_vbevelt
817
def gl_pick_data(self, selection_mode):
818
# Pick TRIANGLES vertices
821
for col, v in self.gl_pick_triangles(selection_mode):
823
color = [(col>>4) & 0xf0, (col) & 0xf0, (col<<4) & 0xf0]
825
glvertices = np.array(vertices or [0], dtype='f')
826
glcolors = np.array(colors or [0], dtype=np.ubyte)
827
# pylint: disable=W0201
828
self.gl_pick_vertices = glvertices
829
self.gl_pick_colors = glcolors
830
assert len(glvertices) == len(glcolors)
831
return glvertices, glcolors
834
class TowerModel (BrickModel):
836
mformat = _(u'{0}×{1}-Tower')
837
symmetry = 180., 90., 180.
839
def __init__(self, size, mirror_distance):
840
BrickModel.__init__(self, size, mirror_distance)
844
def norm_sizes(cls, sizes):
845
x, y, unused_z = sizes
849
class CubeModel (BrickModel):
851
mformat = _(u'{0}×{0}×{0}-Cube')
852
symmetry = 90., 90., 90.
854
def __init__(self, size, mirror_distance):
855
BrickModel.__init__(self, size, mirror_distance)
859
def norm_sizes(cls, sizes):
860
x, unused_y, unused_z = sizes
864
empty_model = EmptyModel()
865
models = [CubeModel, TowerModel, BrickModel]
867
def from_string(modelstr):
869
return '*', '*', (), None
870
re_model = r'''^(\w+)
880
match = re.match(re_model, modelstr, re.X)
882
raise ValueError('Invalid model: ' + modelstr)
883
mtype, width, height, depth, exp = match.group(1, 2, 4, 5, 6)
885
if mtype == Model.type:
889
raise ValueError('Unknown model type %r' % mtype)
890
def convert_if_int(value):
897
sizes = tuple(convert_if_int(s) for s in (width, height, depth))
898
return modelstr, Model, sizes, exp