Cleanup: trailing space
[blender-addons.git] / io_scene_gltf2 / blender / imp / gltf2_blender_node.py
blob917c6cee50fc1575dfc2c5d44134a24fbda327b7
1 # SPDX-License-Identifier: Apache-2.0
2 # Copyright 2018-2021 The glTF-Blender-IO authors.
4 import bpy
5 from mathutils import Vector
6 from ..com.gltf2_blender_extras import set_extras
7 from .gltf2_blender_mesh import BlenderMesh
8 from .gltf2_blender_camera import BlenderCamera
9 from .gltf2_blender_light import BlenderLight
10 from .gltf2_blender_vnode import VNode
11 from io_scene_gltf2.io.imp.gltf2_io_user_extensions import import_user_extensions
13 class BlenderNode():
14 """Blender Node."""
15 def __new__(cls, *args, **kwargs):
16 raise RuntimeError("%s should not be instantiated" % cls)
18 @staticmethod
19 def create_vnode(gltf, vnode_id):
20 """Create VNode and all its descendants."""
21 vnode = gltf.vnodes[vnode_id]
23 gltf.display_current_node += 1
24 if bpy.app.debug_value == 101:
25 gltf.log.critical("Node %d of %d (id %s)", gltf.display_current_node, len(gltf.vnodes), vnode_id)
27 if vnode.type == VNode.Object:
28 gltf_node = gltf.data.nodes[vnode_id] if isinstance(vnode_id, int) else None
29 import_user_extensions('gather_import_node_before_hook', gltf, vnode, gltf_node)
30 obj = BlenderNode.create_object(gltf, vnode_id)
31 import_user_extensions('gather_import_node_after_hook', gltf, vnode, gltf_node, obj)
32 if vnode.is_arma:
33 BlenderNode.create_bones(gltf, vnode_id)
35 elif vnode.type == VNode.Bone:
36 # These are created with their armature
37 pass
39 elif vnode.type == VNode.DummyRoot:
40 # Don't actually create this
41 vnode.blender_object = None
43 for child in vnode.children:
44 BlenderNode.create_vnode(gltf, child)
46 @staticmethod
47 def create_object(gltf, vnode_id):
48 vnode = gltf.vnodes[vnode_id]
50 if vnode.mesh_node_idx is not None:
51 obj = BlenderNode.create_mesh_object(gltf, vnode)
53 elif vnode.camera_node_idx is not None:
54 pynode = gltf.data.nodes[vnode.camera_node_idx]
55 cam = BlenderCamera.create(gltf, vnode, pynode.camera)
56 name = vnode.name or cam.name
57 obj = bpy.data.objects.new(name, cam)
59 # Since we create the actual Blender object after the create call, we call the hook here
60 import_user_extensions('gather_import_camera_after_hook', gltf, vnode, obj, cam)
62 elif vnode.light_node_idx is not None:
63 pynode = gltf.data.nodes[vnode.light_node_idx]
64 light = BlenderLight.create(gltf, vnode, pynode.extensions['KHR_lights_punctual']['light'])
65 name = vnode.name or light.name
66 obj = bpy.data.objects.new(name, light)
68 # Since we create the actual Blender object after the create call, we call the hook here
69 import_user_extensions('gather_import_light_after_hook', gltf, vnode, obj, light)
71 elif vnode.is_arma:
72 armature = bpy.data.armatures.new(vnode.arma_name)
73 name = vnode.name or armature.name
74 obj = bpy.data.objects.new(name, armature)
76 else:
77 # Empty
78 name = vnode.name or vnode.default_name
79 obj = bpy.data.objects.new(name, None)
80 obj.empty_display_size = BlenderNode.calc_empty_display_size(gltf, vnode_id)
82 vnode.blender_object = obj
84 # Set extras (if came from a glTF node)
85 if isinstance(vnode_id, int):
86 pynode = gltf.data.nodes[vnode_id]
87 set_extras(obj, pynode.extras)
89 # Set transform
90 trans, rot, scale = vnode.trs()
91 obj.location = trans
92 obj.rotation_mode = 'QUATERNION'
93 obj.rotation_quaternion = rot
94 obj.scale = scale
96 # Set parent
97 if vnode.parent is not None:
98 parent_vnode = gltf.vnodes[vnode.parent]
99 if parent_vnode.type == VNode.Object:
100 obj.parent = parent_vnode.blender_object
101 elif parent_vnode.type == VNode.Bone:
102 arma_vnode = gltf.vnodes[parent_vnode.bone_arma]
103 obj.parent = arma_vnode.blender_object
104 obj.parent_type = 'BONE'
105 obj.parent_bone = parent_vnode.blender_bone_name
107 # Nodes with a bone parent need to be translated
108 # backwards from the tip to the root
109 obj.location += Vector((0, -parent_vnode.bone_length, 0))
111 bpy.data.scenes[gltf.blender_scene].collection.objects.link(obj)
113 return obj
115 @staticmethod
116 def calc_empty_display_size(gltf, vnode_id):
117 # Use min distance to parent/children to guess size
118 sizes = []
119 vids = [vnode_id] + gltf.vnodes[vnode_id].children
120 for vid in vids:
121 vnode = gltf.vnodes[vid]
122 dist = vnode.trs()[0].length
123 sizes.append(dist * 0.4)
124 return max(min(sizes, default=1), 0.001)
126 @staticmethod
127 def create_bones(gltf, arma_id):
128 arma = gltf.vnodes[arma_id]
129 blender_arma = arma.blender_object
130 armature = blender_arma.data
132 # Find all bones for this arma
133 bone_ids = []
134 def visit(id): # Depth-first walk
135 if gltf.vnodes[id].type == VNode.Bone:
136 bone_ids.append(id)
137 for child in gltf.vnodes[id].children:
138 visit(child)
139 for child in arma.children:
140 visit(child)
142 # Switch into edit mode to create all edit bones
144 if bpy.context.mode != 'OBJECT':
145 bpy.ops.object.mode_set(mode='OBJECT')
146 bpy.context.window.scene = bpy.data.scenes[gltf.blender_scene]
147 bpy.context.view_layer.objects.active = blender_arma
148 bpy.ops.object.mode_set(mode="EDIT")
150 for id in bone_ids:
151 vnode = gltf.vnodes[id]
152 editbone = armature.edit_bones.new(vnode.name or vnode.default_name)
153 vnode.blender_bone_name = editbone.name
154 editbone.use_connect = False # TODO?
156 # Give the position of the bone in armature space
157 arma_mat = vnode.editbone_arma_mat
158 editbone.head = arma_mat @ Vector((0, 0, 0))
159 editbone.tail = arma_mat @ Vector((0, 1, 0))
160 editbone.length = vnode.bone_length
161 editbone.align_roll(arma_mat @ Vector((0, 0, 1)) - editbone.head)
163 if isinstance(id, int):
164 pynode = gltf.data.nodes[id]
165 set_extras(editbone, pynode.extras)
167 # Set all bone parents
168 for id in bone_ids:
169 vnode = gltf.vnodes[id]
170 parent_vnode = gltf.vnodes[vnode.parent]
171 if parent_vnode.type == VNode.Bone:
172 editbone = armature.edit_bones[vnode.blender_bone_name]
173 parent_editbone = armature.edit_bones[parent_vnode.blender_bone_name]
174 editbone.parent = parent_editbone
176 # Switch back to object mode and do pose bones
177 bpy.ops.object.mode_set(mode="OBJECT")
179 for id in bone_ids:
180 vnode = gltf.vnodes[id]
181 pose_bone = blender_arma.pose.bones[vnode.blender_bone_name]
183 # BoneTRS = EditBone * PoseBone
184 # Set PoseBone to make BoneTRS = vnode.trs.
185 t, r, s = vnode.trs()
186 et, er = vnode.editbone_trans, vnode.editbone_rot
187 pose_bone.location = er.conjugated() @ (t - et)
188 pose_bone.rotation_mode = 'QUATERNION'
189 pose_bone.rotation_quaternion = er.conjugated() @ r
190 pose_bone.scale = s
192 if isinstance(id, int):
193 pynode = gltf.data.nodes[id]
194 set_extras(pose_bone, pynode.extras)
196 @staticmethod
197 def create_mesh_object(gltf, vnode):
198 pynode = gltf.data.nodes[vnode.mesh_node_idx]
199 if not (0 <= pynode.mesh < len(gltf.data.meshes)):
200 # Avoid traceback for invalid gltf file: invalid reference to meshes array
201 # So return an empty blender object)
202 return bpy.data.objects.new(vnode.name or "Invalid Mesh Index", None)
203 pymesh = gltf.data.meshes[pynode.mesh]
205 # Key to cache the Blender mesh by.
206 # Same cache key = instances of the same Blender mesh.
207 cache_key = None
208 if not pymesh.shapekey_names:
209 cache_key = (pynode.skin,)
210 else:
211 # Unlike glTF, all instances of a Blender mesh share shapekeys.
212 # So two instances that might have different morph weights need
213 # different cache keys.
214 if pynode.weight_animation is False:
215 cache_key = (pynode.skin, tuple(pynode.weights or []))
216 else:
217 cache_key = None # don't use the cache at all
219 if cache_key is not None and cache_key in pymesh.blender_name:
220 mesh = bpy.data.meshes[pymesh.blender_name[cache_key]]
221 else:
222 gltf.log.info("Blender create Mesh node %s", pymesh.name or pynode.mesh)
223 mesh = BlenderMesh.create(gltf, pynode.mesh, pynode.skin)
224 if cache_key is not None:
225 pymesh.blender_name[cache_key] = mesh.name
227 name = vnode.name or mesh.name
228 obj = bpy.data.objects.new(name, mesh)
230 if pymesh.shapekey_names:
231 BlenderNode.set_morph_weights(gltf, pynode, obj)
233 if pynode.skin is not None:
234 BlenderNode.setup_skinning(gltf, pynode, obj)
236 return obj
238 @staticmethod
239 def set_morph_weights(gltf, pynode, obj):
240 pymesh = gltf.data.meshes[pynode.mesh]
241 weights = pynode.weights or pymesh.weights or []
242 for i, weight in enumerate(weights):
243 if pymesh.shapekey_names[i] is not None:
244 kb = obj.data.shape_keys.key_blocks[pymesh.shapekey_names[i]]
245 # extend range if needed
246 if weight < kb.slider_min: kb.slider_min = weight
247 if weight > kb.slider_max: kb.slider_max = weight
248 kb.value = weight
250 @staticmethod
251 def setup_skinning(gltf, pynode, obj):
252 pyskin = gltf.data.skins[pynode.skin]
254 # Armature/bones should have already been created.
256 # Create an Armature modifier
257 first_bone = gltf.vnodes[pyskin.joints[0]]
258 arma = gltf.vnodes[first_bone.bone_arma]
259 mod = obj.modifiers.new(name="Armature", type="ARMATURE")
260 mod.object = arma.blender_object