From 9013f530fa4613c55e4acd93b39fa4362bf3bf95 Mon Sep 17 00:00:00 2001 From: Tommaso Bianchi Date: Fri, 14 Aug 2026 13:35:03 +0200 Subject: [PATCH] CAD: a sheet body reports no volume, instead of a confident wrong one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mass_properties on an open shell returned volume 96000 with an inertia diagonal of [-4.2e7, -4.2e7, -6.9e7] for a 60x60x40 four-walled box — negative principal moments, which no real body can have. BRepGProp::VolumeProperties integrates the divergence theorem over whatever faces exist; on an open shell that is not a volume at all, and the old code hid the only obvious tell by taking std::abs() of the mass. "valid: true" then asserted the number was trustworthy. This matters because mass_properties is what an agent or a user reaches for to confirm a cut removed the right material. Silent nonsense there means the check passes on garbage. MassProps gains is_solid. For a sheet we compute surface area only — that stays exact — and report volume 0 with the inertia left zeroed. The MCP verb returns is_solid plus a note saying volume and inertia are not defined for an open shell; the GUI's Mass command says "sheet body — N cm² of surface, no volume" rather than quoting material that is not there. Verified on the rig: the sheet now returns volume 0.0, surface_area 9600.0 (exactly 4 x 60 x 40), is_solid false. The solid controls are unchanged and exact — a 60 mm cube reports 216000.0 and 21600.0. Co-Authored-By: Claude Opus 5 (1M context) --- src/libslic3r/GeometryEngine.cpp | 15 +++++++++++++++ src/libslic3r/GeometryEngine.hpp | 3 +++ src/slic3r/GUI/DesignPanel.cpp | 13 +++++++++++++ src/slic3r/GUI/McpControl.cpp | 12 ++++++++++++ 4 files changed, 43 insertions(+) diff --git a/src/libslic3r/GeometryEngine.cpp b/src/libslic3r/GeometryEngine.cpp index 4b60c3f1e9..ae8b4a1e64 100644 --- a/src/libslic3r/GeometryEngine.cpp +++ b/src/libslic3r/GeometryEngine.cpp @@ -470,6 +470,21 @@ GeometryEngine::MassProps GeometryEngine::mass_properties(const TopoDS_Shape& sh MassProps p; if (shape.IsNull()) return p; try { + // A sheet body (open shell, no solid) encloses nothing, and BRepGProp::VolumeProperties + // integrates the divergence theorem over whatever faces exist — on an open shell that is + // not a volume at all. It came back as 96000 with an inertia diagonal of + // [-4.2e7, -4.2e7, -6.9e7] for a 60x60x40 four-walled box: negative principal moments, + // which no real body can have. The old code then hid the only obvious tell by taking + // std::abs() of the mass. Report the honest answer instead — surface area is still + // meaningful, so this is not a failure, just not a solid. + p.is_solid = TopExp_Explorer(shape, TopAbs_SOLID).More(); + if (!p.is_solid) { + GProp_GProps sonly; + BRepGProp::SurfaceProperties(shape, sonly); + p.surface_area = sonly.Mass(); + p.valid = true; // the area IS trustworthy; volume/inertia stay zero + return p; + } GProp_GProps vprops; BRepGProp::VolumeProperties(shape, vprops); double mass = vprops.Mass(); diff --git a/src/libslic3r/GeometryEngine.hpp b/src/libslic3r/GeometryEngine.hpp index deae837919..32d8d96ca6 100644 --- a/src/libslic3r/GeometryEngine.hpp +++ b/src/libslic3r/GeometryEngine.hpp @@ -100,6 +100,9 @@ public: Vec3d center_of_mass{Vec3d::Zero()}; std::array inertia{}; bool valid{false}; + // False for a sheet body (an open shell with no solid). Volume and inertia are then + // meaningless and are reported as zero; surface_area stays meaningful. See the .cpp. + bool is_solid{false}; }; static MassProps mass_properties(const TopoDS_Shape& shape); diff --git a/src/slic3r/GUI/DesignPanel.cpp b/src/slic3r/GUI/DesignPanel.cpp index f0e30cf601..3ebc63d2a0 100644 --- a/src/slic3r/GUI/DesignPanel.cpp +++ b/src/slic3r/GUI/DesignPanel.cpp @@ -5239,6 +5239,19 @@ void DesignPanel::on_mass_properties() wxString name = wxString::Format(_L("Body %d"), m_sel_solid_body + 1); if (!m_doc.bodies[m_sel_solid_body].name.empty()) name = wxString::FromUTF8(m_doc.bodies[m_sel_solid_body].name); + if (!mp.is_solid) { + // Sheet body: quoting a volume here would be inventing material that is not there. + m_status->SetForegroundColour(wxNullColour); + set_status(wxString::Format(_L("%s: sheet body — %.2f cm² of surface, no volume"), + name, mp.surface_area / 100.0)); + m_status->Refresh(); + wxMessageBox(wxString::Format(_L("%s\n\nSheet body (open shell)\nSurface area: %.2f cm²\n\n" + "A sheet encloses no material, so it has no volume. " + "Thicken it into a solid to get one."), + name, mp.surface_area / 100.0), + _L("Mass properties"), wxOK, this); + return; + } m_status->SetForegroundColour(wxNullColour); set_status(wxString::Format(_L("%s: %.3f cm³, %.2f cm²"), name, mp.volume / 1000.0, mp.surface_area / 100.0)); diff --git a/src/slic3r/GUI/McpControl.cpp b/src/slic3r/GUI/McpControl.cpp index 6f0598c66c..5a87fa37c8 100644 --- a/src/slic3r/GUI/McpControl.cpp +++ b/src/slic3r/GUI/McpControl.cpp @@ -556,11 +556,23 @@ json mass_properties(DesignPanel* panel, const json& params) const TopoDS_Shape& shape = body_shape(panel, params); auto mp = GeometryEngine::mass_properties(shape); if (!mp.valid) throw std::runtime_error("mass properties could not be computed (null/empty shape)"); + // A sheet body has no volume and no inertia. Report the area and say so, rather than + // returning numbers a caller would reasonably treat as a material check. + if (!mp.is_solid) + return json{ + {"surface_area", mp.surface_area}, + {"volume", 0.0}, + {"is_solid", false}, + {"valid", mp.valid}, + {"note", "sheet body (open shell): it encloses no material, so volume and inertia " + "are not defined; surface_area is exact"}, + }; return json{ {"volume", mp.volume}, {"surface_area", mp.surface_area}, {"center_of_mass", json::array({mp.center_of_mass.x(), mp.center_of_mass.y(), mp.center_of_mass.z()})}, {"inertia", mp.inertia}, + {"is_solid", true}, {"valid", mp.valid}, }; }