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>
This commit is contained in:
udx
2026-09-28 14:28:46 +08:00
co-authored by Cursor
parent 5f8cb890c8
commit 9793d8581d
273 changed files with 10388 additions and 34 deletions
+11 -1
View File
@@ -81,7 +81,17 @@ def status() -> dict:
def is_logged_in() -> bool:
return storage_state_path().is_file()
path = storage_state_path()
if not path.is_file() or path.stat().st_size < 8:
return False
try:
with path.open("rb") as fp:
if fp.read(1) != b"{":
return False
json.loads(path.read_text("utf-8"))
return True
except (OSError, ValueError, UnicodeDecodeError):
return False
def logout() -> dict:
+3
View File
@@ -798,6 +798,9 @@ def generate_from_image(
saved = directory / "hunyuan_raw.glb"
_download_url(context.request, glb_url, saved, log)
mesh = _extract_mesh(saved, directory)
from .glb_check import assert_glb_textures_ok
assert_glb_textures_ok(mesh)
log.write(f"extracted {mesh}\n")
(directory / "hunyuan_network.json").write_text(
json.dumps(network_hits[-120:], ensure_ascii=False, indent=2),
+88
View File
@@ -0,0 +1,88 @@
"""检查混元 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。"
)