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:
36
web/app.py
36
web/app.py
@ -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"):
|
||||
|
||||
Reference in New Issue
Block a user