mirror of
https://github.com/gumyr/build123d.git
synced 2025-12-06 02:30:55 -08:00
Added Edge.is_interior property Issue #816
This commit is contained in:
parent
0625c77e4e
commit
4aee76f6c0
4 changed files with 179 additions and 4 deletions
|
|
@ -51,8 +51,16 @@ from .utils import (
|
|||
find_max_dimension,
|
||||
)
|
||||
from .zero_d import Vertex, topo_explore_common_vertex
|
||||
from .one_d import Edge, Wire, Mixin1D, edges_to_wires, topo_explore_connected_edges
|
||||
from .two_d import Face, Shell, Mixin2D,sort_wires_by_build_order
|
||||
from .one_d import (
|
||||
Edge,
|
||||
Wire,
|
||||
Mixin1D,
|
||||
edges_to_wires,
|
||||
topo_explore_connected_edges,
|
||||
offset_topods_face,
|
||||
topo_explore_connected_faces,
|
||||
)
|
||||
from .two_d import Face, Shell, Mixin2D, sort_wires_by_build_order
|
||||
from .three_d import Solid, Mixin3D
|
||||
from .composite import Compound, Curve, Sketch, Part
|
||||
|
||||
|
|
@ -79,7 +87,9 @@ __all__ = [
|
|||
"Edge",
|
||||
"Wire",
|
||||
"edges_to_wires",
|
||||
"offset_topods_face",
|
||||
"topo_explore_connected_edges",
|
||||
"topo_explore_connected_faces",
|
||||
"Face",
|
||||
"Shell",
|
||||
"sort_wires_by_build_order",
|
||||
|
|
|
|||
|
|
@ -66,7 +66,11 @@ from scipy.spatial import ConvexHull
|
|||
import OCP.TopAbs as ta
|
||||
from OCP.BRep import BRep_Tool
|
||||
from OCP.BRepAdaptor import BRepAdaptor_CompCurve, BRepAdaptor_Curve
|
||||
from OCP.BRepAlgoAPI import BRepAlgoAPI_Common, BRepAlgoAPI_Splitter
|
||||
from OCP.BRepAlgoAPI import (
|
||||
BRepAlgoAPI_Common,
|
||||
BRepAlgoAPI_Section,
|
||||
BRepAlgoAPI_Splitter,
|
||||
)
|
||||
from OCP.BRepBuilderAPI import (
|
||||
BRepBuilderAPI_DisconnectedWire,
|
||||
BRepBuilderAPI_EmptyWire,
|
||||
|
|
@ -80,6 +84,7 @@ from OCP.BRepExtrema import BRepExtrema_DistShapeShape
|
|||
from OCP.BRepFilletAPI import BRepFilletAPI_MakeFillet2d
|
||||
from OCP.BRepGProp import BRepGProp, BRepGProp_Face
|
||||
from OCP.BRepLib import BRepLib, BRepLib_FindSurface
|
||||
from OCP.BRepOffset import BRepOffset_MakeOffset
|
||||
from OCP.BRepOffsetAPI import BRepOffsetAPI_MakeOffset
|
||||
from OCP.BRepPrimAPI import BRepPrimAPI_MakeHalfSpace
|
||||
from OCP.BRepProj import BRepProj_Projection
|
||||
|
|
@ -134,6 +139,7 @@ from OCP.TopoDS import (
|
|||
TopoDS,
|
||||
TopoDS_Compound,
|
||||
TopoDS_Edge,
|
||||
TopoDS_Face,
|
||||
TopoDS_Shape,
|
||||
TopoDS_Shell,
|
||||
TopoDS_Wire,
|
||||
|
|
@ -223,6 +229,34 @@ class Mixin1D(Shape):
|
|||
raise ValueError("Can't determine direction of empty Edge or Wire")
|
||||
return self.wrapped.Orientation() == TopAbs_Orientation.TopAbs_FORWARD
|
||||
|
||||
@property
|
||||
def is_interior(self) -> bool:
|
||||
"""
|
||||
Check if the edge is an interior edge.
|
||||
|
||||
An interior edge lies between surfaces that are part of the body (internal
|
||||
to the geometry) and does not form part of the exterior boundary.
|
||||
|
||||
Returns:
|
||||
bool: True if the edge is an interior edge, False otherwise.
|
||||
"""
|
||||
# Find the faces connected to this edge and offset them
|
||||
topods_face_pair = topo_explore_connected_faces(self)
|
||||
offset_face_pair = [
|
||||
offset_topods_face(f, self.length / 100) for f in topods_face_pair
|
||||
]
|
||||
|
||||
# Intersect the offset faces
|
||||
sectionor = BRepAlgoAPI_Section(
|
||||
offset_face_pair[0], offset_face_pair[1], PerformNow=False
|
||||
)
|
||||
sectionor.Build()
|
||||
face_intersection_result = sectionor.Shape()
|
||||
|
||||
# If an edge was created the faces intersect and the edge is interior
|
||||
explorer = TopExp_Explorer(face_intersection_result, ta.TopAbs_EDGE)
|
||||
return explorer.More()
|
||||
|
||||
@property
|
||||
def length(self) -> float:
|
||||
"""Edge or Wire length"""
|
||||
|
|
@ -3004,6 +3038,15 @@ def edges_to_wires(edges: Iterable[Edge], tol: float = 1e-6) -> ShapeList[Wire]:
|
|||
return wires
|
||||
|
||||
|
||||
def offset_topods_face(face: TopoDS_Face, amount: float) -> TopoDS_Shape:
|
||||
"""Offset a topods_face"""
|
||||
offsetor = BRepOffset_MakeOffset()
|
||||
offsetor.Initialize(face, Offset=amount, Tol=TOLERANCE)
|
||||
offsetor.MakeOffsetShape()
|
||||
|
||||
return offsetor.Shape()
|
||||
|
||||
|
||||
def topo_explore_connected_edges(
|
||||
edge: Edge, parent: Shape | None = None
|
||||
) -> ShapeList[Edge]:
|
||||
|
|
@ -3029,3 +3072,31 @@ def topo_explore_connected_edges(
|
|||
connected_edges.add(topods_edge)
|
||||
|
||||
return ShapeList(Edge(e) for e in connected_edges)
|
||||
|
||||
|
||||
def topo_explore_connected_faces(
|
||||
edge: Edge, parent: Shape | None = None
|
||||
) -> list[TopoDS_Face]:
|
||||
"""Given an edge extracted from a Shape, return the topods_faces connected to it"""
|
||||
|
||||
parent = parent if parent is not None else edge.topo_parent
|
||||
if parent is None:
|
||||
raise ValueError("edge has no valid parent")
|
||||
|
||||
# make a edge --> faces mapping
|
||||
edge_face_map = TopTools_IndexedDataMapOfShapeListOfShape()
|
||||
TopExp.MapShapesAndAncestors_s(
|
||||
parent.wrapped, ta.TopAbs_EDGE, ta.TopAbs_FACE, edge_face_map
|
||||
)
|
||||
|
||||
# Query the map
|
||||
faces = []
|
||||
if edge_face_map.Contains(edge.wrapped):
|
||||
face_list = edge_face_map.FindFromKey(edge.wrapped)
|
||||
for face in face_list:
|
||||
faces.append(TopoDS.Face_s(face))
|
||||
|
||||
if len(faces) != 2:
|
||||
raise RuntimeError("Invalid # of faces connected to this edge")
|
||||
|
||||
return faces
|
||||
|
|
|
|||
|
|
@ -29,9 +29,11 @@ license:
|
|||
import math
|
||||
import unittest
|
||||
|
||||
from build123d.build_enums import AngularDirection
|
||||
from build123d.build_enums import AngularDirection, GeomType, Transition
|
||||
from build123d.geometry import Axis, Plane, Vector
|
||||
from build123d.objects_curve import CenterArc, EllipticalCenterArc
|
||||
from build123d.objects_sketch import Circle, Rectangle, RegularPolygon
|
||||
from build123d.operations_generic import sweep
|
||||
from build123d.topology import Edge
|
||||
|
||||
|
||||
|
|
@ -284,6 +286,14 @@ class TestEdge(unittest.TestCase):
|
|||
with self.assertRaises(TypeError):
|
||||
Edge(direction=(1, 0, 0))
|
||||
|
||||
def test_is_interior(self):
|
||||
path = RegularPolygon(5, 5).face().outer_wire()
|
||||
profile = path.location_at(0) * (Circle(0.6) & Rectangle(2, 1))
|
||||
target = sweep(profile, path, transition=Transition.RIGHT)
|
||||
inside_edges = target.edges().filter_by(lambda e: e.is_interior)
|
||||
self.assertEqual(len(inside_edges), 5)
|
||||
self.assertTrue(all(e.geom_type == GeomType.ELLIPSE for e in inside_edges))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
from typing import Optional
|
||||
import unittest
|
||||
|
||||
from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeEdge, BRepBuilderAPI_MakeFace
|
||||
from OCP.GProp import GProp_GProps
|
||||
from OCP.BRepGProp import BRepGProp
|
||||
from OCP.gp import gp_Pnt, gp_Pln
|
||||
from OCP.TopoDS import TopoDS_Face, TopoDS_Shape
|
||||
from build123d.build_enums import SortBy
|
||||
|
||||
from build123d.objects_part import Box
|
||||
|
|
@ -12,8 +17,11 @@ from build123d.geometry import (
|
|||
from build123d.topology import (
|
||||
Edge,
|
||||
Face,
|
||||
Shell,
|
||||
Wire,
|
||||
offset_topods_face,
|
||||
topo_explore_connected_edges,
|
||||
topo_explore_connected_faces,
|
||||
topo_explore_common_vertex,
|
||||
)
|
||||
|
||||
|
|
@ -78,6 +86,17 @@ class TestTopoExplore(DirectApiTestCase):
|
|||
connected_edges = topo_explore_connected_edges(face.edges()[0])
|
||||
self.assertEqual(len(connected_edges), 1)
|
||||
|
||||
def test_topo_explore_connected_edges_errors(self):
|
||||
# No parent case
|
||||
with self.assertRaises(ValueError):
|
||||
topo_explore_connected_edges(Edge())
|
||||
|
||||
# Null edge case
|
||||
null_edge = Wire.make_rect(1, 1).edges()[0]
|
||||
null_edge.wrapped = None
|
||||
with self.assertRaises(ValueError):
|
||||
topo_explore_connected_edges(null_edge)
|
||||
|
||||
def test_topo_explore_common_vertex(self):
|
||||
triangle = Face(
|
||||
Wire(
|
||||
|
|
@ -98,5 +117,70 @@ class TestTopoExplore(DirectApiTestCase):
|
|||
)
|
||||
|
||||
|
||||
class TestOffsetTopodsFace(unittest.TestCase):
|
||||
def setUp(self):
|
||||
# Create a simple planar face for testing
|
||||
self.face = Face.make_rect(1, 1).wrapped
|
||||
|
||||
def get_face_center(self, face: TopoDS_Face) -> tuple:
|
||||
"""Calculate the center of a face"""
|
||||
props = GProp_GProps()
|
||||
BRepGProp.SurfaceProperties_s(face, props)
|
||||
center = props.CentreOfMass()
|
||||
return (center.X(), center.Y(), center.Z())
|
||||
|
||||
def test_offset_topods_face(self):
|
||||
# Offset the face by a positive amount
|
||||
offset_amount = 1.0
|
||||
original_center = self.get_face_center(self.face)
|
||||
offset_shape = offset_topods_face(self.face, offset_amount)
|
||||
offset_center = self.get_face_center(offset_shape)
|
||||
self.assertIsInstance(offset_shape, TopoDS_Shape)
|
||||
self.assertAlmostEqual(Vector(0, 0, 1), offset_center)
|
||||
|
||||
# Offset the face by a negative amount
|
||||
offset_amount = -1.0
|
||||
offset_shape = offset_topods_face(self.face, offset_amount)
|
||||
offset_center = self.get_face_center(offset_shape)
|
||||
self.assertIsInstance(offset_shape, TopoDS_Shape)
|
||||
self.assertAlmostEqual(Vector(0, 0, -1), offset_center)
|
||||
|
||||
def test_offset_topods_face_zero(self):
|
||||
# Offset the face by zero amount
|
||||
offset_amount = 0.0
|
||||
original_center = self.get_face_center(self.face)
|
||||
offset_shape = offset_topods_face(self.face, offset_amount)
|
||||
offset_center = self.get_face_center(offset_shape)
|
||||
self.assertIsInstance(offset_shape, TopoDS_Shape)
|
||||
self.assertAlmostEqual(Vector(original_center), offset_center)
|
||||
|
||||
|
||||
class TestTopoExploreConnectedFaces(unittest.TestCase):
|
||||
def setUp(self):
|
||||
# Create a shell with 4 faces
|
||||
walls = Shell.extrude(Wire.make_rect(1, 1), (0, 0, 1))
|
||||
diagonal = Axis((0, 0, 0), (1, 1, 0))
|
||||
|
||||
# Extract the edge that is connected to two faces
|
||||
self.connected_edge = walls.edges().filter_by(Axis.Z).sort_by(diagonal)[-1]
|
||||
|
||||
# Create an edge that is only connected to one face
|
||||
self.unconnected_edge = Face.make_rect(1, 1).edges()[0]
|
||||
|
||||
def test_topo_explore_connected_faces(self):
|
||||
# Add the edge to the faces
|
||||
faces = topo_explore_connected_faces(self.connected_edge)
|
||||
self.assertEqual(len(faces), 2)
|
||||
|
||||
def test_topo_explore_connected_faces_invalid(self):
|
||||
# Test with an edge that is not connected to two faces
|
||||
with self.assertRaises(RuntimeError):
|
||||
topo_explore_connected_faces(self.unconnected_edge)
|
||||
|
||||
# No parent case
|
||||
with self.assertRaises(ValueError):
|
||||
topo_explore_connected_faces(Edge())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue