Ship the WeChat hot-update catalogs and the camp, TideWhale T2, UI atlas, and animation-lab work.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
"""Create an upright Mixamo upload FBX from the legacy Y-up-in-Blender mesh.
|
||||
|
||||
Do not replace the original: existing rigs and review assets use its coordinates.
|
||||
"""
|
||||
import sys,math,json,hashlib
|
||||
from pathlib import Path
|
||||
import bpy
|
||||
import numpy as np
|
||||
from mathutils import Matrix
|
||||
from io_scene_fbx import parse_fbx
|
||||
|
||||
ROOT=Path(__file__).resolve().parents[2]
|
||||
SOURCE=ROOT/'Tools/output/lmgirl_tpose_studio/static/LmGirlTpose.fbx'
|
||||
OUT=ROOT/'Tools/output/lmgirl_tpose_studio/mixamo_upload'
|
||||
TARGET=OUT/'LmGirlTpose_Mixamo.fbx'
|
||||
|
||||
def digest(path):return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
def vertices(o):return np.array([o.matrix_world@v.co for v in o.data.vertices],dtype=float)
|
||||
def uv(o):return np.array([tuple(v.uv) for v in o.data.uv_layers.active.data],dtype=np.float32)
|
||||
def triangles(o):return sum(len(p.vertices)-2 for p in o.data.polygons)
|
||||
|
||||
OUT.mkdir(parents=True,exist_ok=True)
|
||||
before_hash=digest(SOURCE)
|
||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||
bpy.ops.import_scene.fbx(filepath=str(SOURCE),use_anim=False)
|
||||
meshes=[o for o in bpy.context.scene.objects if o.type=='MESH']
|
||||
assert len(meshes)==1 and not any(o.type=='ARMATURE' for o in bpy.context.scene.objects)
|
||||
obj=meshes[0];old_points=vertices(obj);old_uv=uv(obj);old_triangles=triangles(obj)
|
||||
assert np.argmax(np.ptp(old_points,axis=0))==1,'This repair is only for the legacy Y-up Blender source'
|
||||
# Blender's exporter expects a Z-up scene. Restore that before its Y-up conversion.
|
||||
rotation=Matrix.Rotation(math.pi/2,4,'X')
|
||||
obj.matrix_world=rotation@obj.matrix_world
|
||||
bpy.context.view_layer.update()
|
||||
bpy.ops.object.select_all(action='DESELECT');obj.select_set(True);bpy.context.view_layer.objects.active=obj
|
||||
bpy.ops.object.transform_apply(location=True,rotation=True,scale=True)
|
||||
expected=vertices(obj)
|
||||
assert np.argmax(np.ptp(expected,axis=0))==2
|
||||
bpy.ops.export_scene.fbx(filepath=str(TARGET),use_selection=True,object_types={'MESH'},
|
||||
axis_forward='-Z',axis_up='Y',bake_space_transform=True,
|
||||
apply_unit_scale=True,apply_scale_options='FBX_SCALE_ALL',bake_anim=False,
|
||||
path_mode='STRIP',embed_textures=False)
|
||||
|
||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||
bpy.ops.import_scene.fbx(filepath=str(TARGET),use_anim=False)
|
||||
restored=next(o for o in bpy.context.scene.objects if o.type=='MESH')
|
||||
points=vertices(restored);new_uv=uv(restored)
|
||||
assert points.shape==expected.shape and np.max(np.abs(points-expected))<2e-6
|
||||
assert np.array_equal(new_uv,old_uv),'UVs changed'
|
||||
assert triangles(restored)==old_triangles
|
||||
tree,_=parse_fbx.parse(str(TARGET))
|
||||
objects=next(e for e in tree.elems if e.id==b'Objects')
|
||||
model=next(e for e in objects.elems if e.id==b'Model')
|
||||
props=next(e for e in model.elems if e.id==b'Properties70')
|
||||
node_props={e.props[0].decode():list(e.props[4:]) for e in props.elems}
|
||||
assert max(abs(v) for v in node_props.get('Lcl Rotation',[0,0,0]))<1e-5
|
||||
assert all(abs(v-1)<1e-5 for v in node_props.get('Lcl Scaling',[1,1,1]))
|
||||
geometry=next(e for e in objects.elems if e.id==b'Geometry')
|
||||
raw_points=np.array(next(e for e in geometry.elems if e.id==b'Vertices').props[0]).reshape(-1,3)
|
||||
assert np.max(np.abs(raw_points-old_points))<2e-6,'Y-up geometry/front changed'
|
||||
assert digest(SOURCE)==before_hash,'Original source changed'
|
||||
report=dict(source=str(SOURCE.relative_to(ROOT)),output=str(TARGET.relative_to(ROOT)),
|
||||
source_sha256=before_hash,output_sha256=digest(TARGET),source_unchanged=True,
|
||||
repair='Restore Blender Z-up scene (+90 X), apply object transforms, export Y-up with baked space conversion',
|
||||
vertices=len(points),triangles=old_triangles,uv_unchanged=True,
|
||||
raw_fbx_node_rotation_degrees=node_props.get('Lcl Rotation',[0,0,0]),
|
||||
raw_fbx_node_scale=node_props.get('Lcl Scaling',[1,1,1]),
|
||||
raw_yup_geometry_max_error_m=float(np.max(np.abs(raw_points-old_points))),
|
||||
max_roundtrip_position_error_m=float(np.max(np.abs(points-expected))),
|
||||
blender_roundtrip_rotation_degrees=[math.degrees(v) for v in restored.rotation_euler],
|
||||
blender_roundtrip_bounds_min=points.min(0).tolist(),blender_roundtrip_bounds_max=points.max(0).tolist(),
|
||||
online_validation='Not uploaded to Mixamo; local FBX metadata and independent reimport checked')
|
||||
(OUT/'repair.json').write_text(json.dumps(report,indent=2),encoding='utf-8')
|
||||
print(json.dumps(report,indent=2))
|
||||
Reference in New Issue
Block a user