-
Notifications
You must be signed in to change notification settings - Fork 235
/
Copy pathviewer_bmesh.py
441 lines (338 loc) · 14.3 KB
/
viewer_bmesh.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
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# MK2
import numpy as np
import itertools
import collections
import random
from random import random as rnd_float
import bpy
from bpy.props import BoolProperty, StringProperty, BoolVectorProperty
from mathutils import Matrix, Vector
from sverchok.node_tree import SverchCustomTreeNode
from sverchok.data_structure import dataCorrect, fullList, updateNode
from sverchok.utils.sv_bmesh_utils import bmesh_from_pydata
from sverchok.utils.sv_viewer_utils import natural_plus_one, greek_alphabet
from sverchok.utils.sv_obj_helper import SvObjHelper, CALLBACK_OP, get_random_init_v3
from sverchok.utils.modules.sv_bmesh_ops import find_islands_treemap
# this implements a customized version of this import
# from sverchok.nodes.object_nodes.vertex_colors_mk3 import set_vertices
def get_vertex_color_layer(obj):
vcols = obj.data.vertex_colors
vertex_color = vcols.get('SvCol')
return vertex_color or vcols.new(name='SvCol')
def get_random_colors(n):
return [[rnd_float(),]*3 + [1.0] for i in range(n)]
def set_vertices(obj, islands):
vcols = obj.data.vertex_colors
loops = obj.data.loops
loop_count = len(loops)
# [x] generate random colors set for each island
num_colors = len(islands)
# [ ] set seed here
random_colors = get_random_colors(num_colors)
# [x] acquire vertex color layer from object
vertex_color = get_vertex_color_layer(obj)
# [x] generate mapping from index to island color
# this is the slower part
islands_lookup = {}
for isle_num, isle_set in islands.items():
for vert_idx in isle_set:
islands_lookup[vert_idx] = isle_num
vertex_index = np.zeros(loop_count, dtype=int)
loops.foreach_get("vertex_index", vertex_index)
num_components = 4 # ( r g b , not a )
colors = np.empty(loop_count * num_components, dtype=np.float32)
colors.shape = (loop_count, num_components)
idx_lookup = collections.defaultdict(list)
for idx, v_idx in enumerate(vertex_index):
idx_lookup[v_idx].append(idx)
for idx, island in islands_lookup.items():
colors[idx_lookup[idx]] = random_colors[island]
colors.shape = (loop_count * num_components,)
vertex_color.data.foreach_set("color", colors)
obj.data.update()
def default_mesh(name):
return bpy.data.meshes.new(name)
def make_bmesh_geometry(node, obj_index, context, verts, *topology):
collection = context.scene.collection
meshes = bpy.data.meshes
objects = bpy.data.objects
islands = None
edges, faces, materials, matrix = topology
name = f'{node.basedata_name}.{obj_index:04d}'
if name in objects:
sv_object = objects[name]
else:
temp_mesh = default_mesh(name)
sv_object = objects.new(name, temp_mesh)
collection.objects.link(sv_object)
# book-keeping via ID-props!? even this is can be broken by renames
sv_object['idx'] = obj_index
sv_object['madeby'] = node.name
sv_object['basedata_name'] = node.basedata_name
mesh = sv_object.data
current_count = len(mesh.vertices)
propose_count = len(verts)
difference = (propose_count - current_count)
''' With this mode you make a massive assumption about the
constant state of geometry. Assumes the count of verts
edges/faces stays the same, and only updates the locations
node.fixed_verts is not suitable for initial object creation
but if over time you find that the only change is going to be
vertices, this mode can be switched to to increase efficiency
'''
if node.fixed_verts and difference == 0:
f_v = list(itertools.chain.from_iterable(verts))
mesh.vertices.foreach_set('co', f_v)
mesh.update()
else:
''' get bmesh, write bmesh to obj, free bmesh'''
bm = bmesh_from_pydata(verts, edges, faces, normal_update=node.calc_normals)
if materials:
for face, material in zip(bm.faces[:], materials):
if material is not None:
face.material_index = material
bm.to_mesh(sv_object.data)
if node.randomize_vcol_islands:
islands = find_islands_treemap(bm)
bm.free()
sv_object.hide_select = False
if node.randomize_vcol_islands:
set_vertices(sv_object, islands)
if matrix:
# matrix = matrix_sanitizer(matrix)
if node.extended_matrix:
sv_object.data.transform(matrix)
sv_object.matrix_local = Matrix.Identity(4)
else:
sv_object.matrix_local = matrix
else:
sv_object.matrix_local = Matrix.Identity(4)
def make_bmesh_geometry_merged(node, obj_index, context, yielder_object):
scene = context.scene
collection = scene.collection
meshes = bpy.data.meshes
objects = bpy.data.objects
name = f'{node.basedata_name}.{obj_index:04d}'
if name in objects:
sv_object = objects[name]
else:
temp_mesh = default_mesh(name)
sv_object = objects.new(name, temp_mesh)
collection.objects.link(sv_object)
# book-keeping via ID-props!
sv_object['idx'] = obj_index
sv_object['madeby'] = node.name
sv_object['basedata_name'] = node.basedata_name
vert_count = 0
big_verts = []
big_edges = []
big_faces = []
big_materials = []
for result in yielder_object:
verts, topology = result
edges, faces, materials, matrix = topology
if matrix:
# matrix = matrix_sanitizer(matrix)
verts = [matrix @ Vector(v) for v in verts]
big_verts.extend(verts)
big_edges.extend([[a + vert_count, b + vert_count] for a, b in edges])
big_faces.extend([[j + vert_count for j in f] for f in faces])
big_materials.extend(materials)
vert_count += len(verts)
if node.fixed_verts and len(sv_object.data.vertices) == len(big_verts):
mesh = sv_object.data
f_v = list(itertools.chain.from_iterable(big_verts))
mesh.vertices.foreach_set('co', f_v)
mesh.update()
else:
''' get bmesh, write bmesh to obj, free bmesh'''
bm = bmesh_from_pydata(big_verts, big_edges, big_faces, normal_update=node.calc_normals)
if materials:
for face, material in zip(bm.faces[:], big_materials):
if material is not None:
face.material_index = material
bm.to_mesh(sv_object.data)
bm.free()
sv_object.hide_select = False
sv_object.matrix_local = Matrix.Identity(4)
class SvBmeshViewerNodeV28(SverchCustomTreeNode, bpy.types.Node, SvObjHelper):
""" bmv Generate Live geom """
bl_idname = 'SvBmeshViewerNodeV28'
bl_label = 'Viewer BMesh'
bl_icon = 'OUTLINER_OB_MESH'
sv_icon = 'SV_BMESH_VIEWER'
@property
def replacement_nodes(self):
return [('SvMeshViewer', None, None)]
grouping: BoolProperty(default=False, update=SvObjHelper.group_state_update_handler)
merge: BoolProperty(default=False, update=updateNode)
calc_normals: BoolProperty(default=False, update=updateNode)
fixed_verts: BoolProperty(
default=False,
description="Use only with unchanging topology")
autosmooth: BoolProperty(
default=False,
update=updateNode,
description="This auto sets all faces to smooth shade")
extended_matrix: BoolProperty(
default=False,
description='Allows mesh.transform(matrix) operation, quite fast!')
randomize_vcol_islands: BoolProperty(
default=False,
description="experimental option to find islands in the outputmesh and colour them randomly")
draw_3dpanel: BoolProperty(default=False,
update=lambda n, c: bpy.context.scene.sv_ui_node_props.update_properties())
show_wireframe: BoolProperty(default=False, update=updateNode, name="Show Edges")
def sv_init(self, context):
self.sv_init_helper_basedata_name()
self.inputs.new('SvVerticesSocket', 'vertices')
self.inputs.new('SvStringsSocket', 'edges')
self.inputs.new('SvStringsSocket', 'faces')
self.inputs.new('SvStringsSocket', 'material_idx')
self.inputs.new('SvMatrixSocket', 'matrix')
self.outputs.new('SvObjectSocket', "Objects")
def draw_buttons(self, context, layout):
self.draw_live_and_outliner(context, layout)
# additional UI options.
col = layout.column(align=True)
row = col.row(align=True)
row.prop(self, "grouping", text="Group", toggle=True)
row.prop(self, "merge", text="Merge", toggle=True)
self.draw_object_buttons(context, layout)
def draw_buttons_ext(self, context, layout):
# self.draw_buttons(context, layout)
self.draw_ext_object_buttons(context, layout)
col = layout.column(align=True)
box = col.box()
if box:
box.label(text='Beta options')
box.prop(self, 'extended_matrix', text='Extended Matrix')
box.prop(self, 'fixed_verts', text='Fixed vert count')
box.prop(self, 'autosmooth', text='smooth shade')
box.prop(self, 'calc_normals', text='calculate normals')
box.prop(self, 'layer_choice', text='layer')
box.prop(self, 'randomize_vcol_islands', text='randomize vcol islands')
box.prop(self, 'show_wireframe')
col.prop(self, 'draw_3dpanel')
def draw_label(self):
return f"BV {self.basedata_name}"
def draw_buttons_3dpanel(self, layout):
row = layout.row(align=True)
# row.alert = warning
row.prop(self, 'basedata_name', text='')
row.prop_search(self, 'material_pointer', bpy.data, 'materials', text='', icon='MATERIAL_DATA')
#row.operator('node.sv_callback_bmesh_viewer',text='',icon='RESTRICT_SELECT_OFF')
def get_geometry_from_sockets(self):
def get(socket_name):
return self.inputs[socket_name].sv_get(default=[])
mverts = get('vertices')
medges = get('edges')
mfaces = get('faces')
if 'material_idx' in self.inputs:
materials = get('material_idx')
else:
materials = []
mmtrix = get('matrix')
return mverts, medges, mfaces, materials, mmtrix
def get_structure(self, stype, sindex):
if not stype:
return []
try:
j = stype[sindex]
except IndexError:
j = []
finally:
return j
def process(self):
if not self.activate:
if self.outputs[0].is_linked:
self.outputs[0].sv_set(self.get_children())
return
mverts, *mrest = self.get_geometry_from_sockets()
def get_edges_faces_matrices(obj_index):
for geom in mrest:
yield self.get_structure(geom, obj_index)
# extend all non empty lists to longest of mverts or *mrest
maxlen = max(len(mverts), *(map(len, mrest)))
fullList(mverts, maxlen)
for idx in range(4):
if mrest[idx]:
fullList(mrest[idx], maxlen)
if self.merge:
obj_index = 0
def keep_yielding():
# this will yield all in one go.
for idx, Verts in enumerate(mverts):
if not Verts:
continue
data = get_edges_faces_matrices(idx)
yield (Verts, data)
yielder_object = keep_yielding()
make_bmesh_geometry_merged(self, obj_index, bpy.context, yielder_object)
else:
for obj_index, Verts in enumerate(mverts):
if not len(Verts) > 0:
continue
data = get_edges_faces_matrices(obj_index)
make_bmesh_geometry(self, obj_index, bpy.context, Verts, *data)
last_index = (len(mverts) - 1) if not self.merge else 0
self.remove_non_updated_objects(last_index)
objs = self.get_children()
if self.grouping:
self.to_collection(objs)
self.set_corresponding_materials()
self.set_autosmooth(objs)
self.set_wireframe_visibility(objs)
if self.outputs[0].is_linked:
self.outputs[0].sv_set(objs)
def set_autosmooth(self, objs):
if not self.autosmooth:
return
for obj in objs:
mesh = obj.data
smooth_states = [True] * len(mesh.polygons)
mesh.polygons.foreach_set('use_smooth', smooth_states)
mesh.update()
def set_wireframe_visibility(self, objs):
# this will only update the obj if there is a state change.
for obj in objs:
if obj.show_wire != self.show_wireframe:
obj.show_wire = self.show_wireframe
def add_material(self):
mat = bpy.data.materials.new('sv_material')
mat.use_nodes = True
mat.use_fake_user = True
nodes = mat.node_tree.nodes
self.material_pointer = mat
if bpy.context.scene.render.engine == 'CYCLES':
# add attr node to the left of diffuse BSDF + connect it
diffuse_node = nodes['Diffuse BSDF']
attr_node = nodes.new('ShaderNodeAttribute')
attr_node.location = (-170, 300)
attr_node.attribute_name = 'SvCol'
links = mat.node_tree.links
links.new(attr_node.outputs[0], diffuse_node.inputs[0])
def sv_copy(self, other):
dname = get_random_init_v3()
self.basedata_name = dname
def register():
bpy.utils.register_class(SvBmeshViewerNodeV28)
def unregister():
bpy.utils.unregister_class(SvBmeshViewerNodeV28)