Merge branch 'dev' into lexer

This commit is contained in:
Jonathan Wagenet 2025-10-21 13:59:45 -04:00
commit 8c32e3bed3
42 changed files with 3806 additions and 398 deletions

View file

@ -1,156 +1,55 @@
################
#################
Surface Modeling
################
#################
Surface modeling is employed to create objects with non-planar surfaces that can't be
generated using functions like :func:`~operations_part.extrude`,
:func:`~operations_generic.sweep`, or :func:`~operations_part.revolve`. Since there are no
specific builders designed to assist with the creation of non-planar surfaces or objects,
the following should be considered a more advanced technique.
As described in the `topology_` section, a BREP model consists of vertices, edges, faces,
and other elements that define the boundary of an object. When creating objects with
non-planar faces, it is often more convenient to explicitly create the boundary faces of
the object. To illustrate this process, we will create the following game token:
Surface modeling refers to the direct creation and manipulation of the skin of a 3D
object—its bounding faces—rather than starting from volumetric primitives or solid
operations.
.. raw:: html
Instead of defining a shape by extruding or revolving a 2D profile to fill a volume,
surface modeling focuses on building the individual curved or planar faces that together
define the outer boundary of a part. This approach allows for precise control of complex
freeform geometry such as aerodynamic surfaces, boat hulls, or organic transitions that
cannot easily be expressed with simple parametric solids.
<script type="module" src="https://unpkg.com/@google/model-viewer/dist/model-viewer.min.js"></script>
<model-viewer poster="_images/heart_token.png" src="_static/heart_token.glb" alt="Game Token" auto-rotate camera-controls style="width: 100%; height: 50vh;"></model-viewer>
In build123d, as in other CAD kernels based on BREP (Boundary Representation) modeling,
all solids are ultimately defined by their boundaries: a hierarchy of faces, edges, and
vertices. Each face represents a finite patch of a geometric surface (plane, cylinder,
Bézier patch, etc.) bounded by one or more edge loops or wires. When adjacent faces share
edges consistently and close into a continuous boundary, they form a manifold
:class:`~topology.Shell`—the watertight surface of a volume. If this shell is properly
oriented and encloses a finite region of space, the model becomes a solid.
There are several methods of the :class:`~topology.Face` class that can be used to create
non-planar surfaces:
Surface modeling therefore operates at the most fundamental level of BREP construction.
Rather than relying on higher-level modeling operations to implicitly generate faces,
it allows you to construct and connect those faces explicitly. This provides a path to
build geometry that blends analytical and freeform shapes seamlessly, with full control
over continuity, tangency, and curvature across boundaries.
* :meth:`~topology.Face.make_bezier_surface`,
* :meth:`~topology.Face.make_surface`, and
* :meth:`~topology.Face.make_surface_from_array_of_points`.
This section provides:
- A concise overview of surfacebuilding tools in build123d
- Handson tutorials, from fundamentals to advanced techniques like Gordon surfaces
In this case, we'll use the ``make_surface`` method, providing it with the edges that define
the perimeter of the surface and a central point on that surface.
.. rubric:: Available surface methods
To create the perimeter, we'll use a ``BuildLine`` instance as follows. Since the heart is
symmetric, we'll only create half of its surface here:
Methods on :class:`~topology.Face` for creating nonplanar surfaces:
.. code-block:: build123d
with BuildLine() as heart_half:
l1 = JernArc((0, 0), (1, 1.4), 40, -17)
l2 = JernArc(l1 @ 1, l1 % 1, 4.5, 175)
l3 = IntersectingLine(l2 @ 1, l2 % 1, other=Edge.make_line((0, 0), (0, 20)))
l4 = ThreePointArc(l3 @ 1, Vector(0, 0, 1.5) + (l3 @ 1 + l1 @ 0) / 2, l1 @ 0)
Note that ``l4`` is not in the same plane as the other lines; it defines the center line
of the heart and archs up off ``Plane.XY``.
.. image:: ./assets/token_heart_perimeter.png
:align: center
:alt: token perimeter
In preparation for creating the surface, we'll define a point on the surface:
.. code-block:: build123d
surface_pnt = l2.edge().arc_center + Vector(0, 0, 1.5)
We will then use this point to create a non-planar ``Face``:
.. code-block:: build123d
top_right_surface = -Face.make_surface(heart_half.wire(), [surface_pnt]).locate(
Pos(Z=0.5)
)
.. image:: ./assets/token_half_surface.png
:align: center
:alt: token perimeter
Note that the surface was raised up by 0.5 using the locate method. Also, note that
the ``-`` in front of ``Face`` simply flips the face normal so that the colored side
is up, which isn't necessary but helps with viewing.
Now that one half of the top of the heart has been created, the remainder of the top
and bottom can be created by mirroring:
.. code-block:: build123d
top_left_surface = top_right_surface.mirror(Plane.YZ)
bottom_right_surface = top_right_surface.mirror(Plane.XY)
bottom_left_surface = -top_left_surface.mirror(Plane.XY)
The sides of the heart are going to be created by extruding the outside of the perimeter
as follows:
.. code-block:: build123d
left_wire = Wire([l3.edge(), l2.edge(), l1.edge()])
left_side = Face.extrude(left_wire, (0, 0, 1)).locate(Pos(Z=-0.5))
right_side = left_side.mirror(Plane.YZ)
.. image:: ./assets/token_sides.png
:align: center
:alt: token sides
With the top, bottom, and sides, the complete boundary of the object is defined. We can
now put them together, first into a :class:`~topology.Shell` and then into a
:class:`~topology.Solid`:
.. code-block:: build123d
heart = Solid(
Shell(
[
top_right_surface,
top_left_surface,
bottom_right_surface,
bottom_left_surface,
left_side,
right_side,
]
)
)
.. image:: ./assets/token_heart_solid.png
:align: center
:alt: token heart solid
* :meth:`~topology.Face.make_bezier_surface`
* :meth:`~topology.Face.make_gordon_surface`
* :meth:`~topology.Face.make_surface`
* :meth:`~topology.Face.make_surface_from_array_of_points`
* :meth:`~topology.Face.make_surface_from_curves`
* :meth:`~topology.Face.make_surface_patch`
.. note::
When creating a Solid from a Shell, the Shell must be "water-tight," meaning it
should have no holes. For objects with complex Edges, it's best practice to reuse
Edges in adjoining Faces whenever possible to avoid slight mismatches that can
create openings.
Surface modeling is an advanced technique. Robust results usually come from
reusing the same :class:`~topology.Edge` objects across adjacent faces and
ensuring the final :class:`~topology.Shell` is *watertight* or *manifold* (no gaps).
Finally, we'll create the frame around the heart as a simple extrusion of a planar
shape defined by the perimeter of the heart and merge all of the components together:
.. toctree::
:maxdepth: 1
.. code-block:: build123d
tutorial_surface_heart_token.rst
tutorial_spitfire_wing_gordon.rst
with BuildPart() as heart_token:
with BuildSketch() as outline:
with BuildLine():
add(l1)
add(l2)
add(l3)
Line(l3 @ 1, l1 @ 0)
make_face()
mirror(about=Plane.YZ)
center = outline.sketch
offset(amount=2, kind=Kind.INTERSECTION)
add(center, mode=Mode.SUBTRACT)
extrude(amount=2, both=True)
add(heart)
Note that an additional planar line is used to close ``l1`` and ``l3`` so a ``Face``
can be created. The :func:`~operations_generic.offset` function defines the outside of
the frame as a constant distance from the heart itself.
Summary
-------
In this tutorial, we've explored surface modeling techniques to create a non-planar
heart-shaped object using build123d. By utilizing methods from the :class:`~topology.Face`
class, such as :meth:`~topology.Face.make_surface`, we constructed the perimeter and
central point of the surface. We then assembled the complete boundary of the object
by creating the top, bottom, and sides, and combined them into a :class:`~topology.Shell`
and eventually a :class:`~topology.Solid`. Finally, we added a frame around the heart
using the :func:`~operations_generic.offset` function to maintain a constant distance
from the heart.