Added continuity to topo_explore_connected_edges
Some checks are pending
benchmarks / benchmarks (macos-13, 3.12) (push) Waiting to run
benchmarks / benchmarks (macos-14, 3.12) (push) Waiting to run
benchmarks / benchmarks (ubuntu-latest, 3.12) (push) Waiting to run
benchmarks / benchmarks (windows-latest, 3.12) (push) Waiting to run
Upload coverage reports to Codecov / run (push) Waiting to run
pylint / lint (3.10) (push) Waiting to run
Run type checker / typecheck (3.10) (push) Waiting to run
Run type checker / typecheck (3.13) (push) Waiting to run
Wheel building and publishing / Build wheel on ubuntu-latest (push) Waiting to run
Wheel building and publishing / upload_pypi (push) Blocked by required conditions
tests / tests (macos-13, 3.10) (push) Waiting to run
tests / tests (macos-13, 3.13) (push) Waiting to run
tests / tests (macos-14, 3.10) (push) Waiting to run
tests / tests (macos-14, 3.13) (push) Waiting to run
tests / tests (ubuntu-latest, 3.10) (push) Waiting to run
tests / tests (ubuntu-latest, 3.13) (push) Waiting to run
tests / tests (windows-latest, 3.10) (push) Waiting to run
tests / tests (windows-latest, 3.13) (push) Waiting to run

This commit is contained in:
gumyr 2025-07-16 11:41:55 -04:00
parent 5b88b93643
commit 4795bf79ff
3 changed files with 153 additions and 6 deletions

View file

@ -28,7 +28,7 @@ license:
from __future__ import annotations
from enum import Enum, auto
from enum import Enum, auto, IntEnum, unique
from typing import Union
from typing import TypeAlias
@ -89,6 +89,28 @@ class CenterOf(Enum):
return f"<{self.__class__.__name__}.{self.name}>"
@unique
class ContinuityLevel(IntEnum):
"""
Continuity level for evaluating geometric connections.
Used to determine how smoothly adjacent geometry joins together,
such as at shared vertices between edges or shared edges between faces.
Levels:
- C0 (G0): Positional continuityelements meet at a point but may have sharp angles.
- C1 (G1): Tangent continuityelements have the same tangent direction at the junction.
- C2 (G2): Curvature continuityelements have matching curvature at the junction.
These levels correspond to common CAD definitions and are compatible with OCCT's GeomAbs_Shape.
"""
C0 = 0
C1 = 1
C2 = 2
class Extrinsic(Enum):
"""Order to apply extrinsic rotations by axis"""

View file

@ -84,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.BRepLProp import BRepLProp_CLProps, BRepLProp
from OCP.BRepOffset import BRepOffset_MakeOffset
from OCP.BRepOffsetAPI import BRepOffsetAPI_MakeOffset
from OCP.BRepPrimAPI import BRepPrimAPI_MakeHalfSpace
@ -103,6 +104,7 @@ from OCP.Geom import (
)
from OCP.Geom2d import Geom2d_Curve, Geom2d_Line, Geom2d_TrimmedCurve
from OCP.Geom2dAPI import Geom2dAPI_InterCurveCurve
from OCP.GeomAbs import GeomAbs_C0, GeomAbs_G1, GeomAbs_C1, GeomAbs_G2, GeomAbs_C2
from OCP.GeomAPI import (
GeomAPI_IntCS,
GeomAPI_Interpolate,
@ -165,6 +167,7 @@ from OCP.gp import (
)
from build123d.build_enums import (
AngularDirection,
ContinuityLevel,
CenterOf,
FrameMethod,
GeomType,
@ -3213,10 +3216,28 @@ def offset_topods_face(face: TopoDS_Face, amount: float) -> TopoDS_Shape:
def topo_explore_connected_edges(
edge: Edge, parent: Shape | None = None
edge: Edge,
parent: Shape | None = None,
continuity: ContinuityLevel = ContinuityLevel.C0,
) -> ShapeList[Edge]:
"""Given an edge extracted from a Shape, return the edges connected to it"""
"""
Find edges connected to the given edge with at least the requested continuity.
Args:
edge: The reference edge to explore from.
parent: Optional parent Shape. If None, uses edge.topo_parent.
continuity: Minimum required continuity (C0/G0, C1/G1, C2/G2).
Returns:
ShapeList[Edge]: Connected edges meeting the continuity requirement.
"""
continuity_map = {
GeomAbs_C0: ContinuityLevel.C0,
GeomAbs_G1: ContinuityLevel.C1,
GeomAbs_C1: ContinuityLevel.C1,
GeomAbs_G2: ContinuityLevel.C2,
GeomAbs_C2: ContinuityLevel.C2,
}
parent = parent if parent is not None else edge.topo_parent
if parent is None:
raise ValueError("edge has no valid parent")
@ -3233,8 +3254,26 @@ def topo_explore_connected_edges(
if given_topods_edge.IsSame(topods_edge):
continue
# If the edge shares a vertex with the given edge they are connected
if topo_explore_common_vertex(given_topods_edge, topods_edge) is not None:
connected_edges.add(topods_edge)
common_topods_vertex: Vertex | None = topo_explore_common_vertex(
given_topods_edge, topods_edge
)
if common_topods_vertex is not None:
# shared_vertex is the TopoDS_Vertex common to edge1 and edge2
u1 = BRep_Tool.Parameter_s(common_topods_vertex.wrapped, given_topods_edge)
u2 = BRep_Tool.Parameter_s(common_topods_vertex.wrapped, topods_edge)
# Build adaptors so OCCT can work on the curves
curve1 = BRepAdaptor_Curve(given_topods_edge)
curve2 = BRepAdaptor_Curve(topods_edge)
# Get the GeomAbs_Shape enum continuity at the vertex
actual_continuity = BRepLProp.Continuity_s(
curve1, curve2, u1, u2, TOLERANCE, TOLERANCE
)
actual_level = continuity_map.get(actual_continuity, ContinuityLevel.C2)
if actual_level >= continuity:
connected_edges.add(topods_edge)
return ShapeList(Edge(e) for e in connected_edges)

View file

@ -6,7 +6,7 @@ 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.build_enums import ContinuityLevel, GeomType, SortBy
from build123d.objects_part import Box
from build123d.geometry import (
@ -17,6 +17,7 @@ from build123d.geometry import (
from build123d.topology import (
Edge,
Face,
ShapeList,
Shell,
Wire,
offset_topods_face,
@ -48,6 +49,9 @@ class DirectApiTestCase(unittest.TestCase):
self.assertAlmostEqual(first.Z, second_vector.Z, places, msg=msg)
from ocp_vscode import show, show_all
class TestTopoExplore(DirectApiTestCase):
def test_topo_explore_connected_edges(self):
@ -97,6 +101,88 @@ class TestTopoExplore(DirectApiTestCase):
with self.assertRaises(ValueError):
topo_explore_connected_edges(null_edge)
def test_topo_explore_connected_edges_continuity(self):
# Create a 3-edge wire: straight line + smooth spline + sharp corner
# First edge: straight line
e1 = Edge.make_line((0, 0), (1, 0))
# Second edge: spline tangent-aligned to e1 (G1 continuous)
e2 = Edge.make_spline([e1 @ 1, (1, 1)], tangents=[(1, 0), (-1, 0)])
# Third edge: sharp corner from e2 (no G1 continuity)
e3 = Edge.make_line(e2 @ 1, e1 @ 0)
face = Face(Wire([e1, e2, e3]))
extracted_e1 = face.edges().sort_by(Axis.Y)[0]
extracted_e2 = face.edges().filter_by(GeomType.LINE, reverse=True)[0]
# Test C0: Should find both e2 and e3 connected to e1 and e2 respectively
connected_c0 = topo_explore_connected_edges(
extracted_e1, continuity=ContinuityLevel.C0
)
show_all()
self.assertEqual(len(connected_c0), 2)
self.assertTrue(
connected_c0.filter_by(GeomType.LINE, reverse=True)[0].is_same(extracted_e2)
)
# Test C1: Should still find e2 connected to e1 (they're tangent aligned)
connected_c1 = topo_explore_connected_edges(
extracted_e1, continuity=ContinuityLevel.C1
)
self.assertEqual(len(connected_c1), 1)
self.assertTrue(connected_c1[0].is_same(extracted_e2))
# Test C2: No edges are curvature continuous at the junctions
connected_c2 = topo_explore_connected_edges(
extracted_e1, continuity=ContinuityLevel.C2
)
self.assertEqual(len(connected_c2), 0)
# Also test e2 to e3 continuity
connected_e2_c0 = topo_explore_connected_edges(
extracted_e2, continuity=ContinuityLevel.C0
)
self.assertEqual(len(connected_e2_c0), 2) # e1 and e3 connected by vertex
connected_e2_c1 = topo_explore_connected_edges(
extracted_e2, continuity=ContinuityLevel.C1
)
# e3 should be excluded due to sharp corner
self.assertEqual(len(connected_e2_c1), 1)
self.assertTrue(connected_e2_c1[0].is_same(extracted_e1))
connected_e2_c2 = topo_explore_connected_edges(
extracted_e2, continuity=ContinuityLevel.C2
)
self.assertEqual(len(connected_e2_c2), 0)
def test_topo_explore_connected_edges_continuity_loop(self):
# Perfect circle: all edges G2 continuous at their junctions
circle = Edge.make_circle(1)
edges = ShapeList([circle.edge().trim(0, 0.5), circle.edge().trim(0.5, 1.0)])
circle = Face(Wire(edges))
edges = circle.edges()
for e in edges:
connected_c2 = topo_explore_connected_edges(
e, parent=circle, continuity=ContinuityLevel.C2
)
self.assertEqual(len(connected_c2), 1)
connected_c1 = topo_explore_connected_edges(
e, parent=circle, continuity=ContinuityLevel.C1
)
self.assertEqual(len(connected_c1), 1)
connected_c0 = topo_explore_connected_edges(
e, parent=circle, continuity=ContinuityLevel.C0
)
self.assertEqual(len(connected_c0), 1)
def test_topo_explore_common_vertex(self):
triangle = Face(
Wire(