Merge branch 'dev' into tangents
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

This commit is contained in:
gumyr 2025-10-17 11:28:51 -04:00
commit 52b2883fca
3 changed files with 108 additions and 23 deletions

View file

@ -24,7 +24,7 @@ keywords = [
"brep", "brep",
"cad", "cad",
"cadquery", "cadquery",
"opencscade", "opencascade",
"python", "python",
] ]
license = {text = "Apache-2.0"} license = {text = "Apache-2.0"}
@ -44,7 +44,7 @@ dependencies = [
"ipython >= 8.0.0, < 10", "ipython >= 8.0.0, < 10",
"lib3mf >= 2.4.1", "lib3mf >= 2.4.1",
"ocpsvg >= 0.5, < 0.6", "ocpsvg >= 0.5, < 0.6",
"ocp_gordon >= 0.1.13", "ocp_gordon >= 0.1.17",
"trianglesolver", "trianglesolver",
"sympy", "sympy",
"scipy", "scipy",

View file

@ -83,11 +83,12 @@ from OCP.BRepTools import BRepTools, BRepTools_ReShape
from OCP.gce import gce_MakeLin from OCP.gce import gce_MakeLin
from OCP.Geom import ( from OCP.Geom import (
Geom_BezierSurface, Geom_BezierSurface,
Geom_BSplineCurve,
Geom_RectangularTrimmedSurface, Geom_RectangularTrimmedSurface,
Geom_Surface, Geom_Surface,
Geom_TrimmedCurve, Geom_TrimmedCurve,
) )
from OCP.GeomAbs import GeomAbs_C0, GeomAbs_G1, GeomAbs_G2, GeomAbs_CurveType from OCP.GeomAbs import GeomAbs_C0, GeomAbs_CurveType, GeomAbs_G1, GeomAbs_G2
from OCP.GeomAPI import ( from OCP.GeomAPI import (
GeomAPI_ExtremaCurveCurve, GeomAPI_ExtremaCurveCurve,
GeomAPI_PointsToBSplineSurface, GeomAPI_PointsToBSplineSurface,
@ -104,13 +105,17 @@ from OCP.Standard import (
Standard_NoSuchObject, Standard_NoSuchObject,
) )
from OCP.StdFail import StdFail_NotDone from OCP.StdFail import StdFail_NotDone
from OCP.TColgp import TColgp_HArray2OfPnt from OCP.TColgp import TColgp_Array1OfPnt, TColgp_HArray2OfPnt
from OCP.TColStd import TColStd_HArray2OfReal from OCP.TColStd import (
TColStd_Array1OfInteger,
TColStd_Array1OfReal,
TColStd_HArray2OfReal,
)
from OCP.TopExp import TopExp from OCP.TopExp import TopExp
from OCP.TopoDS import TopoDS, TopoDS_Face, TopoDS_Shape, TopoDS_Shell, TopoDS_Solid from OCP.TopoDS import TopoDS, TopoDS_Face, TopoDS_Shape, TopoDS_Shell, TopoDS_Solid
from OCP.TopTools import TopTools_IndexedDataMapOfShapeListOfShape, TopTools_ListOfShape from OCP.TopTools import TopTools_IndexedDataMapOfShapeListOfShape, TopTools_ListOfShape
from typing_extensions import Self
from ocp_gordon import interpolate_curve_network from ocp_gordon import interpolate_curve_network
from typing_extensions import Self
from build123d.build_enums import ( from build123d.build_enums import (
CenterOf, CenterOf,
@ -922,32 +927,65 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
@classmethod @classmethod
def make_gordon_surface( def make_gordon_surface(
cls, cls,
profiles: Iterable[Edge], profiles: Iterable[VectorLike | Edge],
guides: Iterable[Edge], guides: Iterable[VectorLike | Edge],
tolerance: float = 3e-4, tolerance: float = 3e-4,
) -> Face: ) -> Face:
""" """
Creates a Gordon surface from a network of profile and guide curves. Constructs a Gordon surface from a network of profile and guide curves.
Requirements:
1. Profiles and guides may be defined as points or curves.
2. Only the first or last profile or guide may be a point.
3. At least one profile and one guide must be a non-point curve.
4. Each profile must intersect with every guide.
5. Both ends of every profile must lie on a guide.
6. Both ends of every guide must lie on a profile.
Args: Args:
profiles (Iterable[Edge]): Edges representing profile curves. profiles (Iterable[VectorLike | Edge]): Profiles defined as points or edges.
guides (Iterable[Edge]): Edges representing guide curves. guides (Iterable[VectorLike | Edge]): Guides defined as points or edges.
tolerance (float, optional): Tolerance for surface creation and tolerance (float, optional): Tolerance used for surface construction and
intersection calculations. intersection calculations.
Raises: Raises:
ValueError: Input edge cannot be empty ValueError: input Edge cannot be empty.
Returns: Returns:
Face: the interpolated Gordon surface Face: the interpolated Gordon surface
""" """
def to_geom_curve(edge: Edge): def create_zero_length_bspline_curve(
if edge.wrapped is None: point: gp_Pnt, degree: int = 1
raise ValueError("input edge cannot be empty") ) -> Geom_BSplineCurve:
control_points = TColgp_Array1OfPnt(1, 2)
control_points.SetValue(1, point)
control_points.SetValue(2, point)
adaptor = BRepAdaptor_Curve(edge.wrapped) knots = TColStd_Array1OfReal(1, 2)
curve = BRep_Tool.Curve_s(edge.wrapped, 0, 1) knots.SetValue(1, 0.0)
knots.SetValue(2, 1.0)
multiplicities = TColStd_Array1OfInteger(1, 2)
multiplicities.SetValue(1, degree + 1)
multiplicities.SetValue(2, degree + 1)
curve = Geom_BSplineCurve(control_points, knots, multiplicities, degree)
return curve
def to_geom_curve(shape: VectorLike | Edge):
if isinstance(shape, (Vector, tuple, Sequence)):
_shape = Vector(shape)
single_point_curve = create_zero_length_bspline_curve(
gp_Pnt(_shape.wrapped.XYZ())
)
return single_point_curve
if shape.wrapped is None:
raise ValueError("input Edge cannot be empty")
adaptor = BRepAdaptor_Curve(shape.wrapped)
curve = BRep_Tool.Curve_s(shape.wrapped, 0, 1)
if not ( if not (
(adaptor.IsPeriodic() and adaptor.IsClosed()) (adaptor.IsPeriodic() and adaptor.IsClosed())
or adaptor.GetType() == GeomAbs_CurveType.GeomAbs_BSplineCurve or adaptor.GetType() == GeomAbs_CurveType.GeomAbs_BSplineCurve
@ -958,8 +996,8 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
) )
return curve return curve
ocp_profiles = [to_geom_curve(edge) for edge in profiles] ocp_profiles = [to_geom_curve(shape) for shape in profiles]
ocp_guides = [to_geom_curve(edge) for edge in guides] ocp_guides = [to_geom_curve(shape) for shape in guides]
gordon_bspline_surface = interpolate_curve_network( gordon_bspline_surface = interpolate_curve_network(
ocp_profiles, ocp_guides, tolerance=tolerance ocp_profiles, ocp_guides, tolerance=tolerance

View file

@ -31,9 +31,11 @@ import os
import platform import platform
import random import random
import unittest import unittest
from unittest.mock import PropertyMock, patch
from unittest.mock import patch, PropertyMock
from OCP.Geom import Geom_RectangularTrimmedSurface from OCP.Geom import Geom_RectangularTrimmedSurface
from OCP.GeomAPI import GeomAPI_ExtremaCurveCurve
from build123d.build_common import Locations, PolarLocations from build123d.build_common import Locations, PolarLocations
from build123d.build_enums import Align, CenterOf, ContinuityLevel, GeomType from build123d.build_enums import Align, CenterOf, ContinuityLevel, GeomType
from build123d.build_line import BuildLine from build123d.build_line import BuildLine
@ -57,7 +59,6 @@ from build123d.operations_generic import fillet, offset
from build123d.operations_part import extrude from build123d.operations_part import extrude
from build123d.operations_sketch import make_face from build123d.operations_sketch import make_face
from build123d.topology import Edge, Face, Shell, Solid, Wire from build123d.topology import Edge, Face, Shell, Solid, Wire
from OCP.GeomAPI import GeomAPI_ExtremaCurveCurve
class TestFace(unittest.TestCase): class TestFace(unittest.TestCase):
@ -448,7 +449,7 @@ class TestFace(unittest.TestCase):
profiles, guides, tolerance=tolerance profiles, guides, tolerance=tolerance
) )
def test_make_gordon_surface_edge_types(self): def test_make_gordon_surface_input_types(self):
tolerance = 3e-4 tolerance = 3e-4
def point_at_uv_against_expected(u: float, v: float, expected_point: Vector): def point_at_uv_against_expected(u: float, v: float, expected_point: Vector):
@ -538,6 +539,52 @@ class TestFace(unittest.TestCase):
point_at_uv_against_expected(u=0.0, v=0.5, expected_point=guides[0] @ 0.5) point_at_uv_against_expected(u=0.0, v=0.5, expected_point=guides[0] @ 0.5)
point_at_uv_against_expected(u=1.0, v=0.5, expected_point=guides[0] @ 0.5) point_at_uv_against_expected(u=1.0, v=0.5, expected_point=guides[0] @ 0.5)
profiles = [
points[0],
ThreePointArc(
points[1], (points[1] + points[3]) / 2 + Vector(0, 0, 2), points[3]
),
points[2],
]
guides = [
Spline(
points[0],
profiles[1] @ 0,
points[2],
),
Spline(
points[0],
profiles[1] @ 1,
points[2],
),
]
gordon_surface = Face.make_gordon_surface(profiles, guides, tolerance=tolerance)
point_at_uv_against_expected(u=0.0, v=1.0, expected_point=guides[0] @ 1)
point_at_uv_against_expected(u=1.0, v=1.0, expected_point=guides[1] @ 1)
point_at_uv_against_expected(u=1.0, v=0.0, expected_point=points[0])
profiles = [
Line(points[0], points[1]),
(points[0] + points[2]) / 2,
Line(points[3], points[2]),
]
guides = [
Spline(
profiles[0] @ 0,
profiles[1],
profiles[2] @ 0,
),
Spline(
profiles[0] @ 1,
profiles[1],
profiles[2] @ 1,
),
]
with self.assertRaises(ValueError):
gordon_surface = Face.make_gordon_surface(
profiles, guides, tolerance=tolerance
)
def test_make_surface(self): def test_make_surface(self):
corners = [Vector(x, y) for x in [-50.5, 50.5] for y in [-24.5, 24.5]] corners = [Vector(x, y) for x in [-50.5, 50.5] for y in [-24.5, 24.5]]
net_exterior = Wire( net_exterior = Wire(