2026-07-27 18:28:26 +08:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import math
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
from typing import Callable, Iterable
|
|
|
|
|
|
|
|
|
|
from OCC.Core.BRep import BRep_Tool
|
|
|
|
|
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_Transform
|
|
|
|
|
from OCC.Core.BRepCheck import BRepCheck_Analyzer
|
|
|
|
|
from OCC.Core.BRepClass3d import BRepClass3d_SolidClassifier
|
|
|
|
|
from OCC.Core.BRepFilletAPI import BRepFilletAPI_MakeChamfer, BRepFilletAPI_MakeFillet
|
|
|
|
|
from OCC.Core.BRepGProp import brepgprop
|
|
|
|
|
from OCC.Core.BRepMesh import BRepMesh_IncrementalMesh
|
|
|
|
|
from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeCylinder, BRepPrimAPI_MakePrism
|
|
|
|
|
from OCC.Core.Bnd import Bnd_Box
|
|
|
|
|
from OCC.Core.GeomAbs import (
|
|
|
|
|
GeomAbs_BSplineCurve,
|
|
|
|
|
GeomAbs_BSplineSurface,
|
|
|
|
|
GeomAbs_BezierCurve,
|
|
|
|
|
GeomAbs_BezierSurface,
|
|
|
|
|
GeomAbs_Circle,
|
|
|
|
|
GeomAbs_Cone,
|
|
|
|
|
GeomAbs_Cylinder,
|
|
|
|
|
GeomAbs_Ellipse,
|
|
|
|
|
GeomAbs_Hyperbola,
|
|
|
|
|
GeomAbs_Line,
|
|
|
|
|
GeomAbs_OffsetSurface,
|
|
|
|
|
GeomAbs_OtherCurve,
|
|
|
|
|
GeomAbs_OtherSurface,
|
|
|
|
|
GeomAbs_Parabola,
|
|
|
|
|
GeomAbs_Plane,
|
|
|
|
|
GeomAbs_Sphere,
|
|
|
|
|
GeomAbs_SurfaceOfExtrusion,
|
|
|
|
|
GeomAbs_SurfaceOfRevolution,
|
|
|
|
|
GeomAbs_Torus,
|
|
|
|
|
)
|
|
|
|
|
from OCC.Core.GProp import GProp_GProps
|
|
|
|
|
from OCC.Core.ShapeFix import ShapeFix_Shape
|
|
|
|
|
from OCC.Core.ShapeUpgrade import ShapeUpgrade_UnifySameDomain
|
|
|
|
|
from OCC.Core.TopAbs import (
|
|
|
|
|
TopAbs_EDGE,
|
|
|
|
|
TopAbs_EXTERNAL,
|
|
|
|
|
TopAbs_FACE,
|
|
|
|
|
TopAbs_FORWARD,
|
|
|
|
|
TopAbs_IN,
|
|
|
|
|
TopAbs_INTERNAL,
|
|
|
|
|
TopAbs_OUT,
|
|
|
|
|
TopAbs_REVERSED,
|
|
|
|
|
TopAbs_SOLID,
|
|
|
|
|
)
|
|
|
|
|
from OCC.Core.TopExp import TopExp_Explorer, topexp
|
|
|
|
|
from OCC.Core.TopLoc import TopLoc_Location
|
|
|
|
|
from OCC.Core.TopoDS import TopoDS_Compound, TopoDS_Shape, topods
|
|
|
|
|
from OCC.Core.TopTools import TopTools_IndexedDataMapOfShapeListOfShape, TopTools_IndexedMapOfShape
|
|
|
|
|
from OCC.Core.gp import gp_Ax1, gp_Ax2, gp_Dir, gp_Pnt, gp_Trsf, gp_Vec
|
|
|
|
|
from OCC.Extend.TopologyUtils import TopologyExplorer, discretize_edge
|
|
|
|
|
|
|
|
|
|
from .constants import CURVE_TYPES, SNAPSHOT_FACE_LOGICAL_IDS_KEY, SURFACE_TYPES
|
|
|
|
|
from .geometry_utils import * # noqa: F403
|
2026-08-11 18:28:16 +08:00
|
|
|
from .step_io import _prepare_shape_for_step_export, _write_brep, _write_step
|
2026-07-27 18:28:26 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class ExportMixin:
|
|
|
|
|
def export_all(self, filename: str | Path) -> None:
|
|
|
|
|
export_shape = _compound_from_shapes(
|
|
|
|
|
_prepare_shape_for_step_export(part.shape) for part in self.display_parts()
|
|
|
|
|
)
|
|
|
|
|
_write_step(export_shape, Path(filename))
|
|
|
|
|
|
2026-08-11 18:28:16 +08:00
|
|
|
def export_internal_brep(self, filename: str | Path) -> None:
|
|
|
|
|
_write_brep(self.shape, Path(filename))
|
|
|
|
|
|
2026-07-27 18:28:26 +08:00
|
|
|
def export_quality_info(self, scope: str, target_id: int | None = None) -> dict[str, object]:
|
|
|
|
|
if scope == "all":
|
|
|
|
|
return _shape_quality_info("当前完整模型", self.shape, expect_solid=False)
|
|
|
|
|
if scope == "part":
|
|
|
|
|
if target_id is None:
|
|
|
|
|
raise ValueError("Part id is required.")
|
|
|
|
|
part = self.part_by_id(target_id)
|
|
|
|
|
if part is None:
|
|
|
|
|
raise ValueError(f"Unknown part id {target_id}")
|
2026-07-28 18:30:58 +08:00
|
|
|
info = _shape_quality_info(f"特征 {part.id}: {part.name}", part.shape, expect_solid=True)
|
2026-07-27 18:28:26 +08:00
|
|
|
info["part_id"] = part.id
|
|
|
|
|
return info
|
|
|
|
|
if scope == "solid":
|
|
|
|
|
if target_id is None or target_id < 0 or target_id >= len(self.solids):
|
|
|
|
|
raise ValueError(f"Unknown solid id {target_id}")
|
|
|
|
|
part_id, solid = self.solids[target_id]
|
|
|
|
|
info = _shape_quality_info(f"Solid {target_id}", solid, expect_solid=True)
|
|
|
|
|
info["part_id"] = part_id
|
|
|
|
|
info["solid_id"] = target_id
|
|
|
|
|
return info
|
|
|
|
|
if scope == "face":
|
|
|
|
|
if target_id is None or target_id < 0 or target_id >= len(self.faces):
|
|
|
|
|
raise ValueError(f"Unknown face id {target_id}")
|
|
|
|
|
face_ids = self.face_region_ids(target_id)
|
|
|
|
|
shape = self._face_region_shape(target_id)
|
|
|
|
|
label = f"Face {target_id}" if len(face_ids) == 1 else f"Face region from face {target_id}"
|
|
|
|
|
info = _shape_quality_info(label, shape, expect_solid=False)
|
|
|
|
|
info["part_id"] = self.face_part_ids[target_id]
|
|
|
|
|
info["solid_id"] = self.face_solid_ids[target_id]
|
|
|
|
|
info["face_id"] = target_id
|
|
|
|
|
info["face_region_ids"] = tuple(face_ids)
|
|
|
|
|
info["face_region_count"] = len(face_ids)
|
|
|
|
|
return info
|
|
|
|
|
if scope == "edge":
|
|
|
|
|
if target_id is None or target_id < 0 or target_id >= len(self.edges):
|
|
|
|
|
raise ValueError(f"Unknown edge id {target_id}")
|
|
|
|
|
info = _shape_quality_info(f"Edge {target_id}", self.edges[target_id], expect_solid=False)
|
|
|
|
|
info["part_id"] = self.edge_part_ids[target_id]
|
|
|
|
|
info["solid_id"] = self._edge_solid_id(target_id)
|
|
|
|
|
info["edge_id"] = target_id
|
|
|
|
|
return info
|
|
|
|
|
if scope == "feature":
|
|
|
|
|
if target_id is None or target_id < 0 or target_id >= len(self.faces):
|
|
|
|
|
raise ValueError(f"Unknown feature source face id {target_id}")
|
|
|
|
|
feature = self.feature_info(target_id)
|
|
|
|
|
face_ids = _int_values(feature.get("feature_highlight_face_ids")) or [target_id]
|
|
|
|
|
shape = _compound_from_shapes(self.faces[face_id] for face_id in face_ids if 0 <= face_id < len(self.faces))
|
|
|
|
|
info = _shape_quality_info(f"Feature from face {target_id}", shape, expect_solid=False)
|
|
|
|
|
info["part_id"] = self.face_part_ids[target_id]
|
|
|
|
|
info["solid_id"] = self.face_solid_ids[target_id]
|
|
|
|
|
info["face_id"] = target_id
|
|
|
|
|
info["feature_face_ids"] = tuple(face_ids)
|
|
|
|
|
return info
|
|
|
|
|
raise ValueError(f"Unknown export quality scope: {scope}")
|
|
|
|
|
|
|
|
|
|
def export_part(self, part_id: int, filename: str | Path) -> None:
|
|
|
|
|
part = self.part_by_id(part_id)
|
|
|
|
|
if part is None:
|
|
|
|
|
raise ValueError(f"Unknown part id {part_id}")
|
|
|
|
|
_write_step(_prepare_shape_for_step_export(part.shape), Path(filename))
|
|
|
|
|
|
|
|
|
|
def export_solid(self, solid_id: int, filename: str | Path) -> None:
|
|
|
|
|
if solid_id < 0 or solid_id >= len(self.solids):
|
|
|
|
|
raise ValueError(f"Unknown solid id {solid_id}")
|
|
|
|
|
_write_step(_prepare_shape_for_step_export(self.solids[solid_id][1]), Path(filename))
|
|
|
|
|
|
|
|
|
|
def export_face(self, face_id: int, filename: str | Path) -> None:
|
|
|
|
|
if face_id < 0 or face_id >= len(self.faces):
|
|
|
|
|
raise ValueError(f"Unknown face id {face_id}")
|
|
|
|
|
_write_step(self._face_region_shape(face_id), Path(filename))
|
|
|
|
|
|
|
|
|
|
def face_region_ids(self, face_id: int) -> list[int]:
|
|
|
|
|
if face_id < 0 or face_id >= len(self.faces):
|
|
|
|
|
raise ValueError(f"Unknown face id {face_id}")
|
|
|
|
|
return self.connected_same_domain_face_ids(face_id) or [face_id]
|
|
|
|
|
|
|
|
|
|
def face_region_boundary_edge_ids(self, face_id: int) -> list[int]:
|
|
|
|
|
return self._region_boundary_edge_ids(self.face_region_ids(face_id))
|
|
|
|
|
|
|
|
|
|
def _face_region_shape(self, face_id: int) -> TopoDS_Shape:
|
|
|
|
|
face_ids = self.face_region_ids(face_id)
|
|
|
|
|
shapes = [self.faces[item] for item in face_ids if 0 <= item < len(self.faces)]
|
|
|
|
|
if not shapes:
|
|
|
|
|
raise ValueError(f"Face region export did not find any valid faces for face {face_id}.")
|
|
|
|
|
if len(shapes) == 1:
|
|
|
|
|
return shapes[0]
|
|
|
|
|
return _unify_same_domain_shape(_compound_from_shapes(shapes))
|
|
|
|
|
|
|
|
|
|
def export_edge(self, edge_id: int, filename: str | Path) -> None:
|
|
|
|
|
if edge_id < 0 or edge_id >= len(self.edges):
|
|
|
|
|
raise ValueError(f"Unknown edge id {edge_id}")
|
|
|
|
|
_write_step(self.edges[edge_id], Path(filename))
|
|
|
|
|
|
|
|
|
|
def export_feature(self, face_id: int, filename: str | Path) -> None:
|
|
|
|
|
if face_id < 0 or face_id >= len(self.faces):
|
|
|
|
|
raise ValueError(f"Unknown feature source face id {face_id}")
|
|
|
|
|
feature = self.feature_info(face_id)
|
|
|
|
|
face_ids = _int_values(feature.get("feature_highlight_face_ids")) or [face_id]
|
|
|
|
|
shapes = [self.faces[item] for item in face_ids if 0 <= item < len(self.faces)]
|
|
|
|
|
if not shapes:
|
|
|
|
|
raise ValueError("Feature export did not find any valid faces.")
|
|
|
|
|
_write_step(_compound_from_shapes(shapes), Path(filename))
|
|
|
|
|
|