mirror of
https://github.com/Nexoniarz/Fry3DX.git
synced 2026-11-03 04:24:26 +00:00
Print: walls thinner than the least (0.8 mm, set it) shown in yellow, the thinnest told; measured once per shape and size, not each move; tests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
4f32540d62
commit
14e35cceb1
4 changed files with 84 additions and 2 deletions
56
src/app.cpp
56
src/app.cpp
|
|
@ -249,6 +249,13 @@ struct PrintInfo {
|
||||||
glm::vec3 lo{0.0f}, hi{0.0f};
|
glm::vec3 lo{0.0f}, hi{0.0f};
|
||||||
keel::ModelId overhang{};
|
keel::ModelId overhang{};
|
||||||
bool hasOverhang = false;
|
bool hasOverhang = false;
|
||||||
|
// Walls too thin to print: how thick at each face (worked out again only when its shape or
|
||||||
|
// size changes: moving or turning it doesn't), how many under the least, the thinnest.
|
||||||
|
uint64_t thickKey = ~0ull;
|
||||||
|
std::vector<float> thick;
|
||||||
|
int thinFaces = 0;
|
||||||
|
float thinnest = 1e30f;
|
||||||
|
keel::ModelId thin{};
|
||||||
};
|
};
|
||||||
|
|
||||||
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)");
|
||||||
|
|
@ -931,7 +938,8 @@ 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;
|
bool measureSnap_ = true, showOverhangs_ = true, sizing_ = false, showThin_ = true;
|
||||||
|
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;
|
||||||
std::map<uint32_t, PrintInfo> printInfos_;
|
std::map<uint32_t, PrintInfo> printInfos_;
|
||||||
|
|
@ -3007,7 +3015,7 @@ private:
|
||||||
}
|
}
|
||||||
const PrintInfo& printInfo(const Object& o) {
|
const PrintInfo& printInfo(const Object& o) {
|
||||||
PrintInfo& p = printInfos_[o.id];
|
PrintInfo& p = printInfos_[o.id];
|
||||||
uint64_t key = versions_[o.id] * 1000003ull + uint64_t(overhangDeg_ * 10);
|
uint64_t key = versions_[o.id] * 1000003ull + uint64_t(overhangDeg_ * 10) + uint64_t(minWall_ * 1e5f) * 7919ull;
|
||||||
for (float f : {o.position.x, o.position.y, o.position.z, o.rotation.x, o.rotation.y, o.rotation.z, o.rotation.w, o.scale.x, o.scale.y, o.scale.z}) {
|
for (float f : {o.position.x, o.position.y, o.position.z, o.rotation.x, o.rotation.y, o.rotation.z, o.rotation.w, o.scale.x, o.scale.y, o.scale.z}) {
|
||||||
uint32_t b;
|
uint32_t b;
|
||||||
std::memcpy(&b, &f, 4);
|
std::memcpy(&b, &f, 4);
|
||||||
|
|
@ -3039,6 +3047,33 @@ private:
|
||||||
km.submeshes.push_back({0, uint32_t(km.indices.size()), 0});
|
km.submeshes.push_back({0, uint32_t(km.indices.size()), 0});
|
||||||
p.overhang = r_->updateModel(std::format("fry:overhang:{}", o.id), km);
|
p.overhang = r_->updateModel(std::format("fry:overhang:{}", o.id), km);
|
||||||
}
|
}
|
||||||
|
// Thin walls (only of a closed shape: an open one has no inside to be thick).
|
||||||
|
uint64_t thickKey = versions_[o.id] * 1000003ull;
|
||||||
|
for (float f : {o.scale.x, o.scale.y, o.scale.z}) {
|
||||||
|
uint32_t b;
|
||||||
|
std::memcpy(&b, &f, 4);
|
||||||
|
thickKey = (thickKey ^ b) * 1099511628211ull;
|
||||||
|
}
|
||||||
|
if (p.thickKey != thickKey || p.thick.size() != p.world.faces.size())
|
||||||
|
p.thickKey = thickKey, p.thick = p.check.closed() ? fry::wallThickness(p.world) : std::vector<float>(p.world.faces.size(), 1e30f);
|
||||||
|
p.thinFaces = 0, p.thinnest = 1e30f;
|
||||||
|
km.vertices.clear(), km.indices.clear(), km.submeshes.clear();
|
||||||
|
for (size_t f = 0; f < p.world.faces.size(); ++f) {
|
||||||
|
p.thinnest = std::min(p.thinnest, p.thick[f]);
|
||||||
|
if (p.thick[f] >= minWall_) continue;
|
||||||
|
++p.thinFaces;
|
||||||
|
const glm::vec3 n = faceNormal(p.world, int(f));
|
||||||
|
for (const auto& t : triangulate(p.world, int(f)))
|
||||||
|
for (int k : t) {
|
||||||
|
const glm::vec3 q = p.world.verts[size_t(k)] + n * 0.0005f;
|
||||||
|
km.indices.push_back(uint32_t(km.vertices.size()));
|
||||||
|
km.vertices.push_back({{q.x, q.y, q.z}, {n.x, n.y, n.z}, {0, 0}, {0, 0}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (p.thinFaces) {
|
||||||
|
km.submeshes.push_back({0, uint32_t(km.indices.size()), 0});
|
||||||
|
p.thin = r_->updateModel(std::format("fry:thin:{}", o.id), km);
|
||||||
|
}
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
std::vector<Object*> printTargets() { // (what's picked, or everything shown)
|
std::vector<Object*> printTargets() { // (what's picked, or everything shown)
|
||||||
|
|
@ -3082,6 +3117,13 @@ private:
|
||||||
if (const PrintInfo& p = printInfo(o); p.hasOverhang)
|
if (const PrintInfo& p = printInfo(o); p.hasOverhang)
|
||||||
r.drawModel(p.overhang, glm::scale(glm::mat4(1.0f), glm::vec3(kUnits)), glm::vec4(0.95f, 0.15f, 0.1f, 0.55f), nullptr, &flat, false);
|
r.drawModel(p.overhang, glm::scale(glm::mat4(1.0f), glm::vec3(kUnits)), glm::vec4(0.95f, 0.15f, 0.1f, 0.55f), nullptr, &flat, false);
|
||||||
}
|
}
|
||||||
|
if (showThin_) {
|
||||||
|
const keel::ModelLighting flat{{glm::vec3(1.0f), glm::vec3(1.0f), glm::vec3(1.0f), glm::vec3(1.0f), glm::vec3(1.0f), glm::vec3(1.0f)}, 0.0f};
|
||||||
|
for (const Object& o : scene_.objects)
|
||||||
|
if (o.visible)
|
||||||
|
if (const PrintInfo& p = printInfo(o); p.thinFaces)
|
||||||
|
r.drawModel(p.thin, glm::scale(glm::mat4(1.0f), glm::vec3(kUnits)), glm::vec4(1.0f, 0.8f, 0.1f, 0.7f), nullptr, &flat, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void putOnBed() {
|
void putOnBed() {
|
||||||
const auto targets = printTargets();
|
const auto targets = printTargets();
|
||||||
|
|
@ -3380,6 +3422,16 @@ private:
|
||||||
ImGui::SetNextItemWidth(w);
|
ImGui::SetNextItemWidth(w);
|
||||||
ImGui::SliderFloat("##hang", &overhangDeg_, 20.0f, 80.0f, "steeper than %.0f degrees");
|
ImGui::SliderFloat("##hang", &overhangDeg_, 20.0f, 80.0f, "steeper than %.0f degrees");
|
||||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("How far from upright a face can lean over before it needs support (45 for most printers)");
|
if (ImGui::IsItemHovered()) ImGui::SetTooltip("How far from upright a face can lean over before it needs support (45 for most printers)");
|
||||||
|
int thin = 0;
|
||||||
|
float thinnest = 1e30f;
|
||||||
|
for (Object* o : targets) thin += printInfo(*o).thinFaces, thinnest = std::min(thinnest, printInfo(*o).thinnest);
|
||||||
|
if (thin) bad(std::format("{} face{} on walls thinner than {:.1f} mm (yellow), the thinnest {:.2f} mm: it may not print there, or break.", thin, thin == 1 ? "" : "s", minWall_ * 1000, thinnest * 1000));
|
||||||
|
else if (all.openEdges == 0) good(std::format("Walls {:.1f} mm thick or more", minWall_ * 1000).c_str());
|
||||||
|
ImGui::Checkbox("Show thin walls in yellow", &showThin_);
|
||||||
|
float mm = minWall_ * 1000;
|
||||||
|
ImGui::SetNextItemWidth(w);
|
||||||
|
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("Material");
|
ImGui::SeparatorText("Material");
|
||||||
ImGui::SetNextItemWidth(w);
|
ImGui::SetNextItemWidth(w);
|
||||||
|
|
|
||||||
17
src/bvh.cpp
17
src/bvh.cpp
|
|
@ -116,4 +116,21 @@ bool TriangleTree::blocked(const glm::vec3& o, const glm::vec3& d, float maxT, i
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<float> wallThickness(const Mesh& m) {
|
||||||
|
const TriangleTree tree(m, glm::mat4(1.0f));
|
||||||
|
std::vector<float> out(m.faces.size(), 1e30f);
|
||||||
|
for (size_t f = 0; f < m.faces.size(); ++f) {
|
||||||
|
const auto& fv = m.faces[f];
|
||||||
|
if (fv.size() < 3) continue;
|
||||||
|
glm::vec3 c(0.0f);
|
||||||
|
for (int v : fv) c += m.verts[size_t(v)];
|
||||||
|
c /= float(fv.size());
|
||||||
|
const glm::vec3 n = faceNormal(m, int(f));
|
||||||
|
// (a hair inside, so the face itself isn't what it meets)
|
||||||
|
const glm::vec3 from = c - n * 1e-6f;
|
||||||
|
if (auto h = tree.nearest(from, -n)) out[f] = h->t + 1e-6f;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace fry
|
} // namespace fry
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,10 @@ private:
|
||||||
int build(int first, int count);
|
int build(int first, int count);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// How thick the shape is at each face: from its middle straight in (against its normal) to the
|
||||||
|
// surface on the other side (1e30 where nothing's there: an open shape, a face turned in).
|
||||||
|
std::vector<float> wallThickness(const Mesh& m);
|
||||||
|
|
||||||
// Where a ray meets a triangle (Möller–Trumbore), in the ray's lengths.
|
// Where a ray meets a triangle (Möller–Trumbore), in the ray's lengths.
|
||||||
std::optional<float> rayTriangle(const glm::vec3& origin, const glm::vec3& dir, const glm::vec3& a, const glm::vec3& b, const glm::vec3& c);
|
std::optional<float> rayTriangle(const glm::vec3& origin, const glm::vec3& dir, const glm::vec3& a, const glm::vec3& b, const glm::vec3& c);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -449,6 +449,15 @@ 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");
|
||||||
}
|
}
|
||||||
|
// 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);
|
||||||
|
transform(slab, glm::scale(glm::mat4(1), glm::vec3(1.0f, 1.0f, 0.002f)));
|
||||||
|
const std::vector<float> t = wallThickness(slab);
|
||||||
|
int thin = 0, wide = 0;
|
||||||
|
for (size_t f = 0; f < t.size(); ++f) thin += std::abs(t[f] - 0.002f) < 1e-5f, wide += std::abs(t[f] - 1.0f) < 1e-4f;
|
||||||
|
CHECK(thin == 2 && wide == 4, "wall thickness: 2 faces 2 mm, 4 faces 1 m (%d, %d)", thin, wide);
|
||||||
|
}
|
||||||
// The tree of boxes finds the same nearest hit as trying every triangle (and the same "in the way").
|
// The tree of boxes finds the same nearest hit as trying every triangle (and the same "in the way").
|
||||||
{
|
{
|
||||||
Mesh m = makeSphere(32, 16, 1.0f);
|
Mesh m = makeSphere(32, 16, 1.0f);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue