[Bf-extensions-cvs] [6e7dfdd8] master: glTF importer: prettify bones: manage more readable bone rotation
Julien Duroure
noreply at git.blender.org
Mon Mar 9 16:23:17 CET 2020
Commit: 6e7dfdd8a91fdccae321f6192dd22accb5bc2426
Author: Julien Duroure
Date: Mon Mar 9 16:20:54 2020 +0100
Branches: master
https://developer.blender.org/rBA6e7dfdd8a91fdccae321f6192dd22accb5bc2426
glTF importer: prettify bones: manage more readable bone rotation
Thanks to scurest!
===================================================================
M io_scene_gltf2/__init__.py
M io_scene_gltf2/blender/com/gltf2_blender_math.py
M io_scene_gltf2/blender/imp/gltf2_blender_animation_bone.py
M io_scene_gltf2/blender/imp/gltf2_blender_animation_node.py
M io_scene_gltf2/blender/imp/gltf2_blender_node.py
M io_scene_gltf2/blender/imp/gltf2_blender_vnode.py
===================================================================
diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py
index 95e24933..f9ad9b8d 100755
--- a/io_scene_gltf2/__init__.py
+++ b/io_scene_gltf2/__init__.py
@@ -15,7 +15,7 @@
bl_info = {
'name': 'glTF 2.0 format',
'author': 'Julien Duroure, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors',
- "version": (1, 2, 36),
+ "version": (1, 2, 37),
'blender': (2, 82, 7),
'location': 'File > Import-Export',
'description': 'Import-Export as glTF 2.0',
@@ -856,11 +856,26 @@ class ImportGLTF2(Operator, ImportHelper):
description="How normals are computed during import",
default="NORMALS")
+ bone_heuristic: EnumProperty(
+ name="Bone Dir",
+ items=(
+ ("BLENDER", "Blender (+Y)",
+ "Round-trips bone directions in glTFs exported from Blender.\n"
+ "Bone tips are placed on their local +Y axis (in glTF space)"),
+ ("TEMPERANCE", "Temperance",
+ "Okay for many different models.\n"
+ "Bone tips are placed at a child's root")
+ ),
+ description="Heuristic for placing bones. Tries to make bones pretty",
+ default="TEMPERANCE",
+ )
+
def draw(self, context):
layout = self.layout
layout.prop(self, 'import_pack_images')
layout.prop(self, 'import_shading')
+ layout.prop(self, 'bone_heuristic')
def execute(self, context):
return self.import_gltf2(context)
diff --git a/io_scene_gltf2/blender/com/gltf2_blender_math.py b/io_scene_gltf2/blender/com/gltf2_blender_math.py
index fb342bc4..b3f13f65 100755
--- a/io_scene_gltf2/blender/com/gltf2_blender_math.py
+++ b/io_scene_gltf2/blender/com/gltf2_blender_math.py
@@ -170,3 +170,45 @@ def transform_value(value: Vector, _: Matrix = Matrix.Identity(4)) -> Vector:
def round_if_near(value: float, target: float) -> float:
"""If value is very close to target, round to target."""
return value if abs(value - target) > 2.0e-6 else target
+
+def scale_rot_swap_matrix(rot):
+ """Returns a matrix m st. Scale[s] Rot[rot] = Rot[rot] Scale[m s].
+ If rot.to_matrix() is a signed permutation matrix, works for any s.
+ Otherwise works only if s is a uniform scaling.
+ """
+ m = nearby_signed_perm_matrix(rot) # snap to signed perm matrix
+ m.transpose() # invert permutation
+ for i in range(3):
+ for j in range(3):
+ m[i][j] = abs(m[i][j]) # discard sign
+ return m
+
+def nearby_signed_perm_matrix(rot):
+ """Returns a signed permutation matrix close to rot.to_matrix().
+ (A signed permutation matrix is like a permutation matrix, except
+ the non-zero entries can be ±1.)
+ """
+ m = rot.to_matrix()
+ x, y, z = m[0], m[1], m[2]
+
+ # Set the largest entry in the first row to ±1
+ a, b, c = abs(x[0]), abs(x[1]), abs(x[2])
+ i = 0 if a >= b and a >= c else 1 if b >= c else 2
+ x[i] = 1 if x[i] > 0 else -1
+ x[(i+1) % 3] = 0
+ x[(i+2) % 3] = 0
+
+ # Same for second row: only two columns to consider now.
+ a, b = abs(y[(i+1) % 3]), abs(y[(i+2) % 3])
+ j = (i+1) % 3 if a >= b else (i+2) % 3
+ y[j] = 1 if y[j] > 0 else -1
+ y[(j+1) % 3] = 0
+ y[(j+2) % 3] = 0
+
+ # Same for third row: only one column left
+ k = (0 + 1 + 2) - i - j
+ z[k] = 1 if z[k] > 0 else -1
+ z[(k+1) % 3] = 0
+ z[(k+2) % 3] = 0
+
+ return m
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_animation_bone.py b/io_scene_gltf2/blender/imp/gltf2_blender_animation_bone.py
index 62c179c1..6f65de43 100755
--- a/io_scene_gltf2/blender/imp/gltf2_blender_animation_bone.py
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_animation_bone.py
@@ -58,12 +58,14 @@ class BlenderBoneAnim():
else:
translation_keyframes = (gltf.loc_gltf_to_blender(vals) for vals in values)
+ final_translations = vnode.base_locs_to_final_locs(translation_keyframes)
+
# Calculate pose bone trans from final bone trans
edit_trans, edit_rot = vnode.editbone_trans, vnode.editbone_rot
edit_rot_inv = edit_rot.conjugated()
pose_translations = [
edit_rot_inv @ (trans - edit_trans)
- for trans in translation_keyframes
+ for trans in final_translations
]
BlenderBoneAnim.fill_fcurves(
@@ -93,12 +95,14 @@ class BlenderBoneAnim():
else:
quat_keyframes = [gltf.quaternion_gltf_to_blender(vals) for vals in values]
+ final_rots = vnode.base_rots_to_final_rots(quat_keyframes)
+
# Calculate pose bone rotation from final bone rotation
edit_rot = vnode.editbone_rot
edit_rot_inv = edit_rot.conjugated()
pose_rots = [
edit_rot_inv @ rot
- for rot in quat_keyframes
+ for rot in final_rots
]
# Manage antipodal quaternions
@@ -133,10 +137,13 @@ class BlenderBoneAnim():
else:
scale_keyframes = [gltf.scale_gltf_to_blender(vals) for vals in values]
+ final_scales = vnode.base_scales_to_final_scales(scale_keyframes)
+ pose_scales = final_scales # no change needed
+
BlenderBoneAnim.fill_fcurves(
obj.animation_data.action,
keys,
- scale_keyframes,
+ pose_scales,
group_name,
blender_path,
animation.samplers[channel.sampler].interpolation
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_animation_node.py b/io_scene_gltf2/blender/imp/gltf2_blender_animation_node.py
index a0205483..b6369b8b 100755
--- a/io_scene_gltf2/blender/imp/gltf2_blender_animation_node.py
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_animation_node.py
@@ -74,20 +74,22 @@ class BlenderNodeAnim():
blender_path = "location"
group_name = "Location"
num_components = 3
+ values = [gltf.loc_gltf_to_blender(vals) for vals in values]
+ values = vnode.base_locs_to_final_locs(values)
if vnode.parent is not None and gltf.vnodes[vnode.parent].type == VNode.Bone:
# Nodes with a bone parent need to be translated
- # backwards by their bone length (always 1 currently)
- off = Vector((0, -1, 0))
- values = [gltf.loc_gltf_to_blender(vals) + off for vals in values]
- else:
- values = [gltf.loc_gltf_to_blender(vals) for vals in values]
+ # backwards from the tip to the root
+ bone_length = gltf.vnodes[vnode.parent].bone_length
+ off = Vector((0, -bone_length, 0))
+ values = [vals + off for vals in values]
elif channel.target.path == "rotation":
blender_path = "rotation_quaternion"
group_name = "Rotation"
num_components = 4
values = [gltf.quaternion_gltf_to_blender(vals) for vals in values]
+ values = vnode.base_rots_to_final_rots(values)
# Manage antipodal quaternions
for i in range(1, len(values)):
@@ -99,6 +101,7 @@ class BlenderNodeAnim():
group_name = "Scale"
num_components = 3
values = [gltf.scale_gltf_to_blender(vals) for vals in values]
+ values = vnode.base_scales_to_final_scales(values)
coords = [0] * (2 * len(keys))
coords[::2] = (key[0] * fps for key in keys)
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_node.py b/io_scene_gltf2/blender/imp/gltf2_blender_node.py
index d1ffdbe9..6ce91ea3 100755
--- a/io_scene_gltf2/blender/imp/gltf2_blender_node.py
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_node.py
@@ -78,7 +78,7 @@ class BlenderNode():
set_extras(obj, pynode.extras)
# Set transform
- trans, rot, scale = vnode.trs
+ trans, rot, scale = vnode.trs()
obj.location = trans
obj.rotation_mode = 'QUATERNION'
obj.rotation_quaternion = rot
@@ -96,8 +96,8 @@ class BlenderNode():
obj.parent_bone = parent_vnode.blender_bone_name
# Nodes with a bone parent need to be translated
- # backwards by their bone length (always 1 currently)
- obj.location += Vector((0, -1, 0))
+ # backwards from the tip to the root
+ obj.location += Vector((0, -parent_vnode.bone_length, 0))
bpy.data.scenes[gltf.blender_scene].collection.objects.link(obj)
@@ -138,6 +138,7 @@ class BlenderNode():
editbone.head = arma_mat @ Vector((0, 0, 0))
editbone.tail = arma_mat @ Vector((0, 1, 0))
editbone.align_roll(arma_mat @ Vector((0, 0, 1)) - editbone.head)
+ editbone.length = vnode.bone_length
if isinstance(id, int):
pynode = gltf.data.nodes[id]
@@ -161,7 +162,7 @@ class BlenderNode():
# BoneTRS = EditBone * PoseBone
# Set PoseBone to make BoneTRS = vnode.trs.
- t, r, s = vnode.trs
+ t, r, s = vnode.trs()
et, er = vnode.editbone_trans, vnode.editbone_rot
pose_bone.location = er.conjugated() @ (t - et)
pose_bone.rotation_mode = 'QUATERNION'
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_vnode.py b/io_scene_gltf2/blender/imp/gltf2_blender_vnode.py
index bd5edcd1..554c09e9 100644
--- a/io_scene_gltf2/blender/imp/gltf2_blender_vnode.py
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_vnode.py
@@ -15,6 +15,8 @@
import bpy
from mathutils import Vector, Quaternion, Matrix
+from ..com.gltf2_blender_math import scale_rot_swap_matrix, nearby_signed_perm_matrix
+
def compute_vnodes(gltf):
"""Computes the tree of virtual nodes.
Copies the glTF nodes into a tree of VNodes, then performs a series of
@@ -26,6 +28,7 @@ def compute_vnodes(gltf):
fixup_multitype_nodes(gltf)
correct_cameras_and_lights(gltf)
pick_bind_pose(gltf)
+ prettify_bones(gltf)
calc_
@@ Diff output truncated at 10240 characters. @@
More information about the Bf-extensions-cvs
mailing list