diff --git a/src/app.cpp b/src/app.cpp index 3d268b2..3b87057 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -256,6 +256,10 @@ struct PrintInfo { int thinFaces = 0; float thinnest = 1e30f; keel::ModelId thin{}; + // The slice preview's layer: where it was cut, and for which shape (kept beside what it's of). + mutable uint64_t sliceKey = ~0ull; + mutable float sliceZ = -1; + mutable std::vector> slice; }; keel::Cvar fry_printer("fry_printer", "0", keel::CVAR_ARCHIVE, "Fry3DX: the 3D printer (a number in the Print tab's list)"); @@ -525,6 +529,7 @@ public: static keel::Cmd print("fry_print", [](const keel::Args& a) { if (a.size() < 2) return; if (a[1] == "bed") self->putOnBed(); + if (a[1] == "layer" && a.size() > 2) self->showLayer_ = true, self->layer_ = std::stoi(a[2]); if (a[1] == "middle") self->middleOfBed(); if (a[1] == "printer" && a.size() > 2) keel::execute("fry_printer " + a[2]); if (a[1] == "size" && a.size() > 3) self->pushUndo(), self->printResize(std::stoi(a[2]), std::stof(a[3])); @@ -938,7 +943,9 @@ private: // The Print tab. PrintTool printTool_ = PrintTool::None; std::vector measure_; - bool measureSnap_ = true, showOverhangs_ = true, sizing_ = false, showThin_ = true; + bool measureSnap_ = true, showOverhangs_ = true, sizing_ = false, showThin_ = true, showLayer_ = false; + int layer_ = 1; + float layerHeight_ = 0.0002f; // (m) float minWall_ = 0.0008f; // (m: the thinnest wall that prints well, twice a 0.4 mm nozzle) float overhangDeg_ = 45.0f, infill_ = 15.0f; int material_ = 0; @@ -3303,6 +3310,18 @@ private: if (const float t = -ray.origin.z / ray.dir.z; t > 0) return ray.origin + ray.dir * t; return std::nullopt; } + // The slice preview: where the printer goes round at the chosen layer, over the model. + void layerOverlay(ImDrawList* dl) { + const float z = (float(layer_) - 0.5f) * layerHeight_; + for (Object* o : printTargets()) { + const PrintInfo& p = printInfo(*o); + if (p.sliceKey != p.key || p.sliceZ != z) p.sliceKey = p.key, p.sliceZ = z, p.slice = fry::sliceAt(p.world, z); + for (const auto& [a, b] : p.slice) { + ImVec2 sa, sb; + if (project(a, sa) && project(b, sb)) dl->AddLine(sa, sb, IM_COL32(60, 220, 255, 255), 2.5f); + } + } + } void printOverlay(ImDrawList* dl, const ImGuiIO& io) { if (printTool_ != PrintTool::Measure) return; std::vector pts = measure_; @@ -3433,6 +3452,21 @@ private: if (ImGui::SliderFloat("##minwall", &mm, 0.2f, 5.0f, "thinner than %.1f mm")) minWall_ = mm / 1000; if (ImGui::IsItemHovered()) ImGui::SetTooltip("The thinnest wall your printer makes well: about twice the nozzle (0.8 mm for a 0.4 mm nozzle)"); + ImGui::SeparatorText("Layers"); + { + const int layers = std::max(1, int(std::ceil((hi.z - std::max(lo.z, 0.0f)) / layerHeight_ - 1e-4f))); + ImGui::Checkbox("Show one layer (as the printer makes it)", &showLayer_); + if (showLayer_) { + layer_ = std::clamp(layer_, 1, layers); + ImGui::SetNextItemWidth(w); + ImGui::SliderInt("##layer", &layer_, 1, layers, std::format("layer %d of {}", layers).c_str()); + float mm = layerHeight_ * 1000; + ImGui::SetNextItemWidth(w); + if (ImGui::SliderFloat("##lh", &mm, 0.04f, 0.6f, "%.2f mm a layer")) layerHeight_ = std::max(mm, 0.01f) / 1000; + ImGui::TextDisabled("At %.2f mm up", (float(layer_) - 0.5f) * layerHeight_ * 1000); + } else + ImGui::TextDisabled("%d layers of %.2f mm", layers, layerHeight_ * 1000); + } ImGui::SeparatorText("Material"); ImGui::SetNextItemWidth(w); ImGui::Combo("##material", &material_, [](void*, int i) { return kMaterials[i].name; }, nullptr, int(std::size(kMaterials))); @@ -5746,6 +5780,7 @@ private: return; } if ((tab_ == Tab::Print || printTool_ == PrintTool::Measure) && !clean) printOverlay(dl, io); + if (tab_ == Tab::Print && showLayer_ && !clean) layerOverlay(dl); if (clean) { dl->PopClipRect(); return; diff --git a/src/mesh.h b/src/mesh.h index 294c529..09f61e7 100644 --- a/src/mesh.h +++ b/src/mesh.h @@ -178,6 +178,9 @@ bool overhangs(const Mesh& m, int face, float overhang, float bedZ); int closeHoles(Mesh& m); // Turns faces so each piece's all face the same way, outwards; how many were turned. int faceOutward(Mesh& m); +// Where a level plane at height z cuts the shape: the pieces of its outline there (a slicer's +// layer), each from one face. +std::vector> sliceAt(const Mesh& m, float z); // Made ready to print, as far as it can be without guessing: points in the same place joined (an // STL's corners come apart), faces with no area gone, faces there twice once (back to back: both // gone, an inside wall), faces turned the same way, holes closed. What it did. diff --git a/src/print.cpp b/src/print.cpp index 7173fb5..21cb933 100644 --- a/src/print.cpp +++ b/src/print.cpp @@ -175,4 +175,20 @@ Repair repairForPrint(Mesh& m) { return r; } +std::vector> sliceAt(const Mesh& m, float z) { + std::vector> out; + for (size_t f = 0; f < m.faces.size(); ++f) + for (const auto& t : triangulate(m, int(f))) { // (a triangle crosses it twice or not at all; a dented face can more) + glm::vec3 at[2]; + int n = 0; + for (int i = 0; i < 3; ++i) { + const glm::vec3 a = m.verts[size_t(t[size_t(i)])], b = m.verts[size_t(t[size_t((i + 1) % 3)])]; + const float da = a.z - z, db = b.z - z; + if ((da < 0) != (db < 0) && n < 2) at[n++] = glm::mix(a, b, da / (da - db)); + } + if (n == 2) out.push_back({at[0], at[1]}); + } + return out; +} + } // namespace fry diff --git a/tests/mesh_test.cpp b/tests/mesh_test.cpp index b4eb303..80ddabb 100644 --- a/tests/mesh_test.cpp +++ b/tests/mesh_test.cpp @@ -449,6 +449,13 @@ int main() { CHECK(checkForPrint(o).closed() && checkForPrint(o).volume < 0.05f * 0.04f * 0.01f, "an O has its hole (%g m3)", checkForPrint(o).volume); CHECK(makeText({}, "x", st, &err).faces.empty() && !err.empty(), "no font: nothing, and why"); } + // A slicer's layer: a 1 m cube cut halfway is a 4 m outline, a cylinder's a circle's. + { + float len = 0; + for (const auto& [a, b] : sliceAt(makeCube(1.0f), 0.1f)) len += glm::distance(a, b); + CHECK(std::abs(len - 4.0f) < 1e-4f, "a cube's slice: 4 m round (%.4f)", len); + CHECK(sliceAt(makeCube(1.0f), 2.0f).empty(), "above it: nothing"); + } // Wall thickness: a box 2 mm thin one way is 2 mm thick on its big faces, 1 m the other ways. { Mesh slab = makeCube(1.0f);