This example demonstrates iterated CSG: we repeatedly subtract a small sphere (“tool”) from a cube (“workpiece”) along a circular tool path. It showcases:
- The C++17 helper style (
Context::executewith a lambda) for concise command-buffer setup - Using
Operation::input/Operation::outputto pass persistent results between iterations - Predictable, output-sensitive performance for iterated Booleans
Note:
This C++ example uses
ExampleFramework.hh for compact POD types and tiny utilities.
Result

CMake setup
To use the C++ 17 API from CMake, add the Solidean C++ 17 language target and link it to your project:
add_subdirectory(path/to/solidean/lang/cpp17) # Add the Solidean C++ 17 API to your project
target_link_libraries(YourProject PRIVATE Solidean::Cpp17) # Link against the Solidean C++ 17 API
Code
#include <chrono>
#include <cmath>
#include <cstring>
#include <iostream>
#include <vector>
#include <solidean.hh>
#include "ExampleFramework.hh"
int main()
{
auto ctx = solidean::Context::create();
// uniform cube of 20 units side length, centered at the origin
auto arithmetic = ctx->createExactArithmetic(10.f);
// Define a cube-shaped workpiece of sidelength 1.0
auto const workpieceTriangles = example::createCube();
// Define an icosphere with radius 0.15
auto const icoTriangles = example::createIcoSphere(3, {0.0f, 0.0f, 0.0f}, 0.15f);
// Compute a simple circular tool path
std::vector<example::pos3> toolPath;
auto const steps = 50;
for (auto i = 0; i < steps; ++i)
{
constexpr auto pi = float(3.14159265358979323846);
auto const x = 0.35 * std::cos(double(i) / steps * 2.0 * pi);
auto const z = 0.35 * std::sin(double(i) / steps * 2.0 * pi);
toolPath.push_back({float(x), 0.5f, float(z)});
}
// Print some information on the inputs
std::cout << "Number of triangles in workpiece: " << workpieceTriangles.size() << std::endl;
std::cout << "Number of triangles in sphere: " << icoTriangles.size() << std::endl;
// Create the workpiece - the triangle data is reinterpreted as a solidean triangle type
auto workpiece = ctx->createMeshFromTrianglesF32(solidean::as_triangle3_span(workpieceTriangles), *arithmetic);
std::cout << "Iteratively subtracting sphere from workpiece in circular motion in " << steps << " iterations" << std::endl;
// Measure time of actual subtraction task
auto const start = std::chrono::high_resolution_clock::now();
// Iterate over the tool path
for (auto const& p : toolPath)
{
// Create "tool" triangles at current tool path position
std::vector<example::triangle> stepTriangles = icoTriangles;
for (auto& t : stepTriangles)
{
t.p0 += p;
t.p1 += p;
t.p2 += p;
}
// Perform one subtraction iteration (workpiece - tool)
workpiece = ctx->execute( //
*arithmetic,
[&](solidean::Operation& op)
{
auto meshA = op.input(*workpiece);
auto meshB = op.importFromTrianglesF32(solidean::as_triangle3_span(stepTriangles), solidean::MeshType::Supersolid);
return op.output(op.difference(meshA, meshB));
},
solidean::ExecuteMode::Multithreaded);
std::cout << "." << std::flush;
}
std::cout << std::endl;
auto const end = std::chrono::high_resolution_clock::now();
auto const duration = std::chrono::duration<double>(end - start).count();
std::cout << "Processed " << steps << " iterations in " << duration * 1000.0 << "ms (~" << duration * 1000.0 / steps << "ms per iteration)" << std::endl;
// Export the final solidean data to floating point triangles for potential further evaluation or display
auto blob = ctx->execute(*arithmetic,
[&](solidean::Operation& op)
{
auto m = op.input(*workpiece);
return op.exportToTrianglesF32(m);
});
// The data blob contains (immutable) unrolled triangle data
auto const triangleSpan = blob->getTrianglesF32<example::triangle>();
// Copy the triangle data to a vector, e.g. for further processing
auto const triangles = std::vector<example::triangle>(triangleSpan.begin(), triangleSpan.end());
std::cout << "Total number of triangles in processed workpiece: " << triangles.size() << std::endl;
return EXIT_SUCCESS;
}
Notes
-
High-level flow:
- Create a
Context, choose anExactArithmeticbounding box. - Build a workpiece and a tool shape (cube and icosphere here).
- For each tool position, run
execute(*arithmetic, lambda)where the lambda records:
Operation::input→Operation::importFromTrianglesF32→Operation::difference→Operation::output. - After the loop, export with
Operation::exportToTrianglesF32.
- Create a
-
C++17 helper vs. abstract API:
- The C++17 API uses
Context::executewith a lambda that returns the desired output handle (e.g.,op.output(...)or an export). - The abstract API exposes the same steps but typically requires explicit sequencing; the C++17 helper wraps the record-then-execute pattern.
- The C++17 API uses
-
Passing results across iterations:
Operation::outputmaterializes the result as a persistentMesh; the next iteration re-enters it viaOperation::input.- This is the correct way to chain operations; using a
MeshOperandacross different operations is invalid by design.
-
Geometry guarantees:
- The tool mesh is imported as
MeshType::Supersolid, since the a generic tool mesh could overlap itself (it does not in this example though, so the defaultMeshType::Solidwould have been fine as well). - The exactness model is unchanged: float inputs are storage only and are converted to internal exact arithmetic upon import.
- The tool mesh is imported as
-
Execution mode & performance:
- The example uses
ExecuteMode::Multithreaded(default) for throughput. - Iterated Booleans are engineered for output-sensitive behavior; performance scales with what changes, not with full recomputation each step.
- The example uses
-
Export:
- The final result is exported as unrolled triangles. For connectivity and smaller size, prefer
ExportFormat::IndexedTriangleswith position options fromExportOption.
- The final result is exported as unrolled triangles. For connectivity and smaller size, prefer
-
Timing:
- The example times only the iterative subtraction loop to highlight Boolean performance in isolation.