Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/nightly_docker_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,6 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: ${{ env.MAIN_PYTHON_VERSION }}
cache: 'pip'
cache-dependency-path: 'pyproject.toml'

- name: Set up headless display
uses: pyvista/setup-headless-display-action@v2
Expand Down
1 change: 1 addition & 0 deletions doc/changelog.d/1248.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
feat: revolve a sketch given an axis and an origin
Binary file added doc/source/_static/thumbnails/revolving.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 @@ -269,6 +269,7 @@ def intersphinx_pyansys_geometry(switcher_version: str):
"examples/03_modeling/boolean_operations": "_static/thumbnails/boolean_operations.png",
"examples/03_modeling/scale_map_mirror_bodies": "_static/thumbnails/scale_map_mirror_bodies.png", # noqa: E501
"examples/03_modeling/sweep_chain_profile": "_static/thumbnails/sweep_chain_profile.png",
"examples/03_modeling/revolving": "_static/thumbnails/revolving.png",
"examples/03_modeling/export_design": "_static/thumbnails/export_design.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 @@ -40,6 +40,7 @@ These examples demonstrate service-based modeling operations.
examples/03_modeling/boolean_operations.mystnb
examples/03_modeling/scale_map_mirror_bodies.mystnb
examples/03_modeling/sweep_chain_profile.mystnb
examples/03_modeling/revolving.mystnb
examples/03_modeling/export_design.mystnb

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

# Modeling: Revolving a sketch

This example shows how to use the ``revolve_sketch()`` method to
revolve a sketch around an axis to create a 3D body. You can also
specify the angle of revolution to create a partial body.

```{code-cell} ipython3
# Imports
from ansys.geometry.core import Modeler
from ansys.geometry.core.math import (
Plane,
Point2D,
Point3D,
UNITVECTOR3D_X,
UNITVECTOR3D_Z,
)
from ansys.geometry.core.misc import UNITS, Angle
from ansys.geometry.core.sketch import Sketch

```

```{code-cell} ipython3
# Initialize the modeler for this example notebook
m = Modeler()
```

## Example: Creating a quarter of a donut

The following code snippets show how to use the ``revolve_sketch()`` function to create a
quarter of a 3D donut. The process involves defining a quarter of a circle as a profile
and then revolving it around the Z-axis to create a 3D body.

### Initialize the sketch design

Create a design sketch named ``quarter-donut``.

```{code-cell} ipython3
# Initialize the donut sketch design
design = m.create_design("quarter-donut")
```

### Define circle parameters

Set ``path_radius``, which represents the radius of the circular path that the profile
circle sweeps along, to ``5`` units.
Set ``profile_radius``, which represents the radius of the profile circle that sweeps
along the path to create the donut body, to ``2`` units.

```{code-cell} ipython3
# Donut parameters
path_radius = 5
profile_radius = 2
```

### Create the profile circle

Create a circle on the XZ plane centered at the coordinates ``(5, 0, 0)``
and use``profile_radius`` to define the radius. This circle serves as the
profile or cross-sectional shape of the donut.

```{code-cell} ipython3
# Create the circular profile on the XZ-plane centered at (5, 0, 0)
# with a radius of 2
plane_profile = Plane(
origin=Point3D([path_radius, 0, 0]),
direction_x=UNITVECTOR3D_X,
direction_y=UNITVECTOR3D_Z,
)
profile = Sketch(plane=plane_profile)
profile.circle(Point2D([0, 0]), profile_radius)

profile.plot()
```

### Perform the revolve operation

Revolve the profile circle around the Z axis to create a quarter of a donut body.
Set the angle of revolution to 90 degrees in the default direction, which is counterclockwise.

```{code-cell} ipython3
# Revolve the profile around the Z axis and center in the absolute origin
# for an angle of 90 degrees
design.revolve_sketch(
"quarter-donut-body",
sketch=profile,
axis=UNITVECTOR3D_Z,
angle=Angle(90, unit=UNITS.degrees),
rotation_origin=Point3D([0, 0, 0]),
)

design.plot()
```

### Perform a revolve operation with a negative angle of revolution

You can use a negative angle of revolution to create a quarter of a donut in the opposite direction. The following code snippet shows how to create a quarter of a donut in the clockwise direction. The same profile circle is used, but the angle of revolution is set to -90 degrees.

```{code-cell} ipython3
# Initialize the donut sketch design
design = m.create_design("quarter-donut-negative")

# Revolve the profile around the Z axis and center in the absolute origin
# for an angle of -90 degrees (clockwise)
design.revolve_sketch(
"quarter-donut-body-negative",
sketch=profile,
axis=UNITVECTOR3D_Z,
angle=Angle(-90, unit=UNITS.degrees),
rotation_origin=Point3D([0, 0, 0]),
)

design.plot()
```
4 changes: 2 additions & 2 deletions doc/source/examples/03_modeling/sweep_chain_profile.mystnb
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ defined by ``profile_radius``. This circle serves as the profile or cross-sectio
shape of the donut.

```{code-cell} ipython3
# Create the circlular profile on the XZ-plane centered at (5, 0, 0)
# Create the circular profile on the XZ plane centered at (5, 0, 0)
# with a radius of 2
plane_profile = Plane(direction_x=UNITVECTOR3D_X, direction_y=UNITVECTOR3D_Z)
profile = Sketch(plane=plane_profile)
Expand All @@ -90,7 +90,7 @@ Another circle, representing the path along which the profile circle is swept, i
the XY-plane centered at (0, 0, 0). The radius of this circle is defined by ``path_radius``.

```{code-cell} ipython3
# Create the circlular path on the XY-plane centered at (0, 0, 0) with radius 5
# Create the circular path 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))]
```

Expand Down
72 changes: 72 additions & 0 deletions src/ansys/geometry/core/designer/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@
from ansys.geometry.core.math.vector import UnitVector3D, Vector3D
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.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.sketch.sketch import Sketch
from ansys.geometry.core.typing import Real

Expand Down Expand Up @@ -588,6 +590,76 @@ def sweep_chain(
self._master_component.part.bodies.append(tb)
return Body(response.id, response.name, self, tb)

@min_backend_version(24, 2, 0)
@check_input_types
def revolve_sketch(
self,
name: str,
sketch: Sketch,
axis: Vector3D,
angle: Union[Quantity, Angle, Real],
rotation_origin: Point3D,
) -> Body:
"""
Create a solid body by revolving a sketch profile around an axis.

Notes
-----
It is important that the sketch plane origin is not coincident with the rotation
origin. If the sketch plane origin is coincident with the rotation origin, the
distance between the two points is zero, and the revolve operation fails.

Parameters
----------
name : str
User-defined label for the new solid body.
sketch : Sketch
Two-dimensional sketch source for the revolve.
axis : Vector3D
Axis of rotation for the revolve.
angle : Union[~pint.Quantity, Angle, Real]
Angle to revolve the solid body around the axis. The angle can be positive or negative.
rotation_origin : Point3D
Origin of the axis of rotation.

Returns
-------
Body
Revolved body from the given sketch.
"""
# Check that the sketch plane origin is not coincident with the rotation origin
if sketch.plane.origin == rotation_origin:
raise ValueError(
"The sketch plane origin is coincident with the rotation origin. "
+ "The distance between the points is zero, and the revolve operation will fail."
)

# Compute the distance between the rotation origin and the sketch plane
rotation_origin_to_sketch = sketch.plane.origin - rotation_origin
rotation_origin_to_sketch_as_vector = Vector3D(rotation_origin_to_sketch)
distance = Distance(
rotation_origin_to_sketch_as_vector.norm,
unit=rotation_origin_to_sketch.base_unit,
)

# Define the revolve path
circle = Circle(
rotation_origin,
radius=distance,
reference=rotation_origin_to_sketch_as_vector,
axis=axis,
)
angle = angle if isinstance(angle, Angle) else Angle(angle)
interval = (
Interval(0, angle.value.m_as(DEFAULT_UNITS.SERVER_ANGLE))
if angle.value.m >= 0
else Interval(angle.value.m_as(DEFAULT_UNITS.SERVER_ANGLE), 0)
)
path = circle.trim(interval)

# Create the revolved body by delegating to the sweep method
return self.sweep_sketch(name, sketch, [path])

@protect_grpc
@check_input_types
@ensure_design_is_active
Expand Down
2 changes: 0 additions & 2 deletions tests/integration/image_cache/results/.gitignore

This file was deleted.

63 changes: 59 additions & 4 deletions tests/integration/test_design.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,8 @@
UnitVector3D,
Vector3D,
)
from ansys.geometry.core.misc import DEFAULT_UNITS, UNITS, Accuracy, Distance
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.misc import DEFAULT_UNITS, UNITS, Accuracy, Angle, Distance
from ansys.geometry.core.shapes import Circle, Ellipse, Interval, ParamUV
from ansys.geometry.core.sketch import Sketch

from .conftest import FILES_DIR, skip_if_linux
Expand Down Expand Up @@ -2286,3 +2284,60 @@ def test_create_body_from_loft_profile(modeler: Modeler):
# check volume of body
# expected is 0 since it's not a closed surface
assert result.volume.m == 0


def test_revolve_sketch(modeler: Modeler):
"""Test revolving a circular profile for a quarter donut."""
# Initialize the donut sketch design
design = modeler.create_design("quarter-donut")

# Donut parameters
path_radius = 5
profile_radius = 2

# Create the circular profile on the XZ plane centered at (5, 0, 0)
# with a radius of 2
plane_profile = Plane(
origin=Point3D([path_radius, 0, 0]), direction_x=UNITVECTOR3D_X, direction_y=UNITVECTOR3D_Z
)
profile = Sketch(plane=plane_profile)
profile.circle(Point2D([0, 0]), profile_radius)

# Revolve the profile around the Z axis and center in the absolute origin
# for an angle of 90 degrees
body = design.revolve_sketch(
"donut-body",
sketch=profile,
axis=UNITVECTOR3D_Z,
angle=Angle(90, unit=UNITS.degrees),
rotation_origin=Point3D([0, 0, 0]),
)

assert body.is_surface == False
assert body.name == "donut-body"
assert np.isclose(body.volume.m, np.pi**2 * 2 * 5, rtol=1e-3) # quarter of a torus volume


def test_revolve_sketch_fail(modeler: Modeler):
"""Test demonstrating the failure of revolving a sketch when it is located in the
same origin."""
# Initialize the donut sketch design
design = modeler.create_design("revolve-fail")

# Create an XZ plane centered at (0, 0, 0)
plane_profile = Plane(
origin=Point3D([0, 0, 0]), direction_x=UNITVECTOR3D_X, direction_y=UNITVECTOR3D_Z
)
profile = Sketch(plane=plane_profile)

# Try revolving the profile...
with pytest.raises(
ValueError, match="The sketch plane origin is coincident with the rotation origin."
):
design.revolve_sketch(
"donut-body",
sketch=profile,
axis=UNITVECTOR3D_Z,
angle=Angle(90, unit=UNITS.degrees),
rotation_origin=Point3D([0, 0, 0]),
)
Loading