feat: 完善 STEP 一级参数化编辑识别与关系式建模
This commit is contained in:
@@ -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":
|
||||
|
||||
Reference in New Issue
Block a user