diff --git a/src/axom/klee/docs/sphinx/specifying_shapes.rst b/src/axom/klee/docs/sphinx/specifying_shapes.rst index c5cfd704d6..ae7e281c12 100644 --- a/src/axom/klee/docs/sphinx/specifying_shapes.rst +++ b/src/axom/klee/docs/sphinx/specifying_shapes.rst @@ -114,6 +114,8 @@ will match that of the (global or per-shape) `dimensions`. x: 10 +.. _klee-overlay-rules: + Overlay Rules ------------- Shapes are added to meshes in the order in which they appear in the YAML diff --git a/src/axom/quest/docs/scripts/generate_shaping_pipeline_figure.py b/src/axom/quest/docs/scripts/generate_shaping_pipeline_figure.py new file mode 100644 index 0000000000..fe46c62979 --- /dev/null +++ b/src/axom/quest/docs/scripts/generate_shaping_pipeline_figure.py @@ -0,0 +1,323 @@ +#!/usr/bin/env python3 +"""Generate an SVG figure for Quest's shaping pipeline documentation.""" + +from __future__ import annotations + +import argparse +import html +from pathlib import Path + + +WIDTH = 1600 +HEIGHT = 900 + + +def esc(text: str) -> str: + return html.escape(text, quote=True) + + +def rect(x: int, y: int, w: int, h: int, cls: str, rx: int = 28) -> str: + return ( + f"" + ) + + +def line(x1: int, y1: int, x2: int, y2: int, cls: str, marker: bool = True) -> str: + marker_end = " marker-end='url(#arrow)'" if marker else "" + return ( + f"" + ) + + +def text_block(x: int, y: int, lines: list[str], cls: str, line_gap: int = 34) -> str: + parts = [f""] + for idx, item in enumerate(lines): + dy = 0 if idx == 0 else line_gap + parts.append(f"{esc(item)}") + parts.append("") + return "".join(parts) + + +def bullet_list(x: int, y: int, items: list[str], cls: str, line_gap: int = 30) -> str: + parts: list[str] = [] + for idx, item in enumerate(items): + yy = y + idx * line_gap + parts.append(f"") + parts.append(f"{esc(item)}") + return "".join(parts) + + +def stage_box(x: int, y: int, w: int, h: int, label: str, items: list[str], kind: str) -> str: + return "".join( + [ + rect(x, y, w, h, f"panel {kind}"), + text_block(x + 28, y + 52, [label], "stage-title"), + bullet_list(x + 32, y + 110, items, "body-text"), + ] + ) + + +def step_box( + x: int, y: int, w: int, h: int, title: list[str], subtitle: list[str] +) -> str: + return "".join( + [ + rect(x, y, w, h, "step"), + text_block(x + 20, y + 36, title, "step-title", line_gap=22), + text_block(x + 20, y + 74, subtitle, "step-text", line_gap=22), + ] + ) + + +def dashed_loop(x: int, y: int, w: int, h: int) -> str: + return ( + f"" + ) + + +def build_svg() -> str: + out: list[str] = [] + out.append( + f"" + ) + out.append("Quest shaping pipeline") + out.append( + "Diagram showing Klee shape input flowing through Quest " + "shaper setup, a per-shape processing loop, and output material volume " + "fraction fields on the target mesh." + ) + out.append( + """ + + + + + + + + + + + + + + + + + +""" + ) + out.append(f"") + out.append("") + out.append("") + + out.append(text_block(72, 88, ["Quest shaping pipeline"], "stage-title")) + out.append( + text_block( + 74, + 130, + ["Klee describes shapes. Quest loads them, queries the target mesh,", "and writes material volume fractions."], + "caption", + line_gap=28, + ) + ) + + out.append(text_block(100, 214, ["Klee input"], "small-label")) + out.append( + stage_box( + 72, + 234, + 340, + 250, + "Shape set", + [ + "material name", + "geometry path and format", + "transforms", + "replacement rules", + ], + "input", + ) + ) + + out.append(text_block(490, 214, ["Quest setup"], "small-label")) + out.append( + stage_box( + 462, + 234, + 340, + 250, + "Target mesh + shaper", + [ + "MFEM or Blueprint mesh", + "sampling or intersection shaper", + "runtime policy and tolerances", + "optional initial vol_frac_*", + ], + "setup", + ) + ) + + loop_x = 842 + loop_y = 180 + loop_w = 500 + loop_h = 540 + out.append(text_block(loop_x + 32, 214, ["Per-shape loop"], "small-label")) + out.append(dashed_loop(loop_x, loop_y + 38, loop_w, loop_h)) + out.append(text_block(loop_x + 34, loop_y + 98, ["For each shape"], "loop-title")) + + step_x = loop_x + 30 + step_w = 212 + step_h = 100 + row1_y = loop_y + 146 + row2_y = loop_y + 314 + + col2_x = step_x + 238 + bottom_x = step_x + 120 + + out.append( + step_box( + step_x, + row1_y, + step_w, + step_h, + ["loadShape()"], + ["read geometry", "and apply transforms"], + ) + ) + out.append( + step_box( + col2_x, + row1_y, + step_w, + step_h, + ["prepare", "ShapeQuery()"], + ["build spatial index", "or intersection data"], + ) + ) + out.append( + step_box( + step_x, + row2_y, + step_w, + step_h, + ["runShapeQuery()"], + ["measure overlap", "with target zones"], + ) + ) + out.append( + step_box( + col2_x, + row2_y, + step_w, + step_h, + ["applyReplacement", "Rules()"], + ["merge with existing", "materials"], + ) + ) + out.append( + step_box( + bottom_x, + loop_y + 442, + step_w + 52, + step_h, + ["finalizeShapeQuery()"], + ["clear temporary state", "before the next shape"], + ) + ) + + out.append(line(412, 360, 462, 360, "connector")) + out.append(line(802, 360, 842, 360, "connector")) + out.append(line(step_x + step_w, row1_y + 50, col2_x, row1_y + 50, "connector")) + out.append(line(step_x + 106, row1_y + step_h, step_x + 106, row2_y, "connector")) + out.append(line(col2_x + 106, row1_y + step_h, col2_x + 106, row2_y, "connector")) + out.append(line(step_x + step_w, row2_y + 50, col2_x, row2_y + 50, "connector")) + out.append(line(step_x + 106, row2_y + step_h, bottom_x + 110, loop_y + 442, "connector")) + out.append(line(col2_x + 106, row2_y + step_h, bottom_x + 154, loop_y + 442, "connector")) + out.append(line(bottom_x + 132, loop_y + 542, bottom_x + 132, loop_y + 584, "connector-soft")) + out.append(text_block(bottom_x + 154, loop_y + 578, ["next shape"], "step-text")) + + out.append(text_block(1406, 214, ["Quest output"], "small-label")) + out.append( + "".join( + [ + rect(1378, 234, 180, 250, "panel output"), + text_block(1406, 286, ["Volume", "fractions"], "stage-title", line_gap=30), + bullet_list( + 1410, + 346, + [ + "vol_frac_matA", + "vol_frac_matB", + "vol_frac_free", + "ready for output", + ], + "body-text", + ), + ] + ) + ) + out.append(line(1342, 360, 1378, 360, "connector")) + + out.append(rect(1354, 552, 240, 140, "setup", rx=24)) + out.append(text_block(1380, 598, ["Target mesh state"], "step-title")) + out.append(text_block(1380, 638, ["materials now encode", "shape overlap per zone"], "step-text", line_gap=26)) + out.append(line(1468, 484, 1468, 552, "connector")) + + out.append("") + out.append(line(632, 484, 632, 486, "connector-soft", marker=False)) + out.append(text_block(500, 592, ["optional: import existing", "material fields before shaping"], "step-text", line_gap=26)) + + out.append("") + return "".join(out) + + +def parse_args() -> argparse.Namespace: + default_output = ( + Path(__file__).resolve().parent.parent / "sphinx" / "figs" / "shaping_pipeline.svg" + ) + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "-o", + "--output", + type=Path, + default=default_output, + help=f"Path to the output SVG file (default: {default_output})", + ) + return parser.parse_args() + + +def main() -> None: + args = parse_args() + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(build_svg(), encoding="utf-8") + print(f"Wrote {args.output}") + + +if __name__ == "__main__": + main() diff --git a/src/axom/quest/docs/sphinx/delaunay.rst b/src/axom/quest/docs/sphinx/delaunay.rst new file mode 100644 index 0000000000..ace44beda7 --- /dev/null +++ b/src/axom/quest/docs/sphinx/delaunay.rst @@ -0,0 +1,80 @@ +.. ## Copyright (c) Lawrence Livermore National Security, LLC and other +.. ## Axom Project Contributors. See top-level LICENSE and COPYRIGHT +.. ## files for dates and other details. +.. ## +.. ## SPDX-License-Identifier: (BSD-3-Clause) + +*********************** +Delaunay Triangulation +*********************** + +Quest provides a ``quest::Delaunay`` class for incremental construction of a +2D or 3D Delaunay complex from a point set. The class currently builds the +triangulation by inserting points one at a time into an initial bounding mesh, +retriangulating the local cavity after each insertion. + +At a high level, the workflow is: + +#. define a bounding box that contains the points to be inserted +#. initialize the Delaunay object with that boundary +#. insert points incrementally +#. remove the artificial boundary elements +#. optionally validate the resulting complex and write it to VTK + +The current implementation is useful for applications that need a simplicial +mesh over an unstructured point set. Quest also uses this capability in higher +level algorithms such as scattered interpolation. + +The example application +``/src/axom/quest/examples/delaunay_triangulation.cpp`` demonstrates the +basic usage pattern. + +The Delaunay class is templated on the dimension: + +.. literalinclude:: ../../examples/delaunay_triangulation.cpp + :start-after: _quest_delaunay_include_start + :end-before: _quest_delaunay_include_end + :language: C++ + +Creating the triangulation +-------------------------- + +The example creates a bounding box, initializes the Delaunay object, inserts +points, and then removes the artificial boundary: + +.. literalinclude:: ../../examples/delaunay_triangulation.cpp + :start-after: _quest_delaunay_basic_start + :end-before: _quest_delaunay_basic_end + :language: C++ + +The call to ``initializeBoundary()`` is required before inserting points. The +inserted points must lie inside that bounding box. + +Validation +---------- + +Quest provides validation helpers for both the underlying mesh structure and +the Delaunay property itself: + +.. literalinclude:: ../../examples/delaunay_triangulation.cpp + :start-after: _quest_delaunay_validate_start + :end-before: _quest_delaunay_validate_end + :language: C++ + +Output +------ + +The resulting triangulation can be written to a VTK file for inspection: + +.. literalinclude:: ../../examples/delaunay_triangulation.cpp + :start-after: _quest_delaunay_output_start + :end-before: _quest_delaunay_output_end + :language: C++ + +Current scope +------------- + +This page is intentionally conservative. It documents the current user-visible +workflow of Quest's Delaunay triangulation example without trying to freeze the +interface or fully characterize future use cases while the implementation is +still evolving. diff --git a/src/axom/quest/docs/sphinx/figs/intersection.png b/src/axom/quest/docs/sphinx/figs/intersection.png new file mode 100644 index 0000000000..0f14e09a98 Binary files /dev/null and b/src/axom/quest/docs/sphinx/figs/intersection.png differ diff --git a/src/axom/quest/docs/sphinx/figs/sampling.png b/src/axom/quest/docs/sphinx/figs/sampling.png new file mode 100644 index 0000000000..22d6f1c0c9 Binary files /dev/null and b/src/axom/quest/docs/sphinx/figs/sampling.png differ diff --git a/src/axom/quest/docs/sphinx/figs/shaping_overview.png b/src/axom/quest/docs/sphinx/figs/shaping_overview.png new file mode 100644 index 0000000000..bb000d12a4 Binary files /dev/null and b/src/axom/quest/docs/sphinx/figs/shaping_overview.png differ diff --git a/src/axom/quest/docs/sphinx/figs/shaping_pipeline.svg b/src/axom/quest/docs/sphinx/figs/shaping_pipeline.svg new file mode 100644 index 0000000000..04c0b0f168 --- /dev/null +++ b/src/axom/quest/docs/sphinx/figs/shaping_pipeline.svg @@ -0,0 +1,40 @@ +Quest shaping pipelineDiagram showing Klee shape input flowing through Quest shaper setup, a per-shape processing loop, and output material volume fraction fields on the target mesh. + + + + + + + + + + + + + + + + + +Quest shaping pipelineKlee describes shapes. Quest loads them, queries the target mesh,and writes material volume fractions.Klee inputShape setmaterial namegeometry path and formattransformsreplacement rulesQuest setupTarget mesh + shaperMFEM or Blueprint meshsampling or intersection shaperruntime policy and tolerancesoptional initial vol_frac_*Per-shape loopFor each shapeloadShape()read geometryand apply transformsprepareShapeQuery()build spatial indexor intersection datarunShapeQuery()measure overlapwith target zonesapplyReplacementRules()merge with existingmaterialsfinalizeShapeQuery()clear temporary statebefore the next shapenext shapeQuest outputVolumefractionsvol_frac_matAvol_frac_matBvol_frac_freeready for outputTarget mesh statematerials now encodeshape overlap per zoneoptional: import existingmaterial fields before shaping \ No newline at end of file diff --git a/src/axom/quest/docs/sphinx/index.rst b/src/axom/quest/docs/sphinx/index.rst index 01e2606598..67cbe0eda7 100644 --- a/src/axom/quest/docs/sphinx/index.rst +++ b/src/axom/quest/docs/sphinx/index.rst @@ -7,37 +7,31 @@ Quest User Guide ================ -The Quest component of Axom provides several spatial operations and queries -on a ``mint::Mesh``. - - - Operations - - - :ref:`Read a surface mesh` from an STL file - - :ref:`Check for some common mesh errors; deduplicate vertices` - - - vertex welding: merge vertices closer than a specified distance - "epsilon" - - find self-intersections and degenerate triangles in a surface mesh - - watertightness test: is a surface mesh a watertight manifold? - - - Point queries - - - Surface mesh point queries :ref:`in C` or - :ref:`in C++` - - - in/out query: is a point inside or outside a surface mesh? - - signed distance query: find the minimum distance from a query point - to a surface mesh - - - :ref:`Point in cell query`: for a query point, find the - cell of the mesh that holds the point and the point's isoparametric - coordinates within that cell - - :ref:`All nearest neighbors`: given a list of point - locations and regions, find all neighbors of each point in a different - region - - :ref:`Isosurface detection`: generate an - isosurface mesh from a nodal scalar field and an isovalue. - +Axom's Quest component provides spatial queries, geometry readers, contouring, +and shaping algorithms for simulation workflows. Quest works with several Axom +mesh representations, including ``mint::Mesh`` objects, Conduit Blueprint +meshes, and MFEM meshes. + +This guide focuses on the most common Quest workflows: + +* :ref:`Read geometry and meshes ` from STL, Pro/E, STEP, + C2C, and MFEM-based inputs. +* :ref:`Check and repair surface meshes ` before using + algorithms that require watertight geometry. +* Run surface and mesh queries, including :ref:`surface containment and signed + distance `, :ref:`point-in-cell `, and + :ref:`all nearest neighbors `. +* Generate :ref:`isocontours and isosurfaces ` from + nodal scalar fields on Blueprint meshes. +* Build point-set simplicial meshes with :doc:`Quest's Delaunay triangulation `. +* Query curved and linearized shapes with :doc:`Winding Numbers `. +* Approximate curved contour geometry with :doc:`Linearize Curves `. +* Build :ref:`shaping pipelines ` that convert Klee shape + descriptions into material volume fractions on target meshes. + +The Sphinx pages describe the user-facing workflows and show representative +examples from Quest's sources and tests. For a full API reference, use the +generated Doxygen documentation below. API Documentation ----------------- @@ -47,7 +41,7 @@ Doxygen generated API documentation can be found here: `API documentation <../.. .. toctree:: :caption: Contents - :maxdepth: 2 + :maxdepth: 1 read_mesh check_and_repair @@ -56,4 +50,7 @@ Doxygen generated API documentation can be found here: `API documentation <../.. point_in_cell all_nearest_neighbors isosurface_detection - + delaunay + winding_number + linearize_curves + shaping diff --git a/src/axom/quest/docs/sphinx/isosurface_detection.rst b/src/axom/quest/docs/sphinx/isosurface_detection.rst index 00a6218fde..5608bd8977 100644 --- a/src/axom/quest/docs/sphinx/isosurface_detection.rst +++ b/src/axom/quest/docs/sphinx/isosurface_detection.rst @@ -10,15 +10,15 @@ Isosurface Detection ******************** -Quest can generate isosurface meshes for node-centered scalar fields. -This feature takes a structured mesh with some scalar nodal field and -generates an ``UnstructuredMesh`` at a user-specified isovalue. The -isosurface mesh contains information on which elements of the field -mesh it crosses. The output may be useful for material surface -reconstruction and visualization, among other things. +Quest provides the ``quest::MarchingCubes`` class for generating contours from +node-centered scalar fields on Conduit Blueprint meshes. In 2D, the algorithm +produces line segments; in 3D, it produces triangles. The output contour mesh +also records which input cell and domain generated each contour facet, which is +useful for analysis, debugging, and downstream reconstruction workflows. -We support 2D and 3D configurations. The isosurface mesh is a -composed of line segments in 2D and triangles in 3D. +``MarchingCubes`` operates on Blueprint meshes in *multi-domain* form. The +parent topology must be structured and the scalar field must be node-centered. +The class supports both 2D and 3D inputs. .. Note:: @@ -40,98 +40,91 @@ composed of line segments in 2D and triangles in 3D. :width: 400px Planar isocontour generated using the field :math:`f(\mathbf{r}) = - f_0 + \mathbf{r} \cdot \mathbf{n}` and spherical contour generated - using the field field :math:`g(\mathbf{r}) = |\textbf{r} - - \textbf{r}_0|`. Colors denote the domain index in the multi-domain - cubic mesh. - -The algorithm is implemented in the class ``quest::MarchingCubes``. - -The inputs are: - -#. The mesh containing the scalar field. This mesh should be in - Conduit's blueprint format. See - https://llnl-conduit.readthedocs.io/en/latest/blueprint_mesh.html -#. The name of the blueprint coordinates data for the input mesh. -#. The name of the scalar field data within the input mesh. -#. The contour value. - -The following example shows usage of the ``MarchingCubes`` class. -(A complete example is provided in -``src/axom/quest/examples/quest_marching_cubes_example.cpp``.) - -Relevant header files: - -.. sourcecode:: C++ - - #include "conduit_relay_io_blueprint.hpp" - #include "axom/quest/MarchingCubes.hpp" - #include "axom/mint/mesh/UnstructuredMesh.hpp" - -Set up the user's blueprint mesh and the ``MarchingCubes`` object: - -The blueprint mesh must be a structured mesh in multi-domain format. -A domain is a part of a global mesh that has been subdivided for -reasons including parallel partitioning, geometric constraints and -size constraints. Any number of domains is allowed, including zero. -(For single-domain format, see the similar -``MarchingCubesSingleDomain`` class in the ``axom::quest`` namespace.) - -Blueprint convention allows for named coordinate sets and scalar -fields. Here, we tell the ``MarchingCubes`` constructor that the -topology is "mesh", and the name of the nodal scalar -field is "scalarFieldName". - -The constructor's ``quest::MarchingCubesRuntimePolicy::seq`` argument -tells ``mc`` to run sequentially on the host. ``MarchingCubes`` -currently also supports OpenMP and GPU device executions using CUDA -and HIP. - -.. sourcecode:: C++ - - conduit::Node blueprintMesh = blueprint_mesh_from_user(); - quest::MarchingCubes mc(quest::MarchingCubesRuntimePolicy::seq, - blueprintMesh, - "mesh", - "scalarFieldName"); - -Run the algorithm: - -.. sourcecode:: C++ - - double contourValue = 0.5; - mc.computeIsocontour(contourValue); - -Place the isocontour in an output ``mint::UnstructuredMesh`` object: - -``MarchingCubes`` generates the isocontour mesh in an internal format. -Use ``populateContourMesh`` to put it in a ``mint::UnstructuredMesh`` -object. In the future, we will support outputs in blueprint format. - -``populateContourMesh`` provides two scalar fields for the generated -mesh: - -#. the ID of the cell from the input mesh that generated the - isocontour cell. -#. the ID of the domain from the input mesh that generated the - isocontour cell. - -The names of these fields are user-specified. Use empty strings if -you don't need these fields. This example puts cell IDs in -"cellIds" and domain IDs in "domainIds". - -.. sourcecode:: C++ - - mint::UnstructuredMesh contourMesh; - mc.populateContourMesh(contourMesh, "cellIds", "domainIds"); - -After putting the isosurface in the ``UnstructuredMesh`` object, -the ``MarchingCubes`` object is no longer needed. - -MPI-parallel runs: - -For MPI-parallel runs, the input mesh may have local and remote -domains. The algorithm is local in that no data communication is -required to run. The output isosurface mesh uses node and cell -numbers that are locally unique. Users requiring these numbers to be -globally unique should renumber them. + f_0 + \mathbf{r} \cdot \mathbf{n}` and spherical contour generated using + the field :math:`g(\mathbf{r}) = |\textbf{r} - \textbf{r}_0|`. Colors + denote the domain index in the multi-domain mesh. + +Basic workflow +-------------- + +The workflow is: + +#. Construct a ``quest::MarchingCubes`` object with a runtime policy, + allocator, and data-parallel implementation choice. +#. Call ``setMesh()`` with the Blueprint mesh and topology name. A cell mask + field name may also be supplied. +#. Select the node-centered scalar field with ``setFunctionField()``. +#. Call ``computeIsocontour()`` for each isovalue of interest. +#. Export the contour to either a ``mint::UnstructuredMesh`` or the raw output + arrays. + +The example application in +``/src/axom/quest/examples/quest_marching_cubes_example.cpp`` uses the +following setup: + +.. literalinclude:: ../../examples/quest_marching_cubes_example.cpp + :start-after: _quest_marching_cubes_init_start + :end-before: _quest_marching_cubes_init_end + :language: C++ + +After the object is configured, the caller selects a scalar field and computes +the contour: + +.. literalinclude:: ../../examples/quest_marching_cubes_example.cpp + :start-after: _quest_marching_cubes_usage_start + :end-before: _quest_marching_cubes_usage_end + :language: C++ + +Output +------ + +``MarchingCubes`` stores its output internally until it is exported or cleared. +The simplest output path is to populate a ``mint::UnstructuredMesh``: + +.. literalinclude:: ../../examples/quest_marching_cubes_example.cpp + :start-after: _quest_marching_cubes_output_start + :end-before: _quest_marching_cubes_output_end + :language: C++ + +The generated mesh can optionally contain: + +* a field with the parent cell ID for each contour facet +* a field with the parent domain ID for each contour facet + +If host-side ``mint`` output is not desired, the class also exposes array-based +accessors for connectivity, node coordinates, parent cell IDs, and parent +domain IDs. Those arrays remain in the allocator space associated with the +``MarchingCubes`` object. + +Runtime policies and implementation choices +------------------------------------------- + +``MarchingCubes`` accepts an Axom runtime policy, so the contour generation can +run on the CPU or on supported GPU backends. The +``MarchingCubesDataParallelism`` enum controls which implementation is used: + +* ``byPolicy`` chooses the implementation that best matches the runtime policy. +* ``hybridParallel`` uses a partially parallel implementation that performs + well on CPUs. +* ``fullParallel`` uses a more fully parallel implementation that is intended + for highly parallel devices. + +Masking and repeated use +------------------------ + +The optional mask argument to ``setMesh()`` names a cell-centered integer field +used to restrict contour generation. After a mask field is supplied, the caller +can select which mask value to process by calling ``setMaskValue()`` before +``computeIsocontour()``. + +The same ``MarchingCubes`` object can be reused for multiple fields, masks, or +isovalues. ``computeIsocontour()`` appends new facets to the existing output, +while ``clearOutput()`` discards the accumulated contour data. + +MPI-parallel runs +----------------- + +For MPI-parallel runs, the input mesh may contain local and remote domains. The +algorithm itself is local to each rank and does not require communication. The +generated contour mesh uses locally unique node and cell numbering, so callers +that need globally unique IDs must renumber the output. diff --git a/src/axom/quest/docs/sphinx/linearize_curves.rst b/src/axom/quest/docs/sphinx/linearize_curves.rst new file mode 100644 index 0000000000..e74aa95fe8 --- /dev/null +++ b/src/axom/quest/docs/sphinx/linearize_curves.rst @@ -0,0 +1,50 @@ +.. ## Copyright (c) Lawrence Livermore National Security, LLC and other +.. ## Axom Project Contributors. See top-level LICENSE and COPYRIGHT +.. ## files for dates and other details. +.. ## +.. ## SPDX-License-Identifier: (BSD-3-Clause) + +.. _linearize-curves: + +***************** +Linearize Curves +***************** + +``quest::LinearizeCurves`` converts 2D NURBS contour geometry into a +``mint::SEGMENT`` mesh. This is useful when a downstream workflow wants a +discrete polyline approximation instead of evaluating directly on the original +curved representation. + +Quest provides two linearization strategies: + +* ``getLinearMeshUniform()`` samples each knot span with a fixed number of + segments. This gives predictable resolution and is often a good default when + the caller wants simple control over the approximation density. +* ``getLinearMeshNonUniform()`` refines adaptively until the polyline length is + within a requested percent error of a higher-resolution arc-length estimate. + This tends to place more segments where the curve needs them most. + +The winding-number example uses both modes: + +.. literalinclude:: ../../examples/quest_winding_number_2d.cpp + :start-after: _linearize_curves_start + :end-before: _linearize_curves_end + :language: C++ + +In practice, the uniform method is a good fit when a workflow already uses a +``segments per knot span`` control, while the adaptive method is more natural +when the caller wants to bound approximation error rather than choose a fixed +sampling density. + +Revolved Volume +--------------- + +The same utility also provides a revolved-volume helper for contour-based +shapes. This computes the volume generated by revolving the original curves +about the axis of revolution using quadrature on the curve representation, +rather than estimating the volume from an already linearized mesh. + +.. literalinclude:: ../../tests/quest_linearize_curves.cpp + :start-after: _revolved_volume_start + :end-before: _revolved_volume_end + :language: C++ diff --git a/src/axom/quest/docs/sphinx/read_mesh.rst b/src/axom/quest/docs/sphinx/read_mesh.rst index aa8dc0d3f3..d31aa4d135 100644 --- a/src/axom/quest/docs/sphinx/read_mesh.rst +++ b/src/axom/quest/docs/sphinx/read_mesh.rst @@ -6,26 +6,43 @@ .. _reading-mesh: -***************** -Reading in a mesh -***************** - -Applications commonly need to read a mesh file from disk. Quest provides the -``STLReader`` class, which can read binary or ASCII `STL`_ files, as well as the -``PSTLReader`` class for use in parallel codes. STL (stereolithography) -is a common file format for triangle surface meshes. The STL reader classes -will read the file from disk and build a ``mint::Mesh`` object. Quest also -provides the ``ProEReader`` class, for ASCII Pro/E files containing tetrahedra, -and the ``PProEReader`` class for use in parallel codes. PTC Creo is a modeling -application formerly known as Pro/ENGINEER, and its file format is in use among -Axom's users. +**************** +Reading Geometry +**************** + +Quest contains several readers that translate geometry files into Axom data +structures. The most common cases are STL surface meshes, Pro/E tetrahedral +meshes, and geometry inputs used by shaping workflows such as STEP, C2C, and +MFEM contour files. + +The STL and Pro/E readers produce ``mint::Mesh`` objects directly. Other +readers expose geometry in forms that are better suited to downstream Quest +algorithms, such as NURBS patches, NURBS curves, or curved polygons. + +Quest currently provides the following reader families: + +* ``STLReader`` and ``PSTLReader`` for ASCII or binary STL triangle meshes. +* ``ProEReader`` and ``PProEReader`` for ASCII Pro/E tetrahedral meshes. +* ``STEPReader`` and ``PSTEPReader`` for STEP B-Rep geometry represented as + trimmed NURBS patches, with optional triangulated output. +* ``C2CReader`` and ``PC2CReader`` for C2C contour files represented as + NURBS curves, when Axom is built with the C2C dependency. +* ``MFEMReader`` for MFEM contour files represented as curves or curved + polygons, when Axom is built with MFEM support. .. _STL: https://en.wikipedia.org/wiki/STL_(file_format) Reading an STL file ------------------- -The code examples are excerpts from the file ``/src/tools/mesh_tester.cpp``. +STL (stereolithography) is a common file format for triangle surface meshes. +Quest's STL readers load the file from disk and populate an +``mint::UnstructuredMesh`` containing triangles. STL stores triangles as a +"triangle soup", so downstream algorithms often need a cleanup pass to weld +duplicate vertices and check for defects. The next page describes that +workflow. + +The following example is excerpted from ``/src/tools/mesh_tester.cpp``. We include the STL reader header @@ -48,19 +65,22 @@ For convenience, we use typedefs in the axom namespace. :end-before: _read_stl_typedefs_end :language: C++ -The following example shows usage of the STLReader class: +The following example shows how to use ``STLReader``: .. literalinclude:: ../../../../tools/mesh_tester.cpp :start-after: _read_stl_file_start :end-before: _read_stl_file_end :language: C++ -After reading the STL file, the ``STLReader::getMesh`` method gives access to the -underlying mesh data. The reader may then be deleted. +After reading the STL file, ``STLReader::getMesh()`` gives access to the +loaded mesh data. Reading a Pro/E file -------------------- +Quest's ``ProEReader`` reads ASCII Pro/E tetrahedral meshes and can optionally +filter the tetrahedra during input. + As read by Axom, an ASCII Pro/E tet file contains: - Zero or more comment lines starting with a ``#`` character @@ -71,12 +91,11 @@ As read by Axom, an ASCII Pro/E tet file contains: - ``t`` lines, one for each tetrahedron; each line contains a contiguous integer ID starting at 1 and four integers specifying the tet's nodes -Reading an ASCII Pro/E tet file is similar to reading an STL file. The code -examples are excerpts from the file ``/src/axom/quest/examples/quest_proe_bbox.cpp``. -The Pro/E reader has the ability to read a subset of the mesh in the file, -defined by a user-supplied predicate function. The example code shows how -to use a convenience function to specify a predicate that keeps only tets -fully included in a user-supplied bounding box. +Reading an ASCII Pro/E tet file is similar to reading an STL file. The code +examples are excerpts from ``/src/axom/quest/examples/quest_proe_bbox.cpp``. +The example demonstrates one of the reader's useful features: selecting a +subset of the input tetrahedra using a predicate. In this case, the predicate +keeps only tetrahedra whose nodes fall inside a user-supplied bounding box. We include the ProEReader header @@ -99,11 +118,11 @@ For convenience, we specify some type aliases. :end-before: _read_proe_typealiases_end :language: C++ -The following example shows how to use the ProEReader class. +The following example shows how to use ``ProEReader``. Calling ``reader.setTetPredFromBoundingBox(bbox, false)``, as shown in the code, makes a tetrahedron predicate that accepts tets with all four nodes -falling in ``bbox`` and rejects others. Alternately, the user can specify -an arbitrary predicate function with ``setTetPred()``. If the user specifies +falling in ``bbox`` and rejects others. Alternately, the user can specify +an arbitrary predicate function with ``setTetPred()``. If the user specifies no tetrahedron predicate, the reader reads all tets in the file. .. literalinclude:: ../../examples/quest_proe_bbox.cpp @@ -111,5 +130,70 @@ no tetrahedron predicate, the reader reads all tets in the file. :end-before: _read_proe_file_end :language: C++ -After reading the Pro/E file, the ``ProEReader::getMesh`` method gives access -to the underlying mesh data. The reader may then be deleted. +After reading the Pro/E file, ``ProEReader::getMesh()`` gives access to the +loaded mesh data. + +Other Quest readers +------------------- + +Quest includes several other readers that are commonly used with shaping and +CAD-oriented workflows. + +``STEPReader`` +^^^^^^^^^^^^^^ + +``STEPReader`` reads trimmed STEP surfaces using Open Cascade. It can expose +the model as NURBS patches and trimming curves, query metadata such as patch +IDs and bounding boxes, and generate a triangulated ``mint::UnstructuredMesh`` +approximation of the model when a surface mesh is needed. + +This reader is available when Axom is configured with Open Cascade support. +The following example, excerpted from +``/src/axom/quest/examples/quest_winding_number_3d.cpp``, reads a STEP +file, loads its trimmed NURBS patches, and queries the model bounding box. + +.. literalinclude:: ../../examples/quest_winding_number_3d.cpp + :start-after: _read_step_file_start + :end-before: _read_step_file_end + :language: C++ + +When a triangle mesh approximation is needed, ``STEPReader`` can also +triangulate the loaded B-Rep: + +.. literalinclude:: ../../examples/quest_winding_number_3d.cpp + :start-after: _read_step_triangulate_start + :end-before: _read_step_triangulate_end + :language: C++ + +``C2CReader`` +^^^^^^^^^^^^^ + +``C2CReader`` reads contour files and stores the result as NURBS curves. This +reader is available when Axom is configured with the C2C dependency and is +primarily used by shaping workflows that revolve or sample contour geometry. + +The following example, excerpted from +``/src/axom/quest/examples/containment_driver.cpp``, reads a contour file +and linearizes the resulting curves into a segment mesh: + +.. literalinclude:: ../../examples/containment_driver.cpp + :start-after: _read_c2c_file_start + :end-before: _read_c2c_file_end + :language: C++ + +``MFEMReader`` +^^^^^^^^^^^^^^ + +``MFEMReader`` reads MFEM contour files and can return either individual curves +or grouped curved polygons. It is available when Axom is configured with MFEM +support. Quest uses this representation in workflows such as sampling-based +shaping with winding-number containment tests. + +The following example, excerpted from +``/src/axom/quest/examples/quest_winding_number_2d.cpp``, reads an MFEM +contour file into an array of NURBS curves: + +.. literalinclude:: ../../examples/quest_winding_number_2d.cpp + :start-after: _read_mfem_file_start + :end-before: _read_mfem_file_end + :language: C++ diff --git a/src/axom/quest/docs/sphinx/shaping.rst b/src/axom/quest/docs/sphinx/shaping.rst new file mode 100644 index 0000000000..b218a2e695 --- /dev/null +++ b/src/axom/quest/docs/sphinx/shaping.rst @@ -0,0 +1,341 @@ +.. ## Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +.. ## other Axom Project Developers. See the top-level LICENSE file for details. +.. ## +.. ## SPDX-License-Identifier: (BSD-3-Clause) + +.. _shaping-overview: + +Shaping Overview +================ + +Shaping is the process of overlaying additional detail into a mesh by converting +shape geometry into materials described as volume fractions within each mesh zone. +Shaping is used when it is not feasible or practical to directly build features +into the mesh itself. + +.. figure:: figs/shaping_overview.png + :width: 800px + + Shaping permits details to be added into meshes. + +Axom's Klee component describes the models used for shaping. A Klee shape set +contains the shape geometry reference, its material name, replacement rules, and +any transforms that should be applied before shaping. Quest provides the +algorithms that read those shapes, compare them against a target mesh, and +generate the material volume fractions. + +Quest provides two shaping implementations: + +* ``SamplingShaper`` estimates overlap by sampling points in each target zone + and evaluating in/out tests against the shape. +* ``IntersectionShaper`` computes overlap geometrically by intersecting the + target mesh with discretized shape geometry. + +Both shapers share the same high-level pipeline and both write volume fractions +as fields named ``vol_frac_`` on the target mesh. + +For MFEM-based shaping workflows, those material fields are typically +``mfem::GridFunction`` objects registered in the target +``MFEMSidreDataCollection``. The caller chooses the output field order. In the +sampling workflow, that order is set explicitly with +``SamplingShaper::setVolumeFractionOrder()``; in the example driver, it is +controlled by the selected output order. For Blueprint-based shaping workflows, +the same material information is written as fields on the Blueprint mesh. + + +.. _shaping-pipeline: + +Shaping Pipeline +---------------- + +Shaping involves creating a target mesh and data collection, reading a shape +set, creating a shaper, and then iterating over the shapes in the set. For each +shape, the shaper loads the geometry, prepares a spatial query structure, runs +the query against the target mesh, applies replacement rules, and cleans up any +temporary state before moving to the next shape. + +.. figure:: figs/shaping_pipeline.svg + :width: 800px + + Quest shaping pipeline from Klee shape descriptions to material volume fraction fields. + +First, we include relevant Axom headers: + +.. code-block:: c++ + + #include + #include + #include + #include + + using quest = axom::quest; + using klee = axom::klee; + using slic = axom::slic; + using sidre = axom::sidre; + +Quest shaping APIs operate on either: + +* MFEM meshes stored in ``sidre::MFEMSidreDataCollection`` objects +* Blueprint meshes stored in a ``sidre::Group`` or ``conduit::Node`` + +The example below uses an MFEM mesh and an ``MFEMSidreDataCollection`` to store +the volume fraction fields. + +More information on MFEM is covered at the `MFEM examples page `_. + +The MFEM mesh also needs an associated data collection, ``shapingDC``, to +contain the grid functions. Axom provides ``MFEMSidreDataCollection``, a +derived class of MFEM's ``DataCollection`` that interoperates with Sidre. + +.. literalinclude:: ../../examples/shaping_driver.cpp + :start-after: _load_mesh_start + :end-before: _load_mesh_end + :language: C++ + + +Next, create the desired shaper and configure its parameters. Some settings live +in the common ``Shaper`` base class and apply to both shaping methods. Examples +include the runtime policy, the linearization density for curved geometry, the +vertex welding threshold for surface meshes, verbosity, and the controls for +dynamic refinement based on a percent error target. + +The shaper will operate on shapes from a Klee shape set: + +.. code-block:: c++ + + auto shapeSet = klee::readShapeSet("/path/to/klee/file"); + + +The shaper can also be pre-initialized with volume fractions supplied by the +calling code. This is optional, but it is useful when a simulation mesh already +contains background or previously shaped materials. Volume fraction fields use +the ``vol_frac_`` prefix followed by the material name. + +.. literalinclude:: ../../examples/shaping_driver.cpp + :start-after: _import_volume_fractions_start + :end-before: _import_volume_fractions_end + :language: C++ + + +After the target mesh, shaper, and shape set are ready, the shaping pipeline is +the same for both ``SamplingShaper`` and ``IntersectionShaper``. Each shape is: + +#. loaded from its geometry file +#. prepared for querying +#. queried against the target mesh +#. applied through its replacement rules +#. finalized so the shaper is ready for the next shape + +.. literalinclude:: ../../examples/shaping_driver.cpp + :start-after: _shaping_pipeline_begin + :end-before: _shaping_pipeline_end + :language: C++ + +After all shapes have been processed, the shaper adjusts the accumulated volume +fractions so the material fields on the target mesh are ready for output. For +MFEM targets, the result is usually a set of ``vol_frac_`` +``mfem::GridFunction`` fields, one per material, that can be saved with the +data collection or used directly by downstream code. + + +.. _sampling-shaper: + +Sampling Shaper +--------------- + +``SamplingShaper`` creates volume fractions by sampling points inside each +target zone and determining whether those points are inside the shape. The +sampled results are then converted into zone-centered or higher-order volume +fraction fields. + +On MFEM target meshes, these outputs are material-specific +``mfem::GridFunction`` objects. The field order is chosen by the caller, so the +result can be piecewise constant or higher order depending on the needs of the +simulation or analysis workflow. + +``SamplingShaper`` supports: + +* 2D and 3D target meshes +* STL and Pro/E geometry loaded as discrete surfaces +* contour-based workflows, including MFEM contours and revolved shaping through + point projection +* higher-order output volume fraction fields +* CPU execution, with additional runtime-policy-dependent samplers for some + 3D primitive workflows + +.. figure:: figs/sampling.png + :width: 300px + + Sampling shaper tests whether points in a zone are in/out for a shape. + +Sampler selection +^^^^^^^^^^^^^^^^^ + +``SamplingShaper`` uses one of three internal sampler types to answer the +point-vs-shape queries that drive the volume fraction calculation. The sampler +is currently chosen primarily from the shape representation that Quest is +working with and, in one case, from the user-selected sampling method. The +examples below describe the current implementation without implying that the +samplers are inherently tied to specific readers. + +``InOutSampler`` +"""""""""""""""" + +The in/out sampler uses Quest's spatial-index-based containment queries on +discrete geometry. It is the default sampler for shapes that Quest has turned +into discrete curves or surfaces. In the current implementation, this includes: + +* 2D contour geometry loaded from C2C files +* MFEM contour geometry when ``SamplingMethod::InOut`` is selected +* 3D surface geometry loaded from STL files + +In practice, choose the in/out sampler when the shape is being queried through +discrete line or surface geometry and the standard containment query is +appropriate. In the example driver, this is the behavior selected by +``samplingShaper->setSamplingMethod(quest::SamplingShaper::SamplingMethod::InOut);`` +or by leaving the method at its default. + +``PrimitiveSampler`` +"""""""""""""""""""" + +The primitive sampler operates directly on primitive volumetric elements rather +than a surface-based in/out query. In the current implementation, this path is +used when Quest is sampling tetrahedral shape geometry, with a backend chosen +from the active runtime policy. + +Use this path when the shape geometry is already volumetric and Quest can avoid +going through a surface containment query. This is also the sampler family that +maps naturally onto the supported CPU and GPU execution policies for these +primitive-based cases. + +``WindingNumberSampler`` +"""""""""""""""""""""""" + +The winding-number sampler evaluates winding numbers on a curved contour +representation instead of using the standard in/out spatial index. In the +current implementation, this option is available for MFEM contour geometry. + +Select it explicitly with +``samplingShaper->setSamplingMethod(quest::SamplingShaper::SamplingMethod::WindingNumber);``. +Quest will use it when the shape format is MFEM and the sampling method is set +to ``WindingNumber``. + +This sampler is useful when the curved MFEM representation itself is the +important source of truth and you want the containment query to operate on that +curve-based model. It is currently a CPU-oriented path. + +For the broader direct, linearized, and fast approximate generalized +winding-number workflows, see :doc:`Winding Numbers `. + +Curve-based workflows that need a discrete segment representation can use +:doc:`Linearize Curves ` to convert NURBS contours into a +polyline mesh before sampling or winding-number evaluation. + +Summary +""""""" + +In short: + +* choose ``InOut`` for the standard default behavior on shapes queried through + discrete curves or surfaces +* choose ``WindingNumber`` when curved contour geometry should be queried + through winding numbers rather than the standard in/out path +* expect ``PrimitiveSampler`` to be chosen automatically when Quest is sampling + supported volumetric primitive geometry + + +Accuracy +^^^^^^^^ + +The main accuracy controls are: + +* ``setSamplesPerKnotSpan()`` controls how finely curved input geometry is + linearized before sampling. +* ``setQuadratureOrder()`` controls how many sample points are used in each + target zone. +* ``setVolumeFractionOrder()`` controls the polynomial order of the output + volume fraction field. + +For curved inputs, increasing the number of samples per knot span improves the +geometric approximation of the shape. Increasing the quadrature order improves +the overlap estimate within each target zone. + +.. code-block:: c++ + + shaper->setSamplesPerKnotSpan(25); + shaper->setQuadratureOrder(5); + shaper->setVolumeFractionOrder(2); + + +Point Projection +^^^^^^^^^^^^^^^^ + +``SamplingShaper`` can use point projectors to map points from the target mesh +into the coordinate system used by the shape query. One common use case is +projecting 3D mesh points into a 2D RZ space so that a 2D contour can define a +revolved 3D shape. + +.. literalinclude:: ../../examples/shaping_driver.cpp + :start-after: _point_projection_obj_begin + :end-before: _point_projection_obj_end + :language: C++ + +.. literalinclude:: ../../examples/shaping_driver.cpp + :start-after: _point_projection_begin + :end-before: _point_projection_end + :language: C++ + +.. _intersection-shaper: + +Intersection Shaper +-------------------- + +``IntersectionShaper`` computes overlap geometrically instead of sampling. It +discretizes the shape into intersection-friendly primitives and intersects those +primitives with the target mesh zones to compute volume fractions. + +Depending on the input, the generated intersection geometry differs: + +* 2D C2C contours can be refined into segments and intersected against 2D meshes. +* 3D shaping from 2D contours revolves the refined contour into truncated-cone + geometry that is approximated using octahedra. +* Pro/E tetrahedral meshes and analytical 3D shapes are converted into + tetrahedral intersection geometry. +* STL input can be used for 2D triangle-based workflows and other discrete + surface cases handled by the implementation. + +``IntersectionShaper`` supports 3D workflows on CPUs and supported GPU +backends, and it can operate on MFEM or Blueprint target meshes. + +.. figure:: figs/intersection.png + :width: 400px + + Intersection shaper creates revolved geometry for a shape and determines volume intersection with target mesh zones. + +Accuracy +^^^^^^^^^ + +The main accuracy controls for ``IntersectionShaper`` are the refinement level +and the optional percent-error target. The refinement level controls how finely +the internal intersection geometry is subdivided. When a percent error is +provided, Quest can switch to dynamic refinement and keep refining until the +estimated volume error falls below the requested tolerance. + +.. code-block:: c++ + + shaper->setPercentError(0.02); + shaper->setRefinementType(quest::Shaper::RefinementDynamic); + +Materials and replacement rules +------------------------------- + +Both shapers write their results as material volume fractions on the target +mesh. ``IntersectionShaper`` also maintains a free-material field, named +``free`` by default, to represent any volume that has not yet been claimed by a +user-defined material. This field is especially useful when shapes overwrite +existing materials through replacement rules. + +Quest applies replacement rules from the Klee shape description when it merges +each shape's volume fractions into the existing material state. For more on the +``replaces`` and ``does_not_replace`` properties, see Klee's +:ref:`Overlay Rules ` documentation. diff --git a/src/axom/quest/docs/sphinx/winding_number.rst b/src/axom/quest/docs/sphinx/winding_number.rst new file mode 100644 index 0000000000..e4d7faa936 --- /dev/null +++ b/src/axom/quest/docs/sphinx/winding_number.rst @@ -0,0 +1,71 @@ +.. ## Copyright (c) Lawrence Livermore National Security, LLC and other +.. ## Axom Project Contributors. See top-level LICENSE and COPYRIGHT +.. ## files for dates and other details. +.. ## +.. ## SPDX-License-Identifier: (BSD-3-Clause) + +.. _winding-number: + +**************** +Winding Numbers +**************** + +Quest provides generalized winding number workflows for querying curved and +linearized geometry on MFEM query meshes. These workflows are useful when the +goal is not just a binary in/out test at quadrature points, but a +winding-number field and its derived in/out classification over a +user-defined query mesh. + +At a high level, there are two paths: + +* A direct path that evaluates winding number on curved geometry, such as NURBS + curves in 2D or NURBS patches in 3D. This is the most natural choice when + the curved representation is the source of truth and preserving that geometry + in the query is more important than maximizing throughput. +* A linearized path that first replaces the curved shape with segments or + triangles and then evaluates the same winding-number quantity on that + discretized geometry. This path is useful when the input is already discrete + or when a linearized representation is acceptable for the query. + +Basic Workflow +-------------- + +The 2D and 3D winding-number examples follow the same basic pattern: + +* choose a direct curved or linearized query method +* generate an MFEM query mesh and its associated ``winding`` and ``inout`` fields +* preprocess the input geometry for the selected method +* evaluate the query on the mesh + +The 2D example shows this structure directly: + +.. literalinclude:: ../../examples/quest_winding_number_2d.cpp + :start-after: _gwn_query_workflow_start + :end-before: _gwn_query_workflow_end + :language: C++ + +Fast Approximate Methods +------------------------ + +The "fast" winding-number methods build on the linearized path. They keep the +same basic query quantity, but add preprocessing over the segment or triangle +representation so that large batches of queries can be evaluated more +efficiently. In the current implementation, that acceleration uses a hierarchy +over the linearized geometry together with precomputed moment data to +approximate the contribution of well-separated clusters. + +In practice: + +* use the direct workflow when curved geometry fidelity matters most +* use the linearized workflow when a segment or triangle approximation is + already available or acceptable +* use the fast linearized workflow when query counts are high enough that extra + preprocessing is worth the reduction in per-query cost + +Query Outputs +------------- + +The example programs ``quest_winding_number_2d.cpp`` and +``quest_winding_number_3d.cpp`` show these workflows end to end, including +query-mesh setup, preprocessing, winding-number evaluation, and creation of the +derived ``inout`` field by rounding the winding-number result. diff --git a/src/axom/quest/examples/containment_driver.cpp b/src/axom/quest/examples/containment_driver.cpp index 7d6c5399a6..53b3e59486 100644 --- a/src/axom/quest/examples/containment_driver.cpp +++ b/src/axom/quest/examples/containment_driver.cpp @@ -72,6 +72,7 @@ class ContainmentDriver void loadContourMesh(const std::string& inputFile, int segmentsPerKnotSpan) { AXOM_ANNOTATE_SCOPE("load c2c"); + // _read_c2c_file_start quest::C2CReader reader; reader.setFileName(inputFile); reader.read(); @@ -82,6 +83,7 @@ class ContainmentDriver lin.getLinearMeshUniform(reader.getCurvesView(), static_cast(m_surfaceMesh.get()), segmentsPerKnotSpan); + // _read_c2c_file_end } #else void loadContourMesh(const std::string& inputFile, int segmentsPerKnotSpan) diff --git a/src/axom/quest/examples/delaunay_triangulation.cpp b/src/axom/quest/examples/delaunay_triangulation.cpp index 8b4486d288..23dd598c08 100644 --- a/src/axom/quest/examples/delaunay_triangulation.cpp +++ b/src/axom/quest/examples/delaunay_triangulation.cpp @@ -21,6 +21,11 @@ #include "axom/fmt.hpp" #include "axom/CLI11.hpp" +// _quest_delaunay_include_start +using Delaunay2D = axom::quest::Delaunay<2>; +using Delaunay3D = axom::quest::Delaunay<3>; +// _quest_delaunay_include_end + /// Struct to parse and contain command line arguments struct Input { @@ -133,6 +138,7 @@ void run_delaunay(const Input& params) axom::utilities::Timer timer(true); // Create initial Delaunay triangulation over bounding box + // _quest_delaunay_basic_start Delaunay dt; dt.initializeBoundary(bbox); @@ -158,6 +164,7 @@ void run_delaunay(const Input& params) //Remove the starting rectangular box dt.removeBoundary(); + // _quest_delaunay_basic_end timer.stop(); @@ -175,8 +182,10 @@ void run_delaunay(const Input& params) { timer.reset(); timer.start(); + // _quest_delaunay_validate_start dt.getMeshData()->isValid(true); dt.isValid(true); + // _quest_delaunay_validate_end timer.stop(); SLIC_INFO(axom::fmt::format("Validation took {} seconds", timer.elapsedTimeInSec())); } @@ -185,7 +194,9 @@ void run_delaunay(const Input& params) { std::string fname = axom::fmt::format("{}.vtk", outputVTKFile); SLIC_INFO(axom::fmt::format("Writing out final Delaunay complex to file '{}'", fname)); + // _quest_delaunay_output_start dt.writeToVTKFile(fname); + // _quest_delaunay_output_end } SLIC_INFO("Done!"); diff --git a/src/axom/quest/examples/quest_marching_cubes_example.cpp b/src/axom/quest/examples/quest_marching_cubes_example.cpp index 3c19f9734f..b5e0ab9ff6 100644 --- a/src/axom/quest/examples/quest_marching_cubes_example.cpp +++ b/src/axom/quest/examples/quest_marching_cubes_example.cpp @@ -841,9 +841,11 @@ struct ContourTestBase { AXOM_ANNOTATE_SCOPE("MCInit"); initializationTimer.start(); + // _quest_marching_cubes_init_start mcPtr = std::make_unique(params.policy, s_allocatorId, params.dataParallelism); mcPtr->setMesh(computationalMesh.asConduitNode(), "mesh", "mask"); + // _quest_marching_cubes_init_end initializationTimer.stop(); } auto& mc = *mcPtr; @@ -865,6 +867,7 @@ struct ContourTestBase params.objectRepCount, i, params.contourGenCount)); + // _quest_marching_cubes_usage_start mc.clearOutput(); m_strategyFacetPrefixSum.clear(); m_strategyFacetPrefixSum.push_back(0); @@ -894,6 +897,7 @@ struct ContourTestBase } m_strategyFacetPrefixSum.push_back(mc.getContourFacetCount()); } + // _quest_marching_cubes_usage_end } contourGenLoopTimer.stop(); } @@ -956,7 +960,9 @@ struct ContourTestBase #endif axom::utilities::Timer extractTimer(false); extractTimer.start(); + // _quest_marching_cubes_output_start mc.populateContourMesh(contourMesh, m_parentCellIdField, m_domainIdField); + // _quest_marching_cubes_output_end extractTimer.stop(); printTimingStats(extractTimer, "extract"); diff --git a/src/axom/quest/examples/quest_winding_number_2d.cpp b/src/axom/quest/examples/quest_winding_number_2d.cpp index 5e29371646..164082082e 100644 --- a/src/axom/quest/examples/quest_winding_number_2d.cpp +++ b/src/axom/quest/examples/quest_winding_number_2d.cpp @@ -497,6 +497,7 @@ int main(int argc, char** argv) AXOM_ANNOTATE_SCOPE("read_mesh"); axom::quest::MFEMReader mfem_reader; + // _read_mfem_file_start mfem_reader.setFileName(input.inputFile); const int ret = mfem_reader.read(curves); @@ -505,6 +506,7 @@ int main(int argc, char** argv) SLIC_ERROR("Failed to read MFEM file."); return 1; } + // _read_mfem_file_end } // Linearize the input curves if asked for @@ -514,6 +516,7 @@ int main(int argc, char** argv) AXOM_ANNOTATE_SCOPE("linearization"); axom::utilities::Timer timer(true); + // _linearize_curves_start axom::quest::LinearizeCurves lc; if(input.useUniformLinearization) { @@ -523,6 +526,7 @@ int main(int argc, char** argv) { lc.getLinearMeshNonUniform(curves.view(), &poly_mesh, input.percentError); } + // _linearize_curves_end timer.stop(); SLIC_INFO(axom::fmt::format( @@ -553,6 +557,7 @@ int main(int argc, char** argv) // if user did not provide a bounding box, user input bounding box scaled by 10% mfem::DataCollection dc("winding_query"); { + // _gwn_query_workflow_start // Create the desired winding number query instance auto wn_query = make_gwn_query(input.policy, app.got_subcommand("linearize_curves"), input.approximation_order); @@ -582,6 +587,7 @@ int main(int argc, char** argv) // Run the query std::visit([&](auto& wn) { wn.query(dc, input.tol); }, wn_query); + // _gwn_query_workflow_end } // Postprocess query results: norms, ranges, and integral statistics diff --git a/src/axom/quest/examples/quest_winding_number_3d.cpp b/src/axom/quest/examples/quest_winding_number_3d.cpp index e043e0c2a5..faa82d8b15 100644 --- a/src/axom/quest/examples/quest_winding_number_3d.cpp +++ b/src/axom/quest/examples/quest_winding_number_3d.cpp @@ -361,6 +361,7 @@ int main(int argc, char** argv) AXOM_ANNOTATE_SCOPE("read_step"); axom::quest::STEPReader step_reader; + // _read_step_file_start step_reader.setFileName(input.inputFile); step_reader.setVerbosity(input.verbose); @@ -374,6 +375,7 @@ int main(int argc, char** argv) read_timer.stop(); shape_bbox = step_reader.getBRepBoundingBox(); + // _read_step_file_end int num_trimming_curves = 0; for(const auto& patch : step_reader.getPatchArray()) @@ -395,6 +397,7 @@ int main(int argc, char** argv) read_timer.reset(); read_timer.start(); AXOM_ANNOTATE_SCOPE("triangulation"); + // _read_step_triangulate_start const int tc = step_reader.getTriangleMesh(&tri_mesh, input.linear_deflection, input.angular_deflection, @@ -405,6 +408,7 @@ int main(int argc, char** argv) SLIC_ERROR("Failed to triangulate STEP geometry."); return 1; } + // _read_step_triangulate_end read_timer.stop(); SLIC_INFO( diff --git a/src/axom/quest/examples/shaping_driver.cpp b/src/axom/quest/examples/shaping_driver.cpp index 8210d30d06..ee4d6b2f2c 100644 --- a/src/axom/quest/examples/shaping_driver.cpp +++ b/src/axom/quest/examples/shaping_driver.cpp @@ -51,6 +51,7 @@ namespace using Point2D = primal::Point; using Point3D = primal::Point; +// _point_projection_obj_begin struct AxisymmetricProjector32 { AXOM_HOST_DEVICE Point2D operator()(Point3D pt) const @@ -61,6 +62,7 @@ struct AxisymmetricProjector32 return Point2D {z, sqrt(x * x + y * y)}; } }; +// _point_projection_obj_end struct Projector23 { @@ -535,6 +537,7 @@ int main(int argc, char** argv) //--------------------------------------------------------------------------- // Set up DataCollection for shaping //--------------------------------------------------------------------------- + // _load_mesh_start mfem::Mesh* shapingMesh = nullptr; constexpr bool dc_owns_data = true; sidre::MFEMSidreDataCollection shapingDC("shaping", shapingMesh, dc_owns_data); @@ -546,6 +549,7 @@ int main(int argc, char** argv) (pmesh != nullptr) ? new mfem::ParMesh(*pmesh) : new mfem::Mesh(*originalMeshDC->GetMesh()); shapingDC.SetMesh(shapingMesh); } + // _load_mesh_end AXOM_ANNOTATE_END("load mesh"); printMeshInfo(shapingDC.GetMesh(), "After loading"); @@ -596,7 +600,9 @@ int main(int argc, char** argv) // register point projectors if(shapingDC.GetMesh()->Dimension() == 3) { + // _point_projection_begin samplingShaper->setPointProjector32(AxisymmetricProjector32 {}); + // _point_projection_end } else if(shapingDC.GetMesh()->Dimension() == 2) { @@ -618,6 +624,7 @@ int main(int argc, char** argv) //--------------------------------------------------------------------------- // Project initial volume fractions, if applicable //--------------------------------------------------------------------------- + // _import_volume_fractions_start if(auto* samplingShaper = dynamic_cast(shaper)) { AXOM_ANNOTATE_SCOPE("import initial volume fractions"); @@ -651,6 +658,7 @@ int main(int argc, char** argv) // Project provided volume fraction grid functions as quadrature point data samplingShaper->importInitialVolumeFractions(initial_grid_functions); } + // _import_volume_fractions_end AXOM_ANNOTATE_END("setup shaping problem"); AXOM_ANNOTATE_END("init"); @@ -659,6 +667,7 @@ int main(int argc, char** argv) //--------------------------------------------------------------------------- SLIC_INFO(axom::fmt::format("{:=^80}", "Sampling InOut fields for shapes")); AXOM_ANNOTATE_BEGIN("shaping"); + // _shaping_pipeline_begin for(const auto& shape : params.shapeSet.getShapes()) { const std::string shapeFormat = shape.getGeometry().getFormat(); @@ -698,6 +707,7 @@ int main(int argc, char** argv) shaper->finalizeShapeQuery(); slic::flushStreams(); } + // _shaping_pipeline_end AXOM_ANNOTATE_END("shaping"); //--------------------------------------------------------------------------- diff --git a/src/axom/quest/tests/quest_linearize_curves.cpp b/src/axom/quest/tests/quest_linearize_curves.cpp index fa0e5ad5a0..c224c964a7 100644 --- a/src/axom/quest/tests/quest_linearize_curves.cpp +++ b/src/axom/quest/tests/quest_linearize_curves.cpp @@ -130,8 +130,10 @@ TEST(quest_linearize_curves, revolved_volume) // Compute the revolved volume const double expectedVolume = 4.15330715158103; axom::quest::LinearizeCurves lin; + // _revolved_volume_start const auto transform = axom::numerics::Matrix::identity(4); const double revolvedVolume = lin.getRevolvedVolume(curves.view(), transform); + // _revolved_volume_end EXPECT_NEAR(revolvedVolume, expectedVolume, 3.e-3); } diff --git a/src/tools/mesh_tester.cpp b/src/tools/mesh_tester.cpp index 976b34d940..6ff1a676f6 100644 --- a/src/tools/mesh_tester.cpp +++ b/src/tools/mesh_tester.cpp @@ -22,8 +22,8 @@ // _read_stl_include1_start #include "axom/quest/io/STLReader.hpp" -#include "axom/quest/io/STLWriter.hpp" // _read_stl_include1_end +#include "axom/quest/io/STLWriter.hpp" // _check_repair_include_start #include "axom/quest/MeshTester.hpp" // _check_repair_include_end