feat: 增强一级共面同轴事实识别

This commit is contained in:
2026-08-10 18:05:18 +08:00
parent 3a851d0967
commit 3aadd6a34e
5 changed files with 329 additions and 2 deletions
+94
View File
@@ -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)