From ba1df32e884b4db545466849ba17ba928614a00e Mon Sep 17 00:00:00 2001 From: SoftFever Date: Thu, 27 Aug 2026 23:35:18 +0800 Subject: [PATCH] Reset the CAD document with the project, and track its changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New Project and Open Project went through Plater::priv::reset, which drops ModelObjects but not the Model-level recipe, and never touched the Design panel's document at all — so the previous design stayed loaded and its next edit wrote itself into the new project. A design that has not been committed to the plate has no ModelObjects, so the project also read as clean: no autosave, and no unsaved-changes prompt before the reset threw the design away. --- src/slic3r/GUI/CAD/DesignPanel.cpp | 21 ++++++++++++- src/slic3r/GUI/CAD/DesignPanel.hpp | 1 + src/slic3r/GUI/Plater.cpp | 48 ++++++++++++++++++++++-------- 3 files changed, 57 insertions(+), 13 deletions(-) diff --git a/src/slic3r/GUI/CAD/DesignPanel.cpp b/src/slic3r/GUI/CAD/DesignPanel.cpp index 33b1d8b338..619f795697 100644 --- a/src/slic3r/GUI/CAD/DesignPanel.cpp +++ b/src/slic3r/GUI/CAD/DesignPanel.cpp @@ -5,6 +5,7 @@ #include "libslic3r/CAD/GeometryEngine.hpp" // face_by_index for face-extrude gizmo anchor #include "libslic3r/TriangleMesh.hpp" // mesh import: STL/OBJ -> indexed_triangle_set #include "libslic3r/Format/OBJ.hpp" +#include "libslic3r/Format/bbs_3mf.hpp" // put_other_changes: mark the project dirty outside the undo stack #include #include @@ -4620,7 +4621,16 @@ void DesignPanel::sync_recipe_to_model() Plater* plater = wxGetApp().plater(); if (plater == nullptr) return; // An empty document CLEARS it, so a non-CAD project never carries a stale recipe. - plater->model().cad_recipe = m_doc.features.empty() ? std::string() : m_doc.serialize_recipe(); + std::string recipe = m_doc.features.empty() ? std::string() : m_doc.serialize_recipe(); + if (recipe == plater->model().cad_recipe) + return; // no change — this is the rehydrate of a project that was just opened + plater->model().cad_recipe = std::move(recipe); + + // The plater's dirty flag rides its own undo/redo stack, which Design edits never touch, and + // a design that has not been committed to the plate has no ModelObjects either — so without + // this the project reads as clean: no autosave, and no "unsaved changes" prompt on quit. + // Same hook the auxiliary-files panel uses for project data that lives outside the model. + Slic3r::put_other_changes(); } bool DesignPanel::recompute_guarded(const wxString& message) @@ -7427,6 +7437,15 @@ void DesignPanel::on_new_design() _L("Erase all features and bodies and start a new design? This cannot be undone."), _L("New Design"), wxYES_NO | wxICON_EXCLAMATION); if (dlg.ShowModal() != wxID_YES) return; + clear_document(); +} + +// The teardown behind New Design, without the confirmation. Also what New Project / Open +// Project run through Plater::priv::reset: the document lives here rather than in the Model, +// so without this it survives the project that produced it and the next Design edit writes +// the previous project's feature tree into the new one. +void DesignPanel::clear_document() +{ tool_cancel(); // leave any active tool / sketch / constrain cleanly m_doc.clear(); // features + bodies + meshes + history m_edit_index = -1; diff --git a/src/slic3r/GUI/CAD/DesignPanel.hpp b/src/slic3r/GUI/CAD/DesignPanel.hpp index fe26885b20..762c8ebef0 100644 --- a/src/slic3r/GUI/CAD/DesignPanel.hpp +++ b/src/slic3r/GUI/CAD/DesignPanel.hpp @@ -51,6 +51,7 @@ public: explicit DesignPanel(wxWindow* parent); void on_tab_shown(); // re-sync bed to the active printer when the Design tab is activated void on_tab_hidden(); // another tab took over: take the viewport status line down with us + void clear_document(); // New Project / Open Project: drop the document with the project // Rebuild off the UI thread (progress dialog only if it turns out to be slow), so a feature // op on a heavy imported solid does not freeze the window. Returns m_doc.recompute()'s result. // Push the document's recipe into the Model so ANY save path persists it (snaporca-vjk5). diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 69ef16e1f5..ad8c6d556b 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -97,6 +97,9 @@ #include "wxExtensions.hpp" #include "../Utils/PrintHost.hpp" #include "MainFrame.hpp" +#ifdef SLIC3R_CAD +#include "slic3r/GUI/CAD/DesignPanel.hpp" +#endif #include "format.hpp" #include "3DScene.hpp" #include "GLCanvas3D.hpp" @@ -8343,6 +8346,9 @@ std::vector Plater::priv::load_files(const std::vector& input_ int answer_convert_from_meters = wxOK_DEFAULT; int answer_convert_from_imperial_units = wxOK_DEFAULT; int tolal_model_count = 0; + // Whether one of the files being loaded here carried a CAD recipe. A statement about these + // files, not about the plater — q->model() may still hold the previous project's recipe. + bool loaded_cad_recipe = false; int progress_percent = 0; int total_files = input_files.size(); @@ -9290,11 +9296,15 @@ std::vector Plater::priv::load_files(const std::vector& input_ auto loaded_idxs = load_model_objects(model.objects, is_project_file); obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end()); - // load_model_objects only transfers ModelObjects; carry the Model-level CAD - // recipe (Metadata/orca_cad.bin) onto the plater model so the Design tab - // can rehydrate the editable feature tree on reopen. - if (!model.cad_recipe.empty()) + // load_model_objects only transfers ModelObjects; carry the Model-level CAD recipe + // onto the plater model so the Design tab can rehydrate the editable feature tree on + // reopen. Assigned unconditionally on the project-replacing path so that opening a + // project without a recipe clears whatever the previous one left behind; importing a + // plain model into the open project leaves the current recipe alone. + if (is_project_file) { q->model().cad_recipe = model.cad_recipe; + loaded_cad_recipe = !model.cad_recipe.empty(); + } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", finished load_model_objects"); wxString msg = wxString::Format(_L("Loading file: %s"), from_path(real_filename)); @@ -9512,12 +9522,12 @@ std::vector Plater::priv::load_files(const std::vector& input_ // q->model().stl_design_country = ""; //} - // A CAD project legitimately carries no mesh: the model lives in the feature tree - // (Metadata/orca_cad.bin) until it is committed to the plate. Warning "no geometry data" - // for one is false, and it is the LAST thing a user sees after opening a design they spent an - // hour on — it reads as "your work is gone" when the recipe has in fact just been loaded and - // the Design tab will rehydrate it. Count the recipe as geometry. - if (tolal_model_count <= 0 && q->model().cad_recipe.empty() && !q->m_exported_file) { + // A CAD project legitimately carries no mesh: the model lives in the feature tree until it is + // committed to the plate. Warning "no geometry data" for one is false, and it is the LAST thing + // a user sees after opening a design they spent an hour on — it reads as "your work is gone" + // when the recipe has in fact just been loaded and the Design tab will rehydrate it. Count a + // recipe that came from THESE files as geometry. + if (tolal_model_count <= 0 && !loaded_cad_recipe && !q->m_exported_file) { dlg.Hide(); if (!is_user_cancel) { MessageDialog msg(wxGetApp().mainframe, _L("The file does not contain any geometry data."), _L("Warning"), wxYES | wxICON_WARNING); @@ -10056,6 +10066,16 @@ void Plater::priv::reset(bool apply_presets_change) // Stop and reset the Print content. this->background_process.reset(); model.clear_objects(); + // clear_objects() only drops the ModelObjects; the CAD recipe is Model-level state and would + // otherwise be written into every project saved for the rest of the session. + model.cad_recipe.clear(); +#ifdef SLIC3R_CAD + // Same reason, one level up: the Design tab keeps the editable document, not the Model, so + // clearing the recipe alone leaves the tab showing the previous project's feature tree — + // and its next edit syncs that tree straight back into the new project. + if (wxGetApp().mainframe != nullptr && wxGetApp().mainframe->m_design_panel != nullptr) + wxGetApp().mainframe->m_design_panel->clear_document(); +#endif assemble_view->get_canvas3d()->reset_explosion_ratio(); update(); @@ -15508,8 +15528,12 @@ bool Plater::up_to_date(bool saved, bool backup) Slic3r::clear_other_changes(backup); return p->up_to_date(saved, backup); } - return p->model.objects.empty() || (p->up_to_date(saved, backup) && - !Slic3r::has_other_changes(backup)); + // A Design-tab project is object-less until it is committed to the plate, but its feature + // tree is real work: treating it as an empty project skipped both the autosave and the + // "unsaved changes" prompt, so quitting threw it away without asking. Non-CAD projects + // never carry a recipe, so the empty-project shortcut is unchanged for them. + return (p->model.objects.empty() && p->model.cad_recipe.empty()) || + (p->up_to_date(saved, backup) && !Slic3r::has_other_changes(backup)); } bool Plater::add_model(bool imperial_units, std::string fname)