From 3aadd6a34e91974aa6560d78f291e140c617fc30 Mon Sep 17 00:00:00 2001 From: laluo <2135665716@qq.com> Date: Mon, 10 Aug 2026 18:05:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=BC=BA=E4=B8=80=E7=BA=A7?= =?UTF-8?q?=E5=85=B1=E9=9D=A2=E5=90=8C=E8=BD=B4=E4=BA=8B=E5=AE=9E=E8=AF=86?= =?UTF-8?q?=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- scripts/verify_first_level_fact_graph.py | 94 +++++++++++ step_editor/model.py | 190 +++++++++++++++++++++++ step_editor/ui_helpers.py | 42 +++++ step_editor/window_state.py | 1 + 5 files changed, 329 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 89c0807..4e453e4 100644 --- a/README.md +++ b/README.md @@ -289,7 +289,7 @@ STEP/B-Rep 参数化编辑主线 │ ├── [x] [平行/垂直 -> 端面推拉一级关系守门] │ │ └── 移动端面策略会确认直接相邻平面和移动端面可验证,执行前给出平面约束摘要,执行后反查垂直/平行关系。 │ ├── [x] [平行/垂直 -> Face/Edge 一级事实识别] -│ │ └── Face 和 Edge 一级事实图会记录直接邻域内的平行、垂直、斜交关系数量和摘要,供 UI 说明、计划守门和后续约束求解复用。 +│ │ └── Face 和 Edge 一级事实图会记录直接邻域内的平行、垂直、斜交关系数量和摘要;Face 事实图还会明确同域/共面碎片和一级同轴圆柱关系,供 UI 说明、计划守门和后续约束求解复用。 │ ├── [~] [平行/垂直 -> 通用可选约束] │ │ └── Face 偏移可走受限拉伸/切除,Face 面内长度/宽度的“保持关系”会转成所属对象单向缩放,Face 中心会转成所属对象平移,Edge 长度可走端面推拉;这些路径都会要求一级平面关系可验证并做结果反查。跨多面、跨特征的通用约束求解仍需继续泛化。 │ ├── [x] [复杂曲线 Edge -> 可编辑入口守门] @@ -1352,7 +1352,7 @@ git diff --check - `scripts/verify_face_nonrectangular_local_edit.py`:临时生成三角柱 STEP,验证非矩形平面 Face 的 `局部重建`、`缩放特征`、`移动特征`、`拉伸/切除` 语义,在中心、面内尺寸和偏移场景下都能保持单 Solid;同时生成斜顶楔块,验证斜交一级相邻平面会提前阻止 `保持关系`。 - `scripts/verify_face_resize_semantics.py`:加载 `cube_10mm.step`,验证 Face 的面内长度/面内宽度、中心坐标和偏移在 `当前面` 局部编辑、`整体` 所属对象编辑、`拉伸/切除` 与 `保持关系` 之间不会互相混淆;面积路径只作为后端缩放语义回归保留。 - `scripts/verify_face_noop_guards.py`:验证 Face 的拉伸/切除、偏移、中心、面内尺寸和壳体厚度在目标值等于当前值时都会返回 blocked,并给出“不需要修改”的说明。 -- `scripts/verify_first_level_fact_graph.py`:验证正方体平面 Face、通孔圆柱面和槽/半孔候选都会输出统一一级拓扑事实包,包含主体 Face、边界 Edge/Vertex、共享边一级相邻 Face、角色分组,以及明确不自动传播的二级/三级关系;同时验证 Face 拉伸/切除/面积/中心移动、孔径修改和槽宽修改的编辑计划都会携带这份事实。 +- `scripts/verify_first_level_fact_graph.py`:验证正方体平面 Face、拆分共面 Face、空心圆柱端盖、通孔圆柱面和槽/半孔候选都会输出统一一级拓扑事实包,包含主体 Face、同域/共面碎片、一级同轴圆柱关系、边界 Edge/Vertex、共享边一级相邻 Face、角色分组,以及明确不自动传播的二级/三级关系;同时验证 Face 拉伸/切除/面积/中心移动、孔径修改和槽宽修改的编辑计划都会携带这份事实。 - `scripts/verify_feature_recognition_summary.py`:验证平面 Face、带内孔平面 Face、通孔圆柱面、槽/半孔和凸台候选都会输出统一的 `识别摘要`、候选类型、常用优先级、置信度、风险、证据项、`当前可改` 和 `当前受限修改`;全局可编辑对象扫描也会按 Face、孔、槽、凸台、圆角/倒角、壳体、Edge 的常用顺序排序。 - `scripts/verify_face_property_readback.py`:验证 Face 的面内尺寸、中心和偏移编辑完成后,当前选中对象表里的当前值和目标值会重新读到新几何;特别覆盖 `偏移` 的 `局部重建`、`移动特征` 和 `拉伸/切除` 三种语义。面积读回只作为诊断结果验证。 - `scripts/verify_face_push_pull_limits.py`:验证平面 Face 向内切除时会估算当前面背后的材料厚度;浅范围切除允许执行,接近切穿会标为高风险,达到或超过材料厚度会直接 blocked;向外一次性拉伸/切除较大时会标为高风险并通过隔离子进程执行,而不是按绝对距离直接拒绝。 diff --git a/scripts/verify_first_level_fact_graph.py b/scripts/verify_first_level_fact_graph.py index 6be847d..e76feaa 100644 --- a/scripts/verify_first_level_fact_graph.py +++ b/scripts/verify_first_level_fact_graph.py @@ -10,10 +10,13 @@ for path in (PROJECT_ROOT, SCRIPTS_DIR): if str(path) not in sys.path: sys.path.insert(0, str(path)) +from step_editor.geometry_utils import _int_values from step_editor.model import StepModel from step_editor.ui_helpers import INFO_LABELS from step_editor.window_state import WindowStateMixin +from verify_face_coplanar_push_pull import _top_faces, _write_split_top_box # noqa: E402 +from verify_face_mixed_surface_guard import _top_planar_cap_face, _write_hollow_cylinder # noqa: E402 from verify_hole_resize import _first_hole_face, _write_through_hole_model # noqa: E402 from verify_shell_thickness_resize import _first_shell_face, _write_plate_model # noqa: E402 from verify_slot_resize import _first_slot_face, _write_half_round_slot_model # noqa: E402 @@ -121,6 +124,19 @@ def _verify_planar_face_facts() -> None: "一级平面关系" in str(facts.get("first_level_fact_summary") or ""), f"cube planar Face fact summary should include planar relation facts: {facts}", ) + _assert(facts.get("first_level_same_domain_status") == "ready", f"cube same-domain status: {facts}") + _assert( + facts.get("first_level_same_domain_relation") == "coplanar", + f"cube same-domain relation should be coplanar: {facts}", + ) + _assert( + int(facts.get("first_level_same_domain_face_count", 0) or 0) == 1, + f"cube should expose a single same-domain planar Face: {facts}", + ) + _assert( + "first_level_coaxial_cylinder_summary" in facts, + f"cube should still expose coaxial-cylinder fact fields: {facts}", + ) probe = _FactProbe(model) fields = probe._face_first_level_selection_fields(face_id) @@ -154,6 +170,80 @@ def _verify_planar_face_facts() -> None: _assert_plan_facts(plan, label, "face") +def _verify_coplanar_same_domain_facts(root: Path) -> None: + split_path = root / "split_top_box.step" + _write_split_top_box(split_path) + model = StepModel.load(split_path) + top_faces = _top_faces(model, 10.0, 2e-4) + _assert(len(top_faces) == 2, f"split-top box should have two coplanar top Faces, got {top_faces}") + + face_id = top_faces[0] + facts = model.face_first_level_facts(face_id, scope="face") + _assert_common_facts(facts, "split coplanar Face") + _assert( + facts.get("first_level_same_domain_status") == "ready", + f"split coplanar Face should expose same-domain facts: {facts}", + ) + _assert( + facts.get("first_level_same_domain_relation") == "coplanar", + f"split coplanar Face should be marked coplanar: {facts}", + ) + _assert( + set(_int_values(facts.get("first_level_same_domain_face_ids"))) == set(top_faces), + f"split coplanar facts should include both top Faces: {facts}", + ) + _assert( + int(facts.get("first_level_same_domain_fragment_face_count", 0) or 0) == 1, + f"split coplanar facts should expose one synchronized fragment: {facts}", + ) + _assert( + "共面" in str(facts.get("first_level_same_domain_summary") or ""), + f"split coplanar summary should mention coplanar relation: {facts}", + ) + + plan = model.push_pull_plan(face_id, 1.0) + _assert_plan_facts(plan, "split coplanar push-pull plan", "face") + _assert( + set(_int_values(plan.get("first_level_same_domain_face_ids"))) == set(top_faces), + f"split coplanar plan should carry same-domain facts: {plan}", + ) + + +def _verify_coaxial_cylinder_facts(root: Path) -> None: + hollow_path = root / "hollow_cylinder.step" + _write_hollow_cylinder(hollow_path) + model = StepModel.load(hollow_path) + cap_face_id = _top_planar_cap_face(model) + facts = model.face_first_level_facts(cap_face_id, scope="face") + _assert_common_facts(facts, "hollow cylinder cap") + _assert( + facts.get("first_level_coaxial_cylinder_status") == "ready", + f"hollow cylinder cap should expose ready coaxial-cylinder facts: {facts}", + ) + _assert( + int(facts.get("first_level_coaxial_cylinder_count", 0) or 0) >= 1, + f"hollow cylinder cap should have at least one coaxial cylinder relation: {facts}", + ) + _assert( + int(facts.get("first_level_coaxial_cylinder_face_count", 0) or 0) >= 2, + f"hollow cylinder cap should include inner and outer cylindrical Faces: {facts}", + ) + rows = tuple(row for row in facts.get("first_level_coaxial_cylinder_rows", ()) or () if isinstance(row, dict)) + radii = sorted({round(float(radius), 4) for row in rows for radius in tuple(row.get("radii") or ())}) + _assert(radii == [3.0, 8.0], f"hollow cylinder cap should expose inner/outer coaxial radii, got {radii}: {facts}") + _assert( + "同轴圆柱" in str(facts.get("first_level_coaxial_cylinder_summary") or ""), + f"hollow cylinder cap summary should mention coaxial cylinders: {facts}", + ) + + plan = model.push_pull_plan(cap_face_id, 1.0) + _assert_plan_facts(plan, "hollow cylinder cap push-pull plan", "face") + _assert( + plan.get("first_level_coaxial_cylinder_status") == "ready", + f"hollow cylinder cap plan should carry coaxial-cylinder facts: {plan}", + ) + + def _verify_shell_plan_facts(root: Path) -> None: shell_path = root / "thin_plate.step" _write_plate_model(shell_path) @@ -217,12 +307,16 @@ def main() -> int: "first_level_fact_summary", "first_level_fact_subject_face_count", "first_level_fact_adjacent_face_count", + "first_level_same_domain_summary", + "first_level_coaxial_cylinder_summary", ): _assert(key in INFO_LABELS, f"{key} should have a user-facing label") _verify_planar_face_facts() with tempfile.TemporaryDirectory(prefix="geom_param_first_level_facts_") as temp_dir: root = Path(temp_dir) + _verify_coplanar_same_domain_facts(root) + _verify_coaxial_cylinder_facts(root) _verify_shell_plan_facts(root) _verify_cylindrical_facts(root) _verify_slot_facts(root) diff --git a/step_editor/model.py b/step_editor/model.py index a563a38..96d16f5 100644 --- a/step_editor/model.py +++ b/step_editor/model.py @@ -3299,6 +3299,152 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd ), } + def _first_level_same_domain_relation_fields( + self, + *, + face_id: int, + same_domain_face_ids: Iterable[int], + relation_scope: str = "face", + ) -> dict[str, object]: + domain_ids = tuple(sorted({int(item) for item in same_domain_face_ids if 0 <= int(item) < len(self.faces)})) + surface = self.face_surface_kind(face_id) if 0 <= int(face_id) < len(self.faces) else "unknown" + fragment_ids = tuple(item for item in domain_ids if item != int(face_id)) + if surface == "plane": + relation = "coplanar" + relation_label = "共面" + elif surface == "cylinder": + relation = "cocylindrical" + relation_label = "同圆柱面" + elif len(domain_ids) > 1: + relation = "same-domain" + relation_label = "同域" + else: + relation = "single-face" + relation_label = "单 Face" + + if len(domain_ids) > 1: + summary = f"一级同域关系:已识别 {len(domain_ids)} 个{relation_label} Face,会作为同一局部主体。" + elif surface in {"plane", "cylinder"}: + summary = f"一级同域关系:当前为单个{relation_label}主体,未发现可同步碎片 Face。" + else: + summary = "一级同域关系:当前曲面类型暂不支持同域碎片同步,只按单 Face 处理。" + + return { + "first_level_same_domain_status": "ready", + "first_level_same_domain_scope": relation_scope, + "first_level_same_domain_relation": relation, + "first_level_same_domain_relation_label": relation_label, + "first_level_same_domain_surface": surface, + "first_level_same_domain_face_ids": domain_ids, + "first_level_same_domain_face_count": len(domain_ids), + "first_level_same_domain_fragment_face_ids": fragment_ids, + "first_level_same_domain_fragment_face_count": len(fragment_ids), + "first_level_same_domain_summary": summary, + "first_level_same_domain_note": ( + "同域/共面/同圆柱面碎片属于当前一级主体;当前只同步明确支持的编辑路径," + "不会递归扩展到二级、三级邻域。" + ), + } + + def _cylinders_are_coaxial( + self, + left: BRepAdaptor_Surface, + right: BRepAdaptor_Surface, + tolerance: float, + ) -> bool: + if left.GetType() != GeomAbs_Cylinder or right.GetType() != GeomAbs_Cylinder: + return False + left_axis = left.Cylinder().Axis() + right_axis = right.Cylinder().Axis() + axis_dot = abs(_direction_dot(left_axis.Direction(), right_axis.Direction())) + if axis_dot < 1.0 - 1e-6: + return False + distance = _point_axis_distance(left_axis.Location(), left_axis.Direction(), right_axis.Location()) + return distance <= max(tolerance, _shape_diagonal(self.shape) * 1e-7, 1e-6) + + def _first_level_coaxial_cylinder_relation_fields( + self, + *, + subject_face_ids: Iterable[int], + adjacent_face_ids: Iterable[int], + relation_scope: str = "face", + ) -> dict[str, object]: + involved_ids = tuple( + sorted({int(item) for item in (*tuple(subject_face_ids), *tuple(adjacent_face_ids)) if 0 <= int(item) < len(self.faces)}) + ) + diagonal = _shape_diagonal(self.shape) + tolerance = min(max(diagonal * 1e-7, 1e-6), 1e-3) + cylinder_infos: dict[int, dict[str, object]] = {} + for face_id in involved_ids: + surf = BRepAdaptor_Surface(self.faces[face_id]) + if surf.GetType() != GeomAbs_Cylinder: + continue + cylinder = surf.Cylinder() + axis = cylinder.Axis() + cylinder_infos[face_id] = { + "face_id": face_id, + "axis_point": _point_tuple(axis.Location()), + "axis_direction": _dir_tuple(axis.Direction()), + "radius": float(cylinder.Radius()), + } + + rows: list[dict[str, object]] = [] + cylinder_ids = tuple(sorted(cylinder_infos)) + for left_index, left_id in enumerate(cylinder_ids): + left_surf = BRepAdaptor_Surface(self.faces[left_id]) + for right_id in cylinder_ids[left_index + 1 :]: + right_surf = BRepAdaptor_Surface(self.faces[right_id]) + if not self._cylinders_are_coaxial(left_surf, right_surf, tolerance): + continue + left_radius = float(cylinder_infos[left_id]["radius"]) + right_radius = float(cylinder_infos[right_id]["radius"]) + rows.append( + { + "relation_kind": "cylinder-cylinder-axis", + "source": "first-level-cylinder-pair", + "face_ids": (left_id, right_id), + "relation": "coaxial", + "relation_label": "同轴", + "radii": (left_radius, right_radius), + "same_radius": abs(left_radius - right_radius) <= max(tolerance, max(left_radius, right_radius) * 1e-6), + } + ) + + related_face_ids = tuple(sorted({face_id for row in rows for face_id in _int_values(row.get("face_ids"))})) + if rows: + unique_radii = [] + for face_id in related_face_ids: + radius = float(cylinder_infos[face_id]["radius"]) + if not any(abs(radius - existing) <= max(tolerance, max(radius, existing) * 1e-6) for existing in unique_radii): + unique_radii.append(radius) + summary = ( + f"一级同轴圆柱关系:已识别 {len(rows)} 组同轴圆柱 Face;" + f"相关 Face {len(related_face_ids)},半径 {tuple(round(item, 6) for item in sorted(unique_radii))}。" + ) + status = "ready" + elif cylinder_infos: + summary = "一级同轴圆柱关系:一级范围内只有单个圆柱 Face,暂时没有可配对同轴关系。" + status = "unavailable" + else: + summary = "一级同轴圆柱关系:一级范围内没有圆柱 Face。" + status = "unavailable" + + return { + "first_level_coaxial_cylinder_status": status, + "first_level_coaxial_cylinder_scope": relation_scope, + "first_level_coaxial_cylinder_rows": tuple(rows), + "first_level_coaxial_cylinder_count": len(rows), + "first_level_coaxial_cylinder_face_ids": related_face_ids, + "first_level_coaxial_cylinder_face_count": len(related_face_ids), + "first_level_coaxial_cylinder_candidate_face_ids": cylinder_ids, + "first_level_coaxial_cylinder_candidate_face_count": len(cylinder_ids), + "first_level_coaxial_cylinder_summary": summary, + "first_level_coaxial_cylinder_note": ( + "这些是一级范围内圆柱轴线重合的事实;当前只作为识别和守门证据," + "跨特征同轴传播仍不自动执行。" + ), + } + def face_first_level_topology(self, face_id: int) -> dict[str, object]: """Return the explicit first-level B-Rep neighborhood for a Face. @@ -3367,6 +3513,16 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd adjacent_face_ids=adjacent_face_ids, relation_scope="face", ) + same_domain_relation_fields = self._first_level_same_domain_relation_fields( + face_id=face_id, + same_domain_face_ids=same_domain_face_ids, + relation_scope="face", + ) + coaxial_cylinder_relation_fields = self._first_level_coaxial_cylinder_relation_fields( + subject_face_ids=same_domain_face_ids, + adjacent_face_ids=adjacent_face_ids, + relation_scope="face", + ) topology = { "topology_relation_model": "STEP/B-Rep shared-edge first-level", "topology_relation_depth": 1, @@ -3395,6 +3551,8 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd "first_level_face_ids": first_level_face_ids, "first_level_face_count": len(first_level_face_ids), **planar_relation_fields, + **same_domain_relation_fields, + **coaxial_cylinder_relation_fields, "first_level_topology_note": ( f"已识别当前 Face 区域 {len(same_domain_face_ids)} 个 Face、" f"边界 Edge {len(region_boundary_edge_ids)} 条、" @@ -3498,6 +3656,16 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd for adjacent_id, edge_ids in sorted(shared_edges_by_face.items()) ) + same_domain_relation_fields = self._first_level_same_domain_relation_fields( + face_id=face_id, + same_domain_face_ids=side_face_ids, + relation_scope="cylindrical-feature", + ) + coaxial_cylinder_relation_fields = self._first_level_coaxial_cylinder_relation_fields( + subject_face_ids=side_face_ids, + adjacent_face_ids=adjacent_face_ids, + relation_scope="cylindrical-feature", + ) angular_span = feature.get("slot_angular_span", feature.get("angular_span", info.get("angular_span"))) topology = { "topology_relation_model": "STEP/B-Rep cylindrical-feature shared-edge first-level", @@ -3540,6 +3708,8 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd "cylindrical_feature_opening_face_count": len(opening_face_ids), "cylindrical_feature_slot_boundary_face_ids": slot_boundary_face_ids, "cylindrical_feature_slot_boundary_face_count": len(slot_boundary_face_ids), + **same_domain_relation_fields, + **coaxial_cylinder_relation_fields, "first_level_topology_note": ( f"Cylindrical side Faces={len(side_face_ids)}, boundary Edges={len(region_boundary_edge_ids)}, " f"boundary Vertices={len(boundary_vertex_points)}, direct adjacent Faces={len(adjacent_face_ids)}. " @@ -3647,6 +3817,16 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd for key, value in topology.items() if str(key).startswith("first_level_planar_relation_") } + same_domain_relation_fields = { + key: value + for key, value in topology.items() + if str(key).startswith("first_level_same_domain_") + } + coaxial_cylinder_relation_fields = { + key: value + for key, value in topology.items() + if str(key).startswith("first_level_coaxial_cylinder_") + } if not planar_relation_fields: planar_relation_fields = self._first_level_planar_relation_fields( subject_face_ids=subject_face_ids, @@ -3654,13 +3834,21 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd relation_scope=resolved_scope, ) planar_summary = str(planar_relation_fields.get("first_level_planar_relation_summary") or "").strip() + same_domain_summary = str(same_domain_relation_fields.get("first_level_same_domain_summary") or "").strip() + coaxial_summary = str( + coaxial_cylinder_relation_fields.get("first_level_coaxial_cylinder_summary") or "" + ).strip() summary = ( f"{subject_role}: Face {len(subject_face_ids)}, boundary Edge {len(boundary_edge_ids)}, " f"boundary Vertex {len(boundary_vertex_points)}, direct adjacent Face {len(adjacent_face_ids)}; " "deeper relations are recorded as future propagation targets, not edited automatically." ) + if same_domain_summary and int(same_domain_relation_fields.get("first_level_same_domain_face_count", 0) or 0) > 1: + summary = f"{summary} {same_domain_summary}" if planar_summary and planar_relation_fields.get("first_level_planar_relation_status") == "ready": summary = f"{summary} {planar_summary}" + if coaxial_summary and coaxial_cylinder_relation_fields.get("first_level_coaxial_cylinder_status") == "ready": + summary = f"{summary} {coaxial_summary}" facts = { "first_level_fact_model": "STEP/B-Rep first-level fact graph", "first_level_fact_source_model": source_model, @@ -3686,6 +3874,8 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd "first_level_fact_ignored_relation_note": topology.get("topology_ignored_relation_note", ""), "first_level_fact_summary": summary, **planar_relation_fields, + **same_domain_relation_fields, + **coaxial_cylinder_relation_fields, } for item in facts["first_level_fact_subject_face_ids"]: self._face_first_level_fact_cache[(int(item), resolved_scope)] = dict(facts) diff --git a/step_editor/ui_helpers.py b/step_editor/ui_helpers.py index 36de40a..73ac628 100644 --- a/step_editor/ui_helpers.py +++ b/step_editor/ui_helpers.py @@ -91,6 +91,27 @@ INFO_GROUPS: list[tuple[str, list[str]]] = [ "first_level_planar_relation_observed_modes", "first_level_planar_relation_summary", "first_level_planar_relation_note", + "first_level_same_domain_status", + "first_level_same_domain_scope", + "first_level_same_domain_relation", + "first_level_same_domain_relation_label", + "first_level_same_domain_surface", + "first_level_same_domain_face_ids", + "first_level_same_domain_face_count", + "first_level_same_domain_fragment_face_ids", + "first_level_same_domain_fragment_face_count", + "first_level_same_domain_summary", + "first_level_same_domain_note", + "first_level_coaxial_cylinder_status", + "first_level_coaxial_cylinder_scope", + "first_level_coaxial_cylinder_count", + "first_level_coaxial_cylinder_face_ids", + "first_level_coaxial_cylinder_face_count", + "first_level_coaxial_cylinder_candidate_face_ids", + "first_level_coaxial_cylinder_candidate_face_count", + "first_level_coaxial_cylinder_rows", + "first_level_coaxial_cylinder_summary", + "first_level_coaxial_cylinder_note", "face_push_pull_planar_relation_constraint_requested", "face_push_pull_planar_relation_constraint_label", "face_push_pull_planar_constraint_status", @@ -579,6 +600,27 @@ INFO_LABELS = { "first_level_planar_relation_observed_modes": "已识别平面关系", "first_level_planar_relation_summary": "一级平面关系摘要", "first_level_planar_relation_note": "一级平面关系说明", + "first_level_same_domain_status": "一级同域状态", + "first_level_same_domain_scope": "一级同域范围", + "first_level_same_domain_relation": "一级同域关系", + "first_level_same_domain_relation_label": "一级同域关系名称", + "first_level_same_domain_surface": "一级同域曲面", + "first_level_same_domain_face_ids": "一级同域 Face", + "first_level_same_domain_face_count": "一级同域 Face 数", + "first_level_same_domain_fragment_face_ids": "一级同域碎片 Face", + "first_level_same_domain_fragment_face_count": "一级同域碎片数", + "first_level_same_domain_summary": "一级同域摘要", + "first_level_same_domain_note": "一级同域说明", + "first_level_coaxial_cylinder_status": "一级同轴圆柱状态", + "first_level_coaxial_cylinder_scope": "一级同轴圆柱范围", + "first_level_coaxial_cylinder_count": "一级同轴圆柱关系数", + "first_level_coaxial_cylinder_face_ids": "一级同轴圆柱 Face", + "first_level_coaxial_cylinder_face_count": "一级同轴圆柱 Face 数", + "first_level_coaxial_cylinder_candidate_face_ids": "一级圆柱候选 Face", + "first_level_coaxial_cylinder_candidate_face_count": "一级圆柱候选数", + "first_level_coaxial_cylinder_rows": "一级同轴圆柱关系列表", + "first_level_coaxial_cylinder_summary": "一级同轴圆柱摘要", + "first_level_coaxial_cylinder_note": "一级同轴圆柱说明", "face_push_pull_planar_relation_constraint_requested": "Face保持关系已请求", "face_push_pull_planar_relation_constraint_label": "Face保持关系约束", "face_push_pull_planar_constraint_status": "Face保持关系状态", diff --git a/step_editor/window_state.py b/step_editor/window_state.py index 1e9e224..64db0b6 100644 --- a/step_editor/window_state.py +++ b/step_editor/window_state.py @@ -1553,6 +1553,7 @@ class WindowStateMixin: "凸台:圆柱凸台直径/高度/轴心;矩形凸台/矩形槽口袋长宽、中心、高度/深度;多台阶矩形凸台顶层规则台阶。\n" "圆角/倒角:简单已有圆角半径/弧长、简单等半径圆角链半径/弧长、已有等距倒角距离,直线 Edge 新增圆角/倒角。\n" "Edge/解析曲面:直线 Edge 长度/端点/中心、圆/椭圆 Edge、简单圆锥/球/环面。\n" + "一级关系:Face/Edge 平行垂直事实、Face 共面/同域碎片、一级同轴圆柱事实;部分 Face/Edge 操作可选择保持关系。\n" "受限:复杂链式特征、二级/三级拓扑传播和原 CAD 历史恢复。" ) return headline, detail, tooltip