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:
@@ -7,6 +7,7 @@ import sys
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
SUPPORTED_MODEL_EXTENSIONS = (".fbx", ".glb", ".gltf")
|
||||
FBX_YUP_EXPORT_VERSION = 2
|
||||
|
||||
|
||||
def model_format(path):
|
||||
@@ -87,18 +88,87 @@ def prepare_game_export(obj, restore_axis=None):
|
||||
|
||||
|
||||
def export_fbx_source_yup(filepath):
|
||||
"""导出与混元 GLB 相同的 Y-up FBX。调用前顶点须已是 Y-up。"""
|
||||
"""Export prepared Y-up geometry as an actual Y-up FBX scene.
|
||||
|
||||
prepare_game_export stores Y-up vertices inside Blender's Z-up workspace.
|
||||
Passing those straight to the FBX exporter adds a -90 X object rotation,
|
||||
so consumers applying the complete FBX transform see a horizontal model.
|
||||
Restore Blender Z-up on temporary copies, then bake the export conversion.
|
||||
Leave source meshes, UVs, transforms and selection unchanged for callers.
|
||||
"""
|
||||
import math
|
||||
import bpy
|
||||
bpy.ops.export_scene.fbx(
|
||||
filepath=filepath,
|
||||
use_selection=True,
|
||||
object_types={"MESH"},
|
||||
axis_forward="-Z",
|
||||
axis_up="Y",
|
||||
bake_space_transform=False,
|
||||
path_mode="STRIP",
|
||||
embed_textures=False,
|
||||
)
|
||||
from mathutils import Matrix
|
||||
|
||||
selected = list(bpy.context.selected_objects)
|
||||
active = bpy.context.view_layer.objects.active
|
||||
meshes = [obj for obj in selected if obj.type == "MESH"]
|
||||
if not meshes:
|
||||
raise ValueError("Select at least one prepared Y-up mesh for FBX export")
|
||||
copies = []
|
||||
source_names = [(source, source.name) for source in meshes]
|
||||
try:
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
for source, source_name in source_names:
|
||||
duplicate = source.copy()
|
||||
duplicate.data = source.data.copy()
|
||||
copies.append(duplicate)
|
||||
bpy.context.scene.collection.objects.link(duplicate)
|
||||
source.name = source_name + "__fbx_export_source"
|
||||
duplicate.name = source_name
|
||||
duplicate.parent = None
|
||||
duplicate.matrix_world = Matrix.Rotation(math.pi / 2, 4, "X") @ source.matrix_world
|
||||
duplicate.select_set(True)
|
||||
bpy.context.view_layer.objects.active = copies[0]
|
||||
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
|
||||
bpy.ops.export_scene.fbx(
|
||||
filepath=filepath,
|
||||
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,
|
||||
)
|
||||
return validate_fbx_yup_scene(filepath)
|
||||
finally:
|
||||
for duplicate in copies:
|
||||
mesh = duplicate.data
|
||||
bpy.data.objects.remove(duplicate, do_unlink=True)
|
||||
if mesh.users == 0:
|
||||
bpy.data.meshes.remove(mesh)
|
||||
for obj in selected:
|
||||
obj.select_set(True)
|
||||
for source, source_name in source_names:
|
||||
source.name = source_name
|
||||
bpy.context.view_layer.objects.active = active
|
||||
|
||||
|
||||
def validate_fbx_yup_scene(filepath):
|
||||
"""Reject the old extra node rotation even when raw vertices look Y-up."""
|
||||
from io_scene_fbx import parse_fbx
|
||||
|
||||
root, _ = parse_fbx.parse(str(filepath))
|
||||
def properties(element):
|
||||
block = next(e for e in element.elems if e.id == b"Properties70")
|
||||
return {p.props[0].decode(): list(p.props[4:]) for p in block.elems}
|
||||
settings = properties(next(e for e in root.elems if e.id == b"GlobalSettings"))
|
||||
if settings.get("UpAxis") != [1] or settings.get("UpAxisSign") != [1]:
|
||||
raise ValueError("Exported FBX scene is not +Y-up")
|
||||
objects = next(e for e in root.elems if e.id == b"Objects")
|
||||
for model in (e for e in objects.elems if e.id == b"Model"):
|
||||
values = properties(model)
|
||||
if any(abs(v) > 1e-5 for v in values.get("Lcl Rotation", [0, 0, 0])):
|
||||
raise ValueError("Exported FBX retains an unbaked node rotation")
|
||||
if any(abs(v - 1) > 1e-5 for v in values.get("Lcl Scaling", [1, 1, 1])):
|
||||
raise ValueError("Exported FBX retains an unbaked node scale")
|
||||
return {"exportVersion": FBX_YUP_EXPORT_VERSION, "sceneUp": "+Y",
|
||||
"nodeRotationBaked": True, "nodeScaleBaked": True,
|
||||
"unitScaleFactor": settings.get("UnitScaleFactor", [1])[0]}
|
||||
|
||||
TOLERANCE = 0.03 # 面数相对误差容忍
|
||||
MAX_RETRY = 2 # ratio 修正轮数上限
|
||||
|
||||
Reference in New Issue
Block a user