0f96b79dba
两阶段流水线:搭建指南Markdown → JSON → UE5 Actor - 解析器:4种表格变体+范围展开+坐标表达式计算 - 配置:37种BSP资产映射+13种材质+7区域定义 - 验证:265个Actor全部通过,0错误 - Builder:UE5 Python API自动放置Actor+材质+Outliner - 报告:自动生成搭建报告Markdown Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
160 lines
5.7 KiB
Python
160 lines
5.7 KiB
Python
"""搭建报告生成器。
|
||
|
||
从解析数据和构建日志生成 Markdown 格式的搭建报告,
|
||
格式与 docs/technical/关卡-v01-搭建报告.md 一致。
|
||
"""
|
||
|
||
import json
|
||
from pathlib import Path
|
||
from datetime import datetime
|
||
from typing import Optional
|
||
|
||
|
||
def generate_report(
|
||
level_data: dict,
|
||
build_log: Optional[list[dict]] = None,
|
||
output_path: str = "",
|
||
) -> str:
|
||
"""生成搭建报告 Markdown。
|
||
|
||
Args:
|
||
level_data: 解析器输出的关卡数据(含 metadata/actors/summary)。
|
||
build_log: 构建日志(含每个 Actor 的成功/失败状态)。可选。
|
||
output_path: 报告输出路径。为空则只返回字符串。
|
||
|
||
Returns:
|
||
Markdown 格式的搭建报告文本。
|
||
"""
|
||
actors = level_data.get("actors", [])
|
||
summary = level_data.get("summary", {})
|
||
metadata = level_data.get("metadata", {})
|
||
|
||
lines = []
|
||
lines.append("# 白模搭建报告 — 自动生成")
|
||
lines.append("")
|
||
lines.append(f"> 生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M')}")
|
||
lines.append(f"> 数据来源: {metadata.get('source', '未知')}")
|
||
lines.append(f"> 场景尺寸: {metadata.get('scene_size', ['?','?'])[0]}cm × {metadata.get('scene_size', ['?','?'])[1]}cm")
|
||
lines.append("")
|
||
|
||
# ---- 搭建概要 ----
|
||
lines.append("## 一、搭建概要")
|
||
lines.append("")
|
||
|
||
total = summary.get("total_actors", len(actors))
|
||
by_category = summary.get("by_category", {})
|
||
|
||
lines.append(f"- **总 Actor 数**: {total}")
|
||
for cat in ["STR", "PROP", "INTR", "COLL", "BND", "LGT", "PLAY"]:
|
||
count = by_category.get(cat, 0)
|
||
if count > 0:
|
||
cat_name = {"STR": "结构体", "PROP": "布景", "INTR": "交互物",
|
||
"COLL": "收集品", "BND": "边界", "LGT": "光源", "PLAY": "PlayerStart"}
|
||
lines.append(f"- {cat_name.get(cat, cat)} ({cat}): {count}")
|
||
|
||
if build_log:
|
||
ok = sum(1 for b in build_log if b.get("status") == "ok")
|
||
failed = sum(1 for b in build_log if b.get("status") != "ok")
|
||
lines.append(f"- 构建成功: {ok}")
|
||
lines.append(f"- 构建失败: {failed}")
|
||
|
||
lines.append("")
|
||
|
||
# ---- 逐区域对照 ----
|
||
lines.append("## 二、逐区域对照")
|
||
lines.append("")
|
||
|
||
by_zone = summary.get("by_zone", {})
|
||
zone_names = {
|
||
"Z1": "家", "Z2": "小卖部", "Z3": "主街道",
|
||
"Z4": "空地", "Z5": "巷子", "Z6": "水塘边", "Z7": "老居民楼",
|
||
"GLOBAL": "全局",
|
||
}
|
||
|
||
for zone_id in ["Z1", "Z2", "Z3", "Z4", "Z5", "Z6", "Z7", "GLOBAL"]:
|
||
zone_actors = [a for a in actors if a.get("zone_id") == zone_id]
|
||
if not zone_actors:
|
||
continue
|
||
|
||
zone_name = zone_names.get(zone_id, zone_id)
|
||
lines.append(f"### {zone_id} {zone_name}")
|
||
lines.append("")
|
||
lines.append(f"共 {len(zone_actors)} 个 Actor")
|
||
lines.append("")
|
||
lines.append("| # | Actor 名称 | 资产 | 位置 | 缩放 | 材质 | 状态 |")
|
||
lines.append("|---|-----------|------|------|------|------|------|")
|
||
|
||
for j, a in enumerate(zone_actors):
|
||
name = a.get("actor_name", "")
|
||
asset = a.get("asset_key", "—")
|
||
loc = a.get("location", [0, 0, 0])
|
||
loc_str = f"({loc[0]:.0f},{loc[1]:.0f},{loc[2]:.0f})"
|
||
scale = a.get("scale", [1, 1, 1])
|
||
if scale == [1, 1, 1] or scale == (1, 1, 1):
|
||
scale_str = "—"
|
||
else:
|
||
scale_str = f"({scale[0]:.2f},{scale[1]:.2f},{scale[2]:.2f})"
|
||
mat = a.get("material", "—") or "—"
|
||
|
||
# 状态
|
||
status = "✅"
|
||
if build_log:
|
||
log_entry = next((b for b in build_log if b.get("name") == name), None)
|
||
if log_entry:
|
||
status = "✅" if log_entry.get("status") == "ok" else f"❌ {log_entry.get('reason', '')}"
|
||
|
||
lines.append(f"| {j+1} | {name} | {asset} | {loc_str} | {scale_str} | {mat} | {status} |")
|
||
|
||
lines.append("")
|
||
|
||
# ---- 资产使用统计 ----
|
||
lines.append("## 三、资产使用统计")
|
||
lines.append("")
|
||
|
||
asset_usage = {}
|
||
for a in actors:
|
||
key = a.get("asset_key", "")
|
||
if key:
|
||
asset_usage[key] = asset_usage.get(key, 0) + 1
|
||
|
||
lines.append("| 资产名 | 使用次数 |")
|
||
lines.append("|--------|---------|")
|
||
for key in sorted(asset_usage.keys(), key=lambda k: -asset_usage[k]):
|
||
lines.append(f"| {key} | {asset_usage[key]} |")
|
||
lines.append("")
|
||
|
||
# ---- 缩放记录 ----
|
||
lines.append("## 四、缩放记录")
|
||
lines.append("")
|
||
lines.append("| Actor 名称 | 资产 | 缩放 | 实际尺寸 (cm) |")
|
||
lines.append("|-----------|------|------|-------------|")
|
||
|
||
for a in actors:
|
||
scale = a.get("scale", [1, 1, 1])
|
||
if scale != [1, 1, 1] and scale != (1, 1, 1):
|
||
name = a.get("actor_name", "")
|
||
asset_key = a.get("asset_key", "")
|
||
# 计算实际尺寸
|
||
from config.asset_mapping import MESH_ASSETS
|
||
default_size = MESH_ASSETS.get(asset_key, {}).get("default_size", (100, 100, 100))
|
||
if default_size:
|
||
actual = (
|
||
f"{default_size[0]*scale[0]:.0f}×"
|
||
f"{default_size[1]*scale[1]:.0f}×"
|
||
f"{default_size[2]*scale[2]:.0f}"
|
||
)
|
||
else:
|
||
actual = "(默认尺寸未知)"
|
||
lines.append(f"| {name} | {asset_key} | ({scale[0]:.2f},{scale[1]:.2f},{scale[2]:.2f}) | {actual} |")
|
||
|
||
lines.append("")
|
||
|
||
report = "\n".join(lines)
|
||
|
||
# 写入文件
|
||
if output_path:
|
||
Path(output_path).write_text(report, encoding="utf-8")
|
||
print(f"报告已写入: {output_path}")
|
||
|
||
return report
|