78 lines
4.6 KiB
Python
78 lines
4.6 KiB
Python
"""Offline packaging + original-skeleton rebind of a Studio trial (generalized from greathorn_studio_finish.py).
|
|
|
|
Studio mesh -> project Y-up FBX + XPbr -> weights from the ORIGINAL rig (bl_transfer_skin) -> Pipeline import ->
|
|
StudioTrial.Prepare -> per-vertex sole constraints over the original clips -> local 1mm sole fit if needed -> Build/verify
|
|
-> comparison review + capture -> optional Play check. Original prefab/controller/clips/rig hashes must stay unchanged.
|
|
|
|
python Tools/studio_lowpoly_finish.py --actor RockBall --height 0.55 --play-check
|
|
"""
|
|
import argparse, hashlib, json, subprocess, sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
BLENDER = 'D:/Tools/Blender/blender-5.0.0-windows-x64/blender.exe'
|
|
sys.path.insert(0, str(ROOT / 'Tools/model_tools'))
|
|
from mt_run import run_blender_script
|
|
sys.path.insert(0, str(ROOT / 'Tools'))
|
|
from enemy_character_pipeline import unity, script, wait_editor
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument('--actor', required=True)
|
|
ap.add_argument('--height', type=float, required=True)
|
|
ap.add_argument('--play-check', action='store_true')
|
|
ap.add_argument('--recipe', type=Path, help='character recipe whose skinOverrides/skinRegions must be re-applied to the new mesh')
|
|
a = ap.parse_args()
|
|
out = ROOT / 'Tools/output/studio_trial' / a.actor
|
|
receipt = json.loads((out / 'trial.json').read_text(encoding='utf-8'))
|
|
assert receipt['status'] in ('mesh_ready', 'edit_validated', 'validated'), receipt['status']
|
|
kind, prefab, name = receipt['kind'], receipt['prefab'], a.actor + 'Studio'
|
|
asset = f'Assets/Bundles/Characters/{kind}/{name}'
|
|
entry = [a.actor, kind, prefab, a.height]
|
|
|
|
def unchanged():
|
|
for relative, digest in receipt['originals'].items():
|
|
assert hashlib.sha256((ROOT / relative).read_bytes()).hexdigest() == digest, relative + ' changed'
|
|
|
|
def blender(file, args, log):
|
|
with (out / log).open('w', encoding='utf-8') as f:
|
|
subprocess.run([BLENDER, '-b', '-t', '1', '--factory-startup', '--python', str(ROOT / 'Tools/model_tools' / file), '--', *map(str, args)],
|
|
stdout=f, stderr=subprocess.STDOUT, timeout=600, check=True)
|
|
|
|
unchanged()
|
|
blender('bl_prepare_studio_trial.py', [out, name], 'static_export.log')
|
|
result = run_blender_script(BLENDER, 'bl_convert.py', [str(out / f'static/{name}.fbx'), str(out / 'xpbr'), str(receipt.get('textureSize', 1024))])
|
|
(out / 'xpbr.json').write_text(json.dumps(result, indent=2), encoding='utf-8')
|
|
print('XPbr packaging complete', flush=True)
|
|
blender('bl_transfer_skin.py', [ROOT / receipt['rig'], out / f'xpbr/{name}.fbx', out / f'{name}_Rigged.fbx', '--low-up', 'Y', '--align-bounds', *(['--weight-overrides', ROOT / a.recipe] if a.recipe else [])], 'skin_transfer.log')
|
|
print('Original rig weight transfer complete', flush=True)
|
|
unity('set_autotick', enable=True)
|
|
if unity('editor_status')['playMode'] != 'stopped': raise RuntimeError('Editor is in Play; offline trial is ready, import after it stops')
|
|
for f in [f'{name}.fbx', f'{name}_base.png', f'{name}_mix.png']:
|
|
unity('import_asset', source=str(out / 'xpbr' / f), path=asset + '/' + f, confirm=True)
|
|
unity('import_asset', source=str(out / f'{name}_Rigged.fbx'), path=asset + f'/Rigged/{name}_Rigged.fbx', confirm=True)
|
|
script('SetupCharacter', 'SetupCharacter.Run', [kind, name, a.height])
|
|
script('StudioTrial', 'StudioTrial.Prepare', entry)
|
|
script('StudioTrial', 'StudioTrial.ExportFootConstraints', entry)
|
|
blender('bl_fit_studio_soles.py', [out, name], 'sole_fit.log')
|
|
unity('import_asset', source=str(out / f'{name}_Rigged.fbx'), path=asset + f'/Rigged/{name}_Rigged.fbx', confirm=True)
|
|
script('StudioTrial', 'StudioTrial.Build', entry)
|
|
script('StudioTrial', 'StudioTrial.BuildReview', entry)
|
|
script('StudioTrial', 'StudioTrial.Capture', entry)
|
|
receipt['status'] = 'edit_validated'
|
|
if a.play_check:
|
|
unity('editor_play')
|
|
try:
|
|
wait_editor('playing'); script('StudioTrial', 'StudioTrial.Play', entry); receipt['status'] = 'validated'
|
|
finally:
|
|
unity('editor_stop'); wait_editor('stopped')
|
|
unchanged()
|
|
receipt.update(soleFit=json.loads((out / 'sole_fit.json').read_text(encoding='utf-8')),
|
|
riggedSha256=hashlib.sha256((out / f'{name}_Rigged.fbx').read_bytes()).hexdigest(), originalResourcesUnchanged=True)
|
|
(out / 'trial.json').write_text(json.dumps(receipt, indent=2), encoding='utf-8')
|
|
print('PASS studio trial', receipt['status'], flush=True)
|
|
|
|
|
|
if __name__ == '__main__': main()
|