105 lines
5.8 KiB
Python
105 lines
5.8 KiB
Python
"""Validate and plan the authored battlefield scene pipeline.
|
||
No network or model generation is performed by this command.
|
||
"""
|
||
from __future__ import annotations
|
||
import argparse, json
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parents[1]
|
||
MANIFEST = ROOT / "Doc/Design/Scene/Battlefield/battlefield_scene_manifest.json"
|
||
|
||
|
||
def load():
|
||
with MANIFEST.open(encoding="utf-8-sig") as f:
|
||
return json.load(f)
|
||
|
||
def validate(data, require_selection=False):
|
||
errors=[]
|
||
selected=data.get("selectedDesign", "")
|
||
ids={x.get("id") for x in data.get("designOptions", [])}
|
||
if require_selection and selected not in ids: errors.append("selectedDesign 必须先选择 A/B/C")
|
||
if selected and selected not in ids: errors.append(f"未知方案: {selected}")
|
||
limit=int(data.get("componentTypeLimit", 0))
|
||
allowed=set(data.get("allowedComponentTypes", []))
|
||
components=data.get("components", [])
|
||
types=[x.get("type") for x in components]
|
||
if len(types)!=len(set(types)): errors.append("components 存在重复 type")
|
||
if len(types)>limit or len(types)>15: errors.append(f"组件类型 {len(types)} 超过上限")
|
||
for t in types:
|
||
if t not in allowed: errors.append(f"未登记组件类型: {t}")
|
||
ground=data.get("ground", {})
|
||
if not ground.get("type")=="ground": errors.append("ground 必须独立登记为 type=ground")
|
||
if any(x.get("type")=="ground" for x in components): errors.append("ground 不得放入 components")
|
||
if not data.get("assembly", {}).get("outputPrefab"): errors.append("缺少 assembly.outputPrefab")
|
||
return errors
|
||
|
||
def design_ready(data, design_id):
|
||
option = next((x for x in data.get("designOptions", []) if x.get("id") == design_id), None)
|
||
generation = (option or {}).get("generation", {})
|
||
return (
|
||
option is not None
|
||
and generation.get("tool") == "Tools/gpt_image_generate.py"
|
||
and generation.get("model") == "gpt-image-2.5"
|
||
and generation.get("status") == "generated"
|
||
)
|
||
|
||
def components_ready(data):
|
||
ground = data.get("ground", {})
|
||
if ground.get("designGenerationStatus") != "generated":
|
||
return False
|
||
return bool(data.get("components")) and all(
|
||
item.get("generationStatus") in {"generated", "reused"} for item in data.get("components", [])
|
||
)
|
||
|
||
def plan(data):
|
||
selected=data.get("selectedDesign")
|
||
if not selected:
|
||
if data.get("requestedDesign"):
|
||
raise SystemExit(f"已记录用户选择 {data['requestedDesign']},但总览图尚未生成;请先运行 Tools/generate_battlefield_designs.py --design {data['requestedDesign']}")
|
||
raise SystemExit("请先选择方案:python Tools/battlefield_scene_pipeline.py select A")
|
||
if not design_ready(data, selected):
|
||
raise SystemExit("方案总览图尚未由 gpt-image-2.5 生成并登记,请先运行 Tools/generate_battlefield_designs.py")
|
||
if not components_ready(data):
|
||
raise SystemExit("地表或组件设计图尚未由 gpt-image-2.5 生成并登记,请先运行 Tools/generate_battlefield_components.py")
|
||
errs=validate(data, True)
|
||
if errs: raise SystemExit("\n".join(errs))
|
||
print(f"方案 {selected}:{next(x['name'] for x in data['designOptions'] if x['id']==selected)}")
|
||
print("新组件默认:分件图 -> 混元 50k -> Studio 低模 -> Studio UV -> Blender 烘焙 -> 超目标 130% 时保留 UV 减面")
|
||
for item in [data["ground"], *data["components"]]:
|
||
status = item.get("generationStatus", "pending")
|
||
if status == "reused":
|
||
print(f"{item['type']}: reuse -> {item.get('prefab') or item.get('existingPrefab')}; skip model generation")
|
||
continue
|
||
print(f"{item['type']}: target {item['targetTriangles']} tris; keep existing production receipts when already baked/imported")
|
||
print(f" python Tools/scene_component_pipeline.py --manifest {MANIFEST.relative_to(ROOT).as_posix()} --part {item['type']} --dry-run")
|
||
print("Prefab:", data["assembly"]["outputPrefab"])
|
||
|
||
def main():
|
||
p=argparse.ArgumentParser(); sub=p.add_subparsers(dest="cmd", required=True)
|
||
sub.add_parser("status")
|
||
sel=sub.add_parser("select"); sel.add_argument("design", choices=["A","B","C"])
|
||
sub.add_parser("validate"); sub.add_parser("plan")
|
||
args=p.parse_args(); data=load()
|
||
if args.cmd=="status":
|
||
components = data.get("components", [])
|
||
print(json.dumps({
|
||
"requestedDesign": data.get("requestedDesign"),
|
||
"selectedDesign": data.get("selectedDesign"),
|
||
"overviewGeneration": data.get("designGeneration", {}).get("status"),
|
||
"components": len(components),
|
||
"reusedComponents": [x.get("type") for x in components if x.get("generationStatus") == "reused"],
|
||
"newComponents": [x.get("type") for x in components if x.get("generationStatus") == "generated"],
|
||
"ground": bool(data.get("ground")),
|
||
"componentDesignGeneration": data.get("componentDesignGeneration", {}).get("status"),
|
||
"modelComponents": [x.get("type") for x in components if x.get("modelGenerationStatus") in {"baked", "imported"}],
|
||
"assemblyPrefab": data.get("assembly", {}).get("outputPrefab"),
|
||
},ensure_ascii=False))
|
||
elif args.cmd=="select":
|
||
if not design_ready(data, args.design):
|
||
raise SystemExit(f"方案 {args.design} 的设计图尚未由 gpt-image-2.5 生成,请先运行 Tools/generate_battlefield_designs.py --design {args.design}")
|
||
data["selectedDesign"]=args.design; MANIFEST.write_text(json.dumps(data,ensure_ascii=False,indent=2)+"\n",encoding="utf-8"); print("selected",args.design)
|
||
elif args.cmd=="validate":
|
||
errs=validate(data, False); print("PASS" if not errs else "FAIL\n"+"\n".join(errs)); raise SystemExit(1 if errs else 0)
|
||
else: plan(data)
|
||
if __name__=="__main__": main()
|