feat: 推进一级关系参数化编辑与参数导出
This commit is contained in:
+429
-166
@@ -26,6 +26,7 @@ from PySide6.QtGui import QColor
|
||||
|
||||
from .constants import FACE_SELECTION_FEATURE_INFO_SURFACES, FREEFORM_FACE_SURFACES, SNAPSHOT_FACE_LOGICAL_IDS_KEY
|
||||
from .model import StepModel
|
||||
from .parametric_component import component_edit_config_from_spec
|
||||
from .records import OperationRecord
|
||||
from .ui_helpers import * # noqa: F403
|
||||
from .widgets import NoWheelComboBox
|
||||
@@ -172,6 +173,11 @@ def _feature_dimension_keys(action_info: dict[str, object]) -> tuple[str, ...]:
|
||||
surface = str(action_info.get("surface", "") or "")
|
||||
feature_guess = str(action_info.get("feature_guess", "") or "")
|
||||
angular_span = _float_or_none(action_info.get("angular_span"))
|
||||
face_size_keys = (
|
||||
("local_face_width", "local_face_height")
|
||||
if bool(action_info.get("local_face_size_edit_ready"))
|
||||
else ()
|
||||
)
|
||||
|
||||
if surface == "plane":
|
||||
if action_info.get("multistep_prismatic_status") == "blocked":
|
||||
@@ -179,7 +185,8 @@ def _feature_dimension_keys(action_info: dict[str, object]) -> tuple[str, ...]:
|
||||
if action_info.get("existing_chamfer_status") == "candidate":
|
||||
return ("existing_chamfer_distance_estimate",)
|
||||
if action_info.get("prismatic_profile_status") == "candidate":
|
||||
keys = ["local_face_width", "local_face_height"]
|
||||
keys = list(face_size_keys)
|
||||
keys.append("face_target_normal_position")
|
||||
if action_info.get("prismatic_extrusion_status") == "candidate":
|
||||
keys.append("shell_thickness_estimate")
|
||||
if str(action_info.get("prismatic_feature_semantics") or "") in {"additive-boss", "subtractive-pocket"}:
|
||||
@@ -187,15 +194,13 @@ def _feature_dimension_keys(action_info: dict[str, object]) -> tuple[str, ...]:
|
||||
return tuple(keys)
|
||||
if action_info.get("shell_region_status") == "candidate":
|
||||
return (
|
||||
"local_face_width",
|
||||
"local_face_height",
|
||||
*face_size_keys,
|
||||
"face_center_position",
|
||||
"face_target_normal_position",
|
||||
"shell_thickness_estimate",
|
||||
)
|
||||
return (
|
||||
"local_face_width",
|
||||
"local_face_height",
|
||||
*face_size_keys,
|
||||
"face_center_position",
|
||||
"face_target_normal_position",
|
||||
)
|
||||
@@ -340,11 +345,27 @@ class WindowStateMixin:
|
||||
title = f"{title_prefix} {logical_id}"
|
||||
if logical_id != int(face_id):
|
||||
title += f"(当前拓扑 Face {face_id})"
|
||||
return {
|
||||
result = {
|
||||
"selection_title": title,
|
||||
"selection_display_id": logical_id,
|
||||
"selection_topological_face_id": int(face_id),
|
||||
}
|
||||
if self.model is not None:
|
||||
try:
|
||||
region_ids = self.model.face_region_ids(face_id)
|
||||
except Exception:
|
||||
region_ids = []
|
||||
region_ids = sorted({int(item) for item in region_ids if 0 <= int(item) < len(self.model.faces)})
|
||||
if len(region_ids) > 1:
|
||||
result.update(
|
||||
{
|
||||
"face_region_logical_id": logical_id,
|
||||
"face_region_ids": tuple(region_ids),
|
||||
"face_region_count": len(region_ids),
|
||||
"face_region_note": "STEP/B-Rep 将该局部面拆成多个同域拓扑 Face;界面按一个逻辑面区域显示,高亮包含全部碎片。",
|
||||
}
|
||||
)
|
||||
return result
|
||||
|
||||
def _first_level_fact_selection_fields(self, face_id: int, scope: str = "auto") -> dict[str, object]:
|
||||
if self.model is None:
|
||||
@@ -553,6 +574,21 @@ class WindowStateMixin:
|
||||
info.setdefault("feature_type", "可拉伸/切除平面候选")
|
||||
info.setdefault("feature_edit_actions", "拉伸/切除平面")
|
||||
elif surface == "cylinder":
|
||||
same_domain_ids = []
|
||||
try:
|
||||
same_domain_ids = self.model.connected_same_domain_face_ids(face_id) or [face_id]
|
||||
except Exception:
|
||||
same_domain_ids = [face_id]
|
||||
same_domain_ids = sorted({int(item) for item in same_domain_ids if 0 <= int(item) < len(self.model.faces)})
|
||||
if same_domain_ids:
|
||||
info["same_domain_face_ids"] = tuple(same_domain_ids)
|
||||
info["same_domain_face_count"] = len(same_domain_ids)
|
||||
info["feature_highlight_face_ids"] = tuple(same_domain_ids)
|
||||
if len(same_domain_ids) > 1:
|
||||
info.setdefault(
|
||||
"same_domain_note",
|
||||
"当前圆柱面在 STEP/B-Rep 中被拆成多个同域拓扑 Face,界面按一个局部孔/槽区域高亮。",
|
||||
)
|
||||
feature_guess = str(info.get("feature_guess", "") or "")
|
||||
angular_span = _float_or_none(info.get("angular_span"))
|
||||
if feature_guess == "hole/groove candidate":
|
||||
@@ -768,7 +804,7 @@ class WindowStateMixin:
|
||||
if self.load_in_progress:
|
||||
self.statusBar().showMessage("STEP background loading is still running.")
|
||||
return True
|
||||
if self.operation_in_progress:
|
||||
if self.operation_in_progress and not bool(getattr(self, "_property_batch_running_action", False)):
|
||||
self.statusBar().showMessage(message)
|
||||
return True
|
||||
if self.scan_in_progress:
|
||||
@@ -2004,7 +2040,7 @@ class WindowStateMixin:
|
||||
)
|
||||
target_item.setToolTip(self._property_target_tooltip(effective_spec, editable=editable))
|
||||
input_item = self._property_table_item("", editable=False)
|
||||
input_item.setToolTip("勾选后点击“导出参数”,会把这一行作为输入参数写入 data.json。")
|
||||
input_item.setToolTip("勾选后点击“导出参数”,会写入根目录 data.json,并把参数列表嵌入组件 main.py。")
|
||||
row_items = (label_item, current_item, scope_item, target_item, input_item)
|
||||
self._style_property_row_items(row_items, editable=editable, spec=effective_spec)
|
||||
for column, item in enumerate(row_items):
|
||||
@@ -2191,7 +2227,7 @@ class WindowStateMixin:
|
||||
checkbox.setChecked(False)
|
||||
checkbox.setEnabled(exportable)
|
||||
checkbox.setCursor(Qt.CursorShape.PointingHandCursor if exportable else Qt.CursorShape.ArrowCursor)
|
||||
tip = "勾选后点击“导出参数”,会把这一行作为输入参数写入 data.json。"
|
||||
tip = "勾选后点击“导出参数”,会写入根目录 data.json,并把参数列表嵌入组件 main.py。"
|
||||
if not exportable:
|
||||
tip = "当前行不是可导出的尺寸输入参数。"
|
||||
checkbox.setToolTip(tip)
|
||||
@@ -2288,7 +2324,7 @@ class WindowStateMixin:
|
||||
finally:
|
||||
checkbox.blockSignals(was_blocked)
|
||||
tip = (
|
||||
"勾选后点击“导出参数”,会把这一行作为输入参数写入 data.json。"
|
||||
"勾选后点击“导出参数”,会写入根目录 data.json,并把参数列表嵌入组件 main.py。"
|
||||
if exportable
|
||||
else "当前行不是可导出的尺寸输入参数。"
|
||||
)
|
||||
@@ -2442,16 +2478,21 @@ class WindowStateMixin:
|
||||
prismatic_length = _float_or_none(action_info.get("prismatic_length"))
|
||||
prismatic_width = _float_or_none(action_info.get("prismatic_width"))
|
||||
prismatic_depth = _float_or_none(action_info.get("prismatic_extrusion_estimate"))
|
||||
prismatic_reference_face_id = _int_or_none(action_info.get("prismatic_reference_face_id"))
|
||||
part_solid_count = _int_or_none(action_info.get("part_solid_count"))
|
||||
prismatic_size_supported = bool(
|
||||
embedded_prismatic
|
||||
and action_info.get("prismatic_profile_status") == "candidate"
|
||||
and action_info.get("prismatic_extrusion_status") == "candidate"
|
||||
and bool(action_info.get("local_face_size_edit_ready"))
|
||||
and prismatic_length is not None
|
||||
and prismatic_length > 0
|
||||
and prismatic_width is not None
|
||||
and prismatic_width > 0
|
||||
and prismatic_depth is not None
|
||||
and prismatic_depth > 0
|
||||
and prismatic_reference_face_id is not None
|
||||
and (part_solid_count is None or part_solid_count == 1)
|
||||
and len(_int_values(action_info.get("prismatic_connected_side_face_ids"))) == 4
|
||||
)
|
||||
dimensions: list[dict[str, object]] = []
|
||||
@@ -2470,14 +2511,16 @@ class WindowStateMixin:
|
||||
dimension = dict(spec)
|
||||
dimension["parameter_role"] = "dimension"
|
||||
if action_info.get("prismatic_profile_status") == "candidate":
|
||||
if prismatic_size_key:
|
||||
if not prismatic_size_supported:
|
||||
continue
|
||||
if prismatic_size_key and prismatic_size_supported:
|
||||
modes = spec.get("scope_modes")
|
||||
local_mode = modes.get("local") if isinstance(modes, dict) else None
|
||||
local_mode = dict(local_mode) if isinstance(local_mode, dict) else {}
|
||||
is_width_axis = key == "local_face_width"
|
||||
current_limit = prismatic_width if is_width_axis else prismatic_length
|
||||
size_blocker = str(
|
||||
action_info.get("local_face_size_edit_blocker")
|
||||
or "当前矩形特征的长宽、深度、参考面、单 Solid 条件或四个侧壁没有稳定识别,暂不开放长宽修改。"
|
||||
)
|
||||
local_mode.update(
|
||||
{
|
||||
"label": "局部重建",
|
||||
@@ -2488,7 +2531,7 @@ class WindowStateMixin:
|
||||
"输入矩形凸台/口袋的目标长度或宽度;程序会先移除/补回旧矩形包络,"
|
||||
"再重建目标矩形特征。"
|
||||
),
|
||||
"disabled_tip": "当前矩形特征的长宽、深度或四个侧壁没有稳定识别,暂不开放长宽修改。",
|
||||
"disabled_tip": size_blocker,
|
||||
"range_hint": "当前版本不在一次编辑中交换长度和宽度方向。",
|
||||
}
|
||||
)
|
||||
@@ -2752,6 +2795,8 @@ class WindowStateMixin:
|
||||
show_generic_face_edit_specs = bool(has_face and (is_plane or is_shell_candidate) and not is_existing_chamfer)
|
||||
local_face_deform_ready = bool(action_info.get("local_face_deform_ready", True))
|
||||
local_face_deform_blocker = str(action_info.get("local_face_deform_blocker") or "").strip()
|
||||
local_face_size_edit_ready = bool(action_info.get("local_face_size_edit_ready"))
|
||||
local_face_size_edit_blocker = str(action_info.get("local_face_size_edit_blocker") or "").strip()
|
||||
has_fillet_support = len(_int_values(action_info.get("feature_existing_fillet_support_face_ids"))) >= 2
|
||||
is_line_edge = has_edge and curve == "line"
|
||||
specs: list[dict[str, object]] = []
|
||||
@@ -3553,147 +3598,150 @@ class WindowStateMixin:
|
||||
current_face_width = _float_or_none(action_info.get("local_face_width"))
|
||||
current_face_height = _float_or_none(action_info.get("local_face_height"))
|
||||
face_size_tip = (
|
||||
"这里的面内长度/面内宽度是选中 Face 在自身平面内两个主方向上的投影长度,"
|
||||
"不是面积,也不是模型整体高度。局部重建会移动当前 Face 顶点,相邻面会按新顶点重建。"
|
||||
"面内长度/面内宽度是矩形平面特征的两个驱动尺寸,"
|
||||
"不是面积,也不是任意曲面的通用尺寸。局部重建会重建当前矩形特征,相邻面按新边界更新。"
|
||||
)
|
||||
face_size_owner_tip = (
|
||||
"这里的面内长度/面内宽度是选中 Face 在自身平面内两个主方向上的投影长度,"
|
||||
"不是面积,也不是模型整体高度。程序会沿该方向缩放所属特征或 Solid,其它几何会跟随变化。"
|
||||
"面内长度/面内宽度是矩形平面特征的两个驱动尺寸,"
|
||||
"不是面积,也不是任意曲面的通用尺寸。程序会沿该方向缩放所属特征或 Solid,其它几何会跟随变化。"
|
||||
)
|
||||
add_scoped_spec(
|
||||
key="local_face_width",
|
||||
label="面内长度",
|
||||
current_raw=current_face_width if current_face_width is not None else "",
|
||||
target_text=numeric_text(current_face_width),
|
||||
scope_default="local",
|
||||
scope_modes={
|
||||
"local": {
|
||||
"label": "局部重建",
|
||||
"action": "resize_face_width_local",
|
||||
"target_attr": "face_width_input",
|
||||
"enabled": bool(
|
||||
local_face_deform_ready
|
||||
and current_face_width is not None
|
||||
and current_face_width > 0
|
||||
and current_face_center is not None
|
||||
),
|
||||
"enabled_tip": f"输入目标面内长度;{face_size_tip}",
|
||||
"disabled_tip": face_local_disabled_tip(
|
||||
"只有带稳定顶点环和中心坐标的平面 Face 才能尝试修改面内长度。"
|
||||
),
|
||||
"range_hint": f"{face_size_tip} {relative_range_hint(current_face_width, 0.25, 0.8, face_linear_hard_limit)}",
|
||||
if local_face_size_edit_ready:
|
||||
add_scoped_spec(
|
||||
key="local_face_width",
|
||||
label="面内长度",
|
||||
current_raw=current_face_width if current_face_width is not None else "",
|
||||
target_text=numeric_text(current_face_width),
|
||||
scope_default="local",
|
||||
scope_modes={
|
||||
"local": {
|
||||
"label": "局部重建",
|
||||
"action": "resize_face_width_local",
|
||||
"target_attr": "face_width_input",
|
||||
"enabled": bool(
|
||||
local_face_deform_ready
|
||||
and current_face_width is not None
|
||||
and current_face_width > 0
|
||||
and current_face_center is not None
|
||||
),
|
||||
"enabled_tip": f"输入目标面内长度;{face_size_tip}",
|
||||
"disabled_tip": face_local_disabled_tip(
|
||||
local_face_size_edit_blocker
|
||||
or "当前对象还没有识别为稳定矩形平面特征,不能修改面内长度。"
|
||||
),
|
||||
"range_hint": f"{face_size_tip} {relative_range_hint(current_face_width, 0.25, 0.8, face_linear_hard_limit)}",
|
||||
},
|
||||
"keep_relations": {
|
||||
"label": "保持关系",
|
||||
"action": "resize_face_width_keep_relations",
|
||||
"target_attr": "face_width_input",
|
||||
"enabled": bool(
|
||||
current_face_width is not None
|
||||
and current_face_width > 0
|
||||
and current_face_center is not None
|
||||
and (self.selected_part_id is not None or self.selected_solid_id is not None)
|
||||
and face_keep_relation_enabled
|
||||
),
|
||||
"enabled_tip": (
|
||||
"输入目标面内长度;程序会沿该方向缩放所属特征或 Solid,"
|
||||
"并要求一级相邻平面的平行/垂直关系在执行前可验证、执行后可反查。"
|
||||
),
|
||||
"disabled_tip": (
|
||||
face_keep_relation_disabled_tip
|
||||
if not face_keep_relation_enabled
|
||||
else "当前 Face 缺少稳定面内长度、中心坐标或所属对象,不能保持关系地修改面内长度。"
|
||||
),
|
||||
"range_hint": (
|
||||
"保持关系会把面内长度修改转换为所属对象单向缩放,并在结果上反查一级平面关系;"
|
||||
f"{relative_range_hint(current_face_width, 0.25, 0.8, face_linear_hard_limit)}"
|
||||
),
|
||||
},
|
||||
"owning": {
|
||||
"label": "缩放特征",
|
||||
"action": "resize_face_width_owning_scale",
|
||||
"target_attr": "face_width_input",
|
||||
"enabled": bool(
|
||||
current_face_width is not None
|
||||
and current_face_width > 0
|
||||
and current_face_center is not None
|
||||
and (self.selected_part_id is not None or self.selected_solid_id is not None)
|
||||
),
|
||||
"enabled_tip": f"输入目标面内长度;{face_size_owner_tip}",
|
||||
"disabled_tip": "当前 Face 缺少稳定面内长度、中心坐标或所属对象,不能按这个方向缩放所属对象。",
|
||||
"range_hint": f"{face_size_owner_tip} {relative_range_hint(current_face_width, 0.2, 0.5, face_linear_hard_limit)}",
|
||||
},
|
||||
},
|
||||
"keep_relations": {
|
||||
"label": "保持关系",
|
||||
"action": "resize_face_width_keep_relations",
|
||||
"target_attr": "face_width_input",
|
||||
"enabled": bool(
|
||||
current_face_width is not None
|
||||
and current_face_width > 0
|
||||
and current_face_center is not None
|
||||
and (self.selected_part_id is not None or self.selected_solid_id is not None)
|
||||
and face_keep_relation_enabled
|
||||
),
|
||||
"enabled_tip": (
|
||||
"输入目标面内长度;程序会沿该方向缩放所属特征或 Solid,"
|
||||
"并要求一级相邻平面的平行/垂直关系在执行前可验证、执行后可反查。"
|
||||
),
|
||||
"disabled_tip": (
|
||||
face_keep_relation_disabled_tip
|
||||
if not face_keep_relation_enabled
|
||||
else "当前 Face 缺少稳定面内长度、中心坐标或所属对象,不能保持关系地修改面内长度。"
|
||||
),
|
||||
"range_hint": (
|
||||
"保持关系会把面内长度修改转换为所属对象单向缩放,并在结果上反查一级平面关系;"
|
||||
f"{relative_range_hint(current_face_width, 0.25, 0.8, face_linear_hard_limit)}"
|
||||
),
|
||||
value_type="positive",
|
||||
used=("local_face_width", "local_face_width_direction", "local_face_size_center"),
|
||||
**face_scale_limits(current_face_width),
|
||||
)
|
||||
add_scoped_spec(
|
||||
key="local_face_height",
|
||||
label="面内宽度",
|
||||
current_raw=current_face_height if current_face_height is not None else "",
|
||||
target_text=numeric_text(current_face_height),
|
||||
scope_default="local",
|
||||
scope_modes={
|
||||
"local": {
|
||||
"label": "局部重建",
|
||||
"action": "resize_face_height_local",
|
||||
"target_attr": "face_height_input",
|
||||
"enabled": bool(
|
||||
local_face_deform_ready
|
||||
and current_face_height is not None
|
||||
and current_face_height > 0
|
||||
and current_face_center is not None
|
||||
),
|
||||
"enabled_tip": f"输入目标面内宽度;{face_size_tip}",
|
||||
"disabled_tip": face_local_disabled_tip(
|
||||
local_face_size_edit_blocker
|
||||
or "当前对象还没有识别为稳定矩形平面特征,不能修改面内宽度。"
|
||||
),
|
||||
"range_hint": f"{face_size_tip} {relative_range_hint(current_face_height, 0.25, 0.8, face_linear_hard_limit)}",
|
||||
},
|
||||
"keep_relations": {
|
||||
"label": "保持关系",
|
||||
"action": "resize_face_height_keep_relations",
|
||||
"target_attr": "face_height_input",
|
||||
"enabled": bool(
|
||||
current_face_height is not None
|
||||
and current_face_height > 0
|
||||
and current_face_center is not None
|
||||
and (self.selected_part_id is not None or self.selected_solid_id is not None)
|
||||
and face_keep_relation_enabled
|
||||
),
|
||||
"enabled_tip": (
|
||||
"输入目标面内宽度;程序会沿该方向缩放所属特征或 Solid,"
|
||||
"并要求一级相邻平面的平行/垂直关系在执行前可验证、执行后可反查。"
|
||||
),
|
||||
"disabled_tip": (
|
||||
face_keep_relation_disabled_tip
|
||||
if not face_keep_relation_enabled
|
||||
else "当前 Face 缺少稳定面内宽度、中心坐标或所属对象,不能保持关系地修改面内宽度。"
|
||||
),
|
||||
"range_hint": (
|
||||
"保持关系会把面内宽度修改转换为所属对象单向缩放,并在结果上反查一级平面关系;"
|
||||
f"{relative_range_hint(current_face_height, 0.25, 0.8, face_linear_hard_limit)}"
|
||||
),
|
||||
},
|
||||
"owning": {
|
||||
"label": "缩放特征",
|
||||
"action": "resize_face_height_owning_scale",
|
||||
"target_attr": "face_height_input",
|
||||
"enabled": bool(
|
||||
current_face_height is not None
|
||||
and current_face_height > 0
|
||||
and current_face_center is not None
|
||||
and (self.selected_part_id is not None or self.selected_solid_id is not None)
|
||||
),
|
||||
"enabled_tip": f"输入目标面内宽度;{face_size_owner_tip}",
|
||||
"disabled_tip": "当前 Face 缺少稳定面内宽度、中心坐标或所属对象,不能按这个方向缩放所属对象。",
|
||||
"range_hint": f"{face_size_owner_tip} {relative_range_hint(current_face_height, 0.2, 0.5, face_linear_hard_limit)}",
|
||||
},
|
||||
},
|
||||
"owning": {
|
||||
"label": "缩放特征",
|
||||
"action": "resize_face_width_owning_scale",
|
||||
"target_attr": "face_width_input",
|
||||
"enabled": bool(
|
||||
current_face_width is not None
|
||||
and current_face_width > 0
|
||||
and current_face_center is not None
|
||||
and (self.selected_part_id is not None or self.selected_solid_id is not None)
|
||||
),
|
||||
"enabled_tip": f"输入目标面内长度;{face_size_owner_tip}",
|
||||
"disabled_tip": "当前 Face 缺少稳定面内长度、中心坐标或所属对象,不能按这个方向缩放所属对象。",
|
||||
"range_hint": f"{face_size_owner_tip} {relative_range_hint(current_face_width, 0.2, 0.5, face_linear_hard_limit)}",
|
||||
},
|
||||
},
|
||||
value_type="positive",
|
||||
used=("local_face_width", "local_face_width_direction", "local_face_size_center"),
|
||||
**face_scale_limits(current_face_width),
|
||||
)
|
||||
add_scoped_spec(
|
||||
key="local_face_height",
|
||||
label="面内宽度",
|
||||
current_raw=current_face_height if current_face_height is not None else "",
|
||||
target_text=numeric_text(current_face_height),
|
||||
scope_default="local",
|
||||
scope_modes={
|
||||
"local": {
|
||||
"label": "局部重建",
|
||||
"action": "resize_face_height_local",
|
||||
"target_attr": "face_height_input",
|
||||
"enabled": bool(
|
||||
local_face_deform_ready
|
||||
and current_face_height is not None
|
||||
and current_face_height > 0
|
||||
and current_face_center is not None
|
||||
),
|
||||
"enabled_tip": f"输入目标面内宽度;{face_size_tip}",
|
||||
"disabled_tip": face_local_disabled_tip(
|
||||
"只有带稳定顶点环和中心坐标的平面 Face 才能尝试修改面内宽度。"
|
||||
),
|
||||
"range_hint": f"{face_size_tip} {relative_range_hint(current_face_height, 0.25, 0.8, face_linear_hard_limit)}",
|
||||
},
|
||||
"keep_relations": {
|
||||
"label": "保持关系",
|
||||
"action": "resize_face_height_keep_relations",
|
||||
"target_attr": "face_height_input",
|
||||
"enabled": bool(
|
||||
current_face_height is not None
|
||||
and current_face_height > 0
|
||||
and current_face_center is not None
|
||||
and (self.selected_part_id is not None or self.selected_solid_id is not None)
|
||||
and face_keep_relation_enabled
|
||||
),
|
||||
"enabled_tip": (
|
||||
"输入目标面内宽度;程序会沿该方向缩放所属特征或 Solid,"
|
||||
"并要求一级相邻平面的平行/垂直关系在执行前可验证、执行后可反查。"
|
||||
),
|
||||
"disabled_tip": (
|
||||
face_keep_relation_disabled_tip
|
||||
if not face_keep_relation_enabled
|
||||
else "当前 Face 缺少稳定面内宽度、中心坐标或所属对象,不能保持关系地修改面内宽度。"
|
||||
),
|
||||
"range_hint": (
|
||||
"保持关系会把面内宽度修改转换为所属对象单向缩放,并在结果上反查一级平面关系;"
|
||||
f"{relative_range_hint(current_face_height, 0.25, 0.8, face_linear_hard_limit)}"
|
||||
),
|
||||
},
|
||||
"owning": {
|
||||
"label": "缩放特征",
|
||||
"action": "resize_face_height_owning_scale",
|
||||
"target_attr": "face_height_input",
|
||||
"enabled": bool(
|
||||
current_face_height is not None
|
||||
and current_face_height > 0
|
||||
and current_face_center is not None
|
||||
and (self.selected_part_id is not None or self.selected_solid_id is not None)
|
||||
),
|
||||
"enabled_tip": f"输入目标面内宽度;{face_size_owner_tip}",
|
||||
"disabled_tip": "当前 Face 缺少稳定面内宽度、中心坐标或所属对象,不能按这个方向缩放所属对象。",
|
||||
"range_hint": f"{face_size_owner_tip} {relative_range_hint(current_face_height, 0.2, 0.5, face_linear_hard_limit)}",
|
||||
},
|
||||
},
|
||||
value_type="positive",
|
||||
used=("local_face_height", "local_face_height_direction", "local_face_size_center"),
|
||||
**face_scale_limits(current_face_height),
|
||||
)
|
||||
value_type="positive",
|
||||
used=("local_face_height", "local_face_height_direction", "local_face_size_center"),
|
||||
**face_scale_limits(current_face_height),
|
||||
)
|
||||
add_scoped_spec(
|
||||
key="face_center_position",
|
||||
label="中心",
|
||||
@@ -3773,11 +3821,7 @@ class WindowStateMixin:
|
||||
)
|
||||
|
||||
if is_plane and not is_existing_chamfer:
|
||||
plane_origin = _triple_or_none(action_info.get("plane_origin"))
|
||||
plane_direction = (
|
||||
_triple_or_none(action_info.get("push_pull_outward_direction"))
|
||||
or _triple_or_none(action_info.get("normal"))
|
||||
)
|
||||
plane_origin, plane_direction = self._plane_offset_info_for_property(action_info)
|
||||
current_plane_position = None
|
||||
if plane_origin is not None and plane_direction is not None:
|
||||
direction_length = math.sqrt(
|
||||
@@ -3845,10 +3889,10 @@ class WindowStateMixin:
|
||||
"action": "push_pull_face",
|
||||
"target_attr": "offset_input",
|
||||
"enabled": current_plane_position is not None,
|
||||
"enabled_tip": "输入偏移的目标位置;程序会沿当前 Face 法向执行拉伸或切除。",
|
||||
"enabled_tip": "输入偏移的目标位置;程序会沿判定的材料外侧法向执行拉伸或切除。",
|
||||
"disabled_tip": "当前平面缺少稳定移动方向或基准点,不能按目标位置拉伸/切除。",
|
||||
"range_hint": (
|
||||
"偏移是沿当前 Face 法向测量的目标位置,不是面积、不是移动距离,也不是 X/Y/Z 坐标;单位同模型。"
|
||||
"偏移是沿程序判定的材料外侧法向测量的目标位置,不是面积、不是移动距离,也不是 X/Y/Z 坐标;单位同模型。"
|
||||
"程序会把目标位置自动换算成本次拉伸/切除距离。"
|
||||
f"{face_offset_hard_limit}"
|
||||
),
|
||||
@@ -3861,7 +3905,7 @@ class WindowStateMixin:
|
||||
"target_attr": "offset_input",
|
||||
"enabled": keep_relation_enabled,
|
||||
"enabled_tip": (
|
||||
"输入偏移的目标位置;程序会沿当前 Face 法向拉伸/切除,"
|
||||
"输入偏移的目标位置;程序会沿判定的材料外侧法向拉伸/切除,"
|
||||
"并要求一级相邻平面的平行/垂直关系可验证。"
|
||||
),
|
||||
"disabled_tip": keep_relation_disabled_tip,
|
||||
@@ -3884,7 +3928,7 @@ class WindowStateMixin:
|
||||
and current_face_center is not None
|
||||
),
|
||||
"enabled_tip": (
|
||||
"输入偏移的目标位置;程序只把当前 Face 沿法向移动到该目标值,"
|
||||
"输入偏移的目标位置;程序只把当前 Face 沿判定的材料外侧法向移动到该目标值,"
|
||||
"并让相邻平面按新顶点重建。"
|
||||
),
|
||||
"disabled_tip": face_local_disabled_tip(
|
||||
@@ -3908,7 +3952,7 @@ class WindowStateMixin:
|
||||
and (self.selected_part_id is not None or self.selected_solid_id is not None)
|
||||
),
|
||||
"enabled_tip": (
|
||||
"输入偏移的目标位置;程序会沿当前 Face 法向平移所属特征或 Solid,"
|
||||
"输入偏移的目标位置;程序会沿判定的材料外侧法向平移所属特征或 Solid,"
|
||||
"当前面形状和所属对象内部尺寸不变。"
|
||||
),
|
||||
"disabled_tip": "当前平面缺少稳定方向、基准点或所属对象,不能按偏移移动所属特征。",
|
||||
@@ -5863,12 +5907,19 @@ class WindowStateMixin:
|
||||
changed = self._changed_property_rows()
|
||||
enabled = bool(has_model and changed)
|
||||
disabled_tip = "请先选择对象,并在属性表中修改一个可编辑目标值。"
|
||||
if changed and len(changed) > 1:
|
||||
disabled_tip = "当前一次只执行一个几何修改;请只保留一行目标值不同,再点击参数化建模。"
|
||||
if changed:
|
||||
count = len(changed)
|
||||
enabled_tip = (
|
||||
"应用当前被修改的 1 个参数。"
|
||||
if count == 1
|
||||
else f"按表格顺序依次应用当前被修改的 {count} 个参数;失败时会停止后续修改。"
|
||||
)
|
||||
else:
|
||||
enabled_tip = "应用当前被修改的参数。"
|
||||
self._set_control_state(
|
||||
self.apply_property_button,
|
||||
enabled and len(changed) == 1,
|
||||
"应用当前被修改的参数。",
|
||||
enabled,
|
||||
enabled_tip,
|
||||
disabled_tip,
|
||||
)
|
||||
self._update_parameter_export_state(has_model)
|
||||
@@ -5907,6 +5958,37 @@ class WindowStateMixin:
|
||||
)
|
||||
return rows
|
||||
|
||||
def _selected_parameter_component_edits(self, rows: list[dict[str, str]] | None = None) -> list[dict[str, object]]:
|
||||
if not hasattr(self, "property_table"):
|
||||
return []
|
||||
specs = getattr(self, "property_editor_specs", [])
|
||||
row_count = min(self.property_table.rowCount(), len(specs))
|
||||
export_rows = list(rows or self._selected_parameter_export_rows())
|
||||
edits: list[dict[str, object]] = []
|
||||
export_index = 0
|
||||
step_path = self.step_path if isinstance(getattr(self, "step_path", None), Path) else None
|
||||
for row in range(row_count):
|
||||
checkbox = self._property_input_checkbox(row)
|
||||
if checkbox is None or not checkbox.isEnabled() or not checkbox.isChecked():
|
||||
continue
|
||||
if export_index >= len(export_rows):
|
||||
break
|
||||
spec = self._effective_property_spec(specs[row], row=row)
|
||||
if str(spec.get("value_type", "number")) == "command":
|
||||
continue
|
||||
edit = component_edit_config_from_spec(
|
||||
parameter_row=export_rows[export_index],
|
||||
spec=spec,
|
||||
selected_kind=str(getattr(self, "selected_kind", "") or ""),
|
||||
selected_face_id=getattr(self, "selected_face_id", None),
|
||||
selected_edge_id=getattr(self, "selected_edge_id", None),
|
||||
step_path=step_path,
|
||||
)
|
||||
if edit is not None:
|
||||
edits.append(edit)
|
||||
export_index += 1
|
||||
return edits
|
||||
|
||||
def _update_parameter_export_state(self, has_model: bool | None = None) -> None:
|
||||
if not hasattr(self, "export_parameters_button"):
|
||||
return
|
||||
@@ -5922,7 +6004,7 @@ class WindowStateMixin:
|
||||
self._set_control_state(
|
||||
self.export_parameters_button,
|
||||
bool(has_model and selected_rows),
|
||||
f"导出已勾选的 {len(selected_rows)} 个输入参数到 data.json。",
|
||||
f"导出已勾选的 {len(selected_rows)} 个输入参数到 data.json,并生成参数化组件 main.py。",
|
||||
disabled_tip,
|
||||
)
|
||||
|
||||
@@ -5949,6 +6031,63 @@ class WindowStateMixin:
|
||||
changed.append((row, effective_spec, text))
|
||||
return changed
|
||||
|
||||
def _property_batch_item_from_row(self, row: int, spec: dict[str, object], text: str) -> dict[str, object]:
|
||||
return {
|
||||
"key": str(spec.get("key", "")),
|
||||
"label": str(spec.get("label", "")),
|
||||
"action": str(spec.get("action", "")),
|
||||
"scope_key": str(spec.get("scope_key", self._property_scope_value(row, spec))),
|
||||
"target_text": str(text),
|
||||
"source_face_id": spec.get("source_face_id", self.selected_face_id),
|
||||
"source_edge_id": spec.get("source_edge_id", self.selected_edge_id),
|
||||
"selected_kind": self.selected_kind,
|
||||
}
|
||||
|
||||
def _property_batch_row_for_item(self, item: dict[str, object]) -> int | None:
|
||||
specs = getattr(self, "property_editor_specs", [])
|
||||
target_key = str(item.get("key", ""))
|
||||
target_label = str(item.get("label", ""))
|
||||
target_action = str(item.get("action", ""))
|
||||
target_scope = str(item.get("scope_key", ""))
|
||||
for row, spec in enumerate(specs):
|
||||
if target_key and str(spec.get("key", "")) != target_key:
|
||||
continue
|
||||
if target_label and str(spec.get("label", "")) != target_label:
|
||||
continue
|
||||
modes = spec.get("scope_modes")
|
||||
mode: dict[str, object] | None = None
|
||||
if target_scope and isinstance(modes, dict):
|
||||
candidate_mode = modes.get(target_scope)
|
||||
if not isinstance(candidate_mode, dict):
|
||||
continue
|
||||
mode = dict(candidate_mode)
|
||||
effective = self._effective_property_spec(spec, row=row)
|
||||
if target_action and str(effective.get("action", "")) != target_action:
|
||||
scoped_action = str((mode or {}).get("action", ""))
|
||||
if scoped_action != target_action:
|
||||
continue
|
||||
if target_scope and not isinstance(modes, dict):
|
||||
if str(effective.get("scope_key", "")) not in {"", target_scope}:
|
||||
continue
|
||||
return row
|
||||
return None
|
||||
|
||||
def _set_property_scope_value(self, row: int, scope_key: str) -> bool:
|
||||
if not scope_key or not hasattr(self, "property_table"):
|
||||
return True
|
||||
widget = self.property_table.cellWidget(row, PROPERTY_SCOPE_COLUMN)
|
||||
if not isinstance(widget, NoWheelComboBox):
|
||||
return True
|
||||
index = widget.findData(scope_key)
|
||||
if index < 0:
|
||||
index = widget.findText(scope_key)
|
||||
if index < 0:
|
||||
return False
|
||||
if widget.currentIndex() == index:
|
||||
return True
|
||||
widget.setCurrentIndex(index)
|
||||
return True
|
||||
|
||||
def _property_target_text(self, row: int) -> str:
|
||||
if not hasattr(self, "property_table"):
|
||||
return ""
|
||||
@@ -6133,11 +6272,100 @@ class WindowStateMixin:
|
||||
self.statusBar().showMessage("请先在当前选中对象表中修改一个可编辑目标值。")
|
||||
return
|
||||
if len(changed) > 1:
|
||||
QMessageBox.information(self, "一次执行一个修改", "请只修改一行目标值,然后再点击参数化建模。")
|
||||
self._start_property_batch_edit(changed)
|
||||
return
|
||||
row, _spec, _text = changed[0]
|
||||
self.apply_property_row_edit(row)
|
||||
|
||||
def _start_property_batch_edit(self, changed: list[tuple[int, dict[str, object], str]]) -> None:
|
||||
if self._edit_busy("请等待当前编辑完成后再执行批量参数化建模。"):
|
||||
return
|
||||
items = [self._property_batch_item_from_row(row, spec, text) for row, spec, text in changed]
|
||||
self.property_batch_queue = items
|
||||
self.property_batch_total = len(items)
|
||||
self.property_batch_done = 0
|
||||
self.property_batch_active = True
|
||||
self.statusBar().showMessage(f"开始批量参数化建模:共 {len(items)} 个参数。")
|
||||
self._run_next_property_batch_item()
|
||||
|
||||
def _run_next_property_batch_item(self) -> None:
|
||||
queue = list(getattr(self, "property_batch_queue", []) or [])
|
||||
if not queue:
|
||||
total = int(getattr(self, "property_batch_total", 0) or 0)
|
||||
self._clear_property_batch_state()
|
||||
self.statusBar().showMessage(f"批量参数化建模完成:已应用 {total} 个参数。")
|
||||
self._update_property_apply_state()
|
||||
return
|
||||
item = queue.pop(0)
|
||||
self.property_batch_queue = queue
|
||||
row = self._property_batch_row_for_item(item)
|
||||
if row is None:
|
||||
self._clear_property_batch_state()
|
||||
QMessageBox.information(
|
||||
self,
|
||||
"批量参数化建模已停止",
|
||||
f"找不到后续参数“{item.get('label', '')}”,模型拓扑可能已变化。已停止后续修改。",
|
||||
)
|
||||
self._update_property_apply_state()
|
||||
return
|
||||
if not self._set_property_scope_value(row, str(item.get("scope_key", ""))):
|
||||
self._clear_property_batch_state()
|
||||
QMessageBox.information(
|
||||
self,
|
||||
"批量参数化建模已停止",
|
||||
f"参数“{item.get('label', '')}”的建模意图已经不可用。已停止后续修改。",
|
||||
)
|
||||
self._update_property_apply_state()
|
||||
return
|
||||
target_widget = self.property_table.cellWidget(row, PROPERTY_TARGET_COLUMN)
|
||||
if isinstance(target_widget, QLineEdit):
|
||||
target_widget.setText(str(item.get("target_text", "")))
|
||||
done = int(getattr(self, "property_batch_done", 0) or 0)
|
||||
total = int(getattr(self, "property_batch_total", 0) or 0)
|
||||
self.statusBar().showMessage(f"批量参数化建模:正在执行 {done + 1}/{total} - {item.get('label', '')}")
|
||||
self._property_batch_running_action = True
|
||||
self._property_batch_item_callback_seen = False
|
||||
try:
|
||||
self.apply_property_row_edit(row)
|
||||
finally:
|
||||
self._property_batch_running_action = False
|
||||
if (
|
||||
bool(getattr(self, "property_batch_active", False))
|
||||
and not bool(getattr(self, "_property_batch_item_callback_seen", False))
|
||||
and not bool(getattr(self, "operation_in_progress", False))
|
||||
):
|
||||
remaining = len(getattr(self, "property_batch_queue", []) or [])
|
||||
done = int(getattr(self, "property_batch_done", 0) or 0)
|
||||
self._clear_property_batch_state()
|
||||
QMessageBox.information(
|
||||
self,
|
||||
"批量参数化建模已停止",
|
||||
f"参数“{item.get('label', '')}”没有启动可执行修改。已完成 {done} 个,剩余 {remaining} 个未执行。",
|
||||
)
|
||||
self._update_property_apply_state()
|
||||
|
||||
def _clear_property_batch_state(self) -> None:
|
||||
self.property_batch_queue = []
|
||||
self.property_batch_total = 0
|
||||
self.property_batch_done = 0
|
||||
self.property_batch_active = False
|
||||
self._property_batch_running_action = False
|
||||
self._property_batch_item_callback_seen = False
|
||||
|
||||
def _after_property_edit_finished(self, *, success: bool) -> None:
|
||||
if not bool(getattr(self, "property_batch_active", False)):
|
||||
return
|
||||
self._property_batch_item_callback_seen = True
|
||||
if not success:
|
||||
remaining = len(getattr(self, "property_batch_queue", []) or [])
|
||||
done = int(getattr(self, "property_batch_done", 0) or 0)
|
||||
self._clear_property_batch_state()
|
||||
self.statusBar().showMessage(f"批量参数化建模已停止:已完成 {done} 个,剩余 {remaining} 个未执行。")
|
||||
self._update_property_apply_state()
|
||||
return
|
||||
self.property_batch_done = int(getattr(self, "property_batch_done", 0) or 0) + 1
|
||||
QTimer.singleShot(0, self._run_next_property_batch_item)
|
||||
|
||||
def _activate_property_source_feature(self, spec: dict[str, object]) -> None:
|
||||
source_face_id = _int_or_none(spec.get("source_face_id"))
|
||||
if source_face_id is None or source_face_id == self.selected_face_id:
|
||||
@@ -6453,6 +6681,41 @@ class WindowStateMixin:
|
||||
return current_length * target_radius / current_radius
|
||||
raise ValueError(f"这个属性使用了未知的目标换算方式:{transform}")
|
||||
|
||||
def _plane_offset_info_for_property(
|
||||
self,
|
||||
action_info: dict[str, object],
|
||||
) -> tuple[tuple[float, float, float] | None, tuple[float, float, float] | None]:
|
||||
plane_origin = _triple_or_none(action_info.get("plane_origin"))
|
||||
plane_direction = _triple_or_none(action_info.get("push_pull_outward_direction"))
|
||||
selected_face_id = _int_or_none(action_info.get("feature_source_face_id"))
|
||||
if selected_face_id is None:
|
||||
selected_face_id = _int_or_none(action_info.get("topological_face_id"))
|
||||
if selected_face_id is None:
|
||||
selected_face_id = _int_or_none(action_info.get("face_id"))
|
||||
model_faces = getattr(self.model, "faces", None) if self.model is not None else None
|
||||
if (
|
||||
self.model is not None
|
||||
and model_faces is not None
|
||||
and selected_face_id is not None
|
||||
and 0 <= selected_face_id < len(model_faces)
|
||||
and (plane_origin is None or plane_direction is None)
|
||||
):
|
||||
try:
|
||||
full_info = self.model.face_info(selected_face_id)
|
||||
except Exception:
|
||||
full_info = {}
|
||||
if plane_origin is None:
|
||||
plane_origin = _triple_or_none(full_info.get("plane_origin"))
|
||||
if plane_direction is None:
|
||||
plane_direction = _triple_or_none(full_info.get("push_pull_outward_direction"))
|
||||
if plane_direction is None:
|
||||
plane_direction = _triple_or_none(full_info.get("oriented_normal"))
|
||||
if plane_direction is None:
|
||||
plane_direction = _triple_or_none(action_info.get("oriented_normal"))
|
||||
if plane_direction is None:
|
||||
plane_direction = _triple_or_none(action_info.get("normal"))
|
||||
return plane_origin, plane_direction
|
||||
|
||||
def _transform_property_vector3_target(
|
||||
self,
|
||||
spec: dict[str, object],
|
||||
|
||||
Reference in New Issue
Block a user