Skip to content

Commit

Permalink
feat: export non-humanoid
Browse files Browse the repository at this point in the history
  • Loading branch information
saturday06 committed Sep 18, 2023
1 parent 93db502 commit f2003b3
Show file tree
Hide file tree
Showing 9 changed files with 307 additions and 74 deletions.
36 changes: 24 additions & 12 deletions io_scene_vrm/editor/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,14 +307,15 @@ def detect_errors(
):
continue
all_required_bones_exist = False
warning_messages.append(
pgettext(
"Rig will be exported as non-humanoid. "
+ 'Required VRM Bone "{humanoid_name}" is not assigned. '
+ "Please confirm hierarchy of {humanoid_name} and its children. "
+ '"VRM" Panel → "Humanoid" → {humanoid_name} will be empty if hierarchy is wrong'
).format(humanoid_name=human_bone_specification.title)
)
if not human_bones.allow_non_humanoid_rig:
error_messages.append(
pgettext(
'Required VRM Bone "{humanoid_name}" is not assigned. '
+ "Please confirm hierarchy of {humanoid_name} and its children. "
+ '"VRM" Panel → "Humanoid" → {humanoid_name} will be '
+ "empty or displayed in red if hierarchy is wrong"
).format(humanoid_name=human_bone_specification.title)
)

if all_required_bones_exist:
# https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_vrm-1.0-beta/humanoid.md#list-of-humanoid-bones
Expand Down Expand Up @@ -347,6 +348,17 @@ def detect_errors(
parent=parent.title,
)
)
if (
not human_bones.all_required_bones_are_assigned()
and human_bones.allow_non_humanoid_rig
):
warning_messages.append(
pgettext(
"This armature will be exported but not as humanoid."
+ " It can not have animations applied"
+ " for humanoid avatars."
)
)

else:
humanoid = armature_data.vrm_addon_extension.vrm0.humanoid
Expand All @@ -364,12 +376,12 @@ def detect_errors(
):
continue
all_required_bones_exist = False
warning_messages.append(
error_messages.append(
pgettext(
"Rig will be exported as non-humanoid. "
+ 'Required VRM Bone "{humanoid_name}" is not assigned. '
'Required VRM Bone "{humanoid_name}" is not assigned. '
+ "Please confirm hierarchy of {humanoid_name} and its children. "
+ '"VRM" Panel → "Humanoid" → {humanoid_name} will be empty if hierarchy is wrong'
+ '"VRM" Panel → "VRM 0.x Humanoid" → {humanoid_name} will be '
+ "empty or displayed in red if hierarchy is wrong"
).format(humanoid_name=humanoid_name.capitalize())
)
if all_required_bones_exist:
Expand Down
17 changes: 17 additions & 0 deletions io_scene_vrm/editor/vrm1/panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,23 @@ def draw_vrm1_humanoid_layout(
draw_vrm1_humanoid_required_bones_layout(human_bones, armature_box.box())
draw_vrm1_humanoid_optional_bones_layout(human_bones, armature_box.box())

non_humanoid_export_column = layout.column()
non_humanoid_export_column.prop(human_bones, "allow_non_humanoid_rig")
if human_bones.allow_non_humanoid_rig:
non_humanoid_warnings_box = non_humanoid_export_column.box()
non_humanoid_warnings_column = non_humanoid_warnings_box.column(align=True)
text = pgettext(
"VRMs exported as Non-Humanoid\n"
+ "Rigs can not have animations applied\n"
+ "for humanoid avatars."
)
for index, message in enumerate(pgettext(text).splitlines()):
non_humanoid_warnings_column.label(
text=message,
translate=False,
icon="ERROR" if index == 0 else "NONE",
)


class VRM_PT_vrm1_humanoid_armature_object_property(bpy.types.Panel):
bl_idname = "VRM_PT_vrm1_humanoid_armature_object_property"
Expand Down
53 changes: 3 additions & 50 deletions io_scene_vrm/editor/vrm1/property_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,8 @@ class Vrm1HumanBonesPropertyGroup(bpy.types.PropertyGroup):
default=True
)

armature_object_name: bpy.props.StringProperty( # type: ignore[valid-type]
default="" # noqa: F722
allow_non_humanoid_rig: bpy.props.BoolProperty( # type: ignore[valid-type]
name="Allow Non-Humanoid Rig", # noqa: F722
)

def human_bone_name_to_human_bone(
Expand Down Expand Up @@ -313,55 +313,8 @@ def human_bone_name_to_human_bone(

def error_messages(self) -> list[str]:
messages: list[str] = []

# -------------------------------
# Check if Humanoid Bones are assigned
# -------------------------------
from ...common.vrm1 import human_bone as vrm1_human_bone

armature_object_name = self.armature_object_name
armature = bpy.data.objects.get(armature_object_name)
if armature is None:
return messages
armature_data = armature.data
if not isinstance(armature_data, bpy.types.Armature):
return messages

all_required_bones_exist = True
if armature_data.vrm_addon_extension.is_vrm1():
human_bones = armature_data.vrm_addon_extension.vrm1.humanoid.human_bones

human_bone_name_to_human_bone = human_bones.human_bone_name_to_human_bone()
for (
human_bone_name,
human_bone,
) in human_bone_name_to_human_bone.items():
human_bone_specification = vrm1_human_bone.HumanBoneSpecifications.get(
human_bone_name
)
if not human_bone_specification.requirement:
continue
if (
human_bone.node
and human_bone.node.bone_name
and human_bone.node.bone_name in armature_data.bones
):
continue
all_required_bones_exist = False

humanoid_armature = all_required_bones_exist

human_bone_name_to_human_bone = self.human_bone_name_to_human_bone()

# If not humanoid, append a warning message
if not humanoid_armature:
messages.append(
pgettext(
"This armature will be exported but not as humanoid. Not compatible with most VRM apps.",
)
)
return messages
# If humanoid, return list of bones that are not assigned
for name, human_bone in human_bone_name_to_human_bone.items():
specification = HumanBoneSpecifications.get(name)
if not human_bone.node.bone_name:
Expand Down Expand Up @@ -414,7 +367,6 @@ def fixup_human_bones(obj: bpy.types.Object) -> None:

# 複数のボーンマップに同一のBlenderのボーンが設定されていたら片方を削除
fixup = True
fixup = False # See if we can force non-humanoid export
while fixup:
fixup = False
found_node_bone_names = []
Expand Down Expand Up @@ -538,6 +490,7 @@ def check_last_bone_names_and_update(
StringPropertyGroup
]
initial_automatic_bone_assignment: bool # type: ignore[no-redef]
allow_non_humanoid_rig: bool # type: ignore[no-redef]


# https://github.com/vrm-c/vrm-specification/blob/6fb6baaf9b9095a84fb82c8384db36e1afeb3558/specification/VRMC_vrm-1.0-beta/schema/VRMC_vrm.humanoid.schema.json
Expand Down
28 changes: 20 additions & 8 deletions io_scene_vrm/exporter/export_scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,10 @@ def invoke(self, context: bpy.types.Context, event: bpy.types.Event) -> set[str]
bpy.ops.vrm.assign_vrm1_humanoid_human_bones_automatically(
armature_name=armature.name
)
if not human_bones.all_required_bones_are_assigned():
if (
not human_bones.all_required_bones_are_assigned()
and not human_bones.allow_non_humanoid_rig
):
return bpy.ops.wm.vrm_export_human_bones_assignment(
"INVOKE_DEFAULT",
armature_object_name=self.armature_object_name,
Expand Down Expand Up @@ -431,7 +434,10 @@ def execute(self, context: bpy.types.Context) -> set[str]:
defer=False,
)
human_bones = armature_data.vrm_addon_extension.vrm1.humanoid.human_bones
if not human_bones.all_required_bones_are_assigned():
if (
not human_bones.all_required_bones_are_assigned()
and not human_bones.allow_non_humanoid_rig
):
return {"CANCELLED"}
else:
return {"CANCELLED"}
Expand Down Expand Up @@ -505,6 +511,14 @@ def draw_vrm1(layout: bpy.types.UILayout, armature: bpy.types.Object) -> None:
alert_box.label(
text="All VRM Required Bones have been assigned.", icon="CHECKMARK"
)
elif human_bones.allow_non_humanoid_rig:
alert_box = layout.box()
alert_box.label(
text="This armature will be exported but not as humanoid."
+ " It can not have animations applied"
+ " for humanoid avatars.",
icon="CHECKMARK",
)
else:
alert_box = layout.box()
alert_box.alert = True
Expand All @@ -522,6 +536,9 @@ def draw_vrm1(layout: bpy.types.UILayout, armature: bpy.types.Object) -> None:
draw_vrm1_humanoid_required_bones_layout(human_bones, row.column())
draw_vrm1_humanoid_optional_bones_layout(human_bones, row.column())

non_humanoid_export_column = layout.column()
non_humanoid_export_column.prop(human_bones, "allow_non_humanoid_rig")

if TYPE_CHECKING:
# This code is auto generated.
# `poetry run ./scripts/property_typing.py`
Expand Down Expand Up @@ -626,7 +643,6 @@ def invoke(self, context: bpy.types.Context, _event: bpy.types.Event) -> set[str
candidate = self.armature_object_name_candidates.add()
candidate.value = obj.name

Vrm1HumanBonesPropertyGroup.armature_object_name = self.armature_object_name
return context.window_manager.invoke_props_dialog(self, width=600)

def draw(self, _context: bpy.types.Context) -> None:
Expand Down Expand Up @@ -692,11 +708,7 @@ def detect_errors(
if armature_data.vrm_addon_extension.is_vrm1():
humanoid = ext.vrm1.humanoid
if not bool(humanoid.human_bones.all_required_bones_are_assigned()):
logger.debug(
"Non-Humanoid Armature Detected"
) # This is a non-humanoid armature
return []
error_messages.append("Please assign required human bones")
error_messages.append(pgettext("Please assign required human bones"))
else:
error_messages.append(pgettext("Please set the version of VRM to 1.0"))

Expand Down
Loading

0 comments on commit f2003b3

Please sign in to comment.