feat: 完善 Face 一级关系编辑和稳定性校验
This commit is contained in:
@@ -64,7 +64,41 @@ from .geometry_utils import * # noqa: F403
|
||||
|
||||
|
||||
class FeatureMixin:
|
||||
def _first_level_fact_plan_fields(self, face_id: int, scope: str) -> dict[str, object]:
|
||||
try:
|
||||
return self.face_first_level_facts(face_id, scope=scope)
|
||||
except Exception as exc:
|
||||
resolved_scope = str(scope or "face")
|
||||
return {
|
||||
"first_level_fact_model": "STEP/B-Rep first-level fact graph",
|
||||
"first_level_fact_source_model": resolved_scope,
|
||||
"first_level_fact_status": "unavailable",
|
||||
"first_level_fact_relation_depth": 1,
|
||||
"first_level_fact_relation_boundary": "shared-edge",
|
||||
"first_level_fact_scope": resolved_scope,
|
||||
"first_level_fact_subject_role": "selected Face",
|
||||
"first_level_fact_subject_face_ids": (face_id,),
|
||||
"first_level_fact_subject_face_count": 1,
|
||||
"first_level_fact_boundary_edge_ids": (),
|
||||
"first_level_fact_boundary_edge_count": 0,
|
||||
"first_level_fact_boundary_vertex_points": (),
|
||||
"first_level_fact_boundary_vertex_count": 0,
|
||||
"first_level_fact_adjacent_face_ids": (),
|
||||
"first_level_fact_adjacent_face_count": 0,
|
||||
"first_level_fact_adjacent_surface_types": (),
|
||||
"first_level_fact_shared_edges_by_face": (),
|
||||
"first_level_fact_included_face_ids": (face_id,),
|
||||
"first_level_fact_included_face_count": 1,
|
||||
"first_level_fact_role_groups": (),
|
||||
"first_level_fact_ignored_relation_depths": ("second-level", "third-level", "deeper"),
|
||||
"first_level_fact_ignored_relation_note": (
|
||||
"Only first-level shared-edge relations are considered in this stage."
|
||||
),
|
||||
"first_level_fact_summary": f"当前 Face 的一级事实图暂时无法生成:{exc}",
|
||||
}
|
||||
|
||||
def _cylindrical_feature_first_level_plan_fields(self, face_id: int) -> dict[str, object]:
|
||||
fact_fields = self._first_level_fact_plan_fields(face_id, "cylindrical-feature")
|
||||
try:
|
||||
topology = self.cylindrical_feature_first_level_topology(face_id)
|
||||
except Exception as exc:
|
||||
@@ -80,6 +114,7 @@ class FeatureMixin:
|
||||
"first_level_adjacent_face_count": 0,
|
||||
"cylindrical_feature_side_face_ids": (face_id,),
|
||||
"cylindrical_feature_side_face_count": 1,
|
||||
**fact_fields,
|
||||
}
|
||||
|
||||
fields = {
|
||||
@@ -121,6 +156,7 @@ class FeatureMixin:
|
||||
0,
|
||||
),
|
||||
}
|
||||
fields.update(fact_fields)
|
||||
fields["first_level_edit_semantics"] = (
|
||||
"Cylindrical feature edits currently use only first-level shared-edge topology: "
|
||||
"the selected cylinder/slot wall is rebuilt together with its direct boundary neighbors; "
|
||||
@@ -2394,6 +2430,8 @@ class FeatureMixin:
|
||||
"solid_id": info["solid_id"],
|
||||
"diameter": info.get("diameter"),
|
||||
"radius": info.get("radius"),
|
||||
"axis": info.get("axis"),
|
||||
"axis_point": info.get("axis_point"),
|
||||
"angular_span": info.get("angular_span"),
|
||||
"height_estimate": scoped_info.get("height_estimate"),
|
||||
"feature_type": feature.get("feature_type"),
|
||||
@@ -3067,11 +3105,110 @@ class FeatureMixin:
|
||||
)
|
||||
return samples
|
||||
|
||||
def _cylindrical_cap_push_pull_direction(
|
||||
self,
|
||||
face_id: int,
|
||||
surf: BRepAdaptor_Surface,
|
||||
) -> dict[str, object] | None:
|
||||
if face_id < 0 or face_id >= len(self.faces):
|
||||
return None
|
||||
if surf.GetType() != GeomAbs_Plane:
|
||||
return None
|
||||
|
||||
plane_point = surf.Plane().Location()
|
||||
boundary_edge_ids = self._face_boundary_edge_ids(face_id)
|
||||
adjacent_face_ids = self._adjacent_face_ids_for_edges(boundary_edge_ids, face_id)
|
||||
if not adjacent_face_ids:
|
||||
return None
|
||||
|
||||
candidates: list[tuple[float, tuple[float, float, float], dict[str, object]]] = []
|
||||
diagonal = _shape_diagonal(self.shape)
|
||||
tolerance = min(max(diagonal * 1e-7, 1e-6), 1e-3)
|
||||
for adjacent_id in adjacent_face_ids:
|
||||
if adjacent_id < 0 or adjacent_id >= len(self.faces):
|
||||
continue
|
||||
try:
|
||||
side_surf = BRepAdaptor_Surface(self.faces[adjacent_id])
|
||||
except Exception:
|
||||
continue
|
||||
if side_surf.GetType() != GeomAbs_Cylinder:
|
||||
continue
|
||||
|
||||
cylinder = side_surf.Cylinder()
|
||||
radius = max(float(cylinder.Radius()), 0.0)
|
||||
axis = cylinder.Axis()
|
||||
axis_point = axis.Location()
|
||||
axis_dir = axis.Direction()
|
||||
try:
|
||||
axis_range = self._cylindrical_axis_range(adjacent_id, side_surf)
|
||||
except Exception:
|
||||
axis_range = {
|
||||
"v_min": min(float(side_surf.FirstVParameter()), float(side_surf.LastVParameter())),
|
||||
"v_max": max(float(side_surf.FirstVParameter()), float(side_surf.LastVParameter())),
|
||||
}
|
||||
v_min = float(axis_range["v_min"])
|
||||
v_max = float(axis_range["v_max"])
|
||||
height = max(v_max - v_min, 1e-9)
|
||||
cap_parameter = _axis_parameter(axis_point, axis_dir, plane_point)
|
||||
start_distance = abs(cap_parameter - v_min)
|
||||
end_distance = abs(cap_parameter - v_max)
|
||||
end_tolerance = max(height * 0.05, radius * 0.2, tolerance * 10.0, 0.05)
|
||||
if start_distance <= end_distance and start_distance <= end_tolerance:
|
||||
outward = _neg_tuple(_dir_tuple(axis_dir))
|
||||
end_label = "start"
|
||||
score = start_distance
|
||||
elif end_distance <= end_tolerance:
|
||||
outward = _dir_tuple(axis_dir)
|
||||
end_label = "end"
|
||||
score = end_distance
|
||||
else:
|
||||
continue
|
||||
candidates.append(
|
||||
(
|
||||
score,
|
||||
outward,
|
||||
{
|
||||
"cap_axis_face_id": adjacent_id,
|
||||
"cap_axis_end": end_label,
|
||||
"cap_axis_parameter": cap_parameter,
|
||||
"cap_axis_start_parameter": v_min,
|
||||
"cap_axis_end_parameter": v_max,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
if not candidates:
|
||||
return None
|
||||
|
||||
candidates.sort(key=lambda item: item[0])
|
||||
_score, outward, details = candidates[0]
|
||||
for _other_score, other_outward, _other_details in candidates[1:]:
|
||||
if _tuple_dot(outward, other_outward) < 0.92:
|
||||
return None
|
||||
return {
|
||||
"outward_direction": outward,
|
||||
"inward_direction": _neg_tuple(outward),
|
||||
"plus_side_state": "cylindrical-cap-axis",
|
||||
"minus_side_state": "cylindrical-cap-axis",
|
||||
"confidence": "high",
|
||||
"note": "cylindrical cap direction inferred from adjacent cylinder axis",
|
||||
"push_pull_outward_direction": outward,
|
||||
"push_pull_inward_direction": _neg_tuple(outward),
|
||||
"push_pull_plus_side": "cylindrical-cap-axis",
|
||||
"push_pull_minus_side": "cylindrical-cap-axis",
|
||||
"push_pull_confidence": "high",
|
||||
"push_pull_note": "cylindrical cap direction inferred from adjacent cylinder axis",
|
||||
**details,
|
||||
}
|
||||
|
||||
def _plane_push_pull_direction(self, face_id: int, surf: BRepAdaptor_Surface) -> dict[str, object]:
|
||||
face = self.faces[face_id]
|
||||
direction = surf.Plane().Axis().Direction()
|
||||
axis_tuple = _dir_tuple(direction)
|
||||
oriented_tuple = _oriented_dir_tuple(direction, face)
|
||||
cap_direction = self._cylindrical_cap_push_pull_direction(face_id, surf)
|
||||
if cap_direction is not None:
|
||||
return cap_direction
|
||||
fallback = {
|
||||
"outward_direction": oriented_tuple,
|
||||
"inward_direction": _neg_tuple(oriented_tuple),
|
||||
|
||||
Reference in New Issue
Block a user