From 1b1035ff4e9910bf1827abae475d8ba2aa52e050 Mon Sep 17 00:00:00 2001 From: Roger Maitland Date: Sun, 3 May 2026 15:55:59 -0400 Subject: [PATCH] Adding missing tutorial file --- docs/tutorial_stl_reconstruction.rst | 298 +++++++++++++++++++++++++++ 1 file changed, 298 insertions(+) create mode 100644 docs/tutorial_stl_reconstruction.rst diff --git a/docs/tutorial_stl_reconstruction.rst b/docs/tutorial_stl_reconstruction.rst new file mode 100644 index 00000000..ce467e37 --- /dev/null +++ b/docs/tutorial_stl_reconstruction.rst @@ -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.