2026-08-05 15:08:16 +08:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import sys
|
|
|
|
|
import tempfile
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
from OCC.Core.BRepAdaptor import BRepAdaptor_Surface
|
|
|
|
|
from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_MakeFace
|
|
|
|
|
from OCC.Core.GeomAbs import GeomAbs_BSplineSurface
|
|
|
|
|
from OCC.Core.GeomAPI import GeomAPI_PointsToBSplineSurface
|
|
|
|
|
from OCC.Core.TColgp import TColgp_Array2OfPnt
|
|
|
|
|
from OCC.Core.gp import gp_Pnt
|
|
|
|
|
|
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
|
|
|
|
if str(PROJECT_ROOT) not in sys.path:
|
|
|
|
|
sys.path.insert(0, str(PROJECT_ROOT))
|
|
|
|
|
|
|
|
|
|
from step_editor.model import StepModel
|
|
|
|
|
from step_editor.step_io import _write_step
|
|
|
|
|
from step_editor.window_state import WindowStateMixin
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _PropertySpecProbe(WindowStateMixin):
|
|
|
|
|
def __init__(self, face_id: int, selected_kind: str = "face") -> None:
|
|
|
|
|
self.model = object()
|
|
|
|
|
self.operation_in_progress = False
|
|
|
|
|
self.scan_in_progress = False
|
|
|
|
|
self.load_in_progress = False
|
|
|
|
|
self.selected_face_id = face_id
|
|
|
|
|
self.selected_edge_id = None
|
|
|
|
|
self.selected_kind = selected_kind
|
|
|
|
|
self.selected_part_id = 1
|
|
|
|
|
self.selected_solid_id = -1
|
|
|
|
|
self.manual_bottom_face_id = None
|
|
|
|
|
self.manual_slot_pair_face_id = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _write_b_spline_patch(path: Path) -> None:
|
|
|
|
|
points = TColgp_Array2OfPnt(1, 4, 1, 4)
|
|
|
|
|
for row in range(1, 5):
|
|
|
|
|
x = float(row - 1) * 10.0
|
|
|
|
|
for column in range(1, 5):
|
|
|
|
|
y = float(column - 1) * 10.0
|
|
|
|
|
z = 1.2 * ((row - 2.5) ** 2 - (column - 2.5) ** 2)
|
|
|
|
|
points.SetValue(row, column, gp_Pnt(x, y, z))
|
|
|
|
|
surface = GeomAPI_PointsToBSplineSurface(points).Surface()
|
|
|
|
|
face = BRepBuilderAPI_MakeFace(surface, 1e-6).Face()
|
|
|
|
|
_write_step(face, path)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _freeform_face_id(model: StepModel) -> int:
|
|
|
|
|
for face_id, face in enumerate(model.faces):
|
|
|
|
|
surf = BRepAdaptor_Surface(face)
|
|
|
|
|
if surf.GetType() == GeomAbs_BSplineSurface:
|
|
|
|
|
return face_id
|
|
|
|
|
raise SystemExit("B-spline Face was not found")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _specs(info: dict[str, object], selected_kind: str = "face") -> list[dict[str, object]]:
|
|
|
|
|
probe = _PropertySpecProbe(int(info.get("face_id", 0)), selected_kind=selected_kind)
|
|
|
|
|
specs, _used = probe._editable_property_specs(info)
|
|
|
|
|
return specs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _property_rows(info: dict[str, object], selected_kind: str = "face") -> list[dict[str, object]]:
|
|
|
|
|
probe = _PropertySpecProbe(int(info.get("face_id", 0)), selected_kind=selected_kind)
|
|
|
|
|
return probe._property_editor_specs(info, info)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _assert_no_editable_actions(specs: list[dict[str, object]], label: str) -> None:
|
|
|
|
|
editable = [
|
|
|
|
|
str(spec.get("key"))
|
|
|
|
|
for spec in specs
|
|
|
|
|
if bool(spec.get("editable")) and bool(spec.get("enabled")) and bool(spec.get("action"))
|
|
|
|
|
]
|
|
|
|
|
if editable:
|
|
|
|
|
raise SystemExit(f"{label} should not expose editable actions for a freeform Face: {editable}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main() -> int:
|
|
|
|
|
with tempfile.TemporaryDirectory(prefix="geom_param_freeform_guard_") as temp_dir:
|
|
|
|
|
path = Path(temp_dir) / "freeform_patch.step"
|
|
|
|
|
_write_b_spline_patch(path)
|
|
|
|
|
model = StepModel.load(path)
|
|
|
|
|
face_id = _freeform_face_id(model)
|
|
|
|
|
|
|
|
|
|
quick_info = model.quick_face_info(face_id)
|
|
|
|
|
info = model.face_info(face_id)
|
|
|
|
|
for label, selected_info in (("quick", quick_info), ("full", info)):
|
|
|
|
|
if selected_info.get("surface") != "b-spline surface":
|
|
|
|
|
raise SystemExit(f"{label} info should identify a B-spline surface: {selected_info}")
|
|
|
|
|
if selected_info.get("freeform_face_status") != "blocked":
|
|
|
|
|
raise SystemExit(f"{label} info should block freeform Face editing: {selected_info}")
|
|
|
|
|
if selected_info.get("recognition_risk") != "blocked":
|
|
|
|
|
raise SystemExit(f"{label} recognition risk should be blocked: {selected_info}")
|
|
|
|
|
blockers = str(selected_info.get("freeform_face_blockers") or "")
|
|
|
|
|
if "自由曲面" not in blockers or "历史参数" not in blockers:
|
|
|
|
|
raise SystemExit(f"{label} blocker should explain the missing stable history parameter: {selected_info}")
|
|
|
|
|
|
|
|
|
|
editable_specs = _specs(info)
|
|
|
|
|
editable_keys = {str(spec.get("key")) for spec in editable_specs}
|
|
|
|
|
if "freeform_surface_edit_semantics" not in editable_keys:
|
|
|
|
|
raise SystemExit(f"freeform Face should expose a read-only edit semantics row: {editable_specs}")
|
|
|
|
|
forbidden = {
|
|
|
|
|
"area",
|
|
|
|
|
"local_face_width",
|
|
|
|
|
"local_face_height",
|
|
|
|
|
"face_center_position",
|
|
|
|
|
"face_target_normal_position",
|
|
|
|
|
"cone_reference_radius",
|
|
|
|
|
"sphere_radius",
|
|
|
|
|
"torus_major_radius",
|
|
|
|
|
}
|
|
|
|
|
leaked = sorted(forbidden & editable_keys)
|
|
|
|
|
if leaked:
|
|
|
|
|
raise SystemExit(f"freeform Face should not expose parametric edit rows: {leaked}")
|
|
|
|
|
_assert_no_editable_actions(editable_specs, "face mode editable specs")
|
|
|
|
|
|
|
|
|
|
feature_rows = _property_rows(info, selected_kind="feature")
|
|
|
|
|
feature_keys = {str(spec.get("key")) for spec in feature_rows}
|
|
|
|
|
if "no_editable_feature_dimensions" not in feature_keys:
|
|
|
|
|
raise SystemExit(f"feature mode should say there are no reliable dimensions: {feature_rows}")
|
2026-08-10 16:51:52 +08:00
|
|
|
if "freeform_surface_edit_semantics" in feature_keys:
|
|
|
|
|
raise SystemExit(
|
|
|
|
|
"feature parameter table should keep freeform limitations in diagnostics, "
|
|
|
|
|
f"not mixed into editable parameters: {feature_rows}"
|
|
|
|
|
)
|
2026-08-05 15:08:16 +08:00
|
|
|
_assert_no_editable_actions(feature_rows, "feature mode rows")
|
|
|
|
|
|
|
|
|
|
print(
|
|
|
|
|
"freeform Face guard ok: "
|
|
|
|
|
f"face_id={face_id}, surface={info.get('surface')}, "
|
|
|
|
|
f"risk={info.get('recognition_risk')}, specs={sorted(editable_keys)}"
|
|
|
|
|
)
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
raise SystemExit(main())
|