Merge pull request #1099 from gongfan99/gordon_surface
Some checks failed
benchmarks / benchmarks (macos-14, 3.12) (push) Has been cancelled
benchmarks / benchmarks (macos-15-intel, 3.12) (push) Has been cancelled
benchmarks / benchmarks (ubuntu-latest, 3.12) (push) Has been cancelled
benchmarks / benchmarks (windows-latest, 3.12) (push) Has been cancelled
Upload coverage reports to Codecov / run (push) Has been cancelled
pylint / lint (3.10) (push) Has been cancelled
Run type checker / typecheck (3.10) (push) Has been cancelled
Run type checker / typecheck (3.13) (push) Has been cancelled
Wheel building and publishing / Build wheel on ubuntu-latest (push) Has been cancelled
tests / tests (macos-14, 3.10) (push) Has been cancelled
tests / tests (macos-14, 3.13) (push) Has been cancelled
tests / tests (macos-15-intel, 3.10) (push) Has been cancelled
tests / tests (macos-15-intel, 3.13) (push) Has been cancelled
tests / tests (ubuntu-latest, 3.10) (push) Has been cancelled
tests / tests (ubuntu-latest, 3.13) (push) Has been cancelled
tests / tests (windows-latest, 3.10) (push) Has been cancelled
tests / tests (windows-latest, 3.13) (push) Has been cancelled
Wheel building and publishing / upload_pypi (push) Has been cancelled

feat: add Gordon surface implementation and test
This commit is contained in:
Roger Maitland 2025-10-03 10:28:31 -04:00 committed by GitHub
commit 0ca0598907
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 132 additions and 0 deletions

View file

@ -44,6 +44,7 @@ dependencies = [
"ipython >= 8.0.0, < 10",
"lib3mf >= 2.4.1",
"ocpsvg >= 0.5, < 0.6",
"ocp_gordon >= 0.1.12",
"trianglesolver",
"sympy",
"scipy",

View file

@ -105,6 +105,7 @@ from OCP.TopExp import TopExp
from OCP.TopoDS import TopoDS, TopoDS_Face, TopoDS_Shape, TopoDS_Shell, TopoDS_Solid
from OCP.TopTools import TopTools_IndexedDataMapOfShapeListOfShape, TopTools_ListOfShape
from typing_extensions import Self
from ocp_gordon import interpolate_curve_network
from build123d.build_enums import (
CenterOf,
@ -913,6 +914,47 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
return cls(BRepBuilderAPI_MakeFace(bezier, Precision.Confusion_s()).Face())
@classmethod
def make_gordon_surface(
cls,
profiles: Iterable[Edge],
guides: Iterable[Edge],
tolerance: float = 3e-4,
) -> Face:
"""
Creates a Gordon surface from a network of profile and guide curves.
Args:
profiles (Iterable[Edge]): Edges representing profile curves.
guides (Iterable[Edge]): Edges representing guide curves.
tolerance (float, optional): Tolerance for surface creation and
intersection calculations.
Raises:
ValueError: Input edge cannot be empty
Returns:
Face: the interpolated Gordon surface
"""
def to_geom_curve(edge: Edge):
if edge.wrapped is None:
raise ValueError("input edge cannot be empty")
return BRep_Tool.Curve_s(edge.wrapped, 0, 1)
ocp_profiles = [to_geom_curve(edge) for edge in profiles]
ocp_guides = [to_geom_curve(edge) for edge in guides]
gordon_bspline_surface = interpolate_curve_network(
ocp_profiles, ocp_guides, tolerance=tolerance
)
return cls(
BRepBuilderAPI_MakeFace(
gordon_bspline_surface, Precision.Confusion_s()
).Face()
)
@classmethod
def make_plane(
cls,

View file

@ -359,6 +359,95 @@ class TestFace(unittest.TestCase):
self.assertAlmostEqual(loc.position, (0.0, 1.0, 1.5), 5)
self.assertAlmostEqual(loc.orientation, (0, -90, 0), 5)
def test_make_gordon_surface(self):
def create_test_curves(
num_profiles: int = 3,
num_guides: int = 4,
u_range: float = 1.0,
v_range: float = 1.0,
):
profiles: list[Edge] = []
guides: list[Edge] = []
intersection_points = [
[(0.0, 0.0, 0.0) for _ in range(num_guides)]
for _ in range(num_profiles)
]
for i in range(num_profiles):
for j in range(num_guides):
u = i * u_range / (num_profiles - 1)
v = j * v_range / (num_guides - 1)
z = 0.2 * math.sin(u * math.pi) * math.cos(v * math.pi)
intersection_points[i][j] = (u, v, z)
for i in range(num_profiles):
points = [intersection_points[i][j] for j in range(num_guides)]
profiles.append(Spline(points))
for j in range(num_guides):
points = [intersection_points[i][j] for i in range(num_profiles)]
guides.append(Spline(points))
return profiles, guides
profiles, guides = create_test_curves()
tolerance = 3e-4
gordon_surface = Face.make_gordon_surface(profiles, guides, tolerance=tolerance)
self.assertIsInstance(
gordon_surface, Face, "The returned object should be a Face."
)
def point_at_uv_against_expected(u: float, v: float, expected_point: Vector):
point_at_uv = gordon_surface.position_at(u, v)
self.assertAlmostEqual(
point_at_uv.X,
expected_point.X,
delta=tolerance,
msg=f"X coordinate mismatch at ({u},{v})",
)
self.assertAlmostEqual(
point_at_uv.Y,
expected_point.Y,
delta=tolerance,
msg=f"Y coordinate mismatch at ({u},{v})",
)
self.assertAlmostEqual(
point_at_uv.Z,
expected_point.Z,
delta=tolerance,
msg=f"Z coordinate mismatch at ({u},{v})",
)
point_at_uv_against_expected(
u=0.0, v=0.0, expected_point=guides[0].position_at(0.0)
)
point_at_uv_against_expected(
u=1.0, v=0.0, expected_point=profiles[0].position_at(1.0)
)
point_at_uv_against_expected(
u=0.0, v=1.0, expected_point=guides[0].position_at(1.0)
)
point_at_uv_against_expected(
u=1.0, v=1.0, expected_point=profiles[-1].position_at(1.0)
)
temp_curve = profiles[0]
profiles[0] = Edge()
with self.assertRaises(ValueError):
gordon_surface = Face.make_gordon_surface(
profiles, guides, tolerance=tolerance
)
profiles[0] = temp_curve
guides[0] = Edge()
with self.assertRaises(ValueError):
gordon_surface = Face.make_gordon_surface(
profiles, guides, tolerance=tolerance
)
def test_make_surface(self):
corners = [Vector(x, y) for x in [-50.5, 50.5] for y in [-24.5, 24.5]]
net_exterior = Wire(