wip: preserve local engine and agent workflow changes before ganjihong refactor integration
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
# CADFS 全量能力快照对比
|
||||
|
||||
## 范围
|
||||
|
||||
本文档对比第一次归档的 CADFS 全量运行与最新归档的全量运行。两份快照均包含
|
||||
9,347 个源样本。
|
||||
|
||||
| 快照 | 报告生成时间 | 证据目录 |
|
||||
| --- | --- | --- |
|
||||
| 第一次全量运行 | 2026-09-02 20:40:56 | `cadfs_to_cdsl/output-history/20260907-185128/` |
|
||||
| 最新全量运行 | 2026-09-08 21:59:59 | `cadfs_to_cdsl/output-history/20260908-215959/` |
|
||||
|
||||
一个 executor 已注册,仅表示运行时能识别该原子操作;它本身不表示全部 CADFS
|
||||
参数变体、拓扑 selector、body 生命周期或源模型都能正确重建。下文的全量比较与
|
||||
能力缺口表才是实际覆盖范围的证据。
|
||||
|
||||
## 已注册的原子操作
|
||||
|
||||
| 原子操作组 | 第一次全量运行 | 最新全量运行 | 变化 |
|
||||
| --- | --- | --- | --- |
|
||||
| 基础实体 | `sphere_add` | `box_add`, `cylinder_add`, `sphere_add` | 新增方体与圆柱体 |
|
||||
| 盲拉伸/加料拉伸 | `extrude_add_blind`, `extrude_add_two_sided` | 保留原有操作,新增 `extrude_add_blind_with_hole` | 新增 selector 绑定的端盖孔拉伸 |
|
||||
| 切除拉伸 | `extrude_cut_blind` | 保留原有操作,新增 `extrude_cut_through`、`extrude_cut_two_sided` | 新增贯穿与双向切除 |
|
||||
| 曲面拉伸 | 未注册 | `extrude_surface` | 新增仅生成曲面的拉伸路径 |
|
||||
| 回转 | `revolve_add`, `revolve_cut` | 保留原有操作,新增 `revolve_surface` | 新增曲面回转路径 |
|
||||
| 放样 | 未注册 | `loft_add`, `loft_add_with_cap_face` | 新增实体放样与 `CAP_FACE` 驱动放样 |
|
||||
| 扫掠 | 未注册 | `sweep_add` | 新增实体扫掠路径 |
|
||||
| 抽壳 | 未注册 | `shell` | 新增抽壳路径 |
|
||||
| 多 body 布尔 | 未注册 | `boolean_bodies` | 新增 body 的 union/subtract/intersect |
|
||||
| 孔 | `hole_blind`, `hole_counterbore`, `hole_countersink`, `hole_wizard` | 相同 | 无新原子操作 |
|
||||
| 修饰特征 | `fillet`, `chamfer` | 相同 | 无新原子操作;覆盖和 selector 处理有改进 |
|
||||
| 阵列 | `pattern_linear`, `pattern_mirror` | 保留原有操作,新增 `pattern_circular` | 新增环形阵列;replay/body 处理也有改进 |
|
||||
| 基准几何 | `reference_plane`, `reference_axis` | 相同 | 无新原子操作;CADFS 基准面变体覆盖增加 |
|
||||
| 螺纹 | 未注册 | `thread_add`, `thread_cut` | 新增加料/切除螺纹 |
|
||||
| 钣金折弯 | 未注册 | `bend_add` | 已注册;这两份 CADFS 全量快照未单独证明其 CADFS 映射与几何接受率 |
|
||||
|
||||
## 全量能力覆盖
|
||||
|
||||
计数是被记录为能力缺口的样本数。减少表示更多样本进入支持的转换/运行时路径,
|
||||
但不表示每个新增可执行模型都已通过几何验收。
|
||||
|
||||
| 类别 | 能力 | 第一次缺口 | 最新缺口 | 变化 | 当前解释 |
|
||||
| --- | --- | ---: | ---: | ---: | --- |
|
||||
| 新覆盖路径 | `revolve_surface` | 367 | 0 | -367 | 已观察到的样本均进入支持的运行时路径 |
|
||||
| 新覆盖路径 | `extrude_cut_through_all` | 301 | 0 | -301 | 不再因贯穿切除产生此能力缺口 |
|
||||
| 新覆盖路径 | `extrude_cut_two_sided` | 260 | 0 | -260 | 不再因双向切除产生此能力缺口 |
|
||||
| 新覆盖路径 | `extrude_add_through_all` | 3 | 0 | -3 | 不再因贯穿加料产生此能力缺口 |
|
||||
| 新覆盖路径 | `extrude_extent:up_to_next` | 48 | 0 | -48 | 不再因 up-to-next 产生此能力缺口 |
|
||||
| 新覆盖路径 | `reference_plane:line_angle` | 121 | 0 | -121 | 不再因该基准面变体产生此能力缺口 |
|
||||
| 新覆盖路径 | `reference_plane:plane_point` | 46 | 0 | -46 | 不再因该基准面变体产生此能力缺口 |
|
||||
| 新覆盖路径 | `reference_plane:three_point` | 34 | 0 | -34 | 不再因该基准面变体产生此能力缺口 |
|
||||
| 新覆盖路径 | `reference_plane:mid_plane` | 24 | 0 | -24 | 不再因该基准面变体产生此能力缺口 |
|
||||
| 新覆盖路径 | `reference_plane:line_point` | 22 | 0 | -22 | 不再因该基准面变体产生此能力缺口 |
|
||||
| 新覆盖路径 | `reference_plane:curve_point` | 12 | 0 | -12 | 不再因该基准面变体产生此能力缺口 |
|
||||
| 主要覆盖提升 | `extrude` | 3,861 | 1,197 | -2,664 | 最大降幅;派生 profile 与 selector 变体仍待完成 |
|
||||
| 主要覆盖提升 | `shell` | 696 | 57 | -639 | 主路径已存在;面选择器变体仍待完成 |
|
||||
| 主要覆盖提升 | `fillet` | 1,914 | 1,417 | -497 | 覆盖增加;OCC 可行性和后继选择仍阻塞大量模型 |
|
||||
| 主要覆盖提升 | `revolve` | 741 | 344 | -397 | 实体回转覆盖增加 |
|
||||
| 主要覆盖提升 | `loft` | 308 | 68 | -240 | 主放样路径和部分 cap-face 形式已覆盖 |
|
||||
| 主要覆盖提升 | `chamfer` | 589 | 381 | -208 | 覆盖增加;selector 与内核失败仍存在 |
|
||||
| 主要覆盖提升 | `hole` | 623 | 386 | -237 | 更多孔变体进入运行时 |
|
||||
| 主要覆盖提升 | `sweep` | 326 | 133 | -193 | 实体扫掠覆盖增加;路径变体仍待完成 |
|
||||
| 主要覆盖提升 | `booleanBodies` | 187 | 92 | -95 | 多 body 路径覆盖增加 |
|
||||
| 主要覆盖提升 | `cPlane` | 147 | 84 | -63 | 更多基准面路径成功 lower |
|
||||
| 主要覆盖提升 | `mirror` | 262 | 158 | -104 | 阵列/body replay 覆盖增加 |
|
||||
| 主要覆盖提升 | `circularPattern` | 228 | 74 | -154 | 阵列/body replay 覆盖增加 |
|
||||
| 主要覆盖提升 | `transform` | 1 | 230 | +229 | 更多 transform 语义被识别并诊断;不代表完整支持 |
|
||||
| 派生拓扑 | `extrude_profile_topology:cap_edge` | 133 | 91 | -42 | 部分 `CAP_EDGE` 覆盖 |
|
||||
| 派生拓扑 | `extrude_profile_topology:cap_face` | 200 | 183 | -17 | 部分 `CAP_FACE` 覆盖 |
|
||||
| 派生拓扑 | `extrude_profile_topology:intersect` | 382 | 362 | -20 | 部分 `INTERSECT` 覆盖 |
|
||||
| 派生拓扑 | `extrude_profile_topology:offset_face` | 18 | 9 | -9 | 部分 `OFFSET_FACE` 覆盖 |
|
||||
| 派生拓扑 | `extrude_profile_topology:swept_face` | 99 | 94 | -5 | 部分 `SWEPT_FACE` 覆盖 |
|
||||
|
||||
## 最新快照中新分类的未完成边界
|
||||
|
||||
这些条目在最新全量报告中被新分类或单独拆出。它们表示已知未完成语义,不能视为
|
||||
已完成能力。
|
||||
|
||||
| 能力缺口 | 最新受影响样本 | 含义 |
|
||||
| --- | ---: | --- |
|
||||
| `shell_face_selector` | 221 | 这些 source history 的 shell remove-face selector 尚不能解析 |
|
||||
| `delete_bodies` | 121 | body 生命周期中的删除语义不完整 |
|
||||
| `sweep_path` | 68 | 扫掠路径变体尚不能表达或执行 |
|
||||
| `shell_outward` | 19 | 向外抽壳方向尚未完成 |
|
||||
| `extrude_draft_extent` | 15 | 带拔模拉伸的终止语义尚未完成 |
|
||||
| `sweep_remove` | 6 | 扫掠切除语义尚未完成 |
|
||||
| `circular_pattern_remove_source` | 1 | 环形阵列移除源 body 的生命周期尚未完成 |
|
||||
| `extrude_cap_edge_profile` | 1 | `CAP_EDGE` 派生拉伸 profile 尚未完成 |
|
||||
|
||||
## 端到端结果
|
||||
|
||||
结果表区分可执行工件和几何验收。`RP` 是工程验收口径;`strict` 是更严格的诊断,
|
||||
不能隐藏已 RP 通过的工件。
|
||||
|
||||
| 指标 | 第一次全量运行 | 最新全量运行 | 变化 |
|
||||
| --- | ---: | ---: | ---: |
|
||||
| 候选 CDSL 文件 | 8,108 | 8,633 | +525 |
|
||||
| 完整转换 | 2,427 | 5,367 | +2,940 |
|
||||
| 无可执行特征的 deferred | 2,602 | 693 | -1,909 |
|
||||
| 重建 STEP 文件 | 1,812 | 6,134 | +4,322 |
|
||||
| 比较报告 | 1,719 | 6,090 | +4,371 |
|
||||
| RP 通过模型 | 1,159 (12.40%) | 2,031 (21.73%) | +872 |
|
||||
| Strict 通过模型 | 578 (6.18%) | 905 (9.68%) | +327 |
|
||||
| 比较超时或 worker 失败 | 56 | 44 | -12 |
|
||||
| 已重建但几何拒绝 | 568 | 4,059 | +3,491 |
|
||||
| 重建失败 | 617 | 2,144 | +1,527 |
|
||||
| 解析/lowering 失败 | 3 | 21 | +18 |
|
||||
|
||||
当更多 partial 或原先不支持的 history 能进入 engine 后,被拒绝的 STEP 明显增加。
|
||||
这些 STEP 工件和诊断必须保留;它们是 converter/runtime 的证据,不是验收通过的重建。
|
||||
|
||||
## 当前失败优先级
|
||||
|
||||
最新全量运行记录了以下主要重建失败类别。
|
||||
|
||||
| 失败类别 | 受影响重建 | 优先工作 |
|
||||
| --- | ---: | --- |
|
||||
| Selector 找不到 | 982 | 保留拓扑 provenance,实现精确的后继/output-role 绑定 |
|
||||
| Fillet 内核失败 | 347 | 改善通用可行性处理,并保留有界 OCC 诊断 |
|
||||
| Chamfer 内核失败 | 174 | 改善通用可行性处理,并保留有界 OCC 诊断 |
|
||||
| BRep API 失败 | 139 | 调查操作特定的内核前置条件与拓扑输入 |
|
||||
| Compound 拉伸不支持 | 83 | 完成显式多 body/Compound 的拉伸语义 |
|
||||
| Selector 歧义 | 82 | 改善 selector provenance,确定性拒绝不唯一候选 |
|
||||
| 终止目标不可达或空 | 71 | 完成以目标为基础的 extent/trim 语义 |
|
||||
| Boolean 操作失败 | 98 | 完成通用多 body boolean 输入和结果生命周期 |
|
||||
| Shell 操作失败 | 46 | 完成 remove-face 及向内/向外抽壳语义 |
|
||||
|
||||
## 状态
|
||||
|
||||
最新快照显示转换和可执行 STEP 覆盖已有明显扩展,但尚未完成全量重建。只有当一项
|
||||
能力同时具备 FeatureScript lowering、CDSL contract、runtime/adapter 行为、selector/body
|
||||
生命周期、单元覆盖、多个真实语料回归与 RP 比较证据时,才能标记为完成。
|
||||
|
||||
## 证据
|
||||
|
||||
- `cadfs_to_cdsl/output-history/20260907-185128/summary.json`
|
||||
- `cadfs_to_cdsl/output-history/20260907-185128/full_run_report.md`
|
||||
- `cadfs_to_cdsl/output-history/20260908-215959/summary.json`
|
||||
- `cadfs_to_cdsl/output-history/20260908-215959/full_run_report.md`
|
||||
@@ -35,6 +35,11 @@ feature history 生成工程相似的 STEP。
|
||||
- 若已有实现反复依赖样本化补丁、不能表达已出现的通用语义或受内核 API 结构性限制,
|
||||
必须评估替代方案,不得沿错误方向继续累积补丁。替换需要可复现根因、成熟实现或最小
|
||||
原型的对照、contract/迁移影响评估和回归计划;单个样本或偶发内核失败不足以触发重写。
|
||||
- 每项 FeatureScript operation、query 或枚举语义的 lowering,必须对照源文件声明的
|
||||
FeatureScript/standard library 版本对应的官方 API/query contract,并在能力矩阵记录
|
||||
文档或标准库来源、已采用的语义与未覆盖边界。当前官网或其它版本的文档不得替代源版本
|
||||
的默认值和行为。文档只用于确定源语义,不能替代 CDSL provenance、OCC builder history
|
||||
或 selector 的唯一性证据。
|
||||
- 每项能力必须同时具备:FeatureScript lowering、CDSL schema/semantic validation、
|
||||
runtime/adapter 实现、selector/body 语义、单元测试、多个语料回归和比较工件;缺少
|
||||
任一层只能标记为“部分完成”。
|
||||
@@ -495,8 +500,9 @@ instance 生命周期。每项采用或拒绝外部方案的决定、理由和
|
||||
和 body provenance。仅有 runtime CDSL contract 而未由 lowering 产生的能力必须标为
|
||||
部分完成。
|
||||
3. runtime/adapter 按通用算法执行,记录结果 body 和 topology delta,不依赖样本信息。
|
||||
4. 原子语义矩阵包含正向、边界和拒绝测试;至少多个真实语料样本覆盖不同几何和
|
||||
生命周期组合。
|
||||
4. 能力矩阵记录对应 FeatureScript API/query 的源版本文档或标准库依据、采用的语义和
|
||||
未覆盖边界;原子语义矩阵包含正向、边界和拒绝测试,且至少多个真实语料样本覆盖
|
||||
不同几何和生命周期组合。
|
||||
5. 受影响核心集、扩展集和全量 shard 有可复现结果,工程相似通过率、失败数和剩余
|
||||
exception 均更新到本地台账。
|
||||
6. 代码审查确认没有 sample-specific 分支、gold STEP 参数回填、隐式默认尺寸或为
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Any
|
||||
from .featurescript_lexer import Token, lex
|
||||
from .ir import Call, FeatureIR, ModelIR, SketchIR
|
||||
@@ -130,7 +131,18 @@ def _arg_map(call: Call) -> dict[str, Any]:
|
||||
|
||||
|
||||
def parse_featurescript(source: str, sample_id: str = "unknown") -> ModelIR:
|
||||
parser = Parser(source); calls = parser.statements(); model = ModelIR(sample_id, raw_source=source)
|
||||
parser = Parser(source); calls = parser.statements()
|
||||
version = re.search(r"\bFeatureScript\s+(\d+(?:\.\d+)*)\s*;", source)
|
||||
standard_library = re.search(
|
||||
r"\bimport\s*\(\s*path\s*:\s*[\"']([^\"']*onshape/std/[^\"']*)[\"']",
|
||||
source,
|
||||
)
|
||||
model = ModelIR(
|
||||
sample_id,
|
||||
raw_source=source,
|
||||
featurescript_version=version.group(1) if version else None,
|
||||
standard_library=standard_library.group(1) if standard_library else None,
|
||||
)
|
||||
for call in calls:
|
||||
if call.name == "newSketch":
|
||||
definition = _arg_map(call)
|
||||
|
||||
@@ -37,3 +37,7 @@ class ModelIR:
|
||||
sketches: list[SketchIR] = field(default_factory=list)
|
||||
steps: list[Any] = field(default_factory=list)
|
||||
raw_source: str = ""
|
||||
# Source metadata is retained independently from the lowered CDSL so a
|
||||
# selector can be audited against the FeatureScript API contract it used.
|
||||
featurescript_version: str | None = None
|
||||
standard_library: str | None = None
|
||||
|
||||
@@ -49,6 +49,55 @@ def plain(value: Any) -> Any:
|
||||
return value
|
||||
|
||||
|
||||
def _selector_intent(
|
||||
value: Any,
|
||||
*,
|
||||
query_family: str,
|
||||
kind: str,
|
||||
evidence: str,
|
||||
allowed: tuple[str, ...] = ("continuation",),
|
||||
multiplicity: str = "one",
|
||||
output_role: str | None = None,
|
||||
disambiguation: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Keep the source query as semantics, separate from runtime topology IDs."""
|
||||
query = parse_query(value)
|
||||
intent: dict[str, Any] = {
|
||||
"version": "1.0",
|
||||
"kind": kind,
|
||||
"query_family": query_family,
|
||||
# This placeholder is populated from the enclosing ModelIR once the
|
||||
# FeatureScript source header has been parsed.
|
||||
"source_query": {"ast": query.ast, "featurescript_version": "0"},
|
||||
"derivation_policy": {"allowed": list(allowed), "multiplicity": multiplicity},
|
||||
"evidence": evidence,
|
||||
}
|
||||
if query.source_sketch and query.source_entity:
|
||||
intent["source_entity"] = {"sketch_id": query.source_sketch, "entity_id": query.source_entity}
|
||||
if output_role is not None:
|
||||
intent["output_role"] = output_role
|
||||
if disambiguation is not None:
|
||||
intent["disambiguation"] = disambiguation
|
||||
return intent
|
||||
|
||||
|
||||
def _finalize_selector_intents(cdsl: dict[str, Any], model: ModelIR) -> None:
|
||||
"""Bind source-version metadata after all FeatureScript selectors lower."""
|
||||
source_version = model.featurescript_version or "0"
|
||||
for feature in cdsl.get("features") or []:
|
||||
for selector in feature.get("selectors") or []:
|
||||
intent = selector.get("selector_intent") if isinstance(selector, dict) else None
|
||||
if not isinstance(intent, dict):
|
||||
continue
|
||||
selector["selector_intent_version"] = "1.0"
|
||||
source_query = intent.get("source_query")
|
||||
if not isinstance(source_query, dict):
|
||||
continue
|
||||
source_query["featurescript_version"] = source_version
|
||||
if model.standard_library:
|
||||
source_query["standard_library"] = model.standard_library
|
||||
|
||||
|
||||
def _bool(value: Any) -> bool:
|
||||
return value is True or (isinstance(value, str) and value.lower() == "true")
|
||||
|
||||
@@ -573,6 +622,17 @@ def _shell_offset_face_output_role_selector(
|
||||
},
|
||||
"source": "runtime_snapshot",
|
||||
"confidence": 1.0,
|
||||
"selector_intent": _selector_intent(
|
||||
value,
|
||||
query_family="OFFSET_FACE",
|
||||
kind="face",
|
||||
evidence="operation_role",
|
||||
allowed=("boundary", "replacement"),
|
||||
output_role="shell.offset_face",
|
||||
disambiguation={"type": "true_dependency", "sources": [
|
||||
{"owner_feature_id": source["owner_feature_id"], "output_role": source["output_role"]},
|
||||
]},
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -1792,6 +1852,14 @@ def _cap_face_output_role_selector(
|
||||
"output_role": f"{role_prefix}.{'start' if query.is_start else 'end'}",
|
||||
"source": "runtime_snapshot",
|
||||
"confidence": 1.0,
|
||||
"selector_intent": _selector_intent(
|
||||
value,
|
||||
query_family="CAP_FACE",
|
||||
kind="face",
|
||||
evidence="operation_role",
|
||||
allowed=("boundary", "continuation"),
|
||||
output_role=f"{role_prefix}.{'start' if query.is_start else 'end'}",
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -4800,6 +4868,8 @@ def lower_model(model: ModelIR, provenance: dict[str, Any]) -> LoweringResult:
|
||||
diagnostics.append({"code": "feature_deferred", "feature_id": item.feature_id, "operation": item.operation, "message": str(exc)}); complete = False
|
||||
if not features: return LoweringResult(None, "deferred_no_executable_feature", diagnostics, history)
|
||||
cdsl = {"schema": "cad.cdsl.llm.v1", "schema_version": "1.1.0", "kind": "part", "part_id": model.sample_id,
|
||||
"meta": {"unit": "mm", "source": "CADFS", "provenance": provenance, "capability_gaps": sorted({d.get("operation") for d in diagnostics if d.get("operation")})},
|
||||
"meta": {"unit": "mm", "source": "CADFS", "provenance": provenance, "capability_gaps": sorted({d.get("operation") for d in diagnostics if d.get("operation")}),
|
||||
"source_featurescript": {"version": model.featurescript_version or "0", **({"standard_library": model.standard_library} if model.standard_library else {})}},
|
||||
"geometry": {"sketches": sketches}, "features": features}
|
||||
_finalize_selector_intents(cdsl, model)
|
||||
return LoweringResult(cdsl, "converted_complete" if complete else "converted_partial", diagnostics, history)
|
||||
|
||||
@@ -15,6 +15,18 @@ class QueryInfo:
|
||||
source_entity: str | None = None
|
||||
is_start: bool | None = None
|
||||
calls: list[str] = field(default_factory=list)
|
||||
# The AST is intentionally lossless for the parser's value model. Query
|
||||
# aliases may be resolved for lowering, but their nested query semantics
|
||||
# must remain auditable and cannot be collapsed into a geometry hint.
|
||||
ast: dict[str, Any] | list[Any] | str | float | bool | None = None
|
||||
query_combinators: list[str] = field(default_factory=list)
|
||||
filters: list[str] = field(default_factory=list)
|
||||
body_scope: list[str] = field(default_factory=list)
|
||||
disambiguation: list[str] = field(default_factory=list)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if self.ast is None:
|
||||
self.ast = {}
|
||||
|
||||
def as_dict(self) -> dict[str, Any]: return asdict(self)
|
||||
|
||||
@@ -29,10 +41,31 @@ def walk_calls(value: Any):
|
||||
for item in value.values(): yield from walk_calls(item)
|
||||
|
||||
|
||||
def query_ast(value: Any) -> dict[str, Any] | list[Any] | str | float | bool | None:
|
||||
"""Serialize parsed FeatureScript query syntax without evaluating it."""
|
||||
if isinstance(value, Call):
|
||||
return {"call": value.name, "args": [query_ast(arg) for arg in value.args], "line": value.line}
|
||||
if isinstance(value, list):
|
||||
return [query_ast(item) for item in value]
|
||||
if isinstance(value, dict):
|
||||
return {str(key): query_ast(item) for key, item in value.items()}
|
||||
if value is None or isinstance(value, (str, float, bool, int)):
|
||||
return value
|
||||
return str(value)
|
||||
|
||||
|
||||
def parse_query(value: Any) -> QueryInfo:
|
||||
info = QueryInfo()
|
||||
info = QueryInfo(ast=query_ast(value))
|
||||
for call in walk_calls(value):
|
||||
info.calls.append(call.name)
|
||||
if call.name in {"qUnion", "qIntersection", "qSubtraction", "qAdjacent"}:
|
||||
info.query_combinators.append(call.name)
|
||||
if call.name in {"qBodyType", "qOwnerBody"}:
|
||||
info.body_scope.append(call.name)
|
||||
if call.name in {"TDD", "trueDependencyDisambiguation"}:
|
||||
info.disambiguation.append(call.name)
|
||||
if call.name in {"qBodyType", "qOwnerBody", "qAdjacent"}:
|
||||
info.filters.append(call.name)
|
||||
if call.name in {"makeQuery", "qCreatedBy"} and call.args:
|
||||
owner = symbolic_string(call.args[0])
|
||||
if "F" in owner:
|
||||
|
||||
@@ -152,6 +152,18 @@ def bind_candidate_selectors(cdsl: dict[str, Any]) -> tuple[dict[str, Any], list
|
||||
for placeholder in targets:
|
||||
records = prefix_records(placeholder.get("binding_feature_id"), placeholder.get("owner_feature_id"))
|
||||
output_role = str(placeholder.get("output_role") or "").strip()
|
||||
intent = placeholder.get("selector_intent")
|
||||
if (
|
||||
isinstance(intent, dict)
|
||||
and intent.get("query_family") != "GEOMETRIC"
|
||||
and not output_role
|
||||
):
|
||||
# Prefix rebuilding is retained as a diagnostic adapter.
|
||||
# It must not turn an unproven FeatureScript provenance
|
||||
# query into a geometry-scored stable selector.
|
||||
raise ValueError(
|
||||
f"{feature['id']}: selector_kernel_history_missing after prefix rebuild"
|
||||
)
|
||||
if output_role:
|
||||
# Builder output roles are not geometry placeholders. They
|
||||
# remain in the bound CDSL so runtime can resolve the
|
||||
|
||||
@@ -1041,10 +1041,15 @@ class LoweringTests(unittest.TestCase):
|
||||
cap_extrude = features["f_F5"]
|
||||
self.assertEqual(cap_extrude["atomic_id"], "extrude_from_face")
|
||||
self.assertNotIn("sketch_id", cap_extrude)
|
||||
self.assertEqual(cap_extrude["selectors"], [{
|
||||
selector = cap_extrude["selectors"][0]
|
||||
self.assertEqual({key: selector[key] for key in (
|
||||
"kind", "owner_feature_id", "output_role", "source", "confidence",
|
||||
)}, {
|
||||
"kind": "face", "owner_feature_id": "f_F3", "output_role": "extrude.start",
|
||||
"source": "runtime_snapshot", "confidence": 1.0,
|
||||
}])
|
||||
})
|
||||
self.assertEqual(selector["selector_intent_version"], "1.0")
|
||||
self.assertEqual(selector["selector_intent"]["query_family"], "CAP_FACE")
|
||||
|
||||
from engine.cdsl_engine.runtime import rebuild_cdsl
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
@@ -1691,6 +1696,21 @@ class LoweringTests(unittest.TestCase):
|
||||
self.assertEqual(result.diagnostics[0]["code"], "unsupported_engine_capability")
|
||||
self.assertIn("extrude_profile_topology:cap_face", result.diagnostics[0]["capability"])
|
||||
|
||||
def test_cap_face_selector_retains_versioned_provenance_intent(self):
|
||||
source = SOURCE.replace(
|
||||
'\n});\n',
|
||||
'\n extrude(context, id + "F2", {"entities":makeQuery(id + "F1.opExtrude", "CAP_FACE", FACE, {"isStart":false}), "depth":10 * mm});\n});\n',
|
||||
)
|
||||
result = lower_model(parse_featurescript(source, "cap-intent"), {})
|
||||
self.assertEqual(result.status, "converted_complete")
|
||||
selector = next(item for item in result.cdsl["features"] if item["id"] == "f_F2")["selectors"][0]
|
||||
self.assertEqual(selector["selector_intent"]["query_family"], "CAP_FACE")
|
||||
self.assertEqual(selector["selector_intent_version"], "1.0")
|
||||
self.assertEqual(selector["selector_intent"]["source_query"]["featurescript_version"], "1511")
|
||||
self.assertEqual(selector["selector_intent"]["evidence"], "operation_role")
|
||||
from engine.cdsl_engine.semantic_validation import validate_semantic_cdsl
|
||||
self.assertTrue(validate_semantic_cdsl(result.cdsl)["future_rebuild_ready"])
|
||||
|
||||
def test_conversion_writes_status_and_sidecars(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp); source = root / "00000173.txt"; source.write_text(SOURCE)
|
||||
|
||||
@@ -52,6 +52,16 @@ class ParserTests(unittest.TestCase):
|
||||
value = parse_query(query)
|
||||
self.assertEqual((value.owner_feature, value.source_sketch, value.source_entity), ("F1", "F0", "E0"))
|
||||
self.assertFalse(value.is_start)
|
||||
self.assertEqual(value.ast["call"], "makeQuery")
|
||||
self.assertEqual(value.ast["args"][0]["call"], "__binary__")
|
||||
|
||||
def test_source_version_and_standard_library_are_retained(self):
|
||||
source = '''FeatureScript 1511;
|
||||
import(path : "onshape/std/geometry.fs", version : "1511.0");
|
||||
export const f = defineFeature(function(context, id, definition) {});'''
|
||||
model = parse_featurescript(source, "versioned")
|
||||
self.assertEqual(model.featurescript_version, "1511")
|
||||
self.assertEqual(model.standard_library, "onshape/std/geometry.fs")
|
||||
|
||||
def test_block_scoped_query_aliases_do_not_use_the_last_assignment(self):
|
||||
source = r'''
|
||||
|
||||
@@ -123,10 +123,14 @@ class SelectorBindingTests(unittest.TestCase):
|
||||
bound, evidence = bind_candidate_selectors(candidate.cdsl)
|
||||
|
||||
selector = next(item for item in bound["features"] if item["id"] == "f_F3")["selectors"][0]
|
||||
self.assertEqual(selector, {
|
||||
self.assertEqual({key: selector[key] for key in (
|
||||
"kind", "owner_feature_id", "output_role", "source", "confidence",
|
||||
)}, {
|
||||
"kind": "face", "owner_feature_id": "f_F1", "output_role": "extrude.end",
|
||||
"source": "runtime_snapshot", "confidence": 1.0,
|
||||
})
|
||||
self.assertEqual(selector["selector_intent"]["query_family"], "CAP_FACE")
|
||||
self.assertEqual(selector["selector_intent"]["evidence"], "operation_role")
|
||||
self.assertNotIn("stable_id", selector)
|
||||
self.assertNotIn("snapshot_id", selector)
|
||||
self.assertNotIn("geometry", selector)
|
||||
@@ -155,11 +159,15 @@ class SelectorBindingTests(unittest.TestCase):
|
||||
self.assertEqual(first["snapshot_id"], "body:f_F2:face:1")
|
||||
self.assertEqual(first["source"], "runtime_snapshot")
|
||||
self.assertEqual(first["confidence"], 1.0)
|
||||
self.assertEqual(offset, {
|
||||
self.assertEqual({key: offset[key] for key in (
|
||||
"kind", "owner_feature_id", "output_role", "output_role_source", "source", "confidence",
|
||||
)}, {
|
||||
"kind": "face", "owner_feature_id": "f_F2", "output_role": "shell.offset_face",
|
||||
"output_role_source": {"owner_feature_id": "f_F1", "output_role": "extrude.start"},
|
||||
"source": "runtime_snapshot", "confidence": 1.0,
|
||||
})
|
||||
self.assertEqual(offset["selector_intent"]["query_family"], "OFFSET_FACE")
|
||||
self.assertEqual(offset["selector_intent"]["disambiguation"]["type"], "true_dependency")
|
||||
binding = next(item for item in evidence if item["feature_id"] == "f_F3")
|
||||
self.assertEqual(binding["resolved"][0]["snapshot_id"], "body:f_F2:face:1")
|
||||
self.assertEqual(binding["resolved"][1]["record_id"], "body:f_F2:face:5")
|
||||
|
||||
Reference in New Issue
Block a user