feat: 完善 STEP/B-Rep 一级关系参数化编辑
This commit is contained in:
+345
-15
@@ -80,6 +80,12 @@ from .export import ExportMixin
|
||||
from .features import FeatureMixin
|
||||
from .operations import OperationMixin
|
||||
from .polydata import PolydataMixin
|
||||
from .recognition_priority import (
|
||||
feature_recognition_priority,
|
||||
feature_recognition_priority_label,
|
||||
feature_recognition_priority_reason,
|
||||
feature_recognition_sort_key,
|
||||
)
|
||||
from .transforms import TransformMixin
|
||||
from .geometry_utils import * # noqa: F403
|
||||
from .model_types import PartNode, TopologyStats
|
||||
@@ -326,7 +332,11 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
|
||||
raise ValueError(f"Unknown face id {face_id}")
|
||||
full_info = self._face_info_cache.get(face_id)
|
||||
if full_info is not None:
|
||||
return dict(full_info)
|
||||
info = dict(full_info)
|
||||
if str(info.get("surface") or "") == "cylinder" and not str(info.get("feature_type") or ""):
|
||||
info.update(self._cylindrical_feature_label_fields(info))
|
||||
info.update(self._recognition_summary_fields(info))
|
||||
return info
|
||||
cached = self._quick_face_info_cache.get(face_id)
|
||||
if cached is not None:
|
||||
return dict(cached)
|
||||
@@ -419,6 +429,39 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
|
||||
info["slot_chord_width_estimate"] = 2.0 * radius * math.sin(min(u_span, math.tau) * 0.5)
|
||||
info["slot_sagitta_depth_estimate"] = radius * (1.0 - math.cos(min(u_span, math.tau) * 0.5))
|
||||
info["slot_arc_length_estimate"] = radius * u_span
|
||||
info.update(self._quick_cylindrical_feature_hint(face_id, surf, info))
|
||||
if info.get("feature_guess") == "hole/groove candidate":
|
||||
try:
|
||||
side_face_ids = _int_values(info.get("same_domain_face_ids")) or [face_id]
|
||||
axis_range = self._cylindrical_axis_range(face_id, surf, side_face_ids)
|
||||
info.update(self._cylinder_end_opening_info(face_id, surf, axis_range))
|
||||
info.update(self._cylindrical_feature_label_fields(info))
|
||||
except Exception:
|
||||
pass
|
||||
info.update(_cylinder_resize_readiness(info))
|
||||
info.update(_cylinder_depth_readiness(info))
|
||||
info.update(_cylinder_suppress_readiness(info))
|
||||
elif info.get("feature_guess") == "boss/outer-round candidate":
|
||||
info.update(
|
||||
{
|
||||
"resize_status": "blocked",
|
||||
"resize_risk": "blocked",
|
||||
"resize_blockers": "当前对象快速识别为凸台/外圆;请使用凸台直径/高度/轴心入口,不按孔径重切。",
|
||||
"resize_warnings": "",
|
||||
"resize_note": "当前对象快速识别为凸台/外圆;请使用凸台直径/高度/轴心入口,不按孔径重切。",
|
||||
}
|
||||
)
|
||||
info.update(_cylinder_boss_resize_readiness(info))
|
||||
elif info.get("feature_guess") == "round/fillet candidate":
|
||||
info.update(
|
||||
{
|
||||
"resize_status": "blocked",
|
||||
"resize_risk": "blocked",
|
||||
"resize_blockers": "当前对象快速识别为圆角/倒圆;请使用已有圆角半径入口,不按孔径重切。",
|
||||
"resize_warnings": "",
|
||||
"resize_note": "当前对象快速识别为圆角/倒圆;请使用已有圆角半径入口,不按孔径重切。",
|
||||
}
|
||||
)
|
||||
elif surface_type == GeomAbs_Cone:
|
||||
cone = surf.Cone()
|
||||
info["axis_point"] = _point_tuple(cone.Location())
|
||||
@@ -449,6 +492,189 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
|
||||
self._quick_face_info_cache[face_id] = dict(info)
|
||||
return dict(info)
|
||||
|
||||
def _cylindrical_feature_label_fields(self, info: dict[str, object]) -> dict[str, object]:
|
||||
guess = str(info.get("feature_guess", "cylindrical face"))
|
||||
angular_span = _float_or_none(info.get("same_domain_angular_span"))
|
||||
if angular_span is None:
|
||||
angular_span = _float_or_none(info.get("angular_span")) or 0.0
|
||||
is_full = bool(info.get("is_full_cylinder")) or angular_span >= math.tau * 0.92
|
||||
if guess == "hole/groove candidate":
|
||||
if not is_full:
|
||||
return {
|
||||
"feature_type": "槽/半孔候选",
|
||||
"feature_edit_actions": (
|
||||
"调整槽/半孔宽度、深度、圆弧长度、圆弧角度;"
|
||||
"完整槽孔配对会在执行时计算。"
|
||||
),
|
||||
}
|
||||
edit_actions = "调整圆柱孔径"
|
||||
if info.get("cylinder_end_type") == "blind":
|
||||
edit_actions += ";调整盲孔/盲槽深度"
|
||||
else:
|
||||
edit_actions += ";孔深调整需要明确盲孔底面"
|
||||
edit_actions += ";封堵圆柱孔"
|
||||
return {
|
||||
"feature_type": "圆柱孔候选",
|
||||
"feature_edit_actions": edit_actions,
|
||||
}
|
||||
if guess == "boss/outer-round candidate":
|
||||
return {
|
||||
"feature_type": "凸台/外圆候选",
|
||||
"feature_edit_actions": "调整圆柱凸台直径;调整圆柱凸台高度;修改圆柱凸台轴心坐标。",
|
||||
}
|
||||
if guess == "round/fillet candidate":
|
||||
return {
|
||||
"feature_type": "圆角/倒圆候选",
|
||||
"feature_edit_actions": "可尝试修改已有圆角半径;支撑面会在执行时计算。",
|
||||
}
|
||||
return {
|
||||
"feature_type": "未明确圆柱特征",
|
||||
"feature_edit_actions": "可查看圆柱直径/半径;复杂语义需要手动扫描或执行计划确认。",
|
||||
}
|
||||
|
||||
def _quick_cylindrical_feature_hint(
|
||||
self,
|
||||
face_id: int,
|
||||
surf: BRepAdaptor_Surface,
|
||||
info: dict[str, object],
|
||||
) -> dict[str, object]:
|
||||
"""Cheap cylinder labeling for immediate selection feedback.
|
||||
|
||||
This intentionally avoids material-side sampling. It only combines
|
||||
directly connected co-cylindrical fragments and uses face orientation as
|
||||
a hint, so full edit plans still recompute and guard the real feature
|
||||
semantics before changing geometry.
|
||||
"""
|
||||
radius = _float_or_none(info.get("radius")) or 0.0
|
||||
selected_span = _float_or_none(info.get("angular_span")) or 0.0
|
||||
orientation = str(info.get("orientation") or "")
|
||||
try:
|
||||
boundary_edges = int(info.get("boundary_edges", 0) or 0)
|
||||
except (TypeError, ValueError):
|
||||
boundary_edges = 0
|
||||
solid_id = self.face_solid_ids[face_id] if 0 <= face_id < len(self.face_solid_ids) else -1
|
||||
solid_shape = self.solids[solid_id][1] if 0 <= solid_id < len(self.solids) else self.shape
|
||||
solid_diagonal = _shape_diagonal(solid_shape)
|
||||
|
||||
side_face_ids = [face_id]
|
||||
combined_span = selected_span
|
||||
same_domain_note = "快速识别:当前圆柱没有检测到直接相接的同域碎面。"
|
||||
axis_range: dict[str, object] | None = None
|
||||
try:
|
||||
side_face_ids = self.connected_same_domain_face_ids(face_id) or [face_id]
|
||||
spans: list[float] = []
|
||||
for side_id in side_face_ids:
|
||||
side_surf = BRepAdaptor_Surface(self.faces[side_id])
|
||||
if side_surf.GetType() == GeomAbs_Cylinder:
|
||||
spans.append(abs(float(side_surf.LastUParameter()) - float(side_surf.FirstUParameter())))
|
||||
if spans:
|
||||
combined_span = min(sum(spans), math.tau)
|
||||
axis_range = self._cylindrical_axis_range(face_id, surf, side_face_ids)
|
||||
same_domain_note = (
|
||||
f"快速识别:已把 {len(side_face_ids)} 个直接相接的同域圆柱碎面合并判断。"
|
||||
if len(side_face_ids) > 1
|
||||
else same_domain_note
|
||||
)
|
||||
except Exception:
|
||||
axis_range = None
|
||||
|
||||
result: dict[str, object] = {
|
||||
"same_domain_face_ids": tuple(side_face_ids),
|
||||
"same_domain_face_count": len(side_face_ids),
|
||||
"angular_span": combined_span,
|
||||
"same_domain_angular_span": combined_span,
|
||||
"same_domain_note": same_domain_note,
|
||||
"is_full_cylinder": combined_span >= math.tau * 0.92,
|
||||
}
|
||||
if axis_range is not None:
|
||||
result.update(
|
||||
{
|
||||
"same_domain_v_range": (axis_range["v_min"], axis_range["v_max"]),
|
||||
"same_domain_height_estimate": axis_range["span"],
|
||||
"same_domain_range_source": axis_range["range_source"],
|
||||
"height_estimate": axis_range["span"],
|
||||
}
|
||||
)
|
||||
|
||||
is_full = bool(result["is_full_cylinder"])
|
||||
is_partial = not is_full and 1e-6 < combined_span < math.tau * 0.92
|
||||
is_small_radius = solid_diagonal > 0 and radius <= solid_diagonal * 0.04
|
||||
is_fillet_radius = solid_diagonal > 0 and radius <= solid_diagonal * 0.12
|
||||
is_quarter_roundish = 0.15 <= selected_span <= math.pi * 1.05
|
||||
is_fillet_like = is_partial and is_quarter_roundish and is_fillet_radius and boundary_edges >= 4
|
||||
|
||||
if is_full and orientation == "reversed":
|
||||
result.update(
|
||||
{
|
||||
"feature_guess": "hole/groove candidate",
|
||||
"feature_type": "圆柱孔候选",
|
||||
"feature_edit_actions": "调整圆柱孔径;孔深、轴心或封堵会在执行时重新确认一级关系。",
|
||||
"confidence": "medium" if len(side_face_ids) > 1 else "high",
|
||||
"slot_kind": "",
|
||||
"slot_status": "",
|
||||
"slot_angular_span": "",
|
||||
"slot_open_angle": "",
|
||||
"slot_chord_width_estimate": "",
|
||||
"slot_arc_length_estimate": "",
|
||||
"slot_sagitta_depth_estimate": "",
|
||||
"note": "快速识别:完整圆柱且 Face 方向为 reversed,先按孔候选处理;完整计划会再做材料采样确认。",
|
||||
}
|
||||
)
|
||||
elif is_full and orientation == "forward":
|
||||
result.update(
|
||||
{
|
||||
"feature_guess": "boss/outer-round candidate",
|
||||
"feature_type": "凸台/外圆候选",
|
||||
"feature_edit_actions": "调整凸台/外圆直径;高度和轴心会在执行时重新确认一级关系。",
|
||||
"confidence": "medium" if len(side_face_ids) > 1 else "high",
|
||||
"slot_kind": "",
|
||||
"slot_status": "",
|
||||
"slot_angular_span": "",
|
||||
"slot_open_angle": "",
|
||||
"slot_chord_width_estimate": "",
|
||||
"slot_arc_length_estimate": "",
|
||||
"slot_sagitta_depth_estimate": "",
|
||||
"note": "快速识别:完整圆柱且 Face 方向为 forward,先按凸台/外圆候选处理;完整计划会再做材料采样确认。",
|
||||
}
|
||||
)
|
||||
elif is_fillet_like and is_small_radius:
|
||||
result.update(
|
||||
{
|
||||
"feature_guess": "round/fillet candidate",
|
||||
"feature_type": "圆角/倒圆候选",
|
||||
"feature_edit_actions": "可尝试修改已有圆角半径;支撑面会在执行时重新确认。",
|
||||
"confidence": "medium",
|
||||
"note": "快速识别:小半径部分圆柱,先按圆角/倒圆候选处理。",
|
||||
}
|
||||
)
|
||||
elif is_partial:
|
||||
span = min(max(combined_span, 0.0), math.tau)
|
||||
result.update(
|
||||
{
|
||||
"feature_guess": "hole/groove candidate",
|
||||
"feature_type": "槽/半孔候选",
|
||||
"feature_edit_actions": "调整槽/半孔宽度、深度、弧长或弧角;执行时会重新确认槽壁一级关系。",
|
||||
"confidence": "medium" if orientation == "reversed" else "low",
|
||||
"slot_kind": "partial-cylindrical-groove",
|
||||
"slot_status": "candidate",
|
||||
"slot_angular_span": combined_span,
|
||||
"slot_open_angle": max(math.tau - span, 0.0),
|
||||
"slot_chord_width_estimate": 2.0 * radius * math.sin(span * 0.5) if radius > 0 else 0.0,
|
||||
"slot_arc_length_estimate": radius * span,
|
||||
"slot_sagitta_depth_estimate": radius * (1.0 - math.cos(min(span, math.pi) * 0.5)) if radius > 0 else 0.0,
|
||||
"note": "快速识别:部分圆柱先按槽/半孔候选处理;完整计划会再做材料采样和边界确认。",
|
||||
}
|
||||
)
|
||||
else:
|
||||
result.update(
|
||||
{
|
||||
"feature_guess": "cylindrical face",
|
||||
"confidence": "unchecked",
|
||||
"note": "快速识别无法稳定判断孔、槽、凸台或圆角;执行具体修改时会生成完整计划。",
|
||||
}
|
||||
)
|
||||
return result
|
||||
|
||||
def face_info(self, face_id: int) -> dict[str, object]:
|
||||
if face_id in self._face_info_cache:
|
||||
return dict(self._face_info_cache[face_id])
|
||||
@@ -525,6 +751,7 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
|
||||
info.update(_cylinder_boss_resize_readiness(info))
|
||||
info.update(_cylinder_depth_readiness(info))
|
||||
info.update(_cylinder_suppress_readiness(info))
|
||||
info.update(self._cylindrical_feature_label_fields(info))
|
||||
elif surface_type == GeomAbs_Cone:
|
||||
cone = surf.Cone()
|
||||
info["axis_point"] = _point_tuple(cone.Location())
|
||||
@@ -601,6 +828,9 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
|
||||
|
||||
def _recognition_summary_fields(self, info: dict[str, object]) -> dict[str, object]:
|
||||
surface = str(info.get("surface") or "")
|
||||
user_priority = feature_recognition_priority(info)
|
||||
user_priority_label = feature_recognition_priority_label(info)
|
||||
user_priority_reason = feature_recognition_priority_reason(info)
|
||||
candidate = (
|
||||
str(info.get("feature_type") or "").strip()
|
||||
or str(info.get("feature_guess") or "").strip()
|
||||
@@ -637,12 +867,29 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
|
||||
("boss_resize_status", "boss_resize_risk", "boss_resize_blockers", "圆柱凸台直径/高度/轴心"),
|
||||
("depth_status", "depth_risk", "depth_blockers", "盲孔/盲槽深度"),
|
||||
("suppress_status", "suppress_risk", "suppress_blockers", "封堵孔/槽"),
|
||||
("existing_fillet_status", "existing_fillet_risk", "existing_fillet_blockers", "已有圆角半径"),
|
||||
("fillet_status", "fillet_risk", "fillet_blockers", "已有圆角半径"),
|
||||
("chamfer_status", "chamfer_risk", "chamfer_blockers", "倒角"),
|
||||
)
|
||||
feature_guess_for_capability = str(info.get("feature_guess") or "")
|
||||
|
||||
def capability_is_relevant(status_key: str) -> bool:
|
||||
if surface != "cylinder":
|
||||
return True
|
||||
if feature_guess_for_capability == "hole/groove candidate":
|
||||
return status_key in {"resize_status", "depth_status", "suppress_status", "cylinder_resize_status"}
|
||||
if feature_guess_for_capability == "boss/outer-round candidate":
|
||||
return status_key in {"boss_resize_status", "cylinder_resize_status"}
|
||||
if feature_guess_for_capability == "round/fillet candidate":
|
||||
return status_key in {"existing_fillet_status", "fillet_status"}
|
||||
return status_key in {"resize_status", "cylinder_resize_status"}
|
||||
|
||||
relevant_capability_specs = tuple(
|
||||
spec for spec in capability_specs if capability_is_relevant(spec[0])
|
||||
)
|
||||
available_capabilities = {
|
||||
status_key
|
||||
for status_key, _risk_key, _blocker_key, _label in capability_specs
|
||||
for status_key, _risk_key, _blocker_key, _label in relevant_capability_specs
|
||||
if str(info.get(status_key) or "").strip() in {"ready", "caution", "candidate"}
|
||||
}
|
||||
feature_type_text = str(info.get("feature_type") or "")
|
||||
@@ -678,7 +925,7 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
|
||||
risk = value
|
||||
if surface in FREEFORM_FACE_SURFACES:
|
||||
risk = "blocked"
|
||||
for status_key, risk_key, _blocker_key, _label in capability_specs:
|
||||
for status_key, risk_key, _blocker_key, _label in relevant_capability_specs:
|
||||
value = str(info.get(risk_key) or "").strip()
|
||||
if value == "blocked" and has_available_capability:
|
||||
continue
|
||||
@@ -743,6 +990,7 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
|
||||
add("slot_geometry", "部分圆柱槽/半孔几何")
|
||||
if info.get("shell_region_status") == "candidate":
|
||||
add("shell_opposite_face", "找到相对平面/壳体候选")
|
||||
add("user_operation_priority", f"常用操作优先级={user_priority_label}")
|
||||
|
||||
ready_actions: list[str] = []
|
||||
limited_actions: list[str] = []
|
||||
@@ -757,7 +1005,7 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
|
||||
if text and text not in items:
|
||||
items.append(text)
|
||||
|
||||
for status_key, _risk_key, blocker_key, label in capability_specs:
|
||||
for status_key, _risk_key, blocker_key, label in relevant_capability_specs:
|
||||
status = str(info.get(status_key) or "").strip()
|
||||
blocker_text = str(info.get(blocker_key) or "").strip()
|
||||
if status in {"ready", "caution", "candidate"}:
|
||||
@@ -768,7 +1016,7 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
|
||||
if has_planar_push_pull_candidate:
|
||||
add_action(ready_actions, "平面拉伸/切除")
|
||||
if has_local_face_deform:
|
||||
add_action(ready_actions, "当前面面积/U向尺寸/V向尺寸/中心/偏移")
|
||||
add_action(ready_actions, "当前面面内长度/面内宽度/中心/偏移")
|
||||
elif str(info.get("local_face_deform_blocker") or "").strip():
|
||||
add_action(limited_actions, "局部重建尺寸/中心/偏移")
|
||||
if has_shell_candidate:
|
||||
@@ -790,7 +1038,7 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
|
||||
add_unique(limitations, text)
|
||||
else:
|
||||
add_unique(blockers, text)
|
||||
for status_key, _risk_key, blocker_key, _label in capability_specs:
|
||||
for status_key, _risk_key, blocker_key, _label in relevant_capability_specs:
|
||||
text = str(info.get(blocker_key) or "").strip()
|
||||
if not text:
|
||||
continue
|
||||
@@ -830,7 +1078,14 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
|
||||
else:
|
||||
decision = "不建议自动修改"
|
||||
|
||||
summary_parts = [candidate, f"置信度={confidence}", f"风险={risk}", f"评分={score}", f"结论={decision}"]
|
||||
summary_parts = [
|
||||
candidate,
|
||||
f"优先级={user_priority_label}",
|
||||
f"置信度={confidence}",
|
||||
f"风险={risk}",
|
||||
f"评分={score}",
|
||||
f"结论={decision}",
|
||||
]
|
||||
if evidence:
|
||||
summary_evidence = list(evidence[:5])
|
||||
if "first_level_fact_graph" in evidence_keys:
|
||||
@@ -856,6 +1111,9 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
|
||||
"recognition_risk": risk,
|
||||
"recognition_score": score,
|
||||
"recognition_decision": decision,
|
||||
"recognition_user_priority": user_priority,
|
||||
"recognition_user_priority_label": user_priority_label,
|
||||
"recognition_user_priority_reason": user_priority_reason,
|
||||
"recognition_evidence": ";".join(evidence),
|
||||
"recognition_evidence_keys": tuple(evidence_keys),
|
||||
"recognition_ready_actions": ";".join(ready_actions),
|
||||
@@ -1077,15 +1335,17 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
|
||||
association_label = ""
|
||||
if lightweight and surface == "plane":
|
||||
association_label = f"相邻平面 Face {candidate_id}"
|
||||
priority = feature_recognition_priority(related)
|
||||
related.update(
|
||||
{
|
||||
"association_source_face_id": candidate_id,
|
||||
"association_hop_count": hop_count,
|
||||
"association_relation": "shared-edge-topology",
|
||||
"association_label": association_label,
|
||||
"association_priority": (
|
||||
0 if surface == "cylinder" else (1 if surface in {"cone", "sphere", "torus"} else 2)
|
||||
),
|
||||
"association_priority": priority,
|
||||
"recognition_user_priority": priority,
|
||||
"recognition_user_priority_label": feature_recognition_priority_label(related),
|
||||
"recognition_user_priority_reason": feature_recognition_priority_reason(related),
|
||||
}
|
||||
)
|
||||
results.append(related)
|
||||
@@ -1722,6 +1982,35 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
|
||||
**fillet_info,
|
||||
}
|
||||
)
|
||||
scoped_readiness_info = dict(result)
|
||||
scoped_readiness_info["angular_span"] = combined_angular_span
|
||||
scoped_readiness_info["height_estimate"] = axis_range["span"]
|
||||
scoped_readiness_info["feature_guess"] = guess
|
||||
if guess == "hole/groove candidate":
|
||||
result.update(_cylinder_resize_readiness(scoped_readiness_info))
|
||||
result.update(_cylinder_depth_readiness(scoped_readiness_info))
|
||||
result.update(_cylinder_suppress_readiness(scoped_readiness_info))
|
||||
elif guess == "boss/outer-round candidate":
|
||||
result.update(
|
||||
{
|
||||
"resize_status": "blocked",
|
||||
"resize_risk": "blocked",
|
||||
"resize_blockers": "当前对象已识别为凸台/外圆;请使用凸台直径/高度/轴心入口,不按孔径重切。",
|
||||
"resize_warnings": "",
|
||||
"resize_note": "当前对象已识别为凸台/外圆;请使用凸台直径/高度/轴心入口,不按孔径重切。",
|
||||
}
|
||||
)
|
||||
result.update(_cylinder_boss_resize_readiness(scoped_readiness_info))
|
||||
elif guess == "round/fillet candidate":
|
||||
result.update(
|
||||
{
|
||||
"resize_status": "blocked",
|
||||
"resize_risk": "blocked",
|
||||
"resize_blockers": "当前对象已识别为圆角/倒圆;请使用已有圆角半径入口,不按孔径重切。",
|
||||
"resize_warnings": "",
|
||||
"resize_note": "当前对象已识别为圆角/倒圆;请使用已有圆角半径入口,不按孔径重切。",
|
||||
}
|
||||
)
|
||||
return result
|
||||
|
||||
def _cylindrical_slot_info(
|
||||
@@ -1769,19 +2058,60 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
|
||||
|
||||
radius = max(float(info.get("radius", 0.0)), 0.0)
|
||||
angular_span = min(max(float(info.get("angular_span", 0.0)), 0.0), math.tau)
|
||||
support_face_ids = sorted(set(adjacent_face_ids) - set(end_face_ids))
|
||||
adjacent_round_face_ids: list[int] = []
|
||||
radius_tolerance = max(radius * 0.12, _shape_diagonal(self.faces[face_id]) * 1e-5, 1e-4)
|
||||
for adjacent_id in adjacent_face_ids:
|
||||
if adjacent_id == face_id:
|
||||
continue
|
||||
try:
|
||||
adjacent_info = self.face_info(adjacent_id)
|
||||
except Exception:
|
||||
continue
|
||||
adjacent_radius = _float_or_none(adjacent_info.get("radius"))
|
||||
if (
|
||||
adjacent_info.get("surface") == "cylinder"
|
||||
and adjacent_info.get("feature_guess") == "round/fillet candidate"
|
||||
and adjacent_radius is not None
|
||||
and abs(adjacent_radius - radius) <= radius_tolerance
|
||||
):
|
||||
adjacent_round_face_ids.append(adjacent_id)
|
||||
adjacent_round_face_ids = sorted(set(adjacent_round_face_ids))
|
||||
support_face_ids = sorted(set(adjacent_face_ids) - set(end_face_ids) - set(adjacent_round_face_ids))
|
||||
chain_face_ids = tuple(sorted({face_id, *adjacent_round_face_ids}))
|
||||
chain_blocker = (
|
||||
"当前圆角与其它圆角面直接相连,属于复杂圆角链或角部 blend;"
|
||||
"当前版本暂未实现稳定圆角链重建。"
|
||||
if adjacent_round_face_ids
|
||||
else ""
|
||||
)
|
||||
return {
|
||||
"existing_fillet_kind": "cylindrical-round-face",
|
||||
"existing_fillet_status": "candidate",
|
||||
"existing_fillet_status": "blocked" if chain_blocker else "candidate",
|
||||
"existing_fillet_risk": "blocked" if chain_blocker else "medium",
|
||||
"existing_fillet_blockers": chain_blocker,
|
||||
"existing_fillet_radius_estimate": radius,
|
||||
"existing_fillet_angular_span": angular_span,
|
||||
"existing_fillet_arc_length_estimate": radius * angular_span,
|
||||
"feature_existing_fillet_face_ids": (face_id,),
|
||||
"feature_existing_fillet_support_face_ids": tuple(support_face_ids),
|
||||
"feature_existing_fillet_chain_face_ids": chain_face_ids,
|
||||
"feature_existing_fillet_chain_adjacent_face_ids": tuple(adjacent_round_face_ids),
|
||||
"existing_fillet_chain_face_count": len(chain_face_ids),
|
||||
"existing_fillet_chain_status": "chain-candidate" if adjacent_round_face_ids else "single-face",
|
||||
"existing_fillet_chain_note": (
|
||||
"Detected directly connected round/fillet Faces with a matching radius; "
|
||||
"this is treated as a fillet chain or corner blend, not a single isolated fillet."
|
||||
if adjacent_round_face_ids
|
||||
else "No directly connected same-radius round/fillet Face was detected."
|
||||
),
|
||||
"existing_fillet_note": (
|
||||
"这是由局部小半径圆柱面推断出的已有圆角/倒圆候选;"
|
||||
"当前版本可尝试使用 defeature + 重新倒圆修改半径;"
|
||||
"复杂 blend 或支撑面不明确时可能失败并回滚。"
|
||||
chain_blocker
|
||||
if chain_blocker
|
||||
else (
|
||||
"这是由局部小半径圆柱面推断出的已有圆角/倒圆候选;"
|
||||
"当前版本可尝试使用 defeature + 重新倒圆修改半径;"
|
||||
"复杂 blend 或支撑面不明确时可能失败并回滚。"
|
||||
)
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user