feat(pointview): 新增「场景/点位」页签——正交俯视真实场景底图 + 点位精确叠加

第三个页签(与海选审核/演出配置平级),只读查看每个点位集里各点的真实
位置/朝向,配 move.to/camera.focus 时对照用,不必回 Unity 翻 json。

- pointview.js: 独立白模点位查看器(按 kind 上色/朝向箭头/悬停坐标/侧栏清单);
  有底图则把正交俯视真实场景图当画布底图、点位按 shot.bounds 线性投上去
  (像素级对齐家具),带显隐开关;无底图回退黑底白模。
- app.py: /api/pointsets 给有底图的点位集附 shot{url,bounds};新增
  /sceneshot/{name}.png 路由(防目录穿越)。
- Dockerfile/compose: 加 STORY_SCENESHOTS_DIR(/sceneshots) env + 挂载点与注释。

底图由 SGame 仓新增 Editor 工具「剧情场景俯视抓拍」产出
({name}.png + {name}.shot.json,map-local 覆盖范围)。
This commit is contained in:
2026-06-14 11:13:24 +08:00
parent 030f1ee34d
commit 603f78b77f
9 changed files with 414 additions and 4 deletions

View File

@ -34,6 +34,9 @@ _DICT_PATH = os.path.join(_AUTHORING, "ir_dictionary.json")
# 点位集目录:容器内用 STORY_POINTSETS_DIR 指向挂载卷;本地默认指向项目 Assets。
_POINTSETS_DIR = os.environ.get("STORY_POINTSETS_DIR") or \
os.path.join(_PROJ, "Assets", "StreamingAssets", "Story", "PointSets")
# 场景俯视底图目录Unity「剧情场景俯视抓拍」产出 {name}.png + {name}.shot.json含覆盖的 map-local 范围)。
_SCENESHOTS_DIR = os.environ.get("STORY_SCENESHOTS_DIR") or \
os.path.join(_PROJ, "Assets", "StreamingAssets", "Story", "SceneShots")
_STATIC_DIR = os.path.join(_HERE, "static")
COOKIE = "story_auth"
@ -162,6 +165,25 @@ async def dictionary():
return json.load(f)
def _load_shot(name):
"""{name}.shot.json含俯视底图覆盖的 map-local 范围);图与 sidecar 都在才算有效。"""
try:
meta = os.path.join(_SCENESHOTS_DIR, name + ".shot.json")
png = os.path.join(_SCENESHOTS_DIR, name + ".png")
if not (os.path.isfile(meta) and os.path.isfile(png)):
return None
with open(meta, encoding="utf-8") as f:
m = json.load(f)
b = m.get("bounds")
if not (isinstance(b, list) and len(b) == 4):
return None
# 缓存击穿:附 png mtime图更新后前端能拿到新图
return {"url": "/sceneshot/" + name + ".png?v=" + str(int(os.path.getmtime(png))),
"bounds": b, "w": m.get("w"), "h": m.get("h")}
except Exception:
return None
@app.get("/api/pointsets")
async def pointsets():
out = {}
@ -188,11 +210,25 @@ async def pointsets():
for p in pts
],
}
shot = _load_shot(name)
if shot:
out[name]["shot"] = shot # 有正交俯视底图 → 场景/点位页叠真实场景
except Exception as e:
out[name] = {"error": str(e)}
return out
@app.get("/sceneshot/{name}.png")
async def sceneshot(name: str):
# 防目录穿越:只认纯文件名
if not name or "/" in name or "\\" in name or ".." in name:
return JSONResponse({"error": "bad name"}, status_code=400)
p = os.path.join(_SCENESHOTS_DIR, name + ".png")
if not os.path.isfile(p):
return JSONResponse({"error": "not found"}, status_code=404)
return FileResponse(p, media_type="image/png")
# ---------- 事件 CRUD ----------
@app.get("/api/events")
async def events(status: str = "all"):