feat: 完善 Face 一级关系编辑和稳定性
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
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.ui_helpers import _smooth_surface_polydata
|
||||
from step_editor.window_state import WindowStateMixin
|
||||
|
||||
|
||||
MODEL_PATH = PROJECT_ROOT / "assets" / "models" / "geom_extract.step"
|
||||
SOURCE_FACE_ID = 398
|
||||
|
||||
|
||||
def main() -> int:
|
||||
model = StepModel.load(MODEL_PATH)
|
||||
root = model.feature_info(SOURCE_FACE_ID)
|
||||
if root.get("shell_region_status") == "candidate":
|
||||
raise AssertionError("Face 398 remote opposite plane was incorrectly exposed as wall thickness")
|
||||
|
||||
associated = model.associated_feature_infos(SOURCE_FACE_ID)
|
||||
by_type = {str(info.get("feature_type")): info for info in associated}
|
||||
for feature_type in ("凸台/外圆候选", "圆柱孔候选"):
|
||||
if feature_type not in by_type:
|
||||
raise AssertionError(f"missing associated {feature_type}: {tuple(by_type)}")
|
||||
if any(info.get("feature_type") == "规则矩形平面候选" for info in associated):
|
||||
raise AssertionError("unstable 2D-only rectangular candidates should not be associated")
|
||||
|
||||
boss = by_type["凸台/外圆候选"]
|
||||
hole = by_type["圆柱孔候选"]
|
||||
if int(boss.get("association_source_face_id", -1)) != 394:
|
||||
raise AssertionError(f"unexpected boss source: {boss.get('association_source_face_id')}")
|
||||
if int(hole.get("association_source_face_id", -1)) != 1591:
|
||||
raise AssertionError(f"unexpected hole source: {hole.get('association_source_face_id')}")
|
||||
if not bool(boss.get("is_full_cylinder")) or not bool(hole.get("is_full_cylinder")):
|
||||
raise AssertionError("split half-cylinder faces were not combined into full cylinders")
|
||||
|
||||
state = object.__new__(WindowStateMixin)
|
||||
state.model = model
|
||||
state.operation_in_progress = False
|
||||
state.scan_in_progress = False
|
||||
state.load_in_progress = False
|
||||
state.selected_face_id = SOURCE_FACE_ID
|
||||
state.selected_edge_id = None
|
||||
state.selected_kind = "feature"
|
||||
state.selected_part_id = int(root["part_id"])
|
||||
state.selected_solid_id = int(root["solid_id"])
|
||||
context = state._feature_context_info(SOURCE_FACE_ID)
|
||||
specs, _used = state._editable_property_specs(context)
|
||||
rows = state._feature_context_property_specs(specs, context)
|
||||
editable = {
|
||||
(str(row.get("label")), int(row.get("source_face_id", -1)), str(row.get("action")))
|
||||
for row in rows
|
||||
if row.get("parameter_role") == "dimension" and row.get("source_face_id") is not None
|
||||
}
|
||||
expected = {
|
||||
("凸台/外圆候选 · 直径", 394, "resize_boss"),
|
||||
("凸台/外圆候选 · 高度", 394, "resize_boss_height"),
|
||||
("圆柱孔候选 · 直径", 1591, "resize_hole"),
|
||||
("圆柱孔候选 · 盲孔/盲槽深度", 1591, "resize_hole_depth"),
|
||||
}
|
||||
if not expected.issubset(editable):
|
||||
raise AssertionError(f"missing associated editable rows: {expected - editable}")
|
||||
|
||||
display = _smooth_surface_polydata(model.build_face_polydata(deflection=0.035))
|
||||
face_ids = display.GetCellData().GetArray("face_id")
|
||||
normals = display.GetPointData().GetNormals()
|
||||
boss_cells = [
|
||||
index for index in range(display.GetNumberOfCells())
|
||||
if int(face_ids.GetTuple1(index)) == 394
|
||||
]
|
||||
if len(boss_cells) < 400:
|
||||
raise AssertionError(f"cylindrical display tessellation is too coarse: {len(boss_cells)}")
|
||||
plane_cells = [
|
||||
index for index in range(display.GetNumberOfCells())
|
||||
if int(face_ids.GetTuple1(index)) == SOURCE_FACE_ID
|
||||
]
|
||||
plane_points = sorted({
|
||||
display.GetCell(cell_id).GetPointId(point_index)
|
||||
for cell_id in plane_cells
|
||||
for point_index in range(display.GetCell(cell_id).GetNumberOfPoints())
|
||||
})
|
||||
plane_normals = [normals.GetTuple(point_id) for point_id in plane_points]
|
||||
reference = plane_normals[0]
|
||||
if min(sum(a * b for a, b in zip(reference, normal)) for normal in plane_normals) < 0.9999:
|
||||
raise AssertionError("planar B-Rep Face normals were polluted by adjacent faces")
|
||||
|
||||
print("associated feature and display quality ok")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user