Port from snaporca: sketch-only projects save, scripted geometry arrives exact,

and a ladder that draws with the mouse

Three commits carried across (snaporca 4ffd60eacb, 421055c2ec, b71216ce0b):

1. A design made only of sketches must survive being saved. CadDocument::recompute
   returned false with "no solid-producing features" for a document that has no
   solid, and two callers read that as "unusable": the GUI syncs the 3MF recipe
   only after a successful recompute, so a sketch-only design was saved with no
   recipe at all, and deserialize_recipe ends with `return recompute()`, so even a
   project that carried one was refused on load. Having nothing to build is now a
   success; a feature that MEANT to build a solid and produced none still fails.
   DesignPanel::refresh_tree syncs the recipe too, for the paths that call
   m_doc.recompute() directly.

2. Scripted geometry arrives exact. The Horizontal/Vertical inference window and
   the endpoint weld window both close to zero for add_entities_scripted; void
   attribution probes from a point strictly inside each loop instead of from its
   first vertex. Corpus rung 39 graded / 39 fully clean, was 35 with 6 failures.

3. scripts/gui-ladder.py — 17 rungs, 84 properties, all driven by synthetic clicks
   and typed values rather than through the socket.

Parity 17 identical / 8 diverging as expected. Kernel suite here: 188 cases /
2532 assertions.

snaporca-mtav, snaporca-8xg1, snaporca-5hvl, snaporca-730j
This commit is contained in:
Tommaso Bianchi
2026-08-23 01:22:32 +02:00
parent 05ce2607a8
commit 82db99f337
7 changed files with 1211 additions and 11 deletions
+14 -2
View File
@@ -3510,6 +3510,14 @@ bool CadDocument::recompute()
error.clear();
detect_mate_conflicts();
std::vector<CadBody> built;
// Did any feature in this document even ASK for a solid? A document made only of sketches
// and datums has nothing to build, and that is a legitimate state — it is every document
// between drawing the first profile and extruding it. Reporting it as a failure is what
// made a sketch-only design unsaveable AND unopenable: DesignPanel::recompute_guarded syncs
// the 3MF recipe only "on success", so nothing was written, and deserialize_recipe ends with
// `return recompute()`, so a project that did carry a recipe was refused on load with
// "Could not restore the CAD model" while its features sat correctly in the list. snaporca-mtav.
bool any_solid_feature = false;
try {
// Parametric pass: evaluate document variables, then each feature's expression bindings,
// writing the results into the feature's numeric fields before geometry runs.
@@ -3525,6 +3533,8 @@ bool CadDocument::recompute()
if (f.type == CadFeatureType::Plane) continue; // datum: no solid, derived on demand
if (f.type == CadFeatureType::Axis) continue; // datum axis
if (f.type == CadFeatureType::CoordSys) continue; // datum coordinate system
// Past the skips: this feature is one that means to leave a body behind.
any_solid_feature = true;
if (f.type == CadFeatureType::Project) { apply_project(built, f); }
else { route_feature(built, f); }
// Record which feature made each body. "Still unset?" is the whole rule, and it is
@@ -3551,7 +3561,7 @@ bool CadDocument::recompute()
error = "unknown geometry error";
return false;
}
if (built.empty()) { error = "no solid-producing features"; return false; }
if (built.empty() && any_solid_feature) { error = "no solid-producing features"; return false; }
// A feature that leaves a body with a null shape must fail loudly. Until this existed,
// recompute() returned true and the document kept advertising the body: describe_scene
@@ -3624,7 +3634,9 @@ bool CadDocument::recompute()
display_mesh = tessellate_bodies(bodies, display_tri_face, display_tri_body,
display_body_meshes,
linear_deflection, angular_deflection);
if (display_mesh.its.indices.empty()) {
if (display_mesh.its.indices.empty() && any_solid_feature) {
// Empty only because there are no bodies to tessellate is the same legitimate state as
// above: a sketch-only document has nothing to draw as a solid, and that is not a fault.
error = "tessellation produced an empty mesh";
return false;
}