Files

241 lines
8.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
import math
from typing import Mapping
USER_OPERATION_PRIORITY: dict[str, int] = {
"push_pull_plane": 10,
"resize_cylinder": 20,
"resize_depth": 24,
"suppress_cylinder": 28,
"resize_slot_width": 30,
"resize_slot_depth": 31,
"resize_slot_arc_length": 34,
"resize_slot_angular_span": 35,
"resize_boss": 40,
"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,
"resize_edge_length": 70,
"resize_ellipse_edge_major_radius": 72,
"resize_ellipse_edge_minor_radius": 73,
}
USER_PRIORITY_BUCKETS: tuple[tuple[int, str, str], ...] = (
(10, "Face 面编辑", "最常用:平面拉伸/切除、面尺寸、中心和偏移。"),
(20, "孔", "常用:孔径、孔深、孔轴心和封堵。"),
(30, "槽/半孔", "常用:槽宽、槽深、弧长、弧角和槽轴心。"),
(40, "凸台/外圆", "常用:凸台直径、高度和轴心。"),
(50, "圆角/倒角", "常用但风险更高:已有圆角半径、新增圆角或倒角。"),
(60, "壳体厚度", "专项:相对面可识别时修改局部厚度。"),
(70, "Edge 边编辑", "受限:边长、端点、圆边半径等一级关系编辑。"),
(80, "解析曲面", "较少直接修改:圆锥、球面、环面等解析曲面参数。"),
(90, "只读/诊断", "暂未稳定归类为可修改特征。"),
)
EXTERNAL_RELATION_SCORE_WEIGHTS: dict[str, int] = {
"coaxial": 8,
"tangent": 5,
"coplanar": 4,
"parallel": 3,
"perpendicular": 3,
"parallel_axis": 3,
}
EXTERNAL_RELATION_SCORE_LIMIT = 18
EXTERNAL_FEATURE_HINT_SCORE_LIMIT = 12
def _text(value: object) -> str:
return str(value or "").strip()
def _float_or_none(value: object) -> float | None:
try:
return float(value)
except (TypeError, ValueError):
return None
def _int_or_zero(value: object) -> int:
try:
return int(value)
except (TypeError, ValueError):
return 0
def _text_values(value: object) -> tuple[str, ...]:
if value is None or value == "":
return ()
if isinstance(value, str):
return (value.strip(),) if value.strip() else ()
if isinstance(value, (list, tuple, set)):
return tuple(str(item).strip() for item in value if str(item).strip())
return ()
def _relation_types_from_summary(value: object) -> tuple[str, ...]:
text = _text(value)
if not text:
return ()
result: list[str] = []
for chunk in text.replace(";", ",").split(","):
relation_type = chunk.split(":", 1)[0].strip()
if relation_type:
result.append(relation_type)
return tuple(result)
def external_relation_score_bonus(info: Mapping[str, object]) -> int:
relation_types = _text_values(info.get("external_recognition_relation_types")) or _text_values(
info.get("asitus_geometric_relation_types")
)
if not relation_types:
relation_types = _relation_types_from_summary(
info.get("external_recognition_relation_summary")
or info.get("asitus_geometric_relation_summary")
)
relation_count = _int_or_zero(
info.get("external_recognition_relation_count")
or info.get("asitus_geometric_relation_count")
)
score = 0
for relation_type in relation_types:
score += EXTERNAL_RELATION_SCORE_WEIGHTS.get(relation_type, 1)
if relation_count and not relation_types:
score = min(relation_count * 2, EXTERNAL_RELATION_SCORE_LIMIT)
score = max(0, min(score, EXTERNAL_RELATION_SCORE_LIMIT))
hint_score = min(_int_or_zero(info.get("analysis_situs_feature_hint_score")), EXTERNAL_FEATURE_HINT_SCORE_LIMIT)
return max(0, min(score + hint_score, EXTERNAL_RELATION_SCORE_LIMIT + EXTERNAL_FEATURE_HINT_SCORE_LIMIT))
def _confidence_sort_rank(value: object) -> int:
return {
"high": 0,
"medium": 1,
"low": 2,
"pending": 3,
"unchecked": 3,
"none": 4,
}.get(_text(value), 5)
def _is_effectively_full_cylinder(info: Mapping[str, object]) -> bool:
if bool(info.get("is_full_cylinder")):
return True
for key in ("same_domain_angular_span", "angular_span"):
angular_span = _float_or_none(info.get(key))
if angular_span is not None and angular_span >= math.tau * 0.92:
return True
return False
def feature_recognition_priority(info: Mapping[str, object]) -> int:
"""Rank feature candidates by likely user editing frequency.
The rank is intentionally product-facing, not a geometry confidence score:
lower numbers should appear earlier in editable-feature lists.
"""
operation_key = _text(info.get("operation_key"))
if operation_key in USER_OPERATION_PRIORITY:
return USER_OPERATION_PRIORITY[operation_key]
surface = _text(info.get("surface"))
feature_guess = _text(info.get("feature_guess"))
feature_type = _text(info.get("feature_type"))
feature_actions = _text(info.get("feature_edit_actions"))
ready_actions = _text(info.get("recognition_ready_actions"))
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
if surface == "cylinder":
if feature_guess == "hole/groove candidate":
angular_span = _float_or_none(info.get("angular_span"))
if (
not _is_effectively_full_cylinder(info)
and angular_span is not None
and angular_span < math.tau * 0.92
) or _text(info.get("slot_kind")) == "partial-cylindrical-groove" or any(
token in feature_type for token in ("槽/半孔候选", "盲槽", "凹槽")
):
return 30
return 20
if feature_guess == "boss/outer-round candidate":
return 40
if feature_guess == "round/fillet candidate":
return 50
if "孔" in combined_text:
return 20
if "槽" in combined_text or "半孔" in combined_text:
return 30
if "凸台" in combined_text or "外圆" in combined_text:
return 40
if "圆角" in combined_text or "倒圆" in combined_text:
return 50
return 90
if surface == "edge" or _text(info.get("curve")):
if "圆角" in combined_text:
return 52
if "倒角" in combined_text:
return 53
return 70
if surface in {"cone", "sphere", "torus"}:
return 80
if "壳体" in combined_text or _text(info.get("shell_region_status")) == "candidate":
return 60
return 90
def feature_recognition_priority_bucket(priority: int) -> tuple[int, str, str]:
selected = USER_PRIORITY_BUCKETS[-1]
for bucket in USER_PRIORITY_BUCKETS:
if priority >= bucket[0]:
selected = bucket
else:
break
return selected
def feature_recognition_priority_label(info: Mapping[str, object]) -> str:
priority = feature_recognition_priority(info)
_start, label, _reason = feature_recognition_priority_bucket(priority)
return f"{priority:02d} · {label}"
def feature_recognition_priority_reason(info: Mapping[str, object]) -> str:
priority = feature_recognition_priority(info)
_start, _label, reason = feature_recognition_priority_bucket(priority)
return reason
def feature_recognition_sort_key(info: Mapping[str, object]) -> tuple[int, int, int, int, int, int]:
status_order = {"ready": 0, "candidate": 0, "caution": 1, "blocked": 2}
risk_order = {"low": 0, "medium": 1, "high": 2, "blocked": 3}
target_id = info.get("target_id", info.get("face_id", info.get("edge_id", -1)))
try:
numeric_target = int(target_id)
except (TypeError, ValueError):
numeric_target = -1
return (
feature_recognition_priority(info),
status_order.get(_text(info.get("status")), 9),
risk_order.get(_text(info.get("risk")), 9),
_confidence_sort_rank(info.get("confidence") or info.get("recognition_confidence")),
-external_relation_score_bonus(info),
numeric_target,
)