diff options
Diffstat (limited to 'io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py')
-rw-r--r-- | io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py | 374 |
1 files changed, 374 insertions, 0 deletions
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py new file mode 100644 index 00000000..d3edd50a --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py @@ -0,0 +1,374 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2021 The glTF-Blender-IO authors. + +import bpy +import uuid + +from . import gltf2_blender_export_keys +from mathutils import Quaternion, Matrix + +class VExportNode: + + OBJECT = 1 + ARMATURE = 2 + BONE = 3 + LIGHT = 4 + CAMERA = 5 + COLLECTION = 6 + + # Parent type, to be set on child regarding its parent + NO_PARENT = 54 + PARENT_OBJECT = 50 + PARENT_BONE = 51 + PARENT_BONE_RELATIVE = 52 + PARENT_ROOT_BONE = 53 + PARENT_BONE_BONE = 55 + + + def __init__(self): + self.children = [] + self.blender_type = None + self.world_matrix = None + self.parent_type = None + + self.blender_object = None + self.blender_bone = None + + self.force_as_empty = False # Used for instancer display + + # Only for bone/bone and object parented to bone + self.parent_bone_uuid = None + + # Only for bones + self.use_deform = None + + # Only for armature + self.bones = {} + + # For deformed object + self.armature = None # for deformed object and for bone + self.skin = None + + # glTF + self.node = None + + def add_child(self, uuid): + self.children.append(uuid) + + def set_world_matrix(self, matrix): + self.world_matrix = matrix + + def set_blender_data(self, blender_object, blender_bone): + self.blender_object = blender_object + self.blender_bone = blender_bone + + def recursive_display(self, tree, mode): + if mode == "simple": + for c in self.children: + print(self.blender_object.name, "/", self.blender_bone.name if self.blender_bone else "", "-->", tree.nodes[c].blender_object.name, "/", tree.nodes[c].blender_bone.name if tree.nodes[c].blender_bone else "" ) + tree.nodes[c].recursive_display(tree, mode) + +class VExportTree: + def __init__(self, export_settings): + self.nodes = {} + self.roots = [] + + self.export_settings = export_settings + + self.tree_troncated = False + + def add_node(self, node): + self.nodes[node.uuid] = node + + def add_children(self, uuid_parent, uuid_child): + self.nodes[uuid_parent].add_child(uuid_child) + + def construct(self, blender_scene): + bpy.context.window.scene = blender_scene + depsgraph = bpy.context.evaluated_depsgraph_get() + + for blender_object in [obj.original for obj in depsgraph.scene_eval.objects if obj.parent is None]: + self.recursive_node_traverse(blender_object, None, None, Matrix.Identity(4)) + + def recursive_node_traverse(self, blender_object, blender_bone, parent_uuid, parent_coll_matrix_world, armature_uuid=None, dupli_world_matrix=None): + node = VExportNode() + node.uuid = str(uuid.uuid4()) + node.parent_uuid = parent_uuid + node.set_blender_data(blender_object, blender_bone) + + # add to parent if needed + if parent_uuid is not None: + self.add_children(parent_uuid, node.uuid) + else: + self.roots.append(node.uuid) + + # Set blender type + if blender_bone is not None: + node.blender_type = VExportNode.BONE + self.nodes[armature_uuid].bones[blender_bone.name] = node.uuid + node.use_deform = blender_bone.id_data.data.bones[blender_bone.name].use_deform + elif blender_object.type == "ARMATURE": + node.blender_type = VExportNode.ARMATURE + elif blender_object.type == "CAMERA": + node.blender_type = VExportNode.CAMERA + elif blender_object.type == "LIGHT": + node.blender_type = VExportNode.LIGHT + elif blender_object.instance_type == "COLLECTION": + node.blender_type = VExportNode.COLLECTION + else: + node.blender_type = VExportNode.OBJECT + + # For meshes with armature modifier (parent is armature), keep armature uuid + if node.blender_type == VExportNode.OBJECT: + modifiers = {m.type: m for m in blender_object.modifiers} + if "ARMATURE" in modifiers and modifiers["ARMATURE"].object is not None: + if parent_uuid is None or not self.nodes[parent_uuid].blender_type == VExportNode.ARMATURE: + # correct workflow is to parent skinned mesh to armature, but ... + # all users don't use correct workflow + print("WARNING: Armature must be the parent of skinned mesh") + print("Armature is selected by its name, but may be false in case of instances") + # Search an armature by name, and use the first found + # This will be done after all objects are setup + node.armature_needed = modifiers["ARMATURE"].object.name + else: + node.armature = parent_uuid + + # For bones, store uuid of armature + if blender_bone is not None: + node.armature = armature_uuid + + # for bone/bone parenting, store parent, this will help armature tree management + if parent_uuid is not None and self.nodes[parent_uuid].blender_type == VExportNode.BONE and node.blender_type == VExportNode.BONE: + node.parent_bone_uuid = parent_uuid + + + # Objects parented to bone + if parent_uuid is not None and self.nodes[parent_uuid].blender_type == VExportNode.BONE and node.blender_type != VExportNode.BONE: + node.parent_bone_uuid = parent_uuid + + # World Matrix + # Store World Matrix for objects + if dupli_world_matrix is not None: + node.matrix_world = dupli_world_matrix + elif node.blender_type in [VExportNode.OBJECT, VExportNode.COLLECTION, VExportNode.ARMATURE, VExportNode.CAMERA, VExportNode.LIGHT]: + # Matrix World of object is expressed based on collection instance objects are + # So real world matrix is collection world_matrix @ "world_matrix" of object + node.matrix_world = parent_coll_matrix_world @ blender_object.matrix_world.copy() + if node.blender_type == VExportNode.CAMERA and self.export_settings[gltf2_blender_export_keys.CAMERAS]: + correction = Quaternion((2**0.5/2, -2**0.5/2, 0.0, 0.0)) + node.matrix_world @= correction.to_matrix().to_4x4() + elif node.blender_type == VExportNode.LIGHT and self.export_settings[gltf2_blender_export_keys.LIGHTS]: + correction = Quaternion((2**0.5/2, -2**0.5/2, 0.0, 0.0)) + node.matrix_world @= correction.to_matrix().to_4x4() + elif node.blender_type == VExportNode.BONE: + node.matrix_world = self.nodes[node.armature].matrix_world @ blender_bone.matrix + axis_basis_change = Matrix( + ((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))) + node.matrix_world = node.matrix_world @ axis_basis_change + + # Force empty ? + # For duplis, if instancer is not display, we should create an empty + if blender_object.is_instancer is True and blender_object.show_instancer_for_render is False: + node.force_as_empty = True + + # Storing this node + self.add_node(node) + + ###### Manage children ###### + + # standard children + if blender_bone is None and blender_object.is_instancer is False: + for child_object in blender_object.children: + if child_object.parent_bone: + # Object parented to bones + # Will be manage later + continue + else: + # Classic parenting + self.recursive_node_traverse(child_object, None, node.uuid, parent_coll_matrix_world) + + # Collections + if blender_object.instance_type == 'COLLECTION' and blender_object.instance_collection: + for dupli_object in blender_object.instance_collection.objects: + if dupli_object.parent is not None: + continue + self.recursive_node_traverse(dupli_object, None, node.uuid, node.matrix_world) + + # Armature : children are bones with no parent + if blender_object.type == "ARMATURE" and blender_bone is None: + for b in [b for b in blender_object.pose.bones if b.parent is None]: + self.recursive_node_traverse(blender_object, b, node.uuid, parent_coll_matrix_world, node.uuid) + + # Bones + if blender_object.type == "ARMATURE" and blender_bone is not None: + for b in blender_bone.children: + self.recursive_node_traverse(blender_object, b, node.uuid, parent_coll_matrix_world, armature_uuid) + + # Object parented to bone + if blender_bone is not None: + for child_object in [c for c in blender_object.children if c.parent_bone is not None and c.parent_bone == blender_bone.name]: + self.recursive_node_traverse(child_object, None, node.uuid, parent_coll_matrix_world) + + # Duplis + if blender_object.is_instancer is True and blender_object.instance_type != 'COLLECTION': + depsgraph = bpy.context.evaluated_depsgraph_get() + 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)]: + self.recursive_node_traverse(dupl, None, node.uuid, parent_coll_matrix_world, dupli_world_matrix=mat) + + def get_all_objects(self): + return [n.uuid for n in self.nodes.values() if n.blender_type != VExportNode.BONE] + + def get_all_bones(self, uuid): #For armatue Only + if self.nodes[uuid].blender_type == VExportNode.ARMATURE: + def recursive_get_all_bones(uuid): + total = [] + if self.nodes[uuid].blender_type == VExportNode.BONE: + total.append(uuid) + for child_uuid in self.nodes[uuid].children: + total.extend(recursive_get_all_bones(child_uuid)) + + return total + + tot = [] + for c_uuid in self.nodes[uuid].children: + tot.extend(recursive_get_all_bones(c_uuid)) + return tot + else: + return [] + + def display(self, mode): + if mode == "simple": + for n in self.roots: + print("Root", self.nodes[n].blender_object.name, "/", self.nodes[n].blender_bone.name if self.nodes[n].blender_bone else "" ) + self.nodes[n].recursive_display(self, mode) + + + def filter_tag(self): + roots = self.roots.copy() + for r in roots: + self.recursive_filter_tag(r, None) + + def filter_perform(self): + roots = self.roots.copy() + for r in roots: + self.recursive_filter(r, None) # Root, so no parent + + def filter(self): + self.filter_tag() + self.filter_perform() + + + def recursive_filter_tag(self, uuid, parent_keep_tag): + # parent_keep_tag is for collection instance + # some properties (selection, visibility, renderability) + # are defined at collection level, and we need to use these values + # for all objects of the collection instance. + # But some properties (camera, lamp ...) are not defined at collection level + if parent_keep_tag is None: + self.nodes[uuid].keep_tag = self.node_filter_not_inheritable_is_kept(uuid) and self.node_filter_inheritable_is_kept(uuid) + elif parent_keep_tag is True: + self.nodes[uuid].keep_tag = self.node_filter_not_inheritable_is_kept(uuid) + elif parent_keep_tag is False: + self.nodes[uuid].keep_tag = False + else: + print("This should not happen!") + + for child in self.nodes[uuid].children: + if self.nodes[uuid].blender_type == VExportNode.COLLECTION: + self.recursive_filter_tag(child, self.nodes[uuid].keep_tag) + else: + self.recursive_filter_tag(child, parent_keep_tag) + + def recursive_filter(self, uuid, parent_kept_uuid): + children = self.nodes[uuid].children.copy() + + new_parent_kept_uuid = None + if self.nodes[uuid].keep_tag is False: + new_parent_kept_uuid = parent_kept_uuid + # Need to modify tree + if self.nodes[uuid].parent_uuid is not None: + self.nodes[self.nodes[uuid].parent_uuid].children.remove(uuid) + else: + # Remove from root + self.roots.remove(uuid) + else: + new_parent_kept_uuid = uuid + + # If parent_uuid is not parent_kept_uuid, we need to modify children list of parent_kept_uuid + if parent_kept_uuid != self.nodes[uuid].parent_uuid and parent_kept_uuid is not None: + self.tree_troncated = True + self.nodes[parent_kept_uuid].children.append(uuid) + + # If parent_kept_uuid is None, and parent_uuid was not, add to root list + if self.nodes[uuid].parent_uuid is not None and parent_kept_uuid is None: + self.tree_troncated = True + self.roots.append(uuid) + + # Modify parent uuid + self.nodes[uuid].parent_uuid = parent_kept_uuid + + for child in children: + self.recursive_filter(child, new_parent_kept_uuid) + + + def node_filter_not_inheritable_is_kept(self, uuid): + # Export Camera or not + if self.nodes[uuid].blender_type == VExportNode.CAMERA: + if self.export_settings[gltf2_blender_export_keys.CAMERAS] is False: + return False + + # Export Lamp or not + if self.nodes[uuid].blender_type == VExportNode.LIGHT: + if self.export_settings[gltf2_blender_export_keys.LIGHTS] is False: + return False + + # Export deform bones only + if self.nodes[uuid].blender_type == VExportNode.BONE: + if self.export_settings['gltf_def_bones'] is True and self.nodes[uuid].use_deform is False: + # 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 + if len([c for c in self.nodes[uuid].children if self.nodes[c].blender_type != VExportNode.BONE]) != 0: + return True + return False + + return True + + def node_filter_inheritable_is_kept(self, uuid): + + if self.export_settings[gltf2_blender_export_keys.SELECTED] and self.nodes[uuid].blender_object.select_get() is False: + return False + + if self.export_settings[gltf2_blender_export_keys.VISIBLE]: + # The eye in outliner (object) + if self.nodes[uuid].blender_object.visible_get() is False: + return False + + # The screen in outliner (object) + if self.nodes[uuid].blender_object.hide_viewport is True: + return False + + # The screen in outliner (collections) + if all([c.hide_viewport for c in self.nodes[uuid].blender_object.users_collection]): + return False + + # The camera in outliner (object) + if self.export_settings[gltf2_blender_export_keys.RENDERABLE]: + if self.nodes[uuid].blender_object.hide_render is True: + return False + + # The camera in outliner (collections) + if all([c.hide_render for c in self.nodes[uuid].blender_object.users_collection]): + return False + + if self.export_settings[gltf2_blender_export_keys.ACTIVE_COLLECTION]: + found = any(x == self.nodes[uuid].blender_object for x in bpy.context.collection.all_objects) + if not found: + return False + + return True + + def search_missing_armature(self): + for n in [n for n in self.nodes.values() if hasattr(n, "armature_needed") is True]: + candidates = [i for i in self.nodes.values() if i.blender_type == VExportNode.ARMATURE and i.blender_object.name == n.armature_needed] + if len(candidates) > 0: + n.armature = candidates[0].uuid + del n.armature_needed |