From a4e285fccebbc14547b1204e1dca8aeea9204279 Mon Sep 17 00:00:00 2001 From: Jacob Kerstetter Date: Wed, 2 Jul 2025 13:54:38 -0400 Subject: [PATCH 01/13] nurbs sketch wip --- .../geometry/core/shapes/curves/nurbs.py | 5 +- src/ansys/geometry/core/sketch/nurbs.py | 132 ++++++++++++++++++ src/ansys/geometry/core/sketch/sketch.py | 22 +++ 3 files changed, 155 insertions(+), 4 deletions(-) create mode 100644 src/ansys/geometry/core/sketch/nurbs.py diff --git a/src/ansys/geometry/core/shapes/curves/nurbs.py b/src/ansys/geometry/core/shapes/curves/nurbs.py index ab7baafc83..f1aa11a205 100644 --- a/src/ansys/geometry/core/shapes/curves/nurbs.py +++ b/src/ansys/geometry/core/shapes/curves/nurbs.py @@ -167,10 +167,7 @@ def fit_curve_from_points( from geomdl import fitting # Convert points to a format suitable for the fitting function - converted_points = [] - for pt in points: - pt_raw = [*pt] - converted_points.append(pt_raw) + converted_points = [[*pt] for pt in points] # Fit the curve to the points curve = fitting.interpolate_curve(converted_points, degree) diff --git a/src/ansys/geometry/core/sketch/nurbs.py b/src/ansys/geometry/core/sketch/nurbs.py new file mode 100644 index 0000000000..074bfaf2da --- /dev/null +++ b/src/ansys/geometry/core/sketch/nurbs.py @@ -0,0 +1,132 @@ +# Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +"""Provides for creating and managing a nurbs sketch curve.""" + +from typing import TYPE_CHECKING + +from beartype import beartype as check_input_types + +from ansys.geometry.core.math.point import Point2D +from ansys.geometry.core.typing import Real + +if TYPE_CHECKING: # pragma: no cover + import geomdl.NURBS as geomdl_nurbs # noqa: N811 + + +class SketchNurbs: + """Represents a NURBS sketch curve. + + Notes + ----- + This class is a wrapper around the NURBS curve class from the `geomdl` library. + By leveraging the `geomdl` library, this class provides a high-level interface + to create and manipulate NURBS curves. The `geomdl` library is a powerful + library for working with NURBS curves and surfaces. For more information, see + https://pypi.org/project/geomdl/. + + """ + + def __init__(self): + """Initialize the NURBS sketch curve.""" + try: + import geomdl.NURBS as geomdl_nurbs # noqa: N811 + except ImportError as e: # pragma: no cover + raise ImportError( + "The `geomdl` library is required to use the NURBSCurve class. " + "Please install it using `pip install geomdl`." + ) from e + + self._nurbs_curve = geomdl_nurbs.Curve() + + @property + def geomdl_nurbs_curve(self) -> "geomdl_nurbs.Curve": + """Get the underlying NURBS curve. + + Notes + ----- + This property gives access to the full functionality of the NURBS curve + coming from the `geomdl` library. Use with caution. + """ + return self._nurbs_curve + + @property + def control_points(self) -> list[Point2D]: + """Get the control points of the curve.""" + return [Point2D(point) for point in self._nurbs_curve.ctrlpts] + + @property + def degree(self) -> int: + """Get the degree of the curve.""" + return self._nurbs_curve.degree + + @property + def knots(self) -> list[Real]: + """Get the knot vector of the curve.""" + return self._nurbs_curve.knotvector + + @property + def weights(self) -> list[Real]: + """Get the weights of the control points.""" + return self._nurbs_curve.weights + + @classmethod + @check_input_types + def fit_curve_from_points( + cls, + points: list[Point2D], + degree: int = 3, + ) -> "SketchNurbs": + """Fit a NURBS curve to a set of points. + + Parameters + ---------- + points : list[Point2D] + The points to fit the curve to. + degree : int, optional + The degree of the NURBS curve, by default 3. + + Returns + ------- + SketchNurbs + A new instance of SketchNurbs fitted to the given points. + """ + from geomdl import fitting + + curve = fitting.interpolate_curve( + [[*pt] for pt in points], # Convert Point2D to list of coordinates + degree=degree, + ) + + # Construct the NURBSCurve object + nurbs_curve = cls() + nurbs_curve._nurbs_curve.degree = curve.degree + nurbs_curve._nurbs_curve.ctrlpts = [Point2D(entry) for entry in curve.ctrlpts] + nurbs_curve._nurbs_curve.knotvector = curve.knotvector + nurbs_curve._nurbs_curve.weights = curve.weights + + # Verify the curve is valid + try: + nurbs_curve._nurbs_curve._check_variables() + except ValueError as e: + raise ValueError(f"Invalid NURBS curve: {e}") + + return nurbs_curve \ No newline at end of file diff --git a/src/ansys/geometry/core/sketch/sketch.py b/src/ansys/geometry/core/sketch/sketch.py index 36f3e0f1c3..6292b286c9 100644 --- a/src/ansys/geometry/core/sketch/sketch.py +++ b/src/ansys/geometry/core/sketch/sketch.py @@ -561,6 +561,28 @@ def arc_from_start_center_and_angle( ) return self.edge(arc, tag) + def nurbs_from_2d_points( + self, + points: list[Point2D], + tag: str | None = None, + ) -> "Sketch": + """Add a NURBS curve from a list of 2D points. + + Parameters + ---------- + points : list[Point2D] + List of 2D points to define the NURBS curve. + tag : str | None, default: None + User-defined label for identifying the curve. + + Returns + ------- + Sketch + Revised sketch state ready for further sketch actions. + """ + nurbs_curve = NURBSCurve.from_2d_points(points) + return self.edge(nurbs_curve, tag) + def triangle( self, point1: Point2D, From 79cd02f289a96083a1963173d5a3090d6a37cd74 Mon Sep 17 00:00:00 2001 From: Jacob Kerstetter Date: Thu, 3 Jul 2025 11:24:07 -0400 Subject: [PATCH 02/13] sketch nurbs working with visualization --- src/ansys/geometry/core/sketch/nurbs.py | 47 +++++++++++++++++++++++- src/ansys/geometry/core/sketch/sketch.py | 3 +- tests/integration/test_plotter.py | 17 +++++++++ tests/test_sketch.py | 26 +++++++++++++ 4 files changed, 91 insertions(+), 2 deletions(-) diff --git a/src/ansys/geometry/core/sketch/nurbs.py b/src/ansys/geometry/core/sketch/nurbs.py index 074bfaf2da..d7b51d77aa 100644 --- a/src/ansys/geometry/core/sketch/nurbs.py +++ b/src/ansys/geometry/core/sketch/nurbs.py @@ -23,16 +23,19 @@ from typing import TYPE_CHECKING +from ansys.geometry.core.misc.checks import graphics_required from beartype import beartype as check_input_types from ansys.geometry.core.math.point import Point2D +from ansys.geometry.core.sketch.edge import SketchEdge from ansys.geometry.core.typing import Real if TYPE_CHECKING: # pragma: no cover import geomdl.NURBS as geomdl_nurbs # noqa: N811 + import pyvista as pv -class SketchNurbs: +class SketchNurbs(SketchEdge): """Represents a NURBS sketch curve. Notes @@ -88,6 +91,48 @@ def weights(self) -> list[Real]: """Get the weights of the control points.""" return self._nurbs_curve.weights + @property + def start(self) -> Point2D: + """Get the start point of the curve.""" + return Point2D(self._nurbs_curve.evaluate_single(0.0)) + + @property + def end(self) -> Point2D: + """Get the end point of the curve.""" + return Point2D(self._nurbs_curve.evaluate_single(1.0)) + + @property + @graphics_required + def visualization_polydata(self) -> "pv.PolyData": + """Get the VTK polydata representation for PyVista visualization. + + Returns + ------- + pyvista.PolyData + VTK pyvista.Polydata configuration. + + Notes + ----- + The representation lies in the X/Y plane within + the standard global Cartesian coordinate system. + """ + #from geomdl.exchange_vtk import export_polydata + import numpy as np + import pyvista as pv + + # Sample points along the curve + params = np.linspace(0, 1, 100) + points = [self._nurbs_curve.evaluate_single(u) for u in params] # For 2D: [x, y] + + # If 2D, add a zero z-coordinate for PyVista + points = [(*pt, 0.0) for pt in points] + + # Create PolyData and add the line + polydata = pv.PolyData(points) + polydata.lines = [len(points)] + list(range(len(points))) + + return polydata + @classmethod @check_input_types def fit_curve_from_points( diff --git a/src/ansys/geometry/core/sketch/sketch.py b/src/ansys/geometry/core/sketch/sketch.py index 6292b286c9..9ff19e50cb 100644 --- a/src/ansys/geometry/core/sketch/sketch.py +++ b/src/ansys/geometry/core/sketch/sketch.py @@ -39,6 +39,7 @@ from ansys.geometry.core.sketch.ellipse import SketchEllipse from ansys.geometry.core.sketch.face import SketchFace from ansys.geometry.core.sketch.gears import DummyGear, SpurGear +from ansys.geometry.core.sketch.nurbs import SketchNurbs from ansys.geometry.core.sketch.polygon import Polygon from ansys.geometry.core.sketch.segment import SketchSegment from ansys.geometry.core.sketch.slot import Slot @@ -580,7 +581,7 @@ def nurbs_from_2d_points( Sketch Revised sketch state ready for further sketch actions. """ - nurbs_curve = NURBSCurve.from_2d_points(points) + nurbs_curve = SketchNurbs.fit_curve_from_points(points) return self.edge(nurbs_curve, tag) def triangle( diff --git a/tests/integration/test_plotter.py b/tests/integration/test_plotter.py index 688d2e4841..dc48283756 100644 --- a/tests/integration/test_plotter.py +++ b/tests/integration/test_plotter.py @@ -124,6 +124,23 @@ def test_plot_sketch(verify_image_cache): sketch.plot(view_2d=True, screenshot=Path(IMAGE_RESULTS_DIR, "plot_sketch.png")) +@skip_no_xserver +def test_plot_nurbs_sketch(verify_image_cache): + # Create a NURBS sketch instance + sketch = Sketch() + sketch.nurbs_from_2d_points( + [ + Point2D([0, 0]), + Point2D([2, 2]), + Point2D([3, 6]), + Point2D([4, 7]), + ] + ) + + # Plot the NURBS sketch + sketch.plot(view_2d=True, screenshot=Path(IMAGE_RESULTS_DIR, "plot_nurbs_sketch.png")) + + @skip_no_xserver def test_plot_geometryplotter_sketch_pyvista(verify_image_cache): # define sketch diff --git a/tests/test_sketch.py b/tests/test_sketch.py index ae3c33c9af..275eb887b6 100644 --- a/tests/test_sketch.py +++ b/tests/test_sketch.py @@ -329,6 +329,32 @@ def test_sketch_arc_edge(): assert arc3_retrieved[0] == sketch.edges[2] +def test_sketch_nurbs(): + """Test NURBS SketchEdge sketching.""" + # Create a Sketch instance + sketch = Sketch() + + # Create a NURBS curve through 4 points + points = [ + Point2D([0, 0]), + Point2D([2, 2]), + Point2D([3, 6]), + Point2D([4, 7]), + ] + sketch.nurbs_from_2d_points(points, tag="Nurbs1") + assert len(sketch.edges) == 1 + + curve = sketch.edges[0] + assert curve.start == Point2D([0, 0]) + assert curve.end == Point2D([4, 7]) + assert curve.degree == 3 + + # Check retrieving the NURBS curve by tag + nurbs1_retrieved = sketch.get("Nurbs1") + assert len(nurbs1_retrieved) == 1 + assert nurbs1_retrieved[0] == sketch.edges[0] + + def test_sketch_triangle_face(): """Test Triangle SketchFace sketching.""" # Create a Sketch instance From f11312fb735e512d4e1cc29a0d875119a0fb6f73 Mon Sep 17 00:00:00 2001 From: Jacob Kerstetter Date: Thu, 3 Jul 2025 11:26:41 -0400 Subject: [PATCH 03/13] doc edits --- src/ansys/geometry/core/sketch/nurbs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/geometry/core/sketch/nurbs.py b/src/ansys/geometry/core/sketch/nurbs.py index d7b51d77aa..4f2a747856 100644 --- a/src/ansys/geometry/core/sketch/nurbs.py +++ b/src/ansys/geometry/core/sketch/nurbs.py @@ -124,7 +124,7 @@ def visualization_polydata(self) -> "pv.PolyData": params = np.linspace(0, 1, 100) points = [self._nurbs_curve.evaluate_single(u) for u in params] # For 2D: [x, y] - # If 2D, add a zero z-coordinate for PyVista + # Add a zero z-coordinate for PyVista (only supports 3D points) points = [(*pt, 0.0) for pt in points] # Create PolyData and add the line From 9636f2280ba513fff9dea526cfde41623753f76b Mon Sep 17 00:00:00 2001 From: Jacob Kerstetter Date: Thu, 3 Jul 2025 15:10:48 -0400 Subject: [PATCH 04/13] added degree check for fitting from < 4 points added contains_point for 2d nurbs --- src/ansys/geometry/core/sketch/nurbs.py | 38 ++++++++++++++++++++++++- tests/test_sketch.py | 9 ++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/ansys/geometry/core/sketch/nurbs.py b/src/ansys/geometry/core/sketch/nurbs.py index 4f2a747856..0787c56921 100644 --- a/src/ansys/geometry/core/sketch/nurbs.py +++ b/src/ansys/geometry/core/sketch/nurbs.py @@ -23,10 +23,11 @@ from typing import TYPE_CHECKING -from ansys.geometry.core.misc.checks import graphics_required from beartype import beartype as check_input_types +import numpy as np from ansys.geometry.core.math.point import Point2D +from ansys.geometry.core.misc.checks import graphics_required from ansys.geometry.core.sketch.edge import SketchEdge from ansys.geometry.core.typing import Real @@ -50,6 +51,7 @@ class SketchNurbs(SketchEdge): def __init__(self): """Initialize the NURBS sketch curve.""" + super().__init__() try: import geomdl.NURBS as geomdl_nurbs # noqa: N811 except ImportError as e: # pragma: no cover @@ -133,6 +135,28 @@ def visualization_polydata(self) -> "pv.PolyData": return polydata + def contains_point(self, point: Point2D, tolerance: Real = 1e-6) -> bool: + """Check if the curve contains a given point within a specified tolerance. + + Parameters + ---------- + point : Point2D + The point to check. + tolerance : Real, optional + The tolerance for the containment check, by default 1e-6. + + Returns + ------- + bool + True if the curve contains the point within the tolerance, False otherwise. + """ + # Sample points along the curve + params = np.linspace(0, 1, 200) + sampled = [self._nurbs_curve.evaluate_single(u) for u in params] + + # Check if any sampled point is close to the target point + return any(np.linalg.norm(np.array(pt) - np.array(point)) < tolerance for pt in sampled) + @classmethod @check_input_types def fit_curve_from_points( @@ -156,6 +180,18 @@ def fit_curve_from_points( """ from geomdl import fitting + # Check degree compared to number of points provided + if degree < 1: + raise ValueError("Degree must be at least 1.") + if len(points) == 2: + degree = 1 # Force linear interpolation for two points + if len(points) == 3: + degree = 2 # Force quadratic interpolation for three points + if degree >= len(points): + raise ValueError( + f"Degree {degree} is too high for the number of points provided: {len(points)}." + ) + curve = fitting.interpolate_curve( [[*pt] for pt in points], # Convert Point2D to list of coordinates degree=degree, diff --git a/tests/test_sketch.py b/tests/test_sketch.py index 275eb887b6..b37fddff82 100644 --- a/tests/test_sketch.py +++ b/tests/test_sketch.py @@ -344,6 +344,15 @@ def test_sketch_nurbs(): sketch.nurbs_from_2d_points(points, tag="Nurbs1") assert len(sketch.edges) == 1 + # Create another curve through 3 points + sketch.nurbs_from_2d_points( + [ + Point2D([0, 0]), + Point2D([1, 1]), + Point2D([2, 2]), + ], tag="Nurbs2" + ) + curve = sketch.edges[0] assert curve.start == Point2D([0, 0]) assert curve.end == Point2D([4, 7]) From 38b92b0334f6d5c643a6f7bfb73c040bbd9ef617 Mon Sep 17 00:00:00 2001 From: Jacob Kerstetter Date: Tue, 8 Jul 2025 10:00:46 -0400 Subject: [PATCH 05/13] adding contains_point for nurbs edge --- src/ansys/geometry/core/sketch/edge.py | 4 ++++ tests/test_sketch.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/ansys/geometry/core/sketch/edge.py b/src/ansys/geometry/core/sketch/edge.py index 7da77235a8..b40d4b0a26 100644 --- a/src/ansys/geometry/core/sketch/edge.py +++ b/src/ansys/geometry/core/sketch/edge.py @@ -50,6 +50,10 @@ def end(self) -> Point2D: def length(self) -> Quantity: """Length of the edge.""" raise NotImplementedError("Each edge must provide the length definition.") + + def contains_point(self, point: Point2D, tol: float = 1e-6) -> bool: + """Check if the edge contains the given point within a tolerance.""" + raise NotImplementedError("Each edge must provide the contains_point method.") @property def visualization_polydata(self) -> "pv.PolyData": diff --git a/tests/test_sketch.py b/tests/test_sketch.py index b37fddff82..4cd82ce316 100644 --- a/tests/test_sketch.py +++ b/tests/test_sketch.py @@ -363,6 +363,10 @@ def test_sketch_nurbs(): assert len(nurbs1_retrieved) == 1 assert nurbs1_retrieved[0] == sketch.edges[0] + # Check if the curve contains a point + assert sketch.edges[0].contains_point(Point2D([4, 7])) + assert not sketch.edges[0].contains_point(Point2D([5, 5])) + def test_sketch_triangle_face(): """Test Triangle SketchFace sketching.""" From a1ffa395fb73f62444c34ef0c8237bb1b81f1574 Mon Sep 17 00:00:00 2001 From: Jacob Kerstetter Date: Wed, 9 Jul 2025 09:38:37 -0400 Subject: [PATCH 06/13] added test to create a surface from a combination of nurbs and segment sketch edges --- .../core/_grpc/_services/v0/conversions.py | 58 +++++++++++++++++-- src/ansys/geometry/core/sketch/edge.py | 2 +- tests/integration/test_design.py | 29 ++++++++++ 3 files changed, 84 insertions(+), 5 deletions(-) diff --git a/src/ansys/geometry/core/_grpc/_services/v0/conversions.py b/src/ansys/geometry/core/_grpc/_services/v0/conversions.py index cecd103382..07c9d2a7aa 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/conversions.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/conversions.py @@ -84,6 +84,7 @@ from ansys.geometry.core.sketch.edge import SketchEdge from ansys.geometry.core.sketch.ellipse import SketchEllipse from ansys.geometry.core.sketch.face import SketchFace + from ansys.geometry.core.sketch.nurbs import SketchNurbs from ansys.geometry.core.sketch.polygon import Polygon from ansys.geometry.core.sketch.segment import SketchSegment @@ -404,6 +405,7 @@ def from_sketch_shapes_to_grpc_geometries( converted_sketch_edges = from_sketch_edges_to_grpc_geometries(edges, plane) geometries.lines.extend(converted_sketch_edges[0]) geometries.arcs.extend(converted_sketch_edges[1]) + geometries.nurbs_curves.extend(converted_sketch_edges[2]) for face in faces: if isinstance(face, SketchCircle): @@ -429,6 +431,8 @@ def from_sketch_shapes_to_grpc_geometries( one_curve_geometry.ellipses.append(geometries.ellipses[0]) elif len(geometries.polygons) > 0: one_curve_geometry.polygons.append(geometries.polygons[0]) + elif len(geometries.nurbs_curves) > 0: + one_curve_geometry.nurbs_curves.append(geometries.nurbs_curves[0]) return one_curve_geometry else: @@ -438,7 +442,7 @@ def from_sketch_shapes_to_grpc_geometries( def from_sketch_edges_to_grpc_geometries( edges: list["SketchEdge"], plane: "Plane", -) -> tuple[list[GRPCLine], list[GRPCArc]]: +) -> tuple[list[GRPCLine], list[GRPCArc], list[GRPCNurbsCurve]]: """Convert a list of ``SketchEdge`` to a gRPC message. Parameters @@ -450,21 +454,25 @@ def from_sketch_edges_to_grpc_geometries( Returns ------- - tuple[list[GRPCLine], list[GRPCArc]] - Geometry service gRPC line and arc messages. The unit is meters. + tuple[list[GRPCLine], list[GRPCArc], list[GRPCNurbsCurve]] + Geometry service gRPC line, arc, and NURBS curve messages. The unit is meters. """ from ansys.geometry.core.sketch.arc import Arc + from ansys.geometry.core.sketch.nurbs import SketchNurbs from ansys.geometry.core.sketch.segment import SketchSegment arcs = [] segments = [] + nurbs_curves = [] for edge in edges: if isinstance(edge, SketchSegment): segments.append(from_sketch_segment_to_grpc_line(edge, plane)) elif isinstance(edge, Arc): arcs.append(from_sketch_arc_to_grpc_arc(edge, plane)) + elif isinstance(edge, SketchNurbs): + nurbs_curves.append(from_sketch_nurbs_to_grpc_nurbs_curve(edge, plane)) - return (segments, arcs) + return (segments, arcs, nurbs_curves) def from_sketch_arc_to_grpc_arc(arc: "Arc", plane: "Plane") -> GRPCArc: @@ -496,6 +504,48 @@ def from_sketch_arc_to_grpc_arc(arc: "Arc", plane: "Plane") -> GRPCArc: ) +def from_sketch_nurbs_to_grpc_nurbs_curve(curve: "SketchNurbs", plane: "Plane") -> GRPCNurbsCurve: + """Convert a ``SketchNurbs`` class to a NURBS curve gRPC message. + + Parameters + ---------- + nurbs : SketchNurbs + Source NURBS data. + plane : Plane + Plane for positioning the NURBS curve. + + Returns + ------- + GRPCNurbsCurve + Geometry service gRPC NURBS curve message. The unit is meters. + """ + from ansys.api.geometry.v0.models_pb2 import ( + ControlPoint as GRPCControlPoint, + NurbsData as GRPCNurbsData, + ) + + # Convert control points + control_points = [ + GRPCControlPoint( + position=from_point2d_to_grpc_point(plane, pt), + weight=curve.weights[i], + ) + for i, pt in enumerate(curve.control_points) + ] + + # Convert nurbs data + nurbs_data = GRPCNurbsData( + degree=curve.degree, + knots=from_knots_to_grpc_knots(curve.knots), + order=curve.degree + 1, + ) + + return GRPCNurbsCurve( + control_points=control_points, + nurbs_data=nurbs_data, + ) + + def from_sketch_ellipse_to_grpc_ellipse(ellipse: "SketchEllipse", plane: "Plane") -> GRPCEllipse: """Convert a ``SketchEllipse`` class to an ellipse gRPC message. diff --git a/src/ansys/geometry/core/sketch/edge.py b/src/ansys/geometry/core/sketch/edge.py index b40d4b0a26..7e27b4dee1 100644 --- a/src/ansys/geometry/core/sketch/edge.py +++ b/src/ansys/geometry/core/sketch/edge.py @@ -80,7 +80,7 @@ def plane_change(self, plane: "Plane") -> None: Notes ----- This implies that their 3D definition might suffer changes. By default, this - metho does nothing. It is required to be implemented in child ``SketchEdge`` + method does nothing. It is required to be implemented in child ``SketchEdge`` classes. """ pass diff --git a/tests/integration/test_design.py b/tests/integration/test_design.py index e1f8ddf52a..11824e229d 100644 --- a/tests/integration/test_design.py +++ b/tests/integration/test_design.py @@ -3082,6 +3082,35 @@ def test_surface_body_creation(modeler: Modeler): assert body.faces[0].area.m == pytest.approx(39.4784176044 * 2) +def test_create_surface_from_nurbs_sketch(modeler: Modeler): + """Test creating a surface from a NURBS sketch.""" + design = modeler.create_design("NURBS_Sketch_Surface") + + # Create a NURBS sketch + sketch = Sketch() + sketch.nurbs_from_2d_points( + points=[ + Point2D([0, 0]), + Point2D([1, 0]), + Point2D([1, 1]), + Point2D([0, 1]), + ], + tag="nurbs_sketch", + ) + sketch.segment( + start=Point2D([0, 0]), + end=Point2D([0, 1]), + tag="segment_1", + ) + + # Create a surface from the NURBS sketch + surface_body = design.create_surface("NURBS_Surface", sketch) + + assert len(design.bodies) == 1 + assert surface_body.is_surface + assert surface_body.faces[0].area.m > 0 + + def test_design_parameters(modeler: Modeler): """Test the design parameter's functionality.""" design = modeler.open_file(FILES_DIR / "blockswithparameters.dsco") From e59113676d56cd02596555089296b7e000206c78 Mon Sep 17 00:00:00 2001 From: Jacob Kerstetter Date: Thu, 10 Jul 2025 11:39:17 -0400 Subject: [PATCH 07/13] add fill command copy conversions from new services dir to old one add test for fill nurbs sketch --- .../geometry/core/connection/conversions.py | 105 +++++++++++++++++- .../core/designer/geometry_commands.py | 55 +++++++++ tests/integration/test_design.py | 11 +- 3 files changed, 162 insertions(+), 9 deletions(-) diff --git a/src/ansys/geometry/core/connection/conversions.py b/src/ansys/geometry/core/connection/conversions.py index 0166ac830b..d1f047ce93 100644 --- a/src/ansys/geometry/core/connection/conversions.py +++ b/src/ansys/geometry/core/connection/conversions.py @@ -33,10 +33,12 @@ Ellipse as GRPCEllipse, Frame as GRPCFrame, Geometries as GRPCGeometries, + Knot as GRPCKnot, Line as GRPCLine, Material as GRPCMaterial, MaterialProperty as GRPCMaterialProperty, Matrix as GRPCMatrix, + NurbsCurve as GRPCNurbsCurve, Plane as GRPCPlane, Point as GRPCPoint, Polygon as GRPCPolygon, @@ -69,6 +71,7 @@ from ansys.geometry.core.sketch.edge import SketchEdge from ansys.geometry.core.sketch.ellipse import SketchEllipse from ansys.geometry.core.sketch.face import SketchFace +from ansys.geometry.core.sketch.nurbs import SketchNurbs from ansys.geometry.core.sketch.polygon import Polygon from ansys.geometry.core.sketch.segment import SketchSegment @@ -160,11 +163,16 @@ def sketch_shapes_to_grpc_geometries( GRPCGeometries Geometry service gRPC geometries message. The unit is meters. """ + from ansys.geometry.core.sketch.circle import SketchCircle + from ansys.geometry.core.sketch.ellipse import SketchEllipse + from ansys.geometry.core.sketch.polygon import Polygon + geometries = GRPCGeometries() converted_sketch_edges = sketch_edges_to_grpc_geometries(edges, plane) geometries.lines.extend(converted_sketch_edges[0]) geometries.arcs.extend(converted_sketch_edges[1]) + geometries.nurbs_curves.extend(converted_sketch_edges[2]) for face in faces: if isinstance(face, SketchCircle): @@ -190,6 +198,8 @@ def sketch_shapes_to_grpc_geometries( one_curve_geometry.ellipses.append(geometries.ellipses[0]) elif len(geometries.polygons) > 0: one_curve_geometry.polygons.append(geometries.polygons[0]) + elif len(geometries.nurbs_curves) > 0: + one_curve_geometry.nurbs_curves.append(geometries.nurbs_curves[0]) return one_curve_geometry else: @@ -197,9 +207,9 @@ def sketch_shapes_to_grpc_geometries( def sketch_edges_to_grpc_geometries( - edges: list[SketchEdge], - plane: Plane, -) -> tuple[list[GRPCLine], list[GRPCArc]]: + edges: list["SketchEdge"], + plane: "Plane", +) -> tuple[list[GRPCLine], list[GRPCArc], list[GRPCNurbsCurve]]: """Convert a list of ``SketchEdge`` to a gRPC message. Parameters @@ -211,18 +221,25 @@ def sketch_edges_to_grpc_geometries( Returns ------- - tuple[list[GRPCLine], list[GRPCArc]] - Geometry service gRPC line and arc messages. The unit is meters. + tuple[list[GRPCLine], list[GRPCArc], list[GRPCNurbsCurve]] + Geometry service gRPC line, arc, and NURBS curve messages. The unit is meters. """ + from ansys.geometry.core.sketch.arc import Arc + from ansys.geometry.core.sketch.nurbs import SketchNurbs + from ansys.geometry.core.sketch.segment import SketchSegment + arcs = [] segments = [] + nurbs_curves = [] for edge in edges: if isinstance(edge, SketchSegment): segments.append(sketch_segment_to_grpc_line(edge, plane)) elif isinstance(edge, Arc): arcs.append(sketch_arc_to_grpc_arc(edge, plane)) + elif isinstance(edge, SketchNurbs): + nurbs_curves.append(sketch_nurbs_to_grpc_nurbs_curve(edge, plane)) - return (segments, arcs) + return (segments, arcs, nurbs_curves) def sketch_arc_to_grpc_arc(arc: Arc, plane: Plane) -> GRPCArc: @@ -254,6 +271,82 @@ def sketch_arc_to_grpc_arc(arc: Arc, plane: Plane) -> GRPCArc: ) +def sketch_nurbs_to_grpc_nurbs_curve(curve: "SketchNurbs", plane: "Plane") -> GRPCNurbsCurve: + """Convert a ``SketchNurbs`` class to a NURBS curve gRPC message. + + Parameters + ---------- + nurbs : SketchNurbs + Source NURBS data. + plane : Plane + Plane for positioning the NURBS curve. + + Returns + ------- + GRPCNurbsCurve + Geometry service gRPC NURBS curve message. The unit is meters. + """ + from ansys.api.geometry.v0.models_pb2 import ( + ControlPoint as GRPCControlPoint, + NurbsData as GRPCNurbsData, + ) + + # Convert control points + control_points = [ + GRPCControlPoint( + position=point2d_to_grpc_point(plane, pt), + weight=curve.weights[i], + ) + for i, pt in enumerate(curve.control_points) + ] + + # Convert nurbs data + nurbs_data = GRPCNurbsData( + degree=curve.degree, + knots=knots_to_grpc_knots(curve.knots), + order=curve.degree + 1, + ) + + return GRPCNurbsCurve( + control_points=control_points, + nurbs_data=nurbs_data, + ) + + +def knots_to_grpc_knots(knots: list[float]) -> list[GRPCKnot]: + """Convert a list of knots to a list of gRPC knot messages. + + Parameters + ---------- + knots : list[float] + Source knots data. + + Returns + ------- + list[GRPCKnot] + Geometry service gRPC knot messages. + """ + from collections import Counter + + # Count multiplicities + multiplicities = Counter(knots) + + # Get unique knots (parameters) in order + unique_knots = sorted(set(knots)) + knot_multiplicities = [(knot, multiplicities[knot]) for knot in unique_knots] + + # Convert to gRPC knot messages + grpc_knots = [ + GRPCKnot( + parameter=knot, + multiplicity=multiplicity, + ) + for knot, multiplicity in knot_multiplicities + ] + + return grpc_knots + + def sketch_ellipse_to_grpc_ellipse(ellipse: SketchEllipse, plane: Plane) -> GRPCEllipse: """Convert a ``SketchEllipse`` class to an ellipse gRPC message. diff --git a/src/ansys/geometry/core/designer/geometry_commands.py b/src/ansys/geometry/core/designer/geometry_commands.py index cce3e8bd62..178c7bcb43 100644 --- a/src/ansys/geometry/core/designer/geometry_commands.py +++ b/src/ansys/geometry/core/designer/geometry_commands.py @@ -39,6 +39,7 @@ ExtrudeFacesRequest, ExtrudeFacesUpToRequest, FilletRequest, + FillRequest, FullFilletRequest, ModifyCircularPatternRequest, ModifyLinearPatternRequest, @@ -62,6 +63,7 @@ point3d_to_grpc_point, unit_vector_to_grpc_direction, ) +from ansys.geometry.core.designer.body import Body, MasterBody from ansys.geometry.core.designer.component import Component from ansys.geometry.core.designer.mating_conditions import ( AlignCondition, @@ -93,8 +95,10 @@ if TYPE_CHECKING: # pragma: no cover from ansys.geometry.core.designer.body import Body from ansys.geometry.core.designer.component import Component + from ansys.geometry.core.designer.design import Design from ansys.geometry.core.designer.edge import Edge from ansys.geometry.core.designer.face import Face + from ansys.geometry.core.sketch import Sketch @unique @@ -1635,3 +1639,54 @@ def create_orient_condition( result.is_reversed, result.is_valid, ) + + @protect_grpc + @min_backend_version(26, 1, 0) + def fill( + self, + sketch: "Sketch", + design: "Design", + ) -> Body: + """Fill the given sketch on the specified plane within the design. + + Parameters + ---------- + sketch : Sketch + The sketch to fill. + design : Design + The design in which the sketch resides. + + Returns + ------- + Body + The surface body created by filling the sketch. + + Warnings + -------- + This method is only available starting on Ansys release 26R1. + """ + from ansys.geometry.core.connection.conversions import ( + plane_to_grpc_plane, + sketch_shapes_to_grpc_geometries, + ) + + # Create the gRPC request + request = FillRequest( + geometries=sketch_shapes_to_grpc_geometries(sketch.plane, sketch.edges, sketch.faces), + plane=plane_to_grpc_plane(sketch.plane), + parent=design._grpc_id + ) + + # Call the gRPC service + response = self._commands_stub.Fill(request) + + # If successful, create and return the Body object + if response.success: + tb = MasterBody( + response.created_body.id, + response.created_body.name, + self._grpc_client, + is_surface=response.created_body.is_surface, + ) + + return Body(response.created_body.id, response.created_body.name, design, tb) \ No newline at end of file diff --git a/tests/integration/test_design.py b/tests/integration/test_design.py index 11824e229d..8995669927 100644 --- a/tests/integration/test_design.py +++ b/tests/integration/test_design.py @@ -3098,18 +3098,23 @@ def test_create_surface_from_nurbs_sketch(modeler: Modeler): tag="nurbs_sketch", ) sketch.segment( - start=Point2D([0, 0]), - end=Point2D([0, 1]), + start=Point2D([0, -1]), + end=Point2D([0, 2]), tag="segment_1", ) # Create a surface from the NURBS sketch - surface_body = design.create_surface("NURBS_Surface", sketch) + surface_body = modeler.geometry_commands.fill( + sketch=sketch, + design=design, + ) assert len(design.bodies) == 1 assert surface_body.is_surface assert surface_body.faces[0].area.m > 0 + design.plot(screenshot="C:\\Users\\jkerstet\\Downloads\\nurbs_surface.png") + def test_design_parameters(modeler: Modeler): """Test the design parameter's functionality.""" From 2ee3921f4c1501994aa060285284ba66933240f6 Mon Sep 17 00:00:00 2001 From: Jacob Kerstetter Date: Fri, 11 Jul 2025 13:01:51 -0400 Subject: [PATCH 08/13] added test for creating surface from nurbs sketch + overhanging edges --- tests/integration/test_design.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/test_design.py b/tests/integration/test_design.py index 3ca97ed302..0dd11a3f91 100644 --- a/tests/integration/test_design.py +++ b/tests/integration/test_design.py @@ -3104,9 +3104,9 @@ def test_create_surface_from_nurbs_sketch(modeler: Modeler): ) # Create a surface from the NURBS sketch - surface_body = modeler.geometry_commands.fill( + surface_body = design.create_surface( + name="nurbs_surface", sketch=sketch, - design=design, ) assert len(design.bodies) == 1 From 6d25316b0318cde0ae2c74296d83c870379459fa Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 11 Jul 2025 17:04:21 +0000 Subject: [PATCH 09/13] chore: auto fixes from pre-commit hooks --- .../core/designer/geometry_commands.py | 6 ++--- src/ansys/geometry/core/sketch/edge.py | 2 +- src/ansys/geometry/core/sketch/nurbs.py | 24 +++++++++---------- tests/test_sketch.py | 5 ++-- 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/ansys/geometry/core/designer/geometry_commands.py b/src/ansys/geometry/core/designer/geometry_commands.py index 178c7bcb43..38a5d80c2b 100644 --- a/src/ansys/geometry/core/designer/geometry_commands.py +++ b/src/ansys/geometry/core/designer/geometry_commands.py @@ -1648,7 +1648,7 @@ def fill( design: "Design", ) -> Body: """Fill the given sketch on the specified plane within the design. - + Parameters ---------- sketch : Sketch @@ -1674,7 +1674,7 @@ def fill( request = FillRequest( geometries=sketch_shapes_to_grpc_geometries(sketch.plane, sketch.edges, sketch.faces), plane=plane_to_grpc_plane(sketch.plane), - parent=design._grpc_id + parent=design._grpc_id, ) # Call the gRPC service @@ -1689,4 +1689,4 @@ def fill( is_surface=response.created_body.is_surface, ) - return Body(response.created_body.id, response.created_body.name, design, tb) \ No newline at end of file + return Body(response.created_body.id, response.created_body.name, design, tb) diff --git a/src/ansys/geometry/core/sketch/edge.py b/src/ansys/geometry/core/sketch/edge.py index 7e27b4dee1..39baa3a310 100644 --- a/src/ansys/geometry/core/sketch/edge.py +++ b/src/ansys/geometry/core/sketch/edge.py @@ -50,7 +50,7 @@ def end(self) -> Point2D: def length(self) -> Quantity: """Length of the edge.""" raise NotImplementedError("Each edge must provide the length definition.") - + def contains_point(self, point: Point2D, tol: float = 1e-6) -> bool: """Check if the edge contains the given point within a tolerance.""" raise NotImplementedError("Each edge must provide the contains_point method.") diff --git a/src/ansys/geometry/core/sketch/nurbs.py b/src/ansys/geometry/core/sketch/nurbs.py index 0787c56921..2a97178975 100644 --- a/src/ansys/geometry/core/sketch/nurbs.py +++ b/src/ansys/geometry/core/sketch/nurbs.py @@ -38,7 +38,7 @@ class SketchNurbs(SketchEdge): """Represents a NURBS sketch curve. - + Notes ----- This class is a wrapper around the NURBS curve class from the `geomdl` library. @@ -48,7 +48,7 @@ class SketchNurbs(SketchEdge): https://pypi.org/project/geomdl/. """ - + def __init__(self): """Initialize the NURBS sketch curve.""" super().__init__() @@ -72,12 +72,12 @@ def geomdl_nurbs_curve(self) -> "geomdl_nurbs.Curve": coming from the `geomdl` library. Use with caution. """ return self._nurbs_curve - + @property def control_points(self) -> list[Point2D]: """Get the control points of the curve.""" return [Point2D(point) for point in self._nurbs_curve.ctrlpts] - + @property def degree(self) -> int: """Get the degree of the curve.""" @@ -92,7 +92,7 @@ def knots(self) -> list[Real]: def weights(self) -> list[Real]: """Get the weights of the control points.""" return self._nurbs_curve.weights - + @property def start(self) -> Point2D: """Get the start point of the curve.""" @@ -118,7 +118,7 @@ def visualization_polydata(self) -> "pv.PolyData": The representation lies in the X/Y plane within the standard global Cartesian coordinate system. """ - #from geomdl.exchange_vtk import export_polydata + # from geomdl.exchange_vtk import export_polydata import numpy as np import pyvista as pv @@ -132,7 +132,7 @@ def visualization_polydata(self) -> "pv.PolyData": # Create PolyData and add the line polydata = pv.PolyData(points) polydata.lines = [len(points)] + list(range(len(points))) - + return polydata def contains_point(self, point: Point2D, tolerance: Real = 1e-6) -> bool: @@ -153,7 +153,7 @@ def contains_point(self, point: Point2D, tolerance: Real = 1e-6) -> bool: # Sample points along the curve params = np.linspace(0, 1, 200) sampled = [self._nurbs_curve.evaluate_single(u) for u in params] - + # Check if any sampled point is close to the target point return any(np.linalg.norm(np.array(pt) - np.array(point)) < tolerance for pt in sampled) @@ -195,19 +195,19 @@ def fit_curve_from_points( curve = fitting.interpolate_curve( [[*pt] for pt in points], # Convert Point2D to list of coordinates degree=degree, - ) - + ) + # Construct the NURBSCurve object nurbs_curve = cls() nurbs_curve._nurbs_curve.degree = curve.degree nurbs_curve._nurbs_curve.ctrlpts = [Point2D(entry) for entry in curve.ctrlpts] nurbs_curve._nurbs_curve.knotvector = curve.knotvector nurbs_curve._nurbs_curve.weights = curve.weights - + # Verify the curve is valid try: nurbs_curve._nurbs_curve._check_variables() except ValueError as e: raise ValueError(f"Invalid NURBS curve: {e}") - return nurbs_curve \ No newline at end of file + return nurbs_curve diff --git a/tests/test_sketch.py b/tests/test_sketch.py index 4cd82ce316..e661323c60 100644 --- a/tests/test_sketch.py +++ b/tests/test_sketch.py @@ -350,14 +350,15 @@ def test_sketch_nurbs(): Point2D([0, 0]), Point2D([1, 1]), Point2D([2, 2]), - ], tag="Nurbs2" + ], + tag="Nurbs2", ) curve = sketch.edges[0] assert curve.start == Point2D([0, 0]) assert curve.end == Point2D([4, 7]) assert curve.degree == 3 - + # Check retrieving the NURBS curve by tag nurbs1_retrieved = sketch.get("Nurbs1") assert len(nurbs1_retrieved) == 1 From 2bca7ab65817280792548f2a66d2ebb5098285d6 Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Fri, 11 Jul 2025 17:05:39 +0000 Subject: [PATCH 10/13] chore: adding changelog file 2104.added.md [dependabot-skip] --- doc/changelog.d/2104.added.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/2104.added.md diff --git a/doc/changelog.d/2104.added.md b/doc/changelog.d/2104.added.md new file mode 100644 index 0000000000..7817f90c31 --- /dev/null +++ b/doc/changelog.d/2104.added.md @@ -0,0 +1 @@ +Nurbs sketching and surface support \ No newline at end of file From a7dc888c8f1dfd57723741c1ad758a43081d26a2 Mon Sep 17 00:00:00 2001 From: Jacob Kerstetter Date: Fri, 11 Jul 2025 13:12:36 -0400 Subject: [PATCH 11/13] code cleanup --- .../geometry/core/connection/conversions.py | 93 +------------------ .../core/designer/geometry_commands.py | 53 +---------- 2 files changed, 5 insertions(+), 141 deletions(-) diff --git a/src/ansys/geometry/core/connection/conversions.py b/src/ansys/geometry/core/connection/conversions.py index d1f047ce93..ad5b414f9d 100644 --- a/src/ansys/geometry/core/connection/conversions.py +++ b/src/ansys/geometry/core/connection/conversions.py @@ -33,12 +33,10 @@ Ellipse as GRPCEllipse, Frame as GRPCFrame, Geometries as GRPCGeometries, - Knot as GRPCKnot, Line as GRPCLine, Material as GRPCMaterial, MaterialProperty as GRPCMaterialProperty, Matrix as GRPCMatrix, - NurbsCurve as GRPCNurbsCurve, Plane as GRPCPlane, Point as GRPCPoint, Polygon as GRPCPolygon, @@ -71,7 +69,6 @@ from ansys.geometry.core.sketch.edge import SketchEdge from ansys.geometry.core.sketch.ellipse import SketchEllipse from ansys.geometry.core.sketch.face import SketchFace -from ansys.geometry.core.sketch.nurbs import SketchNurbs from ansys.geometry.core.sketch.polygon import Polygon from ansys.geometry.core.sketch.segment import SketchSegment @@ -172,7 +169,6 @@ def sketch_shapes_to_grpc_geometries( converted_sketch_edges = sketch_edges_to_grpc_geometries(edges, plane) geometries.lines.extend(converted_sketch_edges[0]) geometries.arcs.extend(converted_sketch_edges[1]) - geometries.nurbs_curves.extend(converted_sketch_edges[2]) for face in faces: if isinstance(face, SketchCircle): @@ -198,8 +194,6 @@ def sketch_shapes_to_grpc_geometries( one_curve_geometry.ellipses.append(geometries.ellipses[0]) elif len(geometries.polygons) > 0: one_curve_geometry.polygons.append(geometries.polygons[0]) - elif len(geometries.nurbs_curves) > 0: - one_curve_geometry.nurbs_curves.append(geometries.nurbs_curves[0]) return one_curve_geometry else: @@ -209,7 +203,7 @@ def sketch_shapes_to_grpc_geometries( def sketch_edges_to_grpc_geometries( edges: list["SketchEdge"], plane: "Plane", -) -> tuple[list[GRPCLine], list[GRPCArc], list[GRPCNurbsCurve]]: +) -> tuple[list[GRPCLine], list[GRPCArc]]: """Convert a list of ``SketchEdge`` to a gRPC message. Parameters @@ -221,8 +215,8 @@ def sketch_edges_to_grpc_geometries( Returns ------- - tuple[list[GRPCLine], list[GRPCArc], list[GRPCNurbsCurve]] - Geometry service gRPC line, arc, and NURBS curve messages. The unit is meters. + tuple[list[GRPCLine], list[GRPCArc]] + Geometry service gRPC line and arc messages. The unit is meters. """ from ansys.geometry.core.sketch.arc import Arc from ansys.geometry.core.sketch.nurbs import SketchNurbs @@ -230,16 +224,13 @@ def sketch_edges_to_grpc_geometries( arcs = [] segments = [] - nurbs_curves = [] for edge in edges: if isinstance(edge, SketchSegment): segments.append(sketch_segment_to_grpc_line(edge, plane)) elif isinstance(edge, Arc): arcs.append(sketch_arc_to_grpc_arc(edge, plane)) - elif isinstance(edge, SketchNurbs): - nurbs_curves.append(sketch_nurbs_to_grpc_nurbs_curve(edge, plane)) - return (segments, arcs, nurbs_curves) + return (segments, arcs) def sketch_arc_to_grpc_arc(arc: Arc, plane: Plane) -> GRPCArc: @@ -271,82 +262,6 @@ def sketch_arc_to_grpc_arc(arc: Arc, plane: Plane) -> GRPCArc: ) -def sketch_nurbs_to_grpc_nurbs_curve(curve: "SketchNurbs", plane: "Plane") -> GRPCNurbsCurve: - """Convert a ``SketchNurbs`` class to a NURBS curve gRPC message. - - Parameters - ---------- - nurbs : SketchNurbs - Source NURBS data. - plane : Plane - Plane for positioning the NURBS curve. - - Returns - ------- - GRPCNurbsCurve - Geometry service gRPC NURBS curve message. The unit is meters. - """ - from ansys.api.geometry.v0.models_pb2 import ( - ControlPoint as GRPCControlPoint, - NurbsData as GRPCNurbsData, - ) - - # Convert control points - control_points = [ - GRPCControlPoint( - position=point2d_to_grpc_point(plane, pt), - weight=curve.weights[i], - ) - for i, pt in enumerate(curve.control_points) - ] - - # Convert nurbs data - nurbs_data = GRPCNurbsData( - degree=curve.degree, - knots=knots_to_grpc_knots(curve.knots), - order=curve.degree + 1, - ) - - return GRPCNurbsCurve( - control_points=control_points, - nurbs_data=nurbs_data, - ) - - -def knots_to_grpc_knots(knots: list[float]) -> list[GRPCKnot]: - """Convert a list of knots to a list of gRPC knot messages. - - Parameters - ---------- - knots : list[float] - Source knots data. - - Returns - ------- - list[GRPCKnot] - Geometry service gRPC knot messages. - """ - from collections import Counter - - # Count multiplicities - multiplicities = Counter(knots) - - # Get unique knots (parameters) in order - unique_knots = sorted(set(knots)) - knot_multiplicities = [(knot, multiplicities[knot]) for knot in unique_knots] - - # Convert to gRPC knot messages - grpc_knots = [ - GRPCKnot( - parameter=knot, - multiplicity=multiplicity, - ) - for knot, multiplicity in knot_multiplicities - ] - - return grpc_knots - - def sketch_ellipse_to_grpc_ellipse(ellipse: SketchEllipse, plane: Plane) -> GRPCEllipse: """Convert a ``SketchEllipse`` class to an ellipse gRPC message. diff --git a/src/ansys/geometry/core/designer/geometry_commands.py b/src/ansys/geometry/core/designer/geometry_commands.py index 178c7bcb43..b0a9a4ca96 100644 --- a/src/ansys/geometry/core/designer/geometry_commands.py +++ b/src/ansys/geometry/core/designer/geometry_commands.py @@ -1638,55 +1638,4 @@ def create_orient_condition( result.offset, result.is_reversed, result.is_valid, - ) - - @protect_grpc - @min_backend_version(26, 1, 0) - def fill( - self, - sketch: "Sketch", - design: "Design", - ) -> Body: - """Fill the given sketch on the specified plane within the design. - - Parameters - ---------- - sketch : Sketch - The sketch to fill. - design : Design - The design in which the sketch resides. - - Returns - ------- - Body - The surface body created by filling the sketch. - - Warnings - -------- - This method is only available starting on Ansys release 26R1. - """ - from ansys.geometry.core.connection.conversions import ( - plane_to_grpc_plane, - sketch_shapes_to_grpc_geometries, - ) - - # Create the gRPC request - request = FillRequest( - geometries=sketch_shapes_to_grpc_geometries(sketch.plane, sketch.edges, sketch.faces), - plane=plane_to_grpc_plane(sketch.plane), - parent=design._grpc_id - ) - - # Call the gRPC service - response = self._commands_stub.Fill(request) - - # If successful, create and return the Body object - if response.success: - tb = MasterBody( - response.created_body.id, - response.created_body.name, - self._grpc_client, - is_surface=response.created_body.is_surface, - ) - - return Body(response.created_body.id, response.created_body.name, design, tb) \ No newline at end of file + ) \ No newline at end of file From 9601df44d35511928ceb0493b511b3229de7ea46 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 11 Jul 2025 17:13:34 +0000 Subject: [PATCH 12/13] chore: auto fixes from pre-commit hooks --- src/ansys/geometry/core/connection/conversions.py | 1 - src/ansys/geometry/core/designer/geometry_commands.py | 7 ++----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/ansys/geometry/core/connection/conversions.py b/src/ansys/geometry/core/connection/conversions.py index ad5b414f9d..9e30e6a850 100644 --- a/src/ansys/geometry/core/connection/conversions.py +++ b/src/ansys/geometry/core/connection/conversions.py @@ -219,7 +219,6 @@ def sketch_edges_to_grpc_geometries( Geometry service gRPC line and arc messages. The unit is meters. """ from ansys.geometry.core.sketch.arc import Arc - from ansys.geometry.core.sketch.nurbs import SketchNurbs from ansys.geometry.core.sketch.segment import SketchSegment arcs = [] diff --git a/src/ansys/geometry/core/designer/geometry_commands.py b/src/ansys/geometry/core/designer/geometry_commands.py index b0a9a4ca96..7622750026 100644 --- a/src/ansys/geometry/core/designer/geometry_commands.py +++ b/src/ansys/geometry/core/designer/geometry_commands.py @@ -39,7 +39,6 @@ ExtrudeFacesRequest, ExtrudeFacesUpToRequest, FilletRequest, - FillRequest, FullFilletRequest, ModifyCircularPatternRequest, ModifyLinearPatternRequest, @@ -63,7 +62,7 @@ point3d_to_grpc_point, unit_vector_to_grpc_direction, ) -from ansys.geometry.core.designer.body import Body, MasterBody +from ansys.geometry.core.designer.body import Body from ansys.geometry.core.designer.component import Component from ansys.geometry.core.designer.mating_conditions import ( AlignCondition, @@ -95,10 +94,8 @@ if TYPE_CHECKING: # pragma: no cover from ansys.geometry.core.designer.body import Body from ansys.geometry.core.designer.component import Component - from ansys.geometry.core.designer.design import Design from ansys.geometry.core.designer.edge import Edge from ansys.geometry.core.designer.face import Face - from ansys.geometry.core.sketch import Sketch @unique @@ -1638,4 +1635,4 @@ def create_orient_condition( result.offset, result.is_reversed, result.is_valid, - ) \ No newline at end of file + ) From 1b5f78e43000bf56eb7692ee2408413f4f7d2e41 Mon Sep 17 00:00:00 2001 From: Jacob Kerstetter Date: Fri, 11 Jul 2025 13:15:20 -0400 Subject: [PATCH 13/13] more cleanup --- src/ansys/geometry/core/connection/conversions.py | 1 - src/ansys/geometry/core/designer/geometry_commands.py | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/ansys/geometry/core/connection/conversions.py b/src/ansys/geometry/core/connection/conversions.py index ad5b414f9d..9e30e6a850 100644 --- a/src/ansys/geometry/core/connection/conversions.py +++ b/src/ansys/geometry/core/connection/conversions.py @@ -219,7 +219,6 @@ def sketch_edges_to_grpc_geometries( Geometry service gRPC line and arc messages. The unit is meters. """ from ansys.geometry.core.sketch.arc import Arc - from ansys.geometry.core.sketch.nurbs import SketchNurbs from ansys.geometry.core.sketch.segment import SketchSegment arcs = [] diff --git a/src/ansys/geometry/core/designer/geometry_commands.py b/src/ansys/geometry/core/designer/geometry_commands.py index b0a9a4ca96..fbf0d27f94 100644 --- a/src/ansys/geometry/core/designer/geometry_commands.py +++ b/src/ansys/geometry/core/designer/geometry_commands.py @@ -39,7 +39,6 @@ ExtrudeFacesRequest, ExtrudeFacesUpToRequest, FilletRequest, - FillRequest, FullFilletRequest, ModifyCircularPatternRequest, ModifyLinearPatternRequest, @@ -63,7 +62,7 @@ point3d_to_grpc_point, unit_vector_to_grpc_direction, ) -from ansys.geometry.core.designer.body import Body, MasterBody +from ansys.geometry.core.designer.body import Body from ansys.geometry.core.designer.component import Component from ansys.geometry.core.designer.mating_conditions import ( AlignCondition, @@ -95,10 +94,8 @@ if TYPE_CHECKING: # pragma: no cover from ansys.geometry.core.designer.body import Body from ansys.geometry.core.designer.component import Component - from ansys.geometry.core.designer.design import Design from ansys.geometry.core.designer.edge import Edge from ansys.geometry.core.designer.face import Face - from ansys.geometry.core.sketch import Sketch @unique