144 lines
4.2 KiB
Python
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()
|
|
|
|
|