diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index dfd03fbed..6c07f436b 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -66,6 +66,8 @@ #define ENABLE_SEAMS_USING_BATCHED_MODELS (1 && ENABLE_SEAMS_USING_MODELS && ENABLE_2_4_0_ALPHA2) // Enable fixing the z position of color change, pause print and custom gcode markers in preview #define ENABLE_FIX_PREVIEW_OPTIONS_Z (1 && ENABLE_SEAMS_USING_MODELS && ENABLE_FIX_IMPORTING_COLOR_PRINT_VIEW_INTO_GCODEVIEWER && ENABLE_2_4_0_ALPHA2) +// Enable replacing a missing file during reload from disk command +#define ENABLE_RELOAD_FROM_DISK_REPLACE_FILE (1 && ENABLE_2_4_0_ALPHA2) // Enable changes in preview layout #define ENABLE_PREVIEW_LAYOUT (1 && ENABLE_2_4_0_ALPHA2) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 6506cb2c1..76e2e07f4 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1717,6 +1717,9 @@ struct Plater::priv } void export_gcode(fs::path output_path, bool output_path_on_removable_media, PrintHostJob upload_job); void reload_from_disk(); +#if ENABLE_RELOAD_FROM_DISK_REPLACE_FILE + bool replace_volume_with_stl(int object_idx, int volume_idx, const fs::path& new_path, const wxString& snapshot = ""); +#endif // ENABLE_RELOAD_FROM_DISK_REPLACE_FILE void replace_with_stl(); void reload_all_from_disk(); void fix_through_netfabb(const int obj_idx, const int vol_idx = -1); @@ -3194,6 +3197,76 @@ void Plater::priv::update_sla_scene() this->update_restart_background_process(true, true); } +#if ENABLE_RELOAD_FROM_DISK_REPLACE_FILE +bool Plater::priv::replace_volume_with_stl(int object_idx, int volume_idx, const fs::path& new_path, const wxString& snapshot) +{ + const std::string path = new_path.string(); + wxBusyCursor wait; + wxBusyInfo info(_L("Replace from:") + " " + from_u8(path), q->get_current_canvas3D()->get_wxglcanvas()); + + Model new_model; + try { + new_model = Model::read_from_file(path, nullptr, nullptr, Model::LoadAttribute::AddDefaultInstances); + for (ModelObject* model_object : new_model.objects) { + model_object->center_around_origin(); + model_object->ensure_on_bed(); + } + } + catch (std::exception&) { + // error while loading + return false; + } + + if (new_model.objects.size() > 1 || new_model.objects.front()->volumes.size() > 1) { + MessageDialog dlg(q, _L("Unable to replace with more than one volume"), _L("Error during replace"), wxOK | wxOK_DEFAULT | wxICON_WARNING); + dlg.ShowModal(); + return false; + } + + if (!snapshot.empty()) + q->take_snapshot(snapshot); + + ModelObject* old_model_object = model.objects[object_idx]; + ModelVolume* old_volume = old_model_object->volumes[volume_idx]; + + bool sinking = old_model_object->bounding_box().min.z() < SINKING_Z_THRESHOLD; + + ModelObject* new_model_object = new_model.objects.front(); + old_model_object->add_volume(*new_model_object->volumes.front()); + ModelVolume* new_volume = old_model_object->volumes.back(); + new_volume->set_new_unique_id(); + new_volume->config.apply(old_volume->config); + new_volume->set_type(old_volume->type()); + new_volume->set_material_id(old_volume->material_id()); + new_volume->set_transformation(old_volume->get_transformation()); + new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset)); + assert(!old_volume->source.is_converted_from_inches || !old_volume->source.is_converted_from_meters); + if (old_volume->source.is_converted_from_inches) + new_volume->convert_from_imperial_units(); + else if (old_volume->source.is_converted_from_meters) + new_volume->convert_from_meters(); + new_volume->supported_facets.assign(old_volume->supported_facets); + new_volume->seam_facets.assign(old_volume->seam_facets); + new_volume->mmu_segmentation_facets.assign(old_volume->mmu_segmentation_facets); + std::swap(old_model_object->volumes[volume_idx], old_model_object->volumes.back()); + old_model_object->delete_volume(old_model_object->volumes.size() - 1); + if (!sinking) + old_model_object->ensure_on_bed(); + old_model_object->sort_volumes(wxGetApp().app_config->get("order_volumes") == "1"); + + // if object has just one volume, rename object too + if (old_model_object->volumes.size() == 1) + old_model_object->name = old_model_object->volumes.front()->name; + + // update new name in ObjectList + sidebar->obj_list()->update_name_in_list(object_idx, volume_idx); + + sla::reproject_points_and_holes(old_model_object); + + return true; +} +#endif // ENABLE_RELOAD_FROM_DISK_REPLACE_FILE + void Plater::priv::replace_with_stl() { if (! q->canvas3D()->get_gizmos_manager().check_gizmos_closed_except(GLGizmosManager::EType::Undefined)) @@ -3230,8 +3303,10 @@ void Plater::priv::replace_with_stl() return; } - wxString fail_replace; - +#if ENABLE_RELOAD_FROM_DISK_REPLACE_FILE + if (!replace_volume_with_stl(object_idx, volume_idx, out_path, _L("Replace with STL"))) + return; +#else const auto& path = out_path.string(); wxBusyCursor wait; wxBusyInfo info(_L("Replace from:") + " " + from_u8(path), q->get_current_canvas3D()->get_wxglcanvas()); @@ -3292,7 +3367,8 @@ void Plater::priv::replace_with_stl() // update new name in ObjectList sidebar->obj_list()->update_name_in_list(object_idx, volume_idx); - sla::reproject_points_and_holes(old_model_object); + sla::reproject_points_and_holes(old_model_object); +#endif // ENABLE_RELOAD_FROM_DISK_REPLACE_FILE // update 3D scene update(); @@ -3341,6 +3417,9 @@ void Plater::priv::reload_from_disk() // collects paths of files to load std::vector input_paths; std::vector missing_input_paths; +#if ENABLE_RELOAD_FROM_DISK_REPLACE_FILE + std::vector replace_paths; +#endif // ENABLE_RELOAD_FROM_DISK_REPLACE_FILE for (const SelectedVolume& v : selected_volumes) { const ModelObject* object = model.objects[v.object_idx]; const ModelVolume* volume = object->volumes[v.volume_idx]; @@ -3348,8 +3427,28 @@ void Plater::priv::reload_from_disk() if (!volume->source.input_file.empty()) { if (fs::exists(volume->source.input_file)) input_paths.push_back(volume->source.input_file); +#if ENABLE_RELOAD_FROM_DISK_REPLACE_FILE + else { + // searches the source in the same folder containing the object + bool found = false; + if (!object->input_file.empty()) { + fs::path object_path = fs::path(object->input_file).remove_filename(); + if (!object_path.empty()) { + object_path /= fs::path(volume->source.input_file).filename(); + const std::string source_input_file = object_path.string(); + if (fs::exists(source_input_file)) { + input_paths.push_back(source_input_file); + found = true; + } + } + } + if (!found) + missing_input_paths.push_back(volume->source.input_file); + } +#else else missing_input_paths.push_back(volume->source.input_file); +#endif // ENABLE_RELOAD_FROM_DISK_REPLACE_FILE } else if (!object->input_file.empty() && volume->is_model_part() && !volume->name.empty() && !volume->source.is_from_builtin_objects) missing_input_paths.push_back(volume->name); @@ -3392,17 +3491,32 @@ void Plater::priv::reload_from_disk() } } else { +#if ENABLE_RELOAD_FROM_DISK_REPLACE_FILE + wxString message = _L("The selected file") + " (" + from_u8(sel_filename) + ") " + + _L("differs from the original file") + " (" + from_u8(search.filename().string()) + ").\n" + _L("Do you want to replace it") + " ?"; + //wxMessageDialog dlg(q, message, wxMessageBoxCaptionStr, wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION); + MessageDialog dlg(q, message, wxMessageBoxCaptionStr, wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION); + if (dlg.ShowModal() == wxID_YES) + replace_paths.push_back(sel_filename_path); + missing_input_paths.pop_back(); +#else wxString message = _L("It is not allowed to change the file to reload") + " (" + from_u8(search.filename().string()) + ").\n" + _L("Do you want to retry") + " ?"; //wxMessageDialog dlg(q, message, wxMessageBoxCaptionStr, wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION); MessageDialog dlg(q, message, wxMessageBoxCaptionStr, wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION); if (dlg.ShowModal() != wxID_YES) return; +#endif // ENABLE_RELOAD_FROM_DISK_REPLACE_FILE } } std::sort(input_paths.begin(), input_paths.end()); input_paths.erase(std::unique(input_paths.begin(), input_paths.end()), input_paths.end()); +#if ENABLE_RELOAD_FROM_DISK_REPLACE_FILE + std::sort(replace_paths.begin(), replace_paths.end()); + replace_paths.erase(std::unique(replace_paths.begin(), replace_paths.end()), replace_paths.end()); +#endif // ENABLE_RELOAD_FROM_DISK_REPLACE_FILE + std::vector fail_list; // load one file at a time @@ -3497,6 +3611,20 @@ void Plater::priv::reload_from_disk() } } +#if ENABLE_RELOAD_FROM_DISK_REPLACE_FILE + for (size_t i = 0; i < replace_paths.size(); ++i) { + const auto& path = replace_paths[i].string(); + for (const SelectedVolume& sel_v : selected_volumes) { + ModelObject* old_model_object = model.objects[sel_v.object_idx]; + ModelVolume* old_volume = old_model_object->volumes[sel_v.volume_idx]; + bool has_source = !old_volume->source.input_file.empty() && boost::algorithm::iequals(fs::path(old_volume->source.input_file).filename().string(), fs::path(path).filename().string()); + if (!replace_volume_with_stl(sel_v.object_idx, sel_v.volume_idx, path, "")) { + fail_list.push_back(from_u8(has_source ? old_volume->source.input_file : old_volume->name)); + } + } + } +#endif // ENABLE_RELOAD_FROM_DISK_REPLACE_FILE + if (!fail_list.empty()) { wxString message = _L("Unable to reload:") + "\n"; for (const wxString& s : fail_list) {