feat: 完善 Edge 一级编辑与 CAD 建模语义

This commit is contained in:
2026-08-05 18:06:15 +08:00
parent ed1faf51d8
commit 57d75541c3
19 changed files with 2365 additions and 367 deletions
+332 -39
View File
@@ -3,6 +3,7 @@ from __future__ import annotations
from datetime import datetime
import math
from pathlib import Path
import time
from PySide6.QtCore import Qt, QThread, QTimer, Slot
from PySide6.QtWidgets import (
QApplication,
@@ -29,6 +30,8 @@ PROPERTY_TARGET_COLUMN = 3
PROPERTY_ACTION_COLUMN = 4
FEATURE_EDIT_SEMANTICS_KEYS = {
"cad_modeling_form",
"cad_recommended_operation",
"face_first_level_topology",
"cylindrical_feature_first_level_topology",
"slot_edit_semantics",
@@ -338,6 +341,59 @@ class WindowStateMixin:
**self._first_level_fact_selection_fields(face_id, scope="cylindrical-feature"),
}
def _edge_first_level_selection_fields(self, edge_id: int) -> dict[str, object]:
if self.model is None:
return {}
try:
topology = self.model.edge_first_level_topology(edge_id)
facts = self.model.edge_first_level_facts(edge_id)
except Exception as exc:
return {
"topology_relation_depth": 1,
"topology_relation_model": "STEP/B-Rep edge first-level",
"topology_relation_status": "unavailable",
"topology_relation_message": str(exc),
"topology_ignored_relation_depths": ("second-level", "third-level", "deeper"),
"topology_ignored_relation_note": "Current Edge first-level topology is unavailable.",
"first_level_vertex_count": 0,
"first_level_adjacent_edge_count": 0,
"first_level_adjacent_face_count": 0,
"first_level_fact_model": "STEP/B-Rep first-level fact graph",
"first_level_fact_status": "unavailable",
"first_level_fact_source_model": "edge",
"first_level_fact_relation_depth": 1,
"first_level_fact_scope": "edge",
"first_level_fact_subject_edge_ids": (edge_id,),
"first_level_fact_subject_edge_count": 1,
"first_level_fact_boundary_edge_count": 1,
"first_level_fact_boundary_vertex_count": 0,
"first_level_fact_adjacent_edge_count": 0,
"first_level_fact_adjacent_face_count": 0,
"first_level_fact_ignored_relation_depths": ("second-level", "third-level", "deeper"),
"first_level_fact_summary": f"Current Edge first-level fact graph is unavailable: {exc}",
}
return {
"topology_relation_depth": topology.get("topology_relation_depth", 1),
"topology_relation_model": topology.get("topology_relation_model"),
"topology_relation_scope": topology.get("topology_relation_scope"),
"topology_relation_boundary": topology.get("topology_relation_boundary"),
"topology_relation_status": "ready",
"topology_ignored_relation_depths": topology.get("topology_ignored_relation_depths", ()),
"topology_ignored_relation_note": topology.get("topology_ignored_relation_note", ""),
"selected_edge_ids": topology.get("selected_edge_ids", (edge_id,)),
"selected_edge_count": topology.get("selected_edge_count", 1),
"first_level_vertex_points": topology.get("first_level_vertex_points", ()),
"first_level_vertex_count": topology.get("first_level_vertex_count", 0),
"first_level_adjacent_edge_ids": topology.get("first_level_adjacent_edge_ids", ()),
"first_level_adjacent_edge_count": topology.get("first_level_adjacent_edge_count", 0),
"first_level_edge_ids": topology.get("first_level_edge_ids", (edge_id,)),
"first_level_edge_count": topology.get("first_level_edge_count", 1),
"first_level_adjacent_face_ids": topology.get("first_level_adjacent_face_ids", ()),
"first_level_adjacent_face_count": topology.get("first_level_adjacent_face_count", 0),
"first_level_topology_note": topology.get("first_level_topology_note", ""),
**facts,
}
def _feature_info_for_selected_face(self, face_id: int, fallback_info: dict[str, object]) -> dict[str, object]:
if self.model is None:
return dict(fallback_info)
@@ -415,22 +471,28 @@ class WindowStateMixin:
if self.model is None:
return {}
detection_level = self._current_feature_detection_level()
if detection_level == "current-only":
root_info = self._feature_info_for_selected_face(face_id, self.model.quick_face_info(face_id))
else:
root_info = self.model.feature_info(face_id)
if str(root_info.get("surface", "") or "") == "plane":
root_info = self._feature_info_for_selected_face(face_id, self.model.quick_face_info(face_id))
if str(root_info.get("surface", "") or "") == "plane" and "topology_relation_depth" not in root_info:
root_info.update(self._face_first_level_selection_fields(face_id))
elif str(root_info.get("surface", "") or "") == "cylinder" and detection_level != "current-only":
root_info.update(self._cylindrical_first_level_selection_fields(face_id))
associated: list[dict[str, object]] = []
if detection_level in {"associated-only", "secondary"}:
try:
associated = self.model.associated_feature_infos(face_id)
associated = self.model.associated_feature_infos(
face_id,
max_depth=3,
max_scan_faces=72,
max_features=10,
time_budget_seconds=0.55 if detection_level == "associated-only" else 0.75,
lightweight=True,
)
except Exception:
associated = []
if detection_level == "secondary":
associated = self._secondary_associated_feature_infos(face_id, associated)
associated = self._secondary_associated_feature_infos(
face_id,
associated,
time_budget_seconds=0.45,
)
highlight_ids = set(_int_values(root_info.get("feature_highlight_face_ids")) or [face_id])
for item in associated:
@@ -509,11 +571,19 @@ class WindowStateMixin:
self,
source_face_id: int,
direct_infos: list[dict[str, object]],
*,
time_budget_seconds: float | None = None,
) -> list[dict[str, object]]:
if self.model is None:
return list(direct_infos)
results: list[dict[str, object]] = []
seen: set[tuple[str, tuple[int, ...]]] = set()
deadline = None
if time_budget_seconds is not None and time_budget_seconds > 0:
deadline = time.monotonic() + float(time_budget_seconds)
def budget_expired() -> bool:
return deadline is not None and time.monotonic() >= deadline
def add(info: dict[str, object]) -> None:
source_id = _int_or_none(info.get("association_source_face_id"))
@@ -529,17 +599,26 @@ class WindowStateMixin:
for info in direct_infos:
add(info)
for info in list(results):
if budget_expired():
break
parent_id = _int_or_none(info.get("association_source_face_id"))
if parent_id is None:
continue
try:
nested_budget = None
if deadline is not None:
nested_budget = max(0.02, min(0.12, deadline - time.monotonic()))
for nested in self.model.associated_feature_infos(
parent_id,
max_depth=2,
max_scan_faces=48,
max_features=6,
max_depth=1,
max_scan_faces=16,
max_features=4,
time_budget_seconds=nested_budget,
lightweight=True,
):
add(nested)
if budget_expired():
break
except Exception:
continue
results.sort(
@@ -1293,7 +1372,7 @@ class WindowStateMixin:
action_item = self._property_table_item(action_text, editable=False)
target_item.setToolTip(self._property_target_tooltip(effective_spec, editable=editable))
row_items = (label_item, current_item, scope_item, target_item, action_item)
self._style_property_row_items(row_items, editable=editable)
self._style_property_row_items(row_items, editable=editable, spec=effective_spec)
for column, item in enumerate(row_items):
item.setToolTip(item.toolTip() or item.text())
self.property_table.setItem(row, column, item)
@@ -1319,6 +1398,8 @@ class WindowStateMixin:
def _sort_property_specs_for_display(self, specs: list[dict[str, object]]) -> list[dict[str, object]]:
def rank(spec: dict[str, object]) -> int:
if bool(spec.get("pin_top")):
return -1
editable = bool(spec.get("editable"))
enabled = bool(spec.get("enabled"))
has_action = bool(spec.get("action"))
@@ -1340,8 +1421,24 @@ class WindowStateMixin:
items: tuple[QTableWidgetItem, QTableWidgetItem, QTableWidgetItem, QTableWidgetItem, QTableWidgetItem],
*,
editable: bool,
spec: dict[str, object] | None = None,
) -> None:
label_item, current_item, scope_item, target_item, status_item = items
if spec is not None and bool(spec.get("pin_top")):
for item in items:
item.setBackground(QColor("#eef6ff"))
label_item.setForeground(QColor("#075985"))
current_item.setForeground(QColor("#0f172a"))
scope_item.setForeground(QColor("#0369a1"))
target_item.setForeground(QColor("#64748b"))
status_item.setForeground(QColor("#0369a1"))
label_font = label_item.font()
label_font.setBold(True)
label_item.setFont(label_font)
current_font = current_item.font()
current_font.setBold(True)
current_item.setFont(current_font)
return
if editable:
row_backgrounds = ("#fff7ed", "#fffbeb", "#fff7ed", "#fff7ed", "#fff7ed")
for item, color in zip(items, row_backgrounds):
@@ -1421,8 +1518,8 @@ class WindowStateMixin:
tip_key = "enabled_tip" if bool(mode.get("enabled", True)) else "disabled_tip"
tip = str(mode.get(tip_key) or mode.get("enabled_tip") or mode.get("disabled_tip") or "").strip()
if tip:
return f"影响范围{label}\n\n{tip}"
return f"影响范围{label}"
return f"建模意图{label}\n\n{tip}"
return f"建模意图{label}"
def _set_property_target_editor(self, row: int, spec: dict[str, object]) -> None:
editor = QLineEdit(str(spec.get("target_text", "")))
@@ -1632,10 +1729,15 @@ class WindowStateMixin:
dimension["label"] = label_overrides[key]
dimensions.append(dimension)
pinned_explanations = [
dict(spec)
for spec in specs
if str(spec.get("key", "")) in FEATURE_EDIT_SEMANTICS_KEYS and bool(spec.get("pin_top"))
]
explanations = [
dict(spec)
for spec in specs
if str(spec.get("key", "")) in FEATURE_EDIT_SEMANTICS_KEYS
if str(spec.get("key", "")) in FEATURE_EDIT_SEMANTICS_KEYS and not bool(spec.get("pin_top"))
]
if not dimensions:
dimensions.append(
@@ -1654,7 +1756,7 @@ class WindowStateMixin:
),
}
)
return dimensions + explanations
return pinned_explanations + dimensions + explanations
def _feature_context_property_specs(
self,
@@ -1675,6 +1777,7 @@ class WindowStateMixin:
related_specs, _used = self._editable_property_specs(related_info)
feature_label = str(related_info.get("feature_type") or related_info.get("feature_guess") or "关联特征")
source_face_id = _int_or_none(related_info.get("association_source_face_id"))
feature_label = str(related_info.get("association_label") or feature_label)
for spec in self._feature_property_specs(related_specs, related_info):
if spec.get("parameter_role") != "dimension":
continue
@@ -2149,6 +2252,7 @@ class WindowStateMixin:
label: str,
text: str,
tip: str,
pin_top: bool = False,
) -> None:
specs.append(
{
@@ -2161,9 +2265,196 @@ class WindowStateMixin:
"enabled": False,
"status_text": "说明",
"disabled_tip": tip or text,
"pin_top": pin_top,
}
)
def cad_modeling_form() -> tuple[str, str]:
if has_face:
if is_hole_or_groove:
if is_slot_or_half_hole:
return (
"工程特征 / 槽:按槽宽、槽深、弧长、总长或轴心做局部重建。",
"这类对象优先按 Creo 式槽特征理解;当前只传播直接相邻的一级关系,复杂二级/三级联动后续再开放。",
)
return (
"工程特征 / 孔:按孔径、轴心、封堵或盲孔/盲槽深度做重切/补料。",
"这类对象优先按 Creo 式孔特征理解;通孔、盲孔、锥孔和槽孔会根据识别结果开放不同参数。",
)
if is_boss:
return (
"工程特征 / 凸台:按直径、高度或轴心做局部重建。",
"凸台修改会尽量按局部补料、切除端盖或重建包络处理;也可以选择缩放所属对象这类整体语义。",
)
if is_existing_fillet:
return (
"工程特征 / 倒圆角:移除已有圆角后按目标半径重新倒圆。",
"STEP 没有原始倒圆角历史时,只能基于当前圆角面和支撑面推断;复杂 blend 会阻止或回滚。",
)
if is_shell_candidate:
return (
"工程特征 / 抽壳:按壳体厚度或相对面距离调整薄壁区域。",
"壳体厚度修改本质上是移动当前平面或沿厚度方向缩放所属对象,需要相对面和厚度方向识别稳定。",
)
if is_plane:
return (
"柔性建模 / 拉伸切除 / 偏移变换:平面 Face 可按不同意图改变位置、面积或尺寸。",
"平面 Face 不是直接暴露 B-Rep 参数;界面会把目标值映射为拉伸/切除、局部重建、移动特征或缩放特征。",
)
if is_cone:
return (
"工程特征 / 拔模或锥孔:只在简单圆锥或可识别锥孔上开放半角和参考半径。",
"复杂圆锥面可能只是拔模、过渡或导入后的自由裁剪面,当前不会把不可靠参数伪装成可修改特征。",
)
if is_sphere:
return (
"解析曲面 / 缩放特征:球面半径通过缩放所属对象调整。",
"当前不是恢复 CAD 历史里的球面特征,而是围绕识别到的中心做受限几何缩放。",
)
if is_torus:
return (
"解析曲面 / 缩放特征:环面主半径和小半径通过缩放所属对象调整。",
"当前不是恢复 CAD 历史里的环面特征,而是围绕识别到的环面中心和轴线做受限几何缩放。",
)
if is_generic_surface:
surface_name = _format_info_value("surface", surface)
return (
f"自由/复杂曲面:{surface_name} 当前只读,不开放参数化修改。",
"自由曲面需要单独的曲面替换、重拟合或控制点编辑语义;当前一级阶段不把底层 UV 参数当成用户尺寸。",
)
if is_cylinder:
return (
"解析圆柱 / 拉伸切除:可按直径、高度或轴向缩放处理普通圆柱面。",
"如果识别不到孔、槽、凸台或圆角语义,当前只按普通圆柱几何入口开放有限参数。",
)
return (
"导入几何 Face:当前只显示可识别参数。",
"STEP 通常没有完整建模历史;只有能映射到稳定 CAD 建模形式的参数才会开放修改。",
)
if has_edge:
if curve == "line":
return (
"柔性建模 / 移动几何:直线 Edge 可改长度、端点,并作为倒角/倒圆角入口。",
"Edge 长度有多种 CAD 语义:只改当前边、移动端面、相邻圆柱联动或缩放所属对象,界面会让用户选择。",
)
if curve == "circle":
return (
"工程特征入口 / 圆 Edge:可作为孔槽、圆角或相邻圆柱尺寸的参考。",
"圆 Edge 本身通常不是独立历史特征;修改时会优先寻找相邻圆柱、孔槽或圆角语义。",
)
return (
"曲线 Edge:当前只开放能稳定映射的尺寸或倒角/圆角入口。",
"复杂曲线 Edge 暂不直接做自由形变,避免把拓扑边参数误当成 CAD 设计尺寸。",
)
if self.selected_solid_id is not None or self.selected_part_id is not None:
return (
"整体特征 / 移动几何 / 缩放特征:当前对象按整体变换处理。",
"零件或 Solid 层级不代表单个 CAD 历史特征;当前只开放清晰的整体移动、旋转或缩放语义。",
)
return "", ""
def cad_recommended_operation() -> tuple[str, str]:
if has_face:
if is_hole_or_groove:
if is_slot_or_half_hole:
return (
"优先改槽宽、槽深或槽孔总长度;要换位置时改轴心。",
"槽类修改先走局部重建;如果用户选择缩放特征,会连同所属对象上的其它尺寸一起变化。",
)
return (
"优先改孔径/半径;盲孔改深度;要换位置时改轴心。",
"孔类修改会尽量先补旧孔再按目标重新切孔;螺纹孔、复杂孔组和底面识别不稳的盲孔仍可能受限。",
)
if is_boss:
return (
"优先改凸台直径或高度;要换位置时改轴心。",
"凸台高度更接近拉伸/切除端盖;凸台直径和轴心更接近移除旧包络后重建局部凸台。",
)
if is_existing_fillet:
return (
"优先改圆角半径;复杂圆角链先不要用整体缩放替代。",
"已有圆角需要先恢复支撑锐边再重新倒圆,支撑面不明确时失败回滚是正常保护。",
)
if is_shell_candidate:
return (
"优先改壳体厚度;普通位置变化再用偏移变换。",
"壳体厚度需要相对面识别稳定;如果只想移动整个对象,请选择移动特征或缩放特征语义。",
)
if is_plane:
return (
"优先改偏移变换,并选择拉伸/切除;只想让当前面变形时再改面积、U/V尺寸或中心。",
"拉伸/切除更接近常见 CAD 加料/切料;局部重建会让相邻面自然跟随变形,不一定保持垂直。",
)
if is_cone:
return (
"简单圆锥优先改半角或参考半径;复杂拔模面先保持只读。",
"锥面可能来自拔模、沉孔或导入裁剪曲面,识别不稳时不直接开放大参数修改。",
)
if is_sphere:
return (
"优先改球面半径,并确认这是缩放所属对象。",
"当前没有恢复球面历史特征,只能按识别中心做受限几何缩放。",
)
if is_torus:
return (
"优先改环面主半径或小半径,并确认这是缩放所属对象。",
"当前没有恢复环面历史特征,只能按识别中心和轴线做受限几何缩放。",
)
if is_generic_surface:
return (
"当前建议只查看,不直接改自由曲面参数。",
"自由曲面需要曲面替换、重拟合或控制点编辑,不能用面积或包围盒伪装成稳定 CAD 尺寸。",
)
if is_cylinder:
return (
"优先改直径或高度;若它其实是孔、槽、凸台或圆角,请先提高特征探测级别。",
"普通圆柱入口是兜底几何编辑,不等同于已经识别到完整 CAD 特征。",
)
return (
"先查看识别摘要,再只修改标记为可修改的参数。",
"未映射到明确建模形式的 STEP Face 不应强行参数化。",
)
if has_edge:
if curve == "line":
return (
"优先改长度;想保持端面垂直选移动端面,想只动当前边选只改当前Edge。",
"同一条 Edge 改长可能对应多种 CAD 结果,当前必须显式选择建模意图。",
)
if curve == "circle":
return (
"优先让圆 Edge 关联到孔、槽、凸台或圆角直径,不直接自由变形这条边。",
"圆 Edge 往往是相邻特征的边界,直接改边可能破坏孔槽或圆角语义。",
)
return (
"只修改已明确开放的 Edge 参数;复杂曲线先保持只读。",
"复杂曲线需要约束或曲线重拟合,不适合直接用边长驱动。",
)
if self.selected_solid_id is not None or self.selected_part_id is not None:
return (
"优先使用整体移动、旋转或缩放;不要把整体对象误当成单个历史特征。",
"STEP 里零件/Solid 层级通常缺少装配约束和特征历史,整体操作要明确影响范围。",
)
return "", ""
cad_form_text, cad_form_tip = cad_modeling_form()
if cad_form_text:
add_readonly_spec(
key="cad_modeling_form",
label="建模形式",
text=cad_form_text,
tip=cad_form_tip,
pin_top=True,
)
recommended_text, recommended_tip = cad_recommended_operation()
if recommended_text:
add_readonly_spec(
key="cad_recommended_operation",
label="推荐操作",
text=recommended_text,
tip=recommended_tip,
pin_top=True,
)
if has_face:
topology_depth = _int_or_none(action_info.get("topology_relation_depth"))
is_cylindrical_topology = bool(
@@ -2225,7 +2516,7 @@ class WindowStateMixin:
if is_slot_or_half_hole:
add_readonly_spec(
key="slot_edit_semantics",
label="编辑方式",
label="建模意图",
text="槽/半孔:局部扇形槽重建;槽孔总长度会尝试配对另一端。",
tip=(
"槽/半孔宽度、深度和圆弧参数会换算成圆柱半径或圆弧角度,"
@@ -2236,7 +2527,7 @@ class WindowStateMixin:
else:
add_readonly_spec(
key="hole_edit_semantics",
label="编辑方式",
label="建模意图",
text="孔:同轴重切孔壁;移动轴心会先填旧孔再切新孔。",
tip=(
"孔径/半径不是缩放整个模型,而是在当前孔轴线上做受限布尔重切;"
@@ -2247,7 +2538,7 @@ class WindowStateMixin:
elif is_boss:
add_readonly_spec(
key="boss_edit_semantics",
label="编辑方式",
label="建模意图",
text="凸台:直径重建包络;高度拉伸/切除端盖;轴心先移除再补新凸台。",
tip=(
"凸台直径会通过局部布尔补料或移除旧包络后重建目标圆柱;"
@@ -2258,7 +2549,7 @@ class WindowStateMixin:
elif is_existing_fillet:
add_readonly_spec(
key="existing_fillet_edit_semantics",
label="编辑方式",
label="建模意图",
text="圆角:移除已有圆角面,再尝试按目标半径重新倒圆。",
tip=(
"已有圆角修改不是恢复 CAD 历史参数;当前会先 defeature 当前圆角面,"
@@ -2268,11 +2559,11 @@ class WindowStateMixin:
elif is_cone or is_sphere or is_torus:
add_readonly_spec(
key="analytic_surface_edit_semantics",
label="编辑方式",
label="建模意图",
text="解析曲面:围绕中心或轴线缩放所属对象,会影响同对象其它尺寸。",
tip=(
"圆锥、球面、环面这类曲面当前走几何缩放语义,"
"不是只替换单个曲面的历史参数;执行前需要确认影响范围"
"不是只替换单个曲面的历史参数;执行前需要确认建模意图"
),
)
elif is_generic_surface:
@@ -2284,7 +2575,7 @@ class WindowStateMixin:
)
add_readonly_spec(
key="freeform_surface_edit_semantics",
label="编辑方式",
label="建模意图",
text=f"{surface_name}:暂不支持参数化编辑。",
tip=(
f"{blocker} 当前不会把自由曲面的面积、中心、包围盒或底层 UV 参数伪装成可修改尺寸;"
@@ -2311,11 +2602,11 @@ class WindowStateMixin:
),
tip=topology_tip,
)
face_semantics_text = "Face:先改目标值,再用“影响范围”选择局部重建、拉伸/切除、移动特征或缩放特征。"
face_semantics_text = "Face:先改目标值,再用“建模意图”选择局部重建、拉伸/切除、移动特征或缩放特征。"
face_semantics_tip = (
"面积是 Face 的几何面积;U向尺寸/V向尺寸是这个 Face 自身参数方向上的两个投影尺寸;"
"偏移曲面使用 Creo 曲面 Offset 语义,表示沿当前 Face 法向得到的目标位置。"
"影响范围决定这次修改是局部重建当前 Face、按拉伸/切除加减材料,"
"偏移变换使用 Creo 柔性建模里的 Offset 语义,表示沿当前 Face 法向得到的目标位置。"
"建模意图决定这次修改是局部重建当前 Face、按拉伸/切除加减材料,"
"还是移动或缩放所属特征/Solid。"
)
if is_plane and not local_face_deform_ready:
@@ -2327,7 +2618,7 @@ class WindowStateMixin:
)
add_readonly_spec(
key="face_edit_semantics",
label="编辑方式",
label="建模意图",
text=face_semantics_text,
tip=face_semantics_tip,
)
@@ -2335,10 +2626,10 @@ class WindowStateMixin:
if has_edge:
add_readonly_spec(
key="edge_edit_semantics",
label="编辑方式",
label="建模意图",
text="Edge:长度修改可选择局部形变、移动端面或缩放所属对象。",
tip=(
"当前Edge会重建局部相邻面;移动端面更像整体尺寸变化;"
"当前Edge会重建局部相邻面;移动端面更像整体尺寸变化;"
"缩放所属会影响同一特征或 Solid 上的其它尺寸。"
),
)
@@ -2571,7 +2862,7 @@ class WindowStateMixin:
)
add_scoped_spec(
key="face_target_normal_position",
label="偏移曲面",
label="偏移变换",
current_raw=current_plane_position if current_plane_position is not None else "",
target_text=numeric_text(current_plane_position),
scope_default="push_pull",
@@ -2581,10 +2872,10 @@ class WindowStateMixin:
"action": "push_pull_face",
"target_attr": "offset_input",
"enabled": current_plane_position is not None,
"enabled_tip": "输入偏移曲面的目标位置;程序会沿当前 Face 法向执行拉伸或切除。",
"enabled_tip": "输入偏移变换的目标位置;程序会沿当前 Face 法向执行拉伸或切除。",
"disabled_tip": "当前平面缺少稳定移动方向或基准点,不能按目标位置拉伸/切除。",
"range_hint": (
"偏移曲面是沿当前 Face 法向测量的目标位置,不是面积、不是移动距离,也不是 X/Y/Z 坐标;单位同模型。"
"偏移变换是沿当前 Face 法向测量的目标位置,不是面积、不是移动距离,也不是 X/Y/Z 坐标;单位同模型。"
"程序会把目标位置自动换算成本次拉伸/切除距离。"
f"{face_offset_hard_limit}"
),
@@ -2602,11 +2893,11 @@ class WindowStateMixin:
and current_face_center is not None
),
"enabled_tip": (
"输入偏移曲面的目标位置;程序只把当前 Face 沿法向移动到该目标值,"
"输入偏移变换的目标位置;程序只把当前 Face 沿法向移动到该目标值,"
"并让相邻平面按新顶点重建。"
),
"disabled_tip": face_local_disabled_tip(
"当前平面缺少稳定方向、基准点或中心,不能按偏移曲面局部重建当前 Face。"
"当前平面缺少稳定方向、基准点或中心,不能按偏移变换局部重建当前 Face。"
),
"range_hint": (
"局部重建不会自动加料/切削,相邻面可能自然变斜,"
@@ -2626,10 +2917,10 @@ class WindowStateMixin:
and (self.selected_part_id is not None or self.selected_solid_id is not None)
),
"enabled_tip": (
"输入偏移曲面的目标位置;程序会沿当前 Face 法向平移所属特征或 Solid,"
"输入偏移变换的目标位置;程序会沿当前 Face 法向平移所属特征或 Solid,"
"当前面形状和所属对象内部尺寸不变。"
),
"disabled_tip": "当前平面缺少稳定方向、基准点或所属对象,不能按偏移曲面移动所属特征。",
"disabled_tip": "当前平面缺少稳定方向、基准点或所属对象,不能按偏移变换移动所属特征。",
"range_hint": (
"这是移动所属对象,不是拉伸/切除当前 Face;如果想改变形状或厚度,请选择“拉伸/切除”。"
f"{translation_hint()} {face_offset_hard_limit}"
@@ -2682,7 +2973,7 @@ class WindowStateMixin:
"disabled_tip": "当前壳体候选缺少稳定厚度、厚度方向、基准平面或所属对象,不能按厚度缩放所属特征。",
"range_hint": (
"这是缩放所属对象,不是单独移动当前平面;"
f"如果只想移动当前壳体平面区域,请把影响范围设为“拉伸/切除”。 {relative_range_hint(current, 0.2, 0.5, face_linear_hard_limit)}"
f"如果只想移动当前壳体平面区域,请把建模意图设为“拉伸/切除”。 {relative_range_hint(current, 0.2, 0.5, face_linear_hard_limit)}"
),
},
},
@@ -4788,7 +5079,7 @@ class WindowStateMixin:
self._update_action_states()
self._refresh_property_editor()
label = self.edge_length_strategy_combo.currentText() if hasattr(self, "edge_length_strategy_combo") else ""
self.statusBar().showMessage(f"已设置修改策略{label or '自动策略'}")
self.statusBar().showMessage(f"已设置建模意图{label or '自动选择'}")
def set_chamfer_reference_face(self) -> None:
self._update_action_states()
@@ -5126,6 +5417,7 @@ class WindowStateMixin:
return result
if self.selected_kind == "edge" and self.selected_edge_id is not None:
result = self.model.edge_info(self.selected_edge_id)
result.update(self._edge_first_level_selection_fields(self.selected_edge_id))
self._selected_action_info_cache_key = cache_key
self._selected_action_info_cache_value = dict(result)
return result
@@ -5200,6 +5492,7 @@ class WindowStateMixin:
resolved_edge_id = self._resolve_record_edge_id(record)
if resolved_edge_id is not None:
info = self.model.edge_info(resolved_edge_id)
info.update(self._edge_first_level_selection_fields(resolved_edge_id))
self.selected_kind = "edge"
self.selected_edge_id = resolved_edge_id
self.selected_part_id = int(info["part_id"])