The goal of this tutorial is to simulate a simple milling workflow with iterated Booleans. We will:
- Create a cube “workpiece” and a small sphere “tool”
- Generate a circular tool path (many steps)
- For each step, subtract the tool from the current workpiece
- Pass results between iterations using
Operation::input/Operation::output - Export the final result and measure timing
Along the way, you’ll use: Context, ExactArithmetic, Operation::input, Operation::difference, Operation::output, Operation::exportToTrianglesF32, and ExecuteMode.
ExampleFramework.hh to provide tiny POD types (pos3, triangle) and helpers.
1) Project setup (CMake)
Add the C++17 binding and link it:
add_subdirectory(path/to/solidean/lang/cpp17)
target_link_libraries(YourProject PRIVATE Solidean::Cpp17)
Ensure the Solidean dynamic library is discoverable at runtime (same folder as your binary or on your system path).
2) Minimal program
We iteratively subtract a translated sphere from a cube along a circular path, timing only the Boolean loop.
#include <chrono>
#include <cmath>
#include <iostream>
#include <vector>
#include <solidean.hh>
#include "ExampleFramework.hh" // pos3, triangle, createCube, createIcoSphere
int main() {
// 1) Context + exact arithmetic (bounding box [-10,+10]^3)
auto ctx = solidean::Context::create();
auto arithmetic = ctx->createExactArithmetic(10.0f);
// 2) Workpiece: unit cube centered at origin (returns std::vector<example::triangle>)
auto workpieceTris = example::createCube();
// 3) Tool prototype: small icosphere at origin, radius 0.15
const float toolRadius = 0.15f;
auto toolPrototype = example::createIcoSphere(/*subdiv*/3, /*center*/{0.0f, 0.0f, 0.0f}, toolRadius);
// Create persistent mesh for the workpiece
auto workpiece = ctx->createMeshFromTrianglesF32(
solidean::as_triangle3_span(workpieceTris), *arithmetic);
// 4) Build a circular tool path (XZ circle at y=+0.5)
std::vector<example::pos3> path;
const int steps = 50;
const float radius = 0.35f;
for (int i = 0; i < steps; ++i) {
float t = float(i) / float(steps);
float x = radius * std::cos(2.0f * float(M_PI) * t);
float z = radius * std::sin(2.0f * float(M_PI) * t);
path.push_back({x, 0.5f, z});
}
std::cout << "Iteratively subtracting tool along a circular path (" << steps << " steps)..." << std::endl;
// 5) Iterate: translate tool → subtract from current workpiece → materialize new workpiece
auto t0 = std::chrono::high_resolution_clock::now();
for (int i = 0; i < steps; ++i) {
// Translate the tool prototype to the current path position
std::vector<example::triangle> toolStep = toolPrototype;
for (auto& tri : toolStep) {
tri.p0 += path[i];
tri.p1 += path[i];
tri.p2 += path[i];
}
// Execute one subtraction step and materialize the new workpiece
workpiece = ctx->execute( //
*arithmetic,
[&](solidean::Operation& op) {
auto in = op.input(*workpiece);
auto cut = op.importFromTrianglesF32(
solidean::as_triangle3_span(toolStep),
solidean::MeshType::Supersolid); // conservative guarantee
return op.output(op.difference(in, cut));
});
std::cout << "." << std::flush;
}
std::cout << std::endl;
auto t1 = std::chrono::high_resolution_clock::now();
double ms = std::chrono::duration<double, std::milli>(t1 - t0).count();
std::cout << "Finished " << steps << " iterations in " << ms
<< " ms (~" << (ms / steps) << " ms/step)" << std::endl;
// 6) Export final result as unrolled triangles (use indexed if you need connectivity)
auto blob = ctx->execute(*arithmetic, [&](solidean::Operation& op) {
auto m = op.input(*workpiece);
return op.exportToTrianglesF32(m);
});
auto triSpan = blob->getTrianglesF32<example::triangle>();
std::cout << "Final triangle count: " << triSpan.size() << std::endl;
return EXIT_SUCCESS;
}
3) What you learned
-
Chaining via persistent meshes:
UseOperation::outputto materialize a persistentMeshat the end of each step, then feed it back withOperation::inputin the next iteration.
This is the correct way to carry results across operations (do not reuseMeshOperandobjects across different operations). -
Record–then–execute with a lambda:
The C++17 helper callsContext::executewith a lambda that records the step (input → boolean → output) and returns the final handle/export. -
Output-sensitive performance:
Iterated Booleans are designed to scale with what changes each step rather than full recomputation, yielding predictable iteration times. -
Geometry guarantees:
If a tool might self-intersect, import it asMeshType::Supersolidto declare the right guarantees. -
Export for visualization or downstream processing:
UseOperation::exportToTrianglesF32for a quick, unrolled preview. For connectivity and size, prefer indexed exports.
4) Variations to try
- Swap the path for a lawnmower or spiral pattern and compare iteration timing.
- Export indexed triangles with
Operation::exportToIndexedTrianglesF32and compute connected components. - Enable
ExecuteMode::Debugto surface input-constraint violations (slower, but informative). - Replace the sphere with a more complex tool and use
selfUnionfirst to make it strictly solid before iterating.
Next steps
- Browse the API Reference (search functions, classes, and types).
- Scan All Symbols (A–Z) for quick symbol lookup.
- See FAQ for developer-focused questions.
- Check Troubleshooting for common issues and fixes.
- Track changes in the Changelog.