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};
|
||||
keel::ModelId overhang{};
|
||||
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)");
|
||||
|
|
@ -931,7 +938,8 @@ private:
|
|||
// The Print tab.
|
||||
PrintTool printTool_ = PrintTool::None;
|
||||
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;
|
||||
int material_ = 0;
|
||||
std::map<uint32_t, PrintInfo> printInfos_;
|
||||
|
|
@ -3007,7 +3015,7 @@ private:
|
|||
}
|
||||
const PrintInfo& printInfo(const Object& o) {
|
||||
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}) {
|
||||
uint32_t b;
|
||||
std::memcpy(&b, &f, 4);
|
||||
|
|
@ -3039,6 +3047,33 @@ private:
|
|||
km.submeshes.push_back({0, uint32_t(km.indices.size()), 0});
|
||||
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;
|
||||
}
|
||||
std::vector<Object*> printTargets() { // (what's picked, or everything shown)
|
||||
|
|
@ -3082,6 +3117,13 @@ private:
|
|||
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);
|
||||
}
|
||||
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() {
|
||||
const auto targets = printTargets();
|
||||
|
|
@ -3380,6 +3422,16 @@ private:
|
|||
ImGui::SetNextItemWidth(w);
|
||||
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)");
|
||||
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::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;
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -45,6 +45,10 @@ private:
|
|||
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.
|
||||
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(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").
|
||||
{
|
||||
Mesh m = makeSphere(32, 16, 1.0f);
|
||||
|
|
|
|||
Loading…
Reference in a new issue