mirror of
https://github.com/gumyr/build123d.git
synced 2026-05-06 11:57:13 -07:00
Adding missing tutorial file
Some checks are pending
benchmarks / benchmarks (macos-14, 3.12) (push) Waiting to run
benchmarks / benchmarks (macos-15-intel, 3.12) (push) Waiting to run
benchmarks / benchmarks (ubuntu-24.04-arm, 3.12) (push) Waiting to run
benchmarks / benchmarks (ubuntu-latest, 3.12) (push) Waiting to run
benchmarks / benchmarks (windows-latest, 3.12) (push) Waiting to run
Upload coverage reports to Codecov / run (push) Waiting to run
pylint / lint (3.10) (push) Waiting to run
Wheel building and publishing / Build wheel on ubuntu-latest (push) Waiting to run
Wheel building and publishing / upload_pypi (push) Blocked by required conditions
tests / tests (macos-14, 3.14) (push) Waiting to run
tests / tests (macos-15-intel, 3.14) (push) Waiting to run
tests / tests (ubuntu-24.04-arm, 3.14) (push) Waiting to run
tests / tests (ubuntu-latest, 3.10) (push) Waiting to run
tests / tests (ubuntu-latest, 3.14) (push) Waiting to run
tests / tests (windows-latest, 3.14) (push) Waiting to run
Run type checking / typecheck (3.10) (push) Waiting to run
Run type checking / typecheck (3.14) (push) Waiting to run
Some checks are pending
benchmarks / benchmarks (macos-14, 3.12) (push) Waiting to run
benchmarks / benchmarks (macos-15-intel, 3.12) (push) Waiting to run
benchmarks / benchmarks (ubuntu-24.04-arm, 3.12) (push) Waiting to run
benchmarks / benchmarks (ubuntu-latest, 3.12) (push) Waiting to run
benchmarks / benchmarks (windows-latest, 3.12) (push) Waiting to run
Upload coverage reports to Codecov / run (push) Waiting to run
pylint / lint (3.10) (push) Waiting to run
Wheel building and publishing / Build wheel on ubuntu-latest (push) Waiting to run
Wheel building and publishing / upload_pypi (push) Blocked by required conditions
tests / tests (macos-14, 3.14) (push) Waiting to run
tests / tests (macos-15-intel, 3.14) (push) Waiting to run
tests / tests (ubuntu-24.04-arm, 3.14) (push) Waiting to run
tests / tests (ubuntu-latest, 3.10) (push) Waiting to run
tests / tests (ubuntu-latest, 3.14) (push) Waiting to run
tests / tests (windows-latest, 3.14) (push) Waiting to run
Run type checking / typecheck (3.10) (push) Waiting to run
Run type checking / typecheck (3.14) (push) Waiting to run
This commit is contained in:
parent
0a4bb8d6c0
commit
1b1035ff4e
1 changed files with 298 additions and 0 deletions
298
docs/tutorial_stl_reconstruction.rst
Normal file
298
docs/tutorial_stl_reconstruction.rst
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
.. _stl_reconstruction_tutorial:
|
||||
|
||||
#############################################
|
||||
Tutorial: Reconstructing a Design from an STL
|
||||
#############################################
|
||||
|
||||
This tutorial describes a practical workflow for using
|
||||
:func:`~build123d.detect_primitives` to help reconstruct a parametric build123d
|
||||
model from an STL mesh.
|
||||
|
||||
This is not a push-button STL-to-CAD converter. It is a mesh-guided redesign
|
||||
process. The goal is to extract enough analytic structure from a triangulated
|
||||
model to make manual reconstruction faster and more reliable.
|
||||
|
||||
.. warning::
|
||||
|
||||
Rebuilding a design from STL is usually slow, approximate, and manual.
|
||||
STL files contain triangles, not modeling intent. Even when
|
||||
:func:`~build123d.detect_primitives` finds useful planes, cylinders, and
|
||||
spheres, the final build123d model still needs to be designed deliberately.
|
||||
|
||||
Before working through this tutorial, review Steps 1-3 of
|
||||
:ref:`design_tutorial`. The same ideas apply here:
|
||||
|
||||
* identify planes of symmetry
|
||||
* identify likely axes of rotation
|
||||
* choose a convenient origin before doing any serious work
|
||||
|
||||
These preparation steps often reduce the amount of mesh that needs to be
|
||||
reconstructed to one half, one quarter, or even less.
|
||||
|
||||
Overview
|
||||
********
|
||||
|
||||
The workflow described here is:
|
||||
|
||||
1. Import the STL with :class:`~mesher.Mesher`.
|
||||
2. Split the mesh by symmetry planes and isolate the region to redesign.
|
||||
3. Save that reduced mesh section as a BREP file.
|
||||
4. Reload the BREP section while iterating on reconstruction.
|
||||
5. Run :func:`~build123d.detect_primitives`.
|
||||
6. Inspect the returned primitives, leftovers, and generated code.
|
||||
7. Rebuild the design intentionally from those clues.
|
||||
|
||||
The key output of ``detect_primitives`` is guidance:
|
||||
|
||||
* ``primitives`` shows what was recognized analytically
|
||||
* ``leftovers`` shows what was not covered
|
||||
* ``code_lines`` provides algebra-mode fragments that often reveal common
|
||||
planes and likely sketch structure
|
||||
|
||||
Why Cache a Working Section as BREP?
|
||||
************************************
|
||||
|
||||
Importing STL with :class:`~mesher.Mesher` is convenient, but large meshes can be
|
||||
slow to load and process. Once a useful section of the part has been isolated,
|
||||
save it as a BREP file and use that for repeated experimentation.
|
||||
|
||||
BREP files reload much more efficiently in build123d and are better suited to
|
||||
an iterative reconstruction script.
|
||||
|
||||
Preparing the Mesh
|
||||
******************
|
||||
|
||||
Start with the STL import and isolate the smallest useful section of the part.
|
||||
|
||||
.. code-block:: build123d
|
||||
|
||||
from build123d import *
|
||||
|
||||
importer = Mesher()
|
||||
full_mesh = importer.read("target_part.stl")[0]
|
||||
|
||||
# Example: reduce the work to one quarter of a symmetric model
|
||||
quarter_mesh = split(full_mesh, Plane.YZ)
|
||||
quarter_mesh = split(quarter_mesh, Plane.XZ)
|
||||
|
||||
export_brep(quarter_mesh, "target_part_quarter.brep")
|
||||
|
||||
The exact planes depend on the part. The point is not to begin running
|
||||
``detect_primitives`` on the full mesh if symmetry can remove most of the work.
|
||||
|
||||
Reconstruction Script
|
||||
*********************
|
||||
|
||||
Once the mesh section has been cached as BREP, iterate on a separate script or
|
||||
enable a reconstruction section of the same script with a Boolean switch.
|
||||
|
||||
.. code-block:: build123d
|
||||
|
||||
from build123d import *
|
||||
|
||||
working_mesh = import_brep("target_part_quarter.brep")
|
||||
|
||||
primitives, leftovers, code_lines = detect_primitives(working_mesh)
|
||||
|
||||
print(*code_lines, sep="\n")
|
||||
|
||||
This call returns three complementary outputs:
|
||||
|
||||
``primitives``
|
||||
A :class:`~topology.ShapeList` of analytic faces that were recognized from
|
||||
the mesh. These are typically planes, cylinders, and spheres. Planar
|
||||
primitives are returned as rectangles sized to the planar region's
|
||||
bounding box, not as the original tessellated mesh patch.
|
||||
|
||||
``leftovers``
|
||||
Mesh faces that were not matched by the primitive detectors. These indicate
|
||||
freeform regions, noisy regions, or places where manual work is still
|
||||
required.
|
||||
|
||||
``code_lines``
|
||||
Generated algebra-mode code corresponding to the recognized primitives.
|
||||
|
||||
Inspecting the Results
|
||||
**********************
|
||||
|
||||
The inspection step is the heart of this workflow.
|
||||
|
||||
1. Examine the primitives visually
|
||||
==================================
|
||||
|
||||
Look at the returned ``primitives`` and decide what the detector found well.
|
||||
|
||||
Useful questions include:
|
||||
|
||||
* Are the expected planar faces present?
|
||||
* Do fillets appear as cylinders?
|
||||
* Do rounded corners appear as spheres?
|
||||
* Are repeated features recognized consistently?
|
||||
|
||||
In a simple mechanical part, good output often consists of a small number of
|
||||
common planes, repeated cylinders with similar radii, and only a few leftovers.
|
||||
|
||||
2. Examine the leftovers
|
||||
========================
|
||||
|
||||
``leftovers`` show what still needs manual interpretation.
|
||||
|
||||
Large leftover regions often indicate one of three things:
|
||||
|
||||
* the part contains geometry that is not well approximated by planes,
|
||||
cylinders, or spheres
|
||||
* the mesh is noisy or irregular
|
||||
* the working section is still too large to interpret comfortably
|
||||
|
||||
If too much of the mesh appears in ``leftovers``, it may be better to refine
|
||||
the working section, identify more symmetry, or redesign that area manually
|
||||
instead of trying to automate it further.
|
||||
|
||||
3. Examine the generated code
|
||||
=============================
|
||||
|
||||
The generated ``code_lines`` are intentionally written in Algebra mode and use
|
||||
``Plane * Pos`` structure to make repeated placement patterns easier to spot.
|
||||
|
||||
This often helps answer questions such as:
|
||||
|
||||
* which faces lie on the same construction plane?
|
||||
* which circles belong to the same sketch?
|
||||
* which cylindrical or spherical regions are repeated instances of one feature?
|
||||
|
||||
Treat this code as an annotated report, not necessarily as the final model.
|
||||
|
||||
For planar parts in particular, the generated lines are often naturally grouped
|
||||
by plane. A sequence such as ``Plane.XY.offset(...)`` with a few repeated
|
||||
offset values usually indicates related structure that may belong to one sketch
|
||||
or one construction stage.
|
||||
|
||||
Worked Example: Filleted Box
|
||||
****************************
|
||||
|
||||
As a controlled example, consider a filleted box:
|
||||
|
||||
.. code-block:: build123d
|
||||
|
||||
fillet_box = fillet(Box(1, 1, 1).edges(), 0.1)
|
||||
|
||||
Running ``detect_primitives`` on this geometry produces output like:
|
||||
|
||||
.. code-block:: build123d
|
||||
|
||||
r00 = Plane.XY.offset(-0.5) * Pos(-0.4, -0.4) * Rectangle(0.8, 0.8, align=Align.MIN)
|
||||
c01 = Plane.XY.offset(-0.4) * Pos(0.4, 0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))
|
||||
c02 = Plane.XY.offset(-0.4) * Pos(-0.4, 0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))
|
||||
c03 = Plane.XY.offset(-0.4) * Pos(-0.4, -0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))
|
||||
c04 = Plane.XY.offset(-0.4) * Pos(0.4, -0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))
|
||||
r05 = Plane.XY.offset(0.5) * Pos(-0.4, -0.4) * Rectangle(0.8, 0.8, align=Align.MIN)
|
||||
r06 = Plane.YZ.offset(-0.5) * Pos(-0.4, -0.4) * Rectangle(0.8, 0.8, align=Align.MIN)
|
||||
c07 = Plane.YZ.offset(-0.4) * Pos(-0.4, -0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))
|
||||
c08 = Plane.YZ.offset(-0.4) * Pos(-0.4, 0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))
|
||||
c09 = Plane.YZ.offset(-0.4) * Pos(0.4, -0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))
|
||||
c10 = Plane.YZ.offset(-0.4) * Pos(0.4, 0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))
|
||||
r11 = Plane.YZ.offset(0.5) * Pos(-0.4, -0.4) * Rectangle(0.8, 0.8, align=Align.MIN)
|
||||
r12 = Plane.ZX.offset(-0.5) * Pos(-0.4, -0.4) * Rectangle(0.8, 0.8, align=Align.MIN)
|
||||
c13 = Plane.ZX.offset(-0.4) * Pos(-0.4, 0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))
|
||||
c14 = Plane.ZX.offset(-0.4) * Pos(-0.4, -0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))
|
||||
c15 = Plane.ZX.offset(-0.4) * Pos(0.4, -0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))
|
||||
c16 = Plane.ZX.offset(-0.4) * Pos(0.4, 0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))
|
||||
r17 = Plane.ZX.offset(0.5) * Pos(-0.4, -0.4) * Rectangle(0.8, 0.8, align=Align.MIN)
|
||||
s18 = Pos((0.399999, -0.399999, 0.400026)) * Sphere(0.099983).faces().filter_by(GeomType.SPHERE)[0]
|
||||
s19 = Pos((-0.399999, 0.399999, -0.400026)) * Sphere(0.099983).faces().filter_by(GeomType.SPHERE)[0]
|
||||
s20 = Pos((-0.399999, -0.399999, -0.400026)) * Sphere(0.099983).faces().filter_by(GeomType.SPHERE)[0]
|
||||
s21 = Pos((0.399999, 0.399999, -0.400026)) * Sphere(0.099983).faces().filter_by(GeomType.SPHERE)[0]
|
||||
s22 = Pos((-0.399999, 0.400026, 0.399999)) * Sphere(0.099983).faces().filter_by(GeomType.SPHERE)[0]
|
||||
s23 = Pos((-0.399999, -0.399999, 0.400026)) * Sphere(0.099983).faces().filter_by(GeomType.SPHERE)[0]
|
||||
s24 = Pos((0.399999, 0.400026, 0.399999)) * Sphere(0.099983).faces().filter_by(GeomType.SPHERE)[0]
|
||||
s25 = Pos((0.399999, -0.399999, -0.400026)) * Sphere(0.099983).faces().filter_by(GeomType.SPHERE)[0]
|
||||
|
||||
This output is informative in several ways:
|
||||
|
||||
* the six box faces appear as rectangles on three principal planes
|
||||
* the edge fillets appear as cylinders grouped around those same planes
|
||||
* the corner blends appear as spheres near the eight cube corners
|
||||
|
||||
The generated code is also structured by plane:
|
||||
|
||||
* ``Plane.XY.offset(...)`` appears with three distinct offsets
|
||||
* ``Plane.YZ.offset(...)`` appears with three distinct offsets
|
||||
* ``Plane.ZX.offset(...)`` appears with three distinct offsets
|
||||
|
||||
That organization is often more useful than any one primitive by itself
|
||||
because it suggests how the model could be regrouped into sketches and
|
||||
construction steps.
|
||||
|
||||
Although this output is correct and useful, it still does not represent the
|
||||
best final build123d model. The original design intent is much simpler:
|
||||
|
||||
.. code-block:: build123d
|
||||
|
||||
fillet(Box(1, 1, 1).edges(), 0.1)
|
||||
|
||||
That is a good example of the main lesson of this tutorial: the generated code
|
||||
helps reveal structure, but the final model should usually be rewritten in a
|
||||
cleaner, higher-level form.
|
||||
|
||||
Turning Primitive Hints into Sketches
|
||||
*************************************
|
||||
|
||||
Once repeated planes become obvious in ``code_lines``, start grouping related
|
||||
features into sketches and features of your own.
|
||||
|
||||
For example:
|
||||
|
||||
* several rectangles on ``Plane.XY`` may indicate one base sketch and one or
|
||||
more extrusions
|
||||
* repeated circles on one plane may indicate hole or boss locations
|
||||
* a collection of cylinders with the same radius may indicate that a fillet or
|
||||
round was part of the original design intent
|
||||
|
||||
The generated code is often most useful when treated as:
|
||||
|
||||
* a list of candidate construction planes
|
||||
* a list of likely sketch elements
|
||||
* a list of repeated primitive sizes and placements
|
||||
|
||||
Signs of Good Output
|
||||
********************
|
||||
|
||||
``detect_primitives`` is most helpful when:
|
||||
|
||||
* the mesh is reasonably clean
|
||||
* the part is mostly mechanical
|
||||
* many surfaces are planar, cylindrical, or spherical
|
||||
* there are clear planes of symmetry or repeated features
|
||||
|
||||
In these cases, primitives and generated code often cluster into obvious
|
||||
reconstruction steps.
|
||||
|
||||
Signs of Poor Output
|
||||
********************
|
||||
|
||||
Expect more manual work when:
|
||||
|
||||
* the mesh contains freeform surfaces
|
||||
* the STL is noisy or heavily tessellated
|
||||
* the part has no obvious symmetry
|
||||
* many important regions remain in ``leftovers``
|
||||
* the generated code contains many tiny or redundant fragments
|
||||
|
||||
When this happens, it may be faster to use the mesh only as a visual reference
|
||||
and rebuild the part manually from dimensions and intent.
|
||||
|
||||
Summary
|
||||
*******
|
||||
|
||||
The STL reconstruction workflow in build123d is:
|
||||
|
||||
1. analyze the part as a designer, not as a mesh processor
|
||||
2. isolate the smallest useful region with symmetry and splitting
|
||||
3. cache that region as BREP
|
||||
4. run :func:`~build123d.detect_primitives`
|
||||
5. inspect primitives, leftovers, and code
|
||||
6. rebuild the part intentionally in build123d
|
||||
|
||||
The most useful mindset is to treat ``detect_primitives`` as a design assistant.
|
||||
It can show where the planes, cylinders, and spheres probably are, but the
|
||||
final parametric model still comes from careful human interpretation.
|
||||
Loading…
Add table
Add a link
Reference in a new issue