Skip to content

Commit

Permalink
feat: create body from surface (#1454)
Browse files Browse the repository at this point in the history
Co-authored-by: pyansys-ci-bot <[email protected]>
Co-authored-by: Roberto Pastor Muela <[email protected]>
  • Loading branch information
3 people authored Oct 17, 2024
1 parent f5f4a7a commit 052448f
Show file tree
Hide file tree
Showing 10 changed files with 290 additions and 1 deletion.
1 change: 1 addition & 0 deletions doc/changelog.d/1454.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
create body from surface
Binary file added doc/source/_static/thumbnails/quarter_sphere.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions doc/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ def intersphinx_pyansys_geometry(switcher_version: str):
"examples/03_modeling/export_design": "_static/thumbnails/export_design.png",
"examples/03_modeling/design_tree": "_static/thumbnails/design_tree.png",
"examples/03_modeling/service_colors": "_static/thumbnails/service_colors.png",
"examples/03_modeling/surface_bodies": "_static/thumbnails/quarter_sphere.png",
"examples/04_applied/01_naca_airfoils": "_static/thumbnails/naca_airfoils.png",
"examples/04_applied/02_naca_fluent": "_static/thumbnails/naca_fluent.png",
}
Expand Down
1 change: 1 addition & 0 deletions doc/source/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ These examples demonstrate service-based modeling operations.
examples/03_modeling/export_design.mystnb
examples/03_modeling/design_tree.mystnb
examples/03_modeling/service_colors.mystnb
examples/03_modeling/surface_bodies.mystnb

Applied examples
----------------
Expand Down
86 changes: 86 additions & 0 deletions doc/source/examples/03_modeling/surface_bodies.mystnb
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
---
jupytext:
text_representation:
extension: .mystnb
format_name: myst
format_version: 0.13
jupytext_version: 1.16.4
kernelspec:
display_name: Python 3 (ipykernel)
language: python
name: python3
---

# Modeling: Surface bodies and trimmed surfaces
This example will show how to trim different surfaces, and how to use those surfaces to create surface bodies.

## Create a surface
Create a sphere surface. This can be done without launching the modeler.

```{code-cell} ipython3
from ansys.geometry.core.shapes.surfaces import Sphere
from ansys.geometry.core.math import Point3D

surface = Sphere(origin=Point3D([0, 0, 0]), radius=1)
```

Now get information on how the surface is defined and parameterized.

```{code-cell} ipython3
surface.parameterization()
```

## Trim the surface
For a sphere, its parametization is (`u: [0, 2*pi]`, `v:[-pi/2, pi/2]`), where u corresponds to longitude and v corresponds to latitude. We can **trim** a surface by providing new parameters.

```{code-cell} ipython3
from ansys.geometry.core.shapes.box_uv import BoxUV
from ansys.geometry.core.shapes.parameterization import Interval
import math

trimmed_surface = surface.trim(BoxUV(range_u=Interval(0, math.pi), range_v=Interval(0, math.pi/2)))
```

From a TrimmedSurface, you can always refer back to the underlying Surface if needed.

```{code-cell} ipython3
trimmed_surface.geometry
```

## Create a surface body

Now create a surface body by launching the modeler session and providing the trimmed surface. Then plot the body to see how we created a quarter of a sphere as a surface body.

```{code-cell} ipython3
from ansys.geometry.core import launch_modeler

modeler = launch_modeler()
print(modeler)
```

```{code-cell} ipython3
design = modeler.create_design("SurfaceBodyExample")
body = design.create_body_from_surface("trimmed_sphere", trimmed_surface)
design.plot()
```

If the sphere was left untrimmed, it would create a solid body since the surface is fully closed. In this case, since the surface was open, it created a surface body.

This same process can be used with other surfaces including:
- cone
- cylinder
- plane
- torus

Each surface has its own unique parameterization, which must be understood before trying to trim it.

+++

## Close session
When you finish interacting with your modeling service, you should close the active server session. This frees resources wherever the service is running.

Close the server session.

```{code-cell} ipython3
modeler.close()
```
86 changes: 86 additions & 0 deletions src/ansys/geometry/core/connection/conversions.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,10 @@
Point as GRPCPoint,
Polygon as GRPCPolygon,
Surface as GRPCSurface,
SurfaceType as GRPCSurfaceType,
Tessellation,
TrimmedCurve as GRPCTrimmedCurve,
TrimmedSurface as GRPCTrimmedSurface,
)
from ansys.geometry.core.math.frame import Frame
from ansys.geometry.core.math.matrix import Matrix44
Expand All @@ -50,6 +52,7 @@
from ansys.geometry.core.shapes.curves.curve import Curve
from ansys.geometry.core.shapes.curves.ellipse import Ellipse
from ansys.geometry.core.shapes.curves.line import Line
from ansys.geometry.core.shapes.surfaces import TrimmedSurface
from ansys.geometry.core.shapes.surfaces.cone import Cone
from ansys.geometry.core.shapes.surfaces.cylinder import Cylinder
from ansys.geometry.core.shapes.surfaces.plane import PlaneSurface
Expand Down Expand Up @@ -576,3 +579,86 @@ def trimmed_curve_to_grpc_trimmed_curve(curve: "TrimmedCurve") -> GRPCTrimmedCur
interval_start=i_start,
interval_end=i_end,
)


def surface_to_grpc_surface(surface: Surface) -> tuple[GRPCSurface, GRPCSurfaceType]:
"""Convert a ``Surface`` object to a surface gRPC message.
Parameters
----------
surface : Surface
Surface to convert.
Returns
-------
GRPCSurface
Return ``Surface`` as a ``ansys.api.geometry.Surface`` message.
GRPCSurfaceType
Return the grpc surface type of ``Surface``.
"""
grpc_surface = None
surface_type = None
origin = point3d_to_grpc_point(surface.origin)
reference = unit_vector_to_grpc_direction(surface.dir_x)
axis = unit_vector_to_grpc_direction(surface.dir_z)

if isinstance(surface, Plane):
grpc_surface = GRPCSurface(origin=origin, reference=reference, axis=axis)
surface_type = GRPCSurfaceType.SURFACETYPE_PLANE
elif isinstance(surface, Sphere):
grpc_surface = GRPCSurface(
origin=origin, reference=reference, axis=axis, radius=surface.radius.m
)
surface_type = GRPCSurfaceType.SURFACETYPE_SPHERE
elif isinstance(surface, Cylinder):
grpc_surface = GRPCSurface(
origin=origin, reference=reference, axis=axis, radius=surface.radius.m
)
surface_type = GRPCSurfaceType.SURFACETYPE_CYLINDER
elif isinstance(surface, Cone):
grpc_surface = GRPCSurface(
origin=origin,
reference=reference,
axis=axis,
radius=surface.radius.m,
half_angle=surface.half_angle.m,
)
surface_type = GRPCSurfaceType.SURFACETYPE_CONE
elif isinstance(surface, Torus):
grpc_surface = GRPCSurface(
origin=origin,
reference=reference,
axis=axis,
major_radius=surface.major_radius.m,
minor_radius=surface.minor_radius.m,
)
surface_type = GRPCSurfaceType.SURFACETYPE_TORUS

return grpc_surface, surface_type


def trimmed_surface_to_grpc_trimmed_surface(
trimmed_surface: TrimmedSurface,
) -> GRPCTrimmedSurface:
"""Convert a ``TrimmedSurface`` to a trimmed surface gRPC message.
Parameters
----------
trimmed_surface : TrimmedSurface
Surface to convert.
Returns
-------
GRPCTrimmedSurface
Geometry service gRPC ``TrimmedSurface`` message.
"""
surface_geometry, surface_type = surface_to_grpc_surface(trimmed_surface.geometry)

return GRPCTrimmedSurface(
surface=surface_geometry,
type=surface_type,
u_min=trimmed_surface.box_uv.interval_u.start,
u_max=trimmed_surface.box_uv.interval_u.end,
v_min=trimmed_surface.box_uv.interval_v.start,
v_max=trimmed_surface.box_uv.interval_v.end,
)
43 changes: 43 additions & 0 deletions src/ansys/geometry/core/designer/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
CreateExtrudedBodyRequest,
CreatePlanarBodyRequest,
CreateSphereBodyRequest,
CreateSurfaceBodyRequest,
CreateSweepingChainRequest,
CreateSweepingProfileRequest,
TranslateRequest,
Expand All @@ -57,6 +58,7 @@
point3d_to_grpc_point,
sketch_shapes_to_grpc_geometries,
trimmed_curve_to_grpc_trimmed_curve,
trimmed_surface_to_grpc_trimmed_surface,
unit_vector_to_grpc_direction,
)
from ansys.geometry.core.designer.beam import Beam, BeamProfile
Expand All @@ -76,6 +78,7 @@
from ansys.geometry.core.shapes.curves.circle import Circle
from ansys.geometry.core.shapes.curves.trimmed_curve import TrimmedCurve
from ansys.geometry.core.shapes.parameterization import Interval
from ansys.geometry.core.shapes.surfaces import TrimmedSurface
from ansys.geometry.core.sketch.sketch import Sketch
from ansys.geometry.core.typing import Real

Expand Down Expand Up @@ -895,6 +898,46 @@ def create_surface_from_face(self, name: str, face: Face) -> Body:
self._master_component.part.bodies.append(tb)
return Body(response.id, response.name, self, tb)

@protect_grpc
@check_input_types
@ensure_design_is_active
@min_backend_version(25, 1, 0)
def create_body_from_surface(self, name: str, trimmed_surface: TrimmedSurface) -> Body:
"""Create a surface body from a trimmed surface.
Notes
-----
It is possible to create a closed solid body (as opposed to an open surface body) with a
Sphere or Torus if they are untrimmed. This can be validated with `body.is_surface`.
Parameters
----------
name : str
User-defined label for the new surface body.
trimmed_surface : TrimmedSurface
Geometry for the new surface body.
Returns
-------
Body
Surface body.
"""
surface = trimmed_surface_to_grpc_trimmed_surface(trimmed_surface)
request = CreateSurfaceBodyRequest(
name=name,
parent=self.id,
trimmed_surface=surface,
)

self._grpc_client.log.debug(
f"Creating surface body from trimmed surface provided on {self.id}. Creating body..."
)
response = self._bodies_stub.CreateSurfaceBody(request)

tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=response.is_surface)
self._master_component.part.bodies.append(tb)
return Body(response.id, response.name, self, tb)

@check_input_types
@ensure_design_is_active
def create_coordinate_system(self, name: str, frame: Frame) -> CoordinateSystem:
Expand Down
1 change: 1 addition & 0 deletions src/ansys/geometry/core/shapes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@
from ansys.geometry.core.shapes.surfaces.surface import Surface
from ansys.geometry.core.shapes.surfaces.surface_evaluation import SurfaceEvaluation
from ansys.geometry.core.shapes.surfaces.torus import Torus
from ansys.geometry.core.shapes.surfaces.trimmed_surface import TrimmedSurface
1 change: 1 addition & 0 deletions src/ansys/geometry/core/shapes/surfaces/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@
from ansys.geometry.core.shapes.surfaces.surface import Surface
from ansys.geometry.core.shapes.surfaces.surface_evaluation import SurfaceEvaluation
from ansys.geometry.core.shapes.surfaces.torus import Torus
from ansys.geometry.core.shapes.surfaces.trimmed_surface import TrimmedSurface
71 changes: 70 additions & 1 deletion tests/integration/test_design.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,17 @@
Vector3D,
)
from ansys.geometry.core.misc import DEFAULT_UNITS, UNITS, Accuracy, Angle, Distance
from ansys.geometry.core.shapes import Circle, Ellipse, Interval, ParamUV
from ansys.geometry.core.shapes import (
Circle,
Cone,
Cylinder,
Ellipse,
Interval,
ParamUV,
Sphere,
Torus,
)
from ansys.geometry.core.shapes.box_uv import BoxUV
from ansys.geometry.core.sketch import Sketch
from ansys.tools.visualization_interface.utils.color import Color

Expand Down Expand Up @@ -2669,3 +2679,62 @@ def check_list_equality(lines, expected_lines):
" |---(body) nested_1_nested_1_comp_1_circle",
]
assert check_list_equality(lines, ref) is True


def test_surface_body_creation(modeler: Modeler):
"""Test surface body creation from trimmed surfaces."""
design = modeler.create_design("Design1")

# half sphere
surface = Sphere([0, 0, 0], 1)
trimmed_surface = surface.trim(BoxUV(Interval(0, np.pi * 2), Interval(0, np.pi / 2)))
body = design.create_body_from_surface("sphere", trimmed_surface)
assert len(design.bodies) == 1
assert body.is_surface
assert body.faces[0].area.m == pytest.approx(np.pi * 2)

# cylinder
surface = Cylinder([0, 0, 0], 1)
trimmed_surface = surface.trim(BoxUV(Interval(0, np.pi * 2), Interval(0, 1)))
body = design.create_body_from_surface("cylinder", trimmed_surface)

assert len(design.bodies) == 2
assert body.is_surface
assert body.faces[0].area.m == pytest.approx(np.pi * 2)

# cone
surface = Cone([0, 0, 0], 1, np.pi / 4)
trimmed_surface = surface.trim(BoxUV(Interval(0, np.pi * 2), Interval(surface.apex.z.m, 0)))
body = design.create_body_from_surface("cone", trimmed_surface)

assert len(design.bodies) == 3
assert body.is_surface
assert body.faces[0].area.m == pytest.approx(4.44288293816)

# half torus
surface = Torus([0, 0, 0], 2, 1)
trimmed_surface = surface.trim(BoxUV(Interval(0, np.pi), Interval(0, np.pi * 2)))
body = design.create_body_from_surface("torus", trimmed_surface)

assert len(design.bodies) == 4
assert body.is_surface
assert body.faces[0].area.m == pytest.approx(39.4784176044)

# SOLID BODIES

# sphere
surface = Sphere([0, 0, 0], 1)
trimmed_surface = surface.trim(BoxUV(Interval(0, np.pi * 2), Interval(-np.pi / 2, np.pi / 2)))
body = design.create_body_from_surface("sphere_solid", trimmed_surface)
assert len(design.bodies) == 5
assert not body.is_surface
assert body.faces[0].area.m == pytest.approx(np.pi * 4)

# torus
surface = Torus([0, 0, 0], 2, 1)
trimmed_surface = surface.trim(BoxUV(Interval(0, np.pi * 2), Interval(0, np.pi * 2)))
body = design.create_body_from_surface("torus_solid", trimmed_surface)

assert len(design.bodies) == 6
assert not body.is_surface
assert body.faces[0].area.m == pytest.approx(39.4784176044 * 2)

0 comments on commit 052448f

Please sign in to comment.