mirror of
https://github.com/Nexoniarz/Fry3DX.git
synced 2026-11-03 04:24:26 +00:00
Print: a slice preview (one layer as the printer makes it, any layer height); fry_print layer N; tests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
14e35cceb1
commit
67866ed0fb
4 changed files with 62 additions and 1 deletions
37
src/app.cpp
37
src/app.cpp
|
|
@ -256,6 +256,10 @@ struct PrintInfo {
|
||||||
int thinFaces = 0;
|
int thinFaces = 0;
|
||||||
float thinnest = 1e30f;
|
float thinnest = 1e30f;
|
||||||
keel::ModelId thin{};
|
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<std::pair<glm::vec3, glm::vec3>> slice;
|
||||||
};
|
};
|
||||||
|
|
||||||
keel::Cvar fry_printer("fry_printer", "0", keel::CVAR_ARCHIVE, "Fry3DX: the 3D printer (a number in the Print tab's list)");
|
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) {
|
static keel::Cmd print("fry_print", [](const keel::Args& a) {
|
||||||
if (a.size() < 2) return;
|
if (a.size() < 2) return;
|
||||||
if (a[1] == "bed") self->putOnBed();
|
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] == "middle") self->middleOfBed();
|
||||||
if (a[1] == "printer" && a.size() > 2) keel::execute("fry_printer " + a[2]);
|
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]));
|
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.
|
// The Print tab.
|
||||||
PrintTool printTool_ = PrintTool::None;
|
PrintTool printTool_ = PrintTool::None;
|
||||||
std::vector<glm::vec3> measure_;
|
std::vector<glm::vec3> 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 minWall_ = 0.0008f; // (m: the thinnest wall that prints well, twice a 0.4 mm nozzle)
|
||||||
float overhangDeg_ = 45.0f, infill_ = 15.0f;
|
float overhangDeg_ = 45.0f, infill_ = 15.0f;
|
||||||
int material_ = 0;
|
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;
|
if (const float t = -ray.origin.z / ray.dir.z; t > 0) return ray.origin + ray.dir * t;
|
||||||
return std::nullopt;
|
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) {
|
void printOverlay(ImDrawList* dl, const ImGuiIO& io) {
|
||||||
if (printTool_ != PrintTool::Measure) return;
|
if (printTool_ != PrintTool::Measure) return;
|
||||||
std::vector<glm::vec3> pts = measure_;
|
std::vector<glm::vec3> 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::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)");
|
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::SeparatorText("Material");
|
||||||
ImGui::SetNextItemWidth(w);
|
ImGui::SetNextItemWidth(w);
|
||||||
ImGui::Combo("##material", &material_, [](void*, int i) { return kMaterials[i].name; }, nullptr, int(std::size(kMaterials)));
|
ImGui::Combo("##material", &material_, [](void*, int i) { return kMaterials[i].name; }, nullptr, int(std::size(kMaterials)));
|
||||||
|
|
@ -5746,6 +5780,7 @@ private:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ((tab_ == Tab::Print || printTool_ == PrintTool::Measure) && !clean) printOverlay(dl, io);
|
if ((tab_ == Tab::Print || printTool_ == PrintTool::Measure) && !clean) printOverlay(dl, io);
|
||||||
|
if (tab_ == Tab::Print && showLayer_ && !clean) layerOverlay(dl);
|
||||||
if (clean) {
|
if (clean) {
|
||||||
dl->PopClipRect();
|
dl->PopClipRect();
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -178,6 +178,9 @@ bool overhangs(const Mesh& m, int face, float overhang, float bedZ);
|
||||||
int closeHoles(Mesh& m);
|
int closeHoles(Mesh& m);
|
||||||
// Turns faces so each piece's all face the same way, outwards; how many were turned.
|
// Turns faces so each piece's all face the same way, outwards; how many were turned.
|
||||||
int faceOutward(Mesh& m);
|
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<std::pair<glm::vec3, glm::vec3>> 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
|
// 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
|
// 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.
|
// gone, an inside wall), faces turned the same way, holes closed. What it did.
|
||||||
|
|
|
||||||
|
|
@ -175,4 +175,20 @@ Repair repairForPrint(Mesh& m) {
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<glm::vec3, glm::vec3>> sliceAt(const Mesh& m, float z) {
|
||||||
|
std::vector<std::pair<glm::vec3, glm::vec3>> 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
|
} // namespace fry
|
||||||
|
|
|
||||||
|
|
@ -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(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");
|
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.
|
// 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);
|
Mesh slab = makeCube(1.0f);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue