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

144 lines
4.2 KiB
Python

import bpy
from mathutils import Matrix
ASSET_BLEND_PATH = r"A:\\1 Amazon_Active_Projects\\1 BlenderAssets\\Amazon\\amazon-asset_Hard-Hat_v1.1.blend"
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():
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()