diff --git a/localization/i18n/zh_CN/Snapmaker_Orca_zh_CN.po b/localization/i18n/zh_CN/Snapmaker_Orca_zh_CN.po index abef0c29816..f1c28c641a6 100644 --- a/localization/i18n/zh_CN/Snapmaker_Orca_zh_CN.po +++ b/localization/i18n/zh_CN/Snapmaker_Orca_zh_CN.po @@ -15348,4 +15348,7 @@ msgid "Other Colors" msgstr "其他颜色" msgid "Multiple Color" -msgstr "多色" \ No newline at end of file +msgstr "多色" + +msgid "Fit in all view" +msgstr "相机适配" diff --git a/resources/images/fit_camera.svg b/resources/images/fit_camera.svg new file mode 100644 index 00000000000..3945926c8ef --- /dev/null +++ b/resources/images/fit_camera.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/resources/images/fit_camera_dark.svg b/resources/images/fit_camera_dark.svg new file mode 100644 index 00000000000..2f0842b526f --- /dev/null +++ b/resources/images/fit_camera_dark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/resources/images/fit_camera_dark_hover.svg b/resources/images/fit_camera_dark_hover.svg new file mode 100644 index 00000000000..33a002bfc70 --- /dev/null +++ b/resources/images/fit_camera_dark_hover.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/resources/images/fit_camera_hover.svg b/resources/images/fit_camera_hover.svg new file mode 100644 index 00000000000..a31a62768f0 --- /dev/null +++ b/resources/images/fit_camera_hover.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 2ad2daaeca8..5662f4cdb0b 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -504,6 +504,104 @@ ModelObject* Model::add_object(const ModelObject &other) return new_object; } +void Model::InitializeAssemblyPositions(const ModelObjectPtrs& modelObjects) +{ + constexpr double ASSEMBLY_OBJECT_GAP = 10.0; + + if (modelObjects.empty()) + { + return; + } + + ModelObjectPtrs validatedObjects; + validatedObjects.reserve(modelObjects.size()); + for (ModelObject* modelObject : modelObjects) + { + if (modelObject == nullptr || modelObject->instances.empty() || + std::find(objects.begin(), objects.end(), modelObject) == objects.end() || + std::find(validatedObjects.begin(), validatedObjects.end(), modelObject) != validatedObjects.end()) + { + continue; + } + + const BoundingBoxf3& rawBox = modelObject->raw_mesh_bounding_box(); + if (!rawBox.defined) + { + continue; + } + + validatedObjects.push_back(modelObject); + } + + BoundingBoxf3 sceneBox = CalculateAssemblyBoundingBox(validatedObjects); + for (ModelObject* modelObject : validatedObjects) + { + const BoundingBoxf3& rawBox = modelObject->raw_mesh_bounding_box(); + std::vector instanceBoxes(modelObject->instances.size()); + + double objectWidth = 0.0; + bool hasValidInstance = false; + for (size_t instanceIndex = 0; instanceIndex < modelObject->instances.size(); ++instanceIndex) + { + ModelInstance* instance = modelObject->instances[instanceIndex]; + if (instance == nullptr) + { + continue; + } + + if (!instance->is_assemble_initialized()) + { + instance->set_assemble_transformation(instance->get_transformation()); + } + + BoundingBoxf3& instanceBox = instanceBoxes[instanceIndex]; + instanceBox = rawBox.transformed(instance->get_assemble_transformation().get_matrix_no_offset()); + if (!instanceBox.defined) + { + continue; + } + + objectWidth = std::max(objectWidth, instanceBox.size().x()); + hasValidInstance = true; + } + + if (!hasValidInstance) + { + continue; + } + + const double objectCenterX = sceneBox.defined ? sceneBox.max.x() + ASSEMBLY_OBJECT_GAP + objectWidth * 0.5 : 0.0; + const double firstInstanceCenterY = sceneBox.defined ? sceneBox.center().y() : 0.0; + double previousInstanceMaxY = 0.0; + bool firstValidInstance = true; + + for (size_t instanceIndex = 0; instanceIndex < modelObject->instances.size(); ++instanceIndex) + { + ModelInstance* instance = modelObject->instances[instanceIndex]; + if (instance == nullptr) + { + continue; + } + + const BoundingBoxf3& instanceBox = instanceBoxes[instanceIndex]; + if (!instanceBox.defined) + { + continue; + } + + const double instanceCenterY = firstValidInstance ? firstInstanceCenterY : + previousInstanceMaxY + ASSEMBLY_OBJECT_GAP + instanceBox.size().y() * 0.5; + const Vec3d assemblyOffset(objectCenterX - instanceBox.center().x(), + instanceCenterY - instanceBox.center().y(), -instanceBox.min.z()); + instance->set_assemble_offset(assemblyOffset); + previousInstanceMaxY = instanceCenterY + instanceBox.size().y() * 0.5; + firstValidInstance = false; + } + + sceneBox.merge(modelObject->CalculateAssemblyBoundingBox()); + } +} + void Model::delete_object(size_t idx) { ModelObjectPtrs::iterator i = this->objects.begin() + idx; @@ -663,6 +761,23 @@ BoundingBoxf3 Model::bounding_box_exact() const return bb; } +BoundingBoxf3 Model::CalculateAssemblyBoundingBox(const ModelObjectPtrs& excludedObjects) const +{ + BoundingBoxf3 assemblyBox; + for (const ModelObject* modelObject : objects) + { + if (modelObject == nullptr || + std::find(excludedObjects.begin(), excludedObjects.end(), modelObject) != excludedObjects.end()) + { + continue; + } + + assemblyBox.merge(modelObject->CalculateAssemblyBoundingBox()); + } + + return assemblyBox; +} + double Model::max_z() const { double z = 0; @@ -1440,6 +1555,26 @@ const BoundingBoxf3& ModelObject::bounding_box_exact() const return m_bounding_box_exact; } +BoundingBoxf3 ModelObject::CalculateAssemblyBoundingBox() const +{ + BoundingBoxf3 assemblyBox; + const BoundingBoxf3 rawBox = raw_mesh_bounding_box(); + if (!rawBox.defined) + { + return assemblyBox; + } + + for (const ModelInstance* instance : instances) + { + if (instance != nullptr) + { + assemblyBox.merge(rawBox.transformed(instance->get_assemble_transformation().get_matrix())); + } + } + + return assemblyBox; +} + double ModelObject::min_z() const { const_cast(this)->update_min_max_z(); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 15551193790..3402a2f625c 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -444,6 +444,11 @@ class ModelObject final : public ObjectBase const BoundingBoxf3& bounding_box_approx() const; // Returns an exact bounding box of the transformed instances. The result it is being cached. const BoundingBoxf3& bounding_box_exact() const; + /** + * @brief Calculates the bounding box of all instances in assembly-view coordinates. + * @return The merged assembly-view bounding box. + */ + BoundingBoxf3 CalculateAssemblyBoundingBox() const; // Return minimum / maximum of a printable object transformed into the world coordinate system. // All instances share the same min / max Z. double min_z() const; @@ -1281,7 +1286,11 @@ class ModelInstance final : public ObjectBase m_assemble_transformation.set_matrix(transform); } Vec3d get_assemble_offset() const {return m_assemble_transformation.get_offset(); } - void set_assemble_offset(const Vec3d& offset) { m_assemble_transformation.set_offset(offset); } + void set_assemble_offset(const Vec3d& offset) + { + m_assemble_initialized = true; + m_assemble_transformation.set_offset(offset); + } void set_assemble_rotation(const Vec3d &rotation) { m_assemble_transformation.set_rotation(rotation); } void rotate_assemble(double angle, const Vec3d& axis) { m_assemble_transformation.set_rotation(m_assemble_transformation.get_rotation() + Geometry::extract_euler_angles(Eigen::Quaterniond(Eigen::AngleAxisd(angle, axis)).toRotationMatrix())); @@ -1616,6 +1625,12 @@ class Model final : public ObjectBase ModelObject* add_object(const char *name, const char *path, const TriangleMesh &mesh); ModelObject* add_object(const char *name, const char *path, TriangleMesh &&mesh); ModelObject* add_object(const ModelObject &other); + /** + * @brief Initializes newly created objects as one assembly-view layout batch. + * @param modelObjects Objects owned by this model, in their desired layout order. + * Invalid objects and instances are skipped. + */ + void InitializeAssemblyPositions(const ModelObjectPtrs& modelObjects); void delete_object(size_t idx); bool delete_object(ObjectID id); bool delete_object(ModelObject* object); @@ -1640,6 +1655,12 @@ class Model final : public ObjectBase BoundingBoxf3 bounding_box_approx() const; // Returns exact axis aligned bounding box of this model. BoundingBoxf3 bounding_box_exact() const; + /** + * @brief Calculates the assembly-view bounding box for this model. + * @param excludedObjects Objects to exclude from the result. + * @return The merged assembly-view bounding box. + */ + BoundingBoxf3 CalculateAssemblyBoundingBox(const ModelObjectPtrs& excludedObjects = {}) const; // Return maximum height of all printable objects. double max_z() const; // Set the print_volume_state of PrintObject::instances, diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 3c4943628a0..4f6d7b12f99 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1812,6 +1812,25 @@ void GLCanvas3D::zoom_to_plate(int plate_idx) } } +void GLCanvas3D::ZoomToFit() +{ + select_view("plate"); + + if (!m_selection.is_empty()) + { + zoom_to_selection(); + return; + } + + if (m_canvas_type == ECanvasType::CanvasAssembleView) + { + zoom_to_volumes(); + return; + } + + zoom_to_bed(); +} + void GLCanvas3D::select_view(const std::string& direction) { wxGetApp().plater()->get_camera().select_view(direction); @@ -5901,6 +5920,63 @@ void GLCanvas3D::_render_3d_navigator() request_extra_frame(); } + + const float fitButtonSize = ImGui::GetFontSize() * 2.5f; + const float fitButtonGap = 8.0f * sc; + const float fitButtonLeft = viewManipulateLeft + size + fitButtonGap; + const float fitButtonTop = viewManipulateTop - fitButtonSize - 20.0f * sc; + RenderFitCameraButton(fitButtonLeft, fitButtonTop, fitButtonSize); +} + +void GLCanvas3D::RenderFitCameraButton(float left, float top, float buttonSize) +{ + if (buttonSize <= 0.0f) + { + return; + } + + const GLGizmosManager::MENU_ICON_NAME normalIcon = m_is_dark ? + GLGizmosManager::IC_FIT_CAMERA_DARK : GLGizmosManager::IC_FIT_CAMERA; + const GLGizmosManager::MENU_ICON_NAME hoverIcon = m_is_dark ? + GLGizmosManager::IC_FIT_CAMERA_DARK_HOVER : GLGizmosManager::IC_FIT_CAMERA_HOVER; + + if (!m_gizmos.init_icon_textures()) + { + return; + } + + const ImTextureID normalId = m_gizmos.get_icon_texture_id(normalIcon); + const ImTextureID hoverId = m_gizmos.get_icon_texture_id(hoverIcon); + if (normalId == nullptr || hoverId == nullptr) + { + return; + } + + ImGuiWrapper& imgui = *wxGetApp().imgui(); + imgui.set_next_window_pos(left, top, ImGuiCond_Always, 0.0f, 0.0f); + imgui.set_next_window_size(buttonSize, buttonSize, ImGuiCond_Always); + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + imgui.begin(std::string("FitCameraButtonWindow"), ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoBackground | + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | + ImGuiWindowFlags_NoSavedSettings); + + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f)); + if (ImGui::ImageButton3(normalId, hoverId, ImVec2(buttonSize, buttonSize))) + { + ZoomToFit(); + } + + if (ImGui::IsItemHovered()) + { + imgui.tooltip(_L("Fit in all view"), ImGui::GetFontSize() * 20.0f); + } + + ImGui::PopStyleVar(2); + imgui.end(); + ImGui::PopStyleVar(); } #define ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT 0 diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index bd2df0bcec3..daf4d7495db 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -855,6 +855,7 @@ class GLCanvas3D void zoom_to_gcode(); //BBS -1 for current plate void zoom_to_plate(int plate_idx = -1); + void ZoomToFit(); void select_view(const std::string& direction); //BBS: add part plate related logic void select_plate(); @@ -1209,6 +1210,13 @@ class GLCanvas3D bool _render_orient_menu(float left, float right, float bottom, float top); bool _render_arrange_menu(float left, float right, float bottom, float top); void _render_3d_navigator(); + /** + * @brief Renders the fit-camera button next to the 3D navigator. + * @param left Left position in ImGui screen coordinates. + * @param top Top position in ImGui screen coordinates. + * @param buttonSize Width and height of the square button. + */ + void RenderFitCameraButton(float left, float top, float buttonSize); // render thumbnail using the default framebuffer void render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, ModelObjectPtrs& model_objects, const GLVolumeCollection& volumes, std::vector& extruder_colors, GLShaderProgram* shader, Camera::EType camera_type); diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 87a6d909cb5..a72223fcaf5 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -599,7 +599,24 @@ wxMenu* MenuFactory::append_submenu_add_handy_model(wxMenu* menu, ModelVolumeTyp } else return; input_files.push_back((boost::filesystem::path(Slic3r::resources_dir()) / "handy_models" / file_name)); - plater()->load_files(input_files, LoadStrategy::LoadModel); + Plater* currentPlater = plater(); + if (currentPlater == nullptr) + { + return; + } + + const std::vector loadedObjectIndexes = currentPlater->load_files(input_files, LoadStrategy::LoadModel); + Model& model = currentPlater->model(); + ModelObjectPtrs loadedObjects; + loadedObjects.reserve(loadedObjectIndexes.size()); + for (const size_t objectIndex : loadedObjectIndexes) + { + if (objectIndex < model.objects.size()) + { + loadedObjects.push_back(model.objects[objectIndex]); + } + } + model.InitializeAssemblyPositions(loadedObjects); // Suggest to change settings for stringhell // This serves as mini tutorial for new users diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index b1130ea33c1..70619122dc4 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2465,9 +2465,7 @@ void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name new_object->ensure_on_bed(); - //BBS init assmeble transformation - Geometry::Transformation t = new_object->instances[0]->get_transformation(); - new_object->instances[0]->set_assemble_transformation(t); + model.InitializeAssemblyPositions({new_object}); object_idxs.push_back(model.objects.size() - 1); #ifdef _DEBUG diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 1b2bb4bad74..4ed7677512b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -245,6 +245,11 @@ bool GLGizmosManager::init() bool GLGizmosManager::init_icon_textures() { + if (!icon_list.empty()) + { + return true; + } + ImTextureID texture_id; icon_list.clear(); @@ -278,6 +283,26 @@ bool GLGizmosManager::init_icon_textures() else return false; + if (IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/fit_camera.svg", 64, 64, texture_id)) + icon_list.insert(std::make_pair(static_cast(IC_FIT_CAMERA), texture_id)); + else + return false; + + if (IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/fit_camera_hover.svg", 64, 64, texture_id)) + icon_list.insert(std::make_pair(static_cast(IC_FIT_CAMERA_HOVER), texture_id)); + else + return false; + + if (IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/fit_camera_dark.svg", 64, 64, texture_id)) + icon_list.insert(std::make_pair(static_cast(IC_FIT_CAMERA_DARK), texture_id)); + else + return false; + + if (IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/fit_camera_dark_hover.svg", 64, 64, texture_id)) + icon_list.insert(std::make_pair(static_cast(IC_FIT_CAMERA_DARK_HOVER), texture_id)); + else + return false; + return true; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index f68d4038add..240314dd810 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -169,6 +169,10 @@ class GLGizmosManager : public Slic3r::ObjectBase IC_TOOLBAR_RESET_ZERO_HOVER, IC_TOOLBAR_TOOLTIP, IC_TOOLBAR_TOOLTIP_HOVER, + IC_FIT_CAMERA, + IC_FIT_CAMERA_HOVER, + IC_FIT_CAMERA_DARK, + IC_FIT_CAMERA_DARK_HOVER, IC_NAME_COUNT, }; diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index a6e7dd6c6d7..3628f4c7cf3 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -371,6 +371,7 @@ void CreateObjectJob::finalize(bool canceled, std::exception_ptr &eptr) Slic3r::Geometry::Transformation tr(m_transformation); new_object->instances.front()->set_transformation(tr); new_object->ensure_on_bed(); + model.InitializeAssemblyPositions({new_object}); // Actualize right panel and set inside of selection app.obj_list()->paste_objects_into_list({model.objects.size() - 1}); diff --git a/src/slic3r/GUI/Jobs/FillBedJob.cpp b/src/slic3r/GUI/Jobs/FillBedJob.cpp index b4aa69c4e9b..841fc8e0df0 100644 --- a/src/slic3r/GUI/Jobs/FillBedJob.cpp +++ b/src/slic3r/GUI/Jobs/FillBedJob.cpp @@ -173,7 +173,7 @@ void FillBedJob::prepare() ap.poly = m_selected.front().poly; ap.bed_idx = PartPlateList::MAX_PLATES_COUNT; ap.itemid = -1; - ap.setter = [this, mi](const ArrangePolygon &p) { + ap.setter = [this](const ArrangePolygon &p) { ModelObject *mo = m_plater->model().objects[m_object_idx]; ModelObject* newObj = m_plater->model().add_object(*mo); newObj->name = mo->name +" "+ std::to_string(p.itemid); @@ -279,7 +279,8 @@ void FillBedJob::finalize(bool canceled, std::exception_ptr &eptr) return s + int(ap.priority == 0 && ap.bed_idx == 0); }); - int oldSize = m_plater->model().objects.size(); + Model& model = m_plater->model(); + const size_t oldObjectCount = model.objects.size(); if (added_cnt > 0) { //BBS: adjust the selected instances @@ -305,9 +306,15 @@ void FillBedJob::finalize(bool canceled, std::exception_ptr &eptr) BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(":selected: bed_id %1%, trans {%2%,%3%}") % ap.bed_idx % unscale(ap.translation(X)) % unscale(ap.translation(Y)); } - int newSize = m_plater->model().objects.size(); + const size_t newObjectCount = model.objects.size(); + if (newObjectCount > oldObjectCount) + { + ModelObjectPtrs newObjects(model.objects.begin() + oldObjectCount, model.objects.end()); + model.InitializeAssemblyPositions(newObjects); + } + auto obj_list = m_plater->sidebar().obj_list(); - for (size_t i = oldSize; i < newSize; i++) { + for (size_t i = oldObjectCount; i < newObjectCount; i++) { obj_list->add_object_to_list(i, true, true, false); obj_list->update_printable_state(i, 0); } diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 7e53c2aec4d..c6ae085de14 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -242,6 +242,7 @@ void KBShortcutsDialog::fill_shortcuts() {ctrl + "4", L("Camera view - Behind")}, {ctrl + "5", L("Camera Angle - Left side")}, {ctrl + "6", L("Camera Angle - Right side")}, + {"Z", into_u8(_L("Fit in all view"))}, {ctrl + "A", L("Select all objects")}, {ctrl + "D", L("Delete all")}, diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index ba317d2be97..edc1896f22f 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include //#include #include @@ -97,6 +98,54 @@ wxDEFINE_EVENT(EVT_BACKUP_POST, wxCommandEvent); wxDEFINE_EVENT(EVT_LOAD_URL, wxCommandEvent); wxDEFINE_EVENT(EVT_LOAD_PRINTER_URL, LoadPrinterViewEvent); +/** + * @brief Checks whether a wxWidgets text input currently owns the focus. + * @return True when keyboard input belongs to a text control. + */ +static bool IsTextEntryFocused() +{ + for (wxWindow* window = wxWindow::FindFocus(); window != nullptr; window = window->GetParent()) + { + if (dynamic_cast(window) != nullptr) + { + return true; + } + } + + return false; +} + +/** + * @brief Checks whether the Fit Camera shortcut must be passed through. + * @param plater Current plater instance. + * @return True when the shortcut must not trigger Fit Camera. + */ +static bool ShouldSkipFitCameraShortcut(Plater* plater) +{ + if (plater == nullptr || IsTextEntryFocused()) + { + return true; + } + + ImGuiWrapper* const imgui = wxGetApp().imgui(); + if (imgui != nullptr && (imgui->want_keyboard() || imgui->want_text_input())) + { + return true; + } + + GLCanvas3D* const currentCanvas = plater->get_current_canvas3D(); + if (currentCanvas == nullptr) + { + return true; + } + + GLCanvas3D* const viewCanvas = plater->get_view3D_canvas3D(); + GLCanvas3D* const assembleCanvas = plater->get_assmeble_canvas3D(); + return (viewCanvas != nullptr && viewCanvas->get_gizmos_manager().is_running()) || + (assembleCanvas != nullptr && assembleCanvas->get_gizmos_manager().is_running()) || + currentCanvas->get_gizmos_manager().is_running(); +} + enum class ERescaleTarget { Mainframe, @@ -618,6 +667,23 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_ if (m_plater) { m_plater->add_file(); } return; } + + const bool isFitCameraShortcut = !evt.HasAnyModifiers() && + (evt.GetKeyCode() == 'Z' || evt.GetKeyCode() == 'z'); + if (isFitCameraShortcut) + { + if (ShouldSkipFitCameraShortcut(m_plater) || !can_change_view()) + { + evt.Skip(); + } + else + { + ZoomCameraToFit(); + } + + return; + } + evt.Skip(); }); @@ -2375,6 +2441,15 @@ static void add_common_view_menu_items(wxMenu* view_menu, MainFrame* mainFrame, "", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame); append_menu_item(view_menu, wxID_ANY, _L("Right") + "\t" + ctrl + "6", _L("Right View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("right"); }, "", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame); + +#ifdef __APPLE__ + const wxString fitCameraLabel = _L("Fit in all view"); +#else + const wxString fitCameraLabel = _L("Fit in all view") + "\tZ"; +#endif + append_menu_item(view_menu, wxID_ANY, fitCameraLabel, _L("Fit in all view"), + [mainFrame](wxCommandEvent&) { mainFrame->ZoomCameraToFit(); }, + "", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame); } void MainFrame::init_menubar_as_editor() @@ -3560,6 +3635,20 @@ void MainFrame::select_view(const std::string& direction) m_plater->select_view(direction); } +void MainFrame::ZoomCameraToFit() const +{ + if (m_plater == nullptr || !can_change_view()) + { + return; + } + + GLCanvas3D* const canvas = m_plater->canvas3D(); + if (canvas != nullptr) + { + canvas->ZoomToFit(); + } +} + // #ys_FIXME_to_delete void MainFrame::on_presets_changed(SimpleEvent &event) { diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index d32d28a30e4..fe996ff18de 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -320,6 +320,10 @@ class MainFrame : public DPIFrame void request_select_tab(TabPosition pos); int get_calibration_curr_tab(); void select_view(const std::string& direction); + /** + * @brief Fits the active canvas camera to the scene or selection. + */ + void ZoomCameraToFit() const; // Propagate changed configuration from the Tab to the Plater and save changes to the AppConfig void on_config_changed(DynamicPrintConfig* cfg) const ; void set_print_button_to_default(PrintSelectType select_type); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index cc0e8ecef11..6f0f4e209a6 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -10370,6 +10370,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ const float INIT_MODEL_RATIO = 0.75; const float CENTER_AROUND_ORIGIN_RATIO = 0.8; const float LOAD_MODEL_RATIO = 0.9; + bool hasRawGeometryImport = false; for (size_t i = 0; i < input_files.size(); ++i) { int file_percent = 0; @@ -10394,6 +10395,12 @@ std::vector Plater::priv::load_files(const std::vector& input_ // const bool type_zip_amf = !type_3mf && std::regex_match(path.string(), pattern_zip_amf); const bool type_any_amf = !type_3mf && std::regex_match(path.string(), pattern_any_amf); // const bool type_prusa = std::regex_match(path.string(), pattern_prusa); + const bool shouldInitializeAssemblyPosition = boost::algorithm::iends_with(path.string(), ".stl") || + boost::algorithm::iends_with(path.string(), ".obj") || + boost::algorithm::iends_with(path.string(), ".glb") || + boost::algorithm::iends_with(path.string(), ".gltf") || + boost::algorithm::iends_with(path.string(), ".fbx"); + hasRawGeometryImport = hasRawGeometryImport || shouldInitializeAssemblyPosition; Slic3r::Model model; // BBS: add auxiliary files related logic @@ -11185,6 +11192,19 @@ std::vector Plater::priv::load_files(const std::vector& input_ BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", before load_model_objects, count %1%")%model.objects.size(); auto loaded_idxs = load_model_objects(model.objects, is_project_file); obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end()); + if (shouldInitializeAssemblyPosition) + { + ModelObjectPtrs loadedObjects; + loadedObjects.reserve(loaded_idxs.size()); + for (const size_t objectIndex : loaded_idxs) + { + if (objectIndex < q->model().objects.size()) + { + loadedObjects.push_back(q->model().objects[objectIndex]); + } + } + q->model().InitializeAssemblyPositions(loadedObjects); + } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", finished load_model_objects"); wxString msg = wxString::Format(_L("Loading file: %s"), from_path(real_filename)); @@ -11220,6 +11240,19 @@ std::vector Plater::priv::load_files(const std::vector& input_ auto loaded_idxs = load_model_objects(new_model->objects); obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end()); + if (hasRawGeometryImport) + { + ModelObjectPtrs loadedObjects; + loadedObjects.reserve(loaded_idxs.size()); + for (const size_t objectIndex : loaded_idxs) + { + if (objectIndex < q->model().objects.size()) + { + loadedObjects.push_back(q->model().objects[objectIndex]); + } + } + q->model().InitializeAssemblyPositions(loadedObjects); + } } if (new_model) delete new_model; diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 074d33850fb..7aea8f4fa5b 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -3170,11 +3170,8 @@ void Selection::paste_objects_from_clipboard() for (ModelInstance* inst : dst_object->instances) { inst->set_offset(displacement); - - //BBS init asssmble transformation - Geometry::Transformation t = inst->get_transformation(); - inst->set_assemble_transformation(t); } + m_model->InitializeAssemblyPositions({dst_object}); object_idxs.push_back(m_model->objects.size() - 1); #ifdef _DEBUG