feat: 增强一级关系识别与保持关系策略

This commit is contained in:
2026-08-10 14:56:50 +08:00
parent 12250603dd
commit 83c44265ba
24 changed files with 3460 additions and 241 deletions
+96 -7
View File
@@ -156,6 +156,10 @@ def _feature_dimension_keys(action_info: dict[str, object]) -> tuple[str, ...]:
angular_span = _float_or_none(action_info.get("angular_span"))
if surface == "plane":
if action_info.get("multistep_prismatic_status") == "blocked":
return ()
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"]
if action_info.get("prismatic_extrusion_status") == "candidate":
@@ -195,6 +199,8 @@ def _feature_dimension_keys(action_info: dict[str, object]) -> tuple[str, ...]:
if feature_guess == "boss/outer-round candidate":
return ("boss_diameter", "boss_height")
if feature_guess == "round/fillet candidate":
if str(action_info.get("existing_fillet_status") or "") == "blocked":
return ()
return ("existing_fillet_radius_estimate", "existing_fillet_arc_length_estimate")
return ("generic_cylinder_diameter", "cylinder_height")
@@ -1029,13 +1035,23 @@ class WindowStateMixin:
is_generic_surface = has_face and surface in FREEFORM_FACE_SURFACES
is_hole_or_groove = is_cylinder and feature_guess == "hole/groove candidate"
is_boss = is_cylinder and feature_guess == "boss/outer-round candidate"
is_existing_fillet = is_cylinder and feature_guess == "round/fillet candidate"
is_existing_fillet = (
is_cylinder
and feature_guess == "round/fillet candidate"
and str(action_info.get("existing_fillet_status") or "") != "blocked"
)
is_existing_chamfer = (
is_plane
and feature_guess == "chamfer candidate"
and str(action_info.get("existing_chamfer_status") or "") == "candidate"
)
is_full_cylinder = _is_effectively_full_cylinder(action_info)
is_slot_or_half_hole = (
is_hole_or_groove
and not is_full_cylinder
and angular_span is not None
and angular_span < math.tau * 0.92
and str(action_info.get("slot_status") or "") == "candidate"
and _float_or_none(action_info.get("slot_chord_width_estimate")) is not None
)
is_blind = action_info.get("cylinder_end_type") == "blind"
@@ -1049,8 +1065,8 @@ class WindowStateMixin:
)
has_fillet_support = len(_int_values(action_info.get("feature_existing_fillet_support_face_ids"))) >= 2
is_line_edge = has_edge and curve == "line"
can_push = has_model and is_plane
can_resize_shell = has_model and is_shell_candidate
can_push = has_model and is_plane and not is_existing_chamfer
can_resize_shell = has_model and is_shell_candidate and not is_existing_chamfer
can_resize_hole = has_model and is_hole_or_groove
can_resize_slot = has_model and is_slot_or_half_hole
can_resize_boss = has_model and is_boss and is_full_cylinder
@@ -1535,7 +1551,7 @@ class WindowStateMixin:
"Face:面内长度/宽度、中心、偏移、壳体厚度。\n"
"孔/槽:孔径、轴心、封堵、盲孔/盲槽深度、槽宽/槽深/弧长/总长。\n"
"凸台:圆柱凸台直径/高度/轴心;矩形凸台/矩形槽口袋长宽、中心、高度/深度;多台阶矩形凸台顶层规则台阶。\n"
"圆角/倒角:简单已有圆角半径/弧长,直线 Edge 新增圆角/倒角。\n"
"圆角/倒角:简单已有圆角半径/弧长、简单等半径圆角链半径/弧长、已有等距倒角距离,直线 Edge 新增圆角/倒角。\n"
"Edge/解析曲面:直线 Edge 长度/端点/中心、圆/椭圆 Edge、简单圆锥/球/环面。\n"
"受限:复杂链式特征、二级/三级拓扑传播和原 CAD 历史恢复。"
)
@@ -2566,7 +2582,16 @@ class WindowStateMixin:
is_generic_surface = has_face and surface in FREEFORM_FACE_SURFACES
is_hole_or_groove = is_cylinder and feature_guess == "hole/groove candidate"
is_boss = is_cylinder and feature_guess == "boss/outer-round candidate"
is_existing_fillet = is_cylinder and feature_guess == "round/fillet candidate"
is_existing_fillet = (
is_cylinder
and feature_guess == "round/fillet candidate"
and str(action_info.get("existing_fillet_status") or "") != "blocked"
)
is_existing_chamfer = (
is_plane
and feature_guess == "chamfer candidate"
and str(action_info.get("existing_chamfer_status") or "") == "candidate"
)
recognition_fields_present = any(
key in action_info
for key in (
@@ -2605,6 +2630,7 @@ class WindowStateMixin:
and not is_full_cylinder
and angular_span is not None
and angular_span < math.tau * 0.92
and str(action_info.get("slot_status") or "") == "candidate"
and _float_or_none(action_info.get("slot_chord_width_estimate")) is not None
)
is_blind = action_info.get("cylinder_end_type") == "blind"
@@ -2613,7 +2639,7 @@ 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))
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()
has_fillet_support = len(_int_values(action_info.get("feature_existing_fillet_support_face_ids"))) >= 2
@@ -2978,6 +3004,11 @@ class WindowStateMixin:
"工程特征 / 倒圆角:移除已有圆角后按目标半径重新倒圆。",
"STEP 没有原始倒圆角历史时,只能基于当前圆角面和支撑面推断;复杂 blend 会阻止或回滚。",
)
if is_existing_chamfer:
return (
"工程特征 / 倒角:移除已有倒角斜面后按目标距离重新倒角。",
"STEP 没有原始倒角历史时,只能基于当前斜面、支撑面和恢复出的锐边推断;倒角链或不等距倒角先保持受限。",
)
if is_shell_candidate:
return (
"工程特征 / 抽壳:按壳体厚度或相对面距离调整薄壁区域。",
@@ -3062,6 +3093,11 @@ class WindowStateMixin:
"优先改圆角半径;复杂圆角链先不要用整体缩放替代。",
"已有圆角需要先恢复支撑锐边再重新倒圆,支撑面不明确时失败回滚是正常保护。",
)
if is_existing_chamfer:
return (
"优先改倒角距离;复杂倒角链先保持只读或重新选 Edge 新增倒角。",
"已有倒角需要先移除斜面、恢复原始锐边再重新倒角;如果支撑面或长边不明确,会在计划阶段阻止。",
)
if is_shell_candidate:
return (
"优先改壳体厚度;普通位置变化再用偏移。",
@@ -3243,6 +3279,16 @@ class WindowStateMixin:
"再寻找可重新倒圆的锐边。复杂 blend 或支撑面不稳定时会阻止或回滚。"
),
)
elif is_existing_chamfer:
add_readonly_spec(
key="existing_chamfer_edit_semantics",
label="建模意图",
text="倒角:移除已有倒角斜面,再尝试按目标距离重新倒角。",
tip=(
"已有倒角修改不是恢复 CAD 历史参数;当前会先 defeature 当前倒角斜面,"
"再寻找恢复出的原始锐边并按目标距离重新倒角。复杂倒角链、不等距倒角或支撑面不稳定时会阻止或回滚。"
),
)
elif is_cone or is_sphere or is_torus:
add_readonly_spec(
key="analytic_surface_edit_semantics",
@@ -3502,7 +3548,7 @@ class WindowStateMixin:
used=("area_center", "bbox_center"),
)
if is_plane:
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"))
@@ -4547,6 +4593,35 @@ class WindowStateMixin:
used=("existing_fillet_arc_length_estimate", "existing_fillet_angular_span", "angular_span"),
**positive_minimum(),
)
if is_existing_chamfer:
current_distance = _float_or_none(action_info.get("existing_chamfer_distance_estimate"))
chamfer_hint = relative_range_hint(current_distance, 0.35, 1.0)
cross_length = _float_or_none(action_info.get("existing_chamfer_cross_edge_length_estimate"))
if cross_length is not None and cross_length > 0:
chamfer_hint = (
f"{chamfer_hint} 当前倒角截面边长约 {_format_float(cross_length)}"
"目标距离过大时恢复锐边再倒角更容易失败。"
)
add_spec(
key="existing_chamfer_distance_estimate",
label="倒角距离",
current_raw=current_distance if current_distance is not None else "",
target_text=numeric_text(current_distance),
action="resize_existing_chamfer",
target_attr="edge_chamfer_distance_input",
enabled=bool(current_distance is not None and current_distance > 0),
enabled_tip="输入已有倒角面的目标距离;程序会先确认支撑面,再尝试移除旧倒角后重新倒角。",
disabled_tip="当前倒角候选缺少稳定距离估算,暂不放行修改。",
value_type="positive",
range_hint=chamfer_hint,
used=(
"existing_chamfer_distance_estimate",
"existing_chamfer_cross_edge_length_estimate",
"feature_existing_chamfer_support_face_ids",
"feature_existing_chamfer_long_edge_ids",
),
**positive_minimum(),
)
def analytic_surface_enabled(value_present: bool, capability_supported: bool = True) -> bool:
return bool(value_present and capability_supported and analytic_surface_recognition_ready)
@@ -4773,6 +4848,7 @@ class WindowStateMixin:
"auto": "自动选择:按局部边形变、端面移动、相邻圆柱编辑、缩放所属对象的顺序寻找可用路径。",
"local-edge-only-deform": "只改当前Edge:移动被选Edge端点,相邻面自然变斜,整体端面不一起平移。",
"move-edge-end-plane-by-push-pull": "移动端面/保持垂直:端面和端面上的相关边一起移动,相邻平面尽量保持垂直,正方体会更像变成长方体。",
"keep-first-level-planar-relations": "保持关系:保持一级平面平行/垂直关系;只在直接相邻平面和移动端面能验证时执行,不能验证就直接阻止。",
"resize-adjacent-cylinder-from-circular-edge-length": "相邻圆柱直径:把圆Edge长度换算成圆柱直径,优先改孔/槽/凸台局部特征。",
"scale-owning-shape-from-edge": "缩放所属对象:所属特征或 Solid 整体跟随变化,其它尺寸也会变。",
}.get(strategy_mode, "自动选择:程序会在确认窗口显示实际采用的策略。")
@@ -4836,6 +4912,19 @@ class WindowStateMixin:
),
"preselect_combos": edge_length_strategy_preselect("move-edge-end-plane-by-push-pull"),
},
"keep-first-level-planar-relations": {
"label": "保持关系",
"action": "resize_any_edge_length",
"target_attr": "edge_target_length_input",
"enabled": bool(is_line_edge and current_length is not None),
"enabled_tip": "输入当前Edge的目标长度;程序会尝试移动端面,并要求一级平面平行/垂直关系可验证。",
"disabled_tip": "只有直线Edge才能尝试保持一级平面关系;圆Edge请用相邻圆柱,复杂曲线暂未实现。",
"range_hint": (
f"{edge_strategy_effect_text('keep-first-level-planar-relations', current_anchor_mode)} "
f"{relative_range_hint(current_length, 0.25, 0.5)}"
),
"preselect_combos": edge_length_strategy_preselect("keep-first-level-planar-relations"),
},
"resize-adjacent-cylinder-from-circular-edge-length": {
"label": "相邻圆柱",
"action": "resize_any_edge_length",