"""检查混元 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(" 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。" )