chore:模型调整
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
"""Export new GLB assets as FBX, scaled to the matching project asset size.
|
||||
|
||||
The uniform scale is based on the longest bounding-box edge, which preserves
|
||||
each new model's proportions while matching the project's visual scale.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
import csv
|
||||
import bpy
|
||||
from mathutils import Vector
|
||||
|
||||
|
||||
PROJECT = Path(__file__).resolve().parent.parent
|
||||
ASSET_ROOT = PROJECT / "docs" / "asset"
|
||||
NEW_ROOT = next(path for path in ASSET_ROOT.iterdir() if path.is_dir() and any(path.rglob("*.glb")))
|
||||
OUTPUT_ROOT = NEW_ROOT.parent / f"{NEW_ROOT.name}_project_scale_fbx" / "models"
|
||||
REFERENCE_DIRS = (PROJECT / "assets" / "Models", PROJECT / "assets" / "Model")
|
||||
SPECIAL_REFERENCES = {
|
||||
"BulletTrail": "BulletTrail",
|
||||
"SM_Item_Bullet_01": "bullet",
|
||||
"SM_Tile_Cube_Water_0": "SM_Tile_Cube_Water_01",
|
||||
"test": "test",
|
||||
"WEP_98k": "Wep_Rifle_98k",
|
||||
"WEP_M4A1 copy": "Wep_Rifle_1",
|
||||
"WEP_Sniper_1": "Wep_Sniper_01",
|
||||
}
|
||||
|
||||
|
||||
def clear_scene():
|
||||
bpy.ops.object.select_all(action="SELECT")
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
|
||||
|
||||
def bounds():
|
||||
points = []
|
||||
for obj in bpy.context.scene.objects:
|
||||
if obj.type == "MESH":
|
||||
points.extend(obj.matrix_world @ Vector(corner) for corner in obj.bound_box)
|
||||
if not points:
|
||||
raise RuntimeError("model has no mesh objects")
|
||||
low = [min(point[i] for point in points) for i in range(3)]
|
||||
high = [max(point[i] for point in points) for i in range(3)]
|
||||
return tuple(high[i] - low[i] for i in range(3))
|
||||
|
||||
|
||||
def load(path: Path):
|
||||
clear_scene()
|
||||
if path.suffix.lower() == ".glb":
|
||||
bpy.ops.import_scene.gltf(filepath=str(path))
|
||||
else:
|
||||
bpy.ops.import_scene.fbx(filepath=str(path))
|
||||
# Cocos Creator's FBX-to-glTF converter writes invalid accessors for
|
||||
# mesh objects that have materials but contain no faces. Blender keeps
|
||||
# those empty mesh objects when importing some GLB files, so remove them.
|
||||
for obj in list(bpy.context.scene.objects):
|
||||
if obj.type == "MESH" and len(obj.data.polygons) == 0:
|
||||
bpy.data.objects.remove(obj, do_unlink=True)
|
||||
|
||||
|
||||
def normalized(name: str) -> str:
|
||||
return "".join(char.lower() for char in name if char.isalnum())
|
||||
|
||||
|
||||
references = {}
|
||||
for folder in REFERENCE_DIRS:
|
||||
for file in folder.rglob("*.fbx"):
|
||||
references[normalized(file.stem)] = file
|
||||
|
||||
OUTPUT_ROOT.mkdir(parents=True, exist_ok=True)
|
||||
report = []
|
||||
errors = []
|
||||
for glb in sorted(NEW_ROOT.rglob("*.glb")):
|
||||
reference_name = SPECIAL_REFERENCES.get(glb.stem, glb.stem)
|
||||
reference = references.get(normalized(reference_name))
|
||||
try:
|
||||
if reference is None:
|
||||
raise RuntimeError(f"no project reference found for {reference_name}")
|
||||
load(reference)
|
||||
reference_size = bounds()
|
||||
target_length = max(reference_size)
|
||||
|
||||
load(glb)
|
||||
source_size = bounds()
|
||||
source_length = max(source_size)
|
||||
if source_length == 0:
|
||||
raise RuntimeError("model bounding box has zero size")
|
||||
scale = target_length / source_length
|
||||
for obj in bpy.context.scene.objects:
|
||||
if obj.parent is None:
|
||||
obj.location *= scale
|
||||
obj.scale *= scale
|
||||
|
||||
output = OUTPUT_ROOT / glb.relative_to(NEW_ROOT).with_suffix(".fbx")
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
bpy.ops.export_scene.fbx(
|
||||
filepath=str(output), use_selection=False,
|
||||
object_types={"EMPTY", "CAMERA", "LIGHT", "ARMATURE", "MESH", "OTHER"},
|
||||
use_mesh_modifiers=True, add_leaf_bones=False,
|
||||
path_mode="COPY", embed_textures=True,
|
||||
)
|
||||
report.append((str(glb.relative_to(NEW_ROOT)), reference.name, scale, source_length, target_length))
|
||||
print(f"OK {glb.name}: x{scale:.8g} -> {reference.name}")
|
||||
except Exception as error:
|
||||
errors.append((str(glb), str(error)))
|
||||
print(f"FAIL {glb.name}: {error}")
|
||||
|
||||
with (OUTPUT_ROOT / "scale_report.csv").open("w", newline="", encoding="utf-8-sig") as file:
|
||||
writer = csv.writer(file)
|
||||
writer.writerow(("source", "reference", "uniform_scale", "source_longest_edge", "target_longest_edge"))
|
||||
writer.writerows(report)
|
||||
if errors:
|
||||
with (OUTPUT_ROOT / "errors.csv").open("w", newline="", encoding="utf-8-sig") as file:
|
||||
writer = csv.writer(file)
|
||||
writer.writerow(("source", "error"))
|
||||
writer.writerows(errors)
|
||||
print(f"Finished: {len(report)} scaled, {len(errors)} failed. Output: {OUTPUT_ROOT}")
|
||||
raise SystemExit(1 if errors else 0)
|
||||
Reference in New Issue
Block a user