From c364e6665c0451d7dfcea788086044d0e7f3c154 Mon Sep 17 00:00:00 2001 From: dastan-ansys Date: Wed, 13 Mar 2024 14:59:52 -0400 Subject: [PATCH 01/18] added the client side implementations of sweeping chain and sweeping profile --- src/ansys/geometry/core/designer/component.py | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/src/ansys/geometry/core/designer/component.py b/src/ansys/geometry/core/designer/component.py index 0a1ef08e37..2c7e352291 100644 --- a/src/ansys/geometry/core/designer/component.py +++ b/src/ansys/geometry/core/designer/component.py @@ -30,6 +30,7 @@ CreateExtrudedBodyFromFaceProfileRequest, CreateExtrudedBodyRequest, CreatePlanarBodyRequest, + CreateSweepingProfileRequest, TranslateRequest, ) from ansys.api.geometry.v0.bodies_pb2_grpc import BodiesStub @@ -52,6 +53,7 @@ plane_to_grpc_plane, point3d_to_grpc_point, sketch_shapes_to_grpc_geometries, + trimmed_curve_to_grpc_trimmed_curve, unit_vector_to_grpc_direction, ) from ansys.geometry.core.designer.beam import Beam, BeamProfile @@ -68,6 +70,7 @@ from ansys.geometry.core.math.vector import UnitVector3D, Vector3D from ansys.geometry.core.misc.checks import ensure_design_is_active from ansys.geometry.core.misc.measurements import DEFAULT_UNITS, Angle, Distance +from ansys.geometry.core.shapes.curves.trimmed_curve import TrimmedCurve from ansys.geometry.core.sketch.sketch import Sketch from ansys.geometry.core.typing import Real @@ -484,6 +487,107 @@ def extrude_sketch( self._master_component.part.bodies.append(tb) return Body(response.id, response.name, self, tb) + @protect_grpc + @check_input_types + @ensure_design_is_active + def create_sweeping_profile( + self, + name: str, + sketch: Sketch, + path: List[TrimmedCurve], + ) -> Body: + """ + Create a body by sweeping a planar profile along a path. + + Notes + ----- + The newly created body is placed under this component within the design assembly. + + Parameters + ---------- + name : str + User-defined label for the new solid body. + sketch : Sketch + Two-dimensional sketch source for the extrusion. + path : List[TrimmedCurve] + A sweep path. + + Returns + ------- + Body + Created body from the given sketch. + """ + # Convert each ``TrimmedCurve`` in path to equivalent gRPC type + path_grpc = [] + for tc in path: + path_grpc.append(trimmed_curve_to_grpc_trimmed_curve(tc)) + + request = CreateSweepingProfileRequest( + name=name, + parent=self.id, + plane=plane_to_grpc_plane(sketch._plane), + geometries=sketch_shapes_to_grpc_geometries(sketch._plane, sketch.edges, sketch.faces), + path=path_grpc, + ) + + self._grpc_client.log.debug(f"Creating a sweeping profile on {self.id}. Creating body...") + response = self._bodies_stub.CreateSweepingProfile(request) + tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=False) + self._master_component.part.bodies.append(tb) + return Body(response.id, response.name, self, tb) + + @protect_grpc + @check_input_types + @ensure_design_is_active + def create_sweeping_chain( + self, + name: str, + sketch: Sketch, + path: List[TrimmedCurve], + chain: List[TrimmedCurve], + ) -> Body: + """ + Create a body by sweeping a chain of curves along a path. + + Notes + ----- + The newly created body is placed under this component within the design assembly. + + Parameters + ---------- + name : str + User-defined label for the new solid body. + sketch : Sketch + Two-dimensional sketch source for the extrusion. + path : List[TrimmedCurve] + A sweep path. + chain : List[TrimmedCurve] + A chain of trimmed curves. + + Returns + ------- + Body + Created body from the given sketch. + """ + # Convert each ``TrimmedCurve`` in path and chain to equivalent gRPC types + path_grpc = [trimmed_curve_to_grpc_trimmed_curve(tc) for tc in path] + chain_grpc = [trimmed_curve_to_grpc_trimmed_curve(tc) for tc in chain] + + request = CreateSweepingProfileRequest( + name=name, + parent=self.id, + plane=plane_to_grpc_plane(sketch._plane), + geometries=sketch_shapes_to_grpc_geometries(sketch._plane, sketch.edges, sketch.faces), + path=path_grpc, + chain=chain_grpc, + ) + + self._grpc_client.log.debug(f"Creating a sweeping chain on {self.id}. Creating body...") + response = self._bodies_stub.CreateSweepingChain(request) + tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=False) + self._master_component.part.bodies.append(tb) + return Body(response.id, response.name, self, tb) + @protect_grpc @check_input_types @ensure_design_is_active From 39ba4a54524500a3398b7d9216e217c95808401f Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot Date: Wed, 13 Mar 2024 19:08:43 +0000 Subject: [PATCH 02/18] Adding changelog entry: 1056.added.md --- doc/changelog.d/1056.added.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/1056.added.md diff --git a/doc/changelog.d/1056.added.md b/doc/changelog.d/1056.added.md new file mode 100644 index 0000000000..ac92fc2b5e --- /dev/null +++ b/doc/changelog.d/1056.added.md @@ -0,0 +1 @@ +added the client-side implementations of sweeping functions. \ No newline at end of file From f6c1b2703193704ba3bd6347aa4673d7ef100fda Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot Date: Wed, 13 Mar 2024 19:11:53 +0000 Subject: [PATCH 03/18] Adding changelog entry: 1056.added.md --- doc/changelog.d/1056.added.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/changelog.d/1056.added.md b/doc/changelog.d/1056.added.md index ac92fc2b5e..18eec091dd 100644 --- a/doc/changelog.d/1056.added.md +++ b/doc/changelog.d/1056.added.md @@ -1 +1 @@ -added the client-side implementations of sweeping functions. \ No newline at end of file +feat: sweeping-chains-and-profiles \ No newline at end of file From a443fb64cbc94b48d055b46acb7b465dcc99a02c Mon Sep 17 00:00:00 2001 From: dastan-ansys <132925889+dastan-ansys@users.noreply.github.com> Date: Thu, 14 Mar 2024 03:34:21 -0400 Subject: [PATCH 04/18] Update src/ansys/geometry/core/designer/component.py Co-authored-by: Jonah Boling <56607167+jonahrb@users.noreply.github.com> --- src/ansys/geometry/core/designer/component.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/geometry/core/designer/component.py b/src/ansys/geometry/core/designer/component.py index 2c7e352291..98b4bb2200 100644 --- a/src/ansys/geometry/core/designer/component.py +++ b/src/ansys/geometry/core/designer/component.py @@ -510,7 +510,7 @@ def create_sweeping_profile( sketch : Sketch Two-dimensional sketch source for the extrusion. path : List[TrimmedCurve] - A sweep path. + The path to sweep the profile along. Returns ------- From 6aee11a467877ce7952525760ece92fe0b071b9b Mon Sep 17 00:00:00 2001 From: dastan-ansys Date: Thu, 14 Mar 2024 03:39:18 -0400 Subject: [PATCH 05/18] implemented suggestions from initial review --- src/ansys/geometry/core/designer/component.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/ansys/geometry/core/designer/component.py b/src/ansys/geometry/core/designer/component.py index 2c7e352291..d88f78def6 100644 --- a/src/ansys/geometry/core/designer/component.py +++ b/src/ansys/geometry/core/designer/component.py @@ -30,6 +30,7 @@ CreateExtrudedBodyFromFaceProfileRequest, CreateExtrudedBodyRequest, CreatePlanarBodyRequest, + CreateSweepingChainRequest, CreateSweepingProfileRequest, TranslateRequest, ) @@ -68,7 +69,7 @@ from ansys.geometry.core.math.matrix import Matrix44 from ansys.geometry.core.math.point import Point3D from ansys.geometry.core.math.vector import UnitVector3D, Vector3D -from ansys.geometry.core.misc.checks import ensure_design_is_active +from ansys.geometry.core.misc.checks import ensure_design_is_active, min_backend_version from ansys.geometry.core.misc.measurements import DEFAULT_UNITS, Angle, Distance from ansys.geometry.core.shapes.curves.trimmed_curve import TrimmedCurve from ansys.geometry.core.sketch.sketch import Sketch @@ -487,10 +488,11 @@ def extrude_sketch( self._master_component.part.bodies.append(tb) return Body(response.id, response.name, self, tb) + @min_backend_version(24, 2, 0) @protect_grpc @check_input_types @ensure_design_is_active - def create_sweeping_profile( + def sweep_sketch( self, name: str, sketch: Sketch, @@ -510,7 +512,7 @@ def create_sweeping_profile( sketch : Sketch Two-dimensional sketch source for the extrusion. path : List[TrimmedCurve] - A sweep path. + The path to sweep the profile along. Returns ------- @@ -532,17 +534,17 @@ def create_sweeping_profile( self._grpc_client.log.debug(f"Creating a sweeping profile on {self.id}. Creating body...") response = self._bodies_stub.CreateSweepingProfile(request) - tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=False) + tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=True) self._master_component.part.bodies.append(tb) return Body(response.id, response.name, self, tb) + @min_backend_version(24, 2, 0) @protect_grpc @check_input_types @ensure_design_is_active - def create_sweeping_chain( + def sweep_chain( self, name: str, - sketch: Sketch, path: List[TrimmedCurve], chain: List[TrimmedCurve], ) -> Body: @@ -560,7 +562,7 @@ def create_sweeping_chain( sketch : Sketch Two-dimensional sketch source for the extrusion. path : List[TrimmedCurve] - A sweep path. + The path to sweep the chain along. chain : List[TrimmedCurve] A chain of trimmed curves. @@ -573,11 +575,9 @@ def create_sweeping_chain( path_grpc = [trimmed_curve_to_grpc_trimmed_curve(tc) for tc in path] chain_grpc = [trimmed_curve_to_grpc_trimmed_curve(tc) for tc in chain] - request = CreateSweepingProfileRequest( + request = CreateSweepingChainRequest( name=name, parent=self.id, - plane=plane_to_grpc_plane(sketch._plane), - geometries=sketch_shapes_to_grpc_geometries(sketch._plane, sketch.edges, sketch.faces), path=path_grpc, chain=chain_grpc, ) From 6b11e655467988ec82b65fb4c7f87bbc1d7bcf81 Mon Sep 17 00:00:00 2001 From: dastan-ansys Date: Thu, 14 Mar 2024 03:41:28 -0400 Subject: [PATCH 06/18] for some reason the is_surface did not update --- src/ansys/geometry/core/designer/component.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/geometry/core/designer/component.py b/src/ansys/geometry/core/designer/component.py index d88f78def6..99eb207a77 100644 --- a/src/ansys/geometry/core/designer/component.py +++ b/src/ansys/geometry/core/designer/component.py @@ -584,7 +584,7 @@ def sweep_chain( self._grpc_client.log.debug(f"Creating a sweeping chain on {self.id}. Creating body...") response = self._bodies_stub.CreateSweepingChain(request) - tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=False) + tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=True) self._master_component.part.bodies.append(tb) return Body(response.id, response.name, self, tb) From b012fb5a2d5d6282a23d6c08a1ba57a504724eb7 Mon Sep 17 00:00:00 2001 From: jonahrb Date: Thu, 14 Mar 2024 12:09:00 -0400 Subject: [PATCH 07/18] Update pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 109a3bc4c7..6838161756 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ classifiers = [ ] dependencies = [ - "ansys-api-geometry==0.3.12", + "ansys-api-geometry==0.3.13", "ansys-tools-path>=0.3,<1", "beartype>=0.11.0,<1", "google-api-python-client>=1.7.11,<3", From 61963f27d964eb5530809038069e748569d8e5b1 Mon Sep 17 00:00:00 2001 From: dastan-ansys <132925889+dastan-ansys@users.noreply.github.com> Date: Thu, 14 Mar 2024 13:05:52 -0400 Subject: [PATCH 08/18] Update component.py --- src/ansys/geometry/core/designer/component.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/geometry/core/designer/component.py b/src/ansys/geometry/core/designer/component.py index 042900b328..998ff218a1 100644 --- a/src/ansys/geometry/core/designer/component.py +++ b/src/ansys/geometry/core/designer/component.py @@ -585,7 +585,7 @@ def sweep_chain( self._grpc_client.log.debug(f"Creating a sweeping chain on {self.id}. Creating body...") response = self._bodies_stub.CreateSweepingChain(request) - tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=True) + tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=False) self._master_component.part.bodies.append(tb) return Body(response.id, response.name, self, tb) From 7c4493f12a11d0bd431cd67de0582076476634ac Mon Sep 17 00:00:00 2001 From: jonahrb Date: Thu, 14 Mar 2024 13:41:57 -0400 Subject: [PATCH 09/18] Add trim() method to curve/surface - use trim() to create a TrimmedCurve/TrimmedSurface from Curve/Surface - refactor TrimmedSurface constructor --- .../geometry/core/connection/conversions.py | 5 ---- src/ansys/geometry/core/designer/face.py | 10 +++++-- .../geometry/core/shapes/curves/curve.py | 26 ++++++++++++++++++- .../geometry/core/shapes/surfaces/surface.py | 19 +++++++++++++- .../core/shapes/surfaces/trimmed_surface.py | 26 +++++-------------- 5 files changed, 58 insertions(+), 28 deletions(-) diff --git a/src/ansys/geometry/core/connection/conversions.py b/src/ansys/geometry/core/connection/conversions.py index cda75e9a9f..2eed3988a1 100644 --- a/src/ansys/geometry/core/connection/conversions.py +++ b/src/ansys/geometry/core/connection/conversions.py @@ -583,16 +583,11 @@ def trimmed_curve_to_grpc_trimmed_curve(curve: "TrimmedCurve") -> GRPCTrimmedCur Geometry service gRPC ``TrimmedCurve`` message. """ curve_geometry = curve_to_grpc_curve(curve.geometry) - start = point3d_to_grpc_point(curve.start) - end = point3d_to_grpc_point(curve.end) i_start = curve.interval.start i_end = curve.interval.end return GRPCTrimmedCurve( curve=curve_geometry, - start=start, - end=end, interval_start=i_start, interval_end=i_end, - length=curve.length.m, ) diff --git a/src/ansys/geometry/core/designer/face.py b/src/ansys/geometry/core/designer/face.py index 2e87bc2bd2..e76ddd3f7e 100644 --- a/src/ansys/geometry/core/designer/face.py +++ b/src/ansys/geometry/core/designer/face.py @@ -39,6 +39,7 @@ from ansys.geometry.core.math.vector import UnitVector3D from ansys.geometry.core.misc.checks import ensure_design_is_active from ansys.geometry.core.misc.measurements import DEFAULT_UNITS +from ansys.geometry.core.shapes.box_uv import BoxUV from ansys.geometry.core.shapes.curves.trimmed_curve import TrimmedCurve from ansys.geometry.core.shapes.parameterization import Interval from ansys.geometry.core.shapes.surfaces.trimmed_surface import ( @@ -203,12 +204,17 @@ def shape(self) -> TrimmedSurface: direction of the normal vector to ensure it is always facing outward. """ if self._shape is None: + self._grpc_client.log.debug("Requesting face properties from server.") + surface_response = self._faces_stub.GetSurface(self._grpc_id) geometry = grpc_surface_to_surface(surface_response, self._surface_type) + box = self._faces_stub.GetBoxUV(self._grpc_id) + box_uv = BoxUV(Interval(box.start_u, box.end_u), Interval(box.start_v, box.end_v)) + self._shape = ( - ReversedTrimmedSurface(self, geometry) + ReversedTrimmedSurface(geometry, box_uv) if self.is_reversed - else TrimmedSurface(self, geometry) + else TrimmedSurface(geometry, box_uv) ) return self._shape diff --git a/src/ansys/geometry/core/shapes/curves/curve.py b/src/ansys/geometry/core/shapes/curves/curve.py index e15e4eee7f..e9222f5d85 100644 --- a/src/ansys/geometry/core/shapes/curves/curve.py +++ b/src/ansys/geometry/core/shapes/curves/curve.py @@ -22,12 +22,17 @@ """Provides the ``Curve`` class.""" from abc import ABC, abstractmethod +from beartype.typing import TYPE_CHECKING + from ansys.geometry.core.math.matrix import Matrix44 from ansys.geometry.core.math.point import Point3D from ansys.geometry.core.shapes.curves.curve_evaluation import CurveEvaluation -from ansys.geometry.core.shapes.parameterization import Parameterization +from ansys.geometry.core.shapes.parameterization import Interval, Parameterization from ansys.geometry.core.typing import Real +if TYPE_CHECKING: # pragma: no cover + from ansys.geometry.core.shapes.curves.trimmed_curve import TrimmedCurve + class Curve(ABC): """Provides the abstract base class representing a 3D curve.""" @@ -74,3 +79,22 @@ def project_point(self, point: Point3D) -> CurveEvaluation: This method returns the evaluation at the closest point. """ return + + def trim(self, interval: Interval) -> "TrimmedCurve": + """ + Trim this curve by bounding it with an interval. + + Returns + ------- + TrimmedCurve + The resulting bounded curve. + """ + from ansys.geometry.core.shapes.curves.trimmed_curve import TrimmedCurve + + return TrimmedCurve( + self, + self.evaluate(interval.start).position, + self.evaluate(interval.end).position, + interval, + None, # TODO: calculate length on client? + ) diff --git a/src/ansys/geometry/core/shapes/surfaces/surface.py b/src/ansys/geometry/core/shapes/surfaces/surface.py index fae48d4556..cb4b34ee55 100644 --- a/src/ansys/geometry/core/shapes/surfaces/surface.py +++ b/src/ansys/geometry/core/shapes/surfaces/surface.py @@ -22,13 +22,17 @@ """Provides the ``Surface`` class.""" from abc import ABC, abstractmethod -from beartype.typing import Tuple +from beartype.typing import TYPE_CHECKING, Tuple from ansys.geometry.core.math.matrix import Matrix44 from ansys.geometry.core.math.point import Point3D +from ansys.geometry.core.shapes.box_uv import BoxUV from ansys.geometry.core.shapes.parameterization import Parameterization, ParamUV from ansys.geometry.core.shapes.surfaces.surface_evaluation import SurfaceEvaluation +if TYPE_CHECKING: # pragma: no cover + from ansys.geometry.core.shapes.surfaces.trimmed_surface import TrimmedSurface + class Surface(ABC): """Provides the abstract base class for a 3D surface.""" @@ -75,3 +79,16 @@ def project_point(self, point: Point3D) -> SurfaceEvaluation: This method returns the evaluation at the closest point. """ return + + def trim(self, box_uv: BoxUV) -> "TrimmedSurface": + """ + Trim this curve by bounding it with a BoxUV. + + Returns + ------- + TrimmedSurface + The resulting bounded surface. + """ + from ansys.geometry.core.shapes.surfaces.trimmed_surface import TrimmedSurface + + return TrimmedSurface(self, box_uv) diff --git a/src/ansys/geometry/core/shapes/surfaces/trimmed_surface.py b/src/ansys/geometry/core/shapes/surfaces/trimmed_surface.py index 66978e0b4e..9b418e8536 100644 --- a/src/ansys/geometry/core/shapes/surfaces/trimmed_surface.py +++ b/src/ansys/geometry/core/shapes/surfaces/trimmed_surface.py @@ -21,19 +21,14 @@ # SOFTWARE. """Provides the ``TrimmedSurface`` class.""" -from beartype.typing import TYPE_CHECKING - from ansys.geometry.core.math.point import Point3D from ansys.geometry.core.math.vector import UnitVector3D from ansys.geometry.core.shapes.box_uv import BoxUV -from ansys.geometry.core.shapes.parameterization import Interval, ParamUV +from ansys.geometry.core.shapes.parameterization import ParamUV from ansys.geometry.core.shapes.surfaces.surface import Surface from ansys.geometry.core.shapes.surfaces.surface_evaluation import SurfaceEvaluation from ansys.geometry.core.typing import Real -if TYPE_CHECKING: - from ansys.geometry.core.designer.face import Face - class TrimmedSurface: """ @@ -50,15 +45,10 @@ class TrimmedSurface: Underlying mathematical representation of the surface. """ - def __init__(self, face: "Face", geometry: Surface): + def __init__(self, geometry: Surface, box_uv: BoxUV): """Initialize an instance of a trimmed surface.""" - self._face = face self._geometry = geometry - - @property - def face(self) -> "Face": - """Face the trimmed surface belongs to.""" - return self._face + self._box_uv = box_uv @property def geometry(self) -> Surface: @@ -68,9 +58,7 @@ def geometry(self) -> Surface: @property def box_uv(self) -> BoxUV: """Bounding BoxUV of the surface.""" - self._face._grpc_client.log.debug("Requesting box UV from server.") - box = self._face._faces_stub.GetBoxUV(self.face._grpc_id) - return BoxUV(Interval(box.start_u, box.end_u), Interval(box.start_v, box.end_v)) + return self._box_uv def get_proportional_parameters(self, param_uv: ParamUV) -> ParamUV: """ @@ -154,7 +142,7 @@ def evaluate_proportion(self, u: Real, v: Real) -> SurfaceEvaluation: ) ) - # TODO: perimeter + # TODO: perimeter, area? class ReversedTrimmedSurface(TrimmedSurface): @@ -172,9 +160,9 @@ class ReversedTrimmedSurface(TrimmedSurface): Underlying mathematical representation of the surface. """ - def __init__(self, face: "Face", geometry: Surface): + def __init__(self, geometry: Surface, box_uv: BoxUV): """Initialize an instance of a reversed trimmed surface.""" - super().__init__(face, geometry) + super().__init__(geometry, box_uv) def normal(self, u: Real, v: Real) -> UnitVector3D: # noqa: D102 return -self.evaluate_proportion(u, v).normal From 30ac7b30562973dd6e2a0e6be360a00138c8c79e Mon Sep 17 00:00:00 2001 From: dastan-ansys Date: Fri, 15 Mar 2024 17:57:00 -0400 Subject: [PATCH 10/18] added unit tests for sweeps --- tests/integration/test_sweeps.py | 119 +++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 tests/integration/test_sweeps.py diff --git a/tests/integration/test_sweeps.py b/tests/integration/test_sweeps.py new file mode 100644 index 0000000000..c90d95544d --- /dev/null +++ b/tests/integration/test_sweeps.py @@ -0,0 +1,119 @@ +# Copyright (C) 2023 - 2024 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. + +import numpy as np +import pytest + +from ansys.geometry.core import Modeler +from ansys.geometry.core.math.plane import Plane +from ansys.geometry.core.math.point import Point2D, Point3D +from ansys.geometry.core.shapes.curves.circle import Circle +from ansys.geometry.core.shapes.curves.ellipse import Ellipse +from ansys.geometry.core.shapes.parameterization import Interval +from ansys.geometry.core.sketch.sketch import Sketch + + +def test_sweep_sketch(modeler: Modeler): + """Test the sweep_sketch method by creating a revolving a circle profile around an + circular axis to make a donut.""" + + design_sketch = modeler.create_design("donut") + + path_radius = 5 + profile_radius = 2 + + # create a circle on the XZ-plane centered at (5, 0, 0) with radius 2 + profile = Sketch(plane=Plane(direction_x=[1, 0, 0], direction_y=[0, 0, 1])).circle( + Point2D([path_radius, 0]), profile_radius + ) + + # create a circle on the XY-plane centered at (0, 0, 0) with radius 5 + path = [Circle(Point3D([0, 0, 0]), path_radius).trim(Interval(0, 2 * np.pi))] + + body = design_sketch.sweep_sketch("donutsweep", profile, path) + + assert body.is_surface == True + + # check edges + assert len(body.edges) == 0 + + # check faces + assert len(body.faces) == 1 + + # check area of face + # compute expected area (torus with r < R) where r is inner radius and R is outer radius + R = path_radius + profile_radius + r = path_radius - profile_radius + expected_face_area = (np.pi**2) * (R**2 - r**2) + assert body.faces[0].area.m == pytest.approx(expected_face_area) + + # check volume of face + # expected is 0 since it has 0 thickness + assert body.faces[0].volume.m == 0 + + +def test_sweep_chain(modeler: Modeler): + """Test the sweep_chain method by revolving a semi-elliptical profile around an + circular axis to make a bowl.""" + design_chain = modeler.create_design("bowl") + + radius = 10 + + # create quarter-ellipse profile with major radius = 10, minor radius = 5 + profile = [ + Ellipse( + Point3D([0, 0, radius / 2]), radius, radius / 2, reference=[1, 0, 0], axis=[0, 1, 0] + ).trim(Interval(0, np.pi / 2)) + ] + + # create circle on the plane parallel to the XY-plane but moved up by 5 units with radius 10 + path = [Circle(Point3D([0, 0, radius / 2]), radius).trim(Interval(0, 2 * np.pi))] + + # create the bowl body + body = design_chain.sweep_chain("bowlsweep", path, profile) + + assert body.is_surface == False + + # check edges + assert len(body.edges) == 1 + + # check length of edge + # compute expected circumference (circle with radius 10) + expected_edge_cirumference = 2 * np.pi * 10 + assert body.edges[0].length.m == pytest.approx(expected_edge_cirumference) + + # check faces + assert len(body.faces) == 1 + + # check area of face + # compute expected area (half a spheroid) + minor_rad = radius / 2 + e_squared = 1 - (minor_rad**2 / radius**2) + e = np.sqrt(e_squared) + expected_face_area = ( + 2 * np.pi * radius**2 + (minor_rad**2 / e) * np.pi * np.log((1 + e) / (1 - e)) + ) / 2 + assert body.faces[0].area.m == pytest.approx(expected_face_area) + + # check volume of face + # expected is 0 since it's not a closed surface + assert body.faces[0].volume.m == 0 From 66db3388035780111b9a7bf290ba062822dd11db Mon Sep 17 00:00:00 2001 From: dastan-ansys Date: Fri, 15 Mar 2024 18:03:55 -0400 Subject: [PATCH 11/18] volume is body property not face --- tests/integration/test_sweeps.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/test_sweeps.py b/tests/integration/test_sweeps.py index c90d95544d..3183438823 100644 --- a/tests/integration/test_sweeps.py +++ b/tests/integration/test_sweeps.py @@ -68,7 +68,7 @@ def test_sweep_sketch(modeler: Modeler): # check volume of face # expected is 0 since it has 0 thickness - assert body.faces[0].volume.m == 0 + assert body.volume.m == 0 def test_sweep_chain(modeler: Modeler): @@ -116,4 +116,4 @@ def test_sweep_chain(modeler: Modeler): # check volume of face # expected is 0 since it's not a closed surface - assert body.faces[0].volume.m == 0 + assert body.volume.m == 0 From 33cb4d1f2aa159a196079b3663a3c462994f7651 Mon Sep 17 00:00:00 2001 From: jonahrb Date: Sat, 16 Mar 2024 10:32:25 -0400 Subject: [PATCH 12/18] fix is_surface assignment, fix and move tests --- src/ansys/geometry/core/designer/component.py | 4 +- tests/integration/test_design.py | 89 ++++++++++++- tests/integration/test_sweeps.py | 119 ------------------ 3 files changed, 90 insertions(+), 122 deletions(-) delete mode 100644 tests/integration/test_sweeps.py diff --git a/src/ansys/geometry/core/designer/component.py b/src/ansys/geometry/core/designer/component.py index 998ff218a1..ba7933025f 100644 --- a/src/ansys/geometry/core/designer/component.py +++ b/src/ansys/geometry/core/designer/component.py @@ -535,7 +535,7 @@ def sweep_sketch( self._grpc_client.log.debug(f"Creating a sweeping profile on {self.id}. Creating body...") response = self._bodies_stub.CreateSweepingProfile(request) - tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=True) + tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=False) self._master_component.part.bodies.append(tb) return Body(response.id, response.name, self, tb) @@ -585,7 +585,7 @@ def sweep_chain( self._grpc_client.log.debug(f"Creating a sweeping chain on {self.id}. Creating body...") response = self._bodies_stub.CreateSweepingChain(request) - tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=False) + tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=True) self._master_component.part.bodies.append(tb) return Body(response.id, response.name, self, tb) diff --git a/tests/integration/test_design.py b/tests/integration/test_design.py index b25c5a5983..1a0a2b24a5 100644 --- a/tests/integration/test_design.py +++ b/tests/integration/test_design.py @@ -55,7 +55,9 @@ Vector3D, ) from ansys.geometry.core.misc import DEFAULT_UNITS, UNITS, Accuracy, Distance -from ansys.geometry.core.shapes.parameterization import ParamUV +from ansys.geometry.core.shapes.curves.circle import Circle +from ansys.geometry.core.shapes.curves.ellipse import Ellipse +from ansys.geometry.core.shapes.parameterization import Interval, ParamUV from ansys.geometry.core.sketch import Sketch @@ -2167,3 +2169,88 @@ def test_body_mirror(modeler: Modeler): if edge.shape.start not in copy_vertices: copy_vertices.append(edge.shape.start) assert np.allclose(expected_vertices, copy_vertices) + + +def test_sweep_sketch(modeler: Modeler): + """Test the sweep_sketch method by creating a revolving a circle profile around an + circular axis to make a donut.""" + + design_sketch = modeler.create_design("donut") + + path_radius = 5 + profile_radius = 2 + + # create a circle on the XZ-plane centered at (5, 0, 0) with radius 2 + profile = Sketch(plane=Plane(direction_x=[1, 0, 0], direction_y=[0, 0, 1])).circle( + Point2D([path_radius, 0]), profile_radius + ) + + # create a circle on the XY-plane centered at (0, 0, 0) with radius 5 + path = [Circle(Point3D([0, 0, 0]), path_radius).trim(Interval(0, 2 * np.pi))] + + body = design_sketch.sweep_sketch("donutsweep", profile, path) + + assert body.is_surface == False + + # check edges + assert len(body.edges) == 0 + + # check faces + assert len(body.faces) == 1 + + # check area of face + # compute expected area (torus with r < R) where r2 is inner radius and r1 is outer radius + r1 = path_radius + profile_radius + r2 = path_radius - profile_radius + expected_face_area = (np.pi**2) * (r1**2 - r2**2) + assert body.faces[0].area.m == pytest.approx(expected_face_area) + + assert Accuracy.length_is_equal(body.volume.m, 394.7841760435743) + + +def test_sweep_chain(modeler: Modeler): + """Test the sweep_chain method by revolving a semi-elliptical profile around an + circular axis to make a bowl.""" + design_chain = modeler.create_design("bowl") + + radius = 10 + + # create quarter-ellipse profile with major radius = 10, minor radius = 5 + profile = [ + Ellipse( + Point3D([0, 0, radius / 2]), radius, radius / 2, reference=[1, 0, 0], axis=[0, 1, 0] + ).trim(Interval(0, np.pi / 2)) + ] + + # create circle on the plane parallel to the XY-plane but moved up by 5 units with radius 10 + path = [Circle(Point3D([0, 0, radius / 2]), radius).trim(Interval(0, 2 * np.pi))] + + # create the bowl body + body = design_chain.sweep_chain("bowlsweep", path, profile) + + assert body.is_surface == True + + # check edges + assert len(body.edges) == 1 + + # check length of edge + # compute expected circumference (circle with radius 10) + expected_edge_cirumference = 2 * np.pi * 10 + assert body.edges[0].length.m == pytest.approx(expected_edge_cirumference) + + # check faces + assert len(body.faces) == 1 + + # check area of face + # compute expected area (half a spheroid) + minor_rad = radius / 2 + e_squared = 1 - (minor_rad**2 / radius**2) + e = np.sqrt(e_squared) + expected_face_area = ( + 2 * np.pi * radius**2 + (minor_rad**2 / e) * np.pi * np.log((1 + e) / (1 - e)) + ) / 2 + assert body.faces[0].area.m == pytest.approx(expected_face_area) + + # check volume of body + # expected is 0 since it's not a closed surface + assert body.volume.m == 0 diff --git a/tests/integration/test_sweeps.py b/tests/integration/test_sweeps.py deleted file mode 100644 index 3183438823..0000000000 --- a/tests/integration/test_sweeps.py +++ /dev/null @@ -1,119 +0,0 @@ -# Copyright (C) 2023 - 2024 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. - -import numpy as np -import pytest - -from ansys.geometry.core import Modeler -from ansys.geometry.core.math.plane import Plane -from ansys.geometry.core.math.point import Point2D, Point3D -from ansys.geometry.core.shapes.curves.circle import Circle -from ansys.geometry.core.shapes.curves.ellipse import Ellipse -from ansys.geometry.core.shapes.parameterization import Interval -from ansys.geometry.core.sketch.sketch import Sketch - - -def test_sweep_sketch(modeler: Modeler): - """Test the sweep_sketch method by creating a revolving a circle profile around an - circular axis to make a donut.""" - - design_sketch = modeler.create_design("donut") - - path_radius = 5 - profile_radius = 2 - - # create a circle on the XZ-plane centered at (5, 0, 0) with radius 2 - profile = Sketch(plane=Plane(direction_x=[1, 0, 0], direction_y=[0, 0, 1])).circle( - Point2D([path_radius, 0]), profile_radius - ) - - # create a circle on the XY-plane centered at (0, 0, 0) with radius 5 - path = [Circle(Point3D([0, 0, 0]), path_radius).trim(Interval(0, 2 * np.pi))] - - body = design_sketch.sweep_sketch("donutsweep", profile, path) - - assert body.is_surface == True - - # check edges - assert len(body.edges) == 0 - - # check faces - assert len(body.faces) == 1 - - # check area of face - # compute expected area (torus with r < R) where r is inner radius and R is outer radius - R = path_radius + profile_radius - r = path_radius - profile_radius - expected_face_area = (np.pi**2) * (R**2 - r**2) - assert body.faces[0].area.m == pytest.approx(expected_face_area) - - # check volume of face - # expected is 0 since it has 0 thickness - assert body.volume.m == 0 - - -def test_sweep_chain(modeler: Modeler): - """Test the sweep_chain method by revolving a semi-elliptical profile around an - circular axis to make a bowl.""" - design_chain = modeler.create_design("bowl") - - radius = 10 - - # create quarter-ellipse profile with major radius = 10, minor radius = 5 - profile = [ - Ellipse( - Point3D([0, 0, radius / 2]), radius, radius / 2, reference=[1, 0, 0], axis=[0, 1, 0] - ).trim(Interval(0, np.pi / 2)) - ] - - # create circle on the plane parallel to the XY-plane but moved up by 5 units with radius 10 - path = [Circle(Point3D([0, 0, radius / 2]), radius).trim(Interval(0, 2 * np.pi))] - - # create the bowl body - body = design_chain.sweep_chain("bowlsweep", path, profile) - - assert body.is_surface == False - - # check edges - assert len(body.edges) == 1 - - # check length of edge - # compute expected circumference (circle with radius 10) - expected_edge_cirumference = 2 * np.pi * 10 - assert body.edges[0].length.m == pytest.approx(expected_edge_cirumference) - - # check faces - assert len(body.faces) == 1 - - # check area of face - # compute expected area (half a spheroid) - minor_rad = radius / 2 - e_squared = 1 - (minor_rad**2 / radius**2) - e = np.sqrt(e_squared) - expected_face_area = ( - 2 * np.pi * radius**2 + (minor_rad**2 / e) * np.pi * np.log((1 + e) / (1 - e)) - ) / 2 - assert body.faces[0].area.m == pytest.approx(expected_face_area) - - # check volume of face - # expected is 0 since it's not a closed surface - assert body.volume.m == 0 From 84b273935810a97cbab67bd14a169e9897c1cb31 Mon Sep 17 00:00:00 2001 From: jonahrb Date: Sat, 16 Mar 2024 10:40:25 -0400 Subject: [PATCH 13/18] Update test_design.py --- tests/integration/test_design.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/integration/test_design.py b/tests/integration/test_design.py index 1a0a2b24a5..6ff6c3ac6a 100644 --- a/tests/integration/test_design.py +++ b/tests/integration/test_design.py @@ -2175,6 +2175,7 @@ def test_sweep_sketch(modeler: Modeler): """Test the sweep_sketch method by creating a revolving a circle profile around an circular axis to make a donut.""" + skip_if_linux(modeler) design_sketch = modeler.create_design("donut") path_radius = 5 @@ -2211,6 +2212,8 @@ def test_sweep_sketch(modeler: Modeler): def test_sweep_chain(modeler: Modeler): """Test the sweep_chain method by revolving a semi-elliptical profile around an circular axis to make a bowl.""" + + skip_if_linux(modeler) design_chain = modeler.create_design("bowl") radius = 10 From 72b377016f95382470fc594055c9d82e68628c8b Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot Date: Mon, 18 Mar 2024 06:52:18 +0000 Subject: [PATCH 14/18] Adding changelog entry: 1056.added.md --- doc/changelog.d/1056.added.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/changelog.d/1056.added.md b/doc/changelog.d/1056.added.md index 18eec091dd..d49517557b 100644 --- a/doc/changelog.d/1056.added.md +++ b/doc/changelog.d/1056.added.md @@ -1 +1 @@ -feat: sweeping-chains-and-profiles \ No newline at end of file +feat: sweeping chains and profiles \ No newline at end of file From 55fe98d0dc386dfa5887895eef482bdeb1d50884 Mon Sep 17 00:00:00 2001 From: Jonah Boling <56607167+jonahrb@users.noreply.github.com> Date: Mon, 18 Mar 2024 10:08:02 -0400 Subject: [PATCH 15/18] Update src/ansys/geometry/core/shapes/surfaces/surface.py Co-authored-by: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> --- src/ansys/geometry/core/shapes/surfaces/surface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/geometry/core/shapes/surfaces/surface.py b/src/ansys/geometry/core/shapes/surfaces/surface.py index cb4b34ee55..042b019c3a 100644 --- a/src/ansys/geometry/core/shapes/surfaces/surface.py +++ b/src/ansys/geometry/core/shapes/surfaces/surface.py @@ -82,7 +82,7 @@ def project_point(self, point: Point3D) -> SurfaceEvaluation: def trim(self, box_uv: BoxUV) -> "TrimmedSurface": """ - Trim this curve by bounding it with a BoxUV. + Trim this surface by bounding it with a BoxUV. Returns ------- From 044306c14cb6d9d232e2967b7c1e3826e3465f46 Mon Sep 17 00:00:00 2001 From: Jonah Boling <56607167+jonahrb@users.noreply.github.com> Date: Mon, 18 Mar 2024 10:08:19 -0400 Subject: [PATCH 16/18] Update tests/integration/test_design.py Co-authored-by: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> --- tests/integration/test_design.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/integration/test_design.py b/tests/integration/test_design.py index 6ff6c3ac6a..e5d31e60c6 100644 --- a/tests/integration/test_design.py +++ b/tests/integration/test_design.py @@ -2172,8 +2172,7 @@ def test_body_mirror(modeler: Modeler): def test_sweep_sketch(modeler: Modeler): - """Test the sweep_sketch method by creating a revolving a circle profile around an - circular axis to make a donut.""" + """Test revolving a circle profile around a circular axis to make a donut.""" skip_if_linux(modeler) design_sketch = modeler.create_design("donut") From 2c0a3dcc78f10ebf4c5dbddf5cfa62d3291f7f5e Mon Sep 17 00:00:00 2001 From: Jonah Boling <56607167+jonahrb@users.noreply.github.com> Date: Mon, 18 Mar 2024 10:08:31 -0400 Subject: [PATCH 17/18] Update tests/integration/test_design.py Co-authored-by: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> --- tests/integration/test_design.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/integration/test_design.py b/tests/integration/test_design.py index e5d31e60c6..4cde9887e3 100644 --- a/tests/integration/test_design.py +++ b/tests/integration/test_design.py @@ -2209,8 +2209,7 @@ def test_sweep_sketch(modeler: Modeler): def test_sweep_chain(modeler: Modeler): - """Test the sweep_chain method by revolving a semi-elliptical profile around an - circular axis to make a bowl.""" + """Test revolving a semi-elliptical profile around a circular axis to make a bowl.""" skip_if_linux(modeler) design_chain = modeler.create_design("bowl") From 0ef7c4a7d34b71c8298ec76abd13e85079a98048 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 14:09:08 +0000 Subject: [PATCH 18/18] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/integration/test_design.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/integration/test_design.py b/tests/integration/test_design.py index 4cde9887e3..a2b77a8a54 100644 --- a/tests/integration/test_design.py +++ b/tests/integration/test_design.py @@ -2209,7 +2209,8 @@ def test_sweep_sketch(modeler: Modeler): def test_sweep_chain(modeler: Modeler): - """Test revolving a semi-elliptical profile around a circular axis to make a bowl.""" + """Test revolving a semi-elliptical profile around a circular axis to make a + bowl.""" skip_if_linux(modeler) design_chain = modeler.create_design("bowl")