1 # SPDX-FileCopyrightText: 2021 The glTF-Blender-IO authors
3 # SPDX-License-Identifier: Apache-2.0
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
26 INSTANCE
= 7 # For instances of GN
33 # Parent type, to be set on child regarding its parent
37 PARENT_BONE_RELATIVE
= 52
43 # Is used to split instance collection into 2 categories:
45 CHILDREN_IS_IN_COLLECTION
= 91
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
67 self
.use_deform
= None
73 self
.armature
= None # for deformed object and for bone
79 # For mesh instance data of GN instances
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
):
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
)
99 def __init__(self
, export_settings
):
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
)
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):
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:
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
153 self
.roots
.append(node
.uuid
)
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
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
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
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...)
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
221 node
.matrix_world
= parent_coll_matrix_world
.copy()
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
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))
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))
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
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
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
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
276 ###### Manage children ######
278 # GN instance have no children
279 if blender_object
is None:
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
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
:
297 self
.recursive_node_traverse(child_object
, None, node
.uuid
, parent_coll_matrix_world
, new_delta
or delta
, blender_children
)
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:
305 self
.recursive_node_traverse(dupli_object
, None, node
.uuid
, node
.matrix_world
, new_delta
or delta
, blender_children
, is_children_in_collection
=True)
307 # Manage children objects
308 for child
in blender_object
.instance_collection
.objects
:
309 if child
.users_collection
[0].name
!= blender_object
.name
:
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
:
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
):
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
)
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
)
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
:
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
):
376 if self
.nodes
[uuid
].blender_type
== VExportNode
.BONE
:
378 for child_uuid
in self
.nodes
[uuid
].children
:
379 total
.extend(recursive_get_all_bones(child_uuid
))
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
389 self
.nodes
[uuid
].all_bones
= []
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
401 self
.nodes
[uuid
].root_bones_uuid
= []
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
):
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()
418 self
.recursive_filter_tag(r
, None)
420 def filter_perform(self
):
421 roots
= self
.roots
.copy()
423 self
.recursive_filter(r
, None) # Root, so no parent
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
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
)
453 self
.recursive_filter_tag(child
, parent_keep_tag
)
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
)
468 self
.roots
.remove(uuid
)
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
)
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:
496 if self
.nodes
[uuid
].blender_type
== VExportNode
.LIGHT
:
497 if self
.export_settings
['gltf_lights'] is 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:
510 def node_filter_inheritable_is_kept(self
, uuid
):
512 if self
.nodes
[uuid
].blender_object
is None:
513 # geometry node instances
516 if self
.nodes
[uuid
].blender_type
== VExportNode
.COLLECTION
:
517 # Collections, can't be filtered => we always keep them
520 if self
.export_settings
['gltf_selected'] and self
.nodes
[uuid
].blender_object
.select_get() is 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:
528 # The screen in outliner (object)
529 if self
.nodes
[uuid
].blender_object
.hide_viewport
is True:
532 # The screen in outliner (collections)
533 if all([c
.hide_viewport
for c
in self
.nodes
[uuid
].blender_object
.users_collection
]):
536 # The camera in outliner (object)
537 if self
.export_settings
['gltf_renderable']:
538 if self
.nodes
[uuid
].blender_object
.hide_render
is True:
541 # The camera in outliner (collections)
542 if all([c
.hide_render
for c
in self
.nodes
[uuid
].blender_object
.users_collection
]):
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
:
550 found
= any(x
== self
.nodes
[uuid
].blender_object
for x
in local_collection
.all_objects
)
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
)
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
)
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
:
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
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
)}
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
)
591 self
.roots
.remove(uuid
)
592 self
.nodes
[uuid
].keep_tag
= False
594 for c
in self
.nodes
[uuid
].children
:
595 recursive_remove_empty_collections(c
)
597 roots
= self
.roots
.copy()
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
]
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
653 node
.matrix_world
= bone_node
.matrix_world_tail
.copy()
655 self
.add_children(bone_uuid
, node
.uuid
)
659 def add_neutral_bones(self
):
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
))
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(
698 translation
=translation
,
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()
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(
722 gltf2_io_constants
.ComponentType
.Float
,
723 len(array
.flatten()) // gltf2_io_constants
.DataType
.num_elements(gltf2_io_constants
.DataType
.Mat4
),
726 gltf2_io_constants
.DataType
.Mat4
,
729 def get_unused_skins(self
):
730 from .gltf2_blender_gather_skins
import gather_skin
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:
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
)
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:
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]
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
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
)
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.")