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