feat: 接入 SCDM 优先编辑闭环并支持阵列局部间距
接入 SCDM probe/edit/cache/校验链路,增强孔组、阵列、关系式和参数表交互。 支持阵列相邻段间距、移动意图切换、结果回滚校验,并补充对应回归脚本。
This commit is contained in:
@@ -49,6 +49,7 @@ from step_editor.window_state import (
|
||||
RELATION_FORMULA_OBJECT_COMPLETION_LIMIT,
|
||||
WindowStateMixin,
|
||||
)
|
||||
from step_editor.ui_helpers import _selection_mode_label, _selection_mode_value
|
||||
|
||||
|
||||
class _StatusBar:
|
||||
@@ -86,7 +87,23 @@ class _PropertyTableProbe(QWidget, WindowStateMixin):
|
||||
self.scdm_feature_cache = None
|
||||
self.scdm_feature_cache_state = "empty"
|
||||
self.scdm_feature_cache_message = ""
|
||||
self.scdm_edit_runner_ready = {"face.offset", "hole.diameter", "hole.position", "slot.position", "boss.position"}
|
||||
self.scdm_edit_runner_ready = {
|
||||
"face.offset",
|
||||
"hole.diameter",
|
||||
"hole.position",
|
||||
"feature.fill",
|
||||
"slot.width",
|
||||
"slot.depth",
|
||||
"slot.position",
|
||||
"boss.diameter",
|
||||
"boss.height",
|
||||
"boss.position",
|
||||
"round.radius",
|
||||
"chamfer.distance",
|
||||
"feature.delete_round_or_chamfer",
|
||||
"pattern.spacing",
|
||||
"pattern.segment_spacing",
|
||||
}
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
self.object_edit_box = self
|
||||
@@ -1560,6 +1577,65 @@ def _assert_scdm_selection_diagnostics() -> None:
|
||||
_assert("偏移" in str(info.get("scdm_selection_enabled_capabilities")), f"SCDM enabled capability diagnostic missing: {info}")
|
||||
|
||||
|
||||
def _assert_solid_selection_does_not_expand_face_scdm_specs() -> None:
|
||||
probe = _PropertyTableProbe()
|
||||
probe.scdm_feature_cache_state = "ready"
|
||||
probe.scdm_edit_runner_ready = {"face.offset"}
|
||||
probe.scdm_feature_cache = {
|
||||
"objects": [
|
||||
{
|
||||
"objectId": "face:0",
|
||||
"objectType": "face",
|
||||
"geometrySignature": {"faceIds": [0], "surfaceType": "plane", "planeOffset": 0.0},
|
||||
"capabilities": [
|
||||
{
|
||||
"key": "face.offset",
|
||||
"displayName": "偏移",
|
||||
"currentValue": 0.0,
|
||||
"valueKind": "number",
|
||||
"defaultIntent": "推拉平面",
|
||||
"backendOperation": "pull_face_offset",
|
||||
"postCheck": "target_face_offset",
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
"objectId": "face:1",
|
||||
"objectType": "face",
|
||||
"geometrySignature": {"faceIds": [1], "surfaceType": "plane", "planeOffset": -5.0},
|
||||
"capabilities": [
|
||||
{
|
||||
"key": "face.offset",
|
||||
"displayName": "偏移",
|
||||
"currentValue": -5.0,
|
||||
"valueKind": "number",
|
||||
"defaultIntent": "推拉平面",
|
||||
"backendOperation": "pull_face_offset",
|
||||
"postCheck": "target_face_offset",
|
||||
}
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
probe.selected_kind = "face"
|
||||
probe.selected_face_id = 0
|
||||
face_specs = probe._scdm_property_specs_for_selection()
|
||||
_assert(
|
||||
len([item for item in face_specs if item.get("scdm_capability_key") == "face.offset"]) == 1,
|
||||
f"Face selection should show the selected Face SCDM offset only: {face_specs}",
|
||||
)
|
||||
|
||||
probe.selected_kind = "solid"
|
||||
probe.selected_face_id = None
|
||||
probe.selected_solid_id = 0
|
||||
probe.model = SimpleNamespace(face_solid_ids=[0, 0])
|
||||
solid_specs = probe._scdm_property_specs_for_selection()
|
||||
_assert(
|
||||
solid_specs == [],
|
||||
f"Solid selection should not expand every child Face SCDM offset into the parameter table: {solid_specs}",
|
||||
)
|
||||
|
||||
|
||||
def _assert_operation_record_backend_sources() -> None:
|
||||
class _OperationRecordProbe(WindowActionMixin):
|
||||
pass
|
||||
@@ -1718,6 +1794,10 @@ def _assert_large_model_preload_stays_lightweight() -> None:
|
||||
_assert("scdm_local_face_signatures" in window_body, "window SCDM preload should use the lightweight model signature API")
|
||||
_assert("quick_face_info" not in window_body, "window SCDM preload must not call quick_face_info for every Face")
|
||||
loaded_body = _function_text(PROJECT_ROOT / "step_editor/window_core.py", "_apply_loaded_model_result")
|
||||
_assert(
|
||||
"_restore_scdm_feature_cache_from_disk()" in loaded_body,
|
||||
"STEP load should restore a matching SCDM disk cache before launching a new probe",
|
||||
)
|
||||
_assert(
|
||||
"_defer_large_model_recognition_preloads" in loaded_body,
|
||||
"large model loads should defer full external recognition preloads by default",
|
||||
@@ -1727,6 +1807,10 @@ def _assert_large_model_preload_stays_lightweight() -> None:
|
||||
"SCDM edit-result reloads should still be able to force cache refresh for validation",
|
||||
)
|
||||
preload_body = _function_text(PROJECT_ROOT / "step_editor/window_core.py", "_start_scdm_probe_preload")
|
||||
_assert(
|
||||
"_current_scdm_feature_cache_matches_loaded_step()" in preload_body,
|
||||
"SCDM preload should skip relaunching SpaceClaim when a current cache is already installed",
|
||||
)
|
||||
_assert(
|
||||
"local_face_signatures = self._scdm_local_face_signatures()" not in preload_body,
|
||||
"SCDM preload must not build all local Face signatures on the UI thread",
|
||||
@@ -1866,6 +1950,53 @@ def _assert_large_planar_offset_prefers_local_backend() -> None:
|
||||
_assert("SCDM" in str(probe.scdm_selection_status_message), "backend preference should explain that SCDM was bypassed")
|
||||
|
||||
|
||||
def _assert_scdm_target_values_use_backend_units() -> None:
|
||||
probe = _PropertyTableProbe()
|
||||
offset = probe._scdm_property_target_value(
|
||||
{"scdm_capability_key": "face.offset", "value_type": "number", "scdm_unit_scale": 0.001},
|
||||
"2.5",
|
||||
)
|
||||
_assert(abs(float(offset) - 0.0025) <= 1.0e-12, f"SCDM numeric targets should be converted back to backend units: {offset}")
|
||||
position = probe._scdm_property_target_value(
|
||||
{"scdm_capability_key": "hole.position", "value_type": "vector3", "scdm_unit_scale": 0.001},
|
||||
"(1, 2, 3)",
|
||||
)
|
||||
_assert(position == [0.001, 0.002, 0.003], f"SCDM vector targets should be converted back to backend units: {position}")
|
||||
spacing_spec = {"label": "阵列间距", "value_type": "positive", "max_value": 1.3571428571428572}
|
||||
_assert(
|
||||
not probe._property_target_validation_error(spacing_spec, "1.1"),
|
||||
"pattern spacing targets within the support face range should be accepted",
|
||||
)
|
||||
spacing_error = probe._property_target_validation_error(spacing_spec, "2")
|
||||
_assert(
|
||||
"阵列间距" in spacing_error and "1.35714" in spacing_error,
|
||||
f"pattern spacing beyond the support face range should be blocked in the UI: {spacing_error}",
|
||||
)
|
||||
|
||||
|
||||
def _assert_selection_mode_labels_are_english() -> None:
|
||||
expected = {
|
||||
"Feature": "Feature",
|
||||
"Face": "Face",
|
||||
"Edge": "Edge",
|
||||
"Solid": "Solid",
|
||||
"Part": "Part",
|
||||
}
|
||||
for value, label in expected.items():
|
||||
_assert(_selection_mode_label(value) == label, f"selection mode {value} should display as English: {_selection_mode_label(value)}")
|
||||
_assert(_selection_mode_value(label) == value, f"selection mode label {label} should resolve to {value}")
|
||||
for legacy, value in {
|
||||
"智能特征": "Feature",
|
||||
"特征": "Feature",
|
||||
"面": "Face",
|
||||
"边": "Edge",
|
||||
"实体": "Solid",
|
||||
"零件": "Part",
|
||||
"装配零件": "Part",
|
||||
}.items():
|
||||
_assert(_selection_mode_value(legacy) == value, f"legacy selection label {legacy} should resolve to {value}")
|
||||
|
||||
|
||||
def _assert_background_load_uses_worker() -> None:
|
||||
body = _function_text(PROJECT_ROOT / "step_editor/window_core.py", "_load_step_background_or_sync")
|
||||
_assert("LoadWorker(action)" in body, "background STEP load should run through LoadWorker")
|
||||
@@ -1891,6 +2022,8 @@ def main() -> int:
|
||||
not top_level_label_probe.shown_labels,
|
||||
f"property labels were shown as transient top-level windows: {top_level_label_probe.shown_labels}",
|
||||
)
|
||||
_assert_scdm_target_values_use_backend_units()
|
||||
_assert_selection_mode_labels_are_english()
|
||||
_assert_property_table_editor(probe)
|
||||
_assert(probe.current_capability_button.text() == "软件进度", "software progress should be a compact button")
|
||||
_assert("当前支持" in probe.current_capability_button.toolTip(), "software progress button tooltip did not show supported areas")
|
||||
@@ -1952,6 +2085,7 @@ def main() -> int:
|
||||
_assert_relation_formula_ids_follow_model_remap()
|
||||
_assert_mouse_selection_guards()
|
||||
_assert_scdm_selection_diagnostics()
|
||||
_assert_solid_selection_does_not_expand_face_scdm_specs()
|
||||
_assert_operation_record_backend_sources()
|
||||
_assert_scdm_auto_prompt()
|
||||
_assert_property_ui_reroute_guards()
|
||||
|
||||
Reference in New Issue
Block a user