diff --git a/doc/source/_static/thumbnails/101_getting_started.png b/doc/source/_static/thumbnails/101_getting_started.png new file mode 100644 index 0000000000..919ee7fde0 Binary files /dev/null and b/doc/source/_static/thumbnails/101_getting_started.png differ diff --git a/doc/source/_static/thumbnails/advanced_sketching_gears.png b/doc/source/_static/thumbnails/advanced_sketching_gears.png new file mode 100644 index 0000000000..90a789dd31 Binary files /dev/null and b/doc/source/_static/thumbnails/advanced_sketching_gears.png differ diff --git a/doc/source/conf.py b/doc/source/conf.py index 90b0760061..03d1389354 100755 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -155,12 +155,17 @@ ".mystnb": ["jupytext.reads", {"fmt": "mystnb"}], } nbsphinx_thumbnails = { - "examples/basic_usage": "_static/thumbnails/basic_usage.png", - "examples/dynamic_sketch_plane": "_static/thumbnails/dynamic_sketch_plane.png", - "examples/add_design_material": "_static/thumbnails/add_design_material.png", - "examples/plate_with_hole": "_static/thumbnails/plate_with_hole.png", - "examples/tessellation_usage": "_static/thumbnails/tessellation_usage.png", - "examples/design_organization": "_static/thumbnails/design_organization.png", + "examples/01_getting_started/01_math": "_static/thumbnails/101_getting_started.png", + "examples/01_getting_started/02_units": "_static/thumbnails/101_getting_started.png", + "examples/01_getting_started/03_sketching": "_static/thumbnails/101_getting_started.png", + "examples/01_getting_started/04_modeling": "_static/thumbnails/101_getting_started.png", + "examples/02_sketching/basic_usage": "_static/thumbnails/basic_usage.png", + "examples/02_sketching/dynamic_sketch_plane": "_static/thumbnails/dynamic_sketch_plane.png", + "examples/02_sketching/advanced_sketching_gears": "_static/thumbnails/advanced_sketching_gears.png", # noqa: E501 + "examples/03_modeling/add_design_material": "_static/thumbnails/add_design_material.png", + "examples/03_modeling/plate_with_hole": "_static/thumbnails/plate_with_hole.png", + "examples/03_modeling/tessellation_usage": "_static/thumbnails/tessellation_usage.png", + "examples/03_modeling/design_organization": "_static/thumbnails/design_organization.png", } nbsphinx_epilog = """ ---- diff --git a/doc/source/examples.rst b/doc/source/examples.rst index dc8cf44ea9..b24628619f 100644 --- a/doc/source/examples.rst +++ b/doc/source/examples.rst @@ -3,34 +3,36 @@ Examples These examples demonstrate the behavior and usage of PyGeometry. -Math and sketch examples ------------------------- -These examples demonstrate math operations on geometric objects -and basic sketching capabilities. +PyGeometry 101 demos +-------------------- +These examples demonstrate basic demos of different operations you +can perform with PyGeometry. .. nbgallery:: - examples/basic_usage.mystnb - -Service-based examples ----------------------- + examples/01_getting_started/01_math.mystnb + examples/01_getting_started/02_units.mystnb + examples/01_getting_started/03_sketching.mystnb + examples/01_getting_started/04_modeling.mystnb -These examples demonstrate service-based operations. +Sketching examples +------------------ +These examples demonstrate math operations on geometric objects +and sketching capabilities - combined with server-based operations. .. nbgallery:: - examples/add_design_material.mystnb - examples/plate_with_hole.mystnb - examples/tessellation_usage.mystnb - examples/design_organization.mystnb - -Advanced sketching features ---------------------------- + examples/02_sketching/basic_usage.mystnb + examples/02_sketching/dynamic_sketch_plane.mystnb + examples/02_sketching/advanced_sketching_gears.mystnb -These examples demonstrate advanced sketching features - combined with -server-based operations. +Modeling examples +----------------- +These examples demonstrate service-based modeling operations. .. nbgallery:: - examples/dynamic_sketch_plane.mystnb - examples/advanced_sketching_gears.mystnb + examples/03_modeling/add_design_material.mystnb + examples/03_modeling/plate_with_hole.mystnb + examples/03_modeling/tessellation_usage.mystnb + examples/03_modeling/design_organization.mystnb diff --git a/doc/source/examples/01_getting_started/01_math.mystnb b/doc/source/examples/01_getting_started/01_math.mystnb new file mode 100644 index 0000000000..76f8e9f6a4 --- /dev/null +++ b/doc/source/examples/01_getting_started/01_math.mystnb @@ -0,0 +1,165 @@ +--- +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 +--- + +# PyGeometry 101: math + +The ``math`` module is the foundation of PyGeometry. This foundation is built on top of +[NumPy](https://numpy.org/), one of the most renowned mathematical Python libraries. + +In this demo, some of the main PyGeometry math objects are shown +and it is shown why they are important prior to doing more exciting things in PyGeometry. + ++++ + +## Perform required imports + +Start by performing the required imports. + +```{code-cell} ipython3 +import numpy as np + +from ansys.geometry.core.math import Plane, Point2D, Point3D, Vector2D, Vector3D, UnitVector3D +``` + +### ``Point`` and ``Vector`` + +Everything starts with ``Point`` and ``Vector``, which can each be defined in a 2D or 3D form. +These objects inherit from NumPy's ``ndarray``, providing them with enhanced functionalities. +It is very important to remember to pass in the arguments as a list (that is, with brackets ``[ ]``) +when creating these objects. + +``` +Point3D([x, y, z]) +Point2D([x, y]) + +Vector3D([x, y, z]) +Vector2D([x, y]) +``` + +You can perform standard mathematical operations on these objects like the following. + +```{code-cell} ipython3 +vec_1 = Vector3D([1,0,0]) # x-vector +vec_2 = Vector3D([0,1,0]) # y-vector + +print("Sum of vectors [1, 0, 0] + [0, 1, 0]:") +print(vec_1 + vec_2) # sum + +print("\nDot product of vectors [1, 0, 0] * [0, 1, 0]:") +print(vec_1 * vec_2) # dot + +print("\nCross product of vectors [1, 0, 0] % [0, 1, 0]:") +print(vec_1 % vec_2) # cross +``` + +You can also create a ``Vector`` from 2 points. + +```{code-cell} ipython3 +p1 = Point3D([12.4, 532.3, 89]) +p2 = Point3D([-5.7, -67.4, 46.6]) + +vec_3 = Vector3D.from_points(p1, p2) +vec_3 +``` + +You can normalize any vector to create a unit vector, also sometimes known as a ``Direction``. + +```{code-cell} ipython3 +print("Magnitude of vec_3:") +print(vec_3.magnitude) + +print("\nNormalized vec_3:") +print(vec_3.normalize()) + +print("\nNew magnitude:") +print(vec_3.normalize().magnitude) +``` + +There is also a ``UnitVector`` class that will automatically normalize the input. + +```{code-cell} ipython3 +uv = UnitVector3D([1,1,1]) +uv +``` + +Here are a few more convenient mathematical operations. + +```{code-cell} ipython3 +v1 = Vector3D([1, 0, 0]) +v2 = Vector3D([0, 1, 0]) + +print("Vectors are perpendicular:") +print(v1.is_perpendicular_to(v2)) + +print("\nVectors are parallel:") +print(v1.is_parallel_to(v2)) + +print("\nVectors are opposite:") +print(v1.is_opposite(v2)) + +print("\nAngle between vectors:") +print(v1.get_angle_between(v2)) +print(f"{np.pi / 2} == pi/2") +``` + ++++ + +### ``Plane`` objects + +``Plane`` objects also become very important once we get into creating sketches and bodies. It is defined by: +- An ``origin``, which consists of a ``Point3D``. +- Two directions which are ``direction_x`` and ``direction_y``, both of them ``UnitVector3D``objects. + +If no direction vectors are provided, the ``Plane`` will default to the XY plane. + +```{code-cell} ipython3 +plane = Plane(Point3D([0,0,0])) # XY plane + +print("(1, 2, 0) is in XY plane:") +print(plane.is_point_contained(Point3D([1, 2, 0]))) # True + +print("\n(0, 0, 5) is in XY plane:") +print(plane.is_point_contained(Point3D([0, 0, 5]))) # False +``` + ++++ + +## Parametric evaluations + +Parametric evaluations for some curves and surfaces are also implemented. Here is a brief example for a sphere. + +```{code-cell} ipython3 +from ansys.geometry.core.primitives.sphere import Sphere, SphereEvaluation +from ansys.geometry.core.math import Point3D +from ansys.geometry.core.misc import Distance + +sphere = Sphere(Point3D([0,0,0]), Distance(1)) # radius = 1 + +eval = sphere.project_point(Point3D([1,1,1])) + +print("U Parameter:") +print(eval.parameter.u) + +print("\nV Parameter:") +print(eval.parameter.v) +``` + +```{code-cell} ipython3 +print("Point on the sphere:") +eval.position() +``` + +```{code-cell} ipython3 +print("Normal to the surface of the sphere at the evaluation position:") +eval.normal() +``` diff --git a/doc/source/examples/01_getting_started/02_units.mystnb b/doc/source/examples/01_getting_started/02_units.mystnb new file mode 100644 index 0000000000..85fa7a4454 --- /dev/null +++ b/doc/source/examples/01_getting_started/02_units.mystnb @@ -0,0 +1,196 @@ +--- +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 +--- + +# PyGeometry 101: units + +PyGeometry has the capability of handling units inside the source code. In order to do so, +a third-party open-source software which is being used in other PyAnsys libraries +called [Pint](https://pint.readthedocs.io/en/stable/) is used. + +In the following code sections it is shown how to operate with units inside +the PyGeometry codebase and how to create objects with different units. + +```{code-cell} ipython3 +# With this line of code, we will import the units handler - pint.util.UnitRegistry +# +# See docs https://pint.readthedocs.io/en/stable/api/base.html?highlight=UnitRegistry#most-important-classes + +from ansys.geometry.core.misc import UNITS +``` + +Now, with this ``UnitRegistry`` object called ``UNITS``, we will create +``Quantity`` objects. A ``Quantity`` object is simply a container class with two core elements: + +- A number +- A unit + +``Quantity`` objects also have convenience methods for: transforming to different units, comparing +magnitudes/values, units, etc. For more information on their functioning please refer to the +[pint.Quantity](https://pint.readthedocs.io/en/stable/api/base.html#most-important-classes) +docs or the [Pint tutorial](https://pint.readthedocs.io/en/stable/getting/tutorial.html?#tutorial). + +```{code-cell} ipython3 +from pint import Quantity + +a = Quantity(10, UNITS.mm) + +print(f"Object a is a pint.Quantity: {a}") + +print("Let's request its magnitude in different ways (accessor methods):") +print(f"Magnitude: {a.m}.") +print(f"Also magnitude: {a.magnitude}.") + +print("Let's request its units in different ways (accessor methods):") +print(f"Units: {a.u}.") +print(f"Also units: {a.units}.") + +# Quantities can also be compared between different units +# You can also build Quantity objects as follows: +a2 = 10 * UNITS.mm +print(f"Comparing quantities built differently: {a == a2}") + +# Quantities can also be compared between different units +a2_diff_units = 1 * UNITS.cm +print(f"Comparing quantities with different units: {a == a2_diff_units}") +``` + +The way PyGeometry objects work is by returning ``Quantity`` objects whenever that +property requested has a physical meaning. For example, let's see it on ``Point3D`` objects. + +```{code-cell} ipython3 +from ansys.geometry.core.math import Point3D + +point_a = Point3D([1,2,4]) +print("========================= Point3D([1,2,4]) ========================") +print(f"Our Point3D is a numpy.ndarray in SI units: {point_a}.") +print(f"However, if we request each of the coordinates individually...\n") +print(f"X Coordinate: {point_a.x}") +print(f"Y Coordinate: {point_a.y}") +print(f"Z Coordinate: {point_a.z}\n") + +# Now, let's store the information with different units... +point_a_km = Point3D([1,2,4], unit=UNITS.km) +print("================= Point3D([1,2,4], unit=UNITS.km) =================") +print(f"Our Point3D is a numpy.ndarray in SI units: {point_a_km}.") +print(f"However, if we request each of the coordinates individually...\n") +print(f"X Coordinate: {point_a_km.x}") +print(f"Y Coordinate: {point_a_km.y}") +print(f"Z Coordinate: {point_a_km.z}\n") + +# These points, though they are in different units, they can be added together +res = point_a + point_a_km + +print("=================== res = point_a + point_a_km ====================") +print(f"numpy.ndarray: {res}") +print(f"X Coordinate: {res.x}") +print(f"Y Coordinate: {res.y}") +print(f"Z Coordinate: {res.z}") +``` + +PyGeometry has also implemented the concept of **default units**. Let's have a look at them. + +```{code-cell} ipython3 +from ansys.geometry.core.misc import DEFAULT_UNITS + +print("=== Default unit length ===") +print(DEFAULT_UNITS.LENGTH) + +print("=== Default unit angle ===") +print(DEFAULT_UNITS.ANGLE) +``` + +Also, it is important to differentiate between *client-side* default units +and *server-side* ones. Users are able to control both of them. + +```{code-cell} ipython3 +print("=== Default server unit length ===") +print(DEFAULT_UNITS.SERVER_LENGTH) +``` + +Having default units allows us to do the following, for example. + +```{code-cell} ipython3 +from ansys.geometry.core.math import Point2D +from ansys.geometry.core.misc import DEFAULT_UNITS + +DEFAULT_UNITS.LENGTH = UNITS.mm + +point_2d_default_units = Point2D([3, 4]) +print("This is a Point2D with default units") +print(f"X Coordinate: {point_2d_default_units.x}") +print(f"Y Coordinate: {point_2d_default_units.y}") +print(f"numpy.ndarray value: {point_2d_default_units}") + +# Reverting back to original default units +DEFAULT_UNITS.LENGTH = UNITS.m +``` + +PyGeometry also has certain auxiliary classes implemented that provide proper +unit checking when assigning value. Although they are more intended for internal use +of the library, they can also be defined by users. Let's have a look at them. + +```{code-cell} ipython3 +from ansys.geometry.core.misc import Angle, Distance +``` + +Let's start with ``Distance``. The main difference between a ``Quantity`` object +(that is, ``from pint import Quantity``) and a ``Distance``, relies on the fact that +there is an active check on the units passed (in case they are not the default ones). +Let's do some trials. + +```{code-cell} ipython3 +radius = Distance(4) +print(f"We now have a radius of {radius.value}.") + +# Let's try reassigning the value of the distance +radius.value = 7 * UNITS.cm +print(f"After reassignment, we now have a radius of {radius.value}.") + + +# We could also change its units if desired +radius.unit = UNITS.cm +print(f"After changing its units, we now have a radius of {radius.value}.") +``` + +Let's try doing some unreasonable operations now... These will throw out errors. + +```{code-cell} ipython3 +try: + radius.value = 3 * UNITS.degrees +except TypeError as err: + print(f"Error raised: {err}") +``` + +```{code-cell} ipython3 +try: + radius.unit = UNITS.fahrenheit +except TypeError as err: + print(f"Error raised: {err}") +``` + +The same behavior applies to the ``Angle`` object. Let's do some simple trials. + +```{code-cell} ipython3 +import numpy as np + +rotation_angle = Angle(np.pi / 2) +print(f"We now have a rotation angle of {rotation_angle.value}.") + +# Let's try reassigning the value of the distance +rotation_angle.value = 7 * UNITS.degrees +print(f"After reassignment, we now have a rotation angle of {rotation_angle.value}.") + +# We could also change its units if desired +rotation_angle.unit = UNITS.degrees +print(f"After changing its units, we now have a rotation angle of {rotation_angle.value}.") +``` diff --git a/doc/source/examples/01_getting_started/03_sketching.mystnb b/doc/source/examples/01_getting_started/03_sketching.mystnb new file mode 100644 index 0000000000..23e4cf1f81 --- /dev/null +++ b/doc/source/examples/01_getting_started/03_sketching.mystnb @@ -0,0 +1,112 @@ +--- +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 +--- + +# PyGeometry 101: sketching + +PyGeometry allows the building of powerful dynamic sketches without communicating +to the Geometry service server. In this example, a demo on how to build these sketches is shown. + +```{code-cell} ipython3 +from pint import Quantity + +from ansys.geometry.core.math import Plane, Point2D, Point3D, Vector3D +from ansys.geometry.core.misc import UNITS +from ansys.geometry.core.sketch import Sketch +``` + +The ``Sketch`` object is the starting point. Once it is created, you can start +dynamically adding various curves to the sketch. Some of the available curves are +listed below. + +- ``arc`` +- ``box`` +- ``circle`` +- ``ellipse`` +- ``gear`` +- ``polygon`` +- ``segment`` +- ``slot`` +- ``trapezoid`` +- ``triangle`` + +```{code-cell} ipython3 +# Create a Sketch - Box + +sketch = Sketch() + +sketch.segment(Point2D([0,0]), Point2D([0,1])) +sketch.segment(Point2D([0,1]), Point2D([1,1])) +sketch.segment(Point2D([1,1]), Point2D([1,0])) +sketch.segment(Point2D([1,0]), Point2D([0,0])) + +sketch.plot() +``` + +A **fluent functional-style sketching API** is also implemented, where you +can append curves to the sketch with the idea of "never picking up your pen." + +```{code-cell} ipython3 +# Fluent Sketching API - Box + +sketch = Sketch() + +( + sketch.segment(Point2D([0,0]), Point2D([0,1])) + .segment_to_point(Point2D([1,1])) + .segment_to_point(Point2D([1,0])) + .segment_to_point(Point2D([0,0])) +) + +sketch.plot() +``` + +By default, a ``Sketch`` uses the XY-plane. You can instead define +your own ``Plane`` based on an ``origin``, ``direction_x``, and ``direction_y``. + +```{code-cell} ipython3 +# Use a custom plane - Box + +plane = Plane(origin=Point3D([0,0,0]), direction_x=Vector3D([1,2,-1]), direction_y=Vector3D([1,0,1])) + +sketch = Sketch(plane) + +sketch.box(Point2D([0,0]), 1, 1) + +sketch.plot() +``` + +Together, all these simple concepts can be combined to create +powerful sketches. It is up to you to be creative! + +```{code-cell} ipython3 +# Complex Fluent API Sketch - PCB + +sketch = Sketch() + +( + sketch.segment(Point2D([0, 0], unit=UNITS.mm), Point2D([40, 1], unit=UNITS.mm), "LowerEdge") + .arc_to_point(Point2D([41.5, 2.5], unit=UNITS.mm), Point2D([40, 2.5], unit=UNITS.mm), tag="SupportedCorner") + .segment_to_point(Point2D([41.5, 5], unit=UNITS.mm)) + .arc_to_point(Point2D([43, 6.5], unit=UNITS.mm), Point2D([43, 5], unit=UNITS.mm), True) + .segment_to_point(Point2D([55, 6.5], unit=UNITS.mm)) + .arc_to_point(Point2D([56.5, 8], unit=UNITS.mm), Point2D([55, 8], unit=UNITS.mm)) + .segment_to_point(Point2D([56.5, 35], unit=UNITS.mm)) + .arc_to_point(Point2D([55, 36.5], unit=UNITS.mm), Point2D([55, 35], unit=UNITS.mm)) + .segment_to_point(Point2D([0, 36.5], unit=UNITS.mm)) + .segment_to_point(Point2D([0, 0], unit=UNITS.mm)) + .circle(Point2D([4, 4], UNITS.mm), Quantity(1.5, UNITS.mm), "Anchor1") + .circle(Point2D([51, 34.5], UNITS.mm), Quantity(1.5, UNITS.mm), "Anchor2") +) + +sketch.plot() +``` \ No newline at end of file diff --git a/doc/source/examples/01_getting_started/04_modeling.mystnb b/doc/source/examples/01_getting_started/04_modeling.mystnb new file mode 100644 index 0000000000..6104fa5c43 --- /dev/null +++ b/doc/source/examples/01_getting_started/04_modeling.mystnb @@ -0,0 +1,269 @@ +--- +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 +--- + +# PyGeometry 101: modeling + +Once the mathematical constructs, units and sketching capabilities of PyGeometry are clear, +let's dive into the modeling capabilities available with PyGeometry. + ++++ + +PyGeometry is a Python client that connects to a modeling service. The following modeling +services are available for connection: + +- ``DMS``: a **Windows-based modeling service** which has been containerized to ease + distribution, execution and remotability operations. +- ``Geometry service``: this is the **Linux-based approach** of the ``DMS``. + It is currently under development. +- ``Discovery/SpaceClaim``: PyGeometry is also capable of connecting to a running session + of SpaceClaim or Discovery. Though this is not the main use-case, it is also possible. + Performance will not be as high as with the ``DMS`` or the ``Geometry service`` due to + the graphical interface, but it might be useful for some users. + ++++ + +Until now, all the operations performed with PyGeometry did not require communication +with the modeling service. From this point onwards, we will need a modelling service +available. In order to spawn a modeling service session, we have to do the following: + +```{code-cell} ipython3 +from ansys.geometry.core import launch_modeler + +# Let's start a modeler session +modeler = launch_modeler() +print(modeler) +``` + +Users are also allowed to spawn their own services and connect to them. For details on how +to connect to an existing service, read through the +[Modeler API](https://geometry.docs.pyansys.com/dev/autoapi/ansys/geometry/core/modeler/index.html#ansys.geometry.core.modeler.Modeler) + ++++ + +The way the class architecture is implemented is the following: + +* ``Modeler``: this is the handler object for the active service session. It allows users + to connect to an existing service by passing in a ``host`` and a ``port``. It also + allows to create ``Design`` objects, which is where the actual modeling will take + place. Documentation for [Modeler API](https://geometry.docs.pyansys.com/dev/autoapi/ansys/geometry/core/modeler/index.html#ansys.geometry.core.modeler.Modeler) + can be found in the previous link. +* ``Design``: this is the root object of our assembly/tree. A ``Design`` is also a + ``Component``, but with enhanced functionalities such as: creating named selection, + adding materials, handling beam profiles etc. Documentation for [Design API](https://geometry.docs.pyansys.com/release/0.2/autoapi/ansys/geometry/core/designer/design/index.html#ansys.geometry.core.designer.design.Design) + can be found in the previous link. +* ``Component``: one of the main objects for modeling purposes. ``Component`` objects + allow users to create bodies, sub-components, beams, design points, planar surfaces etc. + Documentation for [Component API](https://geometry.docs.pyansys.com/release/0.2/autoapi/ansys/geometry/core/designer/component/index.html#ansys.geometry.core.designer.component.Component) + can be found in the previous link. + +Let's start playing around with them! + ++++ + +First of all, we have to create a ``Sketch``. + +```{code-cell} ipython3 +from ansys.geometry.core.sketch import Sketch +from ansys.geometry.core.math import Point2D +from ansys.geometry.core.misc import UNITS, Distance + +outer_hole_radius = Distance(0.5, UNITS.m) + +sketch = Sketch() +( + sketch.segment(start=Point2D([-4, 5], unit=UNITS.m), end=Point2D([4, 5], unit=UNITS.m)) + .segment_to_point(end=Point2D([4, -5], unit=UNITS.m)) + .segment_to_point(end=Point2D([-4, -5], unit=UNITS.m)) + .segment_to_point(end=Point2D([-4, 5], unit=UNITS.m)) + .box( + center=Point2D([0, 0], unit=UNITS.m), + width=Distance(3, UNITS.m), + height=Distance(3, UNITS.m), + ) + .circle(center=Point2D([3, 4], unit=UNITS.m), radius=outer_hole_radius) + .circle(center=Point2D([-3, -4], unit=UNITS.m), radius=outer_hole_radius) + .circle(center=Point2D([-3, 4], unit=UNITS.m), radius=outer_hole_radius) + .circle(center=Point2D([3, -4], unit=UNITS.m), radius=outer_hole_radius) +) + +# Let's plot the sketch first +sketch.plot() +``` + +Now that we have our sketch ready to be extruded, let's do some modeling operations. + +```{code-cell} ipython3 +# We first start by creating the Design +design = modeler.create_design("ModelingDemo") + +# We will now create a body directly on our design by extruding our sketch +body = design.extrude_sketch( + name="Design_Body", sketch=sketch, distance=Distance(80, unit=UNITS.cm) +) + +# Let's plot our body! +design.plot() +``` + +It has been shown how to extrude a ``Sketch`` object and create a ``Body`` +directly on our ``Design``. Let's investigate our ``Body`` object now. + +```{code-cell} ipython3 +# We can request its faces, edges, volume... +faces = body.faces +edges = body.edges +volume = body.volume + +print(f"This is body {body.name} with id (server-side): {body.id}.") +print(f"This body has {len(faces)} faces and {len(edges)} edges.") +print(f"The body volume is {volume}.") +``` + +Other operations can be performed as well such as adding a midsurface offset +and thickness (only for planar bodies), imprinting curves, assign materials, +translate, copy... + +For example, let's try doing a copy of our object on a new subcomponent +and translating it. + +```{code-cell} ipython3 +from ansys.geometry.core.math import UNITVECTOR3D_X + +# First, create a new component +comp = design.add_component("Component") + +# Now, let's do a copy of our body that belongs to this new component +body_copy = body.copy(parent=comp, name="Design_Component_Body") + +# Finally, let's displace this new body by a certain distance (10m) in a certain direction (X-axis) +body_copy.translate(direction=UNITVECTOR3D_X, distance=Distance(10, unit=UNITS.m)) + +# Let's plot the result of the entire design +design.plot() +``` + +Let's show now how to create and assign materials to the bodies created. + +```{code-cell} ipython3 +from pint import Quantity + +from ansys.geometry.core.materials import Material, MaterialProperty, MaterialPropertyType + +# Let's define some general properties for our material +density = Quantity(125, 10 * UNITS.kg / (UNITS.m * UNITS.m * UNITS.m)) +poisson_ratio = Quantity(0.33, UNITS.dimensionless) +tensile_strength = Quantity(45) # WARNING: If no units are defined, +# it will assume the magnitude is in the expected units by the server + +# Once your material properties are defined you can easily create a material +material = Material( + "steel", + density, + [MaterialProperty(MaterialPropertyType.POISSON_RATIO, "PoissonRatio", poisson_ratio)], +) + +# If you forgot to add a property, or you want to overwrite its value, you can still +# add properties to your created material +material.add_property( + type=MaterialPropertyType.TENSILE_STRENGTH, name="TensileProp", quantity=tensile_strength +) + +# Once your material is properly defined, we will send it to the server. +# This material can then be reused by different objects +design.add_material(material) + +# And now, let's assign our material to our existing bodies +body.assign_material(material) +body_copy.assign_material(material) +``` + +Materials, as of now, do not have any impact on the visualization when plotting +is requested - this could be a nice feature to be added. Changes can +be observed if the final assembly is opened on SpaceClaim or Discovery. + ++++ + +Another interesting feature is the creation of ``NamedSelections``. +This is possible by means of the ``Design`` object. For example, let's create a +``NamedSelection`` with some of the faces of our previous ``body`` and the ``body`` itself. + +```{code-cell} ipython3 +# Let's create the named selection +faces = body.faces +ns = design.create_named_selection("MyNamedSelection", bodies=[body], faces=[faces[0], faces[-1]]) +print(f"This is a named selection called {ns.name} with id (server-side): {ns.id}.") +``` + +Deletion operations for bodies, named selections and components are also possible, +always from the scope expected. For example, if an attempt to delete the original body +from a component that has no ownership over it (i.e our ``comp`` object) this will fail. +If the same attempt is performed from the ``design`` object, this would work. Let's try it out. + +```{code-cell} ipython3 +# If we try deleting this body from an "unauthorized" component... this will not be allowed +comp.delete_body(body) +print(f"Is our body alive? {body.is_alive}") + +# And if we request the plotting of the entire design we can still see it. +design.plot() +``` + +```{code-cell} ipython3 +# Since the body belongs to the ``design`` object and not the ``comp`` object, let's +# delete it from its adequate location +design.delete_body(body) +print(f"Is our body alive? {body.is_alive}") + +# And if we request the plotting of the entire design it is no longer visible. +design.plot() +``` + +Finally, once the modeling operations have finalized, users can request their files +in different formats. The supported formats by the ``DMS`` are shown +[here](https://geometry.docs.pyansys.com/dev/autoapi/ansys/geometry/core/designer/design/index.html#ansys.geometry.core.designer.design.DesignFileFormat). + +Let's try exporting it into the different formats. + +```{code-cell} ipython3 +import os +from pathlib import Path + +from ansys.geometry.core.designer import DesignFileFormat + +# Path to our downloads directory +file_dir = Path(os.getcwd(), "downloads") +file_dir.mkdir(parents=True, exist_ok=True) + +# Download the model in different formats +design.download(file_location=Path(file_dir, "ModelingDemo.scdocx"), format=DesignFileFormat.SCDOCX) +design.download(file_location=Path(file_dir, "ModelingDemo.fmd"), format=DesignFileFormat.FMD) +``` + +More features of each of these objects will be shown in upcoming demos. + ++++ + +Finally, it is highly recommended once you finish interacting with your modeling +service, you close the active server session. This will allow to free resources +wherever the service is running on. + +In order to close the session, do as follows. + +```{code-cell} ipython3 +# Closing our modeling service session +modeler.close() +``` + +Beware that if the service session already existed (that is, it was not launched by +the current client session), any attempt to close it will not succeed. Users would +have to close such services manually. This is a safe-guard for user-spawned services. diff --git a/doc/source/examples/advanced_sketching_gears.mystnb b/doc/source/examples/02_sketching/advanced_sketching_gears.mystnb similarity index 98% rename from doc/source/examples/advanced_sketching_gears.mystnb rename to doc/source/examples/02_sketching/advanced_sketching_gears.mystnb index 980232e349..1913ce9390 100644 --- a/doc/source/examples/advanced_sketching_gears.mystnb +++ b/doc/source/examples/02_sketching/advanced_sketching_gears.mystnb @@ -11,7 +11,7 @@ kernelspec: name: python3 --- -# Advanced sketching: gears +# Sketching: parametric sketching for gears This example describes how to use gear sketching shapes from PyGeometry. diff --git a/doc/source/examples/basic_usage.mystnb b/doc/source/examples/02_sketching/basic_usage.mystnb similarity index 99% rename from doc/source/examples/basic_usage.mystnb rename to doc/source/examples/02_sketching/basic_usage.mystnb index 5610c7b1b2..f6e3bf5cce 100644 --- a/doc/source/examples/basic_usage.mystnb +++ b/doc/source/examples/02_sketching/basic_usage.mystnb @@ -11,7 +11,7 @@ kernelspec: name: python3 --- -# Basic usage +# Sketching: basic usage This example describes how to use basic PyGeometry capabilities. diff --git a/doc/source/examples/dynamic_sketch_plane.mystnb b/doc/source/examples/02_sketching/dynamic_sketch_plane.mystnb similarity index 99% rename from doc/source/examples/dynamic_sketch_plane.mystnb rename to doc/source/examples/02_sketching/dynamic_sketch_plane.mystnb index 9929955c4c..c3a13442f2 100644 --- a/doc/source/examples/dynamic_sketch_plane.mystnb +++ b/doc/source/examples/02_sketching/dynamic_sketch_plane.mystnb @@ -11,7 +11,7 @@ kernelspec: name: python3 --- -# Dynamic sketch plane +# Sketching: dynamic sketch plane The sketch is a lightweight, two-dimensional modeler driven primarily by client-side execution. diff --git a/doc/source/examples/add_design_material.mystnb b/doc/source/examples/03_modeling/add_design_material.mystnb similarity index 98% rename from doc/source/examples/add_design_material.mystnb rename to doc/source/examples/03_modeling/add_design_material.mystnb index 23f705829e..7b02cd0307 100644 --- a/doc/source/examples/add_design_material.mystnb +++ b/doc/source/examples/03_modeling/add_design_material.mystnb @@ -10,7 +10,7 @@ kernelspec: language: python name: python3 --- -# Single body with material assignment +# Modeling: single body with material assignment In PyGeometry, a `body` represents solids or surfaces organized within the ``Design`` assembly. The current state of `sketch`, which is a client-side execution, can be used for the operations of diff --git a/doc/source/examples/design_organization.mystnb b/doc/source/examples/03_modeling/design_organization.mystnb similarity index 98% rename from doc/source/examples/design_organization.mystnb rename to doc/source/examples/03_modeling/design_organization.mystnb index 38286f21dd..754580b1ea 100644 --- a/doc/source/examples/design_organization.mystnb +++ b/doc/source/examples/03_modeling/design_organization.mystnb @@ -11,7 +11,7 @@ kernelspec: name: python3 --- -# Design organization +# Modeling: design organization The ``Design`` instance creates a design project within the remote Geometry service to complete all CAD modeling against. diff --git a/doc/source/examples/plate_with_hole.mystnb b/doc/source/examples/03_modeling/plate_with_hole.mystnb similarity index 96% rename from doc/source/examples/plate_with_hole.mystnb rename to doc/source/examples/03_modeling/plate_with_hole.mystnb index cc0fcecf58..746f6bcb82 100644 --- a/doc/source/examples/plate_with_hole.mystnb +++ b/doc/source/examples/03_modeling/plate_with_hole.mystnb @@ -11,12 +11,12 @@ kernelspec: name: python3 --- -# Rectangular plate with multiple bodies +# Modeling: rectangular plate with multiple bodies You can create multiple bodies from a single sketch by extruding the same sketch in different planes. The sketch is designed as an effective 'fluent functional-style' API with all operations receiving 2D configurations. -To know more about the API, see [Fluent functional-style API in Sketch](../user_guide/shapes.rst). +To know more about the API, see [Fluent functional-style API in Sketch](../../user_guide/shapes.rst). In this example, a box is located in the center of the plate, with the default origin of a sketch plane (origin at ``(0, 0, 0)``). Four holes of equal radius are sketched at the corners of the plate. @@ -115,7 +115,7 @@ design.plot() By using the translate method, you can move the body in a specified direction by a given distance. You can also move a sketch around the global coordinate system. For more information, see -[Dynamic Sketch Plane](dynamic_sketch_plane.mystnb). +[Dynamic Sketch Plane](../02_sketching/dynamic_sketch_plane.mystnb). ```{code-cell} ipython3 longer_body.translate(UnitVector3D([1, 0, 0]), Quantity(4, UNITS.m)) diff --git a/doc/source/examples/tessellation_usage.mystnb b/doc/source/examples/03_modeling/tessellation_usage.mystnb similarity index 98% rename from doc/source/examples/tessellation_usage.mystnb rename to doc/source/examples/03_modeling/tessellation_usage.mystnb index 325af3140b..909a0621d1 100644 --- a/doc/source/examples/tessellation_usage.mystnb +++ b/doc/source/examples/03_modeling/tessellation_usage.mystnb @@ -11,7 +11,7 @@ kernelspec: name: python3 --- -# Tessellation of two bodies +# Modeling: tessellation of two bodies This example creates two stacked bodies and returns the tessellation as two merged bodies. diff --git a/pyproject.toml b/pyproject.toml index 4e902f8eb7..27b029c07c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,6 +61,7 @@ tests = [ ] doc = [ "ansys-sphinx-theme==0.8.2", + "docker==6.0.1", "ipyvtklink==0.2.3", "jupyter_sphinx==0.4.0", "jupytext==1.14.4",