43 lines
2.2 KiB
Python
43 lines
2.2 KiB
Python
"""Repair Studio face winding on welded topology without generating UVs."""
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
import bpy
|
|
import bmesh
|
|
|
|
source, destination, kind = sys.argv[sys.argv.index('--') + 1:][:3]
|
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
|
bpy.ops.import_scene.gltf(filepath=source, merge_vertices=False)
|
|
obj = next(o for o in bpy.context.scene.objects if o.type == 'MESH')
|
|
bpy.context.view_layer.objects.active = obj
|
|
bpy.ops.object.select_all(action='DESELECT'); obj.select_set(True)
|
|
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
|
|
uv = obj.data.uv_layers.active
|
|
uv_before = sorted(tuple(round(c, 6) for c in v.uv) for v in uv.data)
|
|
bm = bmesh.new(); bm.from_mesh(obj.data)
|
|
bmesh.ops.remove_doubles(bm, verts=list(bm.verts), dist=max(obj.dimensions) * 1e-7)
|
|
bmesh.ops.recalc_face_normals(bm, faces=list(bm.faces))
|
|
flipped = 0
|
|
if kind == 'ground':
|
|
# Thin terrain is intentionally authored with a top and underside. Some
|
|
# Studio patches are disconnected, so orient each flat patch explicitly.
|
|
low = min(v.co.z for v in bm.verts); high = max(v.co.z for v in bm.verts)
|
|
midpoint = (low + high) / 2
|
|
for face in bm.faces:
|
|
face.normal_update()
|
|
if abs(face.normal.z) > .35:
|
|
# Terrain is viewed and collided from above. Its paving can be
|
|
# lower than raised grass, so a global height midpoint cannot
|
|
# distinguish the paving top from an underside patch.
|
|
if face.normal.z < 0:
|
|
face.normal_flip(); flipped += 1
|
|
bm.to_mesh(obj.data); bm.free(); obj.data.update()
|
|
uv_after = sorted(tuple(round(c, 6) for c in v.uv) for v in obj.data.uv_layers.active.data)
|
|
assert uv_before == uv_after, 'UV coordinates changed during winding repair'
|
|
bpy.ops.export_scene.gltf(filepath=destination, export_format='GLB', use_selection=True,
|
|
export_materials='EXPORT', export_image_format='AUTO')
|
|
report = dict(kind=kind, uvPreserved=True, flatTerrainFacesFlipped=flipped,
|
|
triangles=sum(len(p.vertices)-2 for p in obj.data.polygons))
|
|
Path(destination).with_suffix('.json').write_text(json.dumps(report, indent=2), encoding='utf-8')
|
|
print('MT_SUMMARY '+json.dumps(report), flush=True)
|