Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion localization/i18n/zh_CN/Snapmaker_Orca_zh_CN.po
Original file line number Diff line number Diff line change
Expand Up @@ -15348,4 +15348,7 @@ msgid "Other Colors"
msgstr "其他颜色"

msgid "Multiple Color"
msgstr "多色"
msgstr "多色"

msgid "Fit in all view"
msgstr "相机适配"
5 changes: 5 additions & 0 deletions resources/images/fit_camera.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions resources/images/fit_camera_dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions resources/images/fit_camera_dark_hover.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions resources/images/fit_camera_hover.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
135 changes: 135 additions & 0 deletions src/libslic3r/Model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<BoundingBoxf3> 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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<ModelObject*>(this)->update_min_max_z();
Expand Down
23 changes: 22 additions & 1 deletion src/libslic3r/Model.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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()));
Expand Down Expand Up @@ -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);
Expand All @@ -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,
Expand Down
76 changes: 76 additions & 0 deletions src/slic3r/GUI/GLCanvas3D.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions src/slic3r/GUI/GLCanvas3D.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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<ColorRGBA>& extruder_colors, GLShaderProgram* shader, Camera::EType camera_type);

Expand Down
19 changes: 18 additions & 1 deletion src/slic3r/GUI/GUI_Factories.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<size_t> 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
Expand Down
4 changes: 1 addition & 3 deletions src/slic3r/GUI/GUI_ObjectList.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading