feat: 完善 Face 一级关系编辑和稳定性校验
This commit is contained in:
@@ -9,6 +9,7 @@ if str(PROJECT_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
|
||||
from step_editor.window_state import WindowStateMixin
|
||||
from step_editor.ui_helpers import _format_value
|
||||
|
||||
|
||||
class _PropertySpecProbe(WindowStateMixin):
|
||||
@@ -315,6 +316,10 @@ def _assert_holed_plane_local_scopes_disabled() -> None:
|
||||
|
||||
|
||||
def main() -> int:
|
||||
relation_depths_text = _format_value(("second-level", "third-level", "deeper"))
|
||||
if "second-level" not in relation_depths_text or "deeper" not in relation_depths_text:
|
||||
raise SystemExit(f"relation-depth tuple should render as text: {relation_depths_text!r}")
|
||||
|
||||
plane_info = {
|
||||
"surface": "plane",
|
||||
"area": 100.0,
|
||||
@@ -485,6 +490,68 @@ def main() -> int:
|
||||
_assert_no_generic_face_leak(cylinder_keys, "cylindrical hole feature")
|
||||
_assert_contains(cylinder_keys, {"diameter", "hole_cylinder_radius"}, "cylindrical hole feature")
|
||||
|
||||
cylinder_topology_info = {
|
||||
"surface": "cylinder",
|
||||
"feature_guess": "hole/groove candidate",
|
||||
"diameter": 6.0,
|
||||
"radius": 3.0,
|
||||
"angular_span": 6.283185307179586,
|
||||
"area": 188.0,
|
||||
"area_center": (0.0, 0.0, 0.0),
|
||||
"bbox_center": (0.0, 0.0, 0.0),
|
||||
"axis": (0.0, 0.0, 1.0),
|
||||
"axis_point": (0.0, 0.0, 0.0),
|
||||
"axis_center": (0.0, 0.0, 5.0),
|
||||
"topology_relation_depth": 1,
|
||||
"topology_relation_model": "STEP/B-Rep cylindrical-feature shared-edge first-level",
|
||||
"topology_relation_status": "ready",
|
||||
"topology_ignored_relation_note": "当前阶段只传播一级关系;二级、三级拓扑暂不自动递归编辑。",
|
||||
"cylindrical_feature_side_face_count": 1,
|
||||
"cylindrical_feature_boundary_edge_count": 2,
|
||||
"cylindrical_feature_boundary_vertex_count": 4,
|
||||
"cylindrical_feature_adjacent_face_count": 2,
|
||||
"cylindrical_feature_end_face_count": 2,
|
||||
"cylindrical_feature_opening_face_count": 2,
|
||||
"first_level_topology_note": "Cylindrical side Faces=1, boundary Edges=2, boundary Vertices=4, direct adjacent Faces=2.",
|
||||
}
|
||||
cylinder_topology_specs = _specs(cylinder_topology_info)
|
||||
cylinder_topology_spec = _spec(cylinder_topology_specs, "cylindrical_feature_first_level_topology")
|
||||
cylinder_topology_text = str(cylinder_topology_spec.get("current_text") or "")
|
||||
for fragment in ("侧壁 Face 1 个", "边界 Edge 2 条", "共享边相邻 Face 2 个"):
|
||||
if fragment not in cylinder_topology_text:
|
||||
raise SystemExit(f"cylindrical feature topology row should explain first-level counts: {cylinder_topology_spec}")
|
||||
if "二级、三级" not in str(cylinder_topology_spec.get("disabled_tip") or ""):
|
||||
raise SystemExit(f"cylindrical feature topology tip should document ignored deeper topology: {cylinder_topology_spec}")
|
||||
cylinder_feature_probe = _PropertySpecProbe()
|
||||
cylinder_feature_specs, _used = cylinder_feature_probe._editable_property_specs(cylinder_topology_info)
|
||||
cylinder_feature_rows = cylinder_feature_probe._feature_property_specs(cylinder_feature_specs, cylinder_topology_info)
|
||||
_spec(cylinder_feature_rows, "cylindrical_feature_first_level_topology")
|
||||
|
||||
generic_cylinder_specs = _specs(
|
||||
{
|
||||
"surface": "cylinder",
|
||||
"feature_guess": "",
|
||||
"diameter": 6.0,
|
||||
"radius": 3.0,
|
||||
"angular_span": 6.283185307179586,
|
||||
"height_estimate": 11.0,
|
||||
"same_domain_height_estimate": 11.0,
|
||||
"area": 207.0,
|
||||
"area_center": (0.0, 0.0, 5.5),
|
||||
"bbox_center": (0.0, 0.0, 5.5),
|
||||
"axis": (0.0, 0.0, 1.0),
|
||||
"axis_point": (0.0, 0.0, 0.0),
|
||||
"axis_center": (0.0, 0.0, 5.5),
|
||||
}
|
||||
)
|
||||
generic_height = _spec(generic_cylinder_specs, "cylinder_height")
|
||||
if generic_height.get("scope_default") != "owning":
|
||||
raise SystemExit(f"generic cylinder height should default to owning-axis resize: {generic_height}")
|
||||
if generic_height.get("action") != "resize_cylindrical_height_owning_scale":
|
||||
raise SystemExit(f"generic cylinder height should use owning-axis action by default: {generic_height}")
|
||||
if generic_height.get("scope_text") != "调整整个特征":
|
||||
raise SystemExit(f"generic cylinder height should show owning scope by default: {generic_height}")
|
||||
|
||||
slot_keys = _spec_keys(
|
||||
{
|
||||
"surface": "cylinder",
|
||||
@@ -510,26 +577,31 @@ def main() -> int:
|
||||
"slot/half-hole feature",
|
||||
)
|
||||
|
||||
boss_keys = _spec_keys(
|
||||
{
|
||||
"surface": "cylinder",
|
||||
"feature_guess": "boss/outer-round candidate",
|
||||
"diameter": 6.0,
|
||||
"radius": 3.0,
|
||||
"angular_span": 6.283185307179586,
|
||||
"height_estimate": 5.0,
|
||||
"same_domain_height_estimate": 5.0,
|
||||
"area": 188.0,
|
||||
"area_center": (0.0, 0.0, 0.0),
|
||||
"bbox_center": (0.0, 0.0, 0.0),
|
||||
"axis": (0.0, 0.0, 1.0),
|
||||
"axis_point": (0.0, 0.0, 0.0),
|
||||
"axis_center": (0.0, 0.0, 5.0),
|
||||
"feature_start_end_face_ids": (1,),
|
||||
}
|
||||
)
|
||||
boss_info = {
|
||||
"surface": "cylinder",
|
||||
"feature_guess": "boss/outer-round candidate",
|
||||
"diameter": 6.0,
|
||||
"radius": 3.0,
|
||||
"angular_span": 6.283185307179586,
|
||||
"height_estimate": 5.0,
|
||||
"same_domain_height_estimate": 5.0,
|
||||
"area": 188.0,
|
||||
"area_center": (0.0, 0.0, 0.0),
|
||||
"bbox_center": (0.0, 0.0, 0.0),
|
||||
"axis": (0.0, 0.0, 1.0),
|
||||
"axis_point": (0.0, 0.0, 0.0),
|
||||
"axis_center": (0.0, 0.0, 5.0),
|
||||
"feature_start_end_face_ids": (1,),
|
||||
}
|
||||
boss_specs = _specs(boss_info)
|
||||
boss_keys = {str(spec.get("key", "")) for spec in boss_specs}
|
||||
_assert_no_generic_face_leak(boss_keys, "boss feature")
|
||||
_assert_contains(boss_keys, {"boss_diameter", "boss_radius", "boss_height"}, "boss feature")
|
||||
boss_height = _spec(boss_specs, "boss_height")
|
||||
if boss_height.get("scope_default") != "owning":
|
||||
raise SystemExit(f"boss height should default to owning-axis resize: {boss_height}")
|
||||
if boss_height.get("action") != "resize_cylindrical_height_owning_scale":
|
||||
raise SystemExit(f"boss height should use owning-axis action by default: {boss_height}")
|
||||
|
||||
fillet_keys = _spec_keys(
|
||||
{
|
||||
@@ -609,6 +681,28 @@ def main() -> int:
|
||||
if surface_spec.get("current_text") != "圆锥面 / 拔模面":
|
||||
raise SystemExit(f"cone surface should be displayed in user-facing Chinese, got {surface_spec}")
|
||||
|
||||
low_recognition_specs = _specs(
|
||||
{
|
||||
"surface": "sphere",
|
||||
"radius": 5.0,
|
||||
"diameter": 10.0,
|
||||
"area": 100.0,
|
||||
"area_center": (0.0, 0.0, 0.0),
|
||||
"bbox_center": (0.0, 0.0, 0.0),
|
||||
"center": (0.0, 0.0, 0.0),
|
||||
"recognition_score": 24,
|
||||
"recognition_confidence": "low",
|
||||
"recognition_risk": "medium",
|
||||
"recognition_blockers": "ambiguous analytic surface",
|
||||
}
|
||||
)
|
||||
for key in ("sphere_radius", "sphere_diameter"):
|
||||
spec = _spec(low_recognition_specs, key)
|
||||
if spec.get("enabled"):
|
||||
raise SystemExit(f"{key} should be read-only when analytic surface recognition is low: {spec}")
|
||||
if "ambiguous analytic surface" not in str(spec.get("disabled_tip") or ""):
|
||||
raise SystemExit(f"{key} disabled tip should explain the recognition blocker: {spec}")
|
||||
|
||||
_assert_target_change_detection()
|
||||
_assert_holed_plane_local_scopes_disabled()
|
||||
_assert_no_legacy_face_source_terms()
|
||||
|
||||
Reference in New Issue
Block a user