1 # SPDX-License-Identifier: Apache-2.0
2 # Copyright 2018-2021 The glTF-Blender-IO authors.
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
15 def __new__(cls
, *args
, **kwargs
):
16 raise RuntimeError("%s should not be instantiated" % cls
)
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
)
33 BlenderNode
.create_bones(gltf
, vnode_id
)
35 elif vnode
.type == VNode
.Bone
:
36 # These are created with their armature
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
)
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
)
72 armature
= bpy
.data
.armatures
.new(vnode
.arma_name
)
73 name
= vnode
.name
or armature
.name
74 obj
= bpy
.data
.objects
.new(name
, armature
)
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
)
90 trans
, rot
, scale
= vnode
.trs()
92 obj
.rotation_mode
= 'QUATERNION'
93 obj
.rotation_quaternion
= rot
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
)
116 def calc_empty_display_size(gltf
, vnode_id
):
117 # Use min distance to parent/children to guess size
119 vids
= [vnode_id
] + gltf
.vnodes
[vnode_id
].children
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)
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
134 def visit(id): # Depth-first walk
135 if gltf
.vnodes
[id].type == VNode
.Bone
:
137 for child
in gltf
.vnodes
[id].children
:
139 for child
in arma
.children
:
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")
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
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")
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
192 if isinstance(id, int):
193 pynode
= gltf
.data
.nodes
[id]
194 set_extras(pose_bone
, pynode
.extras
)
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.
208 if not pymesh
.shapekey_names
:
209 cache_key
= (pynode
.skin
,)
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 []))
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
]]
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
)
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
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