diff --git a/resources/icons/sla_view_original.svg b/resources/icons/sla_view_original.svg
new file mode 100644
index 000000000..4691721c8
--- /dev/null
+++ b/resources/icons/sla_view_original.svg
@@ -0,0 +1,72 @@
+
+
+
+
diff --git a/resources/icons/sla_view_processed.svg b/resources/icons/sla_view_processed.svg
new file mode 100644
index 000000000..a26a0db2f
--- /dev/null
+++ b/resources/icons/sla_view_processed.svg
@@ -0,0 +1,56 @@
+
+
+
+
diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h
index dff8aea9f..5aed97842 100644
--- a/src/imgui/imconfig.h
+++ b/src/imgui/imconfig.h
@@ -166,6 +166,8 @@ namespace ImGui
const wchar_t PauseHoverButton = 0x261B;
const wchar_t OpenButton = 0x261C;
const wchar_t OpenHoverButton = 0x261D;
+ const wchar_t SlaViewOriginal = 0x261E;
+ const wchar_t SlaViewProcessed = 0x261F;
const wchar_t LegendTravel = 0x2701;
const wchar_t LegendWipe = 0x2702;
diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp
index 837f32479..f2a1db3c9 100644
--- a/src/libslic3r/Model.cpp
+++ b/src/libslic3r/Model.cpp
@@ -2097,6 +2097,14 @@ bool ModelObject::has_solid_mesh() const
return false;
}
+bool ModelObject::has_negative_volume_mesh() const
+{
+ for (const ModelVolume* volume : volumes)
+ if (volume->is_negative_volume())
+ return true;
+ return false;
+}
+
void ModelVolume::set_material_id(t_model_material_id material_id)
{
m_material_id = material_id;
diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp
index ea22b968d..359e1fbbd 100644
--- a/src/libslic3r/Model.hpp
+++ b/src/libslic3r/Model.hpp
@@ -503,6 +503,8 @@ public:
// Detect if object has at least one solid mash
bool has_solid_mesh() const;
+ // Detect if object has at least one negative volume mash
+ bool has_negative_volume_mesh() const;
bool is_cut() const { return cut_id.id().valid(); }
bool has_connectors() const;
diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp
index 60d89c9e9..602654633 100644
--- a/src/libslic3r/Technologies.hpp
+++ b/src/libslic3r/Technologies.hpp
@@ -36,6 +36,8 @@
#define ENABLE_MATRICES_DEBUG 0
// Shows an imgui dialog containing data from class ObjectManipulation
#define ENABLE_OBJECT_MANIPULATION_DEBUG 0
+// Shows an imgui dialog containing data for class GLCanvas3D::SLAView
+#define ENABLE_SLA_VIEW_DEBUG_WINDOW 0
// Enable rendering of objects using environment map
diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp
index b4e3936da..dea40fb3e 100644
--- a/src/slic3r/GUI/3DScene.cpp
+++ b/src/slic3r/GUI/3DScene.cpp
@@ -611,10 +611,28 @@ void GLVolumeCollection::load_object_auxiliary(
if (convex_hull.has_value())
v.set_convex_hull(*convex_hull);
v.is_modifier = false;
- v.shader_outside_printer_detection_enabled = (step == slaposSupportTree);
+ v.shader_outside_printer_detection_enabled = (step == slaposSupportTree || step == slaposDrillHoles);
v.set_instance_transformation(model_instance.get_transformation());
};
+ if (milestone == SLAPrintObjectStep::slaposDrillHoles) {
+ if (print_object->get_parts_to_slice().size() > 1) {
+ // Get the mesh.
+ TriangleMesh backend_mesh;
+ std::shared_ptr preview_mesh_ptr = print_object->get_mesh_to_print();
+ if (preview_mesh_ptr != nullptr)
+ backend_mesh = TriangleMesh(*preview_mesh_ptr);
+ if (!backend_mesh.empty()) {
+ backend_mesh.transform(mesh_trafo_inv);
+ TriangleMesh convex_hull = backend_mesh.convex_hull_3d();
+ for (const std::pair& instance_idx : instances) {
+ const ModelInstance& model_instance = *print_object->model_object()->instances[instance_idx.first];
+ add_volume(obj_idx, (int)instance_idx.first, model_instance, slaposDrillHoles, backend_mesh, GLVolume::MODEL_COLOR[0], convex_hull);
+ }
+ }
+ }
+ }
+
// Get the support mesh.
if (milestone == SLAPrintObjectStep::slaposSupportTree) {
TriangleMesh supports_mesh = print_object->support_mesh();
@@ -622,8 +640,8 @@ void GLVolumeCollection::load_object_auxiliary(
supports_mesh.transform(mesh_trafo_inv);
TriangleMesh convex_hull = supports_mesh.convex_hull_3d();
for (const std::pair& instance_idx : instances) {
- const ModelInstance& model_instance = *print_object->model_object()->instances[instance_idx.first];
- add_volume(obj_idx, (int)instance_idx.first, model_instance, slaposSupportTree, supports_mesh, GLVolume::SLA_SUPPORT_COLOR, convex_hull);
+ const ModelInstance& model_instance = *print_object->model_object()->instances[instance_idx.first];
+ add_volume(obj_idx, (int)instance_idx.first, model_instance, slaposSupportTree, supports_mesh, GLVolume::SLA_SUPPORT_COLOR, convex_hull);
}
}
}
@@ -922,7 +940,7 @@ void GLVolumeCollection::update_colors_by_extruder(const DynamicPrintConfig* con
}
for (GLVolume* volume : volumes) {
- if (volume == nullptr || volume->is_modifier || volume->is_wipe_tower || volume->volume_idx() < 0)
+ if (volume == nullptr || volume->is_modifier || volume->is_wipe_tower || volume->is_sla_pad() || volume->is_sla_support())
continue;
int extruder_id = volume->extruder_id - 1;
diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp
index bd4653ab8..4e58e68ec 100644
--- a/src/slic3r/GUI/GCodeViewer.cpp
+++ b/src/slic3r/GUI/GCodeViewer.cpp
@@ -4066,69 +4066,58 @@ void GCodeViewer::render_legend(float& legend_height)
}
};
- auto image_icon = [&imgui](ImGuiWindow& window, const ImVec2& pos, float size, const wchar_t& icon_id) {
- ImGuiIO& io = ImGui::GetIO();
- const ImTextureID tex_id = io.Fonts->TexID;
- const float tex_w = static_cast(io.Fonts->TexWidth);
- const float tex_h = static_cast(io.Fonts->TexHeight);
- const ImFontAtlas::CustomRect* const rect = imgui.GetTextureCustomRect(icon_id);
- const ImVec2 uv0 = { static_cast(rect->X) / tex_w, static_cast(rect->Y) / tex_h };
- const ImVec2 uv1 = { static_cast(rect->X + rect->Width) / tex_w, static_cast(rect->Y + rect->Height) / tex_h };
- window.DrawList->AddImage(tex_id, pos, { pos.x + size, pos.y + size }, uv0, uv1, ImGuiWrapper::to_ImU32({ 1.0f, 1.0f, 1.0f, 1.0f }));
- };
-
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
ImGui::Spacing();
- toggle_button(Preview::OptionType::Travel, _u8L("Travel"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
- image_icon(window, pos, size, ImGui::LegendTravel);
+ toggle_button(Preview::OptionType::Travel, _u8L("Travel"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
+ imgui.draw_icon(window, pos, size, ImGui::LegendTravel);
});
ImGui::SameLine();
- toggle_button(Preview::OptionType::Wipe, _u8L("Wipe"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
- image_icon(window, pos, size, ImGui::LegendWipe);
+ toggle_button(Preview::OptionType::Wipe, _u8L("Wipe"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
+ imgui.draw_icon(window, pos, size, ImGui::LegendWipe);
});
ImGui::SameLine();
- toggle_button(Preview::OptionType::Retractions, _u8L("Retractions"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
- image_icon(window, pos, size, ImGui::LegendRetract);
+ toggle_button(Preview::OptionType::Retractions, _u8L("Retractions"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
+ imgui.draw_icon(window, pos, size, ImGui::LegendRetract);
});
ImGui::SameLine();
- toggle_button(Preview::OptionType::Unretractions, _u8L("Deretractions"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
- image_icon(window, pos, size, ImGui::LegendDeretract);
+ toggle_button(Preview::OptionType::Unretractions, _u8L("Deretractions"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
+ imgui.draw_icon(window, pos, size, ImGui::LegendDeretract);
});
ImGui::SameLine();
- toggle_button(Preview::OptionType::Seams, _u8L("Seams"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
- image_icon(window, pos, size, ImGui::LegendSeams);
+ toggle_button(Preview::OptionType::Seams, _u8L("Seams"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
+ imgui.draw_icon(window, pos, size, ImGui::LegendSeams);
});
ImGui::SameLine();
- toggle_button(Preview::OptionType::ToolChanges, _u8L("Tool changes"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
- image_icon(window, pos, size, ImGui::LegendToolChanges);
+ toggle_button(Preview::OptionType::ToolChanges, _u8L("Tool changes"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
+ imgui.draw_icon(window, pos, size, ImGui::LegendToolChanges);
});
ImGui::SameLine();
- toggle_button(Preview::OptionType::ColorChanges, _u8L("Color changes"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
- image_icon(window, pos, size, ImGui::LegendColorChanges);
+ toggle_button(Preview::OptionType::ColorChanges, _u8L("Color changes"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
+ imgui.draw_icon(window, pos, size, ImGui::LegendColorChanges);
});
ImGui::SameLine();
- toggle_button(Preview::OptionType::PausePrints, _u8L("Print pauses"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
- image_icon(window, pos, size, ImGui::LegendPausePrints);
+ toggle_button(Preview::OptionType::PausePrints, _u8L("Print pauses"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
+ imgui.draw_icon(window, pos, size, ImGui::LegendPausePrints);
});
ImGui::SameLine();
- toggle_button(Preview::OptionType::CustomGCodes, _u8L("Custom G-codes"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
- image_icon(window, pos, size, ImGui::LegendCustomGCodes);
+ toggle_button(Preview::OptionType::CustomGCodes, _u8L("Custom G-codes"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
+ imgui.draw_icon(window, pos, size, ImGui::LegendCustomGCodes);
});
ImGui::SameLine();
- toggle_button(Preview::OptionType::CenterOfGravity, _u8L("Center of gravity"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
- image_icon(window, pos, size, ImGui::LegendCOG);
+ toggle_button(Preview::OptionType::CenterOfGravity, _u8L("Center of gravity"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
+ imgui.draw_icon(window, pos, size, ImGui::LegendCOG);
});
ImGui::SameLine();
if (!wxGetApp().is_gcode_viewer()) {
- toggle_button(Preview::OptionType::Shells, _u8L("Shells"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
- image_icon(window, pos, size, ImGui::LegendShells);
+ toggle_button(Preview::OptionType::Shells, _u8L("Shells"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
+ imgui.draw_icon(window, pos, size, ImGui::LegendShells);
});
ImGui::SameLine();
}
- toggle_button(Preview::OptionType::ToolMarker, _u8L("Tool marker"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
- image_icon(window, pos, size, ImGui::LegendToolMarker);
+ toggle_button(Preview::OptionType::ToolMarker, _u8L("Tool marker"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
+ imgui.draw_icon(window, pos, size, ImGui::LegendToolMarker);
});
bool size_dirty = !ImGui::GetCurrentWindow()->ScrollbarY && ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x != ImGui::GetWindowWidth();
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index 8116e8843..fc2e04d5a 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -1070,6 +1070,250 @@ void GLCanvas3D::load_arrange_settings()
m_arrange_settings_fff_seq_print.alignment = arr_alignment ;
}
+static std::vector processed_objects_idxs(const Model& model, const SLAPrint& sla_print, const GLVolumePtrs& volumes)
+{
+ std::vector ret;
+ GLVolumePtrs matching_volumes;
+ std::copy_if(volumes.begin(), volumes.end(), std::back_inserter(matching_volumes), [](GLVolume* v) {
+ return v->volume_idx() == -(int)slaposDrillHoles; });
+ for (const GLVolume* v : matching_volumes) {
+ const int mo_idx = v->object_idx();
+ const ModelObject* model_object = (mo_idx < (int)model.objects.size()) ? model.objects[mo_idx] : nullptr;
+ if (model_object != nullptr && model_object->instances[v->instance_idx()]->is_printable()) {
+ const SLAPrintObject* print_object = sla_print.get_print_object_by_model_object_id(model_object->id());
+ if (print_object != nullptr && print_object->get_parts_to_slice().size() > 1)
+ ret.push_back(mo_idx);
+ }
+ }
+ std::sort(ret.begin(), ret.end());
+ ret.erase(std::unique(ret.begin(), ret.end()), ret.end());
+ return ret;
+};
+
+static bool composite_id_match(const GLVolume::CompositeID& id1, const GLVolume::CompositeID& id2)
+{
+ return id1.object_id == id2.object_id && id1.instance_id == id2.instance_id;
+}
+
+static bool object_contains_negative_volumes(const Model& model, int obj_id) {
+ return (0 <= obj_id && obj_id < (int)model.objects.size()) ? model.objects[obj_id]->has_negative_volume_mesh() : false;
+}
+
+void GLCanvas3D::SLAView::detect_type_from_volumes(const GLVolumePtrs& volumes)
+{
+ for (auto& [id, type] : m_instances_cache) {
+ type = ESLAViewType::Original;
+ }
+
+ for (const GLVolume* v : volumes) {
+ if (v->volume_idx() == -(int)slaposDrillHoles) {
+ if (object_contains_negative_volumes(*m_parent.get_model(), v->composite_id.object_id)) {
+ const InstancesCacheItem* instance = find_instance_item(v->composite_id);
+ assert(instance != nullptr);
+ set_type(instance->first, ESLAViewType::Processed);
+ }
+ }
+ }
+}
+
+void GLCanvas3D::SLAView::set_type(ESLAViewType new_type)
+{
+ for (auto& [id, type] : m_instances_cache) {
+ type = new_type;
+ if (new_type == ESLAViewType::Processed)
+ select_full_instance(id);
+ }
+}
+
+void GLCanvas3D::SLAView::set_type(const GLVolume::CompositeID& id, ESLAViewType new_type)
+{
+ InstancesCacheItem* instance = find_instance_item(id);
+ assert(instance != nullptr);
+ instance->second = new_type;
+ if (new_type == ESLAViewType::Processed)
+ select_full_instance(id);
+}
+
+void GLCanvas3D::SLAView::update_volumes_visibility(GLVolumePtrs& volumes)
+{
+ const SLAPrint* sla_print = m_parent.sla_print();
+ const std::vector mo_idxs = (sla_print != nullptr) ? processed_objects_idxs(*m_parent.get_model(), *sla_print, volumes) : std::vector();
+
+ std::vector>* raycasters = m_parent.get_raycasters_for_picking(SceneRaycaster::EType::Volume);
+
+ for (GLVolume* v : volumes) {
+ const int obj_idx = v->object_idx();
+ bool active = std::find(mo_idxs.begin(), mo_idxs.end(), obj_idx) == mo_idxs.end();
+ if (!active) {
+ const InstancesCacheItem* instance = find_instance_item(v->composite_id);
+ assert(instance != nullptr);
+ active = (instance->second == ESLAViewType::Processed) ? v->volume_idx() < 0 : v->volume_idx() != -(int)slaposDrillHoles;
+ }
+ v->is_active = active;
+ auto it = std::find_if(raycasters->begin(), raycasters->end(), [v](std::shared_ptr item) { return item->get_raycaster() == v->mesh_raycaster.get(); });
+ if (it != raycasters->end())
+ (*it)->set_active(v->is_active);
+ }
+}
+
+void GLCanvas3D::SLAView::update_instances_cache(const std::vector>& new_to_old_ids_map)
+{
+ // First, extract current instances list from the volumes
+ const GLVolumePtrs& volumes = m_parent.get_volumes().volumes;
+ std::vector new_instances_cache;
+ for (const GLVolume* v : volumes) {
+ new_instances_cache.emplace_back(v->composite_id, ESLAViewType::Original);
+ }
+
+ std::sort(new_instances_cache.begin(), new_instances_cache.end(),
+ [](const InstancesCacheItem& i1, const InstancesCacheItem& i2) {
+ return i1.first.object_id < i2.first.object_id || (i1.first.object_id == i2.first.object_id && i1.first.instance_id < i2.first.instance_id); });
+
+ new_instances_cache.erase(std::unique(new_instances_cache.begin(), new_instances_cache.end(),
+ [](const InstancesCacheItem& i1, const InstancesCacheItem& i2) {
+ return composite_id_match(i1.first, i2.first); }), new_instances_cache.end());
+
+ // Second, update instances type from previous state
+ for (auto& inst_type : new_instances_cache) {
+ const auto map_to_old_it = std::find_if(new_to_old_ids_map.begin(), new_to_old_ids_map.end(), [&inst_type](const std::pair& item) {
+ return composite_id_match(inst_type.first, item.first); });
+
+ const GLVolume::CompositeID old_inst_id = (map_to_old_it != new_to_old_ids_map.end()) ? map_to_old_it->second : inst_type.first;
+ const InstancesCacheItem* old_instance = find_instance_item(old_inst_id);
+ if (old_instance != nullptr)
+ inst_type.second = old_instance->second;
+ }
+
+ m_instances_cache = new_instances_cache;
+}
+
+void GLCanvas3D::SLAView::render_switch_button()
+{
+ const SLAPrint* sla_print = m_parent.sla_print();
+ if (sla_print == nullptr)
+ return;
+
+ const std::vector mo_idxs = processed_objects_idxs(*m_parent.get_model(), *sla_print, m_parent.get_volumes().volumes);
+ if (mo_idxs.empty())
+ return;
+
+ Selection& selection = m_parent.get_selection();
+ const int obj_idx = selection.get_object_idx();
+ if (std::find(mo_idxs.begin(), mo_idxs.end(), obj_idx) == mo_idxs.end())
+ return;
+
+ if (!object_contains_negative_volumes(*m_parent.get_model(), obj_idx))
+ return;
+
+ const int inst_idx = selection.get_instance_idx();
+ if (inst_idx < 0)
+ return;
+
+ const GLVolume::CompositeID composite_id(obj_idx, 0, inst_idx);
+ const InstancesCacheItem* sel_instance = find_instance_item(composite_id);
+ if (sel_instance == nullptr)
+ return;
+
+ const ESLAViewType type = sel_instance->second;
+
+ BoundingBoxf ss_box;
+ if (m_use_instance_bbox) {
+ const Selection::EMode mode = selection.get_mode();
+ if (obj_idx >= 0 && inst_idx >= 0) {
+ const Selection::IndicesList selected_idxs = selection.get_volume_idxs();
+ std::vector idxs_as_vector;
+ idxs_as_vector.assign(selected_idxs.begin(), selected_idxs.end());
+ selection.add_instance(obj_idx, inst_idx, true);
+ ss_box = selection.get_screen_space_bounding_box();
+ selection.add_volumes(mode, idxs_as_vector, true);
+ }
+ }
+
+ if (!ss_box.defined)
+ ss_box = selection.get_screen_space_bounding_box();
+ assert(ss_box.defined);
+
+ ImGuiWrapper& imgui = *wxGetApp().imgui();
+ ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
+ ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
+ ImGui::SetNextWindowPos(ImVec2((float)ss_box.max.x(), (float)ss_box.center().y()), ImGuiCond_Always, ImVec2(0.0, 0.5));
+ imgui.begin(std::string("SLAViewSwitch"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration);
+ const float icon_size = 1.5 * ImGui::GetTextLineHeight();
+ if (imgui.draw_radio_button(_u8L("SLA view"), 1.5f * icon_size, true,
+ [this, &imgui, sel_instance](ImGuiWindow& window, const ImVec2& pos, float size) {
+ const wchar_t icon_id = (sel_instance->second == ESLAViewType::Original) ? ImGui::SlaViewProcessed : ImGui::SlaViewOriginal;
+ imgui.draw_icon(window, pos, size, icon_id);
+ })) {
+ switch (sel_instance->second)
+ {
+ case ESLAViewType::Original: { m_parent.set_sla_view_type(sel_instance->first, ESLAViewType::Processed); break; }
+ case ESLAViewType::Processed: { m_parent.set_sla_view_type(sel_instance->first, ESLAViewType::Original); break; }
+ default: { assert(false); break; }
+ }
+ }
+
+ if (ImGui::IsItemHovered()) {
+ ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND);
+ ImGui::BeginTooltip();
+ wxString tooltip;
+ switch (type)
+ {
+ case ESLAViewType::Original: { tooltip = _L("Show as processed"); break; }
+ case ESLAViewType::Processed: { tooltip = _L("Show as original"); break; }
+ default: { assert(false); break; }
+ }
+
+ imgui.text(tooltip);
+ ImGui::EndTooltip();
+ ImGui::PopStyleColor();
+ }
+ imgui.end();
+ ImGui::PopStyleColor(2);
+}
+
+#if ENABLE_SLA_VIEW_DEBUG_WINDOW
+void GLCanvas3D::SLAView::render_debug_window()
+{
+ ImGuiWrapper& imgui = *wxGetApp().imgui();
+ imgui.begin(std::string("SLAView"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize);
+ for (const auto& [id, type] : m_instances_cache) {
+ imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, "(" + std::to_string(id.object_id) + ", " + std::to_string(id.instance_id) + ")");
+ ImGui::SameLine();
+ imgui.text_colored(ImGui::GetStyleColorVec4(ImGuiCol_Text), (type == ESLAViewType::Original) ? "Original" : "Processed");
+ }
+ if (!m_instances_cache.empty())
+ ImGui::Separator();
+
+ imgui.checkbox("Use instance bounding box", m_use_instance_bbox);
+ imgui.end();
+}
+#endif // ENABLE_SLA_VIEW_DEBUG_WINDOW
+
+GLCanvas3D::SLAView::InstancesCacheItem* GLCanvas3D::SLAView::find_instance_item(const GLVolume::CompositeID& id)
+{
+ auto it = std::find_if(m_instances_cache.begin(), m_instances_cache.end(),
+ [&id](const InstancesCacheItem& item) { return composite_id_match(item.first, id); });
+ return (it == m_instances_cache.end()) ? nullptr : &(*it);
+}
+
+void GLCanvas3D::SLAView::select_full_instance(const GLVolume::CompositeID& id)
+{
+ bool extended_selection = false;
+ Selection& selection = m_parent.get_selection();
+ const Selection::ObjectIdxsToInstanceIdxsMap& sel_cache = selection.get_content();
+ auto obj_it = sel_cache.find(id.object_id);
+ if (obj_it != sel_cache.end()) {
+ auto inst_it = std::find(obj_it->second.begin(), obj_it->second.end(), id.instance_id);
+ if (inst_it != obj_it->second.end()) {
+ selection.add_instance(id.object_id, id.instance_id);
+ extended_selection = true;
+ }
+ }
+
+ if (extended_selection)
+ m_parent.post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT));
+}
+
PrinterTechnology GLCanvas3D::current_printer_technology() const
{
return m_process->current_printer_technology();
@@ -1111,6 +1355,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed)
, m_render_sla_auxiliaries(true)
, m_labels(*this)
, m_slope(m_volumes)
+ , m_sla_view(*this)
{
if (m_canvas != nullptr) {
m_timer.SetOwner(m_canvas);
@@ -1643,6 +1888,15 @@ void GLCanvas3D::render()
wxGetApp().obj_manipul()->render_debug_window();
#endif // ENABLE_OBJECT_MANIPULATION_DEBUG
+ if (wxGetApp().plater()->is_view3D_shown() && current_printer_technology() == ptSLA) {
+ const GLGizmosManager::EType type = m_gizmos.get_current_type();
+ if (type == GLGizmosManager::EType::Undefined)
+ m_sla_view.render_switch_button();
+#if ENABLE_SLA_VIEW_DEBUG_WINDOW
+ m_sla_view.render_debug_window();
+#endif // ENABLE_SLA_VIEW_DEBUG_WINDOW
+ }
+
std::string tooltip;
// Negative coordinate means out of the window, likely because the window was deactivated.
@@ -1882,6 +2136,8 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
size_t volume_idx;
};
+ std::vector> new_to_old_ids_map;
+
// SLA steps to pull the preview meshes for.
typedef std::array SLASteps;
SLASteps sla_steps = { slaposDrillHoles, slaposSupportTree, slaposPad };
@@ -2029,12 +2285,12 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++ obj_idx) {
const ModelObject &model_object = *m_model->objects[obj_idx];
for (int volume_idx = 0; volume_idx < (int)model_object.volumes.size(); ++ volume_idx) {
- const ModelVolume &model_volume = *model_object.volumes[volume_idx];
+ const ModelVolume &model_volume = *model_object.volumes[volume_idx];
for (int instance_idx = 0; instance_idx < (int)model_object.instances.size(); ++ instance_idx) {
- const ModelInstance &model_instance = *model_object.instances[instance_idx];
- ModelVolumeState key(model_volume.id(), model_instance.id());
- auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower);
- assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id);
+ const ModelInstance &model_instance = *model_object.instances[instance_idx];
+ ModelVolumeState key(model_volume.id(), model_instance.id());
+ auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower);
+ assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id);
if (it->new_geometry()) {
// New volume.
auto it_old_volume = std::lower_bound(deleted_volumes.begin(), deleted_volumes.end(), GLVolumeState(it->composite_id), deleted_volumes_lower);
@@ -2047,15 +2303,17 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx);
m_volumes.volumes.back()->geometry_id = key.geometry_id;
update_object_list = true;
- } else {
- // Recycling an old GLVolume.
- GLVolume &existing_volume = *m_volumes.volumes[it->volume_idx];
+ }
+ else {
+ // Recycling an old GLVolume.
+ GLVolume &existing_volume = *m_volumes.volumes[it->volume_idx];
assert(existing_volume.geometry_id == key.geometry_id);
- // Update the Object/Volume/Instance indices into the current Model.
- if (existing_volume.composite_id != it->composite_id) {
- existing_volume.composite_id = it->composite_id;
- update_object_list = true;
- }
+ // Update the Object/Volume/Instance indices into the current Model.
+ if (existing_volume.composite_id != it->composite_id) {
+ new_to_old_ids_map.push_back(std::make_pair(it->composite_id, existing_volume.composite_id));
+ existing_volume.composite_id = it->composite_id;
+ update_object_list = true;
+ }
}
}
}
@@ -2106,7 +2364,9 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
}
else {
// Recycling an old GLVolume. Update the Object/Instance indices into the current Model.
- m_volumes.volumes[it->volume_idx]->composite_id = GLVolume::CompositeID(object_idx, m_volumes.volumes[it->volume_idx]->volume_idx(), instance_idx);
+ const GLVolume::CompositeID new_id(object_idx, m_volumes.volumes[it->volume_idx]->volume_idx(), instance_idx);
+ new_to_old_ids_map.push_back(std::make_pair(new_id, m_volumes.volumes[it->volume_idx]->composite_id));
+ m_volumes.volumes[it->volume_idx]->composite_id = new_id;
m_volumes.volumes[it->volume_idx]->set_instance_transformation(model_object->instances[instance_idx]->get_transformation());
}
}
@@ -2174,12 +2434,30 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
else
m_selection.volumes_changed(map_glvolume_old_to_new);
+ if (printer_technology == ptSLA) {
+ std::sort(new_to_old_ids_map.begin(), new_to_old_ids_map.end(),
+ [](const std::pair& i1, const std::pair& i2) {
+ return i1.first.object_id < i2.first.object_id || (i1.first.object_id == i2.first.object_id && i1.first.instance_id < i2.first.instance_id); });
+
+ new_to_old_ids_map.erase(std::unique(new_to_old_ids_map.begin(), new_to_old_ids_map.end(),
+ [](const std::pair& i1, const std::pair& i2) {
+ return composite_id_match(i1.first, i2.first); }), new_to_old_ids_map.end());
+
+ m_sla_view.update_instances_cache(new_to_old_ids_map);
+ if (m_sla_view_type_detection_active) {
+ m_sla_view.detect_type_from_volumes(m_volumes.volumes);
+ m_sla_view_type_detection_active = false;
+ }
+ m_sla_view.update_volumes_visibility(m_volumes.volumes);
+ update_object_list = true;
+ }
+
m_gizmos.update_data();
m_gizmos.refresh_on_off_state();
// Update the toolbar
- if (update_object_list)
- post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT));
+ if (update_object_list)
+ post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT));
// checks for geometry outside the print volume to render it accordingly
if (!m_volumes.empty()) {
@@ -4203,6 +4481,20 @@ std::pair> GLCanvas3D::get_layers_h
return ret;
}
+void GLCanvas3D::set_sla_view_type(ESLAViewType type)
+{
+ m_sla_view.set_type(type);
+ m_sla_view.update_volumes_visibility(m_volumes.volumes);
+ m_dirty = true;
+}
+
+void GLCanvas3D::set_sla_view_type(const GLVolume::CompositeID& id, ESLAViewType type)
+{
+ m_sla_view.set_type(id, type);
+ m_sla_view.update_volumes_visibility(m_volumes.volumes);
+ m_dirty = true;
+}
+
bool GLCanvas3D::_is_shown_on_screen() const
{
return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false;
diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp
index 0f45d0597..38dcdb8b8 100644
--- a/src/slic3r/GUI/GLCanvas3D.hpp
+++ b/src/slic3r/GUI/GLCanvas3D.hpp
@@ -466,6 +466,12 @@ public:
int alignment = 0;
};
+ enum class ESLAViewType
+ {
+ Original,
+ Processed
+ };
+
private:
wxGLCanvas* m_canvas;
wxGLContext* m_context;
@@ -545,11 +551,37 @@ private:
bool m_tooltip_enabled{ true };
Slope m_slope;
+ class SLAView
+ {
+ public:
+ explicit SLAView(GLCanvas3D& parent) : m_parent(parent) {}
+ void detect_type_from_volumes(const GLVolumePtrs& volumes);
+ void set_type(ESLAViewType type);
+ void set_type(const GLVolume::CompositeID& id, ESLAViewType type);
+ void update_volumes_visibility(GLVolumePtrs& volumes);
+ void update_instances_cache(const std::vector>& new_to_old_ids_map);
+ void render_switch_button();
+
+#if ENABLE_SLA_VIEW_DEBUG_WINDOW
+ void render_debug_window();
+#endif // ENABLE_SLA_VIEW_DEBUG_WINDOW
+
+ private:
+ GLCanvas3D& m_parent;
+ typedef std::pair InstancesCacheItem;
+ std::vector m_instances_cache;
+ bool m_use_instance_bbox{ true };
+
+ InstancesCacheItem* find_instance_item(const GLVolume::CompositeID& id);
+ void select_full_instance(const GLVolume::CompositeID& id);
+ };
+
+ SLAView m_sla_view;
+ bool m_sla_view_type_detection_active{ false };
+
ArrangeSettings m_arrange_settings_fff, m_arrange_settings_sla,
m_arrange_settings_fff_seq_print;
- PrinterTechnology current_printer_technology() const;
-
bool is_arrange_alignment_enabled() const;
template
@@ -654,7 +686,7 @@ private:
GLModel m_background;
public:
- explicit GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed);
+ GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed);
~GLCanvas3D();
bool is_initialized() const { return m_initialized; }
@@ -767,6 +799,8 @@ public:
void zoom_to_gcode();
void select_view(const std::string& direction);
+ PrinterTechnology current_printer_technology() const;
+
void update_volumes_colors_by_extruder();
bool is_dragging() const { return m_gizmos.is_dragging() || (m_moving && !m_mouse.scene_position.isApprox(m_mouse.drag.start_position_3D)); }
@@ -954,6 +988,10 @@ public:
std::pair> get_layers_height_data(int object_id);
+ void set_sla_view_type(ESLAViewType type);
+ void set_sla_view_type(const GLVolume::CompositeID& id, ESLAViewType type);
+ void enable_sla_view_type_detection() { m_sla_view_type_detection_active = true; }
+
private:
bool _is_shown_on_screen() const;
diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp
index 4fa9d6a16..9cbcbc323 100644
--- a/src/slic3r/GUI/GUI_ObjectList.cpp
+++ b/src/slic3r/GUI/GUI_ObjectList.cpp
@@ -758,6 +758,10 @@ void ObjectList::selection_changed()
wxGetApp().obj_layers()->update_scene_from_editor_selection();
}
}
+ else if (type & itVolume) {
+ if (printer_technology() == ptSLA)
+ wxGetApp().plater()->canvas3D()->set_sla_view_type(scene_selection().get_first_volume()->composite_id, GLCanvas3D::ESLAViewType::Original);
+ }
}
part_selection_changed();
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp
index b9ec5cf19..60421a617 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp
@@ -87,6 +87,8 @@ void GLGizmoSlaSupports::data_changed(bool is_serializing)
register_point_raycasters_for_picking();
else
update_point_raycasters_for_picking_transform();
+
+ m_c->instances_hider()->set_hide_full_scene(true);
}
// m_parent.toggle_model_objects_visibility(false);
diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
index 98dd15137..657235511 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
@@ -978,6 +978,9 @@ bool GLGizmosManager::activate_gizmo(EType type)
return false; // gizmo refused to be turned on.
}
+ if (m_parent.current_printer_technology() == ptSLA)
+ m_parent.set_sla_view_type(GLCanvas3D::ESLAViewType::Original);
+
new_gizmo.register_raycasters_for_picking();
// sucessful activation of gizmo
diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp
index f2e6b0287..841ae2048 100644
--- a/src/slic3r/GUI/ImGuiWrapper.cpp
+++ b/src/slic3r/GUI/ImGuiWrapper.cpp
@@ -104,6 +104,8 @@ static const std::map font_icons_large = {
{ImGui::PauseHoverButton , "notification_pause_hover" },
{ImGui::OpenButton , "notification_open" },
{ImGui::OpenHoverButton , "notification_open_hover" },
+ {ImGui::SlaViewOriginal , "sla_view_original" },
+ {ImGui::SlaViewProcessed , "sla_view_processed" },
};
static const std::map font_icons_extra_large = {
@@ -490,6 +492,18 @@ bool ImGuiWrapper::radio_button(const wxString &label, bool active)
return ImGui::RadioButton(label_utf8.c_str(), active);
}
+void ImGuiWrapper::draw_icon(ImGuiWindow& window, const ImVec2& pos, float size, wchar_t icon_id)
+{
+ ImGuiIO& io = ImGui::GetIO();
+ const ImTextureID tex_id = io.Fonts->TexID;
+ const float tex_w = static_cast(io.Fonts->TexWidth);
+ const float tex_h = static_cast(io.Fonts->TexHeight);
+ const ImFontAtlas::CustomRect* const rect = GetTextureCustomRect(icon_id);
+ const ImVec2 uv0 = { static_cast(rect->X) / tex_w, static_cast(rect->Y) / tex_h };
+ const ImVec2 uv1 = { static_cast(rect->X + rect->Width) / tex_w, static_cast(rect->Y + rect->Height) / tex_h };
+ window.DrawList->AddImage(tex_id, pos, { pos.x + size, pos.y + size }, uv0, uv1, ImGuiWrapper::to_ImU32({ 1.0f, 1.0f, 1.0f, 1.0f }));
+}
+
bool ImGuiWrapper::draw_radio_button(const std::string& name, float size, bool active,
std::function draw_callback)
{
diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp
index 077bf568d..d95b934f1 100644
--- a/src/slic3r/GUI/ImGuiWrapper.hpp
+++ b/src/slic3r/GUI/ImGuiWrapper.hpp
@@ -92,9 +92,10 @@ public:
void end();
bool button(const wxString &label, const wxString& tooltip = {});
- bool button(const wxString& label, float width, float height);
+ bool button(const wxString& label, float width, float height);
bool button(const wxString& label, const ImVec2 &size, bool enable); // default size = ImVec2(0.f, 0.f)
bool radio_button(const wxString &label, bool active);
+ void draw_icon(ImGuiWindow& window, const ImVec2& pos, float size, wchar_t icon_id);
bool draw_radio_button(const std::string& name, float size, bool active, std::function draw_callback);
bool checkbox(const wxString &label, bool &value);
static void text(const char *label);
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index 5657f30f2..b657616f0 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -4080,8 +4080,10 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt)
// If RELOAD_SLA_SUPPORT_POINTS, then the SLA gizmo is updated (reload_scene calls update_gizmos_data)
if (view3D->is_dragging())
delayed_scene_refresh = true;
- else
+ else {
+ view3D->get_canvas3d()->enable_sla_view_type_detection();
this->update_sla_scene();
+ }
break;
default: break;
}
@@ -6944,10 +6946,12 @@ void Plater::on_config_change(const DynamicPrintConfig &config)
p->config->set_key_value(opt_key, config.option(opt_key)->clone());
if (opt_key == "printer_technology") {
- this->set_printer_technology(config.opt_enum(opt_key));
+ const PrinterTechnology printer_technology = config.opt_enum(opt_key);
+ this->set_printer_technology(printer_technology);
p->sidebar->show_sliced_info_sizer(false);
p->reset_gcode_toolpaths();
p->view3D->get_canvas3d()->reset_sequential_print_clearance();
+ p->view3D->get_canvas3d()->set_sla_view_type(GLCanvas3D::ESLAViewType::Original);
}
else if (opt_key == "bed_shape" || opt_key == "bed_custom_texture" || opt_key == "bed_custom_model") {
bed_shape_changed = true;
diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp
index 39363dbf4..cbd61a1dd 100644
--- a/src/slic3r/GUI/Selection.cpp
+++ b/src/slic3r/GUI/Selection.cpp
@@ -858,6 +858,43 @@ std::pair Selection::get_bounding_box_in_reference_s
return { out_box, out_trafo.get_matrix_no_scaling_factor() };
}
+BoundingBoxf Selection::get_screen_space_bounding_box()
+{
+ BoundingBoxf ss_box;
+ if (!is_empty()) {
+ const auto& [box, box_trafo] = get_bounding_box_in_current_reference_system();
+
+ // vertices
+ std::vector vertices = {
+ { box.min.x(), box.min.y(), box.min.z() },
+ { box.max.x(), box.min.y(), box.min.z() },
+ { box.max.x(), box.max.y(), box.min.z() },
+ { box.min.x(), box.max.y(), box.min.z() },
+ { box.min.x(), box.min.y(), box.max.z() },
+ { box.max.x(), box.min.y(), box.max.z() },
+ { box.max.x(), box.max.y(), box.max.z() },
+ { box.min.x(), box.max.y(), box.max.z() }
+ };
+
+ const Camera& camera = wxGetApp().plater()->get_camera();
+ const Matrix4d projection_view_matrix = camera.get_projection_matrix().matrix() * camera.get_view_matrix().matrix();
+ const std::array& viewport = camera.get_viewport();
+
+ const double half_w = 0.5 * double(viewport[2]);
+ const double h = double(viewport[3]);
+ const double half_h = 0.5 * h;
+ for (const Vec3d& v : vertices) {
+ const Vec3d world = box_trafo * v;
+ const Vec4d clip = projection_view_matrix * Vec4d(world.x(), world.y(), world.z(), 1.0);
+ const Vec3d ndc = Vec3d(clip.x(), clip.y(), clip.z()) / clip.w();
+ const Vec2d ss = Vec2d(half_w * ndc.x() + double(viewport[0]) + half_w, h - (half_h * ndc.y() + double(viewport[1]) + half_h));
+ ss_box.merge(ss);
+ }
+ }
+
+ return ss_box;
+}
+
void Selection::setup_cache()
{
if (!m_valid)
@@ -1717,8 +1754,7 @@ std::vector Selection::get_volume_idxs_from_volume(unsigned int ob
{
std::vector idxs;
- for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i)
- {
+ for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) {
const GLVolume* v = (*m_volumes)[i];
if (v->object_idx() == (int)object_idx && v->volume_idx() == (int)volume_idx) {
if ((int)instance_idx != -1 && v->instance_idx() == (int)instance_idx)
diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp
index 6395afbd7..5a6fcb5e0 100644
--- a/src/slic3r/GUI/Selection.hpp
+++ b/src/slic3r/GUI/Selection.hpp
@@ -319,6 +319,9 @@ public:
// and the transform to place and orient it in world coordinates
std::pair get_bounding_box_in_reference_system(ECoordinatesType type) const;
+ // Returns the screen space bounding box
+ BoundingBoxf get_screen_space_bounding_box();
+
void setup_cache();
void translate(const Vec3d& displacement, TransformationType transformation_type);