Files
udxandCursor 9793d8581d Add TownSquare scene assets, resource production docs, and model pipeline updates.
Bring in plaza props/prefabs, ZhaoYun source GLB, design originals, and Hunyuan/Blender tooling used to produce them.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-28 14:28:46 +08:00

89 lines
2.9 KiB
Python

"""检查混元 GLB 内嵌贴图能否解码。损坏 PNG 在 Blender 里是品红,烘进 color 就会整灯发紫。"""
from __future__ import annotations
import json
import struct
from pathlib import Path
from typing import List, Tuple
def _glb_chunks(data: bytes) -> Tuple[dict, bytes]:
if len(data) < 12 or data[:4] != b"glTF":
raise ValueError("不是 glTF GLB")
offset = 12
gltf = None
blob = b""
while offset + 8 <= len(data):
chunk_len, chunk_type = struct.unpack_from("<I4s", data, offset)
offset += 8
chunk = data[offset : offset + chunk_len]
offset += chunk_len
offset = (offset + 3) & ~3
if chunk_type == b"JSON":
gltf = json.loads(chunk.decode("utf-8"))
elif chunk_type == b"BIN\x00":
blob = chunk
if gltf is None:
raise ValueError("GLB 没有 JSON chunk")
return gltf, blob
def _image_blobs(gltf: dict, blob: bytes) -> List[bytes]:
views = gltf.get("bufferViews") or []
out: List[bytes] = []
for image in gltf.get("images") or []:
uri = image.get("uri")
if isinstance(uri, str) and uri.startswith("data:"):
continue
index = image.get("bufferView")
if index is None:
continue
view = views[int(index)]
start = int(view.get("byteOffset") or 0)
length = int(view.get("byteLength") or 0)
out.append(blob[start : start + length])
return out
def glb_textures_ok(path: Path) -> Tuple[bool, str]:
"""内嵌贴图都能解码则 (True, '')。无贴图或损坏返回 False 与原因。"""
path = Path(path)
if not path.is_file() or path.stat().st_size < 12:
return False, "文件不存在或过短"
data = path.read_bytes()
if data[:4] != b"glTF":
return False, "文件头不是 glTF"
try:
gltf, blob = _glb_chunks(data)
blobs = _image_blobs(gltf, blob)
except Exception as exc: # noqa: BLE001
return False, str(exc)
if not blobs:
return False, "没有内嵌贴图"
try:
from PIL import Image
except ImportError:
for raw in blobs:
if not (raw.startswith(b"\x89PNG") or raw[:2] == b"\xff\xd8"):
return False, "内嵌贴图不是 PNG/JPEG"
return True, ""
import io
for i, raw in enumerate(blobs):
try:
img = Image.open(io.BytesIO(raw))
img.load()
if img.size[0] < 8 or img.size[1] < 8:
return False, f"贴图 {i} 尺寸无效 {img.size}"
except Exception as exc: # noqa: BLE001
return False, f"贴图 {i} 无法解码: {exc}"
return True, ""
def assert_glb_textures_ok(path: Path) -> None:
ok, reason = glb_textures_ok(path)
if not ok:
raise RuntimeError(
f"{path} 内嵌贴图不可用({reason})。Blender 会显示品红并烘进 color。"
)