-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathshapes.py
313 lines (241 loc) · 9.28 KB
/
shapes.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
import bpy
from math import pi
from math import cos
from math import sin
from math import sqrt
from copy import deepcopy
import numpy as np
from .enum_types import Axis
class BasicShape:
vertices = []
def __init__(self, scale=1.0, offset=(0.0, 0.0)):
# make vertices unique to instance
self.vertices = deepcopy(self.vertices)
self.scale(scale)
self.offset(offset)
self.center()
def scale(self, factor):
for verts in self.vertices:
for i, co in enumerate(verts):
verts[i] = co * factor
def offset(self, offset):
for verts in self.vertices:
for i, co in enumerate(verts):
verts[i] = co + offset[i]
@property
def size(self):
# TODO
return 1.0, 1.0
def center(self):
size_x, size_y = self.size
self.offset((size_x/-2.0, size_y/-2.0))
class Tris2D(BasicShape):
vertices = [
[0.0, 0.0],
[0.0, 1.0],
[1.0, 1.0],
]
class Quad2D(BasicShape):
vertices = deepcopy(Tris2D.vertices) + [deepcopy(Tris2D.vertices[-1]),
[Tris2D.vertices[-1][0], Tris2D.vertices[0][1]],
deepcopy(Tris2D.vertices[0])]
@property
def size(self):
low_left = self.vertices[0]
up_right = self.vertices[2]
return abs(up_right[0] - low_left[0]), abs(up_right[1] - low_left[1])
def frame_vertices(self, thickness=0.25):
inner = Quad2D(scale=1 - thickness)
inner.center()
verts = []
for i in range(0, 4, 3):
verts.append(self.vertices[i])
verts.append(self.vertices[i + 1])
verts.append(inner.vertices[i])
verts.append(inner.vertices[i])
verts.append(inner.vertices[i + 1])
verts.append(self.vertices[i + 1])
verts.append(self.vertices[i + 1])
verts.append(self.vertices[i + 2])
verts.append(inner.vertices[i + 1])
verts.append(inner.vertices[i + 1])
verts.append(inner.vertices[i + 2])
verts.append(self.vertices[i + 2])
return verts
class Rect2D(BasicShape):
# Coordinates (each one is a triangle).
vertices = [
[-0.5, -1.0],
[-0.5, 1.0],
[0.5, 1.0],
[0.5, 1.0],
[0.5, -1.0],
[-0.5, -1.0],
]
class Cross2D(BasicShape):
vertices = deepcopy(Rect2D.vertices) + [
[-1.0, -0.5],
[-1.0, 0.5],
[1.0, 0.5],
[1.0, 0.5],
[1.0, -0.5],
[-1.0, -0.5],
]
class Circle2D(BasicShape):
def __init__(self, scale=1.0, offset=(0.0, 0.0), segments=24):
self.segments = segments
self.vertices = []
if any(offset):
raise NotImplementedError
full_circle = 2 * pi
arc_len = full_circle / self.segments
for i in range(self.segments):
arc = arc_len * i
self.vertices.append([cos(arc) * scale, sin(arc * scale)])
arc = arc_len * (i + 1)
self.vertices.append([cos(arc) * scale, sin(arc) * scale])
self.vertices.append([0.0, 0.0])
@property
def size(self):
vert = self.vertices[0]
diameter = sqrt(pow(vert[0], 2) + pow(vert[1], 2))
return diameter, diameter
def frame_vertices(self, thickness=0.25):
scale = 1 - thickness
verts = []
inner = None
for vert in self.vertices:
if inner:
verts.append(vert)
verts.append(inner)
verts.append(vert)
inner = [vert[0] * scale, vert[1] * scale]
verts.append(inner)
return verts
class Sphere(BasicShape):
def __init__(self, scale=1.0, offset=(0.0, 0.0, 0.0), segments=24, rings=12):
self.segments = segments
self.vertices = []
full_circle = 2 * pi
arc_len = full_circle / self.segments
circle_verts = []
for i in range(self.segments):
arc = arc_len * i
circle_verts.append([cos(arc), sin(arc), 0.0])
arc = arc_len * (i + 1)
circle_verts.append([cos(arc), sin(arc), 0.0])
circle_verts.append([0.0, 0.0, 0.0])
upper = None
# TODO: better way of drawing a sphere
prev_height = 0
next_height = 0
prev_scale = scale
for _ in range(int(rings/2)):
next_height += 2 / rings
next_scale = sqrt(1 - next_height ** 2) * scale
for circle_vert in circle_verts:
if upper:
self.vertices.append([circle_vert[0] * prev_scale, circle_vert[1] * prev_scale, prev_height * scale])
self.vertices.append(upper)
self.vertices.append([circle_vert[0] * prev_scale, circle_vert[1] * prev_scale, prev_height * scale])
upper = [circle_vert[0] * next_scale, circle_vert[1] * next_scale, next_height * scale]
self.vertices.append(upper)
prev_height = next_height
prev_scale = next_scale
prev_height = 0
next_height = 0
prev_scale = scale
for _ in range(int(rings / 2)):
next_height -= 2 / rings
next_scale = sqrt(1 - next_height ** 2) * scale
for circle_vert in circle_verts:
if upper:
self.vertices.append([circle_vert[0] * prev_scale, circle_vert[1] * prev_scale, prev_height * scale])
self.vertices.append(upper)
self.vertices.append([circle_vert[0] * prev_scale, circle_vert[1] * prev_scale, prev_height * scale])
upper = [circle_vert[0] * next_scale, circle_vert[1] * next_scale, next_height * scale]
self.vertices.append(upper)
prev_height = next_height
prev_scale = next_scale
self.offset(offset)
def offset(self, offset):
for i, vert in enumerate(self.vertices):
self.vertices[i] = deepcopy(vert)
for j, offs in enumerate(offset):
self.vertices[i][j] += offs
@property
def size(self):
vert = self.vertices[0]
diameter = sqrt(pow(vert[0], 2) + pow(vert[1], 2))
return diameter, diameter
def frame_vertices(self, thickness=0.25):
scale = 1 - thickness
verts = []
inner = None
for vert in self.vertices:
if inner:
verts.append(vert)
verts.append(inner)
verts.append(vert)
inner = [vert[0] * scale, vert[1] * scale]
verts.append(inner)
return verts
class MeshShape3D(BasicShape):
def __init__(self, mesh, scale=1.0, vertex_groups=None, weight_threshold=0.2):
self._indices = []
self._obj = None
self.scale_factor = scale
self.tris_from_mesh(mesh, vertex_groups=vertex_groups, weight_threshold=weight_threshold)
@property
def vertices(self):
if not self._obj:
return []
dg = bpy.context.evaluated_depsgraph_get()
ob = self._obj.evaluated_get(dg)
mesh = ob.to_mesh()
mesh.calc_loop_triangles()
verts = np.array([ob.matrix_world @ mesh.vertices[i].co for i in self._indices], 'f')
# scale
average = np.average(verts, axis=0)
verts -= average
verts *= self.scale_factor
verts += average
return verts
def tris_from_mesh(self, obj, vertex_groups=[], weight_threshold=0.2):
self._obj = obj
mesh = self._obj.data
mesh.calc_loop_triangles()
self._indices = []
if vertex_groups:
group_idx = [obj.vertex_groups[vertex_group].index for vertex_group in vertex_groups]
for tris in mesh.loop_triangles:
if all(any(g.weight > weight_threshold for g in mesh.vertices[i].groups if g.group in group_idx) for i in tris.vertices):
self._indices.extend(tris.vertices)
else:
indices = np.empty((len(mesh.loop_triangles), 3), 'i')
mesh.loop_triangles.foreach_get(
"vertices", np.reshape(indices, len(mesh.loop_triangles) * 3))
self._indices = np.concatenate(indices)
class MeshShape2D(BasicShape):
def __init__(self, mesh, scale=1.0):
super().__init__(scale)
self.tris_from_mesh(mesh, scale=scale)
def tris_from_mesh(self, mesh, scale=100, matrix=None, view_axis=Axis.Y):
mesh.calc_loop_triangles()
vertices = np.empty((len(mesh.vertices), 3), 'f')
indices = np.empty((len(mesh.loop_triangles), 3), 'i')
mesh.vertices.foreach_get(
"co", np.reshape(vertices, len(mesh.vertices) * 3))
mesh.loop_triangles.foreach_get(
"vertices", np.reshape(indices, len(mesh.loop_triangles) * 3))
if matrix:
# we invert the matrix as we are facing the object
np_mat = np.array(matrix.normalized().inverted().to_3x3())
vertices *= matrix.to_scale()
np.copyto(vertices, vertices @ np_mat)
vertices += matrix.translation
# remove view axis
vertices = np.delete(vertices, view_axis.value, 1)
# scale
vertices *= scale
self.vertices = [vertices[i] for i in np.concatenate(indices)]