144 lines
5.9 KiB
C++
144 lines
5.9 KiB
C++
|
|
/// \file 08_3d_print/main.cpp
|
|||
|
|
/// \brief Complete 3D printing pipeline: SDF → marching cubes → mesh stats → STL export
|
|||
|
|
///
|
|||
|
|
/// Demonstrates end-to-end workflow from parametric SDF shape definition to
|
|||
|
|
/// a slicer-ready STL file, with mesh quality statistics along the way.
|
|||
|
|
|
|||
|
|
#include <vde/sdf/sdf_primitives.h>
|
|||
|
|
#include <vde/sdf/sdf_operations.h>
|
|||
|
|
#include <vde/sdf/sdf_to_mesh.h>
|
|||
|
|
#include <vde/mesh/marching_cubes.h>
|
|||
|
|
#include <vde/mesh/halfedge_mesh.h>
|
|||
|
|
#include <vde/foundation/io_stl.h>
|
|||
|
|
#include <vde/core/aabb.h>
|
|||
|
|
|
|||
|
|
#include <iostream>
|
|||
|
|
#include <iomanip>
|
|||
|
|
#include <cmath>
|
|||
|
|
|
|||
|
|
using namespace vde::core;
|
|||
|
|
using namespace vde::sdf;
|
|||
|
|
using namespace vde::mesh;
|
|||
|
|
using namespace vde::foundation;
|
|||
|
|
|
|||
|
|
// ── helpers ──
|
|||
|
|
|
|||
|
|
/// Compute approximate volume via divergence theorem (loop over faces)
|
|||
|
|
static double compute_volume(const HalfedgeMesh& m) {
|
|||
|
|
double vol = 0.0;
|
|||
|
|
for (size_t fi = 0; fi < m.num_faces(); ++fi) {
|
|||
|
|
auto verts = m.face_vertices(fi);
|
|||
|
|
if (verts.size() < 3) continue;
|
|||
|
|
const Point3D& a = m.vertex(verts[0]);
|
|||
|
|
const Point3D& b = m.vertex(verts[1]);
|
|||
|
|
const Point3D& c = m.vertex(verts[2]);
|
|||
|
|
// Signed volume contribution: (1/6) * (a · (b × c))
|
|||
|
|
vol += a.x() * (b.y() * c.z() - b.z() * c.y())
|
|||
|
|
+ a.y() * (b.z() * c.x() - b.x() * c.z())
|
|||
|
|
+ a.z() * (b.x() * c.y() - b.y() * c.x());
|
|||
|
|
}
|
|||
|
|
return std::abs(vol) / 6.0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Convert HalfedgeMesh faces to STL triangles
|
|||
|
|
static std::vector<StlTriangle> to_stl_triangles(const HalfedgeMesh& m) {
|
|||
|
|
std::vector<StlTriangle> tris;
|
|||
|
|
tris.reserve(m.num_faces());
|
|||
|
|
|
|||
|
|
for (size_t fi = 0; fi < m.num_faces(); ++fi) {
|
|||
|
|
auto verts = m.face_vertices(fi);
|
|||
|
|
if (verts.size() < 3) continue;
|
|||
|
|
StlTriangle t;
|
|||
|
|
t.normal = m.face_normal(static_cast<int>(fi));
|
|||
|
|
t.v0 = m.vertex(verts[0]);
|
|||
|
|
t.v1 = m.vertex(verts[1]);
|
|||
|
|
t.v2 = m.vertex(verts[2]);
|
|||
|
|
tris.push_back(t);
|
|||
|
|
}
|
|||
|
|
return tris;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════
|
|||
|
|
// Main — 3D Print Pipeline
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
int main() {
|
|||
|
|
std::cout << std::fixed << std::setprecision(4);
|
|||
|
|
std::cout << "╔══════════════════════════════════╗\n"
|
|||
|
|
<< "║ 3D Print Pipeline ║\n"
|
|||
|
|
<< "╚══════════════════════════════════╝\n\n";
|
|||
|
|
|
|||
|
|
// ── Step 1: Define the SDF shape ─────────────────────────────────
|
|||
|
|
//
|
|||
|
|
// A smooth blend between a box and a torus creates an organic-looking
|
|||
|
|
// shape that would be challenging to model with traditional CAD.
|
|||
|
|
|
|||
|
|
std::cout << "Step 1: Define SDF shape\n";
|
|||
|
|
|
|||
|
|
// Use Point3D overloads from sdf_primitives.h for type safety
|
|||
|
|
auto shape_sdf = [](double x, double y, double z) -> double {
|
|||
|
|
Point3D p(x, y, z);
|
|||
|
|
double d_box = box(p, Point3D(2.0, 2.0, 2.0));
|
|||
|
|
double d_torus = torus(p, 1.5, 0.3);
|
|||
|
|
return op_smooth_union(d_box, d_torus, 0.4);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
std::cout << " shape = op_smooth_union( box(2,2,2), torus(1.5, 0.3), k=0.4 )\n";
|
|||
|
|
|
|||
|
|
// ── Step 2: Marching cubes → triangle mesh ───────────────────────
|
|||
|
|
|
|||
|
|
std::cout << "\nStep 2: Extract isosurface via marching cubes\n";
|
|||
|
|
|
|||
|
|
const Point3D bmin(-3.0, -3.0, -3.0);
|
|||
|
|
const Point3D bmax( 3.0, 3.0, 3.0);
|
|||
|
|
const int resolution = 80;
|
|||
|
|
|
|||
|
|
auto mc = marching_cubes(shape_sdf, 0.0, bmin, bmax, resolution);
|
|||
|
|
|
|||
|
|
std::cout << " grid: " << resolution << "³ (" << resolution
|
|||
|
|
<< " samples per axis)\n";
|
|||
|
|
std::cout << " raw triangles: " << mc.triangles.size() << "\n";
|
|||
|
|
|
|||
|
|
// ── Step 3: Build half-edge mesh & print stats ───────────────────
|
|||
|
|
|
|||
|
|
std::cout << "\nStep 3: Build half-edge mesh + stats\n";
|
|||
|
|
|
|||
|
|
HalfedgeMesh mesh;
|
|||
|
|
mesh.build_from_triangles(mc.vertices, mc.triangles);
|
|||
|
|
mesh.update_normals();
|
|||
|
|
|
|||
|
|
AABB3D bb = mesh.bounds();
|
|||
|
|
|
|||
|
|
std::cout << " vertices: " << mesh.num_vertices() << "\n"
|
|||
|
|
<< " faces: " << mesh.num_faces() << "\n"
|
|||
|
|
<< " edges: " << mesh.num_edges() << "\n"
|
|||
|
|
<< " volume: " << compute_volume(mesh) << " (approx)\n"
|
|||
|
|
<< " bounding box:\n"
|
|||
|
|
<< " min: (" << bb.min().x() << ", " << bb.min().y() << ", " << bb.min().z() << ")\n"
|
|||
|
|
<< " max: (" << bb.max().x() << ", " << bb.max().y() << ", " << bb.max().z() << ")\n"
|
|||
|
|
<< " size: (" << bb.max().x() - bb.min().x() << ", "
|
|||
|
|
<< bb.max().y() - bb.min().y() << ", "
|
|||
|
|
<< bb.max().z() - bb.min().z() << ")\n";
|
|||
|
|
|
|||
|
|
// ── Step 4: Export STL (binary, for slicers) ─────────────────────
|
|||
|
|
|
|||
|
|
std::cout << "\nStep 4: Export STL for slicing\n";
|
|||
|
|
|
|||
|
|
const char* out_path = "output_3d_print.stl";
|
|||
|
|
auto stl_tris = to_stl_triangles(mesh);
|
|||
|
|
write_stl(out_path, stl_tris);
|
|||
|
|
|
|||
|
|
std::cout << " → " << out_path << " (" << stl_tris.size()
|
|||
|
|
<< " triangles, binary format)\n";
|
|||
|
|
std::cout << " Slicer-ready! Open in PrusaSlicer, Cura, or Bambu Studio.\n";
|
|||
|
|
|
|||
|
|
// ── Bonus: ASCII variant for inspection ──────────────────────────
|
|||
|
|
|
|||
|
|
const char* ascii_path = "output_3d_print_ascii.stl";
|
|||
|
|
write_stl_ascii(ascii_path, stl_tris);
|
|||
|
|
std::cout << " → " << ascii_path << " (ASCII, human-readable)\n";
|
|||
|
|
|
|||
|
|
std::cout << "\nDone.\n";
|
|||
|
|
return 0;
|
|||
|
|
}
|