feat: 完善 STEP 一级参数化编辑识别与关系式建模
This commit is contained in:
@@ -32,6 +32,10 @@ class _SelectionHarness(WindowCoreMixin, WindowStateMixin):
|
||||
self.selected_solid_id = None
|
||||
self.manual_bottom_face_id = None
|
||||
self.manual_slot_pair_face_id = None
|
||||
self.multi_selected_feature_face_ids = []
|
||||
self.multi_selected_hole_entries = []
|
||||
self.multi_selection_active = False
|
||||
self.current_info_values = {}
|
||||
|
||||
def _current_feature_detection_level(self) -> str:
|
||||
return str(self.feature_detection_level)
|
||||
@@ -76,6 +80,38 @@ def _isolated_hole_resize(model: StepModel, face_id: int, diameter: float, root:
|
||||
return str(response.get("message") or "")
|
||||
|
||||
|
||||
def _isolated_hole_axis_move(
|
||||
model: StepModel,
|
||||
face_id: int,
|
||||
target_center: tuple[float, float, float],
|
||||
root: Path,
|
||||
) -> str:
|
||||
input_path = root / "face85_axis_input.brep"
|
||||
output_path = root / "face85_axis_output.brep"
|
||||
request_path = root / "face85_axis_request.json"
|
||||
model.export_internal_brep(input_path)
|
||||
request_path.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"input_path": str(input_path),
|
||||
"output_path": str(output_path),
|
||||
"input_format": "brep",
|
||||
"output_format": "brep",
|
||||
"operation": "move_cylindrical_hole_axis",
|
||||
"args": [face_id, list(target_center)],
|
||||
},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
code = run_request(request_path)
|
||||
response = json.loads(request_path.with_suffix(".response.json").read_text(encoding="utf-8"))
|
||||
_assert(code == 0 and response.get("ok") is True, f"isolated Face {face_id} axis move should pass: {response}")
|
||||
_assert(output_path.exists(), f"isolated Face {face_id} axis move should write output BREP")
|
||||
return str(response.get("message") or "")
|
||||
|
||||
|
||||
def _edge_ids_from_polydata(polydata) -> set[int]:
|
||||
edge_arr = polydata.GetCellData().GetArray("edge_id") if polydata is not None else None
|
||||
if edge_arr is None:
|
||||
@@ -137,6 +173,98 @@ def _assert_rectangular_face_parameters(model: StepModel, harness: _SelectionHar
|
||||
_assert(not missing, f"ICEPAK Face 9 feature parameters should expose length, width and offset: {missing}")
|
||||
|
||||
|
||||
def _assert_face85_hole_axis_move(model: StepModel, harness: _SelectionHarness) -> tuple[float, float, float]:
|
||||
face_id = 85
|
||||
info = model.quick_face_info(face_id)
|
||||
_assert(info.get("surface") == "cylinder", "ICEPAK Face 85 should be cylindrical")
|
||||
_assert(
|
||||
list(info.get("feature_highlight_face_ids") or []) == [85, 96],
|
||||
f"ICEPAK Face 85 should highlight the complete split hole region: {info.get('feature_highlight_face_ids')}",
|
||||
)
|
||||
|
||||
harness.select_feature_face(face_id)
|
||||
feature = model.feature_info(face_id)
|
||||
specs = harness._property_editor_specs(feature, feature)
|
||||
editable_keys = {str(spec.get("key") or "") for spec in specs if bool(spec.get("editable"))}
|
||||
_assert("hole_axis_center" in editable_keys, "ICEPAK Face 85 should expose the hole axis-center move parameter")
|
||||
|
||||
current_center = info.get("axis_center") or feature.get("axis_center")
|
||||
if not (isinstance(current_center, (list, tuple)) and len(current_center) == 3):
|
||||
axis_point = feature.get("axis_point") or info.get("axis_point")
|
||||
axis_direction = feature.get("axis") or info.get("axis")
|
||||
axis_range = feature.get("same_domain_v_range") or info.get("same_domain_v_range") or feature.get("v_range")
|
||||
if (
|
||||
isinstance(axis_point, (list, tuple))
|
||||
and len(axis_point) == 3
|
||||
and isinstance(axis_direction, (list, tuple))
|
||||
and len(axis_direction) == 3
|
||||
and isinstance(axis_range, (list, tuple))
|
||||
and len(axis_range) >= 2
|
||||
):
|
||||
axis_mid = (float(axis_range[0]) + float(axis_range[1])) * 0.5
|
||||
current_center = tuple(float(axis_point[index]) + float(axis_direction[index]) * axis_mid for index in range(3))
|
||||
_assert(isinstance(current_center, (list, tuple)) and len(current_center) == 3, "Face 85 should have an axis center")
|
||||
near_target_center = (float(current_center[0]) + 0.1, float(current_center[1]), float(current_center[2]))
|
||||
plan = model.cylindrical_axis_move_plan(face_id, near_target_center)
|
||||
_assert(plan.get("status") != "blocked", f"Face 85 axis move should not be blocked as a half cylinder: {plan}")
|
||||
_assert(list(plan.get("same_domain_face_ids") or []) == [85, 96], "Face 85 axis move should use both side fragments")
|
||||
_assert(float(plan.get("angular_span") or 0.0) > 6.0, "Face 85 axis move should use full same-domain span")
|
||||
_assert(
|
||||
abs(float(plan.get("selected_angular_span") or 0.0) - 3.141592653589793) <= 1e-6,
|
||||
"Face 85 selected fragment span should still be recorded separately",
|
||||
)
|
||||
target_center = (float(current_center[0]), float(current_center[1]), 6.0)
|
||||
high_risk_plan = model.cylindrical_axis_move_plan(face_id, target_center)
|
||||
_assert(
|
||||
high_risk_plan.get("status") != "blocked",
|
||||
f"Face 85 axis move to Z=6 should be high-risk but still plannable: {high_risk_plan}",
|
||||
)
|
||||
_assert(high_risk_plan.get("risk") == "high", f"Face 85 far axis move should be classified high risk: {high_risk_plan}")
|
||||
_assert(
|
||||
bool(high_risk_plan.get("supports_isolation")),
|
||||
f"Face 85 high-risk axis move should be routed to isolated execution: {high_risk_plan}",
|
||||
)
|
||||
_assert(
|
||||
tuple(round(float(item), 7) for item in high_risk_plan.get("target_axis_center", ())) == (0.5, 1.0, 6.0),
|
||||
f"Face 85 high-risk axis move target should match the requested location: {high_risk_plan}",
|
||||
)
|
||||
return target_center
|
||||
|
||||
|
||||
def _assert_face85_face87_multi_hole_specs(model: StepModel, harness: _SelectionHarness) -> None:
|
||||
entry85 = harness._hole_multi_select_entry(85)
|
||||
entry87 = harness._hole_multi_select_entry(87)
|
||||
_assert(entry85 is not None, "Face 85 should be eligible for multi-hole selection")
|
||||
_assert(entry87 is not None, "Face 87 should be eligible for multi-hole selection")
|
||||
_assert(entry85["logical_id"] != entry87["logical_id"], "Face 85 and Face 87 should be separate hole groups")
|
||||
|
||||
harness.multi_selected_hole_entries = [entry85, entry87]
|
||||
harness.multi_selected_feature_face_ids = [85, 87]
|
||||
harness.multi_selection_active = True
|
||||
harness.selected_kind = "multi_feature"
|
||||
harness.selected_face_id = 87
|
||||
info = harness._selected_action_info()
|
||||
_assert(info.get("multi_selection_kind") == "holes", f"multi selection info should describe holes: {info}")
|
||||
_assert(int(info.get("multi_selected_count") or 0) == 2, f"multi selection should include two holes: {info}")
|
||||
_assert(
|
||||
set(info.get("feature_highlight_face_ids") or ()) == {85, 96, 87, 94},
|
||||
f"multi-hole highlight should include both split-cylinder regions: {info.get('feature_highlight_face_ids')}",
|
||||
)
|
||||
|
||||
specs = harness._property_editor_specs(info, info)
|
||||
keys = [str(spec.get("key") or "") for spec in specs]
|
||||
_assert(
|
||||
keys == ["multi_hole_diameter", "multi_hole_radius", "multi_hole_position_delta", "multi_hole_suppress"],
|
||||
f"multi-hole specs should expose batch hole dimensions and commands: {keys}",
|
||||
)
|
||||
actions = {str(spec.get("action") or "") for spec in specs}
|
||||
_assert(
|
||||
actions
|
||||
== {"resize_multi_selected_holes", "move_multi_selected_holes_by_offset", "suppress_multi_selected_holes"},
|
||||
f"multi-hole specs should expose batch edit actions: {actions}",
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if not MODEL_PATH.exists():
|
||||
print("icepak cylindrical region selection skipped: local ICEPAK-NATURAL.stp is not present")
|
||||
@@ -146,7 +274,11 @@ def main() -> int:
|
||||
_assert_default_edges_hide_same_domain_internal_edges(model, "ICEPAK before edit")
|
||||
harness = _SelectionHarness(model)
|
||||
_assert_rectangular_face_parameters(model, harness)
|
||||
_assert_face85_hole_axis_move(model, harness)
|
||||
_assert_face85_face87_multi_hole_specs(model, harness)
|
||||
expected_regions = {
|
||||
85: [85, 96],
|
||||
96: [85, 96],
|
||||
87: [87, 94],
|
||||
94: [87, 94],
|
||||
84: [84, 97],
|
||||
@@ -176,6 +308,15 @@ def main() -> int:
|
||||
)
|
||||
_assert(bool(feature.get("is_full_cylinder")), f"Face {face_id} should be treated as a full cylinder")
|
||||
|
||||
axis_move_model = StepModel.load(MODEL_PATH)
|
||||
axis_move_target = _assert_face85_hole_axis_move(axis_move_model, _SelectionHarness(axis_move_model))
|
||||
axis_move_result = axis_move_model.move_cylindrical_hole_axis(85, axis_move_target)
|
||||
_assert(
|
||||
"Cylindrical hole axis move completed" in axis_move_result,
|
||||
f"Face 85 axis move should complete: {axis_move_result}",
|
||||
)
|
||||
_assert_default_edges_hide_same_domain_internal_edges(axis_move_model, "ICEPAK after Face 85 axis move")
|
||||
|
||||
resize_model = StepModel.load(MODEL_PATH)
|
||||
plan = resize_model.cylindrical_resize_plan(87, 0.3)
|
||||
_assert(plan.get("feature_type") == "圆柱孔候选", "Face 87 should plan as a cylindrical hole")
|
||||
@@ -191,6 +332,10 @@ def main() -> int:
|
||||
worker_message = _isolated_hole_resize(model, 87, 0.3, Path(temp_dir))
|
||||
_assert("diameter 0.5 -> 0.3" in worker_message, "Face 87 isolated diameter shrink should complete")
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="icepak_face85_axis_isolated_") as temp_dir:
|
||||
worker_message = _isolated_hole_axis_move(model, 85, axis_move_target, Path(temp_dir))
|
||||
_assert("Cylindrical hole axis move completed" in worker_message, "Face 85 isolated axis move should complete")
|
||||
|
||||
print("icepak cylindrical region selection ok")
|
||||
return 0
|
||||
|
||||
|
||||
Reference in New Issue
Block a user