Skip to content

Commit

Permalink
Added a surface modeling tutorial
Browse files Browse the repository at this point in the history
  • Loading branch information
gumyr committed Jul 26, 2024
1 parent cba77ab commit 5b8f0e9
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/_static/heart_token.glb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"accessors":[{"bufferView":0,"byteOffset":0,"componentType":5126,"count":24,"max":[-7.105427357601002e-18,0.006913104915604911,0.002],"min":[-0.009436234540229034,-0.0036398224847742895,-0.002],"type":"VEC3"},{"bufferView":0,"byteOffset":288,"componentType":5126,"count":24,"max":[0.009436234540229023,0.0069131049156049158,0.002],"min":[-7.105427357601002e-18,-0.0036398224847742895,-0.002],"type":"VEC3"},{"bufferView":0,"byteOffset":576,"componentType":5126,"count":292,"max":[0.01198149810812791,0.01857053825460047,-0.0019999999999999998],"min":[-0.01198149810812791,-0.0036398224847742895,-0.002],"type":"VEC3"},{"bufferView":0,"byteOffset":4080,"componentType":5126,"count":126,"max":[-0.0019938355257575945,0.01857053825460047,0.002],"min":[-0.01198149810812791,0.006913104915604911,-0.002],"type":"VEC3"},{"bufferView":0,"byteOffset":5592,"componentType":5126,"count":292,"max":[0.01198149810812791,0.01857053825460047,0.002],"min":[-0.01198149810812791,-0.003639822484774289,0.0019999999999999998],"type":"VEC3"},{"bufferView":0,"byteOffset":9096,"componentType":5126,"count":126,"max":[0.01198149810812791,0.01857053825460047,0.002],"min":[0.001993835525757599,0.0069131049156049158,-0.002],"type":"VEC3"},{"bufferView":0,"byteOffset":10608,"componentType":5126,"count":4,"max":[4.440892098500626e-19,0.017556842459256918,0.002],"min":[-0.0019938355257575945,0.016288471659604506,-0.002],"type":"VEC3"},{"bufferView":0,"byteOffset":10656,"componentType":5126,"count":4,"max":[0.001993835525757599,0.017556842459256918,0.002],"min":[4.440892098500626e-19,0.016288471659604506,-0.002],"type":"VEC3"},{"bufferView":0,"byteOffset":10704,"componentType":5126,"count":20,"max":[0.008219755366121646,0.008500611897940111,-0.0005],"min":[0.0,3.1086244689504385e-18,-0.002],"type":"VEC3"},{"bufferView":0,"byteOffset":10944,"componentType":5126,"count":126,"max":[0.00998186091312856,0.016571142671090886,-0.0005],"min":[0.0030673252791798804,0.008500611897940111,-0.002],"type":"VEC3"},{"bufferView":0,"byteOffset":12456,"componentType":5126,"count":4,"max":[0.0030673252791798804,0.015869353274314578,-0.0005],"min":[0.0,0.013918086097615449,-0.002],"type":"VEC3"},{"bufferView":0,"byteOffset":12504,"componentType":5126,"count":4,"max":[0.0,0.015869353274314578,-0.0005],"min":[-0.0030673252791798804,0.013918086097615449,-0.002],"type":"VEC3"},{"bufferView":0,"byteOffset":12552,"componentType":5126,"count":126,"max":[-0.0030673252791798804,0.016571142671090886,-0.0005],"min":[-0.00998186091312856,0.008500611897940111,-0.002],"type":"VEC3"},{"bufferView":0,"byteOffset":14064,"componentType":5126,"count":20,"max":[0.0,0.008500611897940111,-0.0005],"min":[-0.008219755366121646,3.1086244689504385e-18,-0.002],"type":"VEC3"},{"bufferView":0,"byteOffset":14304,"componentType":5126,"count":20,"max":[0.008219755366121646,0.008500611897940111,0.002],"min":[0.0,3.1086244689504385e-18,0.0005],"type":"VEC3"},{"bufferView":0,"byteOffset":14544,"componentType":5126,"count":126,"max":[0.00998186091312856,0.016571142671090886,0.002],"min":[0.0030673252791798804,0.008500611897940111,0.0004999999999999999],"type":"VEC3"},{"bufferView":0,"byteOffset":16056,"componentType":5126,"count":4,"max":[0.0030673252791798804,0.015869353274314578,0.002],"min":[0.0,0.013918086097615449,0.0005],"type":"VEC3"},{"bufferView":0,"byteOffset":16104,"componentType":5126,"count":4,"max":[0.0,0.015869353274314578,0.002],"min":[-0.0030673252791798804,0.013918086097615449,0.0005],"type":"VEC3"},{"bufferView":0,"byteOffset":16152,"componentType":5126,"count":126,"max":[-0.0030673252791798804,0.016571142671090886,0.002],"min":[-0.00998186091312856,0.008500611897940111,0.0004999999999999999],"type":"VEC3"},{"bufferView":0,"byteOffset":17664,"componentType":5126,"count":20,"max":[0.0,0.008500611897940111,0.002],"min":[-0.008219755366121646,3.1086244689504385e-18,0.0005],"type":"VEC3"},{"bufferView":0,"byteOffset":17904,"componentType":5126,"count":691,"max":[0.00998186091312856,0.016571142671090886,-0.0005],"min":[0.0,3.1086244689504385e-18,-0.002127444137200837],"type":"VEC3"},{"bufferView":0,"byteOffset":26196,"componentType":5126,"count":691,"max":[4.1928858097199226e-19,0.016571142671090886,-0.0005],"min":[-0.00998186091312856,3.1086244689504385e-18,-0.002127444137200837],"type":"VEC3"},{"bufferView":0,"byteOffset":34488,"componentType":5126,"count":691,"max":[0.00998186091312856,0.016571142671090886,0.0021274441372008368],"min":[0.0,3.1086244689504385e-18,0.0004999999999999999],"type":"VEC3"},{"bufferView":0,"byteOffset":42780,"componentType":5126,"count":691,"max":[4.1928858097199226e-19,0.016571142671090886,0.0021274441372008368],"min":[-0.00998186091312856,3.1086244689504385e-18,0.0004999999999999999],"type":"VEC3"},{"bufferView":1,"byteOffset":0,"componentType":5126,"count":24,"type":"VEC3"},{"bufferView":1,"byteOffset":288,"componentType":5126,"count":24,"type":"VEC3"},{"bufferView":1,"byteOffset":576,"componentType":5126,"count":292,"type":"VEC3"},{"bufferView":1,"byteOffset":4080,"componentType":5126,"count":126,"type":"VEC3"},{"bufferView":1,"byteOffset":5592,"componentType":5126,"count":292,"type":"VEC3"},{"bufferView":1,"byteOffset":9096,"componentType":5126,"count":126,"type":"VEC3"},{"bufferView":1,"byteOffset":10608,"componentType":5126,"count":4,"type":"VEC3"},{"bufferView":1,"byteOffset":10656,"componentType":5126,"count":4,"type":"VEC3"},{"bufferView":1,"byteOffset":10704,"componentType":5126,"count":20,"type":"VEC3"},{"bufferView":1,"byteOffset":10944,"componentType":5126,"count":126,"type":"VEC3"},{"bufferView":1,"byteOffset":12456,"componentType":5126,"count":4,"type":"VEC3"},{"bufferView":1,"byteOffset":12504,"componentType":5126,"count":4,"type":"VEC3"},{"bufferView":1,"byteOffset":12552,"componentType":5126,"count":126,"type":"VEC3"},{"bufferView":1,"byteOffset":14064,"componentType":5126,"count":20,"type":"VEC3"},{"bufferView":1,"byteOffset":14304,"componentType":5126,"count":20,"type":"VEC3"},{"bufferView":1,"byteOffset":14544,"componentType":5126,"count":126,"type":"VEC3"},{"bufferView":1,"byteOffset":16056,"componentType":5126,"count":4,"type":"VEC3"},{"bufferView":1,"byteOffset":16104,"componentType":5126,"count":4,"type":"VEC3"},{"bufferView":1,"byteOffset":16152,"componentType":5126,"count":126,"type":"VEC3"},{"bufferView":1,"byteOffset":17664,"componentType":5126,"count":20,"type":"VEC3"},{"bufferView":1,"byteOffset":17904,"componentType":5126,"count":691,"type":"VEC3"},{"bufferView":1,"byteOffset":26196,"componentType":5126,"count":691,"type":"VEC3"},{"bufferView":1,"byteOffset":34488,"componentType":5126,"count":691,"type":"VEC3"},{"bufferView":1,"byteOffset":42780,"componentType":5126,"count":691,"type":"VEC3"},{"bufferView":2,"byteOffset":0,"componentType":5123,"count":66,"type":"SCALAR"},{"bufferView":2,"byteOffset":132,"componentType":5123,"count":66,"type":"SCALAR"},{"bufferView":2,"byteOffset":264,"componentType":5123,"count":876,"type":"SCALAR"},{"bufferView":2,"byteOffset":2016,"componentType":5123,"count":372,"type":"SCALAR"},{"bufferView":2,"byteOffset":2760,"componentType":5123,"count":876,"type":"SCALAR"},{"bufferView":2,"byteOffset":4512,"componentType":5123,"count":372,"type":"SCALAR"},{"bufferView":2,"byteOffset":5256,"componentType":5123,"count":6,"type":"SCALAR"},{"bufferView":2,"byteOffset":5268,"componentType":5123,"count":6,"type":"SCALAR"},{"bufferView":2,"byteOffset":5280,"componentType":5123,"count":54,"type":"SCALAR"},{"bufferView":2,"byteOffset":5388,"componentType":5123,"count":372,"type":"SCALAR"},{"bufferView":2,"byteOffset":6132,"componentType":5123,"count":6,"type":"SCALAR"},{"bufferView":2,"byteOffset":6144,"componentType":5123,"count":6,"type":"SCALAR"},{"bufferView":2,"byteOffset":6156,"componentType":5123,"count":372,"type":"SCALAR"},{"bufferView":2,"byteOffset":6900,"componentType":5123,"count":54,"type":"SCALAR"},{"bufferView":2,"byteOffset":7008,"componentType":5123,"count":54,"type":"SCALAR"},{"bufferView":2,"byteOffset":7116,"componentType":5123,"count":372,"type":"SCALAR"},{"bufferView":2,"byteOffset":7860,"componentType":5123,"count":6,"type":"SCALAR"},{"bufferView":2,"byteOffset":7872,"componentType":5123,"count":6,"type":"SCALAR"},{"bufferView":2,"byteOffset":7884,"componentType":5123,"count":372,"type":"SCALAR"},{"bufferView":2,"byteOffset":8628,"componentType":5123,"count":54,"type":"SCALAR"},{"bufferView":2,"byteOffset":8736,"componentType":5123,"count":3873,"type":"SCALAR"},{"bufferView":2,"byteOffset":16484,"componentType":5123,"count":3873,"type":"SCALAR"},{"bufferView":2,"byteOffset":24232,"componentType":5123,"count":3873,"type":"SCALAR"},{"bufferView":2,"byteOffset":31980,"componentType":5123,"count":3873,"type":"SCALAR"}],"asset":{"generator":"Open CASCADE Technology 7.7 [dev.opencascade.org]","version":"2.0"},"bufferViews":[{"buffer":0,"byteLength":51072,"byteOffset":0,"byteStride":12,"target":34962},{"buffer":0,"byteLength":51072,"byteOffset":51072,"byteStride":12,"target":34962},{"buffer":0,"byteLength":39728,"byteOffset":102144,"target":34963}],"buffers":[{"byteLength":141872,"uri":"heart_token.bin"}],"materials":[{"name":"mat_0","pbrMetallicRoughness":{"baseColorFactor":[0.6000000238418579,0.0,0.0,1.0]},"doubleSided":true}],"meshes":[{"name":"COMPOUND","primitives":[{"attributes":{"NORMAL":24,"POSITION":0},"indices":48,"material":0,"mode":4},{"attributes":{"NORMAL":25,"POSITION":1},"indices":49,"material":0,"mode":4},{"attributes":{"NORMAL":26,"POSITION":2},"indices":50,"material":0,"mode":4},{"attributes":{"NORMAL":27,"POSITION":3},"indices":51,"material":0,"mode":4},{"attributes":{"NORMAL":28,"POSITION":4},"indices":52,"material":0,"mode":4},{"attributes":{"NORMAL":29,"POSITION":5},"indices":53,"material":0,"mode":4},{"attributes":{"NORMAL":30,"POSITION":6},"indices":54,"material":0,"mode":4},{"attributes":{"NORMAL":31,"POSITION":7},"indices":55,"material":0,"mode":4},{"attributes":{"NORMAL":32,"POSITION":8},"indices":56,"material":0,"mode":4},{"attributes":{"NORMAL":33,"POSITION":9},"indices":57,"material":0,"mode":4},{"attributes":{"NORMAL":34,"POSITION":10},"indices":58,"material":0,"mode":4},{"attributes":{"NORMAL":35,"POSITION":11},"indices":59,"material":0,"mode":4},{"attributes":{"NORMAL":36,"POSITION":12},"indices":60,"material":0,"mode":4},{"attributes":{"NORMAL":37,"POSITION":13},"indices":61,"material":0,"mode":4},{"attributes":{"NORMAL":38,"POSITION":14},"indices":62,"material":0,"mode":4},{"attributes":{"NORMAL":39,"POSITION":15},"indices":63,"material":0,"mode":4},{"attributes":{"NORMAL":40,"POSITION":16},"indices":64,"material":0,"mode":4},{"attributes":{"NORMAL":41,"POSITION":17},"indices":65,"material":0,"mode":4},{"attributes":{"NORMAL":42,"POSITION":18},"indices":66,"material":0,"mode":4},{"attributes":{"NORMAL":43,"POSITION":19},"indices":67,"material":0,"mode":4},{"attributes":{"NORMAL":44,"POSITION":20},"indices":68,"material":0,"mode":4},{"attributes":{"NORMAL":45,"POSITION":21},"indices":69,"material":0,"mode":4},{"attributes":{"NORMAL":46,"POSITION":22},"indices":70,"material":0,"mode":4},{"attributes":{"NORMAL":47,"POSITION":23},"indices":71,"material":0,"mode":4}]}],"nodes":[{"rotation":[-0.7071067811865475,0.0,0.0,0.7071067811865475],"mesh":0,"name":"=>[0:1:1:2]"}],"scene":0,"scenes":[{"nodes":[0]}]}
Binary file added docs/assets/token_half_surface.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/token_heart_perimeter.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/token_heart_solid.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/token_sides.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions docs/key_concepts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ use the one you prefer or both if you like.

The following key concepts will help new users understand build123d quickly.

.. _topology:

Topology
========

Expand Down
151 changes: 151 additions & 0 deletions docs/tutorial_surface_modeling.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
################
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:

.. raw:: html

<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>

There are several methods of the :class:`~topology.Face` class that can be used to create
non-planar surfaces:
- :meth:`~topology.Face.make_bezier_surface`,
- :meth:`~topology.Face.make_surface`, and
- :meth:`~topology.Face.make_surface_from_array_of_points`.

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.

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:

.. code-block:: python
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:: python
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:: python
top_right_surface = -Face.make_surface(heart_half.wire(), [surface_pnt]).locate(
Pos(Z=0.5)
)
.. image:: ./assets/token_heart_perimeter.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:: python
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:: python
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:: python
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

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:

.. code-block:: python
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.

Certainly! Here is a summary of the tutorial:

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.
1 change: 1 addition & 0 deletions docs/tutorials.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ as later tutorials build on the concepts introduced in earlier ones.
tutorial_joints.rst
examples_1.rst
tttt.rst
tutorial_surface_modeling.rst

0 comments on commit 5b8f0e9

Please sign in to comment.