feat(tools): 新增execution-to-guide执行案转搭建指南工具
- execution_parser.py: 解析关卡执行案Markdown提取区域/结构体/布景/边界/光照 - asset_expander.py: BSP资产建议展开+绝对坐标计算+旋转推断 - guide_writer.py: 生成精确匹配build_guide_parser格式的搭建指南 - composite_writer.py: 组合资产清单+JSON模板生成 - converter.py: 主入口CLI工具 - 更新level-execution-template.md: 移除交互物/收集品章节,新增组合资产标记 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,596 @@
|
||||
"""搭建指南 Markdown 生成器。
|
||||
|
||||
将 ActorSpec 列表转换为搭建指南 Markdown 文档。
|
||||
输出格式精确匹配 build_guide_parser.py 的解析预期。
|
||||
|
||||
搭建指南结构(8章):
|
||||
〇、全局信息(场景参数、路径前缀、命名规范、Outliner 结构)
|
||||
一、BSP 资产映射表
|
||||
二、Phase B 地面与边界
|
||||
三、Phase C 逐区域搭建
|
||||
四、Phase C 全局物件
|
||||
五、Phase D 光照
|
||||
六、Phase E 验证清单
|
||||
"""
|
||||
|
||||
import logging
|
||||
from dataclasses import asdict
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from execution_parser import (
|
||||
ExecutionPlanData, ZoneInfo, StructureItem, PropItem,
|
||||
CompositeItem, GlobalObject, BoundaryItem, LightItem,
|
||||
)
|
||||
from asset_expander import AssetExpander, ActorSpec
|
||||
|
||||
logger = logging.getLogger("execution_to_guide.writer")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 搭建指南生成器
|
||||
# ============================================================
|
||||
|
||||
class GuideWriter:
|
||||
"""搭建指南 Markdown 生成器。"""
|
||||
|
||||
def __init__(self, plan_data: ExecutionPlanData):
|
||||
"""
|
||||
Args:
|
||||
plan_data: 解析后的执行案数据
|
||||
"""
|
||||
self.plan = plan_data
|
||||
self.expander = AssetExpander(plan_data.zones)
|
||||
self.all_actors: list[ActorSpec] = []
|
||||
self.asset_refs: dict[str, dict] = {} # 用到的资产 key → info
|
||||
|
||||
def generate(self) -> str:
|
||||
"""生成完整的搭建指南 Markdown。"""
|
||||
sections = []
|
||||
|
||||
# 〇、全局信息
|
||||
sections.append(self._write_global_info())
|
||||
|
||||
# 一、BSP 资产映射表
|
||||
sections.append(self._write_asset_mapping())
|
||||
|
||||
# 二、Phase B — 地面与边界
|
||||
sections.append(self._write_ground_and_boundary())
|
||||
|
||||
# 三、Phase C — 逐区域搭建
|
||||
sections.append(self._write_zone_builds())
|
||||
|
||||
# 四、Phase C — 全局物件
|
||||
sections.append(self._write_global_objects())
|
||||
|
||||
# 五、Phase D — 光照
|
||||
sections.append(self._write_lighting())
|
||||
|
||||
# 六、Phase E — 验证清单
|
||||
sections.append(self._write_checklist())
|
||||
|
||||
return "\n\n".join(sections)
|
||||
|
||||
def write_to_file(self, filepath: str):
|
||||
"""生成并写入文件。"""
|
||||
content = self.generate()
|
||||
path = Path(filepath)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(content, encoding="utf-8")
|
||||
logger.info(f"搭建指南已写入: {filepath}")
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 〇、全局信息
|
||||
# ----------------------------------------------------------
|
||||
def _write_global_info(self) -> str:
|
||||
"""生成全局信息章节。"""
|
||||
w, d = self.plan.scene_size
|
||||
lines = [
|
||||
f"# v{self.plan.version} 白模搭建操作指南 — {self.plan.scene_name}",
|
||||
"",
|
||||
f"> 本文档由 execution-to-guide 工具自动生成,供 level-builder 搭建白模关卡使用。",
|
||||
f"> 基于: 关卡-v{self.plan.version}-{self.plan.scene_name}-执行案.md",
|
||||
"",
|
||||
"---",
|
||||
"",
|
||||
"## 〇、全局信息",
|
||||
"",
|
||||
"### 场景参数",
|
||||
"",
|
||||
"| 参数 | 值 |",
|
||||
"|------|-----|",
|
||||
f"| 场景总尺寸 | {w}cm × {d}cm({w // 100}m × {d // 100}m) |",
|
||||
f"| 地面高度基准 | Z = {int(self.plan.ground_z)} |",
|
||||
f"| 主角身高 | ~{int(self.plan.character_height)}cm |",
|
||||
f"| 摄像机高度 | ~{int(self.plan.camera_height)}cm |",
|
||||
f"| 时间设定 | {self.plan.time_setting} |",
|
||||
"| 坐标原点 | 场景西南角地平面 |",
|
||||
"",
|
||||
"### UE5 路径前缀",
|
||||
"",
|
||||
"- **Mesh**: `Content/BlockingStarterPack/Meshes/`",
|
||||
"- **Material**: `Content/BlockingStarterPack/Materials/`",
|
||||
"- **Material (Basics)**: `Content/BlockingStarterPack/Materials/Blocking/Basics/`",
|
||||
"",
|
||||
"### 命名规范",
|
||||
"",
|
||||
"```",
|
||||
"LVL_<ZoneID>_<Category>_<Description>",
|
||||
"Category: STR | PROP | COMP | BND | LGT",
|
||||
"```",
|
||||
"",
|
||||
"### Outliner 文件夹结构",
|
||||
"",
|
||||
"```",
|
||||
f"LVL_{self._level_slug()}/",
|
||||
]
|
||||
|
||||
for zone in self.plan.zones:
|
||||
zone_slug = f"{zone.zone_id}_{zone.zone_name}"
|
||||
lines.append(f" {zone_slug}/")
|
||||
lines.append(" Structure/ Props/")
|
||||
|
||||
lines.append(" Global/")
|
||||
lines.append(" Boundaries/ Lighting/ PlayerStart/")
|
||||
lines.append("```")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 一、BSP 资产映射表
|
||||
# ----------------------------------------------------------
|
||||
def _write_asset_mapping(self) -> str:
|
||||
"""收集用到的所有资产,生成映射表。"""
|
||||
# 先展开所有 Actor 以收集资产引用
|
||||
self._collect_all_actors()
|
||||
|
||||
lines = [
|
||||
"---",
|
||||
"",
|
||||
"## 一、BSP 资产映射表",
|
||||
"",
|
||||
]
|
||||
|
||||
# 按类别分组
|
||||
categories = {}
|
||||
for key, info in self.asset_refs.items():
|
||||
cat = info.get("category", "other")
|
||||
if cat not in categories:
|
||||
categories[cat] = []
|
||||
categories[cat].append((key, info))
|
||||
|
||||
# 类别标题映射
|
||||
cat_titles = {
|
||||
"wall": "建筑 — 墙体",
|
||||
"wall_door": "建筑 — 门墙",
|
||||
"wall_window": "建筑 — 窗墙",
|
||||
"floor": "建筑 — 地板",
|
||||
"door": "建筑 — 门",
|
||||
"window": "建筑 — 窗户",
|
||||
"barrier": "建筑 — 围挡",
|
||||
"fence": "建筑 — 围栏",
|
||||
"beam": "建筑 — 横梁",
|
||||
"building": "建筑 — 大型建筑",
|
||||
"furniture": "家具",
|
||||
"industrial": "工业/装饰",
|
||||
"vehicle": "载具",
|
||||
"primitive": "基础体",
|
||||
"vegetation": "植被",
|
||||
"light_fixture": "灯具",
|
||||
"decoration": "装饰",
|
||||
"other": "其他",
|
||||
}
|
||||
|
||||
for cat_key in ["wall", "wall_door", "wall_window", "floor", "door", "window",
|
||||
"barrier", "fence", "beam", "building", "furniture",
|
||||
"industrial", "vehicle", "vegetation", "light_fixture",
|
||||
"decoration", "other"]:
|
||||
if cat_key not in categories:
|
||||
continue
|
||||
items = categories[cat_key]
|
||||
title = cat_titles.get(cat_key, cat_key)
|
||||
|
||||
lines.append(f"### {title}")
|
||||
lines.append("")
|
||||
lines.append("| 设计文档引用 | UE5 资产路径 | 实际尺寸 (cm) |")
|
||||
lines.append("|-------------|-------------|--------------|")
|
||||
|
||||
for key, info in items:
|
||||
path_short = info.get("path", "")
|
||||
# 缩短路径显示
|
||||
if path_short.startswith("/Game/BlockingStarterPack/Meshes/"):
|
||||
path_short = path_short.replace("/Game/BlockingStarterPack/Meshes/", "")
|
||||
path_short = path_short.split(".")[0] # 去掉 .SM_xxx 后缀
|
||||
|
||||
bounds = info.get("actual_bounds", ((0, 0, 0), (0, 0, 0)))
|
||||
size_str = self._format_bounds_size(bounds)
|
||||
|
||||
lines.append(f"| {key} | `{path_short}` | {size_str} |")
|
||||
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 二、Phase B — 地面与边界
|
||||
# ----------------------------------------------------------
|
||||
def _write_ground_and_boundary(self) -> str:
|
||||
"""生成地面铺设和边界围挡。"""
|
||||
lines = [
|
||||
"---",
|
||||
"",
|
||||
"## 二、Phase B — 地面与边界",
|
||||
"",
|
||||
"### B1. 铺设地面",
|
||||
"",
|
||||
]
|
||||
|
||||
# 全局地面
|
||||
w, d = self.plan.scene_size
|
||||
lines.append("#### 全局地面(MI_DevGray)")
|
||||
lines.append("")
|
||||
lines.append("| # | Actor 名称 | 资产 | 位置 (X,Y,Z) | 缩放 | 材质 |")
|
||||
lines.append("|---|-----------|------|-------------|------|------|")
|
||||
|
||||
# 用 Floor_1000x1000 铺满
|
||||
tiles_x = max(1, w // 1000)
|
||||
tiles_y = max(1, d // 1000)
|
||||
seq = 1
|
||||
for tx in range(tiles_x):
|
||||
for ty in range(tiles_y):
|
||||
cx = tx * 1000 + 500
|
||||
cy = ty * 1000 + 500
|
||||
lines.append(
|
||||
f"| {seq} | LVL_Global_STR_GroundFloor_{seq:02d} | Floor_1000x1000 | "
|
||||
f"({cx},{cy},{int(self.plan.ground_z)}) | — | MI_DevGray |"
|
||||
)
|
||||
seq += 1
|
||||
|
||||
lines.append("")
|
||||
|
||||
# 边界围挡
|
||||
lines.append("### B2. 放置边界围挡")
|
||||
lines.append("")
|
||||
|
||||
for boundary in self.plan.boundaries:
|
||||
seg = boundary.segment
|
||||
lines.append(f"#### {seg}")
|
||||
lines.append("")
|
||||
lines.append("| # | Actor 名称 | 资产 | 位置 (X,Y,Z) | 旋转 (P,Y,R) | 缩放 | 材质 |")
|
||||
lines.append("|---|-----------|------|-------------|-------------|------|------|")
|
||||
|
||||
# 根据边界方向生成围挡
|
||||
bnd_actors = self._generate_boundary_actors(boundary)
|
||||
for i, actor in enumerate(bnd_actors):
|
||||
rot_str = self._fmt_rotation(actor.rotation)
|
||||
scale_str = self._fmt_scale(actor.scale)
|
||||
lines.append(
|
||||
f"| {i + 1} | {actor.actor_name} | {actor.asset_key} | "
|
||||
f"{self._fmt_location(actor.location)} | {rot_str} | {scale_str} | "
|
||||
f"{actor.material} |"
|
||||
)
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def _generate_boundary_actors(self, boundary: BoundaryItem) -> list[ActorSpec]:
|
||||
"""根据边界描述生成围挡 Actor。"""
|
||||
w, d = self.plan.scene_size
|
||||
actors = []
|
||||
|
||||
# 确定边界的起止坐标和方向
|
||||
if "西" in boundary.segment:
|
||||
# X=0, 沿 Y 轴
|
||||
x, y_start, y_end = 0, 0, d
|
||||
yaw = 90.0
|
||||
elif "东" in boundary.segment:
|
||||
x, y_start, y_end = w, 0, d
|
||||
yaw = 90.0
|
||||
elif "南" in boundary.segment:
|
||||
x_start, x_end, y = 0, w, 0
|
||||
yaw = 0.0
|
||||
elif "北" in boundary.segment:
|
||||
x_start, x_end, y = 0, w, d
|
||||
yaw = 0.0
|
||||
else:
|
||||
return []
|
||||
|
||||
# 使用 BarrierWall_600 或 Wall_400x300
|
||||
asset_key = "BarrierWall_600"
|
||||
wall_len = 600 # cm
|
||||
|
||||
seq = 1
|
||||
if "西" in boundary.segment or "东" in boundary.segment:
|
||||
length = y_end - y_start
|
||||
count = max(1, int(length / wall_len))
|
||||
for i in range(count):
|
||||
y_pos = y_start + i * wall_len + wall_len // 2
|
||||
z_pos = 105 # 半高
|
||||
actors.append(ActorSpec(
|
||||
actor_name=f"LVL_Global_BND_{boundary.segment}_{seq:02d}",
|
||||
asset_key=asset_key,
|
||||
asset_path="",
|
||||
location=(x, y_pos, z_pos),
|
||||
rotation=(0.0, yaw, 0.0),
|
||||
scale=(1.0, 1.0, 1.0),
|
||||
material=boundary.tag_color or "MI_DevBrown",
|
||||
material_path="",
|
||||
category="BND",
|
||||
zone_id="GLOBAL",
|
||||
folder="LVL_Level/Global/Boundaries",
|
||||
))
|
||||
seq += 1
|
||||
else:
|
||||
length = x_end - x_start
|
||||
count = max(1, int(length / wall_len))
|
||||
for i in range(count):
|
||||
x_pos = x_start + i * wall_len + wall_len // 2
|
||||
y_pos = y
|
||||
z_pos = 105
|
||||
actors.append(ActorSpec(
|
||||
actor_name=f"LVL_Global_BND_{boundary.segment}_{seq:02d}",
|
||||
asset_key=asset_key,
|
||||
asset_path="",
|
||||
location=(x_pos, y_pos, z_pos),
|
||||
rotation=(0.0, yaw, 0.0),
|
||||
scale=(1.0, 1.0, 1.0),
|
||||
material=boundary.tag_color or "MI_DevBrown",
|
||||
material_path="",
|
||||
category="BND",
|
||||
zone_id="GLOBAL",
|
||||
folder="LVL_Level/Global/Boundaries",
|
||||
))
|
||||
seq += 1
|
||||
|
||||
return actors
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 三、Phase C — 逐区域搭建
|
||||
# ----------------------------------------------------------
|
||||
def _write_zone_builds(self) -> str:
|
||||
"""生成逐区域搭建章节。"""
|
||||
lines = [
|
||||
"---",
|
||||
"",
|
||||
"## 三、Phase C — 逐区域搭建",
|
||||
"",
|
||||
]
|
||||
|
||||
for idx, zone in enumerate(self.plan.zones, 1):
|
||||
lines.append(f"### C{idx}. {zone.zone_id} — {zone.zone_name}")
|
||||
lines.append("")
|
||||
|
||||
# 结构体
|
||||
structures = self.plan.zone_structures.get(zone.zone_id, [])
|
||||
if structures:
|
||||
lines.append(f"#### C{idx}.1 结构体(Structure)")
|
||||
lines.append("")
|
||||
lines.append("| # | Actor 名称 | 资产 | 位置 (X,Y,Z) | 旋转 (P,Y,R) | 缩放 | 材质 |")
|
||||
lines.append("|---|-----------|------|-------------|-------------|------|------|")
|
||||
|
||||
for item in structures:
|
||||
actors = self.expander.expand_structure(item, zone.zone_id)
|
||||
for actor in actors:
|
||||
rot_str = self._fmt_rotation(actor.rotation)
|
||||
scale_str = self._fmt_scale(actor.scale)
|
||||
lines.append(
|
||||
f"| | {actor.actor_name} | {actor.asset_key} | "
|
||||
f"{self._fmt_location(actor.location)} | {rot_str} | {scale_str} | "
|
||||
f"{actor.material} |"
|
||||
)
|
||||
self._track_asset(actor)
|
||||
lines.append("")
|
||||
|
||||
# 布景
|
||||
props = self.plan.zone_props.get(zone.zone_id, [])
|
||||
if props:
|
||||
lines.append(f"#### C{idx}.2 布景物(Props)")
|
||||
lines.append("")
|
||||
lines.append("| # | Actor 名称 | 资产 | 位置 (X,Y,Z) | 缩放 | 材质 | 说明 |")
|
||||
lines.append("|---|-----------|------|-------------|------|------|------|")
|
||||
|
||||
for item in props:
|
||||
actors = self.expander.expand_prop(item, zone.zone_id)
|
||||
for actor in actors:
|
||||
scale_str = self._fmt_scale(actor.scale)
|
||||
lines.append(
|
||||
f"| | {actor.actor_name} | {actor.asset_key} | "
|
||||
f"{self._fmt_location(actor.location)} | {scale_str} | "
|
||||
f"{actor.material} | {actor.note} |"
|
||||
)
|
||||
self._track_asset(actor)
|
||||
lines.append("")
|
||||
|
||||
# 组合资产(如有)
|
||||
composites = self.plan.zone_composites.get(zone.zone_id, [])
|
||||
if composites:
|
||||
lines.append(f"#### C{idx}.3 组合资产(Composite)")
|
||||
lines.append("")
|
||||
lines.append("| # | Actor 名称 | Primitives | 位置 (X,Y,Z) | 缩放 | 材质 | 说明 |")
|
||||
lines.append("|---|-----------|-----------|-------------|------|------|------|")
|
||||
for comp in composites:
|
||||
lines.append(
|
||||
f"| | ⚠️ {comp.name} | {comp.build_plan} | "
|
||||
f"(待展开) | — | — | {comp.note} |"
|
||||
)
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 四、全局物件
|
||||
# ----------------------------------------------------------
|
||||
def _write_global_objects(self) -> str:
|
||||
"""生成全局物件章节。"""
|
||||
lines = [
|
||||
"---",
|
||||
"",
|
||||
"## 四、Phase C — 全局物件",
|
||||
"",
|
||||
]
|
||||
|
||||
if not self.plan.global_objects:
|
||||
lines.append("> 无全局物件。")
|
||||
return "\n".join(lines)
|
||||
|
||||
lines.append("| # | Actor 名称 | 资产 | 位置 (X,Y,Z) | 缩放 | 材质 | 说明 |")
|
||||
lines.append("|---|-----------|------|-------------|------|------|------|")
|
||||
|
||||
for item in self.plan.global_objects:
|
||||
actors = self.expander.expand_global(item)
|
||||
for actor in actors:
|
||||
scale_str = self._fmt_scale(actor.scale)
|
||||
lines.append(
|
||||
f"| | {actor.actor_name} | {actor.asset_key} | "
|
||||
f"{self._fmt_location(actor.location)} | {scale_str} | "
|
||||
f"{actor.material} | {actor.note} |"
|
||||
)
|
||||
self._track_asset(actor)
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 五、光照
|
||||
# ----------------------------------------------------------
|
||||
def _write_lighting(self) -> str:
|
||||
"""生成光照章节。"""
|
||||
lines = [
|
||||
"---",
|
||||
"",
|
||||
"## 五、Phase D — 光照",
|
||||
"",
|
||||
]
|
||||
|
||||
for light in self.plan.lights:
|
||||
if "定向" in light.light_type or "Directional" in light.light_type:
|
||||
lines.append("### D1. 定向光(Directional Light)")
|
||||
lines.append("")
|
||||
lines.append("| 类型 | 位置/方向 | 参数 |")
|
||||
lines.append("|------|---------|------|")
|
||||
lines.append(f"| Directional Light | {light.position} | {light.params} |")
|
||||
lines.append(f"> {light.note}")
|
||||
lines.append("")
|
||||
|
||||
elif "天空" in light.light_type or "Sky" in light.light_type:
|
||||
lines.append("### D2. 天空光(Sky Light)")
|
||||
lines.append("")
|
||||
lines.append("| 类型 | 位置/方向 | 参数 |")
|
||||
lines.append("|------|---------|------|")
|
||||
lines.append(f"| Sky Light | 全局 | {light.params} |")
|
||||
lines.append(f"> {light.note}")
|
||||
lines.append("")
|
||||
|
||||
elif "路灯" in light.light_type or "点光" in light.light_type:
|
||||
lines.append("### D3. 路灯点光源")
|
||||
lines.append("")
|
||||
lines.append(f"> {light.note},数量: {light.count}")
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 六、验证清单
|
||||
# ----------------------------------------------------------
|
||||
def _write_checklist(self) -> str:
|
||||
"""生成验证清单章节。"""
|
||||
total = len(self.all_actors)
|
||||
zones = len(self.plan.zones)
|
||||
|
||||
lines = [
|
||||
"---",
|
||||
"",
|
||||
"## 六、Phase E — 验证清单",
|
||||
"",
|
||||
"| # | 检查项 | 状态 |",
|
||||
"|---|--------|------|",
|
||||
f"| 1 | 场景尺寸 {self.plan.scene_size[0]}×{self.plan.scene_size[1]}cm 正确 | ⬜ |",
|
||||
f"| 2 | {zones} 个区域均已搭建 | ⬜ |",
|
||||
"| 3 | 所有边界围挡无缺口 | ⬜ |",
|
||||
"| 4 | 玩家起点可到达 | ⬜ |",
|
||||
"| 5 | 所有区域可通行 | ⬜ |",
|
||||
"| 6 | 建筑入口与流线对齐 | ⬜ |",
|
||||
"| 7 | 光照方向与场景一致 | ⬜ |",
|
||||
f"| 8 | Actor 总数: {total} | ⬜ |",
|
||||
"",
|
||||
"---",
|
||||
"",
|
||||
f"> 搭建指南生成完成。共 {zones} 个区域,{total} 个 Actor。",
|
||||
]
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 辅助方法
|
||||
# ----------------------------------------------------------
|
||||
def _collect_all_actors(self):
|
||||
"""收集所有区域的 Actor(用于资产映射表)。"""
|
||||
if self.all_actors:
|
||||
return # 已收集
|
||||
|
||||
# 结构体
|
||||
for zone_id, items in self.plan.zone_structures.items():
|
||||
for item in items:
|
||||
actors = self.expander.expand_structure(item, zone_id)
|
||||
self.all_actors.extend(actors)
|
||||
for a in actors:
|
||||
self._track_asset(a)
|
||||
|
||||
# 布景
|
||||
for zone_id, items in self.plan.zone_props.items():
|
||||
for item in items:
|
||||
actors = self.expander.expand_prop(item, zone_id)
|
||||
self.all_actors.extend(actors)
|
||||
for a in actors:
|
||||
self._track_asset(a)
|
||||
|
||||
# 全局物件
|
||||
for item in self.plan.global_objects:
|
||||
actors = self.expander.expand_global(item)
|
||||
self.all_actors.extend(actors)
|
||||
for a in actors:
|
||||
self._track_asset(a)
|
||||
|
||||
def _track_asset(self, actor: ActorSpec):
|
||||
"""跟踪用到的资产。"""
|
||||
if actor.asset_key and actor.asset_key not in self.asset_refs:
|
||||
try:
|
||||
from config.asset_mapping import MESH_ASSETS
|
||||
info = MESH_ASSETS.get(actor.asset_key, {})
|
||||
except ImportError:
|
||||
info = {}
|
||||
self.asset_refs[actor.asset_key] = info
|
||||
|
||||
def _level_slug(self) -> str:
|
||||
"""生成关卡标识符。"""
|
||||
name = self.plan.scene_name.replace(" ", "")
|
||||
return name
|
||||
|
||||
@staticmethod
|
||||
def _fmt_location(loc: tuple) -> str:
|
||||
"""格式化位置。"""
|
||||
return f"({int(loc[0])},{int(loc[1])},{int(loc[2])})"
|
||||
|
||||
@staticmethod
|
||||
def _fmt_rotation(rot: tuple) -> str:
|
||||
"""格式化旋转。"""
|
||||
if rot == (0, 0, 0):
|
||||
return "—"
|
||||
return f"({int(rot[0])},{int(rot[1])},{int(rot[2])})"
|
||||
|
||||
@staticmethod
|
||||
def _fmt_scale(scale: tuple) -> str:
|
||||
"""格式化缩放。"""
|
||||
if scale == (1, 1, 1):
|
||||
return "—"
|
||||
return f"({scale[0]:.2f},{scale[1]:.2f},{scale[2]:.2f})"
|
||||
|
||||
@staticmethod
|
||||
def _format_bounds_size(bounds) -> str:
|
||||
"""格式化包围盒尺寸。"""
|
||||
if not bounds or len(bounds) < 2:
|
||||
return "—"
|
||||
mn, mx = bounds[0], bounds[1]
|
||||
w = mx[0] - mn[0] if len(mx) > 0 else 0
|
||||
d = mx[1] - mn[1] if len(mx) > 1 else 0
|
||||
h = mx[2] - mn[2] if len(mx) > 2 else 0
|
||||
return f"{int(w)}×{int(d)}×{int(h)}"
|
||||
Reference in New Issue
Block a user