feat: 完善 STEP 一级参数化编辑识别与关系式建模

This commit is contained in:
2026-08-14 18:42:39 +08:00
parent 70b59c1de6
commit a633b5a338
26 changed files with 4676 additions and 77 deletions
+163
View File
@@ -13506,6 +13506,169 @@ class OperationMixin:
f"verified_face={verification.get('face_id', '')}."
)
def _cylindrical_hole_batch_entry(self, face_id: int) -> dict[str, object] | None:
if face_id < 0 or face_id >= len(self.faces):
return None
try:
feature = self.feature_info(face_id)
except Exception:
return None
if feature.get("surface") != "cylinder" or str(feature.get("feature_guess") or "") != "hole/groove candidate":
return None
if not _is_effectively_full_cylinder(feature):
return None
diameter = _float_or_none(feature.get("diameter"))
if diameter is None or diameter <= 1e-9:
return None
axis_data = self._cylindrical_face_axis_mid_center(face_id, feature)
center = _tuple_or_none((axis_data or {}).get("current_axis_center"))
if center is None:
center = _tuple_or_none(feature.get("axis_center"))
if center is None:
return None
try:
logical_id = self.face_region_logical_id(face_id)
except Exception:
logical_id = face_id
return {
"face_id": int(face_id),
"logical_id": int(logical_id),
"diameter": float(diameter),
"axis_center": center,
"same_domain_face_ids": tuple(_int_values(feature.get("same_domain_face_ids")) or [face_id]),
}
def _resolve_cylindrical_hole_batch_ref(self, ref: dict[str, object]) -> int | None:
reference_center = _tuple_or_none(ref.get("axis_center") or ref.get("center"))
reference_diameter = _float_or_none(ref.get("diameter"))
if reference_center is None:
return None
preferred_face_id = _int_or_none(ref.get("face_id"))
candidates: list[tuple[float, int]] = []
seen_logical_ids: set[int] = set()
face_order: list[int] = []
if preferred_face_id is not None and 0 <= preferred_face_id < len(self.faces):
face_order.append(preferred_face_id)
face_order.extend(face_id for face_id in range(len(self.faces)) if face_id != preferred_face_id)
for face_id in face_order:
entry = self._cylindrical_hole_batch_entry(face_id)
if entry is None:
continue
logical_id = int(entry.get("logical_id", face_id))
if logical_id in seen_logical_ids:
continue
seen_logical_ids.add(logical_id)
center = _tuple_or_none(entry.get("axis_center"))
diameter = _float_or_none(entry.get("diameter"))
if center is None:
continue
center_distance = _vector_length(_tuple_sub(center, reference_center))
diameter_delta = 0.0
if reference_diameter is not None and diameter is not None:
diameter_delta = abs(diameter - reference_diameter)
distance_limit = max(float(reference_diameter or diameter or 1.0) * 3.0, 1e-3)
if center_distance > distance_limit:
continue
score = center_distance + diameter_delta * 0.1 + (0.0 if face_id == preferred_face_id else 1e-6)
candidates.append((score, int(entry["face_id"])))
if not candidates:
return None
candidates.sort(key=lambda item: item[0])
return candidates[0][1]
def edit_cylindrical_holes_by_refs(
self,
refs: Iterable[dict[str, object]],
target_diameter: float | None = None,
offset: tuple[float, float, float] | None = None,
) -> str:
entries = [dict(item) for item in refs if isinstance(item, dict)]
if len(entries) < 2:
raise ValueError("Batch hole edit requires at least two cylindrical hole references.")
diameter = _float_or_none(target_diameter)
move_offset = _tuple_or_none(offset)
if diameter is None and move_offset is None:
raise ValueError("Batch hole edit requires a target diameter or a position offset.")
if diameter is not None and diameter <= 0:
raise ValueError("Target diameter must be greater than 0.")
if move_offset is not None and _vector_length(move_offset) <= 1e-9:
move_offset = None
if diameter is None and move_offset is None:
raise ValueError("Position offset is zero; no batch hole edit is required.")
snapshot = self.snapshot()
resized = 0
moved = 0
try:
if diameter is not None:
for entry in entries:
face_id = self._resolve_cylindrical_hole_batch_ref(entry)
if face_id is None:
raise RuntimeError(f"Could not resolve cylindrical hole near {entry.get('axis_center')}.")
self.resize_cylindrical_hole(face_id, diameter)
resized += 1
if move_offset is not None:
for entry in entries:
face_id = self._resolve_cylindrical_hole_batch_ref(entry)
if face_id is None:
raise RuntimeError(f"Could not resolve cylindrical hole near {entry.get('axis_center')}.")
current = self._cylindrical_hole_batch_entry(face_id)
center = _tuple_or_none((current or {}).get("axis_center"))
if center is None:
raise RuntimeError(f"Could not read current axis center for cylindrical hole face {face_id}.")
self.move_cylindrical_hole_axis(face_id, _tuple_add(center, move_offset))
moved += 1
except Exception:
self.restore_snapshot(snapshot)
raise
summary_parts: list[str] = []
if resized:
summary_parts.append(f"diameter -> {diameter:g} on {resized} holes")
if moved and move_offset is not None:
summary_parts.append(f"offset {move_offset} on {moved} holes")
return "Multi-hole edit completed: " + "; ".join(summary_parts) + "."
def resize_cylindrical_holes_by_refs(
self,
refs: Iterable[dict[str, object]],
target_diameter: float,
) -> str:
return self.edit_cylindrical_holes_by_refs(refs, target_diameter=target_diameter)
def move_cylindrical_holes_by_offset(
self,
refs: Iterable[dict[str, object]],
offset: tuple[float, float, float],
) -> str:
return self.edit_cylindrical_holes_by_refs(refs, offset=offset)
def suppress_cylindrical_holes_by_refs(
self,
refs: Iterable[dict[str, object]],
) -> str:
entries = [dict(item) for item in refs if isinstance(item, dict)]
if len(entries) < 2:
raise ValueError("Batch hole suppress requires at least two cylindrical hole references.")
snapshot = self.snapshot()
suppressed = 0
try:
for entry in entries:
face_id = self._resolve_cylindrical_hole_batch_ref(entry)
if face_id is None:
raise RuntimeError(f"Could not resolve cylindrical hole near {entry.get('axis_center')}.")
self.suppress_cylindrical_hole(face_id)
suppressed += 1
except Exception:
self.restore_snapshot(snapshot)
raise
return f"Multi-hole suppress completed: {suppressed} holes."
def resize_cylindrical_hole(self, face_id: int, new_diameter: float) -> str:
plan = self.cylindrical_resize_plan(face_id, new_diameter)
if plan["status"] == "blocked":