feat: 完善 STEP 一级参数化编辑识别与关系式建模
This commit is contained in:
@@ -74,6 +74,13 @@ def _unit_triple_or_none(value: object) -> tuple[float, float, float] | None:
|
||||
return (triple[0] / length, triple[1] / length, triple[2] / length)
|
||||
|
||||
|
||||
def _int_or_none(value: object) -> int | None:
|
||||
try:
|
||||
return int(value)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def _compact_plan_value(value: object) -> str:
|
||||
text = _format_value(value)
|
||||
return text if len(text) <= 120 else text[:117] + "..."
|
||||
@@ -1592,8 +1599,9 @@ class WindowActionMixin:
|
||||
if plan.get("status") == "blocked":
|
||||
self._show_blocked_plan_message(title, plan, blocked_status)
|
||||
return False
|
||||
supports_isolation = bool(plan.get("supports_isolation")) or self._quick_edit_title_supports_isolation(title)
|
||||
if (
|
||||
not self._quick_edit_title_supports_isolation(title)
|
||||
not supports_isolation
|
||||
and self._block_unisolated_high_risk_operation(f"已阻止高风险{title}", plan)
|
||||
):
|
||||
return False
|
||||
@@ -1620,7 +1628,7 @@ class WindowActionMixin:
|
||||
+ f"\n{plan.get('message', '')}\n\n"
|
||||
+ (
|
||||
"本次会在隔离子进程里执行高风险 OCC 计算;如果子进程卡死或崩溃,主程序和原模型会保持不变。\n\n"
|
||||
if str(plan.get("risk")) == "high" and self._quick_edit_title_supports_isolation(title)
|
||||
if str(plan.get("risk")) == "high" and supports_isolation
|
||||
else ""
|
||||
)
|
||||
+ "为避免复杂 STEP 在界面线程卡死,本次不会先生成红/绿预览;"
|
||||
@@ -1687,6 +1695,10 @@ class WindowActionMixin:
|
||||
"resize_sphere_radius",
|
||||
"resize_torus_radius",
|
||||
"resize_cylindrical_hole",
|
||||
"edit_cylindrical_holes_by_refs",
|
||||
"resize_cylindrical_holes_by_refs",
|
||||
"move_cylindrical_holes_by_offset",
|
||||
"suppress_cylindrical_holes_by_refs",
|
||||
"resize_cylindrical_owning_scale",
|
||||
"move_cylindrical_hole_axis",
|
||||
"suppress_cylindrical_hole",
|
||||
@@ -2351,6 +2363,200 @@ class WindowActionMixin:
|
||||
isolation=isolation,
|
||||
)
|
||||
|
||||
def _multi_selected_hole_refs(self) -> list[dict[str, object]]:
|
||||
refs: list[dict[str, object]] = []
|
||||
for item in getattr(self, "multi_selected_hole_entries", []) or []:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
center = _triple_or_none(item.get("axis_center"))
|
||||
diameter = _float_or_none(item.get("diameter"))
|
||||
face_id = _int_or_none(item.get("face_id"))
|
||||
logical_id = _int_or_none(item.get("logical_id"))
|
||||
if center is None or diameter is None or diameter <= 0:
|
||||
continue
|
||||
refs.append(
|
||||
{
|
||||
"face_id": face_id,
|
||||
"logical_id": logical_id,
|
||||
"diameter": float(diameter),
|
||||
"axis_center": [float(center[0]), float(center[1]), float(center[2])],
|
||||
"part_id": item.get("part_id"),
|
||||
"solid_id": item.get("solid_id"),
|
||||
}
|
||||
)
|
||||
return refs
|
||||
|
||||
def _run_multi_selected_hole_edit(
|
||||
self,
|
||||
*,
|
||||
target_diameter: float | None = None,
|
||||
offset: tuple[float, float, float] | None = None,
|
||||
) -> None:
|
||||
if self.model is None:
|
||||
return
|
||||
if self._edit_busy("请等待当前编辑完成后再批量修改孔。"):
|
||||
return
|
||||
refs = self._multi_selected_hole_refs()
|
||||
if len(refs) < 2:
|
||||
QMessageBox.information(self, "不能修改", "请先按 Ctrl 选择至少两个完整圆柱孔。")
|
||||
return
|
||||
if target_diameter is None and offset is None:
|
||||
QMessageBox.information(self, "不能修改", "请先输入孔径目标值或位置偏移量。")
|
||||
return
|
||||
if target_diameter is not None and target_diameter <= 0:
|
||||
QMessageBox.information(self, "不能修改", "孔径必须大于 0。")
|
||||
return
|
||||
if offset is not None and _vector_length(offset) <= 1e-9:
|
||||
offset = None
|
||||
if target_diameter is None and offset is None:
|
||||
QMessageBox.information(self, "不能修改", "位置偏移量为 0,不需要修改。")
|
||||
return
|
||||
|
||||
operation_label_parts: list[str] = []
|
||||
if target_diameter is not None:
|
||||
operation_label_parts.append("改孔径")
|
||||
if offset is not None:
|
||||
operation_label_parts.append("移动位置")
|
||||
operation_name = "批量孔" + " + ".join(operation_label_parts)
|
||||
logical_ids = [item.get("logical_id") for item in refs if item.get("logical_id") is not None]
|
||||
refs_arg = [dict(item) for item in refs]
|
||||
offset_arg = list(offset) if offset is not None else None
|
||||
isolation = {
|
||||
"operation": "edit_cylindrical_holes_by_refs",
|
||||
"args": [refs_arg, target_diameter, offset_arg],
|
||||
"timeout_seconds": 300.0,
|
||||
"reason": "multi-hole-isolated-occ-edit",
|
||||
}
|
||||
|
||||
self.clear_edit_preview(render=False)
|
||||
|
||||
def action():
|
||||
return self.model.edit_cylindrical_holes_by_refs(
|
||||
refs_arg,
|
||||
target_diameter=target_diameter,
|
||||
offset=offset,
|
||||
)
|
||||
|
||||
self._run_edit_action(
|
||||
action,
|
||||
operation_name=operation_name,
|
||||
target=f"{len(refs)} holes",
|
||||
parameters={
|
||||
"surface": "cylinder",
|
||||
"feature_type": "multi cylindrical hole",
|
||||
"feature_guess": "hole/groove candidate",
|
||||
"multi_selected_count": len(refs),
|
||||
"multi_selected_logical_ids": tuple(logical_ids),
|
||||
"target_diameter": target_diameter,
|
||||
"axis_move_vector": offset,
|
||||
"resize_strategy": "multi-hole-fill-and-recut",
|
||||
"edit_strategy_label": "批量圆柱孔参数化",
|
||||
"edit_semantics": "按当前多选孔引用逐个重新定位孔组,统一修改孔径或按相同偏移移动位置;失败时整体回滚。",
|
||||
"multi_hole_status": "ready",
|
||||
"multi_hole_risk": "medium",
|
||||
"quick_preflight": True,
|
||||
"ui_preview": "skipped-to-avoid-ui-freeze",
|
||||
},
|
||||
target_kind=None,
|
||||
target_id=None,
|
||||
isolation=isolation,
|
||||
)
|
||||
|
||||
def resize_multi_selected_holes(self) -> None:
|
||||
try:
|
||||
target_diameter = float(self.hole_diameter_input.text())
|
||||
except (AttributeError, ValueError):
|
||||
QMessageBox.information(self, "不能修改", "请输入数字形式的目标孔径。")
|
||||
return
|
||||
self._run_multi_selected_hole_edit(target_diameter=target_diameter)
|
||||
|
||||
def move_multi_selected_holes_by_offset(self) -> None:
|
||||
try:
|
||||
offset = (
|
||||
float(self.translate_x_input.text()),
|
||||
float(self.translate_y_input.text()),
|
||||
float(self.translate_z_input.text()),
|
||||
)
|
||||
except (AttributeError, ValueError):
|
||||
QMessageBox.information(self, "不能修改", "请输入 X/Y/Z 三个数字形式的位置偏移量。")
|
||||
return
|
||||
self._run_multi_selected_hole_edit(offset=offset)
|
||||
|
||||
def suppress_multi_selected_holes(self) -> None:
|
||||
if self.model is None:
|
||||
return
|
||||
if self._edit_busy("请等待当前编辑完成后再批量封堵孔。"):
|
||||
return
|
||||
refs = self._multi_selected_hole_refs()
|
||||
if len(refs) < 2:
|
||||
QMessageBox.information(self, "不能修改", "请先按 Ctrl 选择至少两个完整圆柱孔。")
|
||||
return
|
||||
|
||||
refs_arg = [dict(item) for item in refs]
|
||||
logical_ids = [item.get("logical_id") for item in refs if item.get("logical_id") is not None]
|
||||
isolation = {
|
||||
"operation": "suppress_cylindrical_holes_by_refs",
|
||||
"args": [refs_arg],
|
||||
"timeout_seconds": 300.0,
|
||||
"reason": "multi-hole-suppress-isolated-occ-edit",
|
||||
}
|
||||
self.clear_edit_preview(render=False)
|
||||
|
||||
def action():
|
||||
return self.model.suppress_cylindrical_holes_by_refs(refs_arg)
|
||||
|
||||
self._run_edit_action(
|
||||
action,
|
||||
operation_name="批量封堵孔",
|
||||
target=f"{len(refs)} holes",
|
||||
parameters={
|
||||
"surface": "cylinder",
|
||||
"feature_type": "multi cylindrical hole",
|
||||
"feature_guess": "hole/groove candidate",
|
||||
"multi_selected_count": len(refs),
|
||||
"multi_selected_logical_ids": tuple(logical_ids),
|
||||
"suppress_strategy": "multi-hole-fill",
|
||||
"edit_strategy_label": "批量圆柱孔封堵",
|
||||
"edit_semantics": "按当前多选孔引用逐个封堵;任意孔失败时整次批量操作会回滚。",
|
||||
"multi_hole_status": "ready",
|
||||
"multi_hole_risk": "medium",
|
||||
"quick_preflight": True,
|
||||
"ui_preview": "skipped-to-avoid-ui-freeze",
|
||||
},
|
||||
target_kind=None,
|
||||
target_id=None,
|
||||
isolation=isolation,
|
||||
)
|
||||
|
||||
def apply_multi_selected_hole_property_edit(self, changed: list[tuple[int, dict[str, object], str]]) -> None:
|
||||
target_diameter: float | None = None
|
||||
offset: tuple[float, float, float] | None = None
|
||||
for _row, spec, text in changed:
|
||||
validation_error = self._property_target_validation_error(spec, text)
|
||||
if validation_error:
|
||||
QMessageBox.information(self, "目标值无效", validation_error)
|
||||
return
|
||||
key = str(spec.get("key") or "")
|
||||
try:
|
||||
if key == "multi_hole_diameter":
|
||||
diameter_value = float(text)
|
||||
if target_diameter is not None and abs(target_diameter - diameter_value) > 1e-9:
|
||||
QMessageBox.information(self, "目标值无效", "孔径和半径换算后的目标孔径不一致,请只修改其中一个。")
|
||||
return
|
||||
target_diameter = diameter_value
|
||||
elif key == "multi_hole_radius":
|
||||
diameter_value = float(text) * 2.0
|
||||
if target_diameter is not None and abs(target_diameter - diameter_value) > 1e-9:
|
||||
QMessageBox.information(self, "目标值无效", "孔径和半径换算后的目标孔径不一致,请只修改其中一个。")
|
||||
return
|
||||
target_diameter = diameter_value
|
||||
elif key == "multi_hole_position_delta":
|
||||
offset = self._parse_property_vector3(text)
|
||||
except ValueError as exc:
|
||||
QMessageBox.information(self, "目标值无效", str(exc))
|
||||
return
|
||||
self._run_multi_selected_hole_edit(target_diameter=target_diameter, offset=offset)
|
||||
|
||||
def move_cylindrical_slot_axis(self) -> None:
|
||||
if self.model is None:
|
||||
return
|
||||
@@ -8979,6 +9185,11 @@ class WindowActionMixin:
|
||||
if isinstance(timings, dict):
|
||||
timings["finish_ui"] = time.perf_counter() - finish_started
|
||||
locator_note = self._locate_operation_record(record)
|
||||
relation_note = ""
|
||||
if hasattr(self, "_refresh_relation_formulas_after_model_edit"):
|
||||
relation_note = self._refresh_relation_formulas_after_model_edit()
|
||||
if relation_note:
|
||||
locator_note = f"{locator_note}\n{relation_note}" if locator_note else relation_note
|
||||
except Exception as exc:
|
||||
rollback_message = self._restore_failed_edit_snapshot(result.get("snapshot") if isinstance(result, dict) else None)
|
||||
self._end_edit_task(clear_preview=True)
|
||||
|
||||
Reference in New Issue
Block a user