Export_3ds: Improved distance cue node search
[blender-addons.git] / io_scene_gltf2 / blender / exp / gltf2_blender_gather_tree.py
blobde12915c42b1b265e292d80559554b46c1c87dfa
1 # SPDX-FileCopyrightText: 2021 The glTF-Blender-IO authors
3 # SPDX-License-Identifier: Apache-2.0
5 import bpy
6 import uuid
7 import numpy as np
8 from mathutils import Quaternion, Matrix
9 from ...io.exp.gltf2_io_user_extensions import export_user_extensions
10 from ...io.com import gltf2_io
11 from ...io.imp.gltf2_io_binary import BinaryData
12 from ...io.com import gltf2_io_constants
13 from ...io.exp import gltf2_io_binary_data
14 from ..com.gltf2_blender_default import BLENDER_GLTF_SPECIAL_COLLECTION
15 from . import gltf2_blender_gather_accessors
16 from .gltf2_blender_gather_joints import gather_joint_vnode
18 class VExportNode:
20 OBJECT = 1
21 ARMATURE = 2
22 BONE = 3
23 LIGHT = 4
24 CAMERA = 5
25 COLLECTION = 6
26 INSTANCE = 7 # For instances of GN
28 INSTANCIER = 8
29 NOT_INSTANCIER = 9
30 INST_COLLECTION = 7
33 # Parent type, to be set on child regarding its parent
34 NO_PARENT = 54
35 PARENT_OBJECT = 50
36 PARENT_BONE = 51
37 PARENT_BONE_RELATIVE = 52
38 PARENT_ROOT_BONE = 53
39 PARENT_BONE_BONE = 55
42 # Children type
43 # Is used to split instance collection into 2 categories:
44 CHILDREN_REAL = 90
45 CHILDREN_IS_IN_COLLECTION = 91
48 def __init__(self):
49 self.children = []
50 self.children_type = {} # Used for children of instance collection
51 self.blender_type = None
52 self.matrix_world = None
53 self.parent_type = None
55 self.blender_object = None
56 self.blender_bone = None
57 self.leaf_reference = None # For leaf bones only
59 self.default_hide_viewport = False # Need to store the default value for meshes in case of animation baking on armature
61 self.force_as_empty = False # Used for instancer display
63 # Only for bone/bone and object parented to bone
64 self.parent_bone_uuid = None
66 # Only for bones
67 self.use_deform = None
69 # Only for armature
70 self.bones = {}
72 # For deformed object
73 self.armature = None # for deformed object and for bone
74 self.skin = None
76 # glTF
77 self.node = None
79 # For mesh instance data of GN instances
80 self.data = None
81 self.materials = None
83 self.is_instancier = VExportNode.NOT_INSTANCIER
85 def add_child(self, uuid):
86 self.children.append(uuid)
88 def set_blender_data(self, blender_object, blender_bone):
89 self.blender_object = blender_object
90 self.blender_bone = blender_bone
92 def recursive_display(self, tree, mode):
93 if mode == "simple":
94 for c in self.children:
95 print(tree.nodes[c].uuid, self.blender_object.name if self.blender_object is not None else "GN" + self.data.name, "/", self.blender_bone.name if self.blender_bone else "", "-->", tree.nodes[c].blender_object.name if tree.nodes[c].blender_object else "GN" + tree.nodes[c].data.name, "/", tree.nodes[c].blender_bone.name if tree.nodes[c].blender_bone else "" )
96 tree.nodes[c].recursive_display(tree, mode)
98 class VExportTree:
99 def __init__(self, export_settings):
100 self.nodes = {}
101 self.roots = []
103 self.export_settings = export_settings
105 self.tree_troncated = False
107 self.axis_basis_change = Matrix.Identity(4)
108 if self.export_settings['gltf_yup']:
109 self.axis_basis_change = Matrix(
110 ((1.0, 0.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0), (0.0, -1.0, 0.0, 0.0), (0.0, 0.0, 0.0, 1.0)))
112 def add_node(self, node):
113 self.nodes[node.uuid] = node
115 def add_children(self, uuid_parent, uuid_child):
116 self.nodes[uuid_parent].add_child(uuid_child)
118 def construct(self, blender_scene):
119 bpy.context.window.scene = blender_scene
120 depsgraph = bpy.context.evaluated_depsgraph_get()
122 # Gather parent/children information once, as calling bobj.children is
123 # very expensive operation : takes O(len(bpy.data.objects)) time.
124 # TODO : In case of full collection export, we should add children / collection in the same way
125 blender_children = dict()
126 for bobj in bpy.data.objects:
127 bparent = bobj.parent
128 blender_children.setdefault(bobj, [])
129 blender_children.setdefault(bparent, []).append(bobj)
131 if self.export_settings['gltf_hierarchy_full_collections'] is False:
132 scene_eval = blender_scene.evaluated_get(depsgraph=depsgraph)
133 for blender_object in [obj.original for obj in scene_eval.objects if obj.parent is None]:
134 self.recursive_node_traverse(blender_object, None, None, Matrix.Identity(4), False, blender_children)
135 else:
136 self.recursive_node_traverse(blender_scene.collection, None, None, Matrix.Identity(4), False, blender_children, is_collection=True)
138 def recursive_node_traverse(self, blender_object, blender_bone, parent_uuid, parent_coll_matrix_world, delta, blender_children, armature_uuid=None, dupli_world_matrix=None, data=None, original_object=None, is_collection=False, is_children_in_collection=False):
139 node = VExportNode()
140 node.uuid = str(uuid.uuid4())
141 node.parent_uuid = parent_uuid
142 node.set_blender_data(blender_object, blender_bone)
143 if blender_object is None:
144 node.data = data
145 node.original_object = original_object
147 # add to parent if needed
148 if parent_uuid is not None:
149 self.add_children(parent_uuid, node.uuid)
150 if self.nodes[parent_uuid].blender_type == VExportNode.INST_COLLECTION or original_object is not None:
151 self.nodes[parent_uuid].children_type[node.uuid] = VExportNode.CHILDREN_IS_IN_COLLECTION if is_children_in_collection is True else VExportNode.CHILDREN_REAL
152 else:
153 self.roots.append(node.uuid)
155 # Set blender type
156 if blender_object is None: #GN instance
157 node.blender_type = VExportNode.INSTANCE
158 elif blender_bone is not None:
159 node.blender_type = VExportNode.BONE
160 self.nodes[armature_uuid].bones[blender_bone.name] = node.uuid
161 node.use_deform = blender_bone.id_data.data.bones[blender_bone.name].use_deform
162 elif is_collection is True:
163 node.blender_type = VExportNode.COLLECTION
164 elif blender_object.type == "ARMATURE":
165 node.blender_type = VExportNode.ARMATURE
166 node.default_hide_viewport = blender_object.hide_viewport
167 elif blender_object.type == "CAMERA":
168 node.blender_type = VExportNode.CAMERA
169 elif blender_object.type == "LIGHT":
170 node.blender_type = VExportNode.LIGHT
171 elif blender_object.instance_type == "COLLECTION":
172 node.blender_type = VExportNode.INST_COLLECTION
173 node.default_hide_viewport = blender_object.hide_viewport
174 else:
175 node.blender_type = VExportNode.OBJECT
176 node.default_hide_viewport = blender_object.hide_viewport
178 # For meshes with armature modifier (parent is armature), keep armature uuid
179 if node.blender_type == VExportNode.OBJECT:
180 modifiers = {m.type: m for m in blender_object.modifiers}
181 if "ARMATURE" in modifiers and modifiers["ARMATURE"].object is not None:
182 if parent_uuid is None or not self.nodes[parent_uuid].blender_type == VExportNode.ARMATURE:
183 # correct workflow is to parent skinned mesh to armature, but ...
184 # all users don't use correct workflow
185 self.export_settings['log'].warning(
186 "Armature must be the parent of skinned mesh"
187 "Armature is selected by its name, but may be false in case of instances"
189 # Search an armature by name, and use the first found
190 # This will be done after all objects are setup
191 node.armature_needed = modifiers["ARMATURE"].object.name
192 else:
193 node.armature = parent_uuid
195 # For bones, store uuid of armature
196 if blender_bone is not None:
197 node.armature = armature_uuid
199 # for bone/bone parenting, store parent, this will help armature tree management
200 if parent_uuid is not None and self.nodes[parent_uuid].blender_type == VExportNode.BONE and node.blender_type == VExportNode.BONE:
201 node.parent_bone_uuid = parent_uuid
204 # Objects parented to bone
205 if parent_uuid is not None and self.nodes[parent_uuid].blender_type == VExportNode.BONE and node.blender_type != VExportNode.BONE:
206 node.parent_bone_uuid = parent_uuid
208 # World Matrix
210 # Delta is used when rest transforms are used for armatures
211 # Any children of objects parented to bones must have this delta (for grandchildren, etc...)
212 new_delta = False
214 # Store World Matrix for objects
215 if dupli_world_matrix is not None:
216 node.matrix_world = dupli_world_matrix
217 elif node.blender_type in [VExportNode.OBJECT, VExportNode.COLLECTION, VExportNode.INST_COLLECTION, VExportNode.ARMATURE, VExportNode.CAMERA, VExportNode.LIGHT]:
218 # Matrix World of object is expressed based on collection instance objects are
219 # So real world matrix is collection world_matrix @ "world_matrix" of object
220 if is_collection:
221 node.matrix_world = parent_coll_matrix_world.copy()
222 else:
223 node.matrix_world = parent_coll_matrix_world @ blender_object.matrix_world.copy()
225 # If object is parented to bone, and Rest pose is used for Armature, we need to keep the world matrix transformed relative relative to rest pose,
226 # not the current world matrix (relation to pose)
227 if parent_uuid and self.nodes[parent_uuid].blender_type == VExportNode.BONE and self.export_settings['gltf_rest_position_armature'] is True:
228 _blender_bone = self.nodes[parent_uuid].blender_bone
229 _pose = self.nodes[self.nodes[parent_uuid].armature].matrix_world @ _blender_bone.matrix @ self.axis_basis_change
230 _rest = self.nodes[self.nodes[parent_uuid].armature].matrix_world @ _blender_bone.bone.matrix_local @ self.axis_basis_change
231 _delta = _pose.inverted_safe() @ node.matrix_world
232 node.original_matrix_world = node.matrix_world.copy()
233 node.matrix_world = _rest @ _delta
234 new_delta = True
236 if node.blender_type == VExportNode.CAMERA and self.export_settings['gltf_cameras']:
237 if self.export_settings['gltf_yup']:
238 correction = Quaternion((2**0.5/2, -2**0.5/2, 0.0, 0.0))
239 else:
240 correction = Matrix.Identity(4).to_quaternion()
241 node.matrix_world @= correction.to_matrix().to_4x4()
242 elif node.blender_type == VExportNode.LIGHT and self.export_settings['gltf_lights']:
243 if self.export_settings['gltf_yup']:
244 correction = Quaternion((2**0.5/2, -2**0.5/2, 0.0, 0.0))
245 else:
246 correction = Matrix.Identity(4).to_quaternion()
247 node.matrix_world @= correction.to_matrix().to_4x4()
248 elif node.blender_type == VExportNode.BONE:
249 if self.export_settings['gltf_rest_position_armature'] is False:
250 # Use pose bone for TRS
251 node.matrix_world = self.nodes[node.armature].matrix_world @ blender_bone.matrix
252 if self.export_settings['gltf_leaf_bone'] is True:
253 node.matrix_world_tail = self.nodes[node.armature].matrix_world @ Matrix.Translation(blender_bone.tail)
254 node.matrix_world_tail = node.matrix_world_tail @ self.axis_basis_change
255 else:
256 # Use edit bone for TRS --> REST pose will be used
257 node.matrix_world = self.nodes[node.armature].matrix_world @ blender_bone.bone.matrix_local
258 # Tail will be set after, as we need to be in edit mode
259 node.matrix_world = node.matrix_world @ self.axis_basis_change
261 if delta is True:
262 _pose_parent = self.nodes[parent_uuid].original_matrix_world
263 _rest_parent = self.nodes[parent_uuid].matrix_world
264 _delta = _pose_parent.inverted_safe() @ node.matrix_world
265 node.original_matrix_world = node.matrix_world.copy()
266 node.matrix_world = _rest_parent @ _delta
268 # Force empty ?
269 # For duplis, if instancer is not display, we should create an empty
270 if blender_object and is_collection is False and blender_object.is_instancer is True and blender_object.show_instancer_for_render is False:
271 node.force_as_empty = True
273 # Storing this node
274 self.add_node(node)
276 ###### Manage children ######
278 # GN instance have no children
279 if blender_object is None:
280 return
282 # standard children (of object, or of instance collection)
283 if blender_bone is None and is_collection is False and blender_object.is_instancer is False:
284 for child_object in blender_children[blender_object]:
285 if child_object.parent_bone and child_object.parent_type in ("BONE", "BONE_RELATIVE"):
286 # Object parented to bones
287 # Will be manage later
288 continue
289 else:
290 # Classic parenting
292 # If we export full collection hierarchy, we need to ignore children that are not in the same collection
293 if self.export_settings['gltf_hierarchy_full_collections'] is True:
294 if child_object.users_collection[0].name != blender_object.users_collection[0].name:
295 continue
297 self.recursive_node_traverse(child_object, None, node.uuid, parent_coll_matrix_world, new_delta or delta, blender_children)
299 # Collections
300 if is_collection is False and (blender_object.instance_type == 'COLLECTION' and blender_object.instance_collection):
301 if self.export_settings['gltf_hierarchy_full_collections'] is False:
302 for dupli_object in blender_object.instance_collection.all_objects:
303 if dupli_object.parent is not None:
304 continue
305 self.recursive_node_traverse(dupli_object, None, node.uuid, node.matrix_world, new_delta or delta, blender_children, is_children_in_collection=True)
306 else:
307 # Manage children objects
308 for child in blender_object.instance_collection.objects:
309 if child.users_collection[0].name != blender_object.name:
310 continue
311 self.recursive_node_traverse(child, None, node.uuid, node.matrix_world, new_delta or delta, blender_children)
312 # Manage children collections
313 for child in blender_object.instance_collection.children:
314 self.recursive_node_traverse(child, None, node.uuid, node.matrix_world, new_delta or delta, blender_children, is_collection=True)
316 if is_collection is True: # Only for gltf_hierarchy_full_collections == True
317 # Manage children objects
318 for child in blender_object.objects:
319 if child.users_collection[0].name != blender_object.name:
320 continue
322 # Keep only object if it has no parent, or parent is not in the collection
323 if not (child.parent is None or child.parent.users_collection[0].name != blender_object.name):
324 continue
326 self.recursive_node_traverse(child, None, node.uuid, node.matrix_world, new_delta or delta, blender_children)
327 # Manage children collections
328 for child in blender_object.children:
329 self.recursive_node_traverse(child, None, node.uuid, node.matrix_world, new_delta or delta, blender_children, is_collection=True)
332 # Armature : children are bones with no parent
333 if is_collection is False and blender_object.type == "ARMATURE" and blender_bone is None:
334 for b in [b for b in blender_object.pose.bones if b.parent is None]:
335 self.recursive_node_traverse(blender_object, b, node.uuid, parent_coll_matrix_world, new_delta or delta, blender_children, node.uuid)
337 # Bones
338 if is_collection is False and blender_object.type == "ARMATURE" and blender_bone is not None:
339 for b in blender_bone.children:
340 self.recursive_node_traverse(blender_object, b, node.uuid, parent_coll_matrix_world, new_delta or delta, blender_children, armature_uuid)
342 # Object parented to bone
343 if is_collection is False and blender_bone is not None:
344 for child_object in [c for c in blender_children[blender_object] if c.parent_type == "BONE" and c.parent_bone is not None and c.parent_bone == blender_bone.name]:
345 self.recursive_node_traverse(child_object, None, node.uuid, parent_coll_matrix_world, new_delta or delta, blender_children)
347 # Duplis
348 if is_collection is False and blender_object.is_instancer is True and blender_object.instance_type != 'COLLECTION':
349 depsgraph = bpy.context.evaluated_depsgraph_get()
350 for (dupl, mat) in [(dup.object.original, dup.matrix_world.copy()) for dup in depsgraph.object_instances if dup.parent and id(dup.parent.original) == id(blender_object)]:
351 self.recursive_node_traverse(dupl, None, node.uuid, parent_coll_matrix_world, new_delta or delta, blender_children, dupli_world_matrix=mat)
353 # Geometry Nodes instances
354 if self.export_settings['gltf_gn_mesh'] is True:
355 # Do not force export as empty
356 # Because GN graph can have both geometry and instances
357 depsgraph = bpy.context.evaluated_depsgraph_get()
358 eval = blender_object.evaluated_get(depsgraph)
359 for inst in depsgraph.object_instances: # use only as iterator
360 if inst.parent == eval:
361 if not inst.is_instance:
362 continue
363 if type(inst.object.data).__name__ == "Mesh" and len(inst.object.data.vertices) == 0:
364 continue # This is nested instances, and this mesh has no vertices, so is an instancier for other instances
365 node.is_instancier = VExportNode.INSTANCIER
366 self.recursive_node_traverse(None, None, node.uuid, parent_coll_matrix_world, new_delta or delta, blender_children, dupli_world_matrix=inst.matrix_world.copy(), data=inst.object.data, original_object=blender_object, is_children_in_collection=True)
368 def get_all_objects(self):
369 return [n.uuid for n in self.nodes.values() if n.blender_type != VExportNode.BONE]
371 def get_all_bones(self, uuid): #For armature only
372 if not hasattr(self.nodes[uuid], "all_bones"):
373 if self.nodes[uuid].blender_type == VExportNode.ARMATURE:
374 def recursive_get_all_bones(uuid):
375 total = []
376 if self.nodes[uuid].blender_type == VExportNode.BONE:
377 total.append(uuid)
378 for child_uuid in self.nodes[uuid].children:
379 total.extend(recursive_get_all_bones(child_uuid))
381 return total
383 tot = []
384 for c_uuid in self.nodes[uuid].children:
385 tot.extend(recursive_get_all_bones(c_uuid))
386 self.nodes[uuid].all_bones = tot
387 return tot # Not really needed to return, we are just baking it before export really starts
388 else:
389 self.nodes[uuid].all_bones = []
390 return []
391 else:
392 return self.nodes[uuid].all_bones
394 def get_root_bones_uuid(self, uuid): #For armature only
395 if not hasattr(self.nodes[uuid], "root_bones_uuid"):
396 if self.nodes[uuid].blender_type == VExportNode.ARMATURE:
397 all_armature_children = self.nodes[uuid].children
398 self.nodes[uuid].root_bones_uuid = [c for c in all_armature_children if self.nodes[c].blender_type == VExportNode.BONE]
399 return self.nodes[uuid].root_bones_uuid # Not really needed to return, we are just baking it before export really starts
400 else:
401 self.nodes[uuid].root_bones_uuid = []
402 return []
403 else:
404 return self.nodes[uuid].root_bones_uuid
406 def get_all_node_of_type(self, node_type):
407 return [n.uuid for n in self.nodes.values() if n.blender_type == node_type]
409 def display(self, mode):
410 if mode == "simple":
411 for n in self.roots:
412 print(self.nodes[n].uuid, "Root", self.nodes[n].blender_object.name if self.nodes[n].blender_object else "GN instance", "/", self.nodes[n].blender_bone.name if self.nodes[n].blender_bone else "" )
413 self.nodes[n].recursive_display(self, mode)
415 def filter_tag(self):
416 roots = self.roots.copy()
417 for r in roots:
418 self.recursive_filter_tag(r, None)
420 def filter_perform(self):
421 roots = self.roots.copy()
422 for r in roots:
423 self.recursive_filter(r, None) # Root, so no parent
425 def filter(self):
426 self.filter_tag()
427 export_user_extensions('gather_tree_filter_tag_hook', self.export_settings, self)
428 self.filter_perform()
429 self.remove_empty_collections() # Used only when exporting full collection hierarchy
430 self.remove_filtered_nodes()
432 def recursive_filter_tag(self, uuid, parent_keep_tag):
433 # parent_keep_tag is for collection instance
434 # some properties (selection, visibility, renderability)
435 # are defined at collection level, and we need to use these values
436 # for all objects of the collection instance.
437 # But some properties (camera, lamp ...) are not defined at collection level
438 if parent_keep_tag is None:
439 self.nodes[uuid].keep_tag = self.node_filter_not_inheritable_is_kept(uuid) and self.node_filter_inheritable_is_kept(uuid)
440 elif parent_keep_tag is True:
441 self.nodes[uuid].keep_tag = self.node_filter_not_inheritable_is_kept(uuid)
442 elif parent_keep_tag is False:
443 self.nodes[uuid].keep_tag = False
444 else:
445 self.export_settings['log'].error("This should not happen")
447 for child in self.nodes[uuid].children:
448 if self.nodes[uuid].blender_type == VExportNode.INST_COLLECTION or self.nodes[uuid].is_instancier == VExportNode.INSTANCIER:
449 # We need to split children into 2 categories: real children, and objects inside the collection
450 if self.nodes[uuid].children_type[child] == VExportNode.CHILDREN_IS_IN_COLLECTION:
451 self.recursive_filter_tag(child, self.nodes[uuid].keep_tag)
452 else:
453 self.recursive_filter_tag(child, parent_keep_tag)
454 else:
455 self.recursive_filter_tag(child, parent_keep_tag)
457 def recursive_filter(self, uuid, parent_kept_uuid):
458 children = self.nodes[uuid].children.copy()
460 new_parent_kept_uuid = None
461 if self.nodes[uuid].keep_tag is False:
462 new_parent_kept_uuid = parent_kept_uuid
463 # Need to modify tree
464 if self.nodes[uuid].parent_uuid is not None:
465 self.nodes[self.nodes[uuid].parent_uuid].children.remove(uuid)
466 else:
467 # Remove from root
468 self.roots.remove(uuid)
469 else:
470 new_parent_kept_uuid = uuid
472 # If parent_uuid is not parent_kept_uuid, we need to modify children list of parent_kept_uuid
473 if parent_kept_uuid != self.nodes[uuid].parent_uuid and parent_kept_uuid is not None:
474 self.tree_troncated = True
475 self.nodes[parent_kept_uuid].children.append(uuid)
477 # If parent_kept_uuid is None, and parent_uuid was not, add to root list
478 if self.nodes[uuid].parent_uuid is not None and parent_kept_uuid is None:
479 self.tree_troncated = True
480 self.roots.append(uuid)
482 # Modify parent uuid
483 self.nodes[uuid].parent_uuid = parent_kept_uuid
485 for child in children:
486 self.recursive_filter(child, new_parent_kept_uuid)
489 def node_filter_not_inheritable_is_kept(self, uuid):
490 # Export Camera or not
491 if self.nodes[uuid].blender_type == VExportNode.CAMERA:
492 if self.export_settings['gltf_cameras'] is False:
493 return False
495 # Export Lamp or not
496 if self.nodes[uuid].blender_type == VExportNode.LIGHT:
497 if self.export_settings['gltf_lights'] is False:
498 return False
500 # Export deform bones only
501 if self.nodes[uuid].blender_type == VExportNode.BONE:
502 if self.export_settings['gltf_def_bones'] is True and self.nodes[uuid].use_deform is False:
503 # Check if bone has some objected parented to bone. We need to keep it in that case, even if this is not a def bone
504 if len([c for c in self.nodes[uuid].children if self.nodes[c].blender_type != VExportNode.BONE]) != 0:
505 return True
506 return False
508 return True
510 def node_filter_inheritable_is_kept(self, uuid):
512 if self.nodes[uuid].blender_object is None:
513 # geometry node instances
514 return True
516 if self.nodes[uuid].blender_type == VExportNode.COLLECTION:
517 # Collections, can't be filtered => we always keep them
518 return True
520 if self.export_settings['gltf_selected'] and self.nodes[uuid].blender_object.select_get() is False:
521 return False
523 if self.export_settings['gltf_visible']:
524 # The eye in outliner (object)
525 if self.nodes[uuid].blender_object.visible_get() is False:
526 return False
528 # The screen in outliner (object)
529 if self.nodes[uuid].blender_object.hide_viewport is True:
530 return False
532 # The screen in outliner (collections)
533 if all([c.hide_viewport for c in self.nodes[uuid].blender_object.users_collection]):
534 return False
536 # The camera in outliner (object)
537 if self.export_settings['gltf_renderable']:
538 if self.nodes[uuid].blender_object.hide_render is True:
539 return False
541 # The camera in outliner (collections)
542 if all([c.hide_render for c in self.nodes[uuid].blender_object.users_collection]):
543 return False
545 # If we are given a collection, use all objects from it
546 if self.export_settings['gltf_collection']:
547 local_collection = bpy.data.collections.get((self.export_settings['gltf_collection'], None))
548 if not local_collection:
549 return False
550 found = any(x == self.nodes[uuid].blender_object for x in local_collection.all_objects)
551 if not found:
552 return False
553 else:
554 if self.export_settings['gltf_active_collection'] and not self.export_settings['gltf_active_collection_with_nested']:
555 found = any(x == self.nodes[uuid].blender_object for x in bpy.context.collection.objects)
556 if not found:
557 return False
559 if self.export_settings['gltf_active_collection'] and self.export_settings['gltf_active_collection_with_nested']:
560 found = any(x == self.nodes[uuid].blender_object for x in bpy.context.collection.all_objects)
561 if not found:
562 return False
564 if BLENDER_GLTF_SPECIAL_COLLECTION in bpy.data.collections and self.nodes[uuid].blender_object.name in \
565 bpy.data.collections[BLENDER_GLTF_SPECIAL_COLLECTION].objects:
566 return False
568 if self.export_settings['gltf_armature_object_remove'] is True:
569 # If we remove the Armature object
570 if self.nodes[uuid].blender_type == VExportNode.ARMATURE:
571 self.nodes[uuid].arma_exported = True
572 return False
574 return True
576 def remove_filtered_nodes(self):
577 if self.export_settings['gltf_armature_object_remove'] is True:
578 # If we remove the Armature object
579 self.nodes = {k:n for (k, n) in self.nodes.items() if n.keep_tag is True or (n.keep_tag is False and n.blender_type == VExportNode.ARMATURE)}
580 else:
581 self.nodes = {k:n for (k, n) in self.nodes.items() if n.keep_tag is True}
584 def remove_empty_collections(self):
585 def recursive_remove_empty_collections(uuid):
586 if self.nodes[uuid].blender_type == VExportNode.COLLECTION:
587 if len(self.nodes[uuid].children) == 0:
588 if self.nodes[uuid].parent_uuid is not None:
589 self.nodes[self.nodes[uuid].parent_uuid].children.remove(uuid)
590 else:
591 self.roots.remove(uuid)
592 self.nodes[uuid].keep_tag = False
593 else:
594 for c in self.nodes[uuid].children:
595 recursive_remove_empty_collections(c)
597 roots = self.roots.copy()
598 for r in roots:
599 recursive_remove_empty_collections(r)
601 def search_missing_armature(self):
602 for n in [n for n in self.nodes.values() if hasattr(n, "armature_needed") is True]:
603 candidates = [i for i in self.nodes.values() if i.blender_type == VExportNode.ARMATURE and i.blender_object.name == n.armature_needed]
604 if len(candidates) > 0:
605 n.armature = candidates[0].uuid
606 del n.armature_needed
608 def bake_armature_bone_list(self):
610 if self.export_settings['gltf_leaf_bone'] is True:
611 self.add_leaf_bones()
613 # Used to store data in armature vnode
614 # If armature is removed from export
615 # Data are still available, even if armature is not exported (so bones are re-parented)
616 for n in [n for n in self.nodes.values() if n.blender_type == VExportNode.ARMATURE]:
618 self.get_all_bones(n.uuid)
619 self.get_root_bones_uuid(n.uuid)
621 def add_leaf_bones(self):
623 # If we are using rest pose, we need to get tail of editbone, going to edit mode for each armature
624 if self.export_settings['gltf_rest_position_armature'] is True:
625 for obj_uuid in [n for n in self.nodes if self.nodes[n].blender_type == VExportNode.ARMATURE]:
626 armature = self.nodes[obj_uuid].blender_object
627 bpy.context.view_layer.objects.active = armature
628 bpy.ops.object.mode_set(mode="EDIT")
630 for bone in armature.data.edit_bones:
631 if len(bone.children) == 0:
632 self.nodes[self.nodes[obj_uuid].bones[bone.name]].matrix_world_tail = armature.matrix_world @ Matrix.Translation(bone.tail) @ self.axis_basis_change
634 bpy.ops.object.mode_set(mode="OBJECT")
637 for bone_uuid in [n for n in self.nodes if self.nodes[n].blender_type == VExportNode.BONE \
638 and len(self.nodes[n].children) == 0]:
640 bone_node = self.nodes[bone_uuid]
642 # Add a new node
643 node = VExportNode()
644 node.uuid = str(uuid.uuid4())
645 node.parent_uuid = bone_uuid
646 node.parent_bone_uuid = bone_uuid
647 node.blender_object = bone_node.blender_object
648 node.armature = bone_node.armature
649 node.blender_type = VExportNode.BONE
650 node.leaf_reference = bone_uuid
651 node.keep_tag = True
653 node.matrix_world = bone_node.matrix_world_tail.copy()
655 self.add_children(bone_uuid, node.uuid)
656 self.add_node(node)
659 def add_neutral_bones(self):
660 added_armatures = []
661 for n in [n for n in self.nodes.values() if \
662 n.armature is not None and \
663 n.armature in self.nodes and \
664 n.blender_type == VExportNode.OBJECT and \
665 n.blender_object.type == "MESH" and \
666 hasattr(self.nodes[n.armature], "need_neutral_bone")]: #all skin meshes objects where neutral bone is needed
667 # Only for meshes, as curve can't have skin data (no weights pain available)
669 # Be sure to add it to really exported meshes
670 if n.node.skin is None:
671 self.export_settings['log'].warning("{} has no skin, skipping adding neutral bone data on it.".format(n.blender_object.name))
672 continue
674 if n.armature not in added_armatures:
676 added_armatures.append(n.armature) # Make sure to not insert 2 times the neural bone
678 # First add a new node
679 trans, rot, sca = self.axis_basis_change.decompose()
680 translation, rotation, scale = (None, None, None)
681 if trans[0] != 0.0 or trans[1] != 0.0 or trans[2] != 0.0:
682 translation = [trans[0], trans[1], trans[2]]
683 if rot[0] != 1.0 or rot[1] != 0.0 or rot[2] != 0.0 or rot[3] != 0.0:
684 rotation = [rot[1], rot[2], rot[3], rot[0]]
685 if sca[0] != 1.0 or sca[1] != 1.0 or sca[2] != 1.0:
686 scale = [sca[0], sca[1], sca[2]]
687 neutral_bone = gltf2_io.Node(
688 camera=None,
689 children=None,
690 extensions=None,
691 extras=None,
692 matrix=None,
693 mesh=None,
694 name='neutral_bone',
695 rotation=rotation,
696 scale=scale,
697 skin=None,
698 translation=translation,
699 weights=None
701 # Add it to child list of armature
702 self.nodes[n.armature].node.children.append(neutral_bone)
704 # Add it to joint list
705 n.node.skin.joints.append(neutral_bone)
707 # Need to add an InverseBindMatrix
708 array = BinaryData.decode_accessor_internal(n.node.skin.inverse_bind_matrices)
710 inverse_bind_matrix = (
711 self.axis_basis_change @ self.nodes[n.armature].matrix_world_armature).inverted_safe()
713 matrix = []
714 for column in range(0, 4):
715 for row in range(0, 4):
716 matrix.append(inverse_bind_matrix[row][column])
718 array = np.append(array, np.array([matrix]), axis=0)
719 binary_data = gltf2_io_binary_data.BinaryData.from_list(array.flatten(), gltf2_io_constants.ComponentType.Float)
720 n.node.skin.inverse_bind_matrices = gltf2_blender_gather_accessors.gather_accessor(
721 binary_data,
722 gltf2_io_constants.ComponentType.Float,
723 len(array.flatten()) // gltf2_io_constants.DataType.num_elements(gltf2_io_constants.DataType.Mat4),
724 None,
725 None,
726 gltf2_io_constants.DataType.Mat4,
727 self.export_settings
729 def get_unused_skins(self):
730 from .gltf2_blender_gather_skins import gather_skin
731 skins = []
732 for n in [n for n in self.nodes.values() if n.blender_type == VExportNode.ARMATURE]:
733 if self.export_settings['gltf_armature_object_remove'] is True:
734 if hasattr(n, "arma_exported") is False:
735 continue
736 if len([m for m in self.nodes.values() if m.keep_tag is True and m.blender_type == VExportNode.OBJECT and m.armature == n.uuid]) == 0:
737 skin = gather_skin(n.uuid, self.export_settings)
738 skins.append(skin)
739 return skins
741 def variants_reset_to_original(self):
742 # Only if Variants are displayed and exported
743 if bpy.context.preferences.addons['io_scene_gltf2'].preferences.KHR_materials_variants_ui is False:
744 return
745 objects = [self.nodes[o].blender_object for o in self.get_all_node_of_type(VExportNode.OBJECT) if self.nodes[o].blender_object.type == "MESH" \
746 and self.nodes[o].blender_object.data.get('gltf2_variant_default_materials') is not None]
747 for obj in objects:
748 # loop on material slots ( primitives )
749 for mat_slot_idx, s in enumerate(obj.material_slots):
750 # Check if there is a default material for this slot
751 for i in obj.data.gltf2_variant_default_materials:
752 if i.material_slot_index == mat_slot_idx:
753 s.material = i.default_material
754 break
756 # If not found, keep current material as default
758 def break_bone_hierarchy(self):
759 # Can be usefull when matrix is not decomposable
760 for arma in self.get_all_node_of_type(VExportNode.ARMATURE):
761 bones = self.get_all_bones(arma)
762 for bone in bones:
763 if self.nodes[bone].parent_uuid is not None and self.nodes[bone].parent_uuid != arma:
764 self.nodes[self.nodes[bone].parent_uuid].children.remove(bone)
765 self.nodes[bone].parent_uuid = arma
766 self.nodes[arma].children.append(bone)
768 def break_obj_hierarchy(self):
769 # Can be usefull when matrix is not decomposable
770 # TODO: if we get real collection one day, we probably need to adapt this code
771 for obj in self.get_all_objects():
772 if self.nodes[obj].armature is not None and self.nodes[obj].parent_uuid == self.nodes[obj].armature:
773 continue # Keep skined meshs as children of armature
774 if self.nodes[obj].parent_uuid is not None:
775 self.nodes[self.nodes[obj].parent_uuid].children.remove(obj)
776 self.nodes[obj].parent_uuid = None
777 self.roots.append(obj)
779 def check_if_we_can_remove_armature(self):
780 # If user requested to remove armature, we need to check if it is possible
781 # If is impossible to remove it if armature has multiple root bones. (glTF validator error)
782 # Currently, we manage it at export level, not at each armature level
783 for arma_uuid in [n for n in self.nodes.keys() if self.nodes[n].blender_type == VExportNode.ARMATURE]:
784 if len(self.get_root_bones_uuid(arma_uuid)) > 1:
785 # We can't remove armature
786 self.export_settings['gltf_armature_object_remove'] = False
787 self.export_settings['log'].warning("We can't remove armature object because some armatures have multiple root bones.")
788 break