Skip to content

Commit 052448f

Browse files
jonahrbpyansys-ci-botRobPasMue
authored
feat: create body from surface (#1454)
Co-authored-by: pyansys-ci-bot <[email protected]> Co-authored-by: Roberto Pastor Muela <[email protected]>
1 parent f5f4a7a commit 052448f

File tree

10 files changed

+290
-1
lines changed

10 files changed

+290
-1
lines changed

doc/changelog.d/1454.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
create body from surface
103 KB
Loading

doc/source/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ def intersphinx_pyansys_geometry(switcher_version: str):
292292
"examples/03_modeling/export_design": "_static/thumbnails/export_design.png",
293293
"examples/03_modeling/design_tree": "_static/thumbnails/design_tree.png",
294294
"examples/03_modeling/service_colors": "_static/thumbnails/service_colors.png",
295+
"examples/03_modeling/surface_bodies": "_static/thumbnails/quarter_sphere.png",
295296
"examples/04_applied/01_naca_airfoils": "_static/thumbnails/naca_airfoils.png",
296297
"examples/04_applied/02_naca_fluent": "_static/thumbnails/naca_fluent.png",
297298
}

doc/source/examples.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ These examples demonstrate service-based modeling operations.
4747
examples/03_modeling/export_design.mystnb
4848
examples/03_modeling/design_tree.mystnb
4949
examples/03_modeling/service_colors.mystnb
50+
examples/03_modeling/surface_bodies.mystnb
5051

5152
Applied examples
5253
----------------
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
---
2+
jupytext:
3+
text_representation:
4+
extension: .mystnb
5+
format_name: myst
6+
format_version: 0.13
7+
jupytext_version: 1.16.4
8+
kernelspec:
9+
display_name: Python 3 (ipykernel)
10+
language: python
11+
name: python3
12+
---
13+
14+
# Modeling: Surface bodies and trimmed surfaces
15+
This example will show how to trim different surfaces, and how to use those surfaces to create surface bodies.
16+
17+
## Create a surface
18+
Create a sphere surface. This can be done without launching the modeler.
19+
20+
```{code-cell} ipython3
21+
from ansys.geometry.core.shapes.surfaces import Sphere
22+
from ansys.geometry.core.math import Point3D
23+
24+
surface = Sphere(origin=Point3D([0, 0, 0]), radius=1)
25+
```
26+
27+
Now get information on how the surface is defined and parameterized.
28+
29+
```{code-cell} ipython3
30+
surface.parameterization()
31+
```
32+
33+
## Trim the surface
34+
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.
35+
36+
```{code-cell} ipython3
37+
from ansys.geometry.core.shapes.box_uv import BoxUV
38+
from ansys.geometry.core.shapes.parameterization import Interval
39+
import math
40+
41+
trimmed_surface = surface.trim(BoxUV(range_u=Interval(0, math.pi), range_v=Interval(0, math.pi/2)))
42+
```
43+
44+
From a TrimmedSurface, you can always refer back to the underlying Surface if needed.
45+
46+
```{code-cell} ipython3
47+
trimmed_surface.geometry
48+
```
49+
50+
## Create a surface body
51+
52+
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.
53+
54+
```{code-cell} ipython3
55+
from ansys.geometry.core import launch_modeler
56+
57+
modeler = launch_modeler()
58+
print(modeler)
59+
```
60+
61+
```{code-cell} ipython3
62+
design = modeler.create_design("SurfaceBodyExample")
63+
body = design.create_body_from_surface("trimmed_sphere", trimmed_surface)
64+
design.plot()
65+
```
66+
67+
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.
68+
69+
This same process can be used with other surfaces including:
70+
- cone
71+
- cylinder
72+
- plane
73+
- torus
74+
75+
Each surface has its own unique parameterization, which must be understood before trying to trim it.
76+
77+
+++
78+
79+
## Close session
80+
When you finish interacting with your modeling service, you should close the active server session. This frees resources wherever the service is running.
81+
82+
Close the server session.
83+
84+
```{code-cell} ipython3
85+
modeler.close()
86+
```

src/ansys/geometry/core/connection/conversions.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@
3737
Point as GRPCPoint,
3838
Polygon as GRPCPolygon,
3939
Surface as GRPCSurface,
40+
SurfaceType as GRPCSurfaceType,
4041
Tessellation,
4142
TrimmedCurve as GRPCTrimmedCurve,
43+
TrimmedSurface as GRPCTrimmedSurface,
4244
)
4345
from ansys.geometry.core.math.frame import Frame
4446
from ansys.geometry.core.math.matrix import Matrix44
@@ -50,6 +52,7 @@
5052
from ansys.geometry.core.shapes.curves.curve import Curve
5153
from ansys.geometry.core.shapes.curves.ellipse import Ellipse
5254
from ansys.geometry.core.shapes.curves.line import Line
55+
from ansys.geometry.core.shapes.surfaces import TrimmedSurface
5356
from ansys.geometry.core.shapes.surfaces.cone import Cone
5457
from ansys.geometry.core.shapes.surfaces.cylinder import Cylinder
5558
from ansys.geometry.core.shapes.surfaces.plane import PlaneSurface
@@ -576,3 +579,86 @@ def trimmed_curve_to_grpc_trimmed_curve(curve: "TrimmedCurve") -> GRPCTrimmedCur
576579
interval_start=i_start,
577580
interval_end=i_end,
578581
)
582+
583+
584+
def surface_to_grpc_surface(surface: Surface) -> tuple[GRPCSurface, GRPCSurfaceType]:
585+
"""Convert a ``Surface`` object to a surface gRPC message.
586+
587+
Parameters
588+
----------
589+
surface : Surface
590+
Surface to convert.
591+
592+
Returns
593+
-------
594+
GRPCSurface
595+
Return ``Surface`` as a ``ansys.api.geometry.Surface`` message.
596+
GRPCSurfaceType
597+
Return the grpc surface type of ``Surface``.
598+
"""
599+
grpc_surface = None
600+
surface_type = None
601+
origin = point3d_to_grpc_point(surface.origin)
602+
reference = unit_vector_to_grpc_direction(surface.dir_x)
603+
axis = unit_vector_to_grpc_direction(surface.dir_z)
604+
605+
if isinstance(surface, Plane):
606+
grpc_surface = GRPCSurface(origin=origin, reference=reference, axis=axis)
607+
surface_type = GRPCSurfaceType.SURFACETYPE_PLANE
608+
elif isinstance(surface, Sphere):
609+
grpc_surface = GRPCSurface(
610+
origin=origin, reference=reference, axis=axis, radius=surface.radius.m
611+
)
612+
surface_type = GRPCSurfaceType.SURFACETYPE_SPHERE
613+
elif isinstance(surface, Cylinder):
614+
grpc_surface = GRPCSurface(
615+
origin=origin, reference=reference, axis=axis, radius=surface.radius.m
616+
)
617+
surface_type = GRPCSurfaceType.SURFACETYPE_CYLINDER
618+
elif isinstance(surface, Cone):
619+
grpc_surface = GRPCSurface(
620+
origin=origin,
621+
reference=reference,
622+
axis=axis,
623+
radius=surface.radius.m,
624+
half_angle=surface.half_angle.m,
625+
)
626+
surface_type = GRPCSurfaceType.SURFACETYPE_CONE
627+
elif isinstance(surface, Torus):
628+
grpc_surface = GRPCSurface(
629+
origin=origin,
630+
reference=reference,
631+
axis=axis,
632+
major_radius=surface.major_radius.m,
633+
minor_radius=surface.minor_radius.m,
634+
)
635+
surface_type = GRPCSurfaceType.SURFACETYPE_TORUS
636+
637+
return grpc_surface, surface_type
638+
639+
640+
def trimmed_surface_to_grpc_trimmed_surface(
641+
trimmed_surface: TrimmedSurface,
642+
) -> GRPCTrimmedSurface:
643+
"""Convert a ``TrimmedSurface`` to a trimmed surface gRPC message.
644+
645+
Parameters
646+
----------
647+
trimmed_surface : TrimmedSurface
648+
Surface to convert.
649+
650+
Returns
651+
-------
652+
GRPCTrimmedSurface
653+
Geometry service gRPC ``TrimmedSurface`` message.
654+
"""
655+
surface_geometry, surface_type = surface_to_grpc_surface(trimmed_surface.geometry)
656+
657+
return GRPCTrimmedSurface(
658+
surface=surface_geometry,
659+
type=surface_type,
660+
u_min=trimmed_surface.box_uv.interval_u.start,
661+
u_max=trimmed_surface.box_uv.interval_u.end,
662+
v_min=trimmed_surface.box_uv.interval_v.start,
663+
v_max=trimmed_surface.box_uv.interval_v.end,
664+
)

src/ansys/geometry/core/designer/component.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
CreateExtrudedBodyRequest,
3737
CreatePlanarBodyRequest,
3838
CreateSphereBodyRequest,
39+
CreateSurfaceBodyRequest,
3940
CreateSweepingChainRequest,
4041
CreateSweepingProfileRequest,
4142
TranslateRequest,
@@ -57,6 +58,7 @@
5758
point3d_to_grpc_point,
5859
sketch_shapes_to_grpc_geometries,
5960
trimmed_curve_to_grpc_trimmed_curve,
61+
trimmed_surface_to_grpc_trimmed_surface,
6062
unit_vector_to_grpc_direction,
6163
)
6264
from ansys.geometry.core.designer.beam import Beam, BeamProfile
@@ -76,6 +78,7 @@
7678
from ansys.geometry.core.shapes.curves.circle import Circle
7779
from ansys.geometry.core.shapes.curves.trimmed_curve import TrimmedCurve
7880
from ansys.geometry.core.shapes.parameterization import Interval
81+
from ansys.geometry.core.shapes.surfaces import TrimmedSurface
7982
from ansys.geometry.core.sketch.sketch import Sketch
8083
from ansys.geometry.core.typing import Real
8184

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

901+
@protect_grpc
902+
@check_input_types
903+
@ensure_design_is_active
904+
@min_backend_version(25, 1, 0)
905+
def create_body_from_surface(self, name: str, trimmed_surface: TrimmedSurface) -> Body:
906+
"""Create a surface body from a trimmed surface.
907+
908+
Notes
909+
-----
910+
It is possible to create a closed solid body (as opposed to an open surface body) with a
911+
Sphere or Torus if they are untrimmed. This can be validated with `body.is_surface`.
912+
913+
Parameters
914+
----------
915+
name : str
916+
User-defined label for the new surface body.
917+
trimmed_surface : TrimmedSurface
918+
Geometry for the new surface body.
919+
920+
Returns
921+
-------
922+
Body
923+
Surface body.
924+
"""
925+
surface = trimmed_surface_to_grpc_trimmed_surface(trimmed_surface)
926+
request = CreateSurfaceBodyRequest(
927+
name=name,
928+
parent=self.id,
929+
trimmed_surface=surface,
930+
)
931+
932+
self._grpc_client.log.debug(
933+
f"Creating surface body from trimmed surface provided on {self.id}. Creating body..."
934+
)
935+
response = self._bodies_stub.CreateSurfaceBody(request)
936+
937+
tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=response.is_surface)
938+
self._master_component.part.bodies.append(tb)
939+
return Body(response.id, response.name, self, tb)
940+
898941
@check_input_types
899942
@ensure_design_is_active
900943
def create_coordinate_system(self, name: str, frame: Frame) -> CoordinateSystem:

src/ansys/geometry/core/shapes/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,4 @@
3939
from ansys.geometry.core.shapes.surfaces.surface import Surface
4040
from ansys.geometry.core.shapes.surfaces.surface_evaluation import SurfaceEvaluation
4141
from ansys.geometry.core.shapes.surfaces.torus import Torus
42+
from ansys.geometry.core.shapes.surfaces.trimmed_surface import TrimmedSurface

src/ansys/geometry/core/shapes/surfaces/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,4 @@
2828
from ansys.geometry.core.shapes.surfaces.surface import Surface
2929
from ansys.geometry.core.shapes.surfaces.surface_evaluation import SurfaceEvaluation
3030
from ansys.geometry.core.shapes.surfaces.torus import Torus
31+
from ansys.geometry.core.shapes.surfaces.trimmed_surface import TrimmedSurface

tests/integration/test_design.py

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,17 @@
5656
Vector3D,
5757
)
5858
from ansys.geometry.core.misc import DEFAULT_UNITS, UNITS, Accuracy, Angle, Distance
59-
from ansys.geometry.core.shapes import Circle, Ellipse, Interval, ParamUV
59+
from ansys.geometry.core.shapes import (
60+
Circle,
61+
Cone,
62+
Cylinder,
63+
Ellipse,
64+
Interval,
65+
ParamUV,
66+
Sphere,
67+
Torus,
68+
)
69+
from ansys.geometry.core.shapes.box_uv import BoxUV
6070
from ansys.geometry.core.sketch import Sketch
6171
from ansys.tools.visualization_interface.utils.color import Color
6272

@@ -2669,3 +2679,62 @@ def check_list_equality(lines, expected_lines):
26692679
" |---(body) nested_1_nested_1_comp_1_circle",
26702680
]
26712681
assert check_list_equality(lines, ref) is True
2682+
2683+
2684+
def test_surface_body_creation(modeler: Modeler):
2685+
"""Test surface body creation from trimmed surfaces."""
2686+
design = modeler.create_design("Design1")
2687+
2688+
# half sphere
2689+
surface = Sphere([0, 0, 0], 1)
2690+
trimmed_surface = surface.trim(BoxUV(Interval(0, np.pi * 2), Interval(0, np.pi / 2)))
2691+
body = design.create_body_from_surface("sphere", trimmed_surface)
2692+
assert len(design.bodies) == 1
2693+
assert body.is_surface
2694+
assert body.faces[0].area.m == pytest.approx(np.pi * 2)
2695+
2696+
# cylinder
2697+
surface = Cylinder([0, 0, 0], 1)
2698+
trimmed_surface = surface.trim(BoxUV(Interval(0, np.pi * 2), Interval(0, 1)))
2699+
body = design.create_body_from_surface("cylinder", trimmed_surface)
2700+
2701+
assert len(design.bodies) == 2
2702+
assert body.is_surface
2703+
assert body.faces[0].area.m == pytest.approx(np.pi * 2)
2704+
2705+
# cone
2706+
surface = Cone([0, 0, 0], 1, np.pi / 4)
2707+
trimmed_surface = surface.trim(BoxUV(Interval(0, np.pi * 2), Interval(surface.apex.z.m, 0)))
2708+
body = design.create_body_from_surface("cone", trimmed_surface)
2709+
2710+
assert len(design.bodies) == 3
2711+
assert body.is_surface
2712+
assert body.faces[0].area.m == pytest.approx(4.44288293816)
2713+
2714+
# half torus
2715+
surface = Torus([0, 0, 0], 2, 1)
2716+
trimmed_surface = surface.trim(BoxUV(Interval(0, np.pi), Interval(0, np.pi * 2)))
2717+
body = design.create_body_from_surface("torus", trimmed_surface)
2718+
2719+
assert len(design.bodies) == 4
2720+
assert body.is_surface
2721+
assert body.faces[0].area.m == pytest.approx(39.4784176044)
2722+
2723+
# SOLID BODIES
2724+
2725+
# sphere
2726+
surface = Sphere([0, 0, 0], 1)
2727+
trimmed_surface = surface.trim(BoxUV(Interval(0, np.pi * 2), Interval(-np.pi / 2, np.pi / 2)))
2728+
body = design.create_body_from_surface("sphere_solid", trimmed_surface)
2729+
assert len(design.bodies) == 5
2730+
assert not body.is_surface
2731+
assert body.faces[0].area.m == pytest.approx(np.pi * 4)
2732+
2733+
# torus
2734+
surface = Torus([0, 0, 0], 2, 1)
2735+
trimmed_surface = surface.trim(BoxUV(Interval(0, np.pi * 2), Interval(0, np.pi * 2)))
2736+
body = design.create_body_from_surface("torus_solid", trimmed_surface)
2737+
2738+
assert len(design.bodies) == 6
2739+
assert not body.is_surface
2740+
assert body.faces[0].area.m == pytest.approx(39.4784176044 * 2)

0 commit comments

Comments
 (0)