feat: 完善 STEP/B-Rep 一级关系参数化编辑

This commit is contained in:
2026-08-07 18:08:32 +08:00
parent eef9efcc1e
commit 12250603dd
26 changed files with 4254 additions and 1270 deletions
+432 -120
View File
@@ -77,6 +77,13 @@ def _compact_plan_value(value: object) -> str:
return text if len(text) <= 120 else text[:117] + "..."
def _compact_user_message(value: object, limit: int = 360) -> str:
text = " ".join(str(value or "").replace("\r", "\n").split())
if len(text) <= limit:
return text
return f"{text[: max(0, limit - 1)].rstrip()}..."
class WindowActionMixin:
def export_all(self) -> None:
if self.model is None:
@@ -356,8 +363,7 @@ class WindowActionMixin:
plan = self._push_pull_plan_for_action(face_id, distance)
if plan["status"] == "blocked":
QMessageBox.information(self, "不能拉伸/切除平面", str(plan["message"]))
self.statusBar().showMessage("拉伸/切除平面已阻止")
self._show_blocked_plan_message("拉伸/切除平面", plan, "拉伸/切除平面已阻止")
return
if plan["risk"] != "low":
warnings = str(plan.get("warnings", ""))
@@ -742,8 +748,7 @@ class WindowActionMixin:
if plan is None:
plan = self.model.shell_thickness_plan(face_id, target_thickness)
if plan["status"] == "blocked":
QMessageBox.information(self, "不能调整壳体厚度", str(plan["message"]))
self.statusBar().showMessage("壳体厚度调整已阻止")
self._show_blocked_plan_message("调整壳体厚度", plan, "壳体厚度调整已阻止")
return
if plan["risk"] != "low":
@@ -1073,7 +1078,7 @@ class WindowActionMixin:
delta = target_size - current_size if current_size is not None else None
ratio = abs(delta) / current_size if current_size is not None and current_size > 1e-9 else None
scale = target_size / current_size if current_size is not None and current_size > 1e-9 else None
axis_label = "V向尺寸" if axis_key == "height" else "U向尺寸"
axis_label = "面内宽度" if axis_key == "height" else "面内长度"
return self._quick_blocked_local_face_plan(
face_id,
blocker,
@@ -1153,7 +1158,7 @@ class WindowActionMixin:
face_id,
blocker,
resize_strategy="local-face-plane-offset-deform",
edit_strategy_label="偏移变换(局部重建)",
edit_strategy_label="偏移(局部重建)",
edit_semantics="按当前面垂直方向移动 Face 边界顶点并重建一级相邻面;复杂内孔/多边界 Face 当前不放行。",
info=info,
extra={
@@ -1377,8 +1382,7 @@ class WindowActionMixin:
cancelled_status: str,
) -> bool:
if plan.get("status") == "blocked":
QMessageBox.information(self, f"不能{title}", str(plan.get("message", "")))
self.statusBar().showMessage(blocked_status)
self._show_blocked_plan_message(title, plan, blocked_status)
return False
if (
not self._quick_edit_title_supports_isolation(title)
@@ -1428,11 +1432,11 @@ class WindowActionMixin:
"壳体厚度(缩放特征)缩放所属对象",
"面积(局部重建)",
"面面积(缩放特征)",
"U向尺寸(局部重建)",
"V向尺寸(局部重建)",
"U向尺寸(缩放特征)",
"V向尺寸(缩放特征)",
"偏移变换(局部重建)",
"面内长度(局部重建)",
"面内宽度(局部重建)",
"面内长度(缩放特征)",
"面内宽度(缩放特征)",
"偏移(局部重建)",
"中心(局部重建)",
"圆柱高度调整",
"高度(缩放特征)缩放所属对象",
@@ -1601,6 +1605,280 @@ class WindowActionMixin:
return ""
return "\n\n诊断信息:\n" + "\n".join(f"- {line}" for line in lines)
def _edit_blocker_type(self, reason: str) -> tuple[str, str]:
text = str(reason or "")
unsupported_markers = (
"暂不支持",
"当前版本",
"还没有实现",
"未实现",
"不支持",
"受限能力",
"复杂链式",
"二级",
"三级",
"原 CAD",
"deferred",
"unsupported",
"not implemented",
)
risk_markers = (
"风险过高",
"风险太高",
"高风险",
"危险",
"长时间卡死",
"卡死",
"超时",
"timeout",
"too risky",
"high risk",
)
recognition_markers = (
"识别不足",
"识别不到",
"没有识别",
"未识别",
"识别不稳",
"不稳定",
"不明确",
"无法确认",
"不能确认",
"缺少稳定",
"置信度",
"not recognized",
"not recognise",
"ambiguous",
"unclear",
"unstable",
)
illegal_markers = (
"无效",
"非法",
"必须",
"不能",
"不存在",
"相同",
"超过",
"小于",
"大于",
"切空",
"退化",
"invalid",
"requires",
"must",
"cannot",
"missing",
"empty",
"greater than",
"less than",
"too small",
"too large",
"not recognized",
"not a",
"does not have",
)
lower = text.lower()
if any(marker.lower() in lower for marker in unsupported_markers):
return "暂未实现", "当前版本暂未实现这类稳定修改。"
if any(marker.lower() in lower for marker in risk_markers):
return "风险过高", "当前修改容易破坏 B-Rep 或让几何计算长时间失败,已在执行前阻止。"
if any(marker.lower() in lower for marker in recognition_markers):
return "识别不足", "程序还没有把当前对象识别成足够稳定的 CAD 特征或一级关系。"
if any(marker.lower() in lower for marker in illegal_markers):
return "当前操作不合法", "当前输入或选中对象不满足这个操作的基本条件。"
return "不能稳定修改", "几何内核没有给出可验证的有效 B-Rep 结果。"
def _edit_blocker_reason_from_parameters(self, parameters: dict[str, object], prefix: str = "") -> str:
candidate_keys: list[str] = []
if prefix:
candidate_keys.extend((f"{prefix}_blockers", f"{prefix}_message", f"{prefix}_warnings"))
candidate_keys.extend(
(
"blockers",
"message",
"resize_blockers",
"resize_message",
"depth_blockers",
"depth_message",
"move_axis_blockers",
"move_axis_message",
"boss_resize_blockers",
"boss_height_blockers",
"existing_fillet_blockers",
"fillet_blockers",
"chamfer_blockers",
"push_pull_message",
)
)
for key in candidate_keys:
text = str(parameters.get(key) or "").strip()
if text:
return text
return ""
def _plan_preflight_blocker(self, plan: dict[str, object]) -> tuple[str, str, str] | None:
status = str(plan.get("status") or "").strip().lower()
risk = str(plan.get("risk") or "").strip().lower()
if status != "blocked" and risk != "blocked":
return None
reason = self._edit_blocker_reason_from_parameters(plan) or "计划阶段已判断该操作不可执行。"
blocker_type, detail = self._edit_blocker_type(reason)
return blocker_type, reason, detail
def _show_blocked_plan_message(
self,
title: str,
plan: dict[str, object],
blocked_status: str,
) -> None:
blocker = self._plan_preflight_blocker(plan)
if blocker is None:
blocker = ("不能稳定修改", str(plan.get("message") or "计划阶段已阻止。"), "模型未修改。")
blocker_type, reason, detail = blocker
QMessageBox.information(
self,
"不能修改",
(
"不能修改。\n\n"
f"类型:{blocker_type}\n"
f"操作:{title}\n\n"
f"原因:{_compact_user_message(reason)}\n\n"
f"{detail}\n\n"
"模型未修改。"
),
)
self.statusBar().showMessage(f"{blocked_status}{blocker_type}")
def _show_immediate_cannot_edit_message(
self,
title: str,
reason: str,
blocked_status: str,
) -> None:
blocker_type, detail = self._edit_blocker_type(reason)
QMessageBox.information(
self,
"不能修改",
(
"不能修改。\n\n"
f"类型:{blocker_type}\n"
f"操作:{title}\n\n"
f"原因:{_compact_user_message(reason)}\n\n"
f"{detail}\n\n"
"模型未修改。"
),
)
self.statusBar().showMessage(f"{blocked_status}{blocker_type}")
def _edit_preflight_blocker(self, context: dict[str, object]) -> tuple[str, str, str] | None:
parameters = context.get("parameters")
if not isinstance(parameters, dict):
return None
if bool(parameters.get("ui_deferred_model_plan")) or bool(parameters.get("ui_deferred_shell_thickness_plan")):
if bool(parameters.get("ui_deferred_shell_thickness_plan")):
reason = "当前复杂壳体厚度修改还没有快速、稳定的一级关系判断路径。"
else:
reason = "当前复杂 Face 修改还没有快速、稳定的一级关系判断路径。"
detail = (
"属于当前版本暂未实现的稳定编辑能力。请改用更明确的孔/槽/凸台/Edge 专门入口,"
"或先选择更简单的局部面。"
)
return ("暂未实现", reason, detail)
for key, value in parameters.items():
if str(key).startswith("ui_quick_blocked") and bool(value):
reason = self._edit_blocker_reason_from_parameters(parameters) or "快速预检已判断该操作不可稳定执行。"
blocker_type, detail = self._edit_blocker_type(reason)
return (blocker_type, reason, detail)
for key, value in parameters.items():
key_text = str(key)
if not key_text.endswith("_status"):
continue
if key_text in {"quick_plan_status", "topology_relation_status", "slot_status", "quality_status"}:
continue
if str(value or "").strip().lower() != "blocked":
continue
prefix = key_text[: -len("_status")]
reason = self._edit_blocker_reason_from_parameters(parameters, prefix) or "计划阶段已判断该操作不可执行。"
blocker_type, detail = self._edit_blocker_type(reason)
return (blocker_type, reason, detail)
for key, value in parameters.items():
key_text = str(key)
if not key_text.endswith("_blockers") and key_text != "blockers":
continue
reason = str(value or "").strip()
if not reason:
continue
blocker_type, detail = self._edit_blocker_type(reason)
return (blocker_type, reason, detail)
return None
def _show_edit_preflight_blocker(self, blocker: tuple[str, str, str], context: dict[str, object]) -> None:
blocker_type, reason, detail = blocker
operation_name = str(context.get("operation_name") or "当前操作")
target = str(context.get("target") or "当前对象")
QMessageBox.information(
self,
"不能修改",
(
"不能修改。\n\n"
f"类型:{blocker_type}\n"
f"对象:{target}\n"
f"操作:{operation_name}\n\n"
f"原因:{_compact_user_message(reason)}\n\n"
f"{detail}\n\n"
"模型未修改。"
),
)
self.statusBar().showMessage(f"不能修改:{blocker_type}")
def _user_facing_edit_failure_message(
self,
message: str,
context: dict[str, object] | None,
) -> tuple[str, str]:
raw_message = str(message or "").strip()
reason = raw_message
for marker in ("诊断信息:", "璇婃柇淇℃伅:"):
index = reason.find(marker)
if index >= 0:
reason = reason[:index].strip()
replacements = (
("隔离子进程执行失败;主程序没有崩溃,原模型保持不变。", ""),
("隔离子进程执行超时,已终止危险计算;主程序和原模型保持不变。", ""),
("隔离子进程", "后台几何计算"),
("子进程", "后台计算"),
("isolated worker", "后台几何计算"),
("isolated", "后台"),
)
for old, new in replacements:
reason = reason.replace(old, new)
reason = _compact_user_message(reason)
if not reason:
reason = "后台几何计算没有返回可用结果。"
blocker_type, detail = self._edit_blocker_type(reason)
lower_raw = raw_message.lower()
if "超时" in raw_message or "timeout" in lower_raw:
blocker_type = "暂未实现"
detail = "当前版本还不能稳定处理这类复杂拓扑,已停止继续等待。"
if "returned an invalid b-rep" in lower_raw or "无效 b-rep" in lower_raw:
blocker_type = "几何结果无效"
detail = "几何内核生成的结果没有通过 B-Rep 校验,已回滚。"
operation_name = ""
target = ""
if isinstance(context, dict):
operation_name = str(context.get("operation_name") or "")
target = str(context.get("target") or "")
lines = ["不能修改。", "", f"类型:{blocker_type}"]
if target:
lines.append(f"对象:{target}")
if operation_name:
lines.append(f"操作:{operation_name}")
lines.extend(["", f"原因:{reason or '几何计算失败。'}", "", detail, "", "模型未修改。"])
return "不能修改", "\n".join(lines)
def resize_hole(self) -> None:
if self.model is None:
return
@@ -1992,7 +2270,11 @@ class WindowActionMixin:
if angular_span is None:
angular_span = _float_or_none(info.get("angular_span"))
if angular_span is None or angular_span <= 1e-6 or angular_span >= math.tau * 0.92:
QMessageBox.information(self, f"不能整体修改{label}", "当前候选没有稳定的部分圆柱角度,不能换算为目标圆柱直径。")
self._show_immediate_cannot_edit_message(
f"整体修改{label}",
"当前候选没有稳定的部分圆柱角度,不能换算为目标圆柱直径。",
f"整体修改{label}已阻止",
)
return
span = min(max(float(angular_span), 1e-6), math.tau - 1e-6)
@@ -2000,12 +2282,20 @@ class WindowActionMixin:
sagitta_factor = 1.0 - math.cos(min(span, math.pi) * 0.5)
if mode == "width":
if abs(sin_half_span) <= 1e-6:
QMessageBox.information(self, "不能整体修改槽宽", "当前槽圆弧角度过小,不能可靠换算槽宽。")
self._show_immediate_cannot_edit_message(
"整体修改槽宽",
"当前槽圆弧角度过小,不能可靠换算槽宽。",
"整体修改槽宽已阻止",
)
return
target_diameter = target_value / sin_half_span
elif mode == "depth":
if sagitta_factor <= 1e-6:
QMessageBox.information(self, "不能整体修改槽深", "当前槽圆弧角度过小,不能可靠换算槽深。")
self._show_immediate_cannot_edit_message(
"整体修改槽深",
"当前槽圆弧角度过小,不能可靠换算槽深。",
"整体修改槽深已阻止",
)
return
target_diameter = 2.0 * target_value / sagitta_factor
else:
@@ -2122,11 +2412,19 @@ class WindowActionMixin:
if angular_span is None:
angular_span = _float_or_none(info.get("angular_span"))
if angular_span is None or angular_span <= 1e-6 or angular_span >= math.tau * 0.92:
QMessageBox.information(self, "不能调整槽宽", "当前候选没有稳定的部分圆柱角度,不能换算槽宽。")
self._show_immediate_cannot_edit_message(
"调整槽宽",
"当前候选没有稳定的部分圆柱角度,不能换算槽宽。",
"槽/半孔宽度调整已阻止",
)
return
sin_half_span = math.sin(min(max(angular_span, 1e-6), math.tau - 1e-6) / 2.0)
if abs(sin_half_span) <= 1e-6:
QMessageBox.information(self, "不能调整槽宽", "当前槽圆弧角度过小,不能可靠换算为圆柱直径。")
self._show_immediate_cannot_edit_message(
"调整槽宽",
"当前槽圆弧角度过小,不能可靠换算为圆柱直径。",
"槽/半孔宽度调整已阻止",
)
return
target_diameter = target_width / sin_half_span
@@ -2244,12 +2542,20 @@ class WindowActionMixin:
if angular_span is None:
angular_span = _float_or_none(info.get("angular_span"))
if angular_span is None or angular_span <= 1e-6 or angular_span >= math.tau * 0.92:
QMessageBox.information(self, "不能调整槽深", "当前候选没有稳定的部分圆柱角度,不能换算槽深。")
self._show_immediate_cannot_edit_message(
"调整槽深",
"当前候选没有稳定的部分圆柱角度,不能换算槽深。",
"槽/半孔深度调整已阻止",
)
return
sagitta_factor = 1.0 - math.cos(min(max(angular_span, 1e-6), math.pi) / 2.0)
if sagitta_factor <= 1e-6:
QMessageBox.information(self, "不能调整槽深", "当前槽圆弧角度过小,不能可靠换算为圆柱直径。")
self._show_immediate_cannot_edit_message(
"调整槽深",
"当前槽圆弧角度过小,不能可靠换算为圆柱直径。",
"槽/半孔深度调整已阻止",
)
return
target_diameter = 2.0 * target_depth / sagitta_factor
@@ -2371,7 +2677,11 @@ class WindowActionMixin:
if angular_span is None:
angular_span = _float_or_none(info.get("angular_span"))
if angular_span is None or angular_span <= 1e-6 or angular_span >= math.tau * 0.92:
QMessageBox.information(self, "不能调整圆弧长度", "当前候选没有稳定的部分圆柱角度,不能换算圆弧长度。")
self._show_immediate_cannot_edit_message(
"调整圆弧长度",
"当前候选没有稳定的部分圆柱角度,不能换算圆弧长度。",
"槽/半孔圆弧长度调整已阻止",
)
return
target_diameter = 2.0 * target_arc_length / angular_span
@@ -2509,7 +2819,11 @@ class WindowActionMixin:
if current_angular_span is None:
current_angular_span = _float_or_none(info.get("angular_span"))
if current_angular_span is None or current_angular_span <= 1e-6 or current_angular_span >= math.tau * 0.92:
QMessageBox.information(self, "不能调整圆弧角度", "当前候选没有稳定的部分圆柱角度。")
self._show_immediate_cannot_edit_message(
"调整圆弧角度",
"当前候选没有稳定的部分圆柱角度。",
"槽/半孔圆弧角度调整已阻止",
)
return
plan = self.model.cylindrical_slot_angular_span_plan(face_id, target_angular_span)
@@ -2626,8 +2940,7 @@ class WindowActionMixin:
pair_face_id=pair_face_id,
)
if plan["status"] == "blocked":
QMessageBox.information(self, "不能调整槽孔总长度", str(plan["message"]))
self.statusBar().showMessage("槽孔总长度调整已阻止")
self._show_blocked_plan_message("调整槽孔总长度", plan, "槽孔总长度调整已阻止")
return
lines = [
f"Face: {face_id}",
@@ -2725,8 +3038,7 @@ class WindowActionMixin:
pair_face_id=pair_face_id,
)
if plan["status"] == "blocked":
QMessageBox.information(self, "不能调整槽孔中心距", str(plan["message"]))
self.statusBar().showMessage("槽孔中心距调整已阻止")
self._show_blocked_plan_message("调整槽孔中心距", plan, "槽孔中心距调整已阻止")
return
lines = [
f"Face: {face_id}",
@@ -2817,8 +3129,7 @@ class WindowActionMixin:
return
plan = self.model.cylindrical_boss_resize_plan(self.selected_face_id, diameter)
if plan["status"] == "blocked":
QMessageBox.information(self, "不能调整凸台直径", str(plan["message"]))
self.statusBar().showMessage("圆柱凸台直径调整已阻止")
self._show_blocked_plan_message("调整凸台直径", plan, "圆柱凸台直径调整已阻止")
return
if plan["risk"] != "low":
target_to_height_ratio = plan.get("target_to_height_ratio", "")
@@ -3013,8 +3324,7 @@ class WindowActionMixin:
face_id = self.selected_face_id
plan = self.model.cylindrical_boss_height_plan(face_id, target_height)
if plan["status"] == "blocked":
QMessageBox.information(self, "不能调整凸台高度", str(plan["message"]))
self.statusBar().showMessage("圆柱凸台高度调整已阻止")
self._show_blocked_plan_message("调整凸台高度", plan, "圆柱凸台高度调整已阻止")
return
if plan["risk"] != "low":
@@ -3280,8 +3590,7 @@ class WindowActionMixin:
return
plan = self.model.cylindrical_suppress_plan(self.selected_face_id)
if plan["status"] == "blocked":
QMessageBox.information(self, "不能封堵圆柱孔", str(plan["message"]))
self.statusBar().showMessage("封堵圆柱孔已阻止")
self._show_blocked_plan_message("封堵圆柱孔", plan, "封堵圆柱孔已阻止")
return
if plan["risk"] != "low":
@@ -3717,8 +4026,7 @@ class WindowActionMixin:
plan = self.model.existing_fillet_resize_plan(face_id, target_radius)
isolation = self._isolation_for_plan(plan, "resize_existing_fillet", [face_id, target_radius])
if plan["status"] == "blocked":
QMessageBox.information(self, "不能修改已有圆角", str(plan["message"]))
self.statusBar().showMessage("已有圆角半径修改已阻止")
self._show_blocked_plan_message("修改已有圆角", plan, "已有圆角半径修改已阻止")
return
warnings = str(plan.get("warnings", ""))
@@ -3811,8 +4119,7 @@ class WindowActionMixin:
plan = self.model.edge_fillet_plan(edge_id, radius)
isolation = self._isolation_for_plan(plan, "fillet_edge", [edge_id, radius])
if plan["status"] == "blocked":
QMessageBox.information(self, "不能添加圆角", str(plan["message"]))
self.statusBar().showMessage("添加圆角已阻止")
self._show_blocked_plan_message("添加圆角", plan, "添加圆角已阻止")
return
if plan["risk"] != "low":
@@ -3893,8 +4200,7 @@ class WindowActionMixin:
plan = self.model.edge_chamfer_plan(edge_id, distance)
isolation = self._isolation_for_plan(plan, "chamfer_edge", [edge_id, distance])
if plan["status"] == "blocked":
QMessageBox.information(self, "不能添加倒角", str(plan["message"]))
self.statusBar().showMessage("添加倒角已阻止")
self._show_blocked_plan_message("添加倒角", plan, "添加倒角已阻止")
return
if plan["risk"] != "low":
@@ -4159,8 +4465,7 @@ class WindowActionMixin:
[edge_id, target_length, anchor_mode, strategy_mode],
)
if plan["status"] == "blocked":
QMessageBox.information(self, "不能修改Edge长度", str(plan["message"]))
self.statusBar().showMessage("Edge长度修改已阻止")
self._show_blocked_plan_message("修改Edge长度", plan, "Edge长度修改已阻止")
return
if plan["risk"] != "low":
@@ -4325,8 +4630,7 @@ class WindowActionMixin:
[edge_id, target_length, anchor_mode, strategy_mode],
)
if plan["status"] == "blocked":
QMessageBox.information(self, "不能修改Edge长度", str(plan["message"]))
self.statusBar().showMessage("Edge长度修改已阻止")
self._show_blocked_plan_message("修改Edge长度", plan, "Edge长度修改已阻止")
return
warnings = str(plan.get("warnings", ""))
@@ -4498,8 +4802,7 @@ class WindowActionMixin:
[edge_id, target_radius, axis_kind],
)
if plan["status"] == "blocked":
QMessageBox.information(self, f"不能修改椭圆Edge{axis_label}", str(plan["message"]))
self.statusBar().showMessage(f"椭圆Edge{axis_label}修改已阻止")
self._show_blocked_plan_message(f"修改椭圆Edge{axis_label}", plan, f"椭圆Edge{axis_label}修改已阻止")
return
warnings = str(plan.get("warnings", ""))
@@ -4861,8 +5164,7 @@ class WindowActionMixin:
plan = self.model.conical_reference_radius_plan(face_id, target_radius)
isolation = self._isolation_for_plan(plan, "resize_cone_reference_radius", [face_id, target_radius])
if plan["status"] == "blocked":
QMessageBox.information(self, "不能修改圆锥面参数", str(plan["message"]))
self.statusBar().showMessage("圆锥面参数修改已阻止")
self._show_blocked_plan_message("修改圆锥面参数", plan, "圆锥面参数修改已阻止")
return
if plan["risk"] != "low":
warnings = str(plan.get("warnings", ""))
@@ -4960,8 +5262,7 @@ class WindowActionMixin:
plan = self.model.conical_semi_angle_plan(face_id, target_angle_degrees)
isolation = self._isolation_for_plan(plan, "resize_cone_semi_angle", [face_id, target_angle_degrees])
if plan["status"] == "blocked":
QMessageBox.information(self, "不能修改圆锥半角", str(plan["message"]))
self.statusBar().showMessage("圆锥半角修改已阻止")
self._show_blocked_plan_message("修改圆锥半角", plan, "圆锥半角修改已阻止")
return
if plan["risk"] != "low":
warnings = str(plan.get("warnings", ""))
@@ -5057,8 +5358,7 @@ class WindowActionMixin:
plan = self.model.spherical_radius_plan(face_id, target_radius)
isolation = self._isolation_for_plan(plan, "resize_sphere_radius", [face_id, target_radius])
if plan["status"] == "blocked":
QMessageBox.information(self, "不能修改球面(缩放特征)", str(plan["message"]))
self.statusBar().showMessage("球面(缩放特征)修改已阻止")
self._show_blocked_plan_message("修改球面(缩放特征)", plan, "球面(缩放特征)修改已阻止")
return
if plan["risk"] != "low":
warnings = str(plan.get("warnings", ""))
@@ -5160,8 +5460,7 @@ class WindowActionMixin:
plan = self.model.toroidal_radius_plan(face_id, target_radius, mode_key)
isolation = self._isolation_for_plan(plan, "resize_torus_radius", [face_id, target_radius, mode_key])
if plan["status"] == "blocked":
QMessageBox.information(self, "不能修改环面(缩放特征)", str(plan["message"]))
self.statusBar().showMessage("环面(缩放特征)修改已阻止")
self._show_blocked_plan_message("修改环面(缩放特征)", plan, "环面(缩放特征)修改已阻止")
return
if plan["risk"] != "low":
warnings = str(plan.get("warnings", ""))
@@ -5260,8 +5559,7 @@ class WindowActionMixin:
face_id = self.selected_face_id
plan = self.model.face_area_scale_plan(face_id, target_area)
if plan["status"] == "blocked":
QMessageBox.information(self, "不能修改面面积", str(plan["message"]))
self.statusBar().showMessage("面面积修改已阻止")
self._show_blocked_plan_message("修改面面积", plan, "面面积修改已阻止")
return
warnings = str(plan.get("warnings", ""))
warnings_line = f"警告: {warnings}\n\n" if warnings else ""
@@ -5428,7 +5726,7 @@ class WindowActionMixin:
if self.model is None:
return
axis_key = "height" if str(axis).lower() == "height" else "width"
axis_label = "V向尺寸" if axis_key == "height" else "U向尺寸"
axis_label = "面内宽度" if axis_key == "height" else "面内长度"
if self._edit_busy(f"请等待当前编辑完成后再修改{axis_label}(局部重建)。"):
return
if self.selected_face_id is None:
@@ -5450,10 +5748,10 @@ class WindowActionMixin:
lines = [
f"Face: {face_id}",
f"修改对象: {axis_label}(局部重建)",
f"当前U向尺寸: {_format_value(plan.get('current_face_width'))}",
f"目标U向尺寸: {_format_value(plan.get('target_face_width'))}",
f"当前V向尺寸: {_format_value(plan.get('current_face_height'))}",
f"目标V向尺寸: {_format_value(plan.get('target_face_height'))}",
f"当前面内长度: {_format_value(plan.get('current_face_width'))}",
f"目标面内长度: {_format_value(plan.get('target_face_width'))}",
f"当前面内宽度: {_format_value(plan.get('current_face_height'))}",
f"目标面内宽度: {_format_value(plan.get('target_face_height'))}",
f"变化量: {_format_value(plan.get('face_size_delta'))}",
f"变化比例: {_format_percent(plan.get('face_size_delta_ratio'))}",
f"缩放比例: {_format_value(plan.get('face_size_scale'))}",
@@ -5522,7 +5820,7 @@ class WindowActionMixin:
if self.model is None:
return
axis_key = "height" if str(axis).lower() == "height" else "width"
axis_label = "V向尺寸" if axis_key == "height" else "U向尺寸"
axis_label = "面内宽度" if axis_key == "height" else "面内长度"
if self._edit_busy(f"请等待当前编辑完成后再修改{axis_label}(缩放特征)。"):
return
if self.selected_face_id is None:
@@ -5542,10 +5840,10 @@ class WindowActionMixin:
lines = [
f"Face: {face_id}",
f"修改对象: {axis_label}(缩放特征)",
f"当前U向尺寸: {_format_value(plan.get('current_face_width'))}",
f"目标U向尺寸: {_format_value(plan.get('target_face_width'))}",
f"当前V向尺寸: {_format_value(plan.get('current_face_height'))}",
f"目标V向尺寸: {_format_value(plan.get('target_face_height'))}",
f"当前面内长度: {_format_value(plan.get('current_face_width'))}",
f"目标面内长度: {_format_value(plan.get('target_face_width'))}",
f"当前面内宽度: {_format_value(plan.get('current_face_height'))}",
f"目标面内宽度: {_format_value(plan.get('target_face_height'))}",
f"变化量: {_format_value(plan.get('face_size_delta'))}",
f"变化比例: {_format_percent(plan.get('face_size_delta_ratio'))}",
f"缩放比例: {_format_value(plan.get('face_size_scale'))}",
@@ -5625,7 +5923,7 @@ class WindowActionMixin:
def move_selected_face_plane_position_by_translation(self) -> None:
if self.model is None:
return
if self._edit_busy("请等待当前编辑完成后再按偏移变换平移所属对象。"):
if self._edit_busy("请等待当前编辑完成后再按偏移平移所属对象。"):
return
if self.selected_face_id is None:
QMessageBox.information(self, "未选择Face", "请先选择一个平面 Face。")
@@ -5633,14 +5931,13 @@ class WindowActionMixin:
try:
distance = float(self.offset_input.text())
except (AttributeError, ValueError):
QMessageBox.critical(self, "偏移变换无效", "请输入数字形式的目标偏移变换位置。")
QMessageBox.critical(self, "偏移无效", "请输入数字形式的目标偏移位置。")
return
face_id = self.selected_face_id
plan = self.model.face_plane_offset_owning_translation_plan(face_id, distance)
if plan["status"] == "blocked":
QMessageBox.information(self, "不能按偏移变换平移", str(plan["message"]))
self.statusBar().showMessage("偏移变换整体平移已阻止")
self._show_blocked_plan_message("按偏移平移", plan, "偏移整体平移已阻止")
return
current_position = float(plan.get("current_plane_position", 0.0) or 0.0)
target_position = float(plan.get("target_plane_position", current_position) or current_position)
@@ -5652,7 +5949,7 @@ class WindowActionMixin:
target_position,
plane_direction,
):
self.statusBar().showMessage("已取消偏移变换整体平移")
self.statusBar().showMessage("已取消偏移整体平移")
return
self.clear_edit_preview(render=False)
@@ -5663,7 +5960,7 @@ class WindowActionMixin:
moved_id = plan.get("solid_id") if moved_kind == "solid" else plan.get("part_id")
self._run_edit_action(
action,
operation_name="偏移变换(移动特征)",
operation_name="偏移(移动特征)",
target=f"Face {face_id}",
parameters={
"face_id": face_id,
@@ -5684,8 +5981,8 @@ class WindowActionMixin:
"translate_warnings": plan.get("warnings"),
"translate_blockers": plan.get("blockers"),
"resize_strategy": "translate-owning-shape-from-plane-offset",
"edit_strategy_label": "按偏移变换平移所属对象",
"edit_semantics": "把目标偏移变换位置换算成沿当前面垂直方向的平移量,并平移所属特征或 Solid;不拉伸/切除,不切削,也不补料。",
"edit_strategy_label": "按偏移平移所属对象",
"edit_semantics": "把目标偏移位置换算成沿当前面垂直方向的平移量,并平移所属特征或 Solid;不拉伸/切除,不切削,也不补料。",
},
target_kind="face",
target_id=face_id,
@@ -5694,7 +5991,7 @@ class WindowActionMixin:
def move_selected_face_plane_position_local(self) -> None:
if self.model is None:
return
if self._edit_busy("请等待当前编辑完成后再执行偏移变换(局部重建)。"):
if self._edit_busy("请等待当前编辑完成后再执行偏移(局部重建)。"):
return
if self.selected_face_id is None:
QMessageBox.information(self, "未选择Face", "请先选择一个平面 Face。")
@@ -5702,7 +5999,7 @@ class WindowActionMixin:
try:
distance = float(self.offset_input.text())
except (AttributeError, ValueError):
QMessageBox.critical(self, "偏移变换无效", "请输入数字形式的目标偏移变换位置。")
QMessageBox.critical(self, "偏移无效", "请输入数字形式的目标偏移位置。")
return
face_id = self.selected_face_id
@@ -5711,8 +6008,8 @@ class WindowActionMixin:
plan = self.model.face_plane_offset_local_plan(face_id, distance)
lines = [
f"Face: {face_id}",
f"当前偏移变换位置: {_format_value(plan.get('current_plane_position'))}",
f"目标偏移变换位置: {_format_value(plan.get('target_plane_position'))}",
f"当前偏移位置: {_format_value(plan.get('current_plane_position'))}",
f"目标偏移位置: {_format_value(plan.get('target_plane_position'))}",
f"平面方向: {_format_value(plan.get('plane_direction'))}",
f"移动向量: {_format_value(plan.get('face_center_move_vector'))}",
f"移动距离: {_format_value(plan.get('face_center_move_distance'))}",
@@ -5722,11 +6019,11 @@ class WindowActionMixin:
f"影响目标: {_format_value(plan.get('local_face_deform_target_kind'))}",
]
if not self._confirm_quick_edit_plan(
"偏移变换(局部重建)",
"偏移(局部重建)",
plan,
lines,
"偏移变换(局部重建)已阻止",
"已取消偏移变换(局部重建)",
"偏移(局部重建)已阻止",
"已取消偏移(局部重建)",
):
return
self.clear_edit_preview(render=False)
@@ -5736,7 +6033,7 @@ class WindowActionMixin:
self._run_edit_action(
action,
operation_name="偏移变换(局部重建)",
operation_name="偏移(局部重建)",
target=f"Face {face_id}",
parameters={
"face_id": face_id,
@@ -5758,9 +6055,9 @@ class WindowActionMixin:
"local_face_deform_face_count": plan.get("local_face_deform_face_count"),
"local_face_deform_moved_point_count": plan.get("local_face_deform_moved_point_count"),
"resize_strategy": "local-face-plane-offset-deform",
"edit_strategy_label": "偏移变换(局部重建)",
"edit_strategy_label": "偏移(局部重建)",
"edit_semantics": (
"把目标偏移变换位置换算成沿当前面垂直方向的移动量,移动当前 Face 的顶点并重建相邻平面;"
"把目标偏移位置换算成沿当前面垂直方向的移动量,移动当前 Face 的顶点并重建相邻平面;"
"不拉伸/切除加料/切削,也不平移所属对象。"
),
"resize_status": plan.get("status"),
@@ -5813,12 +6110,15 @@ class WindowActionMixin:
return self.model.move_face_center_owning(face_id, target_center)
else:
QMessageBox.information(self, "不能移动Face中心", "当前 Face 没有关联到稳定的所属对象。")
self._show_immediate_cannot_edit_message(
"移动Face中心",
"当前 Face 没有关联到稳定的所属对象。",
"Face中心移动已阻止",
)
return
if plan["status"] == "blocked":
QMessageBox.information(self, "不能移动Face中心", str(plan["message"]))
self.statusBar().showMessage("Face中心移动已阻止")
self._show_blocked_plan_message("移动Face中心", plan, "Face中心移动已阻止")
return
if not self._confirm_face_center_move_plan(plan, face_id, current_center, target_center):
self.statusBar().showMessage("已取消Face中心移动")
@@ -5868,7 +6168,11 @@ class WindowActionMixin:
face_id = self.selected_face_id
current_center = self._selected_face_center_for_translation(face_id)
if current_center is None:
QMessageBox.information(self, "不能执行中心(局部重建)", "当前 Face 缺少稳定中心坐标。")
self._show_immediate_cannot_edit_message(
"中心(局部重建)",
"当前 Face 缺少稳定中心坐标。",
"中心(局部重建)已阻止",
)
return
target_center = (
current_center[0] + vector[0],
@@ -5953,7 +6257,11 @@ class WindowActionMixin:
info = self._selected_face_info_snapshot()
current_center = self._selected_axis_center_for_translation(info)
if current_center is None:
QMessageBox.information(self, "不能按轴心平移", "当前对象缺少稳定轴心坐标。")
self._show_immediate_cannot_edit_message(
"按轴心平移",
"当前对象缺少稳定轴心坐标。",
"按轴心平移所属对象已阻止",
)
return
target_center = (
current_center[0] + vector[0],
@@ -5977,12 +6285,15 @@ class WindowActionMixin:
return self.model.translate_part(moved_id, vector)
else:
QMessageBox.information(self, "不能按轴心平移", "当前对象没有关联到稳定的所属特征或 Solid。")
self._show_immediate_cannot_edit_message(
"按轴心平移",
"当前对象没有关联到稳定的所属特征或 Solid。",
"按轴心平移所属对象已阻止",
)
return
if plan["status"] == "blocked":
QMessageBox.information(self, "不能按轴心平移", str(plan["message"]))
self.statusBar().showMessage("按轴心平移所属对象已阻止")
self._show_blocked_plan_message("按轴心平移", plan, "按轴心平移所属对象已阻止")
return
if not self._confirm_axis_center_translation_plan(plan, face_id, current_center, target_center):
self.statusBar().showMessage("已取消按轴心平移所属对象")
@@ -6184,23 +6495,23 @@ class WindowActionMixin:
warnings_line = f"警告: {warnings}\n\n" if warnings else ""
result = QMessageBox.question(
self,
"确认偏移变换整体平移",
"确认偏移整体平移",
(
f"Face: {face_id}\n"
f"移动对象: {_format_value(plan.get('target_kind', ''))}\n"
f"零件: {_format_value(plan.get('part_id', ''))}\n"
f"Solid: {_format_value(plan.get('solid_id', ''))}\n"
f"当前偏移变换位置: {_format_value(current_position)}\n"
f"目标偏移变换位置: {_format_value(target_position)}\n"
f"当前偏移位置: {_format_value(current_position)}\n"
f"目标偏移位置: {_format_value(target_position)}\n"
f"平面方向: {_format_value(plane_direction)}\n"
f"平移向量: {_format_value(plan['translation_vector'])}\n"
f"平移距离: {_format_value(plan['translation_distance'])}\n"
f"执行策略: 按偏移变换平移所属对象\n"
f"执行策略: 按偏移平移所属对象\n"
f"建模意图: 沿当前面垂直方向平移所属特征或 Solid;不拉伸/切除,不切削,也不补料。\n"
f"风险: {plan['risk']}\n\n"
f"{warnings_line}"
f"{plan['message']}\n\n"
"如果你想改变厚度或把这个面推出/切入,请在“偏移变换”这一行把建模意图选为“拉伸/切除”。确定继续吗?"
"如果你想改变厚度或把这个面推出/切入,请在“偏移”这一行把建模意图选为“拉伸/切除”。确定继续吗?"
),
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No,
@@ -6223,8 +6534,7 @@ class WindowActionMixin:
part_id = self.selected_part_id
plan = self.model.translate_part_plan(part_id, vector)
if plan["status"] == "blocked":
QMessageBox.information(self, "不能平移零件", str(plan["message"]))
self.statusBar().showMessage("平移零件已阻止")
self._show_blocked_plan_message("平移零件", plan, "平移零件已阻止")
return
if not self._confirm_translation_plan("确认平移零件", plan):
self.statusBar().showMessage("已取消平移零件")
@@ -6268,8 +6578,7 @@ class WindowActionMixin:
solid_id = self.selected_solid_id
plan = self.model.translate_solid_plan(solid_id, vector)
if plan["status"] == "blocked":
QMessageBox.information(self, "不能平移Solid", str(plan["message"]))
self.statusBar().showMessage("平移Solid已阻止")
self._show_blocked_plan_message("平移Solid", plan, "平移Solid已阻止")
return
if not self._confirm_translation_plan("确认平移Solid", plan):
self.statusBar().showMessage("已取消平移Solid")
@@ -6351,8 +6660,7 @@ class WindowActionMixin:
part_id = self.selected_part_id
plan = self.model.rotate_part_plan(part_id, axis, angle)
if plan["status"] == "blocked":
QMessageBox.information(self, "不能旋转零件", str(plan["message"]))
self.statusBar().showMessage("旋转零件已阻止")
self._show_blocked_plan_message("旋转零件", plan, "旋转零件已阻止")
return
if not self._confirm_rotation_plan("确认旋转零件", plan):
self.statusBar().showMessage("已取消旋转零件")
@@ -6397,8 +6705,7 @@ class WindowActionMixin:
solid_id = self.selected_solid_id
plan = self.model.rotate_solid_plan(solid_id, axis, angle)
if plan["status"] == "blocked":
QMessageBox.information(self, "不能旋转Solid", str(plan["message"]))
self.statusBar().showMessage("旋转Solid已阻止")
self._show_blocked_plan_message("旋转Solid", plan, "旋转Solid已阻止")
return
if not self._confirm_rotation_plan("确认旋转Solid", plan):
self.statusBar().showMessage("已取消旋转Solid")
@@ -6445,8 +6752,7 @@ class WindowActionMixin:
part_id = self.selected_part_id
plan = self.model.scale_part_plan(part_id, target_diagonal)
if plan["status"] == "blocked":
QMessageBox.information(self, "不能缩放零件", str(plan["message"]))
self.statusBar().showMessage("缩放零件已阻止")
self._show_blocked_plan_message("缩放零件", plan, "缩放零件已阻止")
return
if not self._confirm_scale_plan("确认缩放零件", plan):
self.statusBar().showMessage("已取消缩放零件")
@@ -6493,8 +6799,7 @@ class WindowActionMixin:
solid_id = self.selected_solid_id
plan = self.model.scale_solid_plan(solid_id, target_diagonal)
if plan["status"] == "blocked":
QMessageBox.information(self, "不能缩放Solid", str(plan["message"]))
self.statusBar().showMessage("缩放Solid已阻止")
self._show_blocked_plan_message("缩放Solid", plan, "缩放Solid已阻止")
return
if not self._confirm_scale_plan("确认缩放Solid", plan):
self.statusBar().showMessage("已取消缩放Solid")
@@ -6594,8 +6899,7 @@ class WindowActionMixin:
run_target_kind = "solid"
if plan["status"] == "blocked":
QMessageBox.information(self, "不能按轴缩放", str(plan["message"]))
self.statusBar().showMessage("按轴缩放已阻止")
self._show_blocked_plan_message("按轴缩放", plan, "按轴缩放已阻止")
return
if not self._confirm_axis_scale_plan(title, plan):
self.statusBar().showMessage("已取消按轴缩放")
@@ -7075,6 +7379,10 @@ class WindowActionMixin:
"edit_result_deflection": result_deflection,
"isolation": dict(isolation or {}),
}
blocker = self._edit_preflight_blocker(context)
if blocker is not None:
self._show_edit_preflight_blocker(blocker, context)
return
self._begin_edit_task(operation_name)
self.pending_edit_context = context
@@ -7563,7 +7871,7 @@ class WindowActionMixin:
target_width = _float_or_none(parameters.get("target_face_width"))
target_height = _float_or_none(parameters.get("target_face_height"))
if target_width is not None or target_height is not None:
checks.append(("U向尺寸/V向尺寸", self._face_target_size_matches(model, face_ids, target_width, target_height)))
checks.append(("面内长度/面内宽度", self._face_target_size_matches(model, face_ids, target_width, target_height)))
target_center = _triple_or_none(parameters.get("target_face_center"))
if target_center is not None:
@@ -7577,7 +7885,7 @@ class WindowActionMixin:
if target_position is not None and plane_direction is not None:
checks.append(
(
"偏移变换",
"偏移",
self._face_target_plane_position_matches(
model,
face_ids,
@@ -8005,9 +8313,9 @@ class WindowActionMixin:
"",
"面积",
"中心",
"U向尺寸",
"V向尺寸",
"偏移变换",
"面内长度",
"面内宽度",
"偏移",
"壳体",
"拉伸/切除",
"圆锥",
@@ -8058,13 +8366,13 @@ class WindowActionMixin:
progress_text = f"{operation_name} 正在后台计算,预览表示局部边形变、端面拉伸/切除或后备策略后的目标范围。"
elif "面积(局部重建)" in operation_name:
progress_text = f"{operation_name} 正在后台计算;当前会在面内移动所选 Face 顶点并重建相邻平面。"
elif "U向尺寸(缩放特征)" in operation_name or "V向尺寸(缩放特征)" in operation_name:
elif "面内长度(缩放特征)" in operation_name or "面内宽度(缩放特征)" in operation_name:
progress_text = f"{operation_name} 正在后台计算;当前会沿选中 Face 的平面内方向缩放所属特征或 Solid,其它几何会跟随变化。"
elif "U向尺寸(局部重建)" in operation_name or "V向尺寸(局部重建)" in operation_name:
elif "面内长度(局部重建)" in operation_name or "面内宽度(局部重建)" in operation_name:
progress_text = f"{operation_name} 正在后台计算;当前会只沿选中 Face 的一个平面内方向缩放顶点并重建相邻平面。"
elif "偏移变换(局部重建)" in operation_name:
elif "偏移(局部重建)" in operation_name:
progress_text = f"{operation_name} 正在后台计算;当前会沿当前面垂直方向移动所选 Face 顶点并重建相邻平面。"
elif "偏移变换(移动特征)" in operation_name:
elif "偏移(移动特征)" in operation_name:
progress_text = f"{operation_name} 正在后台计算;当前会沿当前面垂直方向平移所属特征或 Solid,不拉伸/切除。"
elif "Face中心" in operation_name:
progress_text = f"{operation_name} 正在后台计算;当前会平移所属特征或 Solid,不做单面局部扭曲。"
@@ -8199,15 +8507,19 @@ class WindowActionMixin:
if hasattr(self, "_is_ui_thread") and not self._is_ui_thread():
self._invoke_on_ui_thread(lambda message=message: self._fail_edit_action(message))
return
context = self.pending_edit_context
close_after_cancel = bool(getattr(self, "close_after_edit_cancel", False))
if close_after_cancel:
self.close_after_edit_cancel = False
self._end_edit_task(clear_preview=True)
if not close_after_cancel:
QMessageBox.critical(self, "操作失败", message)
title, user_message = self._user_facing_edit_failure_message(message, context)
QMessageBox.information(self, title, user_message)
self._clear_editable_candidates()
self._clear_cylinder_candidates()
self.statusBar().showMessage("后台编辑已取消,模型已保持在编辑前状态" if close_after_cancel else "操作失败,模型已保持在编辑前状态")
self.statusBar().showMessage(
"后台编辑已取消,模型已保持在编辑前状态" if close_after_cancel else "不能修改,模型未修改"
)
if close_after_cancel:
if self.edit_thread is not None and self.edit_thread.isRunning():
self.edit_thread.quit()