feat: 完善 Face 参数化编辑和隔离执行
This commit is contained in:
+227
-62
@@ -1313,6 +1313,9 @@ class WindowStateMixin:
|
||||
_int_values(action_info.get("feature_start_end_face_ids"))
|
||||
or _int_values(action_info.get("feature_end_end_face_ids"))
|
||||
)
|
||||
show_generic_face_edit_specs = bool(has_face and (is_plane or is_shell_candidate))
|
||||
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()
|
||||
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]] = []
|
||||
@@ -1327,28 +1330,65 @@ class WindowStateMixin:
|
||||
return ""
|
||||
return ", ".join(_format_float(item) for item in value)
|
||||
|
||||
def relative_range_hint(current: object, caution_ratio: float, high_ratio: float) -> str:
|
||||
def relative_range_hint(
|
||||
current: object,
|
||||
caution_ratio: float,
|
||||
high_ratio: float,
|
||||
hard_limits: str = "",
|
||||
) -> str:
|
||||
number = _float_or_none(current)
|
||||
if number is None or number <= 0:
|
||||
return "建议先小幅试改并查看预览,过大变化可能导致布尔失败或周边变形。"
|
||||
base = "建议先小幅试改并查看预览,过大变化可能导致布尔失败或周边变形。"
|
||||
return f"{base} {hard_limits}".strip()
|
||||
lower = max(number * (1.0 - caution_ratio), 0.0)
|
||||
upper = number * (1.0 + caution_ratio)
|
||||
return (
|
||||
base = (
|
||||
f"建议先在 {_format_float(lower)} - {_format_float(upper)} 内试改"
|
||||
f"(相对当前值约 +/-{caution_ratio * 100.0:.0f}%);"
|
||||
f"变化超过 {high_ratio * 100.0:.0f}% 时风险较高。"
|
||||
)
|
||||
return f"{base} {hard_limits}".strip()
|
||||
|
||||
def positive_minimum() -> dict[str, object]:
|
||||
return {"min_value": 0.0, "min_exclusive": True}
|
||||
|
||||
def face_scale_limits(current: object, *, squared: bool = False) -> dict[str, object]:
|
||||
number = _float_or_none(current)
|
||||
if number is None or number <= 0:
|
||||
return positive_minimum()
|
||||
power = 2 if squared else 1
|
||||
return {
|
||||
"min_value": number * (0.05 ** power),
|
||||
"max_value": number * (5.0 ** power),
|
||||
}
|
||||
|
||||
def face_offset_target_limits(current_position: object) -> dict[str, object]:
|
||||
position = _float_or_none(current_position)
|
||||
diagonal = _float_or_none(action_info.get("bbox_diagonal"))
|
||||
if position is None or diagonal is None or diagonal <= 0:
|
||||
return {}
|
||||
span = diagonal * 5.0
|
||||
return {"min_value": position - span, "max_value": position + span}
|
||||
|
||||
def face_vector_move_limit(reference: object) -> dict[str, object]:
|
||||
center = _triple_or_none(reference)
|
||||
diagonal = _float_or_none(action_info.get("bbox_diagonal"))
|
||||
if center is None or diagonal is None or diagonal <= 0:
|
||||
return {}
|
||||
return {
|
||||
"vector_distance_reference": center,
|
||||
"max_vector_distance": diagonal * 5.0,
|
||||
"vector_distance_label": "中心移动距离",
|
||||
}
|
||||
|
||||
def push_pull_hint() -> str:
|
||||
diagonal = _float_or_none(action_info.get("bbox_diagonal"))
|
||||
if diagonal is not None and diagonal > 0:
|
||||
return (
|
||||
"可输入正数或负数,单位同模型;"
|
||||
f"建议单次绝对值不超过 {_format_float(diagonal * 0.08)},"
|
||||
f"超过 {_format_float(diagonal * 0.2)} 时风险较高。"
|
||||
f"超过 {_format_float(diagonal * 0.2)} 时风险较高;"
|
||||
f"超过 {_format_float(diagonal * 5.0)} 会被阻止。"
|
||||
)
|
||||
return "可输入正数或负数,单位同模型;建议先小幅试改并查看预览。"
|
||||
|
||||
@@ -1362,6 +1402,11 @@ class WindowStateMixin:
|
||||
)
|
||||
return "格式为 X, Y, Z;建议先小幅试改并查看预览。"
|
||||
|
||||
face_linear_hard_limit = "目标值低于当前值的 5% 或高于当前值的 5 倍会被阻止。"
|
||||
face_area_hard_limit = "如果目标面积会让面宽/面高缩到当前 5% 以下或放大到 5 倍以上,会被阻止。"
|
||||
face_offset_hard_limit = "如果目标位置与当前位置差距远超当前模型尺寸,会被阻止。"
|
||||
face_center_hard_limit = "如果目标中心与当前中心距离超过所属对象尺寸的 5 倍,会被阻止。"
|
||||
|
||||
def hole_diameter_upper_limit(current: object) -> float | None:
|
||||
current_number = _float_or_none(current)
|
||||
height = _float_or_none(action_info.get("height_estimate"))
|
||||
@@ -1406,6 +1451,11 @@ class WindowStateMixin:
|
||||
"高度、厚度和其它尺寸会同比例变化;如果只想改孔壁/槽壁,请使用普通半径行。"
|
||||
)
|
||||
|
||||
def face_local_disabled_tip(base: str) -> str:
|
||||
if local_face_deform_ready or not local_face_deform_blocker:
|
||||
return base
|
||||
return f"{base} 当前不能只改当前面的原因:{local_face_deform_blocker}"
|
||||
|
||||
def add_spec(
|
||||
*,
|
||||
key: str,
|
||||
@@ -1591,14 +1641,24 @@ class WindowStateMixin:
|
||||
),
|
||||
)
|
||||
elif is_plane or is_shell_candidate:
|
||||
face_semantics_text = "Face:先改目标值,再用“影响范围”选择改当前面、推拉或调整整个特征。"
|
||||
face_semantics_tip = (
|
||||
"面积是面的大小;面宽/面高是这个面自身平面里的两个方向尺寸;"
|
||||
"面偏移是沿当前面垂直方向测到的位置。影响范围决定这次修改只作用在当前 Face,"
|
||||
"还是推拉加料/切削,或带动所属特征或 Solid。"
|
||||
)
|
||||
if is_plane and not local_face_deform_ready:
|
||||
face_semantics_text = "Face:当前面暂不能只改当前面,可用推拉或整体策略。"
|
||||
face_semantics_tip = (
|
||||
"此 Face 不适合做“只改当前面”的局部顶点重建;"
|
||||
f"原因:{local_face_deform_blocker or '当前拓扑不满足局部重建条件。'}"
|
||||
"可优先使用“推拉当前面”、孔/槽专门入口,或选择移动/调整整个特征。"
|
||||
)
|
||||
add_readonly_spec(
|
||||
key="face_edit_semantics",
|
||||
label="编辑方式",
|
||||
text="Face:先改目标值,再用“影响范围”选择只改当前面或调整整个特征。",
|
||||
tip=(
|
||||
"面积、面宽、面高、中心和面偏移会尽量合并成一行参数;"
|
||||
"影响范围下拉框决定这次修改是只作用在当前 Face,还是带动所属特征或 Solid。"
|
||||
),
|
||||
text=face_semantics_text,
|
||||
tip=face_semantics_tip,
|
||||
)
|
||||
|
||||
if has_edge:
|
||||
@@ -1612,7 +1672,7 @@ class WindowStateMixin:
|
||||
),
|
||||
)
|
||||
|
||||
if has_face:
|
||||
if show_generic_face_edit_specs:
|
||||
current_face_area = _float_or_none(action_info.get("area"))
|
||||
current_face_center = (
|
||||
_triple_or_none(action_info.get("area_center"))
|
||||
@@ -1631,6 +1691,7 @@ class WindowStateMixin:
|
||||
"target_attr": "face_area_input",
|
||||
"enabled": bool(
|
||||
is_plane
|
||||
and local_face_deform_ready
|
||||
and current_face_area is not None
|
||||
and current_face_area > 0
|
||||
and current_face_center is not None
|
||||
@@ -1639,10 +1700,12 @@ class WindowStateMixin:
|
||||
"输入目标面积;程序只在当前 Face 平面内缩放这个面的顶点,"
|
||||
"相邻面会按新边界重建。"
|
||||
),
|
||||
"disabled_tip": "只有带稳定面积和中心坐标的平面 Face 才能尝试只修改当前面面积。",
|
||||
"disabled_tip": face_local_disabled_tip(
|
||||
"只有带稳定面积和中心坐标的平面 Face 才能尝试只修改当前面面积。"
|
||||
),
|
||||
"range_hint": (
|
||||
"只改当前面时,相邻面可能自然变斜。当前只对简单全平面实体开放,"
|
||||
f"建议先小幅修改。 {relative_range_hint(current_face_area, 0.15, 0.8)}"
|
||||
f"建议先小幅修改。 {relative_range_hint(current_face_area, 0.15, 0.8, face_area_hard_limit)}"
|
||||
),
|
||||
},
|
||||
"owning": {
|
||||
@@ -1657,24 +1720,24 @@ class WindowStateMixin:
|
||||
"disabled_tip": "当前 Face 缺少稳定面积,不能按目标面积缩放所属对象。",
|
||||
"range_hint": (
|
||||
"调整整个特征会影响同一对象上的其它尺寸。"
|
||||
f" {relative_range_hint(current_face_area, 0.15, 0.8)}"
|
||||
f" {relative_range_hint(current_face_area, 0.15, 0.8, face_area_hard_limit)}"
|
||||
),
|
||||
},
|
||||
},
|
||||
value_type="positive",
|
||||
used=("area", "area_center", "bbox_center"),
|
||||
**positive_minimum(),
|
||||
**face_scale_limits(current_face_area, squared=True),
|
||||
)
|
||||
if is_plane:
|
||||
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 在自身平面内两个稳定方向上的投影尺寸,"
|
||||
"不是全局 X/Y/Z 包围盒尺寸。只修改当前 Face 顶点,相邻面会按新顶点重建。"
|
||||
"这里的面宽/面高是选中 Face 在自身平面内两个稳定方向上的投影长度,"
|
||||
"不是面积,也不是模型整体高度。只修改当前 Face 顶点,相邻面会按新顶点重建。"
|
||||
)
|
||||
face_size_owner_tip = (
|
||||
"这里的宽/高是选中 Face 在自身平面内两个稳定方向上的投影尺寸,"
|
||||
"不是全局 X/Y/Z 包围盒尺寸。程序会沿该方向缩放所属特征或 Solid,其它几何会跟随变化。"
|
||||
"这里的面宽/面高是选中 Face 在自身平面内两个稳定方向上的投影长度,"
|
||||
"不是面积,也不是模型整体高度。程序会沿该方向缩放所属特征或 Solid,其它几何会跟随变化。"
|
||||
)
|
||||
add_scoped_spec(
|
||||
key="local_face_width",
|
||||
@@ -1688,13 +1751,16 @@ class WindowStateMixin:
|
||||
"action": "resize_face_width_local",
|
||||
"target_attr": "face_width_input",
|
||||
"enabled": bool(
|
||||
current_face_width is not None
|
||||
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 才能尝试修改当前面宽。",
|
||||
"range_hint": f"{face_size_tip} {relative_range_hint(current_face_width, 0.25, 0.8)}",
|
||||
"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)}",
|
||||
},
|
||||
"owning": {
|
||||
"label": "调整整个特征",
|
||||
@@ -1707,13 +1773,13 @@ class WindowStateMixin:
|
||||
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)}",
|
||||
"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"),
|
||||
**positive_minimum(),
|
||||
**face_scale_limits(current_face_width),
|
||||
)
|
||||
add_scoped_spec(
|
||||
key="local_face_height",
|
||||
@@ -1727,13 +1793,16 @@ class WindowStateMixin:
|
||||
"action": "resize_face_height_local",
|
||||
"target_attr": "face_height_input",
|
||||
"enabled": bool(
|
||||
current_face_height is not None
|
||||
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 才能尝试修改当前面高。",
|
||||
"range_hint": f"{face_size_tip} {relative_range_hint(current_face_height, 0.25, 0.8)}",
|
||||
"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)}",
|
||||
},
|
||||
"owning": {
|
||||
"label": "调整整个特征",
|
||||
@@ -1746,13 +1815,13 @@ class WindowStateMixin:
|
||||
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)}",
|
||||
"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"),
|
||||
**positive_minimum(),
|
||||
**face_scale_limits(current_face_height),
|
||||
)
|
||||
add_scoped_spec(
|
||||
key="face_center_position",
|
||||
@@ -1765,18 +1834,21 @@ class WindowStateMixin:
|
||||
"label": "只改当前面",
|
||||
"action": "move_selected_face_center_local",
|
||||
"target_attrs": ("translate_x_input", "translate_y_input", "translate_z_input"),
|
||||
"enabled": bool(is_plane and current_face_center is not None),
|
||||
"enabled": bool(is_plane and local_face_deform_ready and current_face_center is not None),
|
||||
"enabled_tip": (
|
||||
"输入这个 Face 中心要移动到的目标 X, Y, Z 坐标;"
|
||||
"程序只移动当前 Face 的顶点,并让相邻平面按新顶点重建。"
|
||||
),
|
||||
"disabled_tip": "只有带稳定中心坐标的平面 Face 才能尝试只移动当前 Face。",
|
||||
"disabled_tip": face_local_disabled_tip(
|
||||
"只有带稳定中心坐标的平面 Face 才能尝试只移动当前 Face。"
|
||||
),
|
||||
"range_hint": (
|
||||
"只改当前面时,相邻面可能自然变斜,非共面面可能被拆成三角面。"
|
||||
f"{translation_hint()}"
|
||||
f"{translation_hint()} {face_center_hard_limit}"
|
||||
),
|
||||
"target_transform": "target_center_to_translation",
|
||||
"transform_context": {"current_center": current_face_center},
|
||||
**face_vector_move_limit(current_face_center),
|
||||
},
|
||||
"owning": {
|
||||
"label": "移动整个特征",
|
||||
@@ -1791,10 +1863,11 @@ class WindowStateMixin:
|
||||
"disabled_tip": "当前 Face 缺少稳定中心坐标或所属对象,不能按中心坐标平移。",
|
||||
"range_hint": (
|
||||
"移动整个特征不会改变当前对象形状,但同一对象会整体搬动。"
|
||||
f"{translation_hint()}"
|
||||
f"{translation_hint()} {face_center_hard_limit}"
|
||||
),
|
||||
"target_transform": "target_center_to_translation",
|
||||
"transform_context": {"current_center": current_face_center},
|
||||
**face_vector_move_limit(current_face_center),
|
||||
},
|
||||
},
|
||||
value_type="vector3",
|
||||
@@ -1837,11 +1910,12 @@ class WindowStateMixin:
|
||||
"action": "push_pull_face",
|
||||
"target_attr": "offset_input",
|
||||
"enabled": current_plane_position is not None,
|
||||
"enabled_tip": "输入目标面偏移;程序会沿当前推拉方向移动面,自动加料或切削。",
|
||||
"enabled_tip": "输入目标面偏移;程序会沿当前面的垂直方向移动面,自动加料或切削。",
|
||||
"disabled_tip": "当前平面缺少稳定移动方向或基准点,不能按目标位置推拉。",
|
||||
"range_hint": (
|
||||
"这是沿当前推拉方向测量的位置值,不是 X/Y/Z 坐标;单位同模型。"
|
||||
"目标值大于当前值通常向外移,小于当前值通常向内移。"
|
||||
"面偏移是沿当前面垂直方向测量的目标值,不是面积、不是移动距离,也不是 X/Y/Z 坐标;单位同模型。"
|
||||
"程序会把目标面偏移自动换算成本次需要移动的距离。"
|
||||
f"{face_offset_hard_limit}"
|
||||
),
|
||||
"target_transform": "plane_target_position_to_offset",
|
||||
"transform_context": {"current_plane_position": current_plane_position},
|
||||
@@ -1852,18 +1926,21 @@ class WindowStateMixin:
|
||||
"target_attr": "offset_input",
|
||||
"enabled": bool(
|
||||
current_plane_position is not None
|
||||
and local_face_deform_ready
|
||||
and plane_direction is not None
|
||||
and current_face_center is not None
|
||||
),
|
||||
"enabled_tip": (
|
||||
"输入目标面偏移;程序只把当前 Face 沿平面方向移动到该位置,"
|
||||
"输入目标面偏移;程序只把当前 Face 沿当前面的垂直方向移动到该目标值,"
|
||||
"并让相邻平面按新顶点重建。"
|
||||
),
|
||||
"disabled_tip": "当前平面缺少稳定方向、基准点或中心,不能按面偏移只移动当前 Face。",
|
||||
"disabled_tip": face_local_disabled_tip(
|
||||
"当前平面缺少稳定方向、基准点或中心,不能按面偏移只移动当前 Face。"
|
||||
),
|
||||
"range_hint": (
|
||||
"只改当前面不会自动加料/切削,相邻面可能自然变斜,"
|
||||
"非共面面可能被拆成三角面。"
|
||||
f"{translation_hint()}"
|
||||
f"{translation_hint()} {face_offset_hard_limit}"
|
||||
),
|
||||
"target_transform": "plane_target_position_to_offset",
|
||||
"transform_context": {"current_plane_position": current_plane_position},
|
||||
@@ -1878,32 +1955,20 @@ class WindowStateMixin:
|
||||
and (self.selected_part_id is not None or self.selected_solid_id is not None)
|
||||
),
|
||||
"enabled_tip": (
|
||||
"输入目标面偏移;程序会沿当前平面方向平移所属特征或 Solid,"
|
||||
"输入目标面偏移;程序会沿当前面的垂直方向平移所属特征或 Solid,"
|
||||
"当前面形状和所属对象内部尺寸不变。"
|
||||
),
|
||||
"disabled_tip": "当前平面缺少稳定方向、基准点或所属对象,不能按面偏移整体平移。",
|
||||
"range_hint": (
|
||||
"这是整体移动所属对象,不是推拉当前面;如果想改变形状或厚度,请选择“推拉当前面”。"
|
||||
f"{translation_hint()}"
|
||||
f"{translation_hint()} {face_offset_hard_limit}"
|
||||
),
|
||||
"target_transform": "plane_target_position_to_offset",
|
||||
"transform_context": {"current_plane_position": current_plane_position},
|
||||
},
|
||||
},
|
||||
used=("plane_origin", "push_pull_outward_direction", "normal"),
|
||||
)
|
||||
add_spec(
|
||||
key="push_pull_distance",
|
||||
label="偏移距离",
|
||||
current_raw=0.0,
|
||||
target_text="0",
|
||||
action="push_pull_face",
|
||||
target_attr="offset_input",
|
||||
enabled=True,
|
||||
enabled_tip="输入本次要让这个面沿当前推拉方向移动多远;正负表示两个相反方向,执行后这里会重新从 0 开始。",
|
||||
disabled_tip="当前对象不是可移动的平面。",
|
||||
range_hint=push_pull_hint(),
|
||||
current_text="0(本次未移动)",
|
||||
**face_offset_target_limits(current_plane_position),
|
||||
)
|
||||
if is_shell_candidate:
|
||||
current = _float_or_none(action_info.get("shell_thickness_estimate"))
|
||||
@@ -1924,7 +1989,7 @@ class WindowStateMixin:
|
||||
"enabled": current is not None,
|
||||
"enabled_tip": "输入薄壁/壳体局部区域的目标厚度;程序会推拉当前平面来改变与相对面的距离。",
|
||||
"disabled_tip": "当前平面没有稳定识别到可修改的薄壁厚度。",
|
||||
"range_hint": relative_range_hint(current, 0.35, 0.8),
|
||||
"range_hint": relative_range_hint(current, 0.35, 0.8, face_linear_hard_limit),
|
||||
},
|
||||
"owning": {
|
||||
"label": "调整整个特征",
|
||||
@@ -1946,13 +2011,13 @@ class WindowStateMixin:
|
||||
"disabled_tip": "当前薄壁候选缺少稳定厚度、厚度方向、基准平面或所属对象,不能按厚度整体缩放。",
|
||||
"range_hint": (
|
||||
"这是整体缩放所属对象,不是推拉当前平面;"
|
||||
f"如果只想移动当前薄壁平面区域,请把影响范围设为“推拉当前面”。 {relative_range_hint(current, 0.2, 0.5)}"
|
||||
f"如果只想移动当前薄壁平面区域,请把影响范围设为“推拉当前面”。 {relative_range_hint(current, 0.2, 0.5, face_linear_hard_limit)}"
|
||||
),
|
||||
},
|
||||
},
|
||||
value_type="positive",
|
||||
used=("shell_thickness_estimate", "shell_current_thickness", "shell_signed_thickness", "normal", "plane_origin"),
|
||||
**positive_minimum(),
|
||||
**face_scale_limits(current),
|
||||
)
|
||||
if is_hole_or_groove:
|
||||
current_diameter = _float_or_none(action_info.get("diameter"))
|
||||
@@ -3180,7 +3245,28 @@ class WindowStateMixin:
|
||||
if is_circle_edge:
|
||||
current_radius = _float_or_none(action_info.get("radius"))
|
||||
current_diameter = _float_or_none(action_info.get("diameter"))
|
||||
circle_edge_center = _triple_or_none(action_info.get("center"))
|
||||
can_resize_circle = bool(current_length is not None and current_length > 0 and current_radius is not None and current_radius > 0)
|
||||
add_spec(
|
||||
key="circle_edge_axis_center",
|
||||
label="圆心/轴心",
|
||||
current_raw=circle_edge_center if circle_edge_center is not None else "",
|
||||
target_text=vector_text(circle_edge_center),
|
||||
action="move_circular_edge_axis_center",
|
||||
target_attrs=("edge_center_x_input", "edge_center_y_input", "edge_center_z_input"),
|
||||
enabled=bool(circle_edge_center is not None and current_radius is not None and current_radius > 0),
|
||||
enabled_tip=(
|
||||
"输入目标 X, Y, Z 坐标;程序会用圆Edge圆心的移动量,"
|
||||
"移动相邻孔、槽或凸台的圆柱轴心。"
|
||||
),
|
||||
disabled_tip="当前圆Edge缺少稳定圆心/半径,或没有可识别的相邻圆柱特征。",
|
||||
value_type="vector3",
|
||||
range_hint=(
|
||||
"格式为 X, Y, Z。这个修改只在点击“修改”时才识别相邻圆柱;"
|
||||
"优先移动局部孔/槽/凸台,不会整体平移零件。建议先小幅移动。"
|
||||
),
|
||||
used=("center", "adjacent_face_ids"),
|
||||
)
|
||||
circle_context = {
|
||||
"edge_current_length": current_length,
|
||||
"edge_current_radius": current_radius,
|
||||
@@ -3614,6 +3700,7 @@ class WindowStateMixin:
|
||||
target_widget.setToolTip(self._property_target_tooltip(effective_spec, editable=editable))
|
||||
widget = self.property_table.cellWidget(row, PROPERTY_ACTION_COLUMN)
|
||||
if isinstance(widget, QPushButton):
|
||||
validation_error = ""
|
||||
if str(effective_spec.get("value_type", "number")) == "command":
|
||||
row_changed = True
|
||||
widget.setText(str(effective_spec.get("button_text") or "执行"))
|
||||
@@ -3623,14 +3710,28 @@ class WindowStateMixin:
|
||||
text = self._property_target_text(row)
|
||||
row_changed = self._property_target_changed(effective_spec, text)
|
||||
empty_target = not bool(text.strip())
|
||||
widget.setText("未输入" if empty_target else ("应用" if row_changed else "未改动"))
|
||||
enabled = bool(has_model and effective_spec.get("enabled") and effective_spec.get("action") and row_changed)
|
||||
validation_error = "" if empty_target else self._property_target_validation_error(effective_spec, text)
|
||||
if empty_target:
|
||||
widget.setText("未输入")
|
||||
elif validation_error:
|
||||
widget.setText("无效")
|
||||
else:
|
||||
widget.setText("应用" if row_changed else "未改动")
|
||||
enabled = bool(
|
||||
has_model
|
||||
and effective_spec.get("enabled")
|
||||
and effective_spec.get("action")
|
||||
and row_changed
|
||||
and not validation_error
|
||||
)
|
||||
if empty_target:
|
||||
tooltip = (
|
||||
f"“{effective_spec.get('label', '当前属性')}”的目标值为空。"
|
||||
"这不会删除模型里的点、边或面,只是暂时没有要应用的目标值;"
|
||||
"重新选择对象会按当前模型值重新填入。"
|
||||
)
|
||||
elif validation_error:
|
||||
tooltip = f"{validation_error} 请调整目标值后再应用。"
|
||||
elif row_changed:
|
||||
scope_label = str(effective_spec.get("scope_label") or "").strip()
|
||||
suffix = f"({scope_label})" if scope_label else ""
|
||||
@@ -3640,7 +3741,8 @@ class WindowStateMixin:
|
||||
range_hint = self._property_range_hint(effective_spec)
|
||||
if range_hint:
|
||||
tooltip = f"{tooltip}\n\n{range_hint}"
|
||||
widget.setProperty("changed", bool(row_changed))
|
||||
widget.setProperty("changed", bool(row_changed and not validation_error))
|
||||
widget.setProperty("invalid", bool(validation_error))
|
||||
widget.setToolTip(tooltip)
|
||||
widget.setCursor(Qt.CursorShape.PointingHandCursor if enabled else Qt.CursorShape.ArrowCursor)
|
||||
widget.style().unpolish(widget)
|
||||
@@ -3679,7 +3781,7 @@ class WindowStateMixin:
|
||||
}:
|
||||
continue
|
||||
text = self._property_target_text(row)
|
||||
if self._property_target_changed(effective_spec, text):
|
||||
if self._property_target_changed(effective_spec, text) and not self._property_target_validation_error(effective_spec, text):
|
||||
changed.append((row, effective_spec, text))
|
||||
return changed
|
||||
|
||||
@@ -3728,6 +3830,65 @@ class WindowStateMixin:
|
||||
return abs(value - current) > PROPERTY_VALUE_TOLERANCE
|
||||
return None
|
||||
|
||||
def _property_target_validation_error(self, spec: dict[str, object], text: str) -> str:
|
||||
value_type = str(spec.get("value_type", "number"))
|
||||
if value_type == "command":
|
||||
return ""
|
||||
if not text.strip():
|
||||
return ""
|
||||
try:
|
||||
if value_type == "vector3":
|
||||
values = self._parse_property_vector3(text)
|
||||
return self._property_vector_distance_error(spec, values)
|
||||
if value_type == "choice":
|
||||
self._property_choice_value(spec, text)
|
||||
return ""
|
||||
if value_type == "number_pair":
|
||||
values = self._parse_property_number_pair(text)
|
||||
if spec.get("positive_pair") and any(value <= 0 for value in values):
|
||||
return "请输入两个大于 0 的目标值。"
|
||||
for value in values:
|
||||
range_error = self._property_scalar_range_error(spec, value)
|
||||
if range_error:
|
||||
return range_error
|
||||
return ""
|
||||
if value_type in {"integer", "integer_or_empty"}:
|
||||
if not text and value_type == "integer_or_empty":
|
||||
return ""
|
||||
try:
|
||||
value = int(text)
|
||||
except ValueError:
|
||||
return "请输入整数形式的目标值。"
|
||||
return self._property_scalar_range_error(spec, float(value))
|
||||
try:
|
||||
value = float(text)
|
||||
except ValueError:
|
||||
return "请输入数字形式的目标值。"
|
||||
if value_type == "positive" and value <= 0:
|
||||
return "请输入大于 0 的目标值。"
|
||||
return self._property_scalar_range_error(spec, value)
|
||||
except ValueError as exc:
|
||||
return str(exc)
|
||||
|
||||
def _property_vector_distance_error(
|
||||
self,
|
||||
spec: dict[str, object],
|
||||
values: tuple[float, float, float],
|
||||
) -> str:
|
||||
max_distance = _float_or_none(spec.get("max_vector_distance"))
|
||||
reference = _triple_or_none(spec.get("vector_distance_reference"))
|
||||
if max_distance is None or max_distance <= 0 or reference is None:
|
||||
return ""
|
||||
distance = math.sqrt(
|
||||
(values[0] - reference[0]) * (values[0] - reference[0])
|
||||
+ (values[1] - reference[1]) * (values[1] - reference[1])
|
||||
+ (values[2] - reference[2]) * (values[2] - reference[2])
|
||||
)
|
||||
if distance > max_distance + PROPERTY_VALUE_TOLERANCE:
|
||||
label = str(spec.get("vector_distance_label") or spec.get("label") or "目标距离")
|
||||
return f"{label} 必须小于或等于 {_format_float(max_distance)}。"
|
||||
return ""
|
||||
|
||||
def _property_target_changed(self, spec: dict[str, object], text: str) -> bool:
|
||||
value_type = str(spec.get("value_type", "number"))
|
||||
if not text:
|
||||
@@ -3824,6 +3985,10 @@ class WindowStateMixin:
|
||||
if not is_command and not self._property_target_changed(spec, text):
|
||||
self.statusBar().showMessage("请先在这一行的目标值列输入一个不同的新值。")
|
||||
return
|
||||
validation_error = "" if is_command else self._property_target_validation_error(spec, text)
|
||||
if validation_error:
|
||||
QMessageBox.information(self, "目标值无效", validation_error)
|
||||
return
|
||||
try:
|
||||
if not is_command:
|
||||
self._sync_property_edit_target(spec, text)
|
||||
|
||||
Reference in New Issue
Block a user