From 05876c15aa49eb6312463adb61d875307160b525 Mon Sep 17 00:00:00 2001 From: Kuravi H Date: Fri, 28 Nov 2025 17:00:13 -0500 Subject: [PATCH 1/4] Add ParabolicCenterArc function --- docs/algebra_definition.rst | 2 +- docs/cheat_sheet.rst | 1 + docs/objects.rst | 7 +++++ docs/objects_1d.py | 8 +++++ src/build123d/__init__.py | 1 + src/build123d/importers.py | 1 + src/build123d/objects_curve.py | 55 +++++++++++++++++++++++++++++++++ src/build123d/topology/one_d.py | 39 ++++++++++++++++++++++- 8 files changed, 112 insertions(+), 2 deletions(-) diff --git a/docs/algebra_definition.rst b/docs/algebra_definition.rst index e87c42c..7172bbc 100644 --- a/docs/algebra_definition.rst +++ b/docs/algebra_definition.rst @@ -29,7 +29,7 @@ Objects and arithmetic :math:`B^2 := \lbrace` ``Sketch``, ``Rectangle``, ``Circle``, ``Ellipse``, ``Rectangle``, ``Polygon``, ``RegularPolygon``, ``Text``, ``Trapezoid``, ``SlotArc``, ``SlotCenterPoint``, ``SlotCenterToCenter``, ``SlotOverall`` :math:`\rbrace` -:math:`B^1 := \lbrace` ``Curve``, ``Bezier``, ``FilletPolyline``, ``PolarLine``, ``Polyline``, ``Spline``, ``Helix``, ``CenterArc``, ``EllipticalCenterArc``, ``RadiusArc``, ``SagittaArc``, ``TangentArc``, ``ThreePointArc``, ``JernArc`` :math:`\rbrace` +:math:`B^1 := \lbrace` ``Curve``, ``Bezier``, ``FilletPolyline``, ``PolarLine``, ``Polyline``, ``Spline``, ``Helix``, ``CenterArc``, ``EllipticalCenterArc``, ``ParabolicCenterArc``, ``RadiusArc``, ``SagittaArc``, ``TangentArc``, ``ThreePointArc``, ``JernArc`` :math:`\rbrace` with :math:`B^3 \subset C^3, B^2 \subset C^2` and :math:`B^1 \subset C^1` diff --git a/docs/cheat_sheet.rst b/docs/cheat_sheet.rst index 8bd0d86..1f2ebb8 100644 --- a/docs/cheat_sheet.rst +++ b/docs/cheat_sheet.rst @@ -23,6 +23,7 @@ Cheat Sheet | :class:`~objects_curve.CenterArc` | :class:`~objects_curve.DoubleTangentArc` | :class:`~objects_curve.EllipticalCenterArc` + | :class:`~objects_curve.ParabolicCenterArc` | :class:`~objects_curve.FilletPolyline` | :class:`~objects_curve.Helix` | :class:`~objects_curve.IntersectingLine` diff --git a/docs/objects.rst b/docs/objects.rst index 85204bf..8b547b0 100644 --- a/docs/objects.rst +++ b/docs/objects.rst @@ -118,6 +118,13 @@ The following objects all can be used in BuildLine contexts. Note that +++ Elliptical arc defined by center, radii & angles + .. grid-item-card:: :class:`~objects_curve.ParabolicCenterArc` + + .. image:: assets/parabolic_center_arc_example.svg + + +++ + Parabolic arc defined by vertex, focal length & angles + .. grid-item-card:: :class:`~objects_curve.FilletPolyline` .. image:: assets/filletpolyline_example.svg diff --git a/docs/objects_1d.py b/docs/objects_1d.py index 1d72359..79baa74 100644 --- a/docs/objects_1d.py +++ b/docs/objects_1d.py @@ -125,6 +125,14 @@ svg.add_shape(elliptical_center_arc.line) svg.add_shape(dot.moved(Location(Vector((0, 0))))) svg.write("assets/elliptical_center_arc_example.svg") +with BuildLine() as parabolic_center_arc: + ParabolicCenterArc((0, 0), 0.5, 60, 0) +s = 100 / max(*parabolic_center_arc.line.bounding_box().size) +svg = ExportSVG(scale=s) +svg.add_shape(parabolic_center_arc.line) +svg.add_shape(dot.moved(Location(Vector((0, 0))))) +svg.write("assets/parabolic_center_arc_example.svg") + with BuildLine() as helix: Helix(1, 3, 1) scene = Compound(helix.line) + Compound.make_triad(0.5) diff --git a/src/build123d/__init__.py b/src/build123d/__init__.py index 2dcf0b0..2217685 100644 --- a/src/build123d/__init__.py +++ b/src/build123d/__init__.py @@ -88,6 +88,7 @@ __all__ = [ "DoubleTangentArc", "EllipticalCenterArc", "EllipticalStartArc", + "ParabolicCenterArc", "FilletPolyline", "Helix", "IntersectingLine", diff --git a/src/build123d/importers.py b/src/build123d/importers.py index d53628a..b4b5743 100644 --- a/src/build123d/importers.py +++ b/src/build123d/importers.py @@ -287,6 +287,7 @@ def import_svg_as_buildline_code( "QuadraticBezier": ["Bezier", "start", "control", "end"], "Arc": [ "EllipticalCenterArc", + "ParabolicCenterArc", # "EllipticalStartArc", "start", "end", diff --git a/src/build123d/objects_curve.py b/src/build123d/objects_curve.py index 71d788b..dc0b8e0 100644 --- a/src/build123d/objects_curve.py +++ b/src/build123d/objects_curve.py @@ -741,6 +741,61 @@ class EllipticalCenterArc(BaseEdgeObject): super().__init__(curve, mode=mode) +class ParabolicCenterArc(BaseEdgeObject): + """Line Object: Parabolic Center Arc + + Create a parabolic arc defined by a vertex point and focal length (distance from focus to vertex). + + Args: + vertex (VectorLike): parabola vertex + focal_length (float): focal length the parabola (distance from the vertex to focus along the x-axis of plane) + start_angle (float, optional): arc start angle. + Defaults to 0.0 + end_angle (float, optional): arc end angle. + Defaults to 90.0 + rotation (float, optional): angle to rotate arc. Defaults to 0.0 + angular_direction (AngularDirection, optional): arc direction. + Defaults to AngularDirection.COUNTER_CLOCKWISE + plane (Plane, optional): base plane. Defaults to Plane.XY + mode (Mode, optional): combination mode. Defaults to Mode.ADD + """ + + _applies_to = [BuildLine._tag] + + def __init__( + self, + vertex: VectorLike, + focal_length: float, + start_angle: float = 0.0, + end_angle: float = 90.0, + rotation: float = 0.0, + angular_direction: AngularDirection = AngularDirection.COUNTER_CLOCKWISE, + mode: Mode = Mode.ADD, + ): + context: BuildLine | None = BuildLine._get_context(self) + validate_inputs(context, self) + + vertex_pnt = WorkplaneList.localize(vertex) + if context is None: + parabola_workplane = Plane.XY + else: + parabola_workplane = copy_module.copy( + WorkplaneList._get_context().workplanes[0] + ) + parabola_workplane.origin = vertex_pnt + curve = Edge.make_parabola( + focal_length=focal_length, + plane=parabola_workplane, + start_angle=start_angle, + end_angle=end_angle, + angular_direction=angular_direction, + ).rotate( + Axis(parabola_workplane.origin, parabola_workplane.z_dir.to_dir()), rotation + ) + + super().__init__(curve, mode=mode) + + class Helix(BaseEdgeObject): """Line Object: Helix diff --git a/src/build123d/topology/one_d.py b/src/build123d/topology/one_d.py index f48c7bb..a9e0172 100644 --- a/src/build123d/topology/one_d.py +++ b/src/build123d/topology/one_d.py @@ -88,7 +88,7 @@ from OCP.BRepOffsetAPI import BRepOffsetAPI_MakeOffset from OCP.BRepPrimAPI import BRepPrimAPI_MakeHalfSpace from OCP.BRepProj import BRepProj_Projection from OCP.BRepTools import BRepTools, BRepTools_WireExplorer -from OCP.GC import GC_MakeArcOfCircle, GC_MakeArcOfEllipse +from OCP.GC import GC_MakeArcOfCircle, GC_MakeArcOfEllipse, GC_MakeArcOfParabola from OCP.GCPnts import ( GCPnts_AbscissaPoint, GCPnts_QuasiUniformDeflection, @@ -151,6 +151,7 @@ from OCP.gp import ( gp_Dir, gp_Dir2d, gp_Elips, + gp_Parab, gp_Pln, gp_Pnt, gp_Pnt2d, @@ -2204,6 +2205,42 @@ class Edge(Mixin1D[TopoDS_Edge]): return ellipse + @classmethod + def make_parabola( + cls, + focal_length: float, + plane: Plane = Plane.XY, + start_angle: float = 0.0, + end_angle: float = 90.0, + angular_direction: AngularDirection = AngularDirection.COUNTER_CLOCKWISE, + ) -> Edge: + """make parabola + + Makes an parabola centered at the origin of plane. + + Args: + focal_length (float): focal length the parabola (distance from the vertex to focus along the x-axis of plane) + plane (Plane, optional): base plane. Defaults to Plane.XY. + start_angle (float, optional): Defaults to 0.0. + end_angle (float, optional): Defaults to 90.0. + angular_direction (AngularDirection, optional): arc direction. + Defaults to AngularDirection.COUNTER_CLOCKWISE. + + Returns: + Edge: full or partial parabola + """ + parabola_gp = gp_Parab(plane.to_gp_ax2(), focal_length) + + parabola_geom = GC_MakeArcOfParabola( + parabola_gp, + start_angle * DEG2RAD, + end_angle * DEG2RAD, + angular_direction == AngularDirection.COUNTER_CLOCKWISE, + ).Value() + parabola = cls(BRepBuilderAPI_MakeEdge(parabola_geom).Edge()) + + return parabola + @classmethod def make_helix( cls, From ae93ee282d666b401969c12639430ee2fe7579bc Mon Sep 17 00:00:00 2001 From: Kuravi H Date: Fri, 28 Nov 2025 17:14:49 -0500 Subject: [PATCH 2/4] Update docs --- docs/objects.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/objects.rst b/docs/objects.rst index 8b547b0..a4365c7 100644 --- a/docs/objects.rst +++ b/docs/objects.rst @@ -248,6 +248,7 @@ Reference .. autoclass:: CenterArc .. autoclass:: DoubleTangentArc .. autoclass:: EllipticalCenterArc +.. autoclass:: ParabolicCenterArc .. autoclass:: FilletPolyline .. autoclass:: Helix .. autoclass:: IntersectingLine From 4652df3e71bef9a0243462ec57598f0c6c88143d Mon Sep 17 00:00:00 2001 From: Kuravi H Date: Fri, 28 Nov 2025 18:02:44 -0500 Subject: [PATCH 3/4] Add HyperbolicCenterArc function --- docs/algebra_definition.rst | 2 +- docs/cheat_sheet.rst | 1 + docs/objects.rst | 8 +++++ docs/objects_1d.py | 8 +++++ src/build123d/__init__.py | 1 + src/build123d/importers.py | 1 + src/build123d/objects_curve.py | 58 +++++++++++++++++++++++++++++++++ src/build123d/topology/one_d.py | 56 ++++++++++++++++++++++++++++++- 8 files changed, 133 insertions(+), 2 deletions(-) diff --git a/docs/algebra_definition.rst b/docs/algebra_definition.rst index 7172bbc..53f1062 100644 --- a/docs/algebra_definition.rst +++ b/docs/algebra_definition.rst @@ -29,7 +29,7 @@ Objects and arithmetic :math:`B^2 := \lbrace` ``Sketch``, ``Rectangle``, ``Circle``, ``Ellipse``, ``Rectangle``, ``Polygon``, ``RegularPolygon``, ``Text``, ``Trapezoid``, ``SlotArc``, ``SlotCenterPoint``, ``SlotCenterToCenter``, ``SlotOverall`` :math:`\rbrace` -:math:`B^1 := \lbrace` ``Curve``, ``Bezier``, ``FilletPolyline``, ``PolarLine``, ``Polyline``, ``Spline``, ``Helix``, ``CenterArc``, ``EllipticalCenterArc``, ``ParabolicCenterArc``, ``RadiusArc``, ``SagittaArc``, ``TangentArc``, ``ThreePointArc``, ``JernArc`` :math:`\rbrace` +:math:`B^1 := \lbrace` ``Curve``, ``Bezier``, ``FilletPolyline``, ``PolarLine``, ``Polyline``, ``Spline``, ``Helix``, ``CenterArc``, ``EllipticalCenterArc``, ``ParabolicCenterArc``, ``HyperbolicCenterArc``, ``RadiusArc``, ``SagittaArc``, ``TangentArc``, ``ThreePointArc``, ``JernArc`` :math:`\rbrace` with :math:`B^3 \subset C^3, B^2 \subset C^2` and :math:`B^1 \subset C^1` diff --git a/docs/cheat_sheet.rst b/docs/cheat_sheet.rst index 1f2ebb8..3361f71 100644 --- a/docs/cheat_sheet.rst +++ b/docs/cheat_sheet.rst @@ -24,6 +24,7 @@ Cheat Sheet | :class:`~objects_curve.DoubleTangentArc` | :class:`~objects_curve.EllipticalCenterArc` | :class:`~objects_curve.ParabolicCenterArc` + | :class:`~objects_curve.HyperbolicCenterArc` | :class:`~objects_curve.FilletPolyline` | :class:`~objects_curve.Helix` | :class:`~objects_curve.IntersectingLine` diff --git a/docs/objects.rst b/docs/objects.rst index a4365c7..df6b0e2 100644 --- a/docs/objects.rst +++ b/docs/objects.rst @@ -125,6 +125,13 @@ The following objects all can be used in BuildLine contexts. Note that +++ Parabolic arc defined by vertex, focal length & angles + .. grid-item-card:: :class:`~objects_curve.HyperbolicCenterArc` + + .. image:: assets/hyperbolic_center_arc_example.svg + + +++ + Hyperbolic arc defined by center, radii & angles + .. grid-item-card:: :class:`~objects_curve.FilletPolyline` .. image:: assets/filletpolyline_example.svg @@ -249,6 +256,7 @@ Reference .. autoclass:: DoubleTangentArc .. autoclass:: EllipticalCenterArc .. autoclass:: ParabolicCenterArc +.. autoclass:: HyperbolicCenterArc .. autoclass:: FilletPolyline .. autoclass:: Helix .. autoclass:: IntersectingLine diff --git a/docs/objects_1d.py b/docs/objects_1d.py index 79baa74..a7d24d7 100644 --- a/docs/objects_1d.py +++ b/docs/objects_1d.py @@ -133,6 +133,14 @@ svg.add_shape(parabolic_center_arc.line) svg.add_shape(dot.moved(Location(Vector((0, 0))))) svg.write("assets/parabolic_center_arc_example.svg") +with BuildLine() as hyperbolic_center_arc: + HyperbolicCenterArc((0, 0), 0.5, 1, 45, 90) +s = 100 / max(*hyperbolic_center_arc.line.bounding_box().size) +svg = ExportSVG(scale=s) +svg.add_shape(hyperbolic_center_arc.line) +svg.add_shape(dot.moved(Location(Vector((0, 0))))) +svg.write("assets/hyperbolic_center_arc_example.svg") + with BuildLine() as helix: Helix(1, 3, 1) scene = Compound(helix.line) + Compound.make_triad(0.5) diff --git a/src/build123d/__init__.py b/src/build123d/__init__.py index 2217685..69df5fe 100644 --- a/src/build123d/__init__.py +++ b/src/build123d/__init__.py @@ -89,6 +89,7 @@ __all__ = [ "EllipticalCenterArc", "EllipticalStartArc", "ParabolicCenterArc", + "HyperbolicCenterArc", "FilletPolyline", "Helix", "IntersectingLine", diff --git a/src/build123d/importers.py b/src/build123d/importers.py index b4b5743..1b376a0 100644 --- a/src/build123d/importers.py +++ b/src/build123d/importers.py @@ -288,6 +288,7 @@ def import_svg_as_buildline_code( "Arc": [ "EllipticalCenterArc", "ParabolicCenterArc", + "HyperbolicCenterArc", # "EllipticalStartArc", "start", "end", diff --git a/src/build123d/objects_curve.py b/src/build123d/objects_curve.py index dc0b8e0..26ce6dc 100644 --- a/src/build123d/objects_curve.py +++ b/src/build123d/objects_curve.py @@ -796,6 +796,64 @@ class ParabolicCenterArc(BaseEdgeObject): super().__init__(curve, mode=mode) +class HyperbolicCenterArc(BaseEdgeObject): + """Line Object: Hyperbolic Center Arc + + Create a hyperbolic arc defined by a center point and focal length (distance from focus to vertex). + + Args: + center (VectorLike): hyperbola center + x_radius (float): x radius of the ellipse (along the x-axis of plane) + y_radius (float): y radius of the ellipse (along the y-axis of plane) + start_angle (float, optional): arc start angle from x-axis. + Defaults to 0.0 + end_angle (float, optional): arc end angle from x-axis. + Defaults to 90.0 + rotation (float, optional): angle to rotate arc. Defaults to 0.0 + angular_direction (AngularDirection, optional): arc direction. + Defaults to AngularDirection.COUNTER_CLOCKWISE + plane (Plane, optional): base plane. Defaults to Plane.XY + mode (Mode, optional): combination mode. Defaults to Mode.ADD + """ + + _applies_to = [BuildLine._tag] + + def __init__( + self, + center: VectorLike, + x_radius: float, + y_radius: float, + start_angle: float = 0.0, + end_angle: float = 90.0, + rotation: float = 0.0, + angular_direction: AngularDirection = AngularDirection.COUNTER_CLOCKWISE, + mode: Mode = Mode.ADD, + ): + context: BuildLine | None = BuildLine._get_context(self) + validate_inputs(context, self) + + center_pnt = WorkplaneList.localize(center) + if context is None: + hyperbola_workplane = Plane.XY + else: + hyperbola_workplane = copy_module.copy( + WorkplaneList._get_context().workplanes[0] + ) + hyperbola_workplane.origin = center_pnt + curve = Edge.make_hyperbola( + x_radius=x_radius, + y_radius=y_radius, + plane=hyperbola_workplane, + start_angle=start_angle, + end_angle=end_angle, + angular_direction=angular_direction, + ).rotate( + Axis(hyperbola_workplane.origin, hyperbola_workplane.z_dir.to_dir()), rotation + ) + + super().__init__(curve, mode=mode) + + class Helix(BaseEdgeObject): """Line Object: Helix diff --git a/src/build123d/topology/one_d.py b/src/build123d/topology/one_d.py index a9e0172..1df41b9 100644 --- a/src/build123d/topology/one_d.py +++ b/src/build123d/topology/one_d.py @@ -88,7 +88,7 @@ from OCP.BRepOffsetAPI import BRepOffsetAPI_MakeOffset from OCP.BRepPrimAPI import BRepPrimAPI_MakeHalfSpace from OCP.BRepProj import BRepProj_Projection from OCP.BRepTools import BRepTools, BRepTools_WireExplorer -from OCP.GC import GC_MakeArcOfCircle, GC_MakeArcOfEllipse, GC_MakeArcOfParabola +from OCP.GC import GC_MakeArcOfCircle, GC_MakeArcOfEllipse, GC_MakeArcOfParabola, GC_MakeArcOfHyperbola from OCP.GCPnts import ( GCPnts_AbscissaPoint, GCPnts_QuasiUniformDeflection, @@ -152,6 +152,7 @@ from OCP.gp import ( gp_Dir2d, gp_Elips, gp_Parab, + gp_Hypr, gp_Pln, gp_Pnt, gp_Pnt2d, @@ -2241,6 +2242,59 @@ class Edge(Mixin1D[TopoDS_Edge]): return parabola + @classmethod + def make_hyperbola( + cls, + x_radius: float, + y_radius: float, + plane: Plane = Plane.XY, + start_angle: float = 360.0, + end_angle: float = 360.0, + angular_direction: AngularDirection = AngularDirection.COUNTER_CLOCKWISE, + ) -> Edge: + """make hyperbola + + Makes a hyperbola centered at the origin of plane. + + Args: + x_radius (float): x radius of the hyperbola (along the x-axis of plane) + y_radius (float): y radius of the hyperbola (along the y-axis of plane) + plane (Plane, optional): base plane. Defaults to Plane.XY. + start_angle (float, optional): Defaults to 360.0. + end_angle (float, optional): Defaults to 360.0. + angular_direction (AngularDirection, optional): arc direction. + Defaults to AngularDirection.COUNTER_CLOCKWISE. + + Returns: + Edge: full or partial hyperbola + """ + ax1 = gp_Ax1(plane.origin.to_pnt(), plane.z_dir.to_dir()) + + if y_radius > x_radius: + # swap x and y radius and rotate by 90° afterwards to create an ellipse + # with x_radius < y_radius + correction_angle = 90.0 * DEG2RAD + hyperbola_gp = gp_Hypr(plane.to_gp_ax2(), y_radius, x_radius).Rotated( + ax1, correction_angle + ) + else: + correction_angle = 0.0 + hyperbola_gp = gp_Hypr(plane.to_gp_ax2(), x_radius, y_radius) + + if start_angle == end_angle: # full hyperbola case + hyperbola = cls(BRepBuilderAPI_MakeEdge(hyperbola_gp).Edge()) + else: # arc case + # take correction_angle into account + hyperbola_geom = GC_MakeArcOfHyperbola( + hyperbola_gp, + start_angle * DEG2RAD - correction_angle, + end_angle * DEG2RAD - correction_angle, + angular_direction == AngularDirection.COUNTER_CLOCKWISE, + ).Value() + hyperbola = cls(BRepBuilderAPI_MakeEdge(hyperbola_geom).Edge()) + + return hyperbola + @classmethod def make_helix( cls, From ddd236c7878e73d9c3d2de3d31eee9c9f98b43dc Mon Sep 17 00:00:00 2001 From: Kuravi H Date: Sat, 29 Nov 2025 12:16:01 -0500 Subject: [PATCH 4/4] Remove plane from docstring Remove parabola and hyperbola from importers --- src/build123d/importers.py | 2 -- src/build123d/objects_curve.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/build123d/importers.py b/src/build123d/importers.py index 1b376a0..d53628a 100644 --- a/src/build123d/importers.py +++ b/src/build123d/importers.py @@ -287,8 +287,6 @@ def import_svg_as_buildline_code( "QuadraticBezier": ["Bezier", "start", "control", "end"], "Arc": [ "EllipticalCenterArc", - "ParabolicCenterArc", - "HyperbolicCenterArc", # "EllipticalStartArc", "start", "end", diff --git a/src/build123d/objects_curve.py b/src/build123d/objects_curve.py index 26ce6dc..7846043 100644 --- a/src/build123d/objects_curve.py +++ b/src/build123d/objects_curve.py @@ -756,7 +756,6 @@ class ParabolicCenterArc(BaseEdgeObject): rotation (float, optional): angle to rotate arc. Defaults to 0.0 angular_direction (AngularDirection, optional): arc direction. Defaults to AngularDirection.COUNTER_CLOCKWISE - plane (Plane, optional): base plane. Defaults to Plane.XY mode (Mode, optional): combination mode. Defaults to Mode.ADD """ @@ -812,7 +811,6 @@ class HyperbolicCenterArc(BaseEdgeObject): rotation (float, optional): angle to rotate arc. Defaults to 0.0 angular_direction (AngularDirection, optional): arc direction. Defaults to AngularDirection.COUNTER_CLOCKWISE - plane (Plane, optional): base plane. Defaults to Plane.XY mode (Mode, optional): combination mode. Defaults to Mode.ADD """