2026-01-01
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user