Files
blender-portable-repo/extensions/user_default/amzncharactertools/ops/hh_spawn.py
T
2026-03-17 15:25:32 -06:00

173 lines
5.4 KiB
Python

import bpy
import os
from mathutils import Matrix
try:
from ..utils import get_addon_preferences
except (ImportError, ValueError):
# Fallback if import fails (e.g., when run as script)
def get_addon_preferences():
test_names = ["bl_ext.vscode_development.AmazonCharacterTools", "amzncharactertools", "AmazonCharacterTools"]
for addon_name in test_names:
addon_prefs = bpy.context.preferences.addons.get(addon_name)
if addon_prefs and hasattr(addon_prefs, 'preferences') and hasattr(addon_prefs.preferences, 'amzn_bsdf_materials_path'):
return addon_prefs.preferences
# Search all addons
for addon_name in bpy.context.preferences.addons.keys():
addon_prefs = bpy.context.preferences.addons.get(addon_name)
if addon_prefs and hasattr(addon_prefs, 'preferences') and hasattr(addon_prefs.preferences, 'amzn_bsdf_materials_path'):
return addon_prefs.preferences
return None
ASSET_OBJECT_NAME = "hard-hat"
GN_MOD_NAME = "hard-hat-transforms"
def find_armature_with_head():
active = bpy.context.active_object
if active and active.type == 'ARMATURE' and 'head' in active.pose.bones:
return active
for obj in bpy.data.objects:
if obj.type == 'ARMATURE' and 'head' in obj.pose.bones:
return obj
return None
def append_hard_hat():
# Get path from addon preferences
prefs = get_addon_preferences()
if not prefs:
print("Error: Could not access addon preferences")
return None
asset_blend_path = prefs.amzn_hardhat_asset_path
if not asset_blend_path or not os.path.exists(asset_blend_path):
print(f"Error: Hard hat asset library path not set or file not found: {asset_blend_path}")
return None
with bpy.data.libraries.load(asset_blend_path, link=False, assets_only=True) as (data_from, data_to):
if ASSET_OBJECT_NAME in data_from.objects:
data_to.objects = [ASSET_OBJECT_NAME]
else:
print(f"Error: '{ASSET_OBJECT_NAME}' not found in asset file")
return None
obj = bpy.data.objects.get(ASSET_OBJECT_NAME)
if not obj:
print("Error: hard-hat object failed to append")
return None
# Link to current collection (temporary)
if obj.name not in bpy.context.collection.objects:
bpy.context.collection.objects.link(obj)
# Make it no longer an asset
try:
obj.asset_clear()
except Exception:
pass
return obj
def strip_geonodes(obj):
for mod in list(obj.modifiers):
if mod.type == 'NODES' and (mod.name == GN_MOD_NAME or (mod.node_group and mod.node_group.name == GN_MOD_NAME)):
obj.modifiers.remove(mod)
def align_to_head(obj, armature):
head_pb = armature.pose.bones.get('head')
if not head_pb:
print("Error: head pose bone not found")
return
# Apply transforms: X-90°, Y90°, Z offset -0.07
import math
from mathutils import Euler, Vector
# Get head bone matrix
head_matrix = armature.matrix_world @ head_pb.matrix
# Apply rotation offsets
rotation_offset = Euler((math.radians(-90), math.radians(90), 0), 'XYZ')
rotation_matrix = rotation_offset.to_matrix().to_4x4()
# First set rotation relative to head
obj.matrix_world = head_matrix @ rotation_matrix
# Then apply global offsets (world space)
obj.matrix_world.translation += Vector((0.0, 0.0, -0.07)) # global Z
obj.matrix_world.translation += Vector((0.0, -0.004, 0.0)) # global Y
# Apply uniform scale before parenting so it sticks
try:
obj.scale *= 1.4
except Exception:
obj.scale = (1.4, 1.4, 1.4)
def get_accessories_collection():
# Prefer an existing 'Accessories' collection that already holds known items
candidates = [c for c in bpy.data.collections if c.name == 'Accessories']
def has_known(c):
names = {o.name for o in c.objects}
return any(n in names for n in ('Device', 'device-band', 'Finger-Scanner', 'Lanyard', 'Vest'))
for c in candidates:
if has_known(c):
return c
if candidates:
return candidates[0]
# Create under scene root if not found
coll = bpy.data.collections.new('Accessories')
bpy.context.scene.collection.children.link(coll)
return coll
def move_to_accessories(obj):
acc = get_accessories_collection()
# Unlink from other collections to avoid duplicates
for c in list(obj.users_collection):
try:
c.objects.unlink(obj)
except Exception:
pass
# Link to Accessories
if obj.name not in acc.objects:
acc.objects.link(obj)
def parent_to_head(obj, armature):
head_pb = armature.pose.bones.get('head')
if not head_pb:
print("Error: head pose bone not found")
return
obj.parent = armature
obj.parent_type = 'BONE'
obj.parent_bone = 'head'
obj.matrix_parent_inverse = (armature.matrix_world @ head_pb.matrix).inverted()
def run():
arm = find_armature_with_head()
if not arm:
print("Error: No armature with 'head' bone found")
return
hat = append_hard_hat()
if not hat:
return
strip_geonodes(hat)
align_to_head(hat, arm)
parent_to_head(hat, arm)
move_to_accessories(hat)
bpy.context.view_layer.objects.active = hat
hat.select_set(True)
print("HH appended, cleaned, aligned, and parented to head.")
run()