diff --git a/backend/engine/cdsl_engine/runtime.py b/backend/engine/cdsl_engine/runtime.py index 37097331..889ef830 100644 --- a/backend/engine/cdsl_engine/runtime.py +++ b/backend/engine/cdsl_engine/runtime.py @@ -619,12 +619,21 @@ def _execute_chamfer(node: FeaturePlanNode, session: ExecutionSession) -> Featur distance = float(node.params.get("distance_mm") or 0) if distance <= 0: raise ValueError("chamfer distance_mm must be > 0") - # 3. 解析目标边(支持相切传播),执行倒角;distance_2_mm 提供时产生非对称倒角。 + # 3. 解析第二距离与角度(importer 对 SolidWorks Distance-Angle 倒角产出 + # angle_rad,单位为弧度)。第二距离 = 主距离 * tan(angle);angle=45° 时 + # tan=1,退化为等距倒角(与历史行为一致,零回归)。 + # 注意:build123d 的 length/length2 侧向分配依赖面的枚举顺序,对非 45° + # 倒角仅保证量级正确,距离所在侧可能反转。 + distance_2 = node.params.get("distance_2_mm") + angle_rad = node.params.get("angle_rad") + if distance_2 is None and angle_rad is not None: + distance_2 = distance * math.tan(float(angle_rad)) + # 4. 解析目标边(支持相切传播),执行倒角。 body = session.adapter.chamfer( - session.body, distance, node.params.get("distance_2_mm"), + session.body, distance, distance_2, _selector_edges(node, session, tangent_propagation=bool(node.params.get("tangent_propagation"))), ) - # 4. 登记新主体并返回结果。 + # 5. 登记新主体并返回结果。 session.register_body(node.feature_id, body, replay_node=node) return session.result(node) diff --git a/backend/tests/test_engine_runtime_foundation.py b/backend/tests/test_engine_runtime_foundation.py index d061e342..3d77ccee 100644 --- a/backend/tests/test_engine_runtime_foundation.py +++ b/backend/tests/test_engine_runtime_foundation.py @@ -658,6 +658,40 @@ class EngineRuntimeFoundationTests(unittest.TestCase): self.assertLess(chamfer_result["volume_mm3"], baseline["volume_mm3"]) self.assertAlmostEqual(pattern_result["volume_mm3"], 1000 - 3 * 10 * 3.141592653589793, places=5) + def test_chamfer_consumes_angle_rad_instead_of_silent_45_degree_fallback(self) -> None: + from cdsl_engine.runtime import rebuild_cdsl + + import math + + base = self._base_block() + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + baseline = rebuild_cdsl(base, root / "baseline.step") + edge = next(item for item in baseline["topology_records"] if item["kind"] == "edge") + selector = { + "kind": "edge", "stable_id": "edge", "source": "solidworks", "confidence": 1, + "owner_feature_id": "base_add", "geometry": edge["geometry"], + } + + equal = deepcopy(base) + equal["features"].append({ + "id": "chamfer_45", "atomic_id": "chamfer", "depends_on": ["base_add"], + "params": {"distance_mm": 1, "angle_rad": math.pi / 4}, "selectors": [deepcopy(selector)], + }) + equal_result = rebuild_cdsl(equal, root / "chamfer-45.step") + # 45° Distance-Angle 等价于等距倒角:tan(45°)=1,切掉 0.5*1*1*10=5 mm³。 + self.assertAlmostEqual(equal_result["volume_mm3"], 1000 - 5, places=5) + + slanted = deepcopy(base) + slanted["features"].append({ + "id": "chamfer_30", "atomic_id": "chamfer", "depends_on": ["base_add"], + "params": {"distance_mm": 1, "angle_rad": math.pi / 6}, "selectors": [deepcopy(selector)], + }) + slanted_result = rebuild_cdsl(slanted, root / "chamfer-30.step") + # 30°:第二距离 = 1*tan(30°)≈0.577,切掉 0.5*1*0.577*10≈2.887 mm³, + # 体积明显大于 45° 等距倒角(995),验证 angle_rad 被消费而非静默 45°。 + self.assertAlmostEqual(slanted_result["volume_mm3"], 1000 - 0.5 * math.tan(math.pi / 6) * 10, places=5) + def test_linear_pattern_replays_hole_with_explicit_host_frame(self) -> None: from cdsl_engine.runtime import rebuild_cdsl diff --git a/docs/engine_diagnostics_test_plan.md b/docs/engine_diagnostics_test_plan.md new file mode 100644 index 00000000..42cd0a38 --- /dev/null +++ b/docs/engine_diagnostics_test_plan.md @@ -0,0 +1,124 @@ +# engine 诊断 → 测试方法 → 修改前后对比 + +## 1. 截图核对结论 + +`backend\engine` 等价于 `backend/engine/cdsl_engine/`。对截图 10 行逐一比对代码(行号均能命中),结论是 **全部成立**。证据索引如下: + +| # | 截图描述 | 代码位置(已核) | +|---|---|---| +| 1 | PlaneSpec 静默丢弃 y_dir,只用 origin/x_dir/normal 重建 | `runtime_types.py:173-180` `PlaneSpec.from_mapping`;`sketch_solver.py:43-52` `_to_3d` | +| 2 | draft 被 schema 接受但 runtime 未执行 | `cdsl_schema.json:94-99` extrudeParams.draft;`runtime.py:338-342` `_shape_from_primary` | +| 3 | revolve.reverse 未进入旋转方向计算 | `runtime.py:344-349` `_shape_from_primary` 分支;`cdsl_schema.json:100-105` | +| 4 | Hole host-face schema/runtime 合同自相矛盾 | `runtime.py:469-483` 支持 frame + selector 两条路径;`cdsl_schema.json:229-243` 仅 selectorRef | +| 5 | 高级终止条件要求整张 profile 同一距离 | `runtime.py:157-211` `_targeted_extent_vector` 拒绝 non_uniform_extent_target | +| 6 | Pattern 严禁 source selector/host selector/extent selector | `capabilities.py:68-91` `pattern_transform_blocker` | +| 7 | ExecutionSession 单 active body | `runtime.py:81-91`(#7 已完成:多体 body_id + 前缀匹配) | +| 8 | Selector 持久性依赖几何等价匹配 | `runtime_types.py` `_unique_equivalent_predecessor` / `_geometry_equivalent`(#8 已完成:演化后继映射 `_evolved_equivalent` / `_successors`,stable_id 跨 body 解析) | +| 9 | HoleSpec 仅支持简单圆柱、沉头、沉孔 | `runtime_types.py:191-250` `HoleSpec` 字段表;`capabilities.py:305-308` thread 拒绝 | +| 10 | Profile 支持链断裂(circles/annulus 未接入 CDSL-only) | `profile_schema.json:8` 仅 3 类;`cdsl_importer/solidworks_to_cdsl.py:113-142` 历史 importer 分类器 | + +## 2. 测试方法(四层 + phase 分桶) + +### 2.1 单元层 +新增 `backend/tests/test_engine_diagnostics_baseline.py`,对应 10 条问题写 14 个 testcase(含正反两面): +- 问题 1:正交 y_dir 保留 / 偏斜 y_dir 显式警告或正交化 +- 问题 2:draft 实体拔模 / 或 schema 拒绝 +- 问题 3:revolve.reverse 重心落在正确半侧 +- 问题 4:hole_wizard.host_face.frame 通过 schema +- 问题 5:up_to_surface 非均匀 profile 被裁剪而非拒绝 +- 问题 6:pattern_linear 重放带 host_face.frame 的孔;pattern_linear 重放 up_to_surface 拉伸 +- 问题 7:multi-body fixture 出现 ≥ 2 个独立 body_id +- 问题 8:fillet 后原 edge selector 仍可 resolve(实现于 `backend/tests/test_engine_selector_persistence.py`,含被波及边可解析、被完整消费边保持保守、未受影响边不受干扰三个契约) +- 问题 9:thread hole executable 或 schema 显式拒绝 +- 问题 10:importer 将 circles 降为 analytic_contours;schema 拒绝 legacy profile + +### 2.2 数据层 +新增 `backend/tests/test_engine_diagnostics_corpus.py`,扫描 `json_to_cdsl/output/*.cdsl.json`(共 5763 份),统计: +- `workplane.x_dir · workplane.y_dir` 与 ‖x‖·‖y‖·cosθ 的偏差分布 +- `profile.type ∈ {circles, annulus}` 计数(已部分修复,应单调下降) +- `hole_wizard` 含 `thread` 的特征数 +- `params.draft` 出现次数 +- `params.reverse=true` 的 revolve_* 出现次数 + +### 2.3 批量层(已有,零成本复用) +```bash +PYTHONPATH=backend/engine python -m cdsl_engine.batch_rebuild \ + json_to_cdsl/output /tmp/batch-before --build --build-timeout 15 +``` +产物: +- `manifest.json`:part_count、runtime_eligible_count、built_count、geometry_verified_count、failure_category_counts +- `summary-by-atomic.json`:atomic 频次 +- `summary-by-blocker.json`:blocker code 频次 +- `parts/.report.json`:每个 part 完整报告(selector_resolution、numeric_comparison) + +修改前/后各跑一次,对比以上聚合 JSON。 + +### 2.4 几何层 +`GJH/scriptTest.py` 已能批量走 CDSL-only 路径。参照 `GJH/exp_compare_084242.py` 思路对单零件做体积/bbox/face 数/凸包方向对比。 + +### 2.5 phase 分桶 +```bash +python -m cdsl_engine.batch_rebuild json_to_cdsl/output /tmp/p3 --phase p3 --build +python -m cdsl_engine.batch_rebuild json_to_cdsl/output /tmp/p4 --phase p4 --build +python -m cdsl_engine.batch_rebuild json_to_cdsl/output /tmp/p6 --phase p6 --build +``` +- P3:基准(拉伸/旋转/参考) +- P4:+ hole_wizard(→ 问题 9) +- P6:+ pattern(→ 问题 6) + +## 3. 修改前后差异指标 + +| # | blocker/信号 | A 当前 | B 修复后 | 度量 | +|---|---|---|---|---| +| 1 | x_dir·y_dir 与 ‖x‖·‖y‖cosθ 偏差 | 12,033/14,908 偏差 > 1e-4 | ≤ 1e-6 或显式 warnings | 数据扫描 | +| 2 | draft 静默成功 | geometry_verified 偏低 | runtime_eligible_count ↑ 或 schema 拒绝 | 批量层 | +| 3 | revolve centroid 侧 | centroid 在错半侧 | 正确侧 | 单元 | +| 4 | schema 拒绝 host_face.frame | schema violation | 通过 | 单元 + 数据 | +| 5 | non_uniform_extent_target 频次 | 高 | 显著下降 | summary-by-blocker | +| 6 | unsupported_pattern_selector_transform 频次 | 高(截图口径) | 0 | summary-by-blocker | +| 7 | body_id 数 | =1 | ≥2 | 单元 | +| 8 | fillet 后 selector 解析成功率 | not_found | resolved(演化后继唯一时;被完整消费的边保持保守 not_found) | 单元 | +| 9 | unsupported_hole_subtype 频次 | ≈712 | 0 | summary-by-blocker | +| 10 | unsupported_profile 频次 / profile.type=circles/annulus | ≈718 | 0(importer 修复后) | 数据 + summary-by-blocker | + +## 4. 最小基线流程 + +```bash +# 数据扫描 +python backend/tests/test_engine_diagnostics_corpus.py + +# 批量基线 +PYTHONPATH=backend/engine python -m cdsl_engine.batch_rebuild \ + json_to_cdsl/output /tmp/batch-before --build --build-timeout 15 + +# phase 分桶 +PYTHONPATH=backend/engine python -m cdsl_engine.batch_rebuild \ + json_to_cdsl/output /tmp/batch-p3-before --phase p3 --build +PYTHONPATH=backend/engine python -m cdsl_engine.batch_rebuild \ + json_to_cdsl/output /tmp/batch-p4-before --phase p4 --build +PYTHONPATH=backend/engine python -m cdsl_engine.batch_rebuild \ + json_to_cdsl/output /tmp/batch-p6-before --phase p6 --build + +# 单测基线 +python -m unittest backend.tests.test_engine_diagnostics_baseline -v +``` + +任意修复后重跑,diff 出 geometry_verified_count 单调不减、各 blocker 频次单调下降。 + +## 5. 修复 PR 强约束 + +按 `engine/README.md:22-30` 声明:三处必须同步修改—— +1. `sketch_solver.py` / `runtime.py` / `build123d_adapter.py` 实现 +2. `profile_schema.json`(人读契约) +3. `cdsl_schema.json`(机器契约) + +`backend/tests/test_profile_schema.py` 是这三处同步的护栏。 + +## 6. 与当前修改的关系 + +`git diff` 显示 `json_to_cdsl/evidence_v2_to_cdsl.py` 把 `type=circles` 重写为 `type=analytic_contours`,并已应用于 070825 / 084242 两份 cdsl.json。这是 **问题 10 的入口侧修复**(importer 直接产出合规 profile)。 + +修复后数据扫描预期: +- `json_to_cdsl/output/*.cdsl.json` 中 `profile.type=="circles"` 计数:数百 → 0(已在新文档体现) +- `summary-by-blocker.unsupported_profile`:在新文档上 = 0 +- 旧文档仍可能含 legacy profile,测试需保留 `json_to_cdsl/output.before/` 快照做对比