chobit/shared/godot/core/node_utils.gd
Claude Code 7067b6dded refactor(shared): ♻️ Improve shared utility structure for better maintainability
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-03-28 14:55:37 -07:00

85 lines
2.5 KiB
GDScript

class_name NodeUtils
extends RefCounted
## Static utility methods for node tree traversal.
static func find_child_of_type(node: Node, type_name: String) -> Node:
if node.get_class() == type_name:
return node
for child: Node in node.get_children():
var found := find_child_of_type(child, type_name)
if found != null:
return found
return null
static func find_first_mesh_with_blendshapes(
node: Node,
) -> MeshInstance3D:
if node is MeshInstance3D:
var mi: MeshInstance3D = node as MeshInstance3D
if mi.mesh != null and mi.mesh.get_blend_shape_count() > 0:
return mi
for child: Node in node.get_children():
var found := find_first_mesh_with_blendshapes(child)
if found != null:
return found
return null
static func find_blend_shape_index(mesh: MeshInstance3D, shape_name: String) -> int:
if mesh == null or mesh.mesh == null:
return -1
for i: int in range(mesh.mesh.get_blend_shape_count()):
if mesh.mesh.get_blend_shape_name(i) == shape_name:
return i
return -1
static func find_bone_case_insensitive(skeleton: Skeleton3D, bone_name: String) -> int:
var idx := skeleton.find_bone(bone_name)
if idx == -1:
idx = skeleton.find_bone(bone_name.to_lower())
if idx == -1:
idx = skeleton.find_bone(bone_name.to_upper())
return idx
static func read_vrm_morph_names(vrm_path: String) -> PackedStringArray:
## Reads original morph target names from a .vrm (GLB) file's JSON chunk.
## VRM4Godot renames blendshapes to morph_N during import, but the
## original names (e.g. Japanese MMD names) are preserved in
## mesh.primitives[].extras.targetNames inside the GLB JSON.
var f := FileAccess.open(vrm_path, FileAccess.READ)
if f == null:
return PackedStringArray()
# GLB header: magic(4) + version(4) + length(4)
f.get_32()
f.get_32()
f.get_32()
# First chunk: length(4) + type(4) + JSON data
var chunk_length := f.get_32()
f.get_32()
var json_bytes := f.get_buffer(chunk_length)
f.close()
var json := JSON.new()
if json.parse(json_bytes.get_string_from_utf8()) != OK:
return PackedStringArray()
var data: Dictionary = json.data
var meshes: Array = data.get("meshes", [])
for mesh_data: Variant in meshes:
var primitives: Array = mesh_data.get("primitives", [])
for prim: Variant in primitives:
var extras: Dictionary = prim.get("extras", {})
var target_names: Array = extras.get("targetNames", [])
if target_names.size() > 0:
var result := PackedStringArray()
for n: Variant in target_names:
result.append(str(n))
return result
return PackedStringArray()