mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-07 17:43:51 +00:00
feat(editor): add --export-stl for 3D-printer-compatible WOM export
ASCII STL is the universal 3D printing format — Cura, PrusaSlicer,
Bambu Studio, Slic3r, OctoPrint, MakerBot Print, and basically every
slicer made in the last 25 years opens it natively. Lets WOM models
drive physical prints with no conversion friction beyond one command:
wowee_editor --export-stl Tree # -> Tree.stl
wowee_editor --export-stl Tree out.stl
Per-spec STL ASCII output:
- 'solid <name>' header / 'endsolid <name>' footer (name sanitized
to alphanum + underscore for slicers that strict-parse)
- Per-triangle 'facet normal nx ny nz' with normal computed from
cross-product of edges 1 and 2 (most slicers use this for
orientation hints; falls back to (0,0,1) for degenerate triangles)
- 'outer loop' with three vertex lines per facet
- No shared vertex pool — STL stores every triangle independently
Why STL alongside OBJ + glTF: OBJ targets DCC tools (Blender etc.),
glTF targets web 3D viewers (Sketchfab, three.js), and STL targets
fabrication. Three different ecosystems, three different format
needs — wowee open formats now bridge to all three.
Verified on a 5-vert/6-tri pyramid: STL has 6 facets with correctly
computed normals (0 -1 0 for the bottom faces, computed slopes for
the side triangles), proper solid/endsolid framing, name preserved
('solid Pyramid').
This commit is contained in:
parent
6d79963f24
commit
9b24e0be8a
1 changed files with 74 additions and 0 deletions
|
|
@ -474,6 +474,8 @@ static void printUsage(const char* argv0) {
|
|||
std::printf(" Convert a WOM model to Wavefront OBJ for use in Blender/MeshLab\n");
|
||||
std::printf(" --export-glb <wom-base> [out.glb]\n");
|
||||
std::printf(" Convert a WOM model to glTF 2.0 binary (.glb) — modern industry standard\n");
|
||||
std::printf(" --export-stl <wom-base> [out.stl]\n");
|
||||
std::printf(" Convert a WOM model to ASCII STL — works with any 3D printer slicer\n");
|
||||
std::printf(" --export-wob-glb <wob-base> [out.glb]\n");
|
||||
std::printf(" Convert a WOB building to glTF 2.0 binary (one mesh, per-group primitives)\n");
|
||||
std::printf(" --export-whm-glb <wot-base> [out.glb]\n");
|
||||
|
|
@ -623,6 +625,7 @@ int main(int argc, char* argv[]) {
|
|||
"--export-wob-obj", "--import-wob-obj",
|
||||
"--export-woc-obj", "--export-whm-obj",
|
||||
"--export-glb", "--export-wob-glb", "--export-whm-glb",
|
||||
"--export-stl",
|
||||
"--convert-m2", "--convert-wmo",
|
||||
"--convert-dbc-json", "--convert-json-dbc", "--convert-blp-png",
|
||||
"--migrate-wom", "--migrate-zone",
|
||||
|
|
@ -4110,6 +4113,77 @@ int main(int argc, char* argv[]) {
|
|||
std::printf(" %u verts, %u tris, %zu primitive(s), %u-byte binary chunk\n",
|
||||
vCount, iCount / 3, primitives.size(), binLen);
|
||||
return 0;
|
||||
} else if (std::strcmp(argv[i], "--export-stl") == 0 && i + 1 < argc) {
|
||||
// ASCII STL export — single most universal 3D-printer format.
|
||||
// Cura, PrusaSlicer, Bambu Studio, Slic3r, OctoPrint, MakerBot
|
||||
// — every slicer made in the last 25 years opens STL natively.
|
||||
// Lets WOM models drive physical prints with no conversion
|
||||
// friction beyond this one command.
|
||||
std::string base = argv[++i];
|
||||
std::string outPath;
|
||||
if (i + 1 < argc && argv[i + 1][0] != '-') outPath = argv[++i];
|
||||
if (base.size() >= 4 && base.substr(base.size() - 4) == ".wom")
|
||||
base = base.substr(0, base.size() - 4);
|
||||
if (!wowee::pipeline::WoweeModelLoader::exists(base)) {
|
||||
std::fprintf(stderr, "WOM not found: %s.wom\n", base.c_str());
|
||||
return 1;
|
||||
}
|
||||
if (outPath.empty()) outPath = base + ".stl";
|
||||
auto wom = wowee::pipeline::WoweeModelLoader::load(base);
|
||||
if (!wom.isValid()) {
|
||||
std::fprintf(stderr, "WOM has no geometry: %s.wom\n", base.c_str());
|
||||
return 1;
|
||||
}
|
||||
std::ofstream out(outPath);
|
||||
if (!out) {
|
||||
std::fprintf(stderr, "Failed to open output: %s\n", outPath.c_str());
|
||||
return 1;
|
||||
}
|
||||
// STL solid name must be alphanumeric + underscores per loose
|
||||
// convention; sanitize whatever the WOM name contains. Empty
|
||||
// -> 'wowee_model'.
|
||||
std::string solidName = wom.name.empty() ? "wowee_model" : wom.name;
|
||||
for (auto& c : solidName) {
|
||||
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
|
||||
(c >= '0' && c <= '9') || c == '_')) c = '_';
|
||||
}
|
||||
out << "solid " << solidName << "\n";
|
||||
// Per-triangle facet — STL has no shared vertex pool, every
|
||||
// triangle stands alone. Compute face normal from cross product
|
||||
// (STL spec requires unit-length face normal; viewers fall
|
||||
// back to per-vertex if zero, but most slicers want the real
|
||||
// value for orientation hints).
|
||||
uint32_t triCount = 0;
|
||||
for (size_t k = 0; k + 2 < wom.indices.size(); k += 3) {
|
||||
uint32_t i0 = wom.indices[k];
|
||||
uint32_t i1 = wom.indices[k + 1];
|
||||
uint32_t i2 = wom.indices[k + 2];
|
||||
if (i0 >= wom.vertices.size() || i1 >= wom.vertices.size() ||
|
||||
i2 >= wom.vertices.size()) continue;
|
||||
const auto& v0 = wom.vertices[i0].position;
|
||||
const auto& v1 = wom.vertices[i1].position;
|
||||
const auto& v2 = wom.vertices[i2].position;
|
||||
glm::vec3 e1 = v1 - v0;
|
||||
glm::vec3 e2 = v2 - v0;
|
||||
glm::vec3 n = glm::cross(e1, e2);
|
||||
float len = glm::length(n);
|
||||
if (len > 1e-12f) n /= len;
|
||||
else n = {0, 0, 1}; // degenerate — STL spec allows any unit normal
|
||||
out << " facet normal " << n.x << " " << n.y << " " << n.z << "\n"
|
||||
<< " outer loop\n"
|
||||
<< " vertex " << v0.x << " " << v0.y << " " << v0.z << "\n"
|
||||
<< " vertex " << v1.x << " " << v1.y << " " << v1.z << "\n"
|
||||
<< " vertex " << v2.x << " " << v2.y << " " << v2.z << "\n"
|
||||
<< " endloop\n"
|
||||
<< " endfacet\n";
|
||||
triCount++;
|
||||
}
|
||||
out << "endsolid " << solidName << "\n";
|
||||
out.close();
|
||||
std::printf("Exported %s.wom -> %s\n", base.c_str(), outPath.c_str());
|
||||
std::printf(" solid '%s', %u facets\n",
|
||||
solidName.c_str(), triCount);
|
||||
return 0;
|
||||
} else if (std::strcmp(argv[i], "--export-wob-glb") == 0 && i + 1 < argc) {
|
||||
// glTF 2.0 binary export for WOB. Same purpose as --export-glb
|
||||
// for WOM but adapted for buildings: each WOB group becomes
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue