feat: 完善 Face 参数化编辑和隔离执行

This commit is contained in:
2026-07-31 16:36:05 +08:00
parent 27e4f7236c
commit bb44e3920d
31 changed files with 5428 additions and 252 deletions
+156 -2
View File
@@ -9,7 +9,12 @@ from OCC.Core.BRepAdaptor import BRepAdaptor_Curve, BRepAdaptor_Surface
from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Defeaturing, BRepAlgoAPI_Fuse
from OCC.Core.BRepBndLib import brepbndlib
from OCC.Core.BOPAlgo import BOPAlgo_GlueFull
from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_MakeVertex, BRepBuilderAPI_Transform
from OCC.Core.BRepBuilderAPI import (
BRepBuilderAPI_MakeFace,
BRepBuilderAPI_MakeVertex,
BRepBuilderAPI_MakeWire,
BRepBuilderAPI_Transform,
)
from OCC.Core.BRepExtrema import BRepExtrema_DistShapeShape
from OCC.Core.BRepCheck import BRepCheck_Analyzer
from OCC.Core.BRepClass3d import BRepClass3d_SolidClassifier
@@ -58,6 +63,7 @@ from OCC.Core.TopAbs import (
TopAbs_OUT,
TopAbs_REVERSED,
TopAbs_SOLID,
TopAbs_WIRE,
)
from OCC.Core.TopExp import TopExp_Explorer, topexp
from OCC.Core.TopLoc import TopLoc_Location
@@ -104,6 +110,7 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
self._face_edge_ids_cache: dict[int, list[int]] = {}
self._edge_face_ids_cache: dict[int, list[int]] = {}
self._same_domain_face_ids_cache: dict[int, list[int]] = {}
self._local_face_deform_readiness_cache: dict[int, dict[str, object]] = {}
self._edge_duplicate_key_ids_cache: dict[tuple[object, ...], list[int]] | None = None
self._same_domain_internal_edge_ids_cache: set[int] | None = None
self._same_domain_duplicate_edge_ids_cache: set[int] | None = None
@@ -184,6 +191,7 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
self._face_edge_ids_cache.clear()
self._edge_face_ids_cache.clear()
self._same_domain_face_ids_cache.clear()
self._local_face_deform_readiness_cache.clear()
self._edge_duplicate_key_ids_cache = None
self._same_domain_internal_edge_ids_cache = None
self._same_domain_duplicate_edge_ids_cache = None
@@ -275,6 +283,7 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
self._face_info_cache.clear()
self._feature_info_cache.clear()
self._same_domain_face_ids_cache.clear()
self._local_face_deform_readiness_cache.clear()
def quick_face_info(self, face_id: int) -> dict[str, object]:
if face_id < 0 or face_id >= len(self.faces):
@@ -310,6 +319,7 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
"selection_info_note": "快速选择信息;同域面、端盖、底面等深层识别会在执行编辑计划或手动扫描时再计算。",
}
info.update(_shape_bounds_info(face))
info.update(self._face_boundary_wire_info(face))
if surface_type == GeomAbs_Plane:
plane = surf.Plane()
direction = plane.Axis().Direction()
@@ -328,6 +338,7 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
info["feature_source_face_id"] = face_id
info["feature_highlight_face_ids"] = (face_id,)
info["feature_edit_actions"] = "推拉平面"
info.update(self._local_face_deform_readiness(face_id))
info.update(
self._local_face_plane_size_info(
face_id,
@@ -420,6 +431,7 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
"boundary_edges": boundary_edges,
}
info.update(_shape_bounds_info(face))
info.update(self._face_boundary_wire_info(face))
if surface_type == GeomAbs_Plane:
plane = surf.Plane()
direction = plane.Axis().Direction()
@@ -433,6 +445,7 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
info["push_pull_minus_side"] = push_pull_direction["minus_side_state"]
info["push_pull_confidence"] = push_pull_direction["confidence"]
info["push_pull_note"] = push_pull_direction["note"]
info.update(self._local_face_deform_readiness(face_id))
info.update(
self._local_face_plane_size_info(
face_id,
@@ -1128,6 +1141,112 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
self._face_edge_ids_cache[face_id] = list(edge_ids)
return edge_ids
def _face_boundary_wire_info(self, face: TopoDS_Shape) -> dict[str, object]:
try:
boundary_wires = len(_explore(face, TopAbs_WIRE))
except Exception:
boundary_wires = 0
inner_boundary_wires = max(boundary_wires - 1, 0)
return {
"boundary_wires": boundary_wires,
"inner_boundary_wires": inner_boundary_wires,
"has_inner_boundaries": inner_boundary_wires > 0,
}
def _local_face_deform_readiness(self, face_id: int) -> dict[str, object]:
if face_id < 0 or face_id >= len(self.faces):
return {
"local_face_deform_ready": False,
"local_face_deform_blocker": "Face ID 不存在,不能做局部 Face 变形。",
}
solid_id = self.face_solid_ids[face_id] if face_id < len(self.face_solid_ids) else -1
cache_key = solid_id if solid_id >= 0 else -(face_id + 1)
cached = self._local_face_deform_readiness_cache.get(cache_key)
if cached is not None:
return dict(cached)
def remember(info: dict[str, object]) -> dict[str, object]:
self._local_face_deform_readiness_cache[cache_key] = dict(info)
return dict(info)
if solid_id < 0 or solid_id >= len(self.solids):
return remember(
{
"local_face_deform_ready": False,
"local_face_deform_face_count": 1,
"local_face_deform_blocker": "找不到当前 Face 所属 Solid,不能做“只改当前面”的局部重建。",
}
)
solid_face_ids = [index for index, item in enumerate(self.face_solid_ids) if item == solid_id]
if not solid_face_ids:
solid_face_ids = [face_id]
face_count = len(solid_face_ids)
if face_count > 128:
return remember(
{
"local_face_deform_ready": False,
"local_face_deform_face_count": face_count,
"local_face_deform_blocker": "所属 Solid 的 Face 数量超过 128,当前版本不开放“只改当前面”的局部重建。",
}
)
source_shape = self.solids[solid_id][1] if 0 <= solid_id < len(self.solids) else self.faces[face_id]
tolerance = max(_shape_diagonal(source_shape) * 1e-7, 1e-6)
for item in solid_face_ids:
face = self.faces[item]
try:
surface = BRepAdaptor_Surface(face)
except Exception:
return remember(
{
"local_face_deform_ready": False,
"local_face_deform_face_count": face_count,
"local_face_deform_blocker": "所属 Solid 里有 Face 不能稳定读取曲面类型,不能做局部 Face 变形。",
}
)
if surface.GetType() != GeomAbs_Plane:
return remember(
{
"local_face_deform_ready": False,
"local_face_deform_face_count": face_count,
"local_face_deform_blocker": "所属 Solid 含有曲面,当前版本只对简单全平面 Solid 开放“只改当前面”。",
}
)
wire_info = self._face_boundary_wire_info(face)
if bool(wire_info.get("has_inner_boundaries")):
return remember(
{
"local_face_deform_ready": False,
"local_face_deform_face_count": face_count,
"local_face_deform_blocker": "所属 Solid 里有带内孔/内边界的 Face,请优先使用孔、槽或推拉等专门修改方式。",
}
)
try:
if len(self._local_deform_face_vertex_points(face, tolerance)) < 3:
return remember(
{
"local_face_deform_ready": False,
"local_face_deform_face_count": face_count,
"local_face_deform_blocker": "所属 Solid 里有 Face 顶点环不能稳定读取,不能做局部 Face 变形。",
}
)
except Exception:
return remember(
{
"local_face_deform_ready": False,
"local_face_deform_face_count": face_count,
"local_face_deform_blocker": "所属 Solid 里有 Face 顶点环读取失败,不能做局部 Face 变形。",
}
)
return remember(
{
"local_face_deform_ready": True,
"local_face_deform_face_count": face_count,
"local_face_deform_blocker": "",
}
)
def face_boundary_edge_ids(self, face_id: int) -> list[int]:
if face_id < 0 or face_id >= len(self.faces):
return []
@@ -1166,6 +1285,22 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
self._face_info_cache.pop(face_id, None)
self._feature_info_cache.pop(face_id, None)
def assign_logical_face_region_exclusive(self, logical_id: int, face_ids: Iterable[int]) -> None:
valid_face_ids = sorted({int(face_id) for face_id in face_ids if 0 <= int(face_id) < len(self.faces)})
if not valid_face_ids:
return
logical_id = int(logical_id)
replacement_id = max([logical_id, len(self.faces), *[int(item) for item in self.face_logical_ids]], default=logical_id) + 1
for face_id, current_logical_id in enumerate(list(self.face_logical_ids)):
if int(current_logical_id) != logical_id or face_id in valid_face_ids:
continue
self.face_logical_ids[face_id] = replacement_id
replacement_id += 1
self._quick_face_info_cache.pop(face_id, None)
self._face_info_cache.pop(face_id, None)
self._feature_info_cache.pop(face_id, None)
self.assign_logical_face_region(logical_id, valid_face_ids)
def nearest_edge_id_to_point(
self,
edge_ids: Iterable[int],
@@ -1487,7 +1622,26 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
return sorted(edge_id for edge_id, count in counts.items() if count == 1)
def _push_pull_profile_shape(self, face_ids: Iterable[int]) -> TopoDS_Shape:
profile_faces = [self.faces[face_id] for face_id in face_ids if 0 <= face_id < len(self.faces)]
valid_face_ids = [int(face_id) for face_id in face_ids if 0 <= int(face_id) < len(self.faces)]
if not valid_face_ids:
raise ValueError("No planar faces were found for push/pull.")
if len(valid_face_ids) > 1:
boundary_edge_ids = self._region_boundary_edge_ids(valid_face_ids)
if boundary_edge_ids:
try:
wire = BRepBuilderAPI_MakeWire()
for edge_id in boundary_edge_ids:
wire.Add(topods.Edge(self.edges[edge_id]))
if not hasattr(wire, "IsDone") or wire.IsDone():
face_builder = BRepBuilderAPI_MakeFace(wire.Wire())
if not hasattr(face_builder, "IsDone") or face_builder.IsDone():
profile = face_builder.Face()
if not profile.IsNull():
_ensure_valid_shape(profile)
return profile
except Exception:
pass
profile_faces = [self.faces[face_id] for face_id in valid_face_ids]
if not profile_faces:
raise ValueError("No planar faces were found for push/pull.")
return _unify_same_domain_shape(_compound_from_shapes(profile_faces))