feat: 增强一级关系识别与保持关系策略
This commit is contained in:
+153
-23
@@ -302,9 +302,11 @@ class FeatureMixin:
|
||||
depth_count = 0
|
||||
suppress_count = 0
|
||||
existing_fillet_count = 0
|
||||
existing_chamfer_count = 0
|
||||
depth_limit = max(2, min(per_type_limit, limit // 12))
|
||||
suppress_limit = max(2, min(per_type_limit, limit // 12))
|
||||
existing_fillet_limit = max(2, min(per_type_limit, limit // 12))
|
||||
existing_chamfer_limit = max(2, min(per_type_limit, limit // 12))
|
||||
slot_width_limit = max(2, min(per_type_limit, limit // 10))
|
||||
slot_depth_limit = max(2, min(per_type_limit, limit // 10))
|
||||
slot_arc_length_limit = max(2, min(per_type_limit, limit // 10))
|
||||
@@ -323,15 +325,27 @@ class FeatureMixin:
|
||||
if existing_fillet_status == "blocked":
|
||||
continue
|
||||
support_face_ids = tuple(feature.get("feature_existing_fillet_support_face_ids", ()))
|
||||
chain_face_ids = tuple(feature.get("feature_existing_fillet_chain_face_ids", ()))
|
||||
chain_status = str(feature.get("existing_fillet_chain_status") or "")
|
||||
is_same_radius_chain = chain_status == "same-radius-chain-candidate" and len(chain_face_ids) > 1
|
||||
support_note = (
|
||||
f"支撑 Face: {support_face_ids}。"
|
||||
if support_face_ids
|
||||
else "暂未识别出稳定支撑 Face。"
|
||||
)
|
||||
fillet_note = (
|
||||
f"这是简单等半径圆角链候选,链上 Face: {chain_face_ids};点击后会选中并预填目标半径/弧长,"
|
||||
"在特征参数表修改后点击“参数化建模”,会尝试一起 defeature 后重新倒圆。"
|
||||
if is_same_radius_chain
|
||||
else (
|
||||
"这是已有圆角/倒圆候选;点击后会选中并预填目标半径/弧长,"
|
||||
"在特征参数表修改后点击“参数化建模”,会尝试 defeature 后重新倒圆。"
|
||||
)
|
||||
)
|
||||
candidates.append(
|
||||
{
|
||||
"operation_key": "inspect_existing_fillet",
|
||||
"operation": "修改已有圆角半径",
|
||||
"operation": "修改已有圆角链半径/弧长" if is_same_radius_chain else "修改已有圆角半径/弧长",
|
||||
"target_kind": "face",
|
||||
"target_id": item["face_id"],
|
||||
"face_id": item["face_id"],
|
||||
@@ -342,13 +356,9 @@ class FeatureMixin:
|
||||
"current_value": feature.get("existing_fillet_radius_estimate", item["radius"]),
|
||||
"current_value_label": "radius",
|
||||
"status": "caution",
|
||||
"risk": "medium" if len(support_face_ids) >= 2 else "high",
|
||||
"risk": "high" if is_same_radius_chain or len(support_face_ids) < 2 else "medium",
|
||||
"confidence": item["confidence"],
|
||||
"note": (
|
||||
"这是已有圆角/倒圆候选;点击后会选中并预填目标半径,"
|
||||
"再点击“修改已有圆角半径”会尝试 defeature 后重新倒圆。"
|
||||
f" {support_note}"
|
||||
),
|
||||
"note": f"{fillet_note} {support_note}",
|
||||
}
|
||||
)
|
||||
existing_fillet_count += 1
|
||||
@@ -356,7 +366,7 @@ class FeatureMixin:
|
||||
if diameter_count < per_type_limit and feature_guess not in {
|
||||
"round/fillet candidate",
|
||||
"boss/outer-round candidate",
|
||||
}:
|
||||
} and str(item.get("resize_status") or "") != "blocked":
|
||||
candidates.append(
|
||||
{
|
||||
"operation_key": "resize_cylinder",
|
||||
@@ -388,6 +398,8 @@ class FeatureMixin:
|
||||
and float(item.get("angular_span", 0.0)) < math.tau * 0.92
|
||||
):
|
||||
feature = self.feature_info(int(item["face_id"]))
|
||||
if str(feature.get("slot_status") or "") != "candidate":
|
||||
continue
|
||||
slot_width = feature.get("slot_chord_width_estimate")
|
||||
if slot_width_count < slot_width_limit and isinstance(slot_width, (int, float)) and float(slot_width) > 0:
|
||||
candidates.append(
|
||||
@@ -666,17 +678,70 @@ class FeatureMixin:
|
||||
plane_count = 0
|
||||
shell_thickness_count = 0
|
||||
shell_thickness_limit = max(2, min(per_type_limit, limit // 10))
|
||||
shell_candidate_rows: list[dict[str, object]] = []
|
||||
shell_scan_pool_limit = max(shell_thickness_limit * 4, 12)
|
||||
face_scan_limit = len(self.faces) if max_scan_faces is None else min(len(self.faces), max(0, int(max_scan_faces)))
|
||||
for face_id, face in enumerate(self.faces[:face_scan_limit]):
|
||||
if progress_callback is not None and face_id % 30 == 0:
|
||||
progress_callback()
|
||||
if plane_count >= per_type_limit and shell_thickness_count >= shell_thickness_limit:
|
||||
if (
|
||||
plane_count >= per_type_limit
|
||||
and len(shell_candidate_rows) >= shell_scan_pool_limit
|
||||
and existing_chamfer_count >= existing_chamfer_limit
|
||||
):
|
||||
break
|
||||
surf = BRepAdaptor_Surface(face)
|
||||
if surf.GetType() != GeomAbs_Plane:
|
||||
continue
|
||||
props = GProp_GProps()
|
||||
brepgprop.SurfaceProperties(face, props)
|
||||
feature: dict[str, object] | None = None
|
||||
quick_info: dict[str, object] | None = None
|
||||
if existing_chamfer_count < existing_chamfer_limit:
|
||||
quick_info = self.quick_face_info(face_id)
|
||||
width = _float_or_none(quick_info.get("local_face_width"))
|
||||
height = _float_or_none(quick_info.get("local_face_height"))
|
||||
aspect = max(width or 0.0, height or 0.0) / max(min(width or 0.0, height or 0.0), 1e-9)
|
||||
if (
|
||||
int(quick_info.get("boundary_edges", 0) or 0) == 4
|
||||
and not bool(quick_info.get("has_inner_boundaries"))
|
||||
and aspect >= 1.8
|
||||
):
|
||||
feature = self.feature_info(face_id)
|
||||
if str(feature.get("existing_chamfer_status") or "") == "candidate":
|
||||
support_face_ids = tuple(feature.get("feature_existing_chamfer_support_face_ids", ()))
|
||||
long_edge_ids = tuple(feature.get("feature_existing_chamfer_long_edge_ids", ()))
|
||||
candidates.append(
|
||||
{
|
||||
"operation_key": "inspect_existing_chamfer",
|
||||
"operation": "修改已有倒角距离",
|
||||
"target_kind": "face",
|
||||
"target_id": face_id,
|
||||
"face_id": face_id,
|
||||
"part_id": self.face_part_ids[face_id],
|
||||
"solid_id": self.face_solid_ids[face_id],
|
||||
"surface": "plane",
|
||||
"feature_guess": "chamfer candidate",
|
||||
"current_value": feature.get("existing_chamfer_distance_estimate"),
|
||||
"current_value_label": "chamfer_distance",
|
||||
"status": "caution",
|
||||
"risk": str(feature.get("existing_chamfer_risk") or "medium"),
|
||||
"confidence": str(feature.get("recognition_confidence") or "medium"),
|
||||
"note": (
|
||||
"这是简单已有倒角斜面;点击后会选中该 Face,并把倒角距离填入参数表。"
|
||||
f" 支撑 Face: {support_face_ids}; 长边 Edge: {long_edge_ids}。"
|
||||
),
|
||||
}
|
||||
)
|
||||
existing_chamfer_count += 1
|
||||
continue
|
||||
if plane_count < per_type_limit or len(shell_candidate_rows) < shell_scan_pool_limit:
|
||||
quick_info = quick_info or self.quick_face_info(face_id)
|
||||
boundary_edges = int(quick_info.get("boundary_edges", 0) or 0)
|
||||
if bool(quick_info.get("has_inner_boundaries")) or boundary_edges > 4:
|
||||
feature = feature or self.feature_info(face_id)
|
||||
if feature.get("multistep_prismatic_status") == "blocked":
|
||||
continue
|
||||
if plane_count < per_type_limit:
|
||||
if detailed:
|
||||
direction_info = self._plane_push_pull_direction(face_id, surf)
|
||||
@@ -709,12 +774,21 @@ class FeatureMixin:
|
||||
}
|
||||
)
|
||||
plane_count += 1
|
||||
if shell_thickness_count < shell_thickness_limit:
|
||||
feature = self.feature_info(face_id)
|
||||
if len(shell_candidate_rows) < shell_scan_pool_limit:
|
||||
feature = feature or self.feature_info(face_id)
|
||||
if feature.get("shell_region_status") == "candidate":
|
||||
shell_confidence = str(feature.get("shell_confidence", "low"))
|
||||
shell_risk = "low" if shell_confidence == "high" else "medium" if shell_confidence == "medium" else "high"
|
||||
candidates.append(
|
||||
shell_note = (
|
||||
"快速扫描:已找到投影重叠的相对平面;点击后会填入参考目标厚度,"
|
||||
"执行时会移动当前平面区域来改变局部壳体厚度。"
|
||||
)
|
||||
if feature.get("open_shell_context_status") == "limited":
|
||||
shell_note = (
|
||||
f"{shell_note} 已识别为开口薄壁壳体上下文;"
|
||||
"完整抽壳/开口面编辑暂未实现,当前只开放壳体厚度、平面推拉或整体缩放。"
|
||||
)
|
||||
shell_candidate_rows.append(
|
||||
{
|
||||
"operation_key": "resize_shell_thickness",
|
||||
"operation": "调整壳体厚度",
|
||||
@@ -730,13 +804,26 @@ class FeatureMixin:
|
||||
"status": "ready" if shell_confidence == "high" else "caution",
|
||||
"risk": shell_risk,
|
||||
"confidence": shell_confidence,
|
||||
"note": (
|
||||
"快速扫描:已找到投影重叠的相对平面;点击后会填入参考目标厚度,"
|
||||
"执行时会移动当前平面区域来改变局部壳体厚度。"
|
||||
),
|
||||
"note": shell_note,
|
||||
"open_shell_context_status": feature.get("open_shell_context_status", ""),
|
||||
}
|
||||
)
|
||||
shell_thickness_count += 1
|
||||
|
||||
def _shell_candidate_sort_key(item: dict[str, object]) -> tuple[int, int, int, float, int]:
|
||||
confidence_rank = {"high": 0, "medium": 1, "low": 2}.get(str(item.get("confidence") or ""), 3)
|
||||
risk_rank = {"low": 0, "medium": 1, "high": 2, "blocked": 3}.get(str(item.get("risk") or ""), 3)
|
||||
thickness = _float_or_none(item.get("current_value"))
|
||||
return (
|
||||
0 if item.get("open_shell_context_status") == "limited" else 1,
|
||||
confidence_rank,
|
||||
risk_rank,
|
||||
thickness if thickness is not None else float("inf"),
|
||||
int(item.get("face_id", 10**9) or 10**9),
|
||||
)
|
||||
|
||||
for shell_candidate in sorted(shell_candidate_rows, key=_shell_candidate_sort_key)[:shell_thickness_limit]:
|
||||
candidates.append(shell_candidate)
|
||||
shell_thickness_count += 1
|
||||
|
||||
fillet_edge_count = 0
|
||||
chamfer_edge_count = 0
|
||||
@@ -959,6 +1046,32 @@ class FeatureMixin:
|
||||
candidate.update(self._cylinder_end_opening_info(face_id, surf))
|
||||
candidate.update(_cylinder_resize_readiness(candidate))
|
||||
candidate.update(_cylinder_boss_resize_readiness(candidate))
|
||||
try:
|
||||
feature = self.feature_info(face_id)
|
||||
for key in (
|
||||
"feature_guess",
|
||||
"feature_type",
|
||||
"feature_edit_actions",
|
||||
"confidence",
|
||||
"slot_kind",
|
||||
"slot_status",
|
||||
"slot_risk",
|
||||
"slot_blockers",
|
||||
"resize_status",
|
||||
"resize_risk",
|
||||
"resize_blockers",
|
||||
"resize_note",
|
||||
"boss_resize_status",
|
||||
"boss_resize_risk",
|
||||
"boss_resize_blockers",
|
||||
"boss_resize_note",
|
||||
"recognition_risk",
|
||||
"recognition_blockers",
|
||||
):
|
||||
if feature.get(key) not in {None, ""}:
|
||||
candidate[key] = feature.get(key)
|
||||
except Exception:
|
||||
pass
|
||||
candidates.append(candidate)
|
||||
if len(candidates) >= limit:
|
||||
break
|
||||
@@ -976,6 +1089,24 @@ class FeatureMixin:
|
||||
}
|
||||
current_diameter = float(info["diameter"])
|
||||
feature = self.feature_info(face_id)
|
||||
if str(feature.get("slot_status") or "") == "blocked":
|
||||
blocker = str(feature.get("slot_blockers") or "当前槽/半孔属于复杂槽或多槽组,暂不开放稳定修改。")
|
||||
return {
|
||||
"status": "blocked",
|
||||
"risk": "blocked",
|
||||
"message": blocker,
|
||||
"warnings": "",
|
||||
"blockers": blocker,
|
||||
"face_id": face_id,
|
||||
"part_id": info.get("part_id"),
|
||||
"solid_id": info.get("solid_id"),
|
||||
"current_diameter": current_diameter,
|
||||
"target_diameter": new_diameter,
|
||||
"feature_type": feature.get("feature_type"),
|
||||
"feature_guess": feature.get("feature_guess"),
|
||||
"slot_status": feature.get("slot_status"),
|
||||
"slot_blockers": blocker,
|
||||
}
|
||||
topology_fields = self._cylindrical_feature_first_level_plan_fields(face_id)
|
||||
axis_range = self._cylindrical_axis_range(
|
||||
face_id,
|
||||
@@ -1271,8 +1402,7 @@ class FeatureMixin:
|
||||
if angular_span is None or angular_span <= 1e-6 or angular_span >= math.tau * 0.92:
|
||||
blockers.append("Selected slot does not have a stable partial-cylinder angular span.")
|
||||
if slot_status and slot_status != "candidate":
|
||||
risk = _max_risk(risk, "high")
|
||||
warnings.append(f"Slot candidate status is {slot_status}.")
|
||||
blockers.append(str(feature.get("slot_blockers") or info.get("slot_blockers") or f"Slot candidate status is {slot_status}."))
|
||||
|
||||
span = min(max(float(angular_span or 0.0), 1e-6), math.tau - 1e-6)
|
||||
sin_half_span = math.sin(span / 2.0)
|
||||
@@ -1464,8 +1594,7 @@ class FeatureMixin:
|
||||
if target_angular_span >= math.tau * 0.92:
|
||||
blockers.append("Target slot angular span must remain below a near-full cylinder.")
|
||||
if slot_status and slot_status != "candidate":
|
||||
risk = _max_risk(risk, "high")
|
||||
warnings.append(f"Slot candidate status is {slot_status}.")
|
||||
blockers.append(str(feature.get("slot_blockers") or info.get("slot_blockers") or f"Slot candidate status is {slot_status}."))
|
||||
|
||||
delta_span = None if current_span <= 0 else target_angular_span - current_span
|
||||
delta_ratio = (
|
||||
@@ -1676,8 +1805,7 @@ class FeatureMixin:
|
||||
risk = _max_risk(risk, "medium")
|
||||
warnings.append("Slot recognition confidence is low.")
|
||||
if slot_status and slot_status != "candidate":
|
||||
risk = _max_risk(risk, "high")
|
||||
warnings.append(f"Slot candidate status is {slot_status}.")
|
||||
blockers.append(str(feature.get("slot_blockers") or info.get("slot_blockers") or f"Slot candidate status is {slot_status}."))
|
||||
|
||||
cutter_plan: dict[str, object] = {}
|
||||
fill_plan: dict[str, object] = {}
|
||||
@@ -2053,6 +2181,8 @@ class FeatureMixin:
|
||||
blockers.append("Current slot face is not recognized as one end of a paired obround slot.")
|
||||
else:
|
||||
blockers.append("Manual paired Face ID could not be used as a compatible obround slot end.")
|
||||
if str(feature.get("slot_status") or "") not in {"", "candidate"}:
|
||||
blockers.append(str(feature.get("slot_blockers") or f"Slot candidate status is {feature.get('slot_status')}."))
|
||||
if target_total_length <= 0:
|
||||
blockers.append("Target slot total length must be greater than 0.")
|
||||
|
||||
|
||||
@@ -117,6 +117,8 @@ def _execute(model: StepModel, operation: str, args: list[object]) -> str:
|
||||
return model.resize_ellipse_edge_axis_radius(int(args[0]), float(args[1]), axis_kind=axis_kind)
|
||||
if operation == "resize_existing_fillet":
|
||||
return model.resize_existing_fillet(int(args[0]), float(args[1]))
|
||||
if operation == "resize_existing_chamfer":
|
||||
return model.resize_existing_chamfer(int(args[0]), float(args[1]))
|
||||
if operation == "fillet_edge":
|
||||
return model.fillet_edge(int(args[0]), float(args[1]))
|
||||
if operation == "chamfer_edge":
|
||||
|
||||
+1056
-52
File diff suppressed because it is too large
Load Diff
+626
-63
File diff suppressed because it is too large
Load Diff
@@ -17,6 +17,7 @@ USER_OPERATION_PRIORITY: dict[str, int] = {
|
||||
"resize_boss_height": 41,
|
||||
"move_boss_axis": 44,
|
||||
"inspect_existing_fillet": 50,
|
||||
"inspect_existing_chamfer": 51,
|
||||
"fillet_edge": 52,
|
||||
"chamfer_edge": 53,
|
||||
"resize_shell_thickness": 60,
|
||||
@@ -77,6 +78,9 @@ def feature_recognition_priority(info: Mapping[str, object]) -> int:
|
||||
|
||||
combined_text = ";".join(item for item in (feature_type, feature_actions, ready_actions) if item)
|
||||
|
||||
if _text(info.get("existing_chamfer_status")) == "candidate" or feature_guess == "chamfer candidate":
|
||||
return 50
|
||||
|
||||
if surface == "plane":
|
||||
return 10
|
||||
|
||||
|
||||
@@ -79,6 +79,29 @@ INFO_GROUPS: list[tuple[str, list[str]]] = [
|
||||
"first_level_fact_ignored_relation_depths",
|
||||
"first_level_fact_ignored_relation_note",
|
||||
"first_level_fact_summary",
|
||||
"first_level_planar_relation_status",
|
||||
"first_level_planar_relation_scope",
|
||||
"first_level_planar_relation_count",
|
||||
"first_level_planar_relation_parallel_count",
|
||||
"first_level_planar_relation_perpendicular_count",
|
||||
"first_level_planar_relation_angled_count",
|
||||
"first_level_planar_relation_face_face_count",
|
||||
"first_level_planar_relation_edge_axis_count",
|
||||
"first_level_planar_relation_rows",
|
||||
"first_level_planar_relation_observed_modes",
|
||||
"first_level_planar_relation_summary",
|
||||
"first_level_planar_relation_note",
|
||||
"edge_length_planar_relation_constraint_requested",
|
||||
"edge_length_planar_relation_constraint_label",
|
||||
"edge_length_planar_constraint_status",
|
||||
"edge_length_planar_constraint_summary",
|
||||
"edge_length_planar_constraint_side_face_count",
|
||||
"edge_length_planar_constraint_parallel_count",
|
||||
"edge_length_planar_constraint_perpendicular_count",
|
||||
"edge_length_planar_constraint_angled_count",
|
||||
"edge_length_planar_constraint_observed_modes",
|
||||
"edge_length_planar_constraint_blockers",
|
||||
"edge_length_planar_constraint_warnings",
|
||||
"selected_edge_ids",
|
||||
"selected_edge_count",
|
||||
"first_level_vertex_points",
|
||||
@@ -146,6 +169,11 @@ INFO_GROUPS: list[tuple[str, list[str]]] = [
|
||||
"existing_fillet_radius_estimate",
|
||||
"existing_fillet_angular_span",
|
||||
"existing_fillet_arc_length_estimate",
|
||||
"existing_chamfer_distance_estimate",
|
||||
"existing_chamfer_cross_edge_length_estimate",
|
||||
"existing_chamfer_long_edge_length_estimate",
|
||||
"existing_chamfer_support_angle_degrees",
|
||||
"existing_chamfer_face_angle_degrees",
|
||||
"current_depth",
|
||||
"target_depth",
|
||||
"delta_depth",
|
||||
@@ -219,6 +247,25 @@ INFO_GROUPS: list[tuple[str, list[str]]] = [
|
||||
"shell_movement_alignment",
|
||||
"shell_note",
|
||||
"shell_region_note",
|
||||
"open_shell_context_status",
|
||||
"open_shell_kind",
|
||||
"open_shell_axis",
|
||||
"open_shell_side",
|
||||
"open_shell_open_direction",
|
||||
"open_shell_wall_thickness_estimate",
|
||||
"open_shell_open_side_area_ratio",
|
||||
"open_shell_closed_side_area_ratio",
|
||||
"open_shell_current_face_role",
|
||||
"open_shell_current_face_id",
|
||||
"open_shell_rim_face_ids",
|
||||
"open_shell_closed_side_face_ids",
|
||||
"open_shell_inner_face_ids",
|
||||
"open_shell_thin_wall_face_ids",
|
||||
"open_shell_related_face_ids",
|
||||
"open_shell_highlight_face_ids",
|
||||
"open_shell_limited_action",
|
||||
"open_shell_blockers",
|
||||
"open_shell_note",
|
||||
],
|
||||
),
|
||||
(
|
||||
@@ -293,6 +340,17 @@ INFO_GROUPS: list[tuple[str, list[str]]] = [
|
||||
"existing_fillet_kind",
|
||||
"existing_fillet_status",
|
||||
"existing_fillet_note",
|
||||
"feature_existing_chamfer_face_ids",
|
||||
"feature_existing_chamfer_support_face_ids",
|
||||
"feature_existing_chamfer_end_face_ids",
|
||||
"feature_existing_chamfer_short_edge_ids",
|
||||
"feature_existing_chamfer_long_edge_ids",
|
||||
"existing_chamfer_kind",
|
||||
"existing_chamfer_status",
|
||||
"existing_chamfer_risk",
|
||||
"existing_chamfer_warnings",
|
||||
"existing_chamfer_blockers",
|
||||
"existing_chamfer_note",
|
||||
"feature_edit_actions",
|
||||
"resize_status",
|
||||
"resize_strategy",
|
||||
@@ -496,6 +554,29 @@ INFO_LABELS = {
|
||||
"first_level_fact_ignored_relation_depths": "暂不传播层级",
|
||||
"first_level_fact_ignored_relation_note": "暂不传播说明",
|
||||
"first_level_fact_summary": "一级事实摘要",
|
||||
"first_level_planar_relation_status": "一级平面关系状态",
|
||||
"first_level_planar_relation_scope": "一级平面关系范围",
|
||||
"first_level_planar_relation_count": "一级平面关系数",
|
||||
"first_level_planar_relation_parallel_count": "一级平行关系数",
|
||||
"first_level_planar_relation_perpendicular_count": "一级垂直关系数",
|
||||
"first_level_planar_relation_angled_count": "一级斜交关系数",
|
||||
"first_level_planar_relation_face_face_count": "Face-Face 平面关系数",
|
||||
"first_level_planar_relation_edge_axis_count": "Face-Edge 方向关系数",
|
||||
"first_level_planar_relation_rows": "一级平面关系列表",
|
||||
"first_level_planar_relation_observed_modes": "已识别平面关系",
|
||||
"first_level_planar_relation_summary": "一级平面关系摘要",
|
||||
"first_level_planar_relation_note": "一级平面关系说明",
|
||||
"edge_length_planar_relation_constraint_requested": "已请求保持一级平面关系",
|
||||
"edge_length_planar_relation_constraint_label": "一级平面关系约束",
|
||||
"edge_length_planar_constraint_status": "端面推拉约束状态",
|
||||
"edge_length_planar_constraint_summary": "端面推拉约束摘要",
|
||||
"edge_length_planar_constraint_side_face_count": "端面推拉侧面数",
|
||||
"edge_length_planar_constraint_parallel_count": "端面推拉平行关系数",
|
||||
"edge_length_planar_constraint_perpendicular_count": "端面推拉垂直关系数",
|
||||
"edge_length_planar_constraint_angled_count": "端面推拉斜交关系数",
|
||||
"edge_length_planar_constraint_observed_modes": "端面推拉已识别关系",
|
||||
"edge_length_planar_constraint_blockers": "端面推拉阻止原因",
|
||||
"edge_length_planar_constraint_warnings": "端面推拉警告",
|
||||
"selected_edge_ids": "当前 Edge",
|
||||
"selected_edge_count": "当前 Edge 数",
|
||||
"first_level_vertex_points": "一级端点 Vertex",
|
||||
@@ -671,6 +752,25 @@ INFO_LABELS = {
|
||||
"shell_movement_alignment": "壳体移动方向匹配度",
|
||||
"shell_note": "壳体识别说明",
|
||||
"shell_region_note": "壳体识别说明",
|
||||
"open_shell_context_status": "开口壳体上下文状态",
|
||||
"open_shell_kind": "开口壳体类型",
|
||||
"open_shell_axis": "开口方向轴",
|
||||
"open_shell_side": "开口侧",
|
||||
"open_shell_open_direction": "开口方向",
|
||||
"open_shell_wall_thickness_estimate": "开口壳体壁厚估算",
|
||||
"open_shell_open_side_area_ratio": "开口侧面积占比",
|
||||
"open_shell_closed_side_area_ratio": "封闭侧面积占比",
|
||||
"open_shell_current_face_role": "当前 Face 壳体角色",
|
||||
"open_shell_current_face_id": "当前开口壳体 Face",
|
||||
"open_shell_rim_face_ids": "开口边界 Face",
|
||||
"open_shell_closed_side_face_ids": "封闭侧 Face",
|
||||
"open_shell_inner_face_ids": "内壁/内底 Face",
|
||||
"open_shell_thin_wall_face_ids": "薄壁 Face",
|
||||
"open_shell_related_face_ids": "开口壳体相关 Face",
|
||||
"open_shell_highlight_face_ids": "开口壳体高亮 Face",
|
||||
"open_shell_limited_action": "受限壳体操作",
|
||||
"open_shell_blockers": "开口壳体限制原因",
|
||||
"open_shell_note": "开口壳体说明",
|
||||
"u_range": "U 参数范围",
|
||||
"v_range": "V 参数范围",
|
||||
"first_parameter": "起始参数",
|
||||
@@ -723,6 +823,22 @@ INFO_LABELS = {
|
||||
"existing_fillet_kind": "已有圆角类型",
|
||||
"existing_fillet_status": "已有圆角识别状态",
|
||||
"existing_fillet_radius_estimate": "已有圆角半径估算",
|
||||
"feature_existing_chamfer_face_ids": "已有倒角 Face",
|
||||
"feature_existing_chamfer_support_face_ids": "已有倒角支撑 Face",
|
||||
"feature_existing_chamfer_end_face_ids": "已有倒角端面 Face",
|
||||
"feature_existing_chamfer_short_edge_ids": "已有倒角短边 Edge",
|
||||
"feature_existing_chamfer_long_edge_ids": "已有倒角长边 Edge",
|
||||
"existing_chamfer_kind": "已有倒角类型",
|
||||
"existing_chamfer_status": "已有倒角识别状态",
|
||||
"existing_chamfer_risk": "已有倒角风险",
|
||||
"existing_chamfer_warnings": "已有倒角警告",
|
||||
"existing_chamfer_blockers": "已有倒角阻止原因",
|
||||
"existing_chamfer_distance_estimate": "已有倒角距离估算",
|
||||
"existing_chamfer_cross_edge_length_estimate": "已有倒角截面边长估算",
|
||||
"existing_chamfer_long_edge_length_estimate": "已有倒角长度估算",
|
||||
"existing_chamfer_support_angle_degrees": "已有倒角支撑面夹角",
|
||||
"existing_chamfer_face_angle_degrees": "已有倒角面角度",
|
||||
"existing_chamfer_note": "已有倒角识别说明",
|
||||
"feature_reference_radius": "特征参考半径",
|
||||
"feature_reference_diameter": "特征参考直径",
|
||||
"feature_cone_small_radius": "锥孔小端半径",
|
||||
|
||||
+155
-20
@@ -1487,6 +1487,7 @@ class WindowActionMixin:
|
||||
"move_circular_edge_axis_center",
|
||||
"resize_ellipse_edge_axis_radius",
|
||||
"resize_existing_fillet",
|
||||
"resize_existing_chamfer",
|
||||
"fillet_edge",
|
||||
"chamfer_edge",
|
||||
"chamfer_edge_asymmetric",
|
||||
@@ -4101,6 +4102,92 @@ class WindowActionMixin:
|
||||
isolation=isolation,
|
||||
)
|
||||
|
||||
def resize_existing_chamfer(self) -> None:
|
||||
if self.model is None:
|
||||
return
|
||||
if self._edit_busy("请等待当前编辑完成后再修改已有倒角。"):
|
||||
return
|
||||
if self.selected_face_id is None:
|
||||
QMessageBox.information(self, "未选择已有倒角", "请先选择一个已有倒角斜面 Face。")
|
||||
return
|
||||
try:
|
||||
target_distance = float(self.edge_chamfer_distance_input.text())
|
||||
except ValueError:
|
||||
QMessageBox.critical(self, "距离无效", "请输入数字形式的目标倒角距离。")
|
||||
return
|
||||
|
||||
face_id = self.selected_face_id
|
||||
plan = self.model.existing_chamfer_resize_plan(face_id, target_distance)
|
||||
isolation = self._isolation_for_plan(plan, "resize_existing_chamfer", [face_id, target_distance])
|
||||
if plan["status"] == "blocked":
|
||||
self._show_blocked_plan_message("修改已有倒角", plan, "已有倒角距离修改已阻止")
|
||||
return
|
||||
|
||||
warnings = str(plan.get("warnings", ""))
|
||||
warnings_line = f"警告: {warnings}\n\n" if warnings else ""
|
||||
result = QMessageBox.question(
|
||||
self,
|
||||
"确认修改已有倒角距离",
|
||||
(
|
||||
f"Face: {plan['face_id']}\n"
|
||||
f"当前估算距离: {_format_value(plan['current_distance'])}\n"
|
||||
f"目标距离: {_format_value(plan['target_distance'])}\n"
|
||||
f"距离变化量: {_format_value(plan['delta_distance'])}\n"
|
||||
f"距离变化比例: {_format_percent(plan['distance_delta_ratio'])}\n"
|
||||
f"支撑 Face: {_format_value(plan.get('feature_existing_chamfer_support_face_ids', ''))}\n"
|
||||
f"长边 Edge: {_format_value(plan.get('feature_existing_chamfer_long_edge_ids', ''))}\n"
|
||||
f"策略: {plan['resize_strategy']}\n"
|
||||
f"执行策略: {_format_value(plan.get('edit_strategy_label'))}\n"
|
||||
f"建模意图: {_format_value(plan.get('edit_semantics'))}\n"
|
||||
f"风险: {plan['risk']}\n\n"
|
||||
f"{warnings_line}"
|
||||
f"{plan['message']}\n\n"
|
||||
"继续操作会先尝试移除当前倒角斜面,再对恢复出的锐边重新倒角。\n"
|
||||
"这不是 CAD 历史特征参数编辑,复杂倒角可能失败。\n\n"
|
||||
"确定继续吗?"
|
||||
),
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||
QMessageBox.StandardButton.No,
|
||||
)
|
||||
if result != QMessageBox.StandardButton.Yes:
|
||||
self.statusBar().showMessage("已取消修改已有倒角距离")
|
||||
return
|
||||
|
||||
def action():
|
||||
return self.model.resize_existing_chamfer(face_id, target_distance)
|
||||
|
||||
self._run_edit_action(
|
||||
action,
|
||||
operation_name="修改已有倒角距离",
|
||||
target=f"Face {face_id}",
|
||||
parameters={
|
||||
"part_id": plan.get("part_id"),
|
||||
"solid_id": plan.get("solid_id"),
|
||||
"old_distance": plan.get("current_distance"),
|
||||
"new_distance": target_distance,
|
||||
"delta_distance": plan.get("delta_distance"),
|
||||
"distance_delta_ratio": plan.get("distance_delta_ratio"),
|
||||
"feature_type": plan.get("feature_type"),
|
||||
"feature_guess": plan.get("feature_guess"),
|
||||
"confidence": plan.get("confidence"),
|
||||
"existing_chamfer_status": plan.get("status"),
|
||||
"existing_chamfer_risk": plan.get("risk"),
|
||||
"existing_chamfer_message": plan.get("message"),
|
||||
"existing_chamfer_warnings": plan.get("warnings"),
|
||||
"existing_chamfer_blockers": plan.get("blockers"),
|
||||
"feature_existing_chamfer_support_face_ids": plan.get("feature_existing_chamfer_support_face_ids"),
|
||||
"feature_existing_chamfer_long_edge_ids": plan.get("feature_existing_chamfer_long_edge_ids"),
|
||||
"resize_strategy": plan.get("resize_strategy"),
|
||||
"edit_strategy_label": plan.get("edit_strategy_label"),
|
||||
"edit_semantics": plan.get("edit_semantics"),
|
||||
"resize_note": plan.get("resize_note"),
|
||||
"ui_preview": "skipped-for-existing-chamfer",
|
||||
},
|
||||
target_kind="face",
|
||||
target_id=face_id,
|
||||
isolation=isolation,
|
||||
)
|
||||
|
||||
def fillet_edge(self) -> None:
|
||||
if self.model is None:
|
||||
return
|
||||
@@ -4483,6 +4570,7 @@ class WindowActionMixin:
|
||||
f"建模意图: {_format_value(plan.get('edge_length_strategy_label', ''))}\n"
|
||||
f"固定约束: {_format_value(plan.get('edge_length_constraint_summary', ''))}\n"
|
||||
f"执行影响: {_format_value(plan.get('edge_length_impact_summary', ''))}\n"
|
||||
f"平面关系约束: {_format_value(plan.get('edge_length_planar_constraint_summary', ''))}\n"
|
||||
f"策略: {_format_value(plan.get('resize_strategy', ''))}\n"
|
||||
f"局部形变目标: {_format_value(plan.get('local_edge_deform_target_kind', ''))}\n"
|
||||
f"局部形变: {_format_value(plan.get('local_edge_deform_anchor', ''))}\n"
|
||||
@@ -4539,6 +4627,26 @@ class WindowActionMixin:
|
||||
"edge_length_strategy_label": plan.get("edge_length_strategy_label"),
|
||||
"edge_length_constraint_summary": plan.get("edge_length_constraint_summary"),
|
||||
"edge_length_impact_summary": plan.get("edge_length_impact_summary"),
|
||||
"edge_length_planar_relation_constraint_requested": plan.get(
|
||||
"edge_length_planar_relation_constraint_requested"
|
||||
),
|
||||
"edge_length_planar_relation_constraint_label": plan.get(
|
||||
"edge_length_planar_relation_constraint_label"
|
||||
),
|
||||
"edge_length_planar_constraint_status": plan.get("edge_length_planar_constraint_status"),
|
||||
"edge_length_planar_constraint_summary": plan.get("edge_length_planar_constraint_summary"),
|
||||
"edge_length_planar_constraint_side_face_count": plan.get(
|
||||
"edge_length_planar_constraint_side_face_count"
|
||||
),
|
||||
"edge_length_planar_constraint_parallel_count": plan.get(
|
||||
"edge_length_planar_constraint_parallel_count"
|
||||
),
|
||||
"edge_length_planar_constraint_perpendicular_count": plan.get(
|
||||
"edge_length_planar_constraint_perpendicular_count"
|
||||
),
|
||||
"edge_length_planar_constraint_angled_count": plan.get("edge_length_planar_constraint_angled_count"),
|
||||
"edge_length_planar_constraint_blockers": plan.get("edge_length_planar_constraint_blockers"),
|
||||
"edge_length_planar_constraint_warnings": plan.get("edge_length_planar_constraint_warnings"),
|
||||
"edit_strategy_label": plan.get("edit_strategy_label"),
|
||||
"edit_semantics": plan.get("edit_semantics"),
|
||||
"local_edge_deform_target_kind": plan.get("local_edge_deform_target_kind"),
|
||||
@@ -4650,6 +4758,7 @@ class WindowActionMixin:
|
||||
f"建模意图: {_format_value(plan.get('edge_length_strategy_label', ''))}\n"
|
||||
f"固定约束: {_format_value(plan.get('edge_length_constraint_summary', ''))}\n"
|
||||
f"执行影响: {_format_value(plan.get('edge_length_impact_summary', ''))}\n"
|
||||
f"平面关系约束: {_format_value(plan.get('edge_length_planar_constraint_summary', ''))}\n"
|
||||
f"策略: {_format_value(strategy)}\n"
|
||||
f"局部形变目标: {_format_value(plan.get('local_edge_deform_target_kind', ''))}\n"
|
||||
f"局部形变: {_format_value(plan.get('local_edge_deform_anchor', ''))}\n"
|
||||
@@ -4707,6 +4816,26 @@ class WindowActionMixin:
|
||||
"edge_length_strategy_label": plan.get("edge_length_strategy_label"),
|
||||
"edge_length_constraint_summary": plan.get("edge_length_constraint_summary"),
|
||||
"edge_length_impact_summary": plan.get("edge_length_impact_summary"),
|
||||
"edge_length_planar_relation_constraint_requested": plan.get(
|
||||
"edge_length_planar_relation_constraint_requested"
|
||||
),
|
||||
"edge_length_planar_relation_constraint_label": plan.get(
|
||||
"edge_length_planar_relation_constraint_label"
|
||||
),
|
||||
"edge_length_planar_constraint_status": plan.get("edge_length_planar_constraint_status"),
|
||||
"edge_length_planar_constraint_summary": plan.get("edge_length_planar_constraint_summary"),
|
||||
"edge_length_planar_constraint_side_face_count": plan.get(
|
||||
"edge_length_planar_constraint_side_face_count"
|
||||
),
|
||||
"edge_length_planar_constraint_parallel_count": plan.get(
|
||||
"edge_length_planar_constraint_parallel_count"
|
||||
),
|
||||
"edge_length_planar_constraint_perpendicular_count": plan.get(
|
||||
"edge_length_planar_constraint_perpendicular_count"
|
||||
),
|
||||
"edge_length_planar_constraint_angled_count": plan.get("edge_length_planar_constraint_angled_count"),
|
||||
"edge_length_planar_constraint_blockers": plan.get("edge_length_planar_constraint_blockers"),
|
||||
"edge_length_planar_constraint_warnings": plan.get("edge_length_planar_constraint_warnings"),
|
||||
"edit_strategy_label": plan.get("edit_strategy_label"),
|
||||
"edit_semantics": plan.get("edit_semantics"),
|
||||
"local_edge_deform_target_kind": plan.get("local_edge_deform_target_kind"),
|
||||
@@ -4894,7 +5023,7 @@ class WindowActionMixin:
|
||||
float(getattr(self, f"{prefix}_z_input").text()),
|
||||
)
|
||||
except (AttributeError, ValueError):
|
||||
QMessageBox.critical(self, "Edge endpoint coordinate invalid", "Please enter numeric X, Y and Z values.")
|
||||
QMessageBox.critical(self, "Edge端点坐标无效", "请输入数字形式的 X、Y、Z 目标坐标。")
|
||||
return None
|
||||
|
||||
def move_edge_start_point(self) -> None:
|
||||
@@ -4992,10 +5121,10 @@ class WindowActionMixin:
|
||||
def move_edge_center_point(self) -> None:
|
||||
if self.model is None:
|
||||
return
|
||||
if self._edit_busy("Please wait for the current edit to finish before moving an Edge center."):
|
||||
if self._edit_busy("请等待当前编辑完成后再移动 Edge 中心。"):
|
||||
return
|
||||
if self.selected_edge_id is None:
|
||||
QMessageBox.information(self, "No Edge selected", "Please select an Edge first.")
|
||||
QMessageBox.information(self, "未选择Edge", "请先选择一条 Edge。")
|
||||
return
|
||||
try:
|
||||
target_center = (
|
||||
@@ -5004,7 +5133,7 @@ class WindowActionMixin:
|
||||
float(self.edge_center_z_input.text()),
|
||||
)
|
||||
except (AttributeError, ValueError):
|
||||
QMessageBox.critical(self, "Edge center coordinate invalid", "Please enter numeric X, Y and Z values.")
|
||||
QMessageBox.critical(self, "Edge中心坐标无效", "请输入数字形式的 X、Y、Z 目标坐标。")
|
||||
return
|
||||
|
||||
edge_id = self.selected_edge_id
|
||||
@@ -5012,12 +5141,13 @@ class WindowActionMixin:
|
||||
isolation = self._isolation_for_plan(plan, "move_edge_center", [edge_id, target_center])
|
||||
lines = [
|
||||
f"Edge: {edge_id}",
|
||||
f"Current center: {_format_value(plan.get('current_edge_center'))}",
|
||||
f"Target center: {_format_value(plan.get('target_edge_center'))}",
|
||||
f"Move vector: {_format_value(plan.get('moved_edge_center_delta'))}",
|
||||
f"Edge length: {_format_value(plan.get('current_length'))}",
|
||||
f"Strategy: {_format_value(plan.get('resize_strategy'))}",
|
||||
f"Rebuilt faces: {_format_value(plan.get('local_edge_deform_face_count'))}",
|
||||
f"当前中心: {_format_value(plan.get('current_edge_center'))}",
|
||||
f"目标中心: {_format_value(plan.get('target_edge_center'))}",
|
||||
f"移动量: {_format_value(plan.get('moved_edge_center_delta'))}",
|
||||
f"Edge长度: {_format_value(plan.get('current_length'))}",
|
||||
f"建模语义: {_format_value(plan.get('edit_strategy_label'))}",
|
||||
f"影响范围: {_format_value(plan.get('edit_semantics'))}",
|
||||
f"重建Face数: {_format_value(plan.get('local_edge_deform_face_count'))}",
|
||||
]
|
||||
if not self._confirm_quick_edit_plan(
|
||||
"移动Edge中心",
|
||||
@@ -5054,6 +5184,8 @@ class WindowActionMixin:
|
||||
"local_edge_deform_start_move": plan.get("local_edge_deform_start_move"),
|
||||
"local_edge_deform_end_move": plan.get("local_edge_deform_end_move"),
|
||||
"resize_strategy": plan.get("resize_strategy"),
|
||||
"edit_strategy_label": plan.get("edit_strategy_label"),
|
||||
"edit_semantics": plan.get("edit_semantics"),
|
||||
"resize_status": plan.get("status"),
|
||||
"resize_risk": plan.get("risk"),
|
||||
"resize_message": plan.get("message"),
|
||||
@@ -5068,10 +5200,10 @@ class WindowActionMixin:
|
||||
def _move_edge_endpoint(self, endpoint_role: str) -> None:
|
||||
if self.model is None:
|
||||
return
|
||||
if self._edit_busy("Please wait for the current edit to finish before moving an Edge endpoint."):
|
||||
if self._edit_busy("请等待当前编辑完成后再移动 Edge 端点。"):
|
||||
return
|
||||
if self.selected_edge_id is None:
|
||||
QMessageBox.information(self, "No Edge selected", "Please select an Edge first.")
|
||||
QMessageBox.information(self, "未选择Edge", "请先选择一条 Edge。")
|
||||
return
|
||||
|
||||
target_point = self._edge_endpoint_target_from_inputs(endpoint_role)
|
||||
@@ -5084,14 +5216,15 @@ class WindowActionMixin:
|
||||
title = "移动Edge起点" if endpoint_role == "start" else "移动Edge终点"
|
||||
lines = [
|
||||
f"Edge: {edge_id}",
|
||||
f"Endpoint: {_format_value(plan.get('edge_endpoint_label'))}",
|
||||
f"Current endpoint: {_format_value(plan.get('current_endpoint_point'))}",
|
||||
f"Target endpoint: {_format_value(plan.get('target_endpoint_point'))}",
|
||||
f"Move vector: {_format_value(plan.get('moved_endpoint_delta'))}",
|
||||
f"Current Edge length: {_format_value(plan.get('current_length'))}",
|
||||
f"Result Edge length: {_format_value(plan.get('target_length'))}",
|
||||
f"Strategy: {_format_value(plan.get('resize_strategy'))}",
|
||||
f"Rebuilt faces: {_format_value(plan.get('local_edge_deform_face_count'))}",
|
||||
f"端点: {_format_value(plan.get('edge_endpoint_label'))}",
|
||||
f"当前端点: {_format_value(plan.get('current_endpoint_point'))}",
|
||||
f"目标端点: {_format_value(plan.get('target_endpoint_point'))}",
|
||||
f"移动量: {_format_value(plan.get('moved_endpoint_delta'))}",
|
||||
f"当前Edge长度: {_format_value(plan.get('current_length'))}",
|
||||
f"结果Edge长度: {_format_value(plan.get('target_length'))}",
|
||||
f"建模语义: {_format_value(plan.get('edit_strategy_label'))}",
|
||||
f"影响范围: {_format_value(plan.get('edit_semantics'))}",
|
||||
f"重建Face数: {_format_value(plan.get('local_edge_deform_face_count'))}",
|
||||
]
|
||||
if not self._confirm_quick_edit_plan(
|
||||
title,
|
||||
@@ -5133,6 +5266,8 @@ class WindowActionMixin:
|
||||
"local_edge_deform_start_move": plan.get("local_edge_deform_start_move"),
|
||||
"local_edge_deform_end_move": plan.get("local_edge_deform_end_move"),
|
||||
"resize_strategy": plan.get("resize_strategy"),
|
||||
"edit_strategy_label": plan.get("edit_strategy_label"),
|
||||
"edit_semantics": plan.get("edit_semantics"),
|
||||
"resize_status": plan.get("status"),
|
||||
"resize_risk": plan.get("risk"),
|
||||
"resize_message": plan.get("message"),
|
||||
|
||||
@@ -2358,6 +2358,10 @@ class WindowCoreMixin:
|
||||
self._set_selection_mode("Feature")
|
||||
self.select_feature(int(target_id))
|
||||
self.statusBar().showMessage(f"已选择已有圆角/倒圆候选Face {target_id}")
|
||||
elif action == "inspect_existing_chamfer":
|
||||
self._set_selection_mode("Feature")
|
||||
self.select_feature(int(target_id))
|
||||
self.statusBar().showMessage(f"已选择已有倒角候选Face {target_id}")
|
||||
elif action == "fillet_edge":
|
||||
self._set_selection_mode("Edge")
|
||||
self.select_edge(int(target_id))
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user