from __future__ import annotations from pathlib import Path import sys PROJECT_ROOT = Path(__file__).resolve().parent.parent SCRIPT_DIR = PROJECT_ROOT / "scripts" README_PATH = PROJECT_ROOT / "README.md" if str(SCRIPT_DIR) not in sys.path: sys.path.insert(0, str(SCRIPT_DIR)) from verify_first_level_edit_suites import QUICK_COMMANDS, STAGES # noqa: E402 def _assert(condition: bool, message: str) -> None: if not condition: raise AssertionError(message) def _command_path(script_name: str) -> Path: script_path = SCRIPT_DIR / script_name if script_path.exists(): return script_path return PROJECT_ROOT / script_name def _commands() -> tuple[tuple[str, str, tuple[str, ...]], ...]: collected: list[tuple[str, str, tuple[str, ...]]] = [] for stage_name, _stage_label, cases in STAGES: for _label, command in cases: collected.append((stage_name, command[0], command[1:])) for _label, command in QUICK_COMMANDS: collected.append(("quick", command[0], command[1:])) return tuple(collected) def _verify_script_inventory() -> None: for stage_name, script_name, _args in _commands(): path = _command_path(script_name) _assert(path.exists(), f"{stage_name} command target is missing: {script_name}") def _verify_readme_mentions(readme: str) -> None: required_fragments = ( "当前整体验证基线", "不等于 CAD 级完成", "Face 阶段的当前验收口径", "SCDM-first 主路线", "核心路线只保留下面这一棵树", "SCDM 内部如何处理相邻面、圆角链、二级/三级拓扑传播,交给 SCDM", "本软件不再把手写一级、二级、三级传播当成新增能力主线", "`[x]` 已适配", "`[~]` 部分适配", "`[ ]` 待适配", "[scdm_probe_job.json -> /RunScript 扫描 STEP]", "[scdm_feature_cache.json -> 映射为本软件能力字典和中文参数]", "[参数化建模 -> 多个目标值统一提交,不再每行一个操作按钮]", "[公式输入 -> 支持 Face85.直径 = Face87.半径", "[批量联动 -> 多条公式先求值成一组目标参数", "[关系式管理 -> 公式启停、删除回滚、基础单位字面量 mm/cm/m、JSON 导入/导出已接", "[Face 偏移 -> face.offset / OffsetFaces]", "[槽宽 -> slot.width / OffsetFaces", "[槽深 -> slot.depth / Move 或 OffsetFaces", "[本地 OCCT -> 只保留已验证兜底能力,不再作为新主线扩展]", "Face 阶段的当前验收口径(本地 OCCT 兜底基线,R1 已收口)", "已验收:平面 Face 的 `偏移`,稳定矩形/简单全平面 Face 的 `面内长度`、`面内宽度`", "未实现/不承诺:原 CAD 历史恢复、任意复杂 Face 的通用局部重建", "孔/槽阶段的当前验收口径(本地 OCCT 兜底基线,R2/R3 已收口)", "已验收:圆柱孔/盲孔的 `直径`、`半径`、`轴心`、`盲孔深度`", "已验收:槽/半孔/长圆槽的 `槽宽`、`槽深`、`圆弧长度`", "未实现/不承诺:孔组、阵列孔、同尺寸孔联动、多槽组联动", "2026-08-11,在 `pyocc` 环境下已通过 `python scripts\\verify_first_level_edit_suites.py --stage hole-slot`", "覆盖 R2/R3 孔槽专项套件、隔离执行、逻辑 Face ID 保持和孔槽阶段收口口径", "本地 OCCT 兜底能力的当前基线", "verify_first_level_edit_suites.py --quick", "verify_first_level_edit_suites.py --stage face", "verify_first_level_edit_suites.py --stage hole-slot", "verify_first_level_edit_suites.py --stage edge", "verify_first_level_edit_suites.py --stage boss --stage round-chamfer --stage shell --stage analytic", "一级编辑总验证入口", "真实 STEP 失败项", ) for fragment in required_fragments: _assert(fragment in readme, f"README missing first-level acceptance fragment: {fragment}") for stage_name, _stage_label, _cases in STAGES: _assert( f"--stage {stage_name}" in readme or stage_name in {"round-chamfer", "shell", "analytic"}, f"README should mention how to run stage {stage_name}", ) for stage_name, script_name, _args in _commands(): if script_name == "main.py": continue _assert(script_name in readme, f"README should mention {stage_name} command script {script_name}") def _verify_roadmap_scope(readme: str) -> None: start_marker = "SCDM-first 主路线" end_marker = "└── 8. 交付与兜底边界" start = readme.find(start_marker) end = readme.find(end_marker) _assert(start >= 0 and end > start, "README should contain a single SCDM-first roadmap before fallback boundary") active_scope = readme[start:end] deferred_scope = readme[end:] required_active_fragments = ( "├── 0. 后端发现与可用性", "│ ├── [x] [自动发现 SpaceClaim.exe -> 缓存路径、来源、版本和验证结果]", "├── 1. SCDM 识别与缓存", "│ ├── [x] [scdm_probe_job.json -> /RunScript 扫描 STEP]", "│ ├── [x] [scdm_feature_cache.json -> 映射为本软件能力字典和中文参数]", "├── 2. 能力字典、参数表和用户入口", "├── 3. 关系式与参数联动", "[公式输入 -> 支持 Face85.直径 = Face87.半径 这类对象.参数表达式和补全]", "[添加守门 -> 阻止自引用、重复目标、循环依赖和当前无可执行参数的公式]", "[批量联动 -> 多条公式先求值成一组目标参数,再合并为一个参数化建模任务]", "├── 4. SCDM 参数化建模执行", "├── 5. 结果校验、回滚和 ID 续接", "├── 6. 当前已开放或正在开放的 SCDM 能力", "[阵列间距 -> pattern.spacing / Move,整体阵列保持中心不变并等距重排;安全范围由支撑面动态计算,不针对 Face92 写死]", "[局部间距 -> pattern.segment_spacing / Move,按“FaceA-FaceB 间距”或“零件A-零件B 间距”修改相邻段;已支持固定前项移动后侧、固定后项移动前侧、两侧均分保持中心、只移动前项、只移动后项]", "[阵列实例位置 -> pattern.instance_position / Move,只移动当前阵列成员;不自动保持整体阵列等距,真实 STEP 回测待补]", "[壳体厚度 -> shell.thickness / Move,薄壁两平面配对后固定一侧、移动另一侧;真实 STEP 回测待补]", "├── 7. 下一批只按 SCDM 能力适配", "[SCDM 之外的新能力 -> 等 SCDM 能力适配完再评估]", ) for fragment in required_active_fragments: _assert(fragment in active_scope, f"README active roadmap missing: {fragment}") forbidden_active_fragments = ( "SCDM-first 统一实施路线", "SCDM-first Capability 适配路线图", "SCDM-first Capability 适配路线", "├── 6. Edge 一级编辑,补齐底层直接改边能力", "└── 8. 二级 / 三级关系", "[Face 二级传播", "多槽组 ->", ) for fragment in forbidden_active_fragments: _assert(fragment not in active_scope, f"README SCDM-first roadmap should not keep old route scope: {fragment}") _assert("[Analysis Situs -> 只做辅助定位、兜底识别和开源对照]" in deferred_scope, "README should keep Analysis Situs as auxiliary boundary") _assert("[本地 OCCT -> 只保留已验证兜底能力,不再作为新主线扩展]" in deferred_scope, "README should keep OCCT as fallback boundary") _assert("[SCDM 之外的新能力 -> 等 SCDM 能力适配完再评估]" in deferred_scope, "README should defer non-SCDM expansion") forbidden_global_fragments = ( "SCDM-first 统一实施路线", "SCDM-first Capability 适配路线图", "SCDM-first Capability 适配路线", ) for fragment in forbidden_global_fragments: _assert(fragment not in readme, f"README should keep only one SCDM-first route, found old title: {fragment}") def main() -> int: readme = README_PATH.read_text(encoding="utf-8") _verify_script_inventory() _verify_readme_mentions(readme) _verify_roadmap_scope(readme) print("first-level acceptance docs ok") return 0 if __name__ == "__main__": raise SystemExit(main())