From 910db38ae85d911bbca09ab06b15d7bf588c71b8 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 19 May 2022 12:34:43 +0200 Subject: [PATCH 01/22] Fixed rotation, using gizmo, for instances whose offset does not match with bounding box center --- src/slic3r/GUI/Selection.cpp | 5533 +++++++++++++++++----------------- 1 file changed, 2768 insertions(+), 2765 deletions(-) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 7ccc6fc256..e7c4e1763f 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1,2765 +1,2768 @@ -#include "libslic3r/libslic3r.h" -#include "Selection.hpp" - -#include "3DScene.hpp" -#include "GLCanvas3D.hpp" -#include "GUI_App.hpp" -#include "GUI.hpp" -#include "GUI_ObjectManipulation.hpp" -#include "GUI_ObjectList.hpp" -#include "Gizmos/GLGizmoBase.hpp" -#include "Camera.hpp" -#include "Plater.hpp" -#include "slic3r/Utils/UndoRedo.hpp" - -#include "libslic3r/LocalesUtils.hpp" -#include "libslic3r/Model.hpp" -#include "libslic3r/PresetBundle.hpp" -#include "libslic3r/BuildVolume.hpp" - -#include - -#include -#include - -static const Slic3r::ColorRGBA UNIFORM_SCALE_COLOR = Slic3r::ColorRGBA::ORANGE(); -static const Slic3r::ColorRGBA SOLID_PLANE_COLOR = Slic3r::ColorRGBA::ORANGE(); -static const Slic3r::ColorRGBA TRANSPARENT_PLANE_COLOR = { 0.8f, 0.8f, 0.8f, 0.5f }; - -namespace Slic3r { -namespace GUI { - -Selection::VolumeCache::TransformCache::TransformCache() - : position(Vec3d::Zero()) - , rotation(Vec3d::Zero()) - , scaling_factor(Vec3d::Ones()) - , mirror(Vec3d::Ones()) - , rotation_matrix(Transform3d::Identity()) - , scale_matrix(Transform3d::Identity()) - , mirror_matrix(Transform3d::Identity()) - , full_matrix(Transform3d::Identity()) -{ -} - -Selection::VolumeCache::TransformCache::TransformCache(const Geometry::Transformation& transform) - : position(transform.get_offset()) - , rotation(transform.get_rotation()) - , scaling_factor(transform.get_scaling_factor()) - , mirror(transform.get_mirror()) - , full_matrix(transform.get_matrix()) -{ - rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation); - scale_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scaling_factor); - mirror_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d::Ones(), mirror); -} - -Selection::VolumeCache::VolumeCache(const Geometry::Transformation& volume_transform, const Geometry::Transformation& instance_transform) - : m_volume(volume_transform) - , m_instance(instance_transform) -{ -} - -bool Selection::Clipboard::is_sla_compliant() const -{ - if (m_mode == Selection::Volume) - return false; - - for (const ModelObject* o : m_model->objects) { - if (o->is_multiparts()) - return false; - - for (const ModelVolume* v : o->volumes) { - if (v->is_modifier()) - return false; - } - } - - return true; -} - -Selection::Clipboard::Clipboard() -{ - m_model.reset(new Model); -} - -void Selection::Clipboard::reset() -{ - m_model->clear_objects(); -} - -bool Selection::Clipboard::is_empty() const -{ - return m_model->objects.empty(); -} - -ModelObject* Selection::Clipboard::add_object() -{ - return m_model->add_object(); -} - -ModelObject* Selection::Clipboard::get_object(unsigned int id) -{ - return (id < (unsigned int)m_model->objects.size()) ? m_model->objects[id] : nullptr; -} - -const ModelObjectPtrs& Selection::Clipboard::get_objects() const -{ - return m_model->objects; -} - -Selection::Selection() - : m_volumes(nullptr) - , m_model(nullptr) - , m_enabled(false) - , m_mode(Instance) - , m_type(Empty) - , m_valid(false) - , m_scale_factor(1.0f) -{ - this->set_bounding_boxes_dirty(); -} - - -void Selection::set_volumes(GLVolumePtrs* volumes) -{ - m_volumes = volumes; - update_valid(); -} - -// Init shall be called from the OpenGL render function, so that the OpenGL context is initialized! -bool Selection::init() -{ - m_arrow.init_from(straight_arrow(10.0f, 5.0f, 5.0f, 10.0f, 1.0f)); - m_curved_arrow.init_from(circular_arrow(16, 10.0f, 5.0f, 10.0f, 5.0f, 1.0f)); -#if ENABLE_RENDER_SELECTION_CENTER - m_vbo_sphere.init_from(its_make_sphere(0.75, PI / 12.0)); -#endif // ENABLE_RENDER_SELECTION_CENTER - - return true; -} - -void Selection::set_model(Model* model) -{ - m_model = model; - update_valid(); -} - -void Selection::add(unsigned int volume_idx, bool as_single_selection, bool check_for_already_contained) -{ - if (!m_valid || (unsigned int)m_volumes->size() <= volume_idx) - return; - - const GLVolume* volume = (*m_volumes)[volume_idx]; - // wipe tower is already selected - if (is_wipe_tower() && volume->is_wipe_tower) - return; - - bool keep_instance_mode = (m_mode == Instance) && !as_single_selection; - bool already_contained = check_for_already_contained && contains_volume(volume_idx); - - // resets the current list if needed - bool needs_reset = as_single_selection && !already_contained; - needs_reset |= volume->is_wipe_tower; - needs_reset |= is_wipe_tower() && !volume->is_wipe_tower; - needs_reset |= as_single_selection && !is_any_modifier() && volume->is_modifier; - needs_reset |= is_any_modifier() && !volume->is_modifier; - - if (!already_contained || needs_reset) { - wxGetApp().plater()->take_snapshot(_L("Selection-Add"), UndoRedo::SnapshotType::Selection); - - if (needs_reset) - clear(); - - if (!keep_instance_mode) - m_mode = volume->is_modifier ? Volume : Instance; - } - else - // keep current mode - return; - - switch (m_mode) - { - case Volume: - { - if (volume->volume_idx() >= 0 && (is_empty() || volume->instance_idx() == get_instance_idx())) - do_add_volume(volume_idx); - - break; - } - case Instance: - { - Plater::SuppressSnapshots suppress(wxGetApp().plater()); - add_instance(volume->object_idx(), volume->instance_idx(), as_single_selection); - break; - } - } - - update_type(); - this->set_bounding_boxes_dirty(); -} - -void Selection::remove(unsigned int volume_idx) -{ - if (!m_valid || (unsigned int)m_volumes->size() <= volume_idx) - return; - - if (!contains_volume(volume_idx)) - return; - - wxGetApp().plater()->take_snapshot(_L("Selection-Remove"), UndoRedo::SnapshotType::Selection); - - GLVolume* volume = (*m_volumes)[volume_idx]; - - switch (m_mode) - { - case Volume: - { - do_remove_volume(volume_idx); - break; - } - case Instance: - { - do_remove_instance(volume->object_idx(), volume->instance_idx()); - break; - } - } - - update_type(); - this->set_bounding_boxes_dirty(); -} - -void Selection::add_object(unsigned int object_idx, bool as_single_selection) -{ - if (!m_valid) - return; - - std::vector volume_idxs = get_volume_idxs_from_object(object_idx); - if ((!as_single_selection && contains_all_volumes(volume_idxs)) || - (as_single_selection && matches(volume_idxs))) - return; - - wxGetApp().plater()->take_snapshot(_L("Selection-Add Object"), UndoRedo::SnapshotType::Selection); - - // resets the current list if needed - if (as_single_selection) - clear(); - - m_mode = Instance; - - do_add_volumes(volume_idxs); - - update_type(); - this->set_bounding_boxes_dirty(); -} - -void Selection::remove_object(unsigned int object_idx) -{ - if (!m_valid) - return; - - wxGetApp().plater()->take_snapshot(_L("Selection-Remove Object"), UndoRedo::SnapshotType::Selection); - - do_remove_object(object_idx); - - update_type(); - this->set_bounding_boxes_dirty(); -} - -void Selection::add_instance(unsigned int object_idx, unsigned int instance_idx, bool as_single_selection) -{ - if (!m_valid) - return; - - const std::vector volume_idxs = get_volume_idxs_from_instance(object_idx, instance_idx); - if ((!as_single_selection && contains_all_volumes(volume_idxs)) || - (as_single_selection && matches(volume_idxs))) - return; - - wxGetApp().plater()->take_snapshot(_L("Selection-Add Instance"), UndoRedo::SnapshotType::Selection); - - // resets the current list if needed - if (as_single_selection) - clear(); - - m_mode = Instance; - - do_add_volumes(volume_idxs); - - update_type(); - this->set_bounding_boxes_dirty(); -} - -void Selection::remove_instance(unsigned int object_idx, unsigned int instance_idx) -{ - if (!m_valid) - return; - - wxGetApp().plater()->take_snapshot(_L("Selection-Remove Instance"), UndoRedo::SnapshotType::Selection); - - do_remove_instance(object_idx, instance_idx); - - update_type(); - this->set_bounding_boxes_dirty(); -} - -void Selection::add_volume(unsigned int object_idx, unsigned int volume_idx, int instance_idx, bool as_single_selection) -{ - if (!m_valid) - return; - - std::vector volume_idxs = get_volume_idxs_from_volume(object_idx, instance_idx, volume_idx); - if ((!as_single_selection && contains_all_volumes(volume_idxs)) || - (as_single_selection && matches(volume_idxs))) - return; - - // resets the current list if needed - if (as_single_selection) - clear(); - - m_mode = Volume; - - do_add_volumes(volume_idxs); - - update_type(); - this->set_bounding_boxes_dirty(); -} - -void Selection::remove_volume(unsigned int object_idx, unsigned int volume_idx) -{ - if (!m_valid) - return; - - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { - GLVolume* v = (*m_volumes)[i]; - if (v->object_idx() == (int)object_idx && v->volume_idx() == (int)volume_idx) - do_remove_volume(i); - } - - update_type(); - this->set_bounding_boxes_dirty(); -} - -void Selection::add_volumes(EMode mode, const std::vector& volume_idxs, bool as_single_selection) -{ - if (!m_valid) - return; - - if ((!as_single_selection && contains_all_volumes(volume_idxs)) || - (as_single_selection && matches(volume_idxs))) - return; - - // resets the current list if needed - if (as_single_selection) - clear(); - - m_mode = mode; - for (unsigned int i : volume_idxs) { - if (i < (unsigned int)m_volumes->size()) - do_add_volume(i); - } - - update_type(); - this->set_bounding_boxes_dirty(); -} - -void Selection::remove_volumes(EMode mode, const std::vector& volume_idxs) -{ - if (!m_valid) - return; - - m_mode = mode; - for (unsigned int i : volume_idxs) { - if (i < (unsigned int)m_volumes->size()) - do_remove_volume(i); - } - - update_type(); - this->set_bounding_boxes_dirty(); -} - -void Selection::add_all() -{ - if (!m_valid) - return; - - unsigned int count = 0; - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { - if (!(*m_volumes)[i]->is_wipe_tower) - ++count; - } - - if ((unsigned int)m_list.size() == count) - return; - - wxGetApp().plater()->take_snapshot(_(L("Selection-Add All")), UndoRedo::SnapshotType::Selection); - - m_mode = Instance; - clear(); - - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { - if (!(*m_volumes)[i]->is_wipe_tower) - do_add_volume(i); - } - - update_type(); - this->set_bounding_boxes_dirty(); -} - -void Selection::remove_all() -{ - if (!m_valid) - return; - - if (is_empty()) - return; - -// Not taking the snapshot with non-empty Redo stack will likely be more confusing than losing the Redo stack. -// Let's wait for user feedback. -// if (!wxGetApp().plater()->can_redo()) - wxGetApp().plater()->take_snapshot(_L("Selection-Remove All"), UndoRedo::SnapshotType::Selection); - - m_mode = Instance; - clear(); -} - -void Selection::set_deserialized(EMode mode, const std::vector> &volumes_and_instances) -{ - if (! m_valid) - return; - - m_mode = mode; - for (unsigned int i : m_list) - (*m_volumes)[i]->selected = false; - m_list.clear(); - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++ i) - if (std::binary_search(volumes_and_instances.begin(), volumes_and_instances.end(), (*m_volumes)[i]->geometry_id)) - do_add_volume(i); - update_type(); - set_bounding_boxes_dirty(); -} - -void Selection::clear() -{ - if (!m_valid) - return; - - if (m_list.empty()) - return; - - // ensure that the volumes get the proper color before next call to render (expecially needed for transparent volumes) - for (unsigned int i : m_list) { - GLVolume& volume = *(*m_volumes)[i]; - volume.selected = false; - volume.set_render_color(volume.color.is_transparent()); - } - - m_list.clear(); - - update_type(); - set_bounding_boxes_dirty(); - - // this happens while the application is closing - if (wxGetApp().obj_manipul() == nullptr) - return; - - // resets the cache in the sidebar - wxGetApp().obj_manipul()->reset_cache(); - - // #et_FIXME fake KillFocus from sidebar - wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false); -} - -// Update the selection based on the new instance IDs. -void Selection::instances_changed(const std::vector &instance_ids_selected) -{ - assert(m_valid); - assert(m_mode == Instance); - m_list.clear(); - for (unsigned int volume_idx = 0; volume_idx < (unsigned int)m_volumes->size(); ++ volume_idx) { - const GLVolume *volume = (*m_volumes)[volume_idx]; - auto it = std::lower_bound(instance_ids_selected.begin(), instance_ids_selected.end(), volume->geometry_id.second); - if (it != instance_ids_selected.end() && *it == volume->geometry_id.second) - this->do_add_volume(volume_idx); - } - update_type(); - this->set_bounding_boxes_dirty(); -} - -// Update the selection based on the map from old indices to new indices after m_volumes changed. -// If the current selection is by instance, this call may select newly added volumes, if they belong to already selected instances. -void Selection::volumes_changed(const std::vector &map_volume_old_to_new) -{ - assert(m_valid); - assert(m_mode == Volume); - IndicesList list_new; - for (unsigned int idx : m_list) - if (map_volume_old_to_new[idx] != size_t(-1)) { - unsigned int new_idx = (unsigned int)map_volume_old_to_new[idx]; - (*m_volumes)[new_idx]->selected = true; - list_new.insert(new_idx); - } - m_list = std::move(list_new); - update_type(); - this->set_bounding_boxes_dirty(); -} - -bool Selection::is_single_full_instance() const -{ - if (m_type == SingleFullInstance) - return true; - - if (m_type == SingleFullObject) - return get_instance_idx() != -1; - - if (m_list.empty() || m_volumes->empty()) - return false; - - int object_idx = m_valid ? get_object_idx() : -1; - if (object_idx < 0 || (int)m_model->objects.size() <= object_idx) - return false; - - int instance_idx = (*m_volumes)[*m_list.begin()]->instance_idx(); - - std::set volumes_idxs; - for (unsigned int i : m_list) { - const GLVolume* v = (*m_volumes)[i]; - if (object_idx != v->object_idx() || instance_idx != v->instance_idx()) - return false; - - int volume_idx = v->volume_idx(); - if (volume_idx >= 0) - volumes_idxs.insert(volume_idx); - } - - return m_model->objects[object_idx]->volumes.size() == volumes_idxs.size(); -} - -bool Selection::is_from_single_object() const -{ - const int idx = get_object_idx(); -#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - return 0 <= idx && idx < int(m_model->objects.size()); -#else - return 0 <= idx && idx < 1000; -#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL -} - -bool Selection::is_sla_compliant() const -{ - if (m_mode == Volume) - return false; - - for (unsigned int i : m_list) { - if ((*m_volumes)[i]->is_modifier) - return false; - } - - return true; -} - -bool Selection::contains_all_volumes(const std::vector& volume_idxs) const -{ - for (unsigned int i : volume_idxs) { - if (m_list.find(i) == m_list.end()) - return false; - } - - return true; -} - -bool Selection::contains_any_volume(const std::vector& volume_idxs) const -{ - for (unsigned int i : volume_idxs) { - if (m_list.find(i) != m_list.end()) - return true; - } - - return false; -} - -bool Selection::matches(const std::vector& volume_idxs) const -{ - unsigned int count = 0; - - for (unsigned int i : volume_idxs) { - if (m_list.find(i) != m_list.end()) - ++count; - else - return false; - } - - return count == (unsigned int)m_list.size(); -} - -bool Selection::requires_uniform_scale() const -{ - if (is_single_full_instance() || is_single_modifier() || is_single_volume()) - return false; - - return true; -} - -int Selection::get_object_idx() const -{ - return (m_cache.content.size() == 1) ? m_cache.content.begin()->first : -1; -} - -int Selection::get_instance_idx() const -{ - if (m_cache.content.size() == 1) { - const InstanceIdxsList& idxs = m_cache.content.begin()->second; - if (idxs.size() == 1) - return *idxs.begin(); - } - - return -1; -} - -const Selection::InstanceIdxsList& Selection::get_instance_idxs() const -{ - assert(m_cache.content.size() == 1); - return m_cache.content.begin()->second; -} - -const GLVolume* Selection::get_volume(unsigned int volume_idx) const -{ - return (m_valid && (volume_idx < (unsigned int)m_volumes->size())) ? (*m_volumes)[volume_idx] : nullptr; -} - -const BoundingBoxf3& Selection::get_bounding_box() const -{ - if (!m_bounding_box.has_value()) { - std::optional* bbox = const_cast*>(&m_bounding_box); - *bbox = BoundingBoxf3(); - if (m_valid) { - for (unsigned int i : m_list) { - (*bbox)->merge((*m_volumes)[i]->transformed_convex_hull_bounding_box()); - } - } - } - return *m_bounding_box; -} - -const BoundingBoxf3& Selection::get_unscaled_instance_bounding_box() const -{ - if (!m_unscaled_instance_bounding_box.has_value()) { - std::optional* bbox = const_cast*>(&m_unscaled_instance_bounding_box); - *bbox = BoundingBoxf3(); - if (m_valid) { - for (unsigned int i : m_list) { - const GLVolume& volume = *(*m_volumes)[i]; - if (volume.is_modifier) - continue; - Transform3d trafo = volume.get_instance_transformation().get_matrix(false, false, true, false) * volume.get_volume_transformation().get_matrix(); - trafo.translation().z() += volume.get_sla_shift_z(); - (*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo)); - } - } - } - return *m_unscaled_instance_bounding_box; -} - -const BoundingBoxf3& Selection::get_scaled_instance_bounding_box() const -{ - if (!m_scaled_instance_bounding_box.has_value()) { - std::optional* bbox = const_cast*>(&m_scaled_instance_bounding_box); - *bbox = BoundingBoxf3(); - if (m_valid) { - for (unsigned int i : m_list) { - const GLVolume& volume = *(*m_volumes)[i]; - if (volume.is_modifier) - continue; - Transform3d trafo = volume.get_instance_transformation().get_matrix(false, false, false, false) * volume.get_volume_transformation().get_matrix(); - trafo.translation().z() += volume.get_sla_shift_z(); - (*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo)); - } - } - } - return *m_scaled_instance_bounding_box; -} - -void Selection::setup_cache() -{ - if (!m_valid) - return; - - set_caches(); -} - -void Selection::translate(const Vec3d& displacement, bool local) -{ - if (!m_valid) - return; - - EMode translation_type = m_mode; - - for (unsigned int i : m_list) { - GLVolume& v = *(*m_volumes)[i]; - if (m_mode == Volume || v.is_wipe_tower) { - if (local) - v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + displacement); - else { - const Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; - v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement); - } - } - else if (m_mode == Instance) { - if (is_from_fully_selected_instance(i)) - v.set_instance_offset(m_cache.volumes_data[i].get_instance_position() + displacement); - else { - const Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; - v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement); - translation_type = Volume; - } - } - } - -#if !DISABLE_INSTANCES_SYNCH - if (translation_type == Instance) - synchronize_unselected_instances(SYNC_ROTATION_NONE); - else if (translation_type == Volume) - synchronize_unselected_volumes(); -#endif // !DISABLE_INSTANCES_SYNCH - - ensure_not_below_bed(); - set_bounding_boxes_dirty(); - wxGetApp().plater()->canvas3D()->requires_check_outside_state(); -} - -// Rotate an object around one of the axes. Only one rotation component is expected to be changing. -void Selection::rotate(const Vec3d& rotation, TransformationType transformation_type) -{ - if (!m_valid) - return; - - // Only relative rotation values are allowed in the world coordinate system. - assert(!transformation_type.world() || transformation_type.relative()); - - if (!is_wipe_tower()) { - int rot_axis_max = 0; - if (rotation.isApprox(Vec3d::Zero())) { - for (unsigned int i : m_list) { - GLVolume &v = *(*m_volumes)[i]; - if (m_mode == Instance) { - v.set_instance_rotation(m_cache.volumes_data[i].get_instance_rotation()); - v.set_instance_offset(m_cache.volumes_data[i].get_instance_position()); - } - else if (m_mode == Volume) { - v.set_volume_rotation(m_cache.volumes_data[i].get_volume_rotation()); - v.set_volume_offset(m_cache.volumes_data[i].get_volume_position()); - } - } - } - else { // this is not the wipe tower - //FIXME this does not work for absolute rotations (transformation_type.absolute() is true) - rotation.cwiseAbs().maxCoeff(&rot_axis_max); - -// if ( single instance or single volume ) - // Rotate around center , if only a single object or volume -// transformation_type.set_independent(); - - // For generic rotation, we want to rotate the first volume in selection, and then to synchronize the other volumes with it. - std::vector object_instance_first(m_model->objects.size(), -1); - auto rotate_instance = [this, &rotation, &object_instance_first, rot_axis_max, transformation_type](GLVolume &volume, int i) { - const int first_volume_idx = object_instance_first[volume.object_idx()]; - if (rot_axis_max != 2 && first_volume_idx != -1) { - // Generic rotation, but no rotation around the Z axis. - // Always do a local rotation (do not consider the selection to be a rigid body). - assert(is_approx(rotation.z(), 0.0)); - const GLVolume &first_volume = *(*m_volumes)[first_volume_idx]; - const Vec3d &rotation = first_volume.get_instance_rotation(); - const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[first_volume_idx].get_instance_rotation(), m_cache.volumes_data[i].get_instance_rotation()); - volume.set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2) + z_diff)); - } - else { - // extracts rotations from the composed transformation - Vec3d new_rotation = transformation_type.world() ? - Geometry::extract_euler_angles(Geometry::assemble_transform(Vec3d::Zero(), rotation) * m_cache.volumes_data[i].get_instance_rotation_matrix()) : - transformation_type.absolute() ? rotation : rotation + m_cache.volumes_data[i].get_instance_rotation(); - if (rot_axis_max == 2 && transformation_type.joint()) { - // Only allow rotation of multiple instances as a single rigid body when rotating around the Z axis. - const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), new_rotation); - volume.set_instance_offset(m_cache.dragging_center + Eigen::AngleAxisd(z_diff, Vec3d::UnitZ()) * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); - } - volume.set_instance_rotation(new_rotation); - object_instance_first[volume.object_idx()] = i; - } - }; - - for (unsigned int i : m_list) { - GLVolume &v = *(*m_volumes)[i]; - if (is_single_full_instance()) - rotate_instance(v, i); - else if (is_single_volume() || is_single_modifier()) { - if (transformation_type.independent()) - v.set_volume_rotation(m_cache.volumes_data[i].get_volume_rotation() + rotation); - else { - const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); - const Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); - v.set_volume_rotation(new_rotation); - } - } - else - { - if (m_mode == Instance) - rotate_instance(v, i); - else if (m_mode == Volume) { - // extracts rotations from the composed transformation - Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); - Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); - if (transformation_type.joint()) { - const Vec3d local_pivot = m_cache.volumes_data[i].get_instance_full_matrix().inverse() * m_cache.dragging_center; - const Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() - local_pivot); - v.set_volume_offset(local_pivot + offset); - } - v.set_volume_rotation(new_rotation); - } - } - } - } - - #if !DISABLE_INSTANCES_SYNCH - if (m_mode == Instance) - synchronize_unselected_instances((rot_axis_max == 2) ? SYNC_ROTATION_NONE : SYNC_ROTATION_GENERAL); - else if (m_mode == Volume) - synchronize_unselected_volumes(); - #endif // !DISABLE_INSTANCES_SYNCH - } - else { // it's the wipe tower that's selected and being rotated - GLVolume& volume = *((*m_volumes)[*m_list.begin()]); // the wipe tower is always alone in the selection - - // make sure the wipe tower rotates around its center, not origin - // we can assume that only Z rotation changes - const Vec3d center_local = volume.transformed_bounding_box().center() - volume.get_volume_offset(); - const Vec3d center_local_new = Eigen::AngleAxisd(rotation(2)-volume.get_volume_rotation()(2), Vec3d(0.0, 0.0, 1.0)) * center_local; - volume.set_volume_rotation(rotation); - volume.set_volume_offset(volume.get_volume_offset() + center_local - center_local_new); - } - - set_bounding_boxes_dirty(); - wxGetApp().plater()->canvas3D()->requires_check_outside_state(); -} - -void Selection::flattening_rotate(const Vec3d& normal) -{ - // We get the normal in untransformed coordinates. We must transform it using the instance matrix, find out - // how to rotate the instance so it faces downwards and do the rotation. All that for all selected instances. - // The function assumes that is_from_single_object() holds. - assert(Slic3r::is_approx(normal.norm(), 1.)); - - if (!m_valid) - return; - - for (unsigned int i : m_list) { - GLVolume& v = *(*m_volumes)[i]; - // Normal transformed from the object coordinate space to the world coordinate space. - const auto &voldata = m_cache.volumes_data[i]; - Vec3d tnormal = (Geometry::assemble_transform( - Vec3d::Zero(), voldata.get_instance_rotation(), - voldata.get_instance_scaling_factor().cwiseInverse(), voldata.get_instance_mirror()) * normal).normalized(); - // Additional rotation to align tnormal with the down vector in the world coordinate space. - auto extra_rotation = Eigen::Quaterniond().setFromTwoVectors(tnormal, - Vec3d::UnitZ()); - v.set_instance_rotation(Geometry::extract_euler_angles(extra_rotation.toRotationMatrix() * m_cache.volumes_data[i].get_instance_rotation_matrix())); - } - -#if !DISABLE_INSTANCES_SYNCH - // Apply the same transformation also to other instances, - // but respect their possibly diffrent z-rotation. - if (m_mode == Instance) - synchronize_unselected_instances(SYNC_ROTATION_GENERAL); -#endif // !DISABLE_INSTANCES_SYNCH - - this->set_bounding_boxes_dirty(); -} - -void Selection::scale(const Vec3d& scale, TransformationType transformation_type) -{ - if (!m_valid) - return; - - for (unsigned int i : m_list) { - GLVolume &v = *(*m_volumes)[i]; - if (is_single_full_instance()) { - if (transformation_type.relative()) { - Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale); - Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3); - // extracts scaling factors from the composed transformation - Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); - if (transformation_type.joint()) - v.set_instance_offset(m_cache.dragging_center + m * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); - - v.set_instance_scaling_factor(new_scale); - } - else { - if (transformation_type.world() && (std::abs(scale.x() - scale.y()) > EPSILON || std::abs(scale.x() - scale.z()) > EPSILON)) { - // Non-uniform scaling. Transform the scaling factors into the local coordinate system. - // This is only possible, if the instance rotation is mulitples of ninety degrees. - assert(Geometry::is_rotation_ninety_degrees(v.get_instance_rotation())); - v.set_instance_scaling_factor((v.get_instance_transformation().get_matrix(true, false, true, true).matrix().block<3, 3>(0, 0).transpose() * scale).cwiseAbs()); - } - else - v.set_instance_scaling_factor(scale); - } - } - else if (is_single_volume() || is_single_modifier()) - v.set_volume_scaling_factor(scale); - else { - Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale); - if (m_mode == Instance) { - Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3); - // extracts scaling factors from the composed transformation - Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); - if (transformation_type.joint()) - v.set_instance_offset(m_cache.dragging_center + m * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); - - v.set_instance_scaling_factor(new_scale); - } - else if (m_mode == Volume) { - Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_volume_scale_matrix()).matrix().block(0, 0, 3, 3); - // extracts scaling factors from the composed transformation - Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); - if (transformation_type.joint()) { - Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() + m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center); - v.set_volume_offset(m_cache.dragging_center - m_cache.volumes_data[i].get_instance_position() + offset); - } - v.set_volume_scaling_factor(new_scale); - } - } - } - -#if !DISABLE_INSTANCES_SYNCH - if (m_mode == Instance) - synchronize_unselected_instances(SYNC_ROTATION_NONE); - else if (m_mode == Volume) - synchronize_unselected_volumes(); -#endif // !DISABLE_INSTANCES_SYNCH - - ensure_on_bed(); - set_bounding_boxes_dirty(); - wxGetApp().plater()->canvas3D()->requires_check_outside_state(); -} - -void Selection::scale_to_fit_print_volume(const BuildVolume& volume) -{ - auto fit = [this](double s, Vec3d offset) { - if (s <= 0.0 || s == 1.0) - return; - - wxGetApp().plater()->take_snapshot(_L("Scale To Fit")); - - TransformationType type; - type.set_world(); - type.set_relative(); - type.set_joint(); - - // apply scale - setup_cache(); - scale(s * Vec3d::Ones(), type); - wxGetApp().plater()->canvas3D()->do_scale(""); // avoid storing another snapshot - - // center selection on print bed - setup_cache(); - offset.z() = -get_bounding_box().min.z(); - translate(offset); - wxGetApp().plater()->canvas3D()->do_move(""); // avoid storing another snapshot - - wxGetApp().obj_manipul()->set_dirty(); - }; - - auto fit_rectangle = [this, fit](const BuildVolume& volume) { - const BoundingBoxf3 print_volume = volume.bounding_volume(); - const Vec3d print_volume_size = print_volume.size(); - - // adds 1/100th of a mm on all sides to avoid false out of print volume detections due to floating-point roundings - const Vec3d box_size = get_bounding_box().size() + 0.02 * Vec3d::Ones(); - - const double sx = (box_size.x() != 0.0) ? print_volume_size.x() / box_size.x() : 0.0; - const double sy = (box_size.y() != 0.0) ? print_volume_size.y() / box_size.y() : 0.0; - const double sz = (box_size.z() != 0.0) ? print_volume_size.z() / box_size.z() : 0.0; - - if (sx != 0.0 && sy != 0.0 && sz != 0.0) - fit(std::min(sx, std::min(sy, sz)), print_volume.center() - get_bounding_box().center()); - }; - - auto fit_circle = [this, fit](const BuildVolume& volume) { - const Geometry::Circled& print_circle = volume.circle(); - double print_circle_radius = unscale(print_circle.radius); - - if (print_circle_radius == 0.0) - return; - - Points points; - double max_z = 0.0; - for (unsigned int i : m_list) { - const GLVolume& v = *(*m_volumes)[i]; - TriangleMesh hull_3d = *v.convex_hull(); - hull_3d.transform(v.world_matrix()); - max_z = std::max(max_z, hull_3d.bounding_box().size().z()); - const Polygon hull_2d = hull_3d.convex_hull(); - points.insert(points.end(), hull_2d.begin(), hull_2d.end()); - } - - if (points.empty()) - return; - - const Geometry::Circled circle = Geometry::smallest_enclosing_circle_welzl(points); - // adds 1/100th of a mm on all sides to avoid false out of print volume detections due to floating-point roundings - const double circle_radius = unscale(circle.radius) + 0.01; - - if (circle_radius == 0.0 || max_z == 0.0) - return; - - const double s = std::min(print_circle_radius / circle_radius, volume.max_print_height() / max_z); - const Vec3d sel_center = get_bounding_box().center(); - const Vec3d offset = s * (Vec3d(unscale(circle.center.x()), unscale(circle.center.y()), 0.5 * max_z) - sel_center); - const Vec3d print_center = { unscale(print_circle.center.x()), unscale(print_circle.center.y()), 0.5 * volume.max_print_height() }; - fit(s, print_center - (sel_center + offset)); - }; - - if (is_empty() || m_mode == Volume) - return; - - switch (volume.type()) - { - case BuildVolume::Type::Rectangle: { fit_rectangle(volume); break; } - case BuildVolume::Type::Circle: { fit_circle(volume); break; } - default: { break; } - } -} - -void Selection::mirror(Axis axis) -{ - if (!m_valid) - return; - - for (unsigned int i : m_list) { - GLVolume& v = *(*m_volumes)[i]; - if (is_single_full_instance()) - v.set_instance_mirror(axis, -v.get_instance_mirror(axis)); - else if (m_mode == Volume) - v.set_volume_mirror(axis, -v.get_volume_mirror(axis)); - } - -#if !DISABLE_INSTANCES_SYNCH - if (m_mode == Instance) - synchronize_unselected_instances(SYNC_ROTATION_NONE); - else if (m_mode == Volume) - synchronize_unselected_volumes(); -#endif // !DISABLE_INSTANCES_SYNCH - - set_bounding_boxes_dirty(); -} - -void Selection::translate(unsigned int object_idx, const Vec3d& displacement) -{ - if (!m_valid) - return; - - for (unsigned int i : m_list) { - GLVolume& v = *(*m_volumes)[i]; - if (v.object_idx() == (int)object_idx) - v.set_instance_offset(v.get_instance_offset() + displacement); - } - - std::set done; // prevent processing volumes twice - done.insert(m_list.begin(), m_list.end()); - - for (unsigned int i : m_list) { - if (done.size() == m_volumes->size()) - break; - -#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - if ((*m_volumes)[i]->is_wipe_tower) - continue; - - int object_idx = (*m_volumes)[i]->object_idx(); -#else - int object_idx = (*m_volumes)[i]->object_idx(); - if (object_idx >= 1000) - continue; -#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - - // Process unselected volumes of the object. - for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { - if (done.size() == m_volumes->size()) - break; - - if (done.find(j) != done.end()) - continue; - - GLVolume& v = *(*m_volumes)[j]; - if (v.object_idx() != object_idx) - continue; - - v.set_instance_offset(v.get_instance_offset() + displacement); - done.insert(j); - } - } - - this->set_bounding_boxes_dirty(); -} - -void Selection::translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement) -{ - if (!m_valid) - return; - - for (unsigned int i : m_list) { - GLVolume& v = *(*m_volumes)[i]; - if (v.object_idx() == (int)object_idx && v.instance_idx() == (int)instance_idx) - v.set_instance_offset(v.get_instance_offset() + displacement); - } - - std::set done; // prevent processing volumes twice - done.insert(m_list.begin(), m_list.end()); - - for (unsigned int i : m_list) { - if (done.size() == m_volumes->size()) - break; - -#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - if ((*m_volumes)[i]->is_wipe_tower) - continue; - - int object_idx = (*m_volumes)[i]->object_idx(); -#else - int object_idx = (*m_volumes)[i]->object_idx(); - if (object_idx >= 1000) - continue; -#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - - // Process unselected volumes of the object. - for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { - if (done.size() == m_volumes->size()) - break; - - if (done.find(j) != done.end()) - continue; - - GLVolume& v = *(*m_volumes)[j]; - if (v.object_idx() != object_idx || v.instance_idx() != (int)instance_idx) - continue; - - v.set_instance_offset(v.get_instance_offset() + displacement); - done.insert(j); - } - } - - this->set_bounding_boxes_dirty(); -} - -void Selection::erase() -{ - if (!m_valid) - return; - - if (is_single_full_object()) - wxGetApp().obj_list()->delete_from_model_and_list(ItemType::itObject, get_object_idx(), 0); - else if (is_multiple_full_object()) { - std::vector items; - items.reserve(m_cache.content.size()); - for (ObjectIdxsToInstanceIdxsMap::iterator it = m_cache.content.begin(); it != m_cache.content.end(); ++it) { - items.emplace_back(ItemType::itObject, it->first, 0); - } - wxGetApp().obj_list()->delete_from_model_and_list(items); - } - else if (is_multiple_full_instance()) { - std::set> instances_idxs; - for (ObjectIdxsToInstanceIdxsMap::iterator obj_it = m_cache.content.begin(); obj_it != m_cache.content.end(); ++obj_it) { - for (InstanceIdxsList::reverse_iterator inst_it = obj_it->second.rbegin(); inst_it != obj_it->second.rend(); ++inst_it) { - instances_idxs.insert(std::make_pair(obj_it->first, *inst_it)); - } - } - - std::vector items; - items.reserve(instances_idxs.size()); - for (const std::pair& i : instances_idxs) { - items.emplace_back(ItemType::itInstance, i.first, i.second); - } - wxGetApp().obj_list()->delete_from_model_and_list(items); - } - else if (is_single_full_instance()) - wxGetApp().obj_list()->delete_from_model_and_list(ItemType::itInstance, get_object_idx(), get_instance_idx()); - else if (is_mixed()) { - std::set items_set; - std::map volumes_in_obj; - - for (auto i : m_list) { - const auto gl_vol = (*m_volumes)[i]; - const auto glv_obj_idx = gl_vol->object_idx(); - const auto model_object = m_model->objects[glv_obj_idx]; - - if (model_object->instances.size() == 1) { - if (model_object->volumes.size() == 1) - items_set.insert(ItemForDelete(ItemType::itObject, glv_obj_idx, -1)); - else { - items_set.insert(ItemForDelete(ItemType::itVolume, glv_obj_idx, gl_vol->volume_idx())); - int idx = (volumes_in_obj.find(glv_obj_idx) == volumes_in_obj.end()) ? 0 : volumes_in_obj.at(glv_obj_idx); - volumes_in_obj[glv_obj_idx] = ++idx; - } - continue; - } - - const auto glv_ins_idx = gl_vol->instance_idx(); - - for (auto obj_ins : m_cache.content) { - if (obj_ins.first == glv_obj_idx) { - if (obj_ins.second.find(glv_ins_idx) != obj_ins.second.end()) { - if (obj_ins.second.size() == model_object->instances.size()) - items_set.insert(ItemForDelete(ItemType::itObject, glv_obj_idx, -1)); - else - items_set.insert(ItemForDelete(ItemType::itInstance, glv_obj_idx, glv_ins_idx)); - - break; - } - } - } - } - - std::vector items; - items.reserve(items_set.size()); - for (const ItemForDelete& i : items_set) { - if (i.type == ItemType::itVolume) { - const int vol_in_obj_cnt = volumes_in_obj.find(i.obj_idx) == volumes_in_obj.end() ? 0 : volumes_in_obj.at(i.obj_idx); - if (vol_in_obj_cnt == (int)m_model->objects[i.obj_idx]->volumes.size()) { - if (i.sub_obj_idx == vol_in_obj_cnt - 1) - items.emplace_back(ItemType::itObject, i.obj_idx, 0); - continue; - } - } - items.emplace_back(i.type, i.obj_idx, i.sub_obj_idx); - } - - wxGetApp().obj_list()->delete_from_model_and_list(items); - } - else { - std::set> volumes_idxs; - for (unsigned int i : m_list) { - const GLVolume* v = (*m_volumes)[i]; - // Only remove volumes associated with ModelVolumes from the object list. - // Temporary meshes (SLA supports or pads) are not managed by the object list. - if (v->volume_idx() >= 0) - volumes_idxs.insert(std::make_pair(v->object_idx(), v->volume_idx())); - } - - std::vector items; - items.reserve(volumes_idxs.size()); - for (const std::pair& v : volumes_idxs) { - items.emplace_back(ItemType::itVolume, v.first, v.second); - } - - wxGetApp().obj_list()->delete_from_model_and_list(items); - ensure_not_below_bed(); - } -} - -void Selection::render(float scale_factor) -{ - if (!m_valid || is_empty()) - return; - - m_scale_factor = scale_factor; - // render cumulative bounding box of selected volumes -#if ENABLE_LEGACY_OPENGL_REMOVAL - render_bounding_box(get_bounding_box(), ColorRGB::WHITE()); -#else - render_selected_volumes(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - render_synchronized_volumes(); -} - -#if ENABLE_RENDER_SELECTION_CENTER -void Selection::render_center(bool gizmo_is_dragging) -{ - if (!m_valid || is_empty()) - return; - -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLShaderProgram* shader = wxGetApp().get_shader("flat"); - if (shader == nullptr) - return; - - shader->start_using(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - const Vec3d center = gizmo_is_dragging ? m_cache.dragging_center : get_bounding_box().center(); - - glsafe(::glDisable(GL_DEPTH_TEST)); - -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::assemble_transform(center); - - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#else - glsafe(::glPushMatrix()); - glsafe(::glTranslated(center.x(), center.y(), center.z())); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_vbo_sphere.set_color(ColorRGBA::WHITE()); -#else - m_vbo_sphere.set_color(-1, ColorRGBA::WHITE()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - m_vbo_sphere.render(); - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - -#if ENABLE_LEGACY_OPENGL_REMOVAL - shader->stop_using(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} -#endif // ENABLE_RENDER_SELECTION_CENTER - -void Selection::render_sidebar_hints(const std::string& sidebar_field) -{ - if (sidebar_field.empty()) - return; - -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLShaderProgram* shader = wxGetApp().get_shader(boost::starts_with(sidebar_field, "layer") ? "flat" : "gouraud_light"); - if (shader == nullptr) - return; - - shader->start_using(); -#else - GLShaderProgram* shader = nullptr; - - if (!boost::starts_with(sidebar_field, "layer")) { - shader = wxGetApp().get_shader("gouraud_light"); - if (shader == nullptr) - return; - - shader->start_using(); - glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); - } -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - glsafe(::glEnable(GL_DEPTH_TEST)); - -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Transform3d base_matrix = Geometry::assemble_transform(get_bounding_box().center()); - Transform3d orient_matrix = Transform3d::Identity(); -#else - glsafe(::glPushMatrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - if (!boost::starts_with(sidebar_field, "layer")) { -#if ENABLE_GL_SHADERS_ATTRIBUTES - shader->set_uniform("emission_factor", 0.05f); -#else - const Vec3d& center = get_bounding_box().center(); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - if (is_single_full_instance() && !wxGetApp().obj_manipul()->get_world_coordinates()) { -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glTranslated(center.x(), center.y(), center.z())); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - if (!boost::starts_with(sidebar_field, "position")) { -#if !ENABLE_GL_SHADERS_ATTRIBUTES - Transform3d orient_matrix = Transform3d::Identity(); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - if (boost::starts_with(sidebar_field, "scale")) - orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); - else if (boost::starts_with(sidebar_field, "rotation")) { - if (boost::ends_with(sidebar_field, "x")) - orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); - else if (boost::ends_with(sidebar_field, "y")) { - const Vec3d& rotation = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation(); - if (rotation.x() == 0.0) - orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); - else - orient_matrix.rotate(Eigen::AngleAxisd(rotation.z(), Vec3d::UnitZ())); - } - } -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glMultMatrixd(orient_matrix.data())); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - } - } - else if (is_single_volume() || is_single_modifier()) { -#if ENABLE_GL_SHADERS_ATTRIBUTES - orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); -#else - glsafe(::glTranslated(center.x(), center.y(), center.z())); - Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - if (!boost::starts_with(sidebar_field, "position")) - orient_matrix = orient_matrix * (*m_volumes)[*m_list.begin()]->get_volume_transformation().get_matrix(true, false, true, true); - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glMultMatrixd(orient_matrix.data())); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - } - else { -#if ENABLE_GL_SHADERS_ATTRIBUTES - if (requires_local_axes()) - orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); -#else - glsafe(::glTranslated(center.x(), center.y(), center.z())); - if (requires_local_axes()) { - const Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); - glsafe(::glMultMatrixd(orient_matrix.data())); - } -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - } - } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - if (!boost::starts_with(sidebar_field, "layer")) - glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - -#if ENABLE_GL_SHADERS_ATTRIBUTES - if (boost::starts_with(sidebar_field, "position")) - render_sidebar_position_hints(sidebar_field, *shader, base_matrix * orient_matrix); - else if (boost::starts_with(sidebar_field, "rotation")) - render_sidebar_rotation_hints(sidebar_field, *shader, base_matrix * orient_matrix); - else if (boost::starts_with(sidebar_field, "scale") || boost::starts_with(sidebar_field, "size")) - render_sidebar_scale_hints(sidebar_field, *shader, base_matrix * orient_matrix); - else if (boost::starts_with(sidebar_field, "layer")) - render_sidebar_layers_hints(sidebar_field, *shader); -#else - if (boost::starts_with(sidebar_field, "position")) - render_sidebar_position_hints(sidebar_field); - else if (boost::starts_with(sidebar_field, "rotation")) - render_sidebar_rotation_hints(sidebar_field); - else if (boost::starts_with(sidebar_field, "scale") || boost::starts_with(sidebar_field, "size")) - render_sidebar_scale_hints(sidebar_field); - else if (boost::starts_with(sidebar_field, "layer")) - render_sidebar_layers_hints(sidebar_field); - - glsafe(::glPopMatrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - -#if !ENABLE_LEGACY_OPENGL_REMOVAL - if (!boost::starts_with(sidebar_field, "layer")) -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - shader->stop_using(); -} - -bool Selection::requires_local_axes() const -{ - return m_mode == Volume && is_from_single_instance(); -} - -void Selection::copy_to_clipboard() -{ - if (!m_valid) - return; - - m_clipboard.reset(); - - for (const ObjectIdxsToInstanceIdxsMap::value_type& object : m_cache.content) { - ModelObject* src_object = m_model->objects[object.first]; - ModelObject* dst_object = m_clipboard.add_object(); - dst_object->name = src_object->name; - dst_object->input_file = src_object->input_file; - dst_object->config.assign_config(src_object->config); - dst_object->sla_support_points = src_object->sla_support_points; - dst_object->sla_points_status = src_object->sla_points_status; - dst_object->sla_drain_holes = src_object->sla_drain_holes; - dst_object->layer_config_ranges = src_object->layer_config_ranges; // #ys_FIXME_experiment - dst_object->layer_height_profile.assign(src_object->layer_height_profile); - dst_object->origin_translation = src_object->origin_translation; - - for (int i : object.second) { - dst_object->add_instance(*src_object->instances[i]); - } - - for (unsigned int i : m_list) { - // Copy the ModelVolumes only for the selected GLVolumes of the 1st selected instance. - const GLVolume* volume = (*m_volumes)[i]; - if (volume->object_idx() == object.first && volume->instance_idx() == *object.second.begin()) { - int volume_idx = volume->volume_idx(); - if (0 <= volume_idx && volume_idx < (int)src_object->volumes.size()) { - ModelVolume* src_volume = src_object->volumes[volume_idx]; - ModelVolume* dst_volume = dst_object->add_volume(*src_volume); - dst_volume->set_new_unique_id(); - } - else - assert(false); - } - } - } - - m_clipboard.set_mode(m_mode); -} - -void Selection::paste_from_clipboard() -{ - if (!m_valid || m_clipboard.is_empty()) - return; - - switch (m_clipboard.get_mode()) - { - case Volume: - { - if (is_from_single_instance()) - paste_volumes_from_clipboard(); - - break; - } - case Instance: - { - if (m_mode == Instance) - paste_objects_from_clipboard(); - - break; - } - } -} - -std::vector Selection::get_volume_idxs_from_object(unsigned int object_idx) const -{ - std::vector idxs; - - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) - { - if ((*m_volumes)[i]->object_idx() == (int)object_idx) - idxs.push_back(i); - } - - return idxs; -} - -std::vector Selection::get_volume_idxs_from_instance(unsigned int object_idx, unsigned int instance_idx) const -{ - std::vector idxs; - - 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->instance_idx() == (int)instance_idx)) - idxs.push_back(i); - } - - return idxs; -} - -std::vector Selection::get_volume_idxs_from_volume(unsigned int object_idx, unsigned int instance_idx, unsigned int volume_idx) const -{ - std::vector idxs; - - 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)) - idxs.push_back(i); - } - } - - return idxs; -} - -std::vector Selection::get_missing_volume_idxs_from(const std::vector& volume_idxs) const -{ - std::vector idxs; - - for (unsigned int i : m_list) - { - std::vector::const_iterator it = std::find(volume_idxs.begin(), volume_idxs.end(), i); - if (it == volume_idxs.end()) - idxs.push_back(i); - } - - return idxs; -} - -std::vector Selection::get_unselected_volume_idxs_from(const std::vector& volume_idxs) const -{ - std::vector idxs; - - for (unsigned int i : volume_idxs) - { - if (m_list.find(i) == m_list.end()) - idxs.push_back(i); - } - - return idxs; -} - -void Selection::update_valid() -{ - m_valid = (m_volumes != nullptr) && (m_model != nullptr); -} - -void Selection::update_type() -{ - m_cache.content.clear(); - m_type = Mixed; - - for (unsigned int i : m_list) - { - const GLVolume* volume = (*m_volumes)[i]; - int obj_idx = volume->object_idx(); - int inst_idx = volume->instance_idx(); - ObjectIdxsToInstanceIdxsMap::iterator obj_it = m_cache.content.find(obj_idx); - if (obj_it == m_cache.content.end()) - obj_it = m_cache.content.insert(ObjectIdxsToInstanceIdxsMap::value_type(obj_idx, InstanceIdxsList())).first; - - obj_it->second.insert(inst_idx); - } - - bool requires_disable = false; - - if (!m_valid) - m_type = Invalid; - else - { - if (m_list.empty()) - m_type = Empty; - else if (m_list.size() == 1) - { - const GLVolume* first = (*m_volumes)[*m_list.begin()]; - if (first->is_wipe_tower) - m_type = WipeTower; - else if (first->is_modifier) - { - m_type = SingleModifier; - requires_disable = true; - } - else - { - const ModelObject* model_object = m_model->objects[first->object_idx()]; - unsigned int volumes_count = (unsigned int)model_object->volumes.size(); - unsigned int instances_count = (unsigned int)model_object->instances.size(); - if (volumes_count * instances_count == 1) - { - m_type = SingleFullObject; - // ensures the correct mode is selected - m_mode = Instance; - } - else if (volumes_count == 1) // instances_count > 1 - { - m_type = SingleFullInstance; - // ensures the correct mode is selected - m_mode = Instance; - } - else - { - m_type = SingleVolume; - requires_disable = true; - } - } - } - else - { - unsigned int sla_volumes_count = 0; - // Note: sla_volumes_count is a count of the selected sla_volumes per object instead of per instance, like a model_volumes_count is - for (unsigned int i : m_list) { - if ((*m_volumes)[i]->volume_idx() < 0) - ++sla_volumes_count; - } - - if (m_cache.content.size() == 1) // single object - { - const ModelObject* model_object = m_model->objects[m_cache.content.begin()->first]; - unsigned int model_volumes_count = (unsigned int)model_object->volumes.size(); - - unsigned int instances_count = (unsigned int)model_object->instances.size(); - unsigned int selected_instances_count = (unsigned int)m_cache.content.begin()->second.size(); - if (model_volumes_count * instances_count + sla_volumes_count == (unsigned int)m_list.size()) - { - m_type = SingleFullObject; - // ensures the correct mode is selected - m_mode = Instance; - } - else if (selected_instances_count == 1) - { - if (model_volumes_count + sla_volumes_count == (unsigned int)m_list.size()) - { - m_type = SingleFullInstance; - // ensures the correct mode is selected - m_mode = Instance; - } - else - { - unsigned int modifiers_count = 0; - for (unsigned int i : m_list) - { - if ((*m_volumes)[i]->is_modifier) - ++modifiers_count; - } - - if (modifiers_count == 0) - m_type = MultipleVolume; - else if (modifiers_count == (unsigned int)m_list.size()) - m_type = MultipleModifier; - - requires_disable = true; - } - } - else if ((selected_instances_count > 1) && (selected_instances_count * model_volumes_count + sla_volumes_count == (unsigned int)m_list.size())) - { - m_type = MultipleFullInstance; - // ensures the correct mode is selected - m_mode = Instance; - } - } - else - { - unsigned int sels_cntr = 0; - for (ObjectIdxsToInstanceIdxsMap::iterator it = m_cache.content.begin(); it != m_cache.content.end(); ++it) - { - const ModelObject* model_object = m_model->objects[it->first]; - unsigned int volumes_count = (unsigned int)model_object->volumes.size(); - unsigned int instances_count = (unsigned int)model_object->instances.size(); - sels_cntr += volumes_count * instances_count; - } - if (sels_cntr + sla_volumes_count == (unsigned int)m_list.size()) - { - m_type = MultipleFullObject; - // ensures the correct mode is selected - m_mode = Instance; - } - } - } - } - - int object_idx = get_object_idx(); - int instance_idx = get_instance_idx(); - for (GLVolume* v : *m_volumes) - { - v->disabled = requires_disable ? (v->object_idx() != object_idx) || (v->instance_idx() != instance_idx) : false; - } - -#if ENABLE_SELECTION_DEBUG_OUTPUT - std::cout << "Selection: "; - std::cout << "mode: "; - switch (m_mode) - { - case Volume: - { - std::cout << "Volume"; - break; - } - case Instance: - { - std::cout << "Instance"; - break; - } - } - - std::cout << " - type: "; - - switch (m_type) - { - case Invalid: - { - std::cout << "Invalid" << std::endl; - break; - } - case Empty: - { - std::cout << "Empty" << std::endl; - break; - } - case WipeTower: - { - std::cout << "WipeTower" << std::endl; - break; - } - case SingleModifier: - { - std::cout << "SingleModifier" << std::endl; - break; - } - case MultipleModifier: - { - std::cout << "MultipleModifier" << std::endl; - break; - } - case SingleVolume: - { - std::cout << "SingleVolume" << std::endl; - break; - } - case MultipleVolume: - { - std::cout << "MultipleVolume" << std::endl; - break; - } - case SingleFullObject: - { - std::cout << "SingleFullObject" << std::endl; - break; - } - case MultipleFullObject: - { - std::cout << "MultipleFullObject" << std::endl; - break; - } - case SingleFullInstance: - { - std::cout << "SingleFullInstance" << std::endl; - break; - } - case MultipleFullInstance: - { - std::cout << "MultipleFullInstance" << std::endl; - break; - } - case Mixed: - { - std::cout << "Mixed" << std::endl; - break; - } - } -#endif // ENABLE_SELECTION_DEBUG_OUTPUT -} - -void Selection::set_caches() -{ - m_cache.volumes_data.clear(); - m_cache.sinking_volumes.clear(); - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { - const GLVolume& v = *(*m_volumes)[i]; - m_cache.volumes_data.emplace(i, VolumeCache(v.get_volume_transformation(), v.get_instance_transformation())); - if (v.is_sinking()) - m_cache.sinking_volumes.push_back(i); - } - m_cache.dragging_center = get_bounding_box().center(); -} - -void Selection::do_add_volume(unsigned int volume_idx) -{ - m_list.insert(volume_idx); - GLVolume* v = (*m_volumes)[volume_idx]; - v->selected = true; - if (v->hover == GLVolume::HS_Select || v->hover == GLVolume::HS_Deselect) - v->hover = GLVolume::HS_Hover; -} - -void Selection::do_add_volumes(const std::vector& volume_idxs) -{ - for (unsigned int i : volume_idxs) - { - if (i < (unsigned int)m_volumes->size()) - do_add_volume(i); - } -} - -void Selection::do_remove_volume(unsigned int volume_idx) -{ - IndicesList::iterator v_it = m_list.find(volume_idx); - if (v_it == m_list.end()) - return; - - m_list.erase(v_it); - - (*m_volumes)[volume_idx]->selected = false; -} - -void Selection::do_remove_instance(unsigned int object_idx, unsigned int instance_idx) -{ - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { - GLVolume* v = (*m_volumes)[i]; - if (v->object_idx() == (int)object_idx && v->instance_idx() == (int)instance_idx) - do_remove_volume(i); - } -} - -void Selection::do_remove_object(unsigned int object_idx) -{ - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { - GLVolume* v = (*m_volumes)[i]; - if (v->object_idx() == (int)object_idx) - do_remove_volume(i); - } -} - -#if !ENABLE_LEGACY_OPENGL_REMOVAL -void Selection::render_selected_volumes() const -{ - float color[3] = { 1.0f, 1.0f, 1.0f }; - render_bounding_box(get_bounding_box(), color); -} -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - -void Selection::render_synchronized_volumes() -{ - if (m_mode == Instance) - return; - -#if !ENABLE_LEGACY_OPENGL_REMOVAL - float color[3] = { 1.0f, 1.0f, 0.0f }; -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - - for (unsigned int i : m_list) { - const GLVolume& volume = *(*m_volumes)[i]; - int object_idx = volume.object_idx(); - int volume_idx = volume.volume_idx(); - for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { - if (i == j) - continue; - - const GLVolume& v = *(*m_volumes)[j]; - if (v.object_idx() != object_idx || v.volume_idx() != volume_idx) - continue; - -#if ENABLE_LEGACY_OPENGL_REMOVAL - render_bounding_box(v.transformed_convex_hull_bounding_box(), ColorRGB::YELLOW()); -#else - render_bounding_box(v.transformed_convex_hull_bounding_box(), color); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - } -} - -#if ENABLE_LEGACY_OPENGL_REMOVAL -void Selection::render_bounding_box(const BoundingBoxf3& box, const ColorRGB& color) -{ -#else -void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) const -{ - if (color == nullptr) - return; - - const Vec3f b_min = box.min.cast(); - const Vec3f b_max = box.max.cast(); - const Vec3f size = 0.2f * box.size().cast(); - - glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glColor3fv(color)); - glsafe(::glLineWidth(2.0f * m_scale_factor)); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - -#if ENABLE_LEGACY_OPENGL_REMOVAL - const BoundingBoxf3& curr_box = m_box.get_bounding_box(); - if (!m_box.is_initialized() || !is_approx(box.min, curr_box.min) || !is_approx(box.max, curr_box.max)) { - m_box.reset(); - - const Vec3f b_min = box.min.cast(); - const Vec3f b_max = box.max.cast(); - const Vec3f size = 0.2f * box.size().cast(); - - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; - init_data.reserve_vertices(48); - init_data.reserve_indices(48); - - // vertices - init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_min.x() + size.x(), b_min.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_min.y() + size.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z() + size.z())); - - init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_max.x() - size.x(), b_min.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_min.y() + size.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_min.z() + size.z())); - - init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_max.x() - size.x(), b_max.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_max.y() - size.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_min.z() + size.z())); - - init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_min.x() + size.x(), b_max.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_max.y() - size.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_min.z() + size.z())); - - init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_min.x() + size.x(), b_min.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_min.y() + size.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_max.z() - size.z())); - - init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_max.x() - size.x(), b_min.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_min.y() + size.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_max.z() - size.z())); - - init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_max.x() - size.x(), b_max.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_max.y() - size.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_max.z() - size.z())); - - init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_min.x() + size.x(), b_max.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_max.y() - size.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_max.z() - size.z())); - - // indices - for (unsigned int i = 0; i < 48; ++i) { - init_data.add_index(i); - } - - m_box.init_from(std::move(init_data)); - } - - glsafe(::glEnable(GL_DEPTH_TEST)); - - glsafe(::glLineWidth(2.0f * m_scale_factor)); - - GLShaderProgram* shader = wxGetApp().get_shader("flat"); - if (shader == nullptr) - return; - - shader->start_using(); -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - shader->set_uniform("view_model_matrix", camera.get_view_matrix()); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - m_box.set_color(to_rgba(color)); - m_box.render(); - shader->stop_using(); -#else - ::glBegin(GL_LINES); - - ::glVertex3f(b_min(0), b_min(1), b_min(2)); ::glVertex3f(b_min(0) + size(0), b_min(1), b_min(2)); - ::glVertex3f(b_min(0), b_min(1), b_min(2)); ::glVertex3f(b_min(0), b_min(1) + size(1), b_min(2)); - ::glVertex3f(b_min(0), b_min(1), b_min(2)); ::glVertex3f(b_min(0), b_min(1), b_min(2) + size(2)); - - ::glVertex3f(b_max(0), b_min(1), b_min(2)); ::glVertex3f(b_max(0) - size(0), b_min(1), b_min(2)); - ::glVertex3f(b_max(0), b_min(1), b_min(2)); ::glVertex3f(b_max(0), b_min(1) + size(1), b_min(2)); - ::glVertex3f(b_max(0), b_min(1), b_min(2)); ::glVertex3f(b_max(0), b_min(1), b_min(2) + size(2)); - - ::glVertex3f(b_max(0), b_max(1), b_min(2)); ::glVertex3f(b_max(0) - size(0), b_max(1), b_min(2)); - ::glVertex3f(b_max(0), b_max(1), b_min(2)); ::glVertex3f(b_max(0), b_max(1) - size(1), b_min(2)); - ::glVertex3f(b_max(0), b_max(1), b_min(2)); ::glVertex3f(b_max(0), b_max(1), b_min(2) + size(2)); - - ::glVertex3f(b_min(0), b_max(1), b_min(2)); ::glVertex3f(b_min(0) + size(0), b_max(1), b_min(2)); - ::glVertex3f(b_min(0), b_max(1), b_min(2)); ::glVertex3f(b_min(0), b_max(1) - size(1), b_min(2)); - ::glVertex3f(b_min(0), b_max(1), b_min(2)); ::glVertex3f(b_min(0), b_max(1), b_min(2) + size(2)); - - ::glVertex3f(b_min(0), b_min(1), b_max(2)); ::glVertex3f(b_min(0) + size(0), b_min(1), b_max(2)); - ::glVertex3f(b_min(0), b_min(1), b_max(2)); ::glVertex3f(b_min(0), b_min(1) + size(1), b_max(2)); - ::glVertex3f(b_min(0), b_min(1), b_max(2)); ::glVertex3f(b_min(0), b_min(1), b_max(2) - size(2)); - - ::glVertex3f(b_max(0), b_min(1), b_max(2)); ::glVertex3f(b_max(0) - size(0), b_min(1), b_max(2)); - ::glVertex3f(b_max(0), b_min(1), b_max(2)); ::glVertex3f(b_max(0), b_min(1) + size(1), b_max(2)); - ::glVertex3f(b_max(0), b_min(1), b_max(2)); ::glVertex3f(b_max(0), b_min(1), b_max(2) - size(2)); - - ::glVertex3f(b_max(0), b_max(1), b_max(2)); ::glVertex3f(b_max(0) - size(0), b_max(1), b_max(2)); - ::glVertex3f(b_max(0), b_max(1), b_max(2)); ::glVertex3f(b_max(0), b_max(1) - size(1), b_max(2)); - ::glVertex3f(b_max(0), b_max(1), b_max(2)); ::glVertex3f(b_max(0), b_max(1), b_max(2) - size(2)); - - ::glVertex3f(b_min(0), b_max(1), b_max(2)); ::glVertex3f(b_min(0) + size(0), b_max(1), b_max(2)); - ::glVertex3f(b_min(0), b_max(1), b_max(2)); ::glVertex3f(b_min(0), b_max(1) - size(1), b_max(2)); - ::glVertex3f(b_min(0), b_max(1), b_max(2)); ::glVertex3f(b_min(0), b_max(1), b_max(2) - size(2)); - - glsafe(::glEnd()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} - -static ColorRGBA get_color(Axis axis) -{ - return AXES_COLOR[axis]; -} - -#if ENABLE_GL_SHADERS_ATTRIBUTES -void Selection::render_sidebar_position_hints(const std::string& sidebar_field, GLShaderProgram& shader, const Transform3d& matrix) -#else -void Selection::render_sidebar_position_hints(const std::string& sidebar_field) -#endif // ENABLE_GL_SHADERS_ATTRIBUTES -{ -#if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - const Transform3d view_matrix = camera.get_view_matrix() * matrix; - shader.set_uniform("projection_matrix", camera.get_projection_matrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - if (boost::ends_with(sidebar_field, "x")) { -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Transform3d view_model_matrix = view_matrix * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitZ()); - shader.set_uniform("view_model_matrix", view_model_matrix); - shader.set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); -#else - glsafe(::glRotated(-90.0, 0.0, 0.0, 1.0)); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - m_arrow.set_color(get_color(X)); - m_arrow.render(); - } - else if (boost::ends_with(sidebar_field, "y")) { -#if ENABLE_GL_SHADERS_ATTRIBUTES - shader.set_uniform("view_model_matrix", view_matrix); - shader.set_uniform("normal_matrix", (Matrix3d)view_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - m_arrow.set_color(get_color(Y)); - m_arrow.render(); - } - else if (boost::ends_with(sidebar_field, "z")) { -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Transform3d view_model_matrix = view_matrix * Geometry::assemble_transform(Vec3d::Zero(), 0.5 * PI * Vec3d::UnitX()); - shader.set_uniform("view_model_matrix", view_model_matrix); - shader.set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); -#else - glsafe(::glRotated(90.0, 1.0, 0.0, 0.0)); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - m_arrow.set_color(get_color(Z)); - m_arrow.render(); - } -#else - if (boost::ends_with(sidebar_field, "x")) { - glsafe(::glRotated(-90.0, 0.0, 0.0, 1.0)); - m_arrow.set_color(-1, get_color(X)); - m_arrow.render(); - } - else if (boost::ends_with(sidebar_field, "y")) { - m_arrow.set_color(-1, get_color(Y)); - m_arrow.render(); - } - else if (boost::ends_with(sidebar_field, "z")) { - glsafe(::glRotated(90.0, 1.0, 0.0, 0.0)); - m_arrow.set_color(-1, get_color(Z)); - m_arrow.render(); - } -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} - -#if ENABLE_GL_SHADERS_ATTRIBUTES -void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field, GLShaderProgram& shader, const Transform3d& matrix) -#else -void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field) -#endif // ENABLE_GL_SHADERS_ATTRIBUTES -{ -#if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_GL_SHADERS_ATTRIBUTES - auto render_sidebar_rotation_hint = [this](GLShaderProgram& shader, const Transform3d& matrix) { - Transform3d view_model_matrix = matrix; - shader.set_uniform("view_model_matrix", view_model_matrix); - shader.set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); - m_curved_arrow.render(); - view_model_matrix = matrix * Geometry::assemble_transform(Vec3d::Zero(), PI * Vec3d::UnitZ()); - shader.set_uniform("view_model_matrix", view_model_matrix); - shader.set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); - m_curved_arrow.render(); - }; - - const Camera& camera = wxGetApp().plater()->get_camera(); - const Transform3d view_matrix = camera.get_view_matrix() * matrix; - shader.set_uniform("projection_matrix", camera.get_projection_matrix()); -#else - auto render_sidebar_rotation_hint = [this]() { - m_curved_arrow.render(); - glsafe(::glRotated(180.0, 0.0, 0.0, 1.0)); - m_curved_arrow.render(); - }; -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - if (boost::ends_with(sidebar_field, "x")) { -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glRotated(90.0, 0.0, 1.0, 0.0)); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - m_curved_arrow.set_color(get_color(X)); -#if ENABLE_GL_SHADERS_ATTRIBUTES - render_sidebar_rotation_hint(shader, view_matrix * Geometry::assemble_transform(Vec3d::Zero(), 0.5 * PI * Vec3d::UnitY())); -#else - render_sidebar_rotation_hint(); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - } - else if (boost::ends_with(sidebar_field, "y")) { -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glRotated(-90.0, 1.0, 0.0, 0.0)); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - m_curved_arrow.set_color(get_color(Y)); -#if ENABLE_GL_SHADERS_ATTRIBUTES - render_sidebar_rotation_hint(shader, view_matrix * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitX())); -#else - render_sidebar_rotation_hint(); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - } - else if (boost::ends_with(sidebar_field, "z")) { - m_curved_arrow.set_color(get_color(Z)); -#if ENABLE_GL_SHADERS_ATTRIBUTES - render_sidebar_rotation_hint(shader, view_matrix); -#else - render_sidebar_rotation_hint(); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - } -#else - auto render_sidebar_rotation_hint = [this]() { - m_curved_arrow.render(); - glsafe(::glRotated(180.0, 0.0, 0.0, 1.0)); - m_curved_arrow.render(); - }; - - if (boost::ends_with(sidebar_field, "x")) { - glsafe(::glRotated(90.0, 0.0, 1.0, 0.0)); - m_curved_arrow.set_color(-1, get_color(X)); - render_sidebar_rotation_hint(); - } - else if (boost::ends_with(sidebar_field, "y")) { - glsafe(::glRotated(-90.0, 1.0, 0.0, 0.0)); - m_curved_arrow.set_color(-1, get_color(Y)); - render_sidebar_rotation_hint(); - } - else if (boost::ends_with(sidebar_field, "z")) { - m_curved_arrow.set_color(-1, get_color(Z)); - render_sidebar_rotation_hint(); - } -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} - -#if ENABLE_GL_SHADERS_ATTRIBUTES -void Selection::render_sidebar_scale_hints(const std::string& sidebar_field, GLShaderProgram& shader, const Transform3d& matrix) -#else -void Selection::render_sidebar_scale_hints(const std::string& sidebar_field) -#endif // ENABLE_GL_SHADERS_ATTRIBUTES -{ - const bool uniform_scale = requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling(); - -#if ENABLE_GL_SHADERS_ATTRIBUTES - auto render_sidebar_scale_hint = [this, uniform_scale](Axis axis, GLShaderProgram& shader, const Transform3d& matrix) { -#else - auto render_sidebar_scale_hint = [this, uniform_scale](Axis axis) { -#endif // ENABLE_GL_SHADERS_ATTRIBUTES -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_arrow.set_color(uniform_scale ? UNIFORM_SCALE_COLOR : get_color(axis)); -#else - m_arrow.set_color(-1, uniform_scale ? UNIFORM_SCALE_COLOR : get_color(axis)); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - -#if ENABLE_GL_SHADERS_ATTRIBUTES - Transform3d view_model_matrix = matrix * Geometry::assemble_transform(5.0 * Vec3d::UnitY()); - shader.set_uniform("view_model_matrix", view_model_matrix); - shader.set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); -#else - GLShaderProgram* shader = wxGetApp().get_current_shader(); - if (shader != nullptr) - shader->set_uniform("emission_factor", 0.0f); - - glsafe(::glTranslated(0.0, 5.0, 0.0)); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - m_arrow.render(); - -#if ENABLE_GL_SHADERS_ATTRIBUTES - view_model_matrix = matrix * Geometry::assemble_transform(-5.0 * Vec3d::UnitY(), PI * Vec3d::UnitZ()); - shader.set_uniform("view_model_matrix", view_model_matrix); - shader.set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); -#else - glsafe(::glTranslated(0.0, -10.0, 0.0)); - glsafe(::glRotated(180.0, 0.0, 0.0, 1.0)); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - m_arrow.render(); - }; - -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - const Transform3d view_matrix = camera.get_view_matrix() * matrix; - shader.set_uniform("projection_matrix", camera.get_projection_matrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - if (boost::ends_with(sidebar_field, "x") || uniform_scale) { -#if ENABLE_GL_SHADERS_ATTRIBUTES - render_sidebar_scale_hint(X, shader, view_matrix * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitZ())); -#else - glsafe(::glPushMatrix()); - glsafe(::glRotated(-90.0, 0.0, 0.0, 1.0)); - render_sidebar_scale_hint(X); - glsafe(::glPopMatrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - } - - if (boost::ends_with(sidebar_field, "y") || uniform_scale) { -#if ENABLE_GL_SHADERS_ATTRIBUTES - render_sidebar_scale_hint(Y, shader, view_matrix); -#else - glsafe(::glPushMatrix()); - render_sidebar_scale_hint(Y); - glsafe(::glPopMatrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - } - - if (boost::ends_with(sidebar_field, "z") || uniform_scale) { -#if ENABLE_GL_SHADERS_ATTRIBUTES - render_sidebar_scale_hint(Z, shader, view_matrix * Geometry::assemble_transform(Vec3d::Zero(), 0.5 * PI * Vec3d::UnitX())); -#else - glsafe(::glPushMatrix()); - glsafe(::glRotated(90.0, 1.0, 0.0, 0.0)); - render_sidebar_scale_hint(Z); - glsafe(::glPopMatrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - } -} - -#if ENABLE_GL_SHADERS_ATTRIBUTES -void Selection::render_sidebar_layers_hints(const std::string& sidebar_field, GLShaderProgram& shader) -#else -void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) -#endif // ENABLE_GL_SHADERS_ATTRIBUTES -{ - static const float Margin = 10.0f; - - std::string field = sidebar_field; - - // extract max_z - std::string::size_type pos = field.rfind("_"); - if (pos == std::string::npos) - return; - - const float max_z = float(string_to_double_decimal_point(field.substr(pos + 1))); - - // extract min_z - field = field.substr(0, pos); - pos = field.rfind("_"); - if (pos == std::string::npos) - return; - - const float min_z = float(string_to_double_decimal_point(field.substr(pos + 1))); - - // extract type - field = field.substr(0, pos); - pos = field.rfind("_"); - if (pos == std::string::npos) - return; - - const int type = std::stoi(field.substr(pos + 1)); - - const BoundingBoxf3& box = get_bounding_box(); - -#if !ENABLE_LEGACY_OPENGL_REMOVAL - const float min_x = float(box.min.x()) - Margin; - const float max_x = float(box.max.x()) + Margin; - const float min_y = float(box.min.y()) - Margin; - const float max_y = float(box.max.y()) + Margin; -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - - // view dependend order of rendering to keep correct transparency - const bool camera_on_top = wxGetApp().plater()->get_camera().is_looking_downward(); - const float z1 = camera_on_top ? min_z : max_z; - const float z2 = camera_on_top ? max_z : min_z; - -#if ENABLE_LEGACY_OPENGL_REMOVAL - const Vec3f p1 = { float(box.min.x()) - Margin, float(box.min.y()) - Margin, z1 }; - const Vec3f p2 = { float(box.max.x()) + Margin, float(box.max.y()) + Margin, z2 }; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glDisable(GL_CULL_FACE)); - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - if (!m_planes.models[0].is_initialized() || !is_approx(m_planes.check_points[0], p1)) { - m_planes.check_points[0] = p1; - m_planes.models[0].reset(); - - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; - init_data.reserve_vertices(4); - init_data.reserve_indices(6); - - // vertices - init_data.add_vertex(Vec3f(p1.x(), p1.y(), z1)); - init_data.add_vertex(Vec3f(p2.x(), p1.y(), z1)); - init_data.add_vertex(Vec3f(p2.x(), p2.y(), z1)); - init_data.add_vertex(Vec3f(p1.x(), p2.y(), z1)); - - // indices - init_data.add_triangle(0, 1, 2); - init_data.add_triangle(2, 3, 0); - - m_planes.models[0].init_from(std::move(init_data)); - } - - if (!m_planes.models[1].is_initialized() || !is_approx(m_planes.check_points[1], p2)) { - m_planes.check_points[1] = p2; - m_planes.models[1].reset(); - - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; - init_data.reserve_vertices(4); - init_data.reserve_indices(6); - - // vertices - init_data.add_vertex(Vec3f(p1.x(), p1.y(), z2)); - init_data.add_vertex(Vec3f(p2.x(), p1.y(), z2)); - init_data.add_vertex(Vec3f(p2.x(), p2.y(), z2)); - init_data.add_vertex(Vec3f(p1.x(), p2.y(), z2)); - - // indices - init_data.add_triangle(0, 1, 2); - init_data.add_triangle(2, 3, 0); - - m_planes.models[1].init_from(std::move(init_data)); - } - -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - shader.set_uniform("view_model_matrix", camera.get_view_matrix()); - shader.set_uniform("projection_matrix", camera.get_projection_matrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - m_planes.models[0].set_color((camera_on_top && type == 1) || (!camera_on_top && type == 2) ? SOLID_PLANE_COLOR : TRANSPARENT_PLANE_COLOR); - m_planes.models[0].render(); - m_planes.models[1].set_color((camera_on_top && type == 2) || (!camera_on_top && type == 1) ? SOLID_PLANE_COLOR : TRANSPARENT_PLANE_COLOR); - m_planes.models[1].render(); -#else - ::glBegin(GL_QUADS); - ::glColor4fv((camera_on_top && type == 1) || (!camera_on_top && type == 2) ? SOLID_PLANE_COLOR.data() : TRANSPARENT_PLANE_COLOR.data()); - ::glVertex3f(min_x, min_y, z1); - ::glVertex3f(max_x, min_y, z1); - ::glVertex3f(max_x, max_y, z1); - ::glVertex3f(min_x, max_y, z1); - glsafe(::glEnd()); - - ::glBegin(GL_QUADS); - ::glColor4fv((camera_on_top && type == 2) || (!camera_on_top && type == 1) ? SOLID_PLANE_COLOR.data() : TRANSPARENT_PLANE_COLOR.data()); - ::glVertex3f(min_x, min_y, z2); - ::glVertex3f(max_x, min_y, z2); - ::glVertex3f(max_x, max_y, z2); - ::glVertex3f(min_x, max_y, z2); - glsafe(::glEnd()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - glsafe(::glEnable(GL_CULL_FACE)); - glsafe(::glDisable(GL_BLEND)); -} - -#ifndef NDEBUG -static bool is_rotation_xy_synchronized(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) -{ - const Eigen::AngleAxisd angle_axis(Geometry::rotation_xyz_diff(rot_xyz_from, rot_xyz_to)); - const Vec3d axis = angle_axis.axis(); - const double angle = angle_axis.angle(); - if (std::abs(angle) < 1e-8) - return true; - assert(std::abs(axis.x()) < 1e-8); - assert(std::abs(axis.y()) < 1e-8); - assert(std::abs(std::abs(axis.z()) - 1.) < 1e-8); - return std::abs(axis.x()) < 1e-8 && std::abs(axis.y()) < 1e-8 && std::abs(std::abs(axis.z()) - 1.) < 1e-8; -} - -static void verify_instances_rotation_synchronized(const Model &model, const GLVolumePtrs &volumes) -{ - for (int idx_object = 0; idx_object < int(model.objects.size()); ++idx_object) { - int idx_volume_first = -1; - for (int i = 0; i < (int)volumes.size(); ++i) { - if (volumes[i]->object_idx() == idx_object) { - idx_volume_first = i; - break; - } - } - assert(idx_volume_first != -1); // object without instances? - if (idx_volume_first == -1) - continue; - const Vec3d &rotation0 = volumes[idx_volume_first]->get_instance_rotation(); - for (int i = idx_volume_first + 1; i < (int)volumes.size(); ++i) - if (volumes[i]->object_idx() == idx_object) { - const Vec3d &rotation = volumes[i]->get_instance_rotation(); - assert(is_rotation_xy_synchronized(rotation, rotation0)); - } - } -} -#endif /* NDEBUG */ - -void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_type) -{ - std::set done; // prevent processing volumes twice - done.insert(m_list.begin(), m_list.end()); - - for (unsigned int i : m_list) { - if (done.size() == m_volumes->size()) - break; - - const GLVolume* volume = (*m_volumes)[i]; -#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - if (volume->is_wipe_tower) - continue; - - const int object_idx = volume->object_idx(); -#else - const int object_idx = volume->object_idx(); - if (object_idx >= 1000) - continue; -#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - - const int instance_idx = volume->instance_idx(); - const Vec3d& rotation = volume->get_instance_rotation(); - const Vec3d& scaling_factor = volume->get_instance_scaling_factor(); - const Vec3d& mirror = volume->get_instance_mirror(); - - // Process unselected instances. - for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { - if (done.size() == m_volumes->size()) - break; - - if (done.find(j) != done.end()) - continue; - - GLVolume* v = (*m_volumes)[j]; - if (v->object_idx() != object_idx || v->instance_idx() == instance_idx) - continue; - - assert(is_rotation_xy_synchronized(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation())); - switch (sync_rotation_type) { - case SYNC_ROTATION_NONE: { - // z only rotation -> synch instance z - // The X,Y rotations should be synchronized from start to end of the rotation. - assert(is_rotation_xy_synchronized(rotation, v->get_instance_rotation())); - if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) - v->set_instance_offset(Z, volume->get_instance_offset().z()); - break; - } - case SYNC_ROTATION_GENERAL: - // generic rotation -> update instance z with the delta of the rotation. - const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation()); - v->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff }); - break; - } - - v->set_instance_scaling_factor(scaling_factor); - v->set_instance_mirror(mirror); - - done.insert(j); - } - } - -#ifndef NDEBUG - verify_instances_rotation_synchronized(*m_model, *m_volumes); -#endif /* NDEBUG */ -} - -void Selection::synchronize_unselected_volumes() -{ - for (unsigned int i : m_list) { - const GLVolume* volume = (*m_volumes)[i]; -#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - if (volume->is_wipe_tower) - continue; - - const int object_idx = volume->object_idx(); -#else - const int object_idx = volume->object_idx(); - if (object_idx >= 1000) - continue; -#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - - const int volume_idx = volume->volume_idx(); - const Vec3d& offset = volume->get_volume_offset(); - const Vec3d& rotation = volume->get_volume_rotation(); - const Vec3d& scaling_factor = volume->get_volume_scaling_factor(); - const Vec3d& mirror = volume->get_volume_mirror(); - - // Process unselected volumes. - for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { - if (j == i) - continue; - - GLVolume* v = (*m_volumes)[j]; - if (v->object_idx() != object_idx || v->volume_idx() != volume_idx) - continue; - - v->set_volume_offset(offset); - v->set_volume_rotation(rotation); - v->set_volume_scaling_factor(scaling_factor); - v->set_volume_mirror(mirror); - } - } -} - -void Selection::ensure_on_bed() -{ - typedef std::map, double> InstancesToZMap; - InstancesToZMap instances_min_z; - - for (size_t i = 0; i < m_volumes->size(); ++i) { - GLVolume* volume = (*m_volumes)[i]; - if (!volume->is_wipe_tower && !volume->is_modifier && - std::find(m_cache.sinking_volumes.begin(), m_cache.sinking_volumes.end(), i) == m_cache.sinking_volumes.end()) { - const double min_z = volume->transformed_convex_hull_bounding_box().min.z(); - std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); - InstancesToZMap::iterator it = instances_min_z.find(instance); - if (it == instances_min_z.end()) - it = instances_min_z.insert(InstancesToZMap::value_type(instance, DBL_MAX)).first; - - it->second = std::min(it->second, min_z); - } - } - - for (GLVolume* volume : *m_volumes) { - std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); - InstancesToZMap::iterator it = instances_min_z.find(instance); - if (it != instances_min_z.end()) - volume->set_instance_offset(Z, volume->get_instance_offset(Z) - it->second); - } -} - -void Selection::ensure_not_below_bed() -{ - typedef std::map, double> InstancesToZMap; - InstancesToZMap instances_max_z; - - for (size_t i = 0; i < m_volumes->size(); ++i) { - GLVolume* volume = (*m_volumes)[i]; - if (!volume->is_wipe_tower && !volume->is_modifier) { - const double max_z = volume->transformed_convex_hull_bounding_box().max.z(); - const std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); - InstancesToZMap::iterator it = instances_max_z.find(instance); - if (it == instances_max_z.end()) - it = instances_max_z.insert({ instance, -DBL_MAX }).first; - - it->second = std::max(it->second, max_z); - } - } - - if (is_any_volume()) { - for (unsigned int i : m_list) { - GLVolume& volume = *(*m_volumes)[i]; - const std::pair instance = std::make_pair(volume.object_idx(), volume.instance_idx()); - InstancesToZMap::const_iterator it = instances_max_z.find(instance); - const double z_shift = SINKING_MIN_Z_THRESHOLD - it->second; - if (it != instances_max_z.end() && z_shift > 0.0) - volume.set_volume_offset(Z, volume.get_volume_offset(Z) + z_shift); - } - } - else { - for (GLVolume* volume : *m_volumes) { - const std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); - InstancesToZMap::const_iterator it = instances_max_z.find(instance); - if (it != instances_max_z.end() && it->second < SINKING_MIN_Z_THRESHOLD) - volume->set_instance_offset(Z, volume->get_instance_offset(Z) + SINKING_MIN_Z_THRESHOLD - it->second); - } - } -} - -bool Selection::is_from_fully_selected_instance(unsigned int volume_idx) const -{ - struct SameInstance - { - int obj_idx; - int inst_idx; - GLVolumePtrs& volumes; - - SameInstance(int obj_idx, int inst_idx, GLVolumePtrs& volumes) : obj_idx(obj_idx), inst_idx(inst_idx), volumes(volumes) {} - bool operator () (unsigned int i) { return (volumes[i]->volume_idx() >= 0) && (volumes[i]->object_idx() == obj_idx) && (volumes[i]->instance_idx() == inst_idx); } - }; - - if ((unsigned int)m_volumes->size() <= volume_idx) - return false; - - GLVolume* volume = (*m_volumes)[volume_idx]; - int object_idx = volume->object_idx(); - if ((int)m_model->objects.size() <= object_idx) - return false; - - unsigned int count = (unsigned int)std::count_if(m_list.begin(), m_list.end(), SameInstance(object_idx, volume->instance_idx(), *m_volumes)); - return count == (unsigned int)m_model->objects[object_idx]->volumes.size(); -} - -void Selection::paste_volumes_from_clipboard() -{ -#ifdef _DEBUG - check_model_ids_validity(*m_model); -#endif /* _DEBUG */ - - int dst_obj_idx = get_object_idx(); - if ((dst_obj_idx < 0) || ((int)m_model->objects.size() <= dst_obj_idx)) - return; - - ModelObject* dst_object = m_model->objects[dst_obj_idx]; - - int dst_inst_idx = get_instance_idx(); - if ((dst_inst_idx < 0) || ((int)dst_object->instances.size() <= dst_inst_idx)) - return; - - ModelObject* src_object = m_clipboard.get_object(0); - if (src_object != nullptr) - { - ModelInstance* dst_instance = dst_object->instances[dst_inst_idx]; - BoundingBoxf3 dst_instance_bb = dst_object->instance_bounding_box(dst_inst_idx); - Transform3d src_matrix = src_object->instances[0]->get_transformation().get_matrix(true); - Transform3d dst_matrix = dst_instance->get_transformation().get_matrix(true); - bool from_same_object = (src_object->input_file == dst_object->input_file) && src_matrix.isApprox(dst_matrix); - - // used to keep relative position of multivolume selections when pasting from another object - BoundingBoxf3 total_bb; - - ModelVolumePtrs volumes; - for (ModelVolume* src_volume : src_object->volumes) - { - ModelVolume* dst_volume = dst_object->add_volume(*src_volume); - dst_volume->set_new_unique_id(); - if (from_same_object) - { -// // if the volume comes from the same object, apply the offset in world system -// double offset = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.05); -// dst_volume->translate(dst_matrix.inverse() * Vec3d(offset, offset, 0.0)); - } - else - { - // if the volume comes from another object, apply the offset as done when adding modifiers - // see ObjectList::load_generic_subobject() - total_bb.merge(dst_volume->mesh().bounding_box().transformed(src_volume->get_matrix())); - } - - volumes.push_back(dst_volume); -#ifdef _DEBUG - check_model_ids_validity(*m_model); -#endif /* _DEBUG */ - } - - // keeps relative position of multivolume selections - if (!from_same_object) - { - for (ModelVolume* v : volumes) - { - v->set_offset((v->get_offset() - total_bb.center()) + dst_matrix.inverse() * (Vec3d(dst_instance_bb.max(0), dst_instance_bb.min(1), dst_instance_bb.min(2)) + 0.5 * total_bb.size() - dst_instance->get_transformation().get_offset())); - } - } - - wxGetApp().obj_list()->paste_volumes_into_list(dst_obj_idx, volumes); - } - -#ifdef _DEBUG - check_model_ids_validity(*m_model); -#endif /* _DEBUG */ -} - -void Selection::paste_objects_from_clipboard() -{ -#ifdef _DEBUG - check_model_ids_validity(*m_model); -#endif /* _DEBUG */ - - std::vector object_idxs; - const ModelObjectPtrs& src_objects = m_clipboard.get_objects(); - for (const ModelObject* src_object : src_objects) - { - ModelObject* dst_object = m_model->add_object(*src_object); - double offset = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.05); - Vec3d displacement(offset, offset, 0.0); - for (ModelInstance* inst : dst_object->instances) - { - inst->set_offset(inst->get_offset() + displacement); - } - - object_idxs.push_back(m_model->objects.size() - 1); -#ifdef _DEBUG - check_model_ids_validity(*m_model); -#endif /* _DEBUG */ - } - - wxGetApp().obj_list()->paste_objects_into_list(object_idxs); - -#ifdef _DEBUG - check_model_ids_validity(*m_model); -#endif /* _DEBUG */ -} - -} // namespace GUI -} // namespace Slic3r +#include "libslic3r/libslic3r.h" +#include "Selection.hpp" + +#include "3DScene.hpp" +#include "GLCanvas3D.hpp" +#include "GUI_App.hpp" +#include "GUI.hpp" +#include "GUI_ObjectManipulation.hpp" +#include "GUI_ObjectList.hpp" +#include "Gizmos/GLGizmoBase.hpp" +#include "Camera.hpp" +#include "Plater.hpp" +#include "slic3r/Utils/UndoRedo.hpp" + +#include "libslic3r/LocalesUtils.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/PresetBundle.hpp" +#include "libslic3r/BuildVolume.hpp" + +#include + +#include +#include + +static const Slic3r::ColorRGBA UNIFORM_SCALE_COLOR = Slic3r::ColorRGBA::ORANGE(); +static const Slic3r::ColorRGBA SOLID_PLANE_COLOR = Slic3r::ColorRGBA::ORANGE(); +static const Slic3r::ColorRGBA TRANSPARENT_PLANE_COLOR = { 0.8f, 0.8f, 0.8f, 0.5f }; + +namespace Slic3r { +namespace GUI { + +Selection::VolumeCache::TransformCache::TransformCache() + : position(Vec3d::Zero()) + , rotation(Vec3d::Zero()) + , scaling_factor(Vec3d::Ones()) + , mirror(Vec3d::Ones()) + , rotation_matrix(Transform3d::Identity()) + , scale_matrix(Transform3d::Identity()) + , mirror_matrix(Transform3d::Identity()) + , full_matrix(Transform3d::Identity()) +{ +} + +Selection::VolumeCache::TransformCache::TransformCache(const Geometry::Transformation& transform) + : position(transform.get_offset()) + , rotation(transform.get_rotation()) + , scaling_factor(transform.get_scaling_factor()) + , mirror(transform.get_mirror()) + , full_matrix(transform.get_matrix()) +{ + rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation); + scale_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scaling_factor); + mirror_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d::Ones(), mirror); +} + +Selection::VolumeCache::VolumeCache(const Geometry::Transformation& volume_transform, const Geometry::Transformation& instance_transform) + : m_volume(volume_transform) + , m_instance(instance_transform) +{ +} + +bool Selection::Clipboard::is_sla_compliant() const +{ + if (m_mode == Selection::Volume) + return false; + + for (const ModelObject* o : m_model->objects) { + if (o->is_multiparts()) + return false; + + for (const ModelVolume* v : o->volumes) { + if (v->is_modifier()) + return false; + } + } + + return true; +} + +Selection::Clipboard::Clipboard() +{ + m_model.reset(new Model); +} + +void Selection::Clipboard::reset() +{ + m_model->clear_objects(); +} + +bool Selection::Clipboard::is_empty() const +{ + return m_model->objects.empty(); +} + +ModelObject* Selection::Clipboard::add_object() +{ + return m_model->add_object(); +} + +ModelObject* Selection::Clipboard::get_object(unsigned int id) +{ + return (id < (unsigned int)m_model->objects.size()) ? m_model->objects[id] : nullptr; +} + +const ModelObjectPtrs& Selection::Clipboard::get_objects() const +{ + return m_model->objects; +} + +Selection::Selection() + : m_volumes(nullptr) + , m_model(nullptr) + , m_enabled(false) + , m_mode(Instance) + , m_type(Empty) + , m_valid(false) + , m_scale_factor(1.0f) +{ + this->set_bounding_boxes_dirty(); +} + + +void Selection::set_volumes(GLVolumePtrs* volumes) +{ + m_volumes = volumes; + update_valid(); +} + +// Init shall be called from the OpenGL render function, so that the OpenGL context is initialized! +bool Selection::init() +{ + m_arrow.init_from(straight_arrow(10.0f, 5.0f, 5.0f, 10.0f, 1.0f)); + m_curved_arrow.init_from(circular_arrow(16, 10.0f, 5.0f, 10.0f, 5.0f, 1.0f)); +#if ENABLE_RENDER_SELECTION_CENTER + m_vbo_sphere.init_from(its_make_sphere(0.75, PI / 12.0)); +#endif // ENABLE_RENDER_SELECTION_CENTER + + return true; +} + +void Selection::set_model(Model* model) +{ + m_model = model; + update_valid(); +} + +void Selection::add(unsigned int volume_idx, bool as_single_selection, bool check_for_already_contained) +{ + if (!m_valid || (unsigned int)m_volumes->size() <= volume_idx) + return; + + const GLVolume* volume = (*m_volumes)[volume_idx]; + // wipe tower is already selected + if (is_wipe_tower() && volume->is_wipe_tower) + return; + + bool keep_instance_mode = (m_mode == Instance) && !as_single_selection; + bool already_contained = check_for_already_contained && contains_volume(volume_idx); + + // resets the current list if needed + bool needs_reset = as_single_selection && !already_contained; + needs_reset |= volume->is_wipe_tower; + needs_reset |= is_wipe_tower() && !volume->is_wipe_tower; + needs_reset |= as_single_selection && !is_any_modifier() && volume->is_modifier; + needs_reset |= is_any_modifier() && !volume->is_modifier; + + if (!already_contained || needs_reset) { + wxGetApp().plater()->take_snapshot(_L("Selection-Add"), UndoRedo::SnapshotType::Selection); + + if (needs_reset) + clear(); + + if (!keep_instance_mode) + m_mode = volume->is_modifier ? Volume : Instance; + } + else + // keep current mode + return; + + switch (m_mode) + { + case Volume: + { + if (volume->volume_idx() >= 0 && (is_empty() || volume->instance_idx() == get_instance_idx())) + do_add_volume(volume_idx); + + break; + } + case Instance: + { + Plater::SuppressSnapshots suppress(wxGetApp().plater()); + add_instance(volume->object_idx(), volume->instance_idx(), as_single_selection); + break; + } + } + + update_type(); + this->set_bounding_boxes_dirty(); +} + +void Selection::remove(unsigned int volume_idx) +{ + if (!m_valid || (unsigned int)m_volumes->size() <= volume_idx) + return; + + if (!contains_volume(volume_idx)) + return; + + wxGetApp().plater()->take_snapshot(_L("Selection-Remove"), UndoRedo::SnapshotType::Selection); + + GLVolume* volume = (*m_volumes)[volume_idx]; + + switch (m_mode) + { + case Volume: + { + do_remove_volume(volume_idx); + break; + } + case Instance: + { + do_remove_instance(volume->object_idx(), volume->instance_idx()); + break; + } + } + + update_type(); + this->set_bounding_boxes_dirty(); +} + +void Selection::add_object(unsigned int object_idx, bool as_single_selection) +{ + if (!m_valid) + return; + + std::vector volume_idxs = get_volume_idxs_from_object(object_idx); + if ((!as_single_selection && contains_all_volumes(volume_idxs)) || + (as_single_selection && matches(volume_idxs))) + return; + + wxGetApp().plater()->take_snapshot(_L("Selection-Add Object"), UndoRedo::SnapshotType::Selection); + + // resets the current list if needed + if (as_single_selection) + clear(); + + m_mode = Instance; + + do_add_volumes(volume_idxs); + + update_type(); + this->set_bounding_boxes_dirty(); +} + +void Selection::remove_object(unsigned int object_idx) +{ + if (!m_valid) + return; + + wxGetApp().plater()->take_snapshot(_L("Selection-Remove Object"), UndoRedo::SnapshotType::Selection); + + do_remove_object(object_idx); + + update_type(); + this->set_bounding_boxes_dirty(); +} + +void Selection::add_instance(unsigned int object_idx, unsigned int instance_idx, bool as_single_selection) +{ + if (!m_valid) + return; + + const std::vector volume_idxs = get_volume_idxs_from_instance(object_idx, instance_idx); + if ((!as_single_selection && contains_all_volumes(volume_idxs)) || + (as_single_selection && matches(volume_idxs))) + return; + + wxGetApp().plater()->take_snapshot(_L("Selection-Add Instance"), UndoRedo::SnapshotType::Selection); + + // resets the current list if needed + if (as_single_selection) + clear(); + + m_mode = Instance; + + do_add_volumes(volume_idxs); + + update_type(); + this->set_bounding_boxes_dirty(); +} + +void Selection::remove_instance(unsigned int object_idx, unsigned int instance_idx) +{ + if (!m_valid) + return; + + wxGetApp().plater()->take_snapshot(_L("Selection-Remove Instance"), UndoRedo::SnapshotType::Selection); + + do_remove_instance(object_idx, instance_idx); + + update_type(); + this->set_bounding_boxes_dirty(); +} + +void Selection::add_volume(unsigned int object_idx, unsigned int volume_idx, int instance_idx, bool as_single_selection) +{ + if (!m_valid) + return; + + std::vector volume_idxs = get_volume_idxs_from_volume(object_idx, instance_idx, volume_idx); + if ((!as_single_selection && contains_all_volumes(volume_idxs)) || + (as_single_selection && matches(volume_idxs))) + return; + + // resets the current list if needed + if (as_single_selection) + clear(); + + m_mode = Volume; + + do_add_volumes(volume_idxs); + + update_type(); + this->set_bounding_boxes_dirty(); +} + +void Selection::remove_volume(unsigned int object_idx, unsigned int volume_idx) +{ + if (!m_valid) + return; + + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { + GLVolume* v = (*m_volumes)[i]; + if (v->object_idx() == (int)object_idx && v->volume_idx() == (int)volume_idx) + do_remove_volume(i); + } + + update_type(); + this->set_bounding_boxes_dirty(); +} + +void Selection::add_volumes(EMode mode, const std::vector& volume_idxs, bool as_single_selection) +{ + if (!m_valid) + return; + + if ((!as_single_selection && contains_all_volumes(volume_idxs)) || + (as_single_selection && matches(volume_idxs))) + return; + + // resets the current list if needed + if (as_single_selection) + clear(); + + m_mode = mode; + for (unsigned int i : volume_idxs) { + if (i < (unsigned int)m_volumes->size()) + do_add_volume(i); + } + + update_type(); + this->set_bounding_boxes_dirty(); +} + +void Selection::remove_volumes(EMode mode, const std::vector& volume_idxs) +{ + if (!m_valid) + return; + + m_mode = mode; + for (unsigned int i : volume_idxs) { + if (i < (unsigned int)m_volumes->size()) + do_remove_volume(i); + } + + update_type(); + this->set_bounding_boxes_dirty(); +} + +void Selection::add_all() +{ + if (!m_valid) + return; + + unsigned int count = 0; + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { + if (!(*m_volumes)[i]->is_wipe_tower) + ++count; + } + + if ((unsigned int)m_list.size() == count) + return; + + wxGetApp().plater()->take_snapshot(_(L("Selection-Add All")), UndoRedo::SnapshotType::Selection); + + m_mode = Instance; + clear(); + + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { + if (!(*m_volumes)[i]->is_wipe_tower) + do_add_volume(i); + } + + update_type(); + this->set_bounding_boxes_dirty(); +} + +void Selection::remove_all() +{ + if (!m_valid) + return; + + if (is_empty()) + return; + +// Not taking the snapshot with non-empty Redo stack will likely be more confusing than losing the Redo stack. +// Let's wait for user feedback. +// if (!wxGetApp().plater()->can_redo()) + wxGetApp().plater()->take_snapshot(_L("Selection-Remove All"), UndoRedo::SnapshotType::Selection); + + m_mode = Instance; + clear(); +} + +void Selection::set_deserialized(EMode mode, const std::vector> &volumes_and_instances) +{ + if (! m_valid) + return; + + m_mode = mode; + for (unsigned int i : m_list) + (*m_volumes)[i]->selected = false; + m_list.clear(); + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++ i) + if (std::binary_search(volumes_and_instances.begin(), volumes_and_instances.end(), (*m_volumes)[i]->geometry_id)) + do_add_volume(i); + update_type(); + set_bounding_boxes_dirty(); +} + +void Selection::clear() +{ + if (!m_valid) + return; + + if (m_list.empty()) + return; + + // ensure that the volumes get the proper color before next call to render (expecially needed for transparent volumes) + for (unsigned int i : m_list) { + GLVolume& volume = *(*m_volumes)[i]; + volume.selected = false; + volume.set_render_color(volume.color.is_transparent()); + } + + m_list.clear(); + + update_type(); + set_bounding_boxes_dirty(); + + // this happens while the application is closing + if (wxGetApp().obj_manipul() == nullptr) + return; + + // resets the cache in the sidebar + wxGetApp().obj_manipul()->reset_cache(); + + // #et_FIXME fake KillFocus from sidebar + wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false); +} + +// Update the selection based on the new instance IDs. +void Selection::instances_changed(const std::vector &instance_ids_selected) +{ + assert(m_valid); + assert(m_mode == Instance); + m_list.clear(); + for (unsigned int volume_idx = 0; volume_idx < (unsigned int)m_volumes->size(); ++ volume_idx) { + const GLVolume *volume = (*m_volumes)[volume_idx]; + auto it = std::lower_bound(instance_ids_selected.begin(), instance_ids_selected.end(), volume->geometry_id.second); + if (it != instance_ids_selected.end() && *it == volume->geometry_id.second) + this->do_add_volume(volume_idx); + } + update_type(); + this->set_bounding_boxes_dirty(); +} + +// Update the selection based on the map from old indices to new indices after m_volumes changed. +// If the current selection is by instance, this call may select newly added volumes, if they belong to already selected instances. +void Selection::volumes_changed(const std::vector &map_volume_old_to_new) +{ + assert(m_valid); + assert(m_mode == Volume); + IndicesList list_new; + for (unsigned int idx : m_list) + if (map_volume_old_to_new[idx] != size_t(-1)) { + unsigned int new_idx = (unsigned int)map_volume_old_to_new[idx]; + (*m_volumes)[new_idx]->selected = true; + list_new.insert(new_idx); + } + m_list = std::move(list_new); + update_type(); + this->set_bounding_boxes_dirty(); +} + +bool Selection::is_single_full_instance() const +{ + if (m_type == SingleFullInstance) + return true; + + if (m_type == SingleFullObject) + return get_instance_idx() != -1; + + if (m_list.empty() || m_volumes->empty()) + return false; + + int object_idx = m_valid ? get_object_idx() : -1; + if (object_idx < 0 || (int)m_model->objects.size() <= object_idx) + return false; + + int instance_idx = (*m_volumes)[*m_list.begin()]->instance_idx(); + + std::set volumes_idxs; + for (unsigned int i : m_list) { + const GLVolume* v = (*m_volumes)[i]; + if (object_idx != v->object_idx() || instance_idx != v->instance_idx()) + return false; + + int volume_idx = v->volume_idx(); + if (volume_idx >= 0) + volumes_idxs.insert(volume_idx); + } + + return m_model->objects[object_idx]->volumes.size() == volumes_idxs.size(); +} + +bool Selection::is_from_single_object() const +{ + const int idx = get_object_idx(); +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + return 0 <= idx && idx < int(m_model->objects.size()); +#else + return 0 <= idx && idx < 1000; +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL +} + +bool Selection::is_sla_compliant() const +{ + if (m_mode == Volume) + return false; + + for (unsigned int i : m_list) { + if ((*m_volumes)[i]->is_modifier) + return false; + } + + return true; +} + +bool Selection::contains_all_volumes(const std::vector& volume_idxs) const +{ + for (unsigned int i : volume_idxs) { + if (m_list.find(i) == m_list.end()) + return false; + } + + return true; +} + +bool Selection::contains_any_volume(const std::vector& volume_idxs) const +{ + for (unsigned int i : volume_idxs) { + if (m_list.find(i) != m_list.end()) + return true; + } + + return false; +} + +bool Selection::matches(const std::vector& volume_idxs) const +{ + unsigned int count = 0; + + for (unsigned int i : volume_idxs) { + if (m_list.find(i) != m_list.end()) + ++count; + else + return false; + } + + return count == (unsigned int)m_list.size(); +} + +bool Selection::requires_uniform_scale() const +{ + if (is_single_full_instance() || is_single_modifier() || is_single_volume()) + return false; + + return true; +} + +int Selection::get_object_idx() const +{ + return (m_cache.content.size() == 1) ? m_cache.content.begin()->first : -1; +} + +int Selection::get_instance_idx() const +{ + if (m_cache.content.size() == 1) { + const InstanceIdxsList& idxs = m_cache.content.begin()->second; + if (idxs.size() == 1) + return *idxs.begin(); + } + + return -1; +} + +const Selection::InstanceIdxsList& Selection::get_instance_idxs() const +{ + assert(m_cache.content.size() == 1); + return m_cache.content.begin()->second; +} + +const GLVolume* Selection::get_volume(unsigned int volume_idx) const +{ + return (m_valid && (volume_idx < (unsigned int)m_volumes->size())) ? (*m_volumes)[volume_idx] : nullptr; +} + +const BoundingBoxf3& Selection::get_bounding_box() const +{ + if (!m_bounding_box.has_value()) { + std::optional* bbox = const_cast*>(&m_bounding_box); + *bbox = BoundingBoxf3(); + if (m_valid) { + for (unsigned int i : m_list) { + (*bbox)->merge((*m_volumes)[i]->transformed_convex_hull_bounding_box()); + } + } + } + return *m_bounding_box; +} + +const BoundingBoxf3& Selection::get_unscaled_instance_bounding_box() const +{ + if (!m_unscaled_instance_bounding_box.has_value()) { + std::optional* bbox = const_cast*>(&m_unscaled_instance_bounding_box); + *bbox = BoundingBoxf3(); + if (m_valid) { + for (unsigned int i : m_list) { + const GLVolume& volume = *(*m_volumes)[i]; + if (volume.is_modifier) + continue; + Transform3d trafo = volume.get_instance_transformation().get_matrix(false, false, true, false) * volume.get_volume_transformation().get_matrix(); + trafo.translation().z() += volume.get_sla_shift_z(); + (*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo)); + } + } + } + return *m_unscaled_instance_bounding_box; +} + +const BoundingBoxf3& Selection::get_scaled_instance_bounding_box() const +{ + if (!m_scaled_instance_bounding_box.has_value()) { + std::optional* bbox = const_cast*>(&m_scaled_instance_bounding_box); + *bbox = BoundingBoxf3(); + if (m_valid) { + for (unsigned int i : m_list) { + const GLVolume& volume = *(*m_volumes)[i]; + if (volume.is_modifier) + continue; + Transform3d trafo = volume.get_instance_transformation().get_matrix(false, false, false, false) * volume.get_volume_transformation().get_matrix(); + trafo.translation().z() += volume.get_sla_shift_z(); + (*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo)); + } + } + } + return *m_scaled_instance_bounding_box; +} + +void Selection::setup_cache() +{ + if (!m_valid) + return; + + set_caches(); +} + +void Selection::translate(const Vec3d& displacement, bool local) +{ + if (!m_valid) + return; + + EMode translation_type = m_mode; + + for (unsigned int i : m_list) { + GLVolume& v = *(*m_volumes)[i]; + if (m_mode == Volume || v.is_wipe_tower) { + if (local) + v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + displacement); + else { + const Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; + v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement); + } + } + else if (m_mode == Instance) { + if (is_from_fully_selected_instance(i)) + v.set_instance_offset(m_cache.volumes_data[i].get_instance_position() + displacement); + else { + const Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; + v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement); + translation_type = Volume; + } + } + } + +#if !DISABLE_INSTANCES_SYNCH + if (translation_type == Instance) + synchronize_unselected_instances(SYNC_ROTATION_NONE); + else if (translation_type == Volume) + synchronize_unselected_volumes(); +#endif // !DISABLE_INSTANCES_SYNCH + + ensure_not_below_bed(); + set_bounding_boxes_dirty(); + wxGetApp().plater()->canvas3D()->requires_check_outside_state(); +} + +// Rotate an object around one of the axes. Only one rotation component is expected to be changing. +void Selection::rotate(const Vec3d& rotation, TransformationType transformation_type) +{ + if (!m_valid) + return; + + // Only relative rotation values are allowed in the world coordinate system. + assert(!transformation_type.world() || transformation_type.relative()); + + if (!is_wipe_tower()) { + int rot_axis_max = 0; + if (rotation.isApprox(Vec3d::Zero())) { + for (unsigned int i : m_list) { + GLVolume &v = *(*m_volumes)[i]; + if (m_mode == Instance) { + v.set_instance_rotation(m_cache.volumes_data[i].get_instance_rotation()); + v.set_instance_offset(m_cache.volumes_data[i].get_instance_position()); + } + else if (m_mode == Volume) { + v.set_volume_rotation(m_cache.volumes_data[i].get_volume_rotation()); + v.set_volume_offset(m_cache.volumes_data[i].get_volume_position()); + } + } + } + else { // this is not the wipe tower + //FIXME this does not work for absolute rotations (transformation_type.absolute() is true) + rotation.cwiseAbs().maxCoeff(&rot_axis_max); + +// if ( single instance or single volume ) + // Rotate around center , if only a single object or volume +// transformation_type.set_independent(); + + // For generic rotation, we want to rotate the first volume in selection, and then to synchronize the other volumes with it. + std::vector object_instance_first(m_model->objects.size(), -1); + auto rotate_instance = [this, &rotation, &object_instance_first, rot_axis_max, transformation_type](GLVolume &volume, int i) { + const int first_volume_idx = object_instance_first[volume.object_idx()]; + if (rot_axis_max != 2 && first_volume_idx != -1) { + // Generic rotation, but no rotation around the Z axis. + // Always do a local rotation (do not consider the selection to be a rigid body). + assert(is_approx(rotation.z(), 0.0)); + const GLVolume &first_volume = *(*m_volumes)[first_volume_idx]; + const Vec3d &rotation = first_volume.get_instance_rotation(); + const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[first_volume_idx].get_instance_rotation(), m_cache.volumes_data[i].get_instance_rotation()); + volume.set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2) + z_diff)); + } + else { + // extracts rotations from the composed transformation + Vec3d new_rotation = transformation_type.world() ? + Geometry::extract_euler_angles(Geometry::assemble_transform(Vec3d::Zero(), rotation) * m_cache.volumes_data[i].get_instance_rotation_matrix()) : + transformation_type.absolute() ? rotation : rotation + m_cache.volumes_data[i].get_instance_rotation(); + if (rot_axis_max == 2 && transformation_type.joint()) { + // Only allow rotation of multiple instances as a single rigid body when rotating around the Z axis. + const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), new_rotation); + volume.set_instance_offset(m_cache.dragging_center + Eigen::AngleAxisd(z_diff, Vec3d::UnitZ()) * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); + } + else if (!(m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center).isApprox(Vec3d::Zero())) + volume.set_instance_offset(m_cache.dragging_center + Geometry::assemble_transform(Vec3d::Zero(), new_rotation) * m_cache.volumes_data[i].get_instance_rotation_matrix().inverse() * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); + + volume.set_instance_rotation(new_rotation); + object_instance_first[volume.object_idx()] = i; + } + }; + + for (unsigned int i : m_list) { + GLVolume &v = *(*m_volumes)[i]; + if (is_single_full_instance()) + rotate_instance(v, i); + else if (is_single_volume() || is_single_modifier()) { + if (transformation_type.independent()) + v.set_volume_rotation(m_cache.volumes_data[i].get_volume_rotation() + rotation); + else { + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); + const Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); + v.set_volume_rotation(new_rotation); + } + } + else + { + if (m_mode == Instance) + rotate_instance(v, i); + else if (m_mode == Volume) { + // extracts rotations from the composed transformation + Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); + Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); + if (transformation_type.joint()) { + const Vec3d local_pivot = m_cache.volumes_data[i].get_instance_full_matrix().inverse() * m_cache.dragging_center; + const Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() - local_pivot); + v.set_volume_offset(local_pivot + offset); + } + v.set_volume_rotation(new_rotation); + } + } + } + } + + #if !DISABLE_INSTANCES_SYNCH + if (m_mode == Instance) + synchronize_unselected_instances((rot_axis_max == 2) ? SYNC_ROTATION_NONE : SYNC_ROTATION_GENERAL); + else if (m_mode == Volume) + synchronize_unselected_volumes(); + #endif // !DISABLE_INSTANCES_SYNCH + } + else { // it's the wipe tower that's selected and being rotated + GLVolume& volume = *((*m_volumes)[*m_list.begin()]); // the wipe tower is always alone in the selection + + // make sure the wipe tower rotates around its center, not origin + // we can assume that only Z rotation changes + const Vec3d center_local = volume.transformed_bounding_box().center() - volume.get_volume_offset(); + const Vec3d center_local_new = Eigen::AngleAxisd(rotation(2)-volume.get_volume_rotation()(2), Vec3d(0.0, 0.0, 1.0)) * center_local; + volume.set_volume_rotation(rotation); + volume.set_volume_offset(volume.get_volume_offset() + center_local - center_local_new); + } + + set_bounding_boxes_dirty(); + wxGetApp().plater()->canvas3D()->requires_check_outside_state(); +} + +void Selection::flattening_rotate(const Vec3d& normal) +{ + // We get the normal in untransformed coordinates. We must transform it using the instance matrix, find out + // how to rotate the instance so it faces downwards and do the rotation. All that for all selected instances. + // The function assumes that is_from_single_object() holds. + assert(Slic3r::is_approx(normal.norm(), 1.)); + + if (!m_valid) + return; + + for (unsigned int i : m_list) { + GLVolume& v = *(*m_volumes)[i]; + // Normal transformed from the object coordinate space to the world coordinate space. + const auto &voldata = m_cache.volumes_data[i]; + Vec3d tnormal = (Geometry::assemble_transform( + Vec3d::Zero(), voldata.get_instance_rotation(), + voldata.get_instance_scaling_factor().cwiseInverse(), voldata.get_instance_mirror()) * normal).normalized(); + // Additional rotation to align tnormal with the down vector in the world coordinate space. + auto extra_rotation = Eigen::Quaterniond().setFromTwoVectors(tnormal, - Vec3d::UnitZ()); + v.set_instance_rotation(Geometry::extract_euler_angles(extra_rotation.toRotationMatrix() * m_cache.volumes_data[i].get_instance_rotation_matrix())); + } + +#if !DISABLE_INSTANCES_SYNCH + // Apply the same transformation also to other instances, + // but respect their possibly diffrent z-rotation. + if (m_mode == Instance) + synchronize_unselected_instances(SYNC_ROTATION_GENERAL); +#endif // !DISABLE_INSTANCES_SYNCH + + this->set_bounding_boxes_dirty(); +} + +void Selection::scale(const Vec3d& scale, TransformationType transformation_type) +{ + if (!m_valid) + return; + + for (unsigned int i : m_list) { + GLVolume &v = *(*m_volumes)[i]; + if (is_single_full_instance()) { + if (transformation_type.relative()) { + Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale); + Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3); + // extracts scaling factors from the composed transformation + Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); + if (transformation_type.joint()) + v.set_instance_offset(m_cache.dragging_center + m * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); + + v.set_instance_scaling_factor(new_scale); + } + else { + if (transformation_type.world() && (std::abs(scale.x() - scale.y()) > EPSILON || std::abs(scale.x() - scale.z()) > EPSILON)) { + // Non-uniform scaling. Transform the scaling factors into the local coordinate system. + // This is only possible, if the instance rotation is mulitples of ninety degrees. + assert(Geometry::is_rotation_ninety_degrees(v.get_instance_rotation())); + v.set_instance_scaling_factor((v.get_instance_transformation().get_matrix(true, false, true, true).matrix().block<3, 3>(0, 0).transpose() * scale).cwiseAbs()); + } + else + v.set_instance_scaling_factor(scale); + } + } + else if (is_single_volume() || is_single_modifier()) + v.set_volume_scaling_factor(scale); + else { + Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale); + if (m_mode == Instance) { + Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3); + // extracts scaling factors from the composed transformation + Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); + if (transformation_type.joint()) + v.set_instance_offset(m_cache.dragging_center + m * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); + + v.set_instance_scaling_factor(new_scale); + } + else if (m_mode == Volume) { + Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_volume_scale_matrix()).matrix().block(0, 0, 3, 3); + // extracts scaling factors from the composed transformation + Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); + if (transformation_type.joint()) { + Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() + m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center); + v.set_volume_offset(m_cache.dragging_center - m_cache.volumes_data[i].get_instance_position() + offset); + } + v.set_volume_scaling_factor(new_scale); + } + } + } + +#if !DISABLE_INSTANCES_SYNCH + if (m_mode == Instance) + synchronize_unselected_instances(SYNC_ROTATION_NONE); + else if (m_mode == Volume) + synchronize_unselected_volumes(); +#endif // !DISABLE_INSTANCES_SYNCH + + ensure_on_bed(); + set_bounding_boxes_dirty(); + wxGetApp().plater()->canvas3D()->requires_check_outside_state(); +} + +void Selection::scale_to_fit_print_volume(const BuildVolume& volume) +{ + auto fit = [this](double s, Vec3d offset) { + if (s <= 0.0 || s == 1.0) + return; + + wxGetApp().plater()->take_snapshot(_L("Scale To Fit")); + + TransformationType type; + type.set_world(); + type.set_relative(); + type.set_joint(); + + // apply scale + setup_cache(); + scale(s * Vec3d::Ones(), type); + wxGetApp().plater()->canvas3D()->do_scale(""); // avoid storing another snapshot + + // center selection on print bed + setup_cache(); + offset.z() = -get_bounding_box().min.z(); + translate(offset); + wxGetApp().plater()->canvas3D()->do_move(""); // avoid storing another snapshot + + wxGetApp().obj_manipul()->set_dirty(); + }; + + auto fit_rectangle = [this, fit](const BuildVolume& volume) { + const BoundingBoxf3 print_volume = volume.bounding_volume(); + const Vec3d print_volume_size = print_volume.size(); + + // adds 1/100th of a mm on all sides to avoid false out of print volume detections due to floating-point roundings + const Vec3d box_size = get_bounding_box().size() + 0.02 * Vec3d::Ones(); + + const double sx = (box_size.x() != 0.0) ? print_volume_size.x() / box_size.x() : 0.0; + const double sy = (box_size.y() != 0.0) ? print_volume_size.y() / box_size.y() : 0.0; + const double sz = (box_size.z() != 0.0) ? print_volume_size.z() / box_size.z() : 0.0; + + if (sx != 0.0 && sy != 0.0 && sz != 0.0) + fit(std::min(sx, std::min(sy, sz)), print_volume.center() - get_bounding_box().center()); + }; + + auto fit_circle = [this, fit](const BuildVolume& volume) { + const Geometry::Circled& print_circle = volume.circle(); + double print_circle_radius = unscale(print_circle.radius); + + if (print_circle_radius == 0.0) + return; + + Points points; + double max_z = 0.0; + for (unsigned int i : m_list) { + const GLVolume& v = *(*m_volumes)[i]; + TriangleMesh hull_3d = *v.convex_hull(); + hull_3d.transform(v.world_matrix()); + max_z = std::max(max_z, hull_3d.bounding_box().size().z()); + const Polygon hull_2d = hull_3d.convex_hull(); + points.insert(points.end(), hull_2d.begin(), hull_2d.end()); + } + + if (points.empty()) + return; + + const Geometry::Circled circle = Geometry::smallest_enclosing_circle_welzl(points); + // adds 1/100th of a mm on all sides to avoid false out of print volume detections due to floating-point roundings + const double circle_radius = unscale(circle.radius) + 0.01; + + if (circle_radius == 0.0 || max_z == 0.0) + return; + + const double s = std::min(print_circle_radius / circle_radius, volume.max_print_height() / max_z); + const Vec3d sel_center = get_bounding_box().center(); + const Vec3d offset = s * (Vec3d(unscale(circle.center.x()), unscale(circle.center.y()), 0.5 * max_z) - sel_center); + const Vec3d print_center = { unscale(print_circle.center.x()), unscale(print_circle.center.y()), 0.5 * volume.max_print_height() }; + fit(s, print_center - (sel_center + offset)); + }; + + if (is_empty() || m_mode == Volume) + return; + + switch (volume.type()) + { + case BuildVolume::Type::Rectangle: { fit_rectangle(volume); break; } + case BuildVolume::Type::Circle: { fit_circle(volume); break; } + default: { break; } + } +} + +void Selection::mirror(Axis axis) +{ + if (!m_valid) + return; + + for (unsigned int i : m_list) { + GLVolume& v = *(*m_volumes)[i]; + if (is_single_full_instance()) + v.set_instance_mirror(axis, -v.get_instance_mirror(axis)); + else if (m_mode == Volume) + v.set_volume_mirror(axis, -v.get_volume_mirror(axis)); + } + +#if !DISABLE_INSTANCES_SYNCH + if (m_mode == Instance) + synchronize_unselected_instances(SYNC_ROTATION_NONE); + else if (m_mode == Volume) + synchronize_unselected_volumes(); +#endif // !DISABLE_INSTANCES_SYNCH + + set_bounding_boxes_dirty(); +} + +void Selection::translate(unsigned int object_idx, const Vec3d& displacement) +{ + if (!m_valid) + return; + + for (unsigned int i : m_list) { + GLVolume& v = *(*m_volumes)[i]; + if (v.object_idx() == (int)object_idx) + v.set_instance_offset(v.get_instance_offset() + displacement); + } + + std::set done; // prevent processing volumes twice + done.insert(m_list.begin(), m_list.end()); + + for (unsigned int i : m_list) { + if (done.size() == m_volumes->size()) + break; + +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + if ((*m_volumes)[i]->is_wipe_tower) + continue; + + int object_idx = (*m_volumes)[i]->object_idx(); +#else + int object_idx = (*m_volumes)[i]->object_idx(); + if (object_idx >= 1000) + continue; +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + + // Process unselected volumes of the object. + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { + if (done.size() == m_volumes->size()) + break; + + if (done.find(j) != done.end()) + continue; + + GLVolume& v = *(*m_volumes)[j]; + if (v.object_idx() != object_idx) + continue; + + v.set_instance_offset(v.get_instance_offset() + displacement); + done.insert(j); + } + } + + this->set_bounding_boxes_dirty(); +} + +void Selection::translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement) +{ + if (!m_valid) + return; + + for (unsigned int i : m_list) { + GLVolume& v = *(*m_volumes)[i]; + if (v.object_idx() == (int)object_idx && v.instance_idx() == (int)instance_idx) + v.set_instance_offset(v.get_instance_offset() + displacement); + } + + std::set done; // prevent processing volumes twice + done.insert(m_list.begin(), m_list.end()); + + for (unsigned int i : m_list) { + if (done.size() == m_volumes->size()) + break; + +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + if ((*m_volumes)[i]->is_wipe_tower) + continue; + + int object_idx = (*m_volumes)[i]->object_idx(); +#else + int object_idx = (*m_volumes)[i]->object_idx(); + if (object_idx >= 1000) + continue; +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + + // Process unselected volumes of the object. + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { + if (done.size() == m_volumes->size()) + break; + + if (done.find(j) != done.end()) + continue; + + GLVolume& v = *(*m_volumes)[j]; + if (v.object_idx() != object_idx || v.instance_idx() != (int)instance_idx) + continue; + + v.set_instance_offset(v.get_instance_offset() + displacement); + done.insert(j); + } + } + + this->set_bounding_boxes_dirty(); +} + +void Selection::erase() +{ + if (!m_valid) + return; + + if (is_single_full_object()) + wxGetApp().obj_list()->delete_from_model_and_list(ItemType::itObject, get_object_idx(), 0); + else if (is_multiple_full_object()) { + std::vector items; + items.reserve(m_cache.content.size()); + for (ObjectIdxsToInstanceIdxsMap::iterator it = m_cache.content.begin(); it != m_cache.content.end(); ++it) { + items.emplace_back(ItemType::itObject, it->first, 0); + } + wxGetApp().obj_list()->delete_from_model_and_list(items); + } + else if (is_multiple_full_instance()) { + std::set> instances_idxs; + for (ObjectIdxsToInstanceIdxsMap::iterator obj_it = m_cache.content.begin(); obj_it != m_cache.content.end(); ++obj_it) { + for (InstanceIdxsList::reverse_iterator inst_it = obj_it->second.rbegin(); inst_it != obj_it->second.rend(); ++inst_it) { + instances_idxs.insert(std::make_pair(obj_it->first, *inst_it)); + } + } + + std::vector items; + items.reserve(instances_idxs.size()); + for (const std::pair& i : instances_idxs) { + items.emplace_back(ItemType::itInstance, i.first, i.second); + } + wxGetApp().obj_list()->delete_from_model_and_list(items); + } + else if (is_single_full_instance()) + wxGetApp().obj_list()->delete_from_model_and_list(ItemType::itInstance, get_object_idx(), get_instance_idx()); + else if (is_mixed()) { + std::set items_set; + std::map volumes_in_obj; + + for (auto i : m_list) { + const auto gl_vol = (*m_volumes)[i]; + const auto glv_obj_idx = gl_vol->object_idx(); + const auto model_object = m_model->objects[glv_obj_idx]; + + if (model_object->instances.size() == 1) { + if (model_object->volumes.size() == 1) + items_set.insert(ItemForDelete(ItemType::itObject, glv_obj_idx, -1)); + else { + items_set.insert(ItemForDelete(ItemType::itVolume, glv_obj_idx, gl_vol->volume_idx())); + int idx = (volumes_in_obj.find(glv_obj_idx) == volumes_in_obj.end()) ? 0 : volumes_in_obj.at(glv_obj_idx); + volumes_in_obj[glv_obj_idx] = ++idx; + } + continue; + } + + const auto glv_ins_idx = gl_vol->instance_idx(); + + for (auto obj_ins : m_cache.content) { + if (obj_ins.first == glv_obj_idx) { + if (obj_ins.second.find(glv_ins_idx) != obj_ins.second.end()) { + if (obj_ins.second.size() == model_object->instances.size()) + items_set.insert(ItemForDelete(ItemType::itObject, glv_obj_idx, -1)); + else + items_set.insert(ItemForDelete(ItemType::itInstance, glv_obj_idx, glv_ins_idx)); + + break; + } + } + } + } + + std::vector items; + items.reserve(items_set.size()); + for (const ItemForDelete& i : items_set) { + if (i.type == ItemType::itVolume) { + const int vol_in_obj_cnt = volumes_in_obj.find(i.obj_idx) == volumes_in_obj.end() ? 0 : volumes_in_obj.at(i.obj_idx); + if (vol_in_obj_cnt == (int)m_model->objects[i.obj_idx]->volumes.size()) { + if (i.sub_obj_idx == vol_in_obj_cnt - 1) + items.emplace_back(ItemType::itObject, i.obj_idx, 0); + continue; + } + } + items.emplace_back(i.type, i.obj_idx, i.sub_obj_idx); + } + + wxGetApp().obj_list()->delete_from_model_and_list(items); + } + else { + std::set> volumes_idxs; + for (unsigned int i : m_list) { + const GLVolume* v = (*m_volumes)[i]; + // Only remove volumes associated with ModelVolumes from the object list. + // Temporary meshes (SLA supports or pads) are not managed by the object list. + if (v->volume_idx() >= 0) + volumes_idxs.insert(std::make_pair(v->object_idx(), v->volume_idx())); + } + + std::vector items; + items.reserve(volumes_idxs.size()); + for (const std::pair& v : volumes_idxs) { + items.emplace_back(ItemType::itVolume, v.first, v.second); + } + + wxGetApp().obj_list()->delete_from_model_and_list(items); + ensure_not_below_bed(); + } +} + +void Selection::render(float scale_factor) +{ + if (!m_valid || is_empty()) + return; + + m_scale_factor = scale_factor; + // render cumulative bounding box of selected volumes +#if ENABLE_LEGACY_OPENGL_REMOVAL + render_bounding_box(get_bounding_box(), ColorRGB::WHITE()); +#else + render_selected_volumes(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + render_synchronized_volumes(); +} + +#if ENABLE_RENDER_SELECTION_CENTER +void Selection::render_center(bool gizmo_is_dragging) +{ + if (!m_valid || is_empty()) + return; + +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; + + shader->start_using(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + const Vec3d center = gizmo_is_dragging ? m_cache.dragging_center : get_bounding_box().center(); + + glsafe(::glDisable(GL_DEPTH_TEST)); + +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::assemble_transform(center); + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#else + glsafe(::glPushMatrix()); + glsafe(::glTranslated(center.x(), center.y(), center.z())); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_vbo_sphere.set_color(ColorRGBA::WHITE()); +#else + m_vbo_sphere.set_color(-1, ColorRGBA::WHITE()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + m_vbo_sphere.render(); + +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + +#if ENABLE_LEGACY_OPENGL_REMOVAL + shader->stop_using(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} +#endif // ENABLE_RENDER_SELECTION_CENTER + +void Selection::render_sidebar_hints(const std::string& sidebar_field) +{ + if (sidebar_field.empty()) + return; + +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader(boost::starts_with(sidebar_field, "layer") ? "flat" : "gouraud_light"); + if (shader == nullptr) + return; + + shader->start_using(); +#else + GLShaderProgram* shader = nullptr; + + if (!boost::starts_with(sidebar_field, "layer")) { + shader = wxGetApp().get_shader("gouraud_light"); + if (shader == nullptr) + return; + + shader->start_using(); + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); + } +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + glsafe(::glEnable(GL_DEPTH_TEST)); + +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d base_matrix = Geometry::assemble_transform(get_bounding_box().center()); + Transform3d orient_matrix = Transform3d::Identity(); +#else + glsafe(::glPushMatrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + if (!boost::starts_with(sidebar_field, "layer")) { +#if ENABLE_GL_SHADERS_ATTRIBUTES + shader->set_uniform("emission_factor", 0.05f); +#else + const Vec3d& center = get_bounding_box().center(); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + if (is_single_full_instance() && !wxGetApp().obj_manipul()->get_world_coordinates()) { +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glTranslated(center.x(), center.y(), center.z())); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + if (!boost::starts_with(sidebar_field, "position")) { +#if !ENABLE_GL_SHADERS_ATTRIBUTES + Transform3d orient_matrix = Transform3d::Identity(); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + if (boost::starts_with(sidebar_field, "scale")) + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); + else if (boost::starts_with(sidebar_field, "rotation")) { + if (boost::ends_with(sidebar_field, "x")) + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); + else if (boost::ends_with(sidebar_field, "y")) { + const Vec3d& rotation = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation(); + if (rotation.x() == 0.0) + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); + else + orient_matrix.rotate(Eigen::AngleAxisd(rotation.z(), Vec3d::UnitZ())); + } + } +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glMultMatrixd(orient_matrix.data())); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + } + } + else if (is_single_volume() || is_single_modifier()) { +#if ENABLE_GL_SHADERS_ATTRIBUTES + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); +#else + glsafe(::glTranslated(center.x(), center.y(), center.z())); + Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + if (!boost::starts_with(sidebar_field, "position")) + orient_matrix = orient_matrix * (*m_volumes)[*m_list.begin()]->get_volume_transformation().get_matrix(true, false, true, true); + +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glMultMatrixd(orient_matrix.data())); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + } + else { +#if ENABLE_GL_SHADERS_ATTRIBUTES + if (requires_local_axes()) + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); +#else + glsafe(::glTranslated(center.x(), center.y(), center.z())); + if (requires_local_axes()) { + const Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); + glsafe(::glMultMatrixd(orient_matrix.data())); + } +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + } + } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + if (!boost::starts_with(sidebar_field, "layer")) + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + +#if ENABLE_GL_SHADERS_ATTRIBUTES + if (boost::starts_with(sidebar_field, "position")) + render_sidebar_position_hints(sidebar_field, *shader, base_matrix * orient_matrix); + else if (boost::starts_with(sidebar_field, "rotation")) + render_sidebar_rotation_hints(sidebar_field, *shader, base_matrix * orient_matrix); + else if (boost::starts_with(sidebar_field, "scale") || boost::starts_with(sidebar_field, "size")) + render_sidebar_scale_hints(sidebar_field, *shader, base_matrix * orient_matrix); + else if (boost::starts_with(sidebar_field, "layer")) + render_sidebar_layers_hints(sidebar_field, *shader); +#else + if (boost::starts_with(sidebar_field, "position")) + render_sidebar_position_hints(sidebar_field); + else if (boost::starts_with(sidebar_field, "rotation")) + render_sidebar_rotation_hints(sidebar_field); + else if (boost::starts_with(sidebar_field, "scale") || boost::starts_with(sidebar_field, "size")) + render_sidebar_scale_hints(sidebar_field); + else if (boost::starts_with(sidebar_field, "layer")) + render_sidebar_layers_hints(sidebar_field); + + glsafe(::glPopMatrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + +#if !ENABLE_LEGACY_OPENGL_REMOVAL + if (!boost::starts_with(sidebar_field, "layer")) +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + shader->stop_using(); +} + +bool Selection::requires_local_axes() const +{ + return m_mode == Volume && is_from_single_instance(); +} + +void Selection::copy_to_clipboard() +{ + if (!m_valid) + return; + + m_clipboard.reset(); + + for (const ObjectIdxsToInstanceIdxsMap::value_type& object : m_cache.content) { + ModelObject* src_object = m_model->objects[object.first]; + ModelObject* dst_object = m_clipboard.add_object(); + dst_object->name = src_object->name; + dst_object->input_file = src_object->input_file; + dst_object->config.assign_config(src_object->config); + dst_object->sla_support_points = src_object->sla_support_points; + dst_object->sla_points_status = src_object->sla_points_status; + dst_object->sla_drain_holes = src_object->sla_drain_holes; + dst_object->layer_config_ranges = src_object->layer_config_ranges; // #ys_FIXME_experiment + dst_object->layer_height_profile.assign(src_object->layer_height_profile); + dst_object->origin_translation = src_object->origin_translation; + + for (int i : object.second) { + dst_object->add_instance(*src_object->instances[i]); + } + + for (unsigned int i : m_list) { + // Copy the ModelVolumes only for the selected GLVolumes of the 1st selected instance. + const GLVolume* volume = (*m_volumes)[i]; + if (volume->object_idx() == object.first && volume->instance_idx() == *object.second.begin()) { + int volume_idx = volume->volume_idx(); + if (0 <= volume_idx && volume_idx < (int)src_object->volumes.size()) { + ModelVolume* src_volume = src_object->volumes[volume_idx]; + ModelVolume* dst_volume = dst_object->add_volume(*src_volume); + dst_volume->set_new_unique_id(); + } + else + assert(false); + } + } + } + + m_clipboard.set_mode(m_mode); +} + +void Selection::paste_from_clipboard() +{ + if (!m_valid || m_clipboard.is_empty()) + return; + + switch (m_clipboard.get_mode()) + { + case Volume: + { + if (is_from_single_instance()) + paste_volumes_from_clipboard(); + + break; + } + case Instance: + { + if (m_mode == Instance) + paste_objects_from_clipboard(); + + break; + } + } +} + +std::vector Selection::get_volume_idxs_from_object(unsigned int object_idx) const +{ + std::vector idxs; + + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) + { + if ((*m_volumes)[i]->object_idx() == (int)object_idx) + idxs.push_back(i); + } + + return idxs; +} + +std::vector Selection::get_volume_idxs_from_instance(unsigned int object_idx, unsigned int instance_idx) const +{ + std::vector idxs; + + 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->instance_idx() == (int)instance_idx)) + idxs.push_back(i); + } + + return idxs; +} + +std::vector Selection::get_volume_idxs_from_volume(unsigned int object_idx, unsigned int instance_idx, unsigned int volume_idx) const +{ + std::vector idxs; + + 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)) + idxs.push_back(i); + } + } + + return idxs; +} + +std::vector Selection::get_missing_volume_idxs_from(const std::vector& volume_idxs) const +{ + std::vector idxs; + + for (unsigned int i : m_list) + { + std::vector::const_iterator it = std::find(volume_idxs.begin(), volume_idxs.end(), i); + if (it == volume_idxs.end()) + idxs.push_back(i); + } + + return idxs; +} + +std::vector Selection::get_unselected_volume_idxs_from(const std::vector& volume_idxs) const +{ + std::vector idxs; + + for (unsigned int i : volume_idxs) + { + if (m_list.find(i) == m_list.end()) + idxs.push_back(i); + } + + return idxs; +} + +void Selection::update_valid() +{ + m_valid = (m_volumes != nullptr) && (m_model != nullptr); +} + +void Selection::update_type() +{ + m_cache.content.clear(); + m_type = Mixed; + + for (unsigned int i : m_list) + { + const GLVolume* volume = (*m_volumes)[i]; + int obj_idx = volume->object_idx(); + int inst_idx = volume->instance_idx(); + ObjectIdxsToInstanceIdxsMap::iterator obj_it = m_cache.content.find(obj_idx); + if (obj_it == m_cache.content.end()) + obj_it = m_cache.content.insert(ObjectIdxsToInstanceIdxsMap::value_type(obj_idx, InstanceIdxsList())).first; + + obj_it->second.insert(inst_idx); + } + + bool requires_disable = false; + + if (!m_valid) + m_type = Invalid; + else + { + if (m_list.empty()) + m_type = Empty; + else if (m_list.size() == 1) + { + const GLVolume* first = (*m_volumes)[*m_list.begin()]; + if (first->is_wipe_tower) + m_type = WipeTower; + else if (first->is_modifier) + { + m_type = SingleModifier; + requires_disable = true; + } + else + { + const ModelObject* model_object = m_model->objects[first->object_idx()]; + unsigned int volumes_count = (unsigned int)model_object->volumes.size(); + unsigned int instances_count = (unsigned int)model_object->instances.size(); + if (volumes_count * instances_count == 1) + { + m_type = SingleFullObject; + // ensures the correct mode is selected + m_mode = Instance; + } + else if (volumes_count == 1) // instances_count > 1 + { + m_type = SingleFullInstance; + // ensures the correct mode is selected + m_mode = Instance; + } + else + { + m_type = SingleVolume; + requires_disable = true; + } + } + } + else + { + unsigned int sla_volumes_count = 0; + // Note: sla_volumes_count is a count of the selected sla_volumes per object instead of per instance, like a model_volumes_count is + for (unsigned int i : m_list) { + if ((*m_volumes)[i]->volume_idx() < 0) + ++sla_volumes_count; + } + + if (m_cache.content.size() == 1) // single object + { + const ModelObject* model_object = m_model->objects[m_cache.content.begin()->first]; + unsigned int model_volumes_count = (unsigned int)model_object->volumes.size(); + + unsigned int instances_count = (unsigned int)model_object->instances.size(); + unsigned int selected_instances_count = (unsigned int)m_cache.content.begin()->second.size(); + if (model_volumes_count * instances_count + sla_volumes_count == (unsigned int)m_list.size()) + { + m_type = SingleFullObject; + // ensures the correct mode is selected + m_mode = Instance; + } + else if (selected_instances_count == 1) + { + if (model_volumes_count + sla_volumes_count == (unsigned int)m_list.size()) + { + m_type = SingleFullInstance; + // ensures the correct mode is selected + m_mode = Instance; + } + else + { + unsigned int modifiers_count = 0; + for (unsigned int i : m_list) + { + if ((*m_volumes)[i]->is_modifier) + ++modifiers_count; + } + + if (modifiers_count == 0) + m_type = MultipleVolume; + else if (modifiers_count == (unsigned int)m_list.size()) + m_type = MultipleModifier; + + requires_disable = true; + } + } + else if ((selected_instances_count > 1) && (selected_instances_count * model_volumes_count + sla_volumes_count == (unsigned int)m_list.size())) + { + m_type = MultipleFullInstance; + // ensures the correct mode is selected + m_mode = Instance; + } + } + else + { + unsigned int sels_cntr = 0; + for (ObjectIdxsToInstanceIdxsMap::iterator it = m_cache.content.begin(); it != m_cache.content.end(); ++it) + { + const ModelObject* model_object = m_model->objects[it->first]; + unsigned int volumes_count = (unsigned int)model_object->volumes.size(); + unsigned int instances_count = (unsigned int)model_object->instances.size(); + sels_cntr += volumes_count * instances_count; + } + if (sels_cntr + sla_volumes_count == (unsigned int)m_list.size()) + { + m_type = MultipleFullObject; + // ensures the correct mode is selected + m_mode = Instance; + } + } + } + } + + int object_idx = get_object_idx(); + int instance_idx = get_instance_idx(); + for (GLVolume* v : *m_volumes) + { + v->disabled = requires_disable ? (v->object_idx() != object_idx) || (v->instance_idx() != instance_idx) : false; + } + +#if ENABLE_SELECTION_DEBUG_OUTPUT + std::cout << "Selection: "; + std::cout << "mode: "; + switch (m_mode) + { + case Volume: + { + std::cout << "Volume"; + break; + } + case Instance: + { + std::cout << "Instance"; + break; + } + } + + std::cout << " - type: "; + + switch (m_type) + { + case Invalid: + { + std::cout << "Invalid" << std::endl; + break; + } + case Empty: + { + std::cout << "Empty" << std::endl; + break; + } + case WipeTower: + { + std::cout << "WipeTower" << std::endl; + break; + } + case SingleModifier: + { + std::cout << "SingleModifier" << std::endl; + break; + } + case MultipleModifier: + { + std::cout << "MultipleModifier" << std::endl; + break; + } + case SingleVolume: + { + std::cout << "SingleVolume" << std::endl; + break; + } + case MultipleVolume: + { + std::cout << "MultipleVolume" << std::endl; + break; + } + case SingleFullObject: + { + std::cout << "SingleFullObject" << std::endl; + break; + } + case MultipleFullObject: + { + std::cout << "MultipleFullObject" << std::endl; + break; + } + case SingleFullInstance: + { + std::cout << "SingleFullInstance" << std::endl; + break; + } + case MultipleFullInstance: + { + std::cout << "MultipleFullInstance" << std::endl; + break; + } + case Mixed: + { + std::cout << "Mixed" << std::endl; + break; + } + } +#endif // ENABLE_SELECTION_DEBUG_OUTPUT +} + +void Selection::set_caches() +{ + m_cache.volumes_data.clear(); + m_cache.sinking_volumes.clear(); + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { + const GLVolume& v = *(*m_volumes)[i]; + m_cache.volumes_data.emplace(i, VolumeCache(v.get_volume_transformation(), v.get_instance_transformation())); + if (v.is_sinking()) + m_cache.sinking_volumes.push_back(i); + } + m_cache.dragging_center = get_bounding_box().center(); +} + +void Selection::do_add_volume(unsigned int volume_idx) +{ + m_list.insert(volume_idx); + GLVolume* v = (*m_volumes)[volume_idx]; + v->selected = true; + if (v->hover == GLVolume::HS_Select || v->hover == GLVolume::HS_Deselect) + v->hover = GLVolume::HS_Hover; +} + +void Selection::do_add_volumes(const std::vector& volume_idxs) +{ + for (unsigned int i : volume_idxs) + { + if (i < (unsigned int)m_volumes->size()) + do_add_volume(i); + } +} + +void Selection::do_remove_volume(unsigned int volume_idx) +{ + IndicesList::iterator v_it = m_list.find(volume_idx); + if (v_it == m_list.end()) + return; + + m_list.erase(v_it); + + (*m_volumes)[volume_idx]->selected = false; +} + +void Selection::do_remove_instance(unsigned int object_idx, unsigned int instance_idx) +{ + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { + GLVolume* v = (*m_volumes)[i]; + if (v->object_idx() == (int)object_idx && v->instance_idx() == (int)instance_idx) + do_remove_volume(i); + } +} + +void Selection::do_remove_object(unsigned int object_idx) +{ + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { + GLVolume* v = (*m_volumes)[i]; + if (v->object_idx() == (int)object_idx) + do_remove_volume(i); + } +} + +#if !ENABLE_LEGACY_OPENGL_REMOVAL +void Selection::render_selected_volumes() const +{ + float color[3] = { 1.0f, 1.0f, 1.0f }; + render_bounding_box(get_bounding_box(), color); +} +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + +void Selection::render_synchronized_volumes() +{ + if (m_mode == Instance) + return; + +#if !ENABLE_LEGACY_OPENGL_REMOVAL + float color[3] = { 1.0f, 1.0f, 0.0f }; +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + + for (unsigned int i : m_list) { + const GLVolume& volume = *(*m_volumes)[i]; + int object_idx = volume.object_idx(); + int volume_idx = volume.volume_idx(); + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { + if (i == j) + continue; + + const GLVolume& v = *(*m_volumes)[j]; + if (v.object_idx() != object_idx || v.volume_idx() != volume_idx) + continue; + +#if ENABLE_LEGACY_OPENGL_REMOVAL + render_bounding_box(v.transformed_convex_hull_bounding_box(), ColorRGB::YELLOW()); +#else + render_bounding_box(v.transformed_convex_hull_bounding_box(), color); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + } +} + +#if ENABLE_LEGACY_OPENGL_REMOVAL +void Selection::render_bounding_box(const BoundingBoxf3& box, const ColorRGB& color) +{ +#else +void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) const +{ + if (color == nullptr) + return; + + const Vec3f b_min = box.min.cast(); + const Vec3f b_max = box.max.cast(); + const Vec3f size = 0.2f * box.size().cast(); + + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glColor3fv(color)); + glsafe(::glLineWidth(2.0f * m_scale_factor)); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + +#if ENABLE_LEGACY_OPENGL_REMOVAL + const BoundingBoxf3& curr_box = m_box.get_bounding_box(); + if (!m_box.is_initialized() || !is_approx(box.min, curr_box.min) || !is_approx(box.max, curr_box.max)) { + m_box.reset(); + + const Vec3f b_min = box.min.cast(); + const Vec3f b_max = box.max.cast(); + const Vec3f size = 0.2f * box.size().cast(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(48); + init_data.reserve_indices(48); + + // vertices + init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x() + size.x(), b_min.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y() + size.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z() + size.z())); + + init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x() - size.x(), b_min.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_min.y() + size.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_min.z() + size.z())); + + init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x() - size.x(), b_max.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_max.y() - size.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_min.z() + size.z())); + + init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x() + size.x(), b_max.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_max.y() - size.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_min.z() + size.z())); + + init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x() + size.x(), b_min.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y() + size.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_max.z() - size.z())); + + init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x() - size.x(), b_min.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_min.y() + size.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_max.z() - size.z())); + + init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x() - size.x(), b_max.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_max.y() - size.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_max.z() - size.z())); + + init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x() + size.x(), b_max.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_max.y() - size.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_max.z() - size.z())); + + // indices + for (unsigned int i = 0; i < 48; ++i) { + init_data.add_index(i); + } + + m_box.init_from(std::move(init_data)); + } + + glsafe(::glEnable(GL_DEPTH_TEST)); + + glsafe(::glLineWidth(2.0f * m_scale_factor)); + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; + + shader->start_using(); +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_box.set_color(to_rgba(color)); + m_box.render(); + shader->stop_using(); +#else + ::glBegin(GL_LINES); + + ::glVertex3f(b_min(0), b_min(1), b_min(2)); ::glVertex3f(b_min(0) + size(0), b_min(1), b_min(2)); + ::glVertex3f(b_min(0), b_min(1), b_min(2)); ::glVertex3f(b_min(0), b_min(1) + size(1), b_min(2)); + ::glVertex3f(b_min(0), b_min(1), b_min(2)); ::glVertex3f(b_min(0), b_min(1), b_min(2) + size(2)); + + ::glVertex3f(b_max(0), b_min(1), b_min(2)); ::glVertex3f(b_max(0) - size(0), b_min(1), b_min(2)); + ::glVertex3f(b_max(0), b_min(1), b_min(2)); ::glVertex3f(b_max(0), b_min(1) + size(1), b_min(2)); + ::glVertex3f(b_max(0), b_min(1), b_min(2)); ::glVertex3f(b_max(0), b_min(1), b_min(2) + size(2)); + + ::glVertex3f(b_max(0), b_max(1), b_min(2)); ::glVertex3f(b_max(0) - size(0), b_max(1), b_min(2)); + ::glVertex3f(b_max(0), b_max(1), b_min(2)); ::glVertex3f(b_max(0), b_max(1) - size(1), b_min(2)); + ::glVertex3f(b_max(0), b_max(1), b_min(2)); ::glVertex3f(b_max(0), b_max(1), b_min(2) + size(2)); + + ::glVertex3f(b_min(0), b_max(1), b_min(2)); ::glVertex3f(b_min(0) + size(0), b_max(1), b_min(2)); + ::glVertex3f(b_min(0), b_max(1), b_min(2)); ::glVertex3f(b_min(0), b_max(1) - size(1), b_min(2)); + ::glVertex3f(b_min(0), b_max(1), b_min(2)); ::glVertex3f(b_min(0), b_max(1), b_min(2) + size(2)); + + ::glVertex3f(b_min(0), b_min(1), b_max(2)); ::glVertex3f(b_min(0) + size(0), b_min(1), b_max(2)); + ::glVertex3f(b_min(0), b_min(1), b_max(2)); ::glVertex3f(b_min(0), b_min(1) + size(1), b_max(2)); + ::glVertex3f(b_min(0), b_min(1), b_max(2)); ::glVertex3f(b_min(0), b_min(1), b_max(2) - size(2)); + + ::glVertex3f(b_max(0), b_min(1), b_max(2)); ::glVertex3f(b_max(0) - size(0), b_min(1), b_max(2)); + ::glVertex3f(b_max(0), b_min(1), b_max(2)); ::glVertex3f(b_max(0), b_min(1) + size(1), b_max(2)); + ::glVertex3f(b_max(0), b_min(1), b_max(2)); ::glVertex3f(b_max(0), b_min(1), b_max(2) - size(2)); + + ::glVertex3f(b_max(0), b_max(1), b_max(2)); ::glVertex3f(b_max(0) - size(0), b_max(1), b_max(2)); + ::glVertex3f(b_max(0), b_max(1), b_max(2)); ::glVertex3f(b_max(0), b_max(1) - size(1), b_max(2)); + ::glVertex3f(b_max(0), b_max(1), b_max(2)); ::glVertex3f(b_max(0), b_max(1), b_max(2) - size(2)); + + ::glVertex3f(b_min(0), b_max(1), b_max(2)); ::glVertex3f(b_min(0) + size(0), b_max(1), b_max(2)); + ::glVertex3f(b_min(0), b_max(1), b_max(2)); ::glVertex3f(b_min(0), b_max(1) - size(1), b_max(2)); + ::glVertex3f(b_min(0), b_max(1), b_max(2)); ::glVertex3f(b_min(0), b_max(1), b_max(2) - size(2)); + + glsafe(::glEnd()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} + +static ColorRGBA get_color(Axis axis) +{ + return AXES_COLOR[axis]; +} + +#if ENABLE_GL_SHADERS_ATTRIBUTES +void Selection::render_sidebar_position_hints(const std::string& sidebar_field, GLShaderProgram& shader, const Transform3d& matrix) +#else +void Selection::render_sidebar_position_hints(const std::string& sidebar_field) +#endif // ENABLE_GL_SHADERS_ATTRIBUTES +{ +#if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d view_matrix = camera.get_view_matrix() * matrix; + shader.set_uniform("projection_matrix", camera.get_projection_matrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + if (boost::ends_with(sidebar_field, "x")) { +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d view_model_matrix = view_matrix * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitZ()); + shader.set_uniform("view_model_matrix", view_model_matrix); + shader.set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); +#else + glsafe(::glRotated(-90.0, 0.0, 0.0, 1.0)); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_arrow.set_color(get_color(X)); + m_arrow.render(); + } + else if (boost::ends_with(sidebar_field, "y")) { +#if ENABLE_GL_SHADERS_ATTRIBUTES + shader.set_uniform("view_model_matrix", view_matrix); + shader.set_uniform("normal_matrix", (Matrix3d)view_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_arrow.set_color(get_color(Y)); + m_arrow.render(); + } + else if (boost::ends_with(sidebar_field, "z")) { +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d view_model_matrix = view_matrix * Geometry::assemble_transform(Vec3d::Zero(), 0.5 * PI * Vec3d::UnitX()); + shader.set_uniform("view_model_matrix", view_model_matrix); + shader.set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); +#else + glsafe(::glRotated(90.0, 1.0, 0.0, 0.0)); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_arrow.set_color(get_color(Z)); + m_arrow.render(); + } +#else + if (boost::ends_with(sidebar_field, "x")) { + glsafe(::glRotated(-90.0, 0.0, 0.0, 1.0)); + m_arrow.set_color(-1, get_color(X)); + m_arrow.render(); + } + else if (boost::ends_with(sidebar_field, "y")) { + m_arrow.set_color(-1, get_color(Y)); + m_arrow.render(); + } + else if (boost::ends_with(sidebar_field, "z")) { + glsafe(::glRotated(90.0, 1.0, 0.0, 0.0)); + m_arrow.set_color(-1, get_color(Z)); + m_arrow.render(); + } +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} + +#if ENABLE_GL_SHADERS_ATTRIBUTES +void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field, GLShaderProgram& shader, const Transform3d& matrix) +#else +void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field) +#endif // ENABLE_GL_SHADERS_ATTRIBUTES +{ +#if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_GL_SHADERS_ATTRIBUTES + auto render_sidebar_rotation_hint = [this](GLShaderProgram& shader, const Transform3d& matrix) { + Transform3d view_model_matrix = matrix; + shader.set_uniform("view_model_matrix", view_model_matrix); + shader.set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); + m_curved_arrow.render(); + view_model_matrix = matrix * Geometry::assemble_transform(Vec3d::Zero(), PI * Vec3d::UnitZ()); + shader.set_uniform("view_model_matrix", view_model_matrix); + shader.set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); + m_curved_arrow.render(); + }; + + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d view_matrix = camera.get_view_matrix() * matrix; + shader.set_uniform("projection_matrix", camera.get_projection_matrix()); +#else + auto render_sidebar_rotation_hint = [this]() { + m_curved_arrow.render(); + glsafe(::glRotated(180.0, 0.0, 0.0, 1.0)); + m_curved_arrow.render(); + }; +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + if (boost::ends_with(sidebar_field, "x")) { +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glRotated(90.0, 0.0, 1.0, 0.0)); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + m_curved_arrow.set_color(get_color(X)); +#if ENABLE_GL_SHADERS_ATTRIBUTES + render_sidebar_rotation_hint(shader, view_matrix * Geometry::assemble_transform(Vec3d::Zero(), 0.5 * PI * Vec3d::UnitY())); +#else + render_sidebar_rotation_hint(); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + } + else if (boost::ends_with(sidebar_field, "y")) { +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glRotated(-90.0, 1.0, 0.0, 0.0)); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + m_curved_arrow.set_color(get_color(Y)); +#if ENABLE_GL_SHADERS_ATTRIBUTES + render_sidebar_rotation_hint(shader, view_matrix * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitX())); +#else + render_sidebar_rotation_hint(); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + } + else if (boost::ends_with(sidebar_field, "z")) { + m_curved_arrow.set_color(get_color(Z)); +#if ENABLE_GL_SHADERS_ATTRIBUTES + render_sidebar_rotation_hint(shader, view_matrix); +#else + render_sidebar_rotation_hint(); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + } +#else + auto render_sidebar_rotation_hint = [this]() { + m_curved_arrow.render(); + glsafe(::glRotated(180.0, 0.0, 0.0, 1.0)); + m_curved_arrow.render(); + }; + + if (boost::ends_with(sidebar_field, "x")) { + glsafe(::glRotated(90.0, 0.0, 1.0, 0.0)); + m_curved_arrow.set_color(-1, get_color(X)); + render_sidebar_rotation_hint(); + } + else if (boost::ends_with(sidebar_field, "y")) { + glsafe(::glRotated(-90.0, 1.0, 0.0, 0.0)); + m_curved_arrow.set_color(-1, get_color(Y)); + render_sidebar_rotation_hint(); + } + else if (boost::ends_with(sidebar_field, "z")) { + m_curved_arrow.set_color(-1, get_color(Z)); + render_sidebar_rotation_hint(); + } +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} + +#if ENABLE_GL_SHADERS_ATTRIBUTES +void Selection::render_sidebar_scale_hints(const std::string& sidebar_field, GLShaderProgram& shader, const Transform3d& matrix) +#else +void Selection::render_sidebar_scale_hints(const std::string& sidebar_field) +#endif // ENABLE_GL_SHADERS_ATTRIBUTES +{ + const bool uniform_scale = requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling(); + +#if ENABLE_GL_SHADERS_ATTRIBUTES + auto render_sidebar_scale_hint = [this, uniform_scale](Axis axis, GLShaderProgram& shader, const Transform3d& matrix) { +#else + auto render_sidebar_scale_hint = [this, uniform_scale](Axis axis) { +#endif // ENABLE_GL_SHADERS_ATTRIBUTES +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_arrow.set_color(uniform_scale ? UNIFORM_SCALE_COLOR : get_color(axis)); +#else + m_arrow.set_color(-1, uniform_scale ? UNIFORM_SCALE_COLOR : get_color(axis)); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + +#if ENABLE_GL_SHADERS_ATTRIBUTES + Transform3d view_model_matrix = matrix * Geometry::assemble_transform(5.0 * Vec3d::UnitY()); + shader.set_uniform("view_model_matrix", view_model_matrix); + shader.set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); +#else + GLShaderProgram* shader = wxGetApp().get_current_shader(); + if (shader != nullptr) + shader->set_uniform("emission_factor", 0.0f); + + glsafe(::glTranslated(0.0, 5.0, 0.0)); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_arrow.render(); + +#if ENABLE_GL_SHADERS_ATTRIBUTES + view_model_matrix = matrix * Geometry::assemble_transform(-5.0 * Vec3d::UnitY(), PI * Vec3d::UnitZ()); + shader.set_uniform("view_model_matrix", view_model_matrix); + shader.set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); +#else + glsafe(::glTranslated(0.0, -10.0, 0.0)); + glsafe(::glRotated(180.0, 0.0, 0.0, 1.0)); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_arrow.render(); + }; + +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d view_matrix = camera.get_view_matrix() * matrix; + shader.set_uniform("projection_matrix", camera.get_projection_matrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + if (boost::ends_with(sidebar_field, "x") || uniform_scale) { +#if ENABLE_GL_SHADERS_ATTRIBUTES + render_sidebar_scale_hint(X, shader, view_matrix * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitZ())); +#else + glsafe(::glPushMatrix()); + glsafe(::glRotated(-90.0, 0.0, 0.0, 1.0)); + render_sidebar_scale_hint(X); + glsafe(::glPopMatrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + } + + if (boost::ends_with(sidebar_field, "y") || uniform_scale) { +#if ENABLE_GL_SHADERS_ATTRIBUTES + render_sidebar_scale_hint(Y, shader, view_matrix); +#else + glsafe(::glPushMatrix()); + render_sidebar_scale_hint(Y); + glsafe(::glPopMatrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + } + + if (boost::ends_with(sidebar_field, "z") || uniform_scale) { +#if ENABLE_GL_SHADERS_ATTRIBUTES + render_sidebar_scale_hint(Z, shader, view_matrix * Geometry::assemble_transform(Vec3d::Zero(), 0.5 * PI * Vec3d::UnitX())); +#else + glsafe(::glPushMatrix()); + glsafe(::glRotated(90.0, 1.0, 0.0, 0.0)); + render_sidebar_scale_hint(Z); + glsafe(::glPopMatrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + } +} + +#if ENABLE_GL_SHADERS_ATTRIBUTES +void Selection::render_sidebar_layers_hints(const std::string& sidebar_field, GLShaderProgram& shader) +#else +void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) +#endif // ENABLE_GL_SHADERS_ATTRIBUTES +{ + static const float Margin = 10.0f; + + std::string field = sidebar_field; + + // extract max_z + std::string::size_type pos = field.rfind("_"); + if (pos == std::string::npos) + return; + + const float max_z = float(string_to_double_decimal_point(field.substr(pos + 1))); + + // extract min_z + field = field.substr(0, pos); + pos = field.rfind("_"); + if (pos == std::string::npos) + return; + + const float min_z = float(string_to_double_decimal_point(field.substr(pos + 1))); + + // extract type + field = field.substr(0, pos); + pos = field.rfind("_"); + if (pos == std::string::npos) + return; + + const int type = std::stoi(field.substr(pos + 1)); + + const BoundingBoxf3& box = get_bounding_box(); + +#if !ENABLE_LEGACY_OPENGL_REMOVAL + const float min_x = float(box.min.x()) - Margin; + const float max_x = float(box.max.x()) + Margin; + const float min_y = float(box.min.y()) - Margin; + const float max_y = float(box.max.y()) + Margin; +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + + // view dependend order of rendering to keep correct transparency + const bool camera_on_top = wxGetApp().plater()->get_camera().is_looking_downward(); + const float z1 = camera_on_top ? min_z : max_z; + const float z2 = camera_on_top ? max_z : min_z; + +#if ENABLE_LEGACY_OPENGL_REMOVAL + const Vec3f p1 = { float(box.min.x()) - Margin, float(box.min.y()) - Margin, z1 }; + const Vec3f p2 = { float(box.max.x()) + Margin, float(box.max.y()) + Margin, z2 }; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glDisable(GL_CULL_FACE)); + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + if (!m_planes.models[0].is_initialized() || !is_approx(m_planes.check_points[0], p1)) { + m_planes.check_points[0] = p1; + m_planes.models[0].reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(4); + init_data.reserve_indices(6); + + // vertices + init_data.add_vertex(Vec3f(p1.x(), p1.y(), z1)); + init_data.add_vertex(Vec3f(p2.x(), p1.y(), z1)); + init_data.add_vertex(Vec3f(p2.x(), p2.y(), z1)); + init_data.add_vertex(Vec3f(p1.x(), p2.y(), z1)); + + // indices + init_data.add_triangle(0, 1, 2); + init_data.add_triangle(2, 3, 0); + + m_planes.models[0].init_from(std::move(init_data)); + } + + if (!m_planes.models[1].is_initialized() || !is_approx(m_planes.check_points[1], p2)) { + m_planes.check_points[1] = p2; + m_planes.models[1].reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(4); + init_data.reserve_indices(6); + + // vertices + init_data.add_vertex(Vec3f(p1.x(), p1.y(), z2)); + init_data.add_vertex(Vec3f(p2.x(), p1.y(), z2)); + init_data.add_vertex(Vec3f(p2.x(), p2.y(), z2)); + init_data.add_vertex(Vec3f(p1.x(), p2.y(), z2)); + + // indices + init_data.add_triangle(0, 1, 2); + init_data.add_triangle(2, 3, 0); + + m_planes.models[1].init_from(std::move(init_data)); + } + +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + shader.set_uniform("view_model_matrix", camera.get_view_matrix()); + shader.set_uniform("projection_matrix", camera.get_projection_matrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + m_planes.models[0].set_color((camera_on_top && type == 1) || (!camera_on_top && type == 2) ? SOLID_PLANE_COLOR : TRANSPARENT_PLANE_COLOR); + m_planes.models[0].render(); + m_planes.models[1].set_color((camera_on_top && type == 2) || (!camera_on_top && type == 1) ? SOLID_PLANE_COLOR : TRANSPARENT_PLANE_COLOR); + m_planes.models[1].render(); +#else + ::glBegin(GL_QUADS); + ::glColor4fv((camera_on_top && type == 1) || (!camera_on_top && type == 2) ? SOLID_PLANE_COLOR.data() : TRANSPARENT_PLANE_COLOR.data()); + ::glVertex3f(min_x, min_y, z1); + ::glVertex3f(max_x, min_y, z1); + ::glVertex3f(max_x, max_y, z1); + ::glVertex3f(min_x, max_y, z1); + glsafe(::glEnd()); + + ::glBegin(GL_QUADS); + ::glColor4fv((camera_on_top && type == 2) || (!camera_on_top && type == 1) ? SOLID_PLANE_COLOR.data() : TRANSPARENT_PLANE_COLOR.data()); + ::glVertex3f(min_x, min_y, z2); + ::glVertex3f(max_x, min_y, z2); + ::glVertex3f(max_x, max_y, z2); + ::glVertex3f(min_x, max_y, z2); + glsafe(::glEnd()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + glsafe(::glEnable(GL_CULL_FACE)); + glsafe(::glDisable(GL_BLEND)); +} + +#ifndef NDEBUG +static bool is_rotation_xy_synchronized(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) +{ + const Eigen::AngleAxisd angle_axis(Geometry::rotation_xyz_diff(rot_xyz_from, rot_xyz_to)); + const Vec3d axis = angle_axis.axis(); + const double angle = angle_axis.angle(); + if (std::abs(angle) < 1e-8) + return true; + assert(std::abs(axis.x()) < 1e-8); + assert(std::abs(axis.y()) < 1e-8); + assert(std::abs(std::abs(axis.z()) - 1.) < 1e-8); + return std::abs(axis.x()) < 1e-8 && std::abs(axis.y()) < 1e-8 && std::abs(std::abs(axis.z()) - 1.) < 1e-8; +} + +static void verify_instances_rotation_synchronized(const Model &model, const GLVolumePtrs &volumes) +{ + for (int idx_object = 0; idx_object < int(model.objects.size()); ++idx_object) { + int idx_volume_first = -1; + for (int i = 0; i < (int)volumes.size(); ++i) { + if (volumes[i]->object_idx() == idx_object) { + idx_volume_first = i; + break; + } + } + assert(idx_volume_first != -1); // object without instances? + if (idx_volume_first == -1) + continue; + const Vec3d &rotation0 = volumes[idx_volume_first]->get_instance_rotation(); + for (int i = idx_volume_first + 1; i < (int)volumes.size(); ++i) + if (volumes[i]->object_idx() == idx_object) { + const Vec3d &rotation = volumes[i]->get_instance_rotation(); + assert(is_rotation_xy_synchronized(rotation, rotation0)); + } + } +} +#endif /* NDEBUG */ + +void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_type) +{ + std::set done; // prevent processing volumes twice + done.insert(m_list.begin(), m_list.end()); + + for (unsigned int i : m_list) { + if (done.size() == m_volumes->size()) + break; + + const GLVolume* volume = (*m_volumes)[i]; +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + if (volume->is_wipe_tower) + continue; + + const int object_idx = volume->object_idx(); +#else + const int object_idx = volume->object_idx(); + if (object_idx >= 1000) + continue; +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + + const int instance_idx = volume->instance_idx(); + const Vec3d& rotation = volume->get_instance_rotation(); + const Vec3d& scaling_factor = volume->get_instance_scaling_factor(); + const Vec3d& mirror = volume->get_instance_mirror(); + + // Process unselected instances. + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { + if (done.size() == m_volumes->size()) + break; + + if (done.find(j) != done.end()) + continue; + + GLVolume* v = (*m_volumes)[j]; + if (v->object_idx() != object_idx || v->instance_idx() == instance_idx) + continue; + + assert(is_rotation_xy_synchronized(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation())); + switch (sync_rotation_type) { + case SYNC_ROTATION_NONE: { + // z only rotation -> synch instance z + // The X,Y rotations should be synchronized from start to end of the rotation. + assert(is_rotation_xy_synchronized(rotation, v->get_instance_rotation())); + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) + v->set_instance_offset(Z, volume->get_instance_offset().z()); + break; + } + case SYNC_ROTATION_GENERAL: + // generic rotation -> update instance z with the delta of the rotation. + const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation()); + v->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff }); + break; + } + + v->set_instance_scaling_factor(scaling_factor); + v->set_instance_mirror(mirror); + + done.insert(j); + } + } + +#ifndef NDEBUG + verify_instances_rotation_synchronized(*m_model, *m_volumes); +#endif /* NDEBUG */ +} + +void Selection::synchronize_unselected_volumes() +{ + for (unsigned int i : m_list) { + const GLVolume* volume = (*m_volumes)[i]; +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + if (volume->is_wipe_tower) + continue; + + const int object_idx = volume->object_idx(); +#else + const int object_idx = volume->object_idx(); + if (object_idx >= 1000) + continue; +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + + const int volume_idx = volume->volume_idx(); + const Vec3d& offset = volume->get_volume_offset(); + const Vec3d& rotation = volume->get_volume_rotation(); + const Vec3d& scaling_factor = volume->get_volume_scaling_factor(); + const Vec3d& mirror = volume->get_volume_mirror(); + + // Process unselected volumes. + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { + if (j == i) + continue; + + GLVolume* v = (*m_volumes)[j]; + if (v->object_idx() != object_idx || v->volume_idx() != volume_idx) + continue; + + v->set_volume_offset(offset); + v->set_volume_rotation(rotation); + v->set_volume_scaling_factor(scaling_factor); + v->set_volume_mirror(mirror); + } + } +} + +void Selection::ensure_on_bed() +{ + typedef std::map, double> InstancesToZMap; + InstancesToZMap instances_min_z; + + for (size_t i = 0; i < m_volumes->size(); ++i) { + GLVolume* volume = (*m_volumes)[i]; + if (!volume->is_wipe_tower && !volume->is_modifier && + std::find(m_cache.sinking_volumes.begin(), m_cache.sinking_volumes.end(), i) == m_cache.sinking_volumes.end()) { + const double min_z = volume->transformed_convex_hull_bounding_box().min.z(); + std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); + InstancesToZMap::iterator it = instances_min_z.find(instance); + if (it == instances_min_z.end()) + it = instances_min_z.insert(InstancesToZMap::value_type(instance, DBL_MAX)).first; + + it->second = std::min(it->second, min_z); + } + } + + for (GLVolume* volume : *m_volumes) { + std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); + InstancesToZMap::iterator it = instances_min_z.find(instance); + if (it != instances_min_z.end()) + volume->set_instance_offset(Z, volume->get_instance_offset(Z) - it->second); + } +} + +void Selection::ensure_not_below_bed() +{ + typedef std::map, double> InstancesToZMap; + InstancesToZMap instances_max_z; + + for (size_t i = 0; i < m_volumes->size(); ++i) { + GLVolume* volume = (*m_volumes)[i]; + if (!volume->is_wipe_tower && !volume->is_modifier) { + const double max_z = volume->transformed_convex_hull_bounding_box().max.z(); + const std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); + InstancesToZMap::iterator it = instances_max_z.find(instance); + if (it == instances_max_z.end()) + it = instances_max_z.insert({ instance, -DBL_MAX }).first; + + it->second = std::max(it->second, max_z); + } + } + + if (is_any_volume()) { + for (unsigned int i : m_list) { + GLVolume& volume = *(*m_volumes)[i]; + const std::pair instance = std::make_pair(volume.object_idx(), volume.instance_idx()); + InstancesToZMap::const_iterator it = instances_max_z.find(instance); + const double z_shift = SINKING_MIN_Z_THRESHOLD - it->second; + if (it != instances_max_z.end() && z_shift > 0.0) + volume.set_volume_offset(Z, volume.get_volume_offset(Z) + z_shift); + } + } + else { + for (GLVolume* volume : *m_volumes) { + const std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); + InstancesToZMap::const_iterator it = instances_max_z.find(instance); + if (it != instances_max_z.end() && it->second < SINKING_MIN_Z_THRESHOLD) + volume->set_instance_offset(Z, volume->get_instance_offset(Z) + SINKING_MIN_Z_THRESHOLD - it->second); + } + } +} + +bool Selection::is_from_fully_selected_instance(unsigned int volume_idx) const +{ + struct SameInstance + { + int obj_idx; + int inst_idx; + GLVolumePtrs& volumes; + + SameInstance(int obj_idx, int inst_idx, GLVolumePtrs& volumes) : obj_idx(obj_idx), inst_idx(inst_idx), volumes(volumes) {} + bool operator () (unsigned int i) { return (volumes[i]->volume_idx() >= 0) && (volumes[i]->object_idx() == obj_idx) && (volumes[i]->instance_idx() == inst_idx); } + }; + + if ((unsigned int)m_volumes->size() <= volume_idx) + return false; + + GLVolume* volume = (*m_volumes)[volume_idx]; + int object_idx = volume->object_idx(); + if ((int)m_model->objects.size() <= object_idx) + return false; + + unsigned int count = (unsigned int)std::count_if(m_list.begin(), m_list.end(), SameInstance(object_idx, volume->instance_idx(), *m_volumes)); + return count == (unsigned int)m_model->objects[object_idx]->volumes.size(); +} + +void Selection::paste_volumes_from_clipboard() +{ +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ + + int dst_obj_idx = get_object_idx(); + if ((dst_obj_idx < 0) || ((int)m_model->objects.size() <= dst_obj_idx)) + return; + + ModelObject* dst_object = m_model->objects[dst_obj_idx]; + + int dst_inst_idx = get_instance_idx(); + if ((dst_inst_idx < 0) || ((int)dst_object->instances.size() <= dst_inst_idx)) + return; + + ModelObject* src_object = m_clipboard.get_object(0); + if (src_object != nullptr) + { + ModelInstance* dst_instance = dst_object->instances[dst_inst_idx]; + BoundingBoxf3 dst_instance_bb = dst_object->instance_bounding_box(dst_inst_idx); + Transform3d src_matrix = src_object->instances[0]->get_transformation().get_matrix(true); + Transform3d dst_matrix = dst_instance->get_transformation().get_matrix(true); + bool from_same_object = (src_object->input_file == dst_object->input_file) && src_matrix.isApprox(dst_matrix); + + // used to keep relative position of multivolume selections when pasting from another object + BoundingBoxf3 total_bb; + + ModelVolumePtrs volumes; + for (ModelVolume* src_volume : src_object->volumes) + { + ModelVolume* dst_volume = dst_object->add_volume(*src_volume); + dst_volume->set_new_unique_id(); + if (from_same_object) + { +// // if the volume comes from the same object, apply the offset in world system +// double offset = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.05); +// dst_volume->translate(dst_matrix.inverse() * Vec3d(offset, offset, 0.0)); + } + else + { + // if the volume comes from another object, apply the offset as done when adding modifiers + // see ObjectList::load_generic_subobject() + total_bb.merge(dst_volume->mesh().bounding_box().transformed(src_volume->get_matrix())); + } + + volumes.push_back(dst_volume); +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ + } + + // keeps relative position of multivolume selections + if (!from_same_object) + { + for (ModelVolume* v : volumes) + { + v->set_offset((v->get_offset() - total_bb.center()) + dst_matrix.inverse() * (Vec3d(dst_instance_bb.max(0), dst_instance_bb.min(1), dst_instance_bb.min(2)) + 0.5 * total_bb.size() - dst_instance->get_transformation().get_offset())); + } + } + + wxGetApp().obj_list()->paste_volumes_into_list(dst_obj_idx, volumes); + } + +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ +} + +void Selection::paste_objects_from_clipboard() +{ +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ + + std::vector object_idxs; + const ModelObjectPtrs& src_objects = m_clipboard.get_objects(); + for (const ModelObject* src_object : src_objects) + { + ModelObject* dst_object = m_model->add_object(*src_object); + double offset = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.05); + Vec3d displacement(offset, offset, 0.0); + for (ModelInstance* inst : dst_object->instances) + { + inst->set_offset(inst->get_offset() + displacement); + } + + object_idxs.push_back(m_model->objects.size() - 1); +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ + } + + wxGetApp().obj_list()->paste_objects_into_list(object_idxs); + +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ +} + +} // namespace GUI +} // namespace Slic3r From 41b64e189a799239b959f6b3582bf4f7749d4940 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 19 May 2022 12:37:54 +0200 Subject: [PATCH 02/22] Bumped up version to 2.6.0-alpha0: the development of 2.5.x will be separated and based on 2.4.2. master branch will be used for development of 2.6.x --- version.inc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/version.inc b/version.inc index 93b3763239..3e1460a0ed 100644 --- a/version.inc +++ b/version.inc @@ -3,7 +3,7 @@ set(SLIC3R_APP_NAME "PrusaSlicer") set(SLIC3R_APP_KEY "PrusaSlicer") -set(SLIC3R_VERSION "2.5.0-alpha0") +set(SLIC3R_VERSION "2.6.0-alpha0") set(SLIC3R_BUILD_ID "PrusaSlicer-${SLIC3R_VERSION}+UNKNOWN") -set(SLIC3R_RC_VERSION "2,5,0,0") -set(SLIC3R_RC_VERSION_DOTS "2.5.0.0") +set(SLIC3R_RC_VERSION "2,6,0,0") +set(SLIC3R_RC_VERSION_DOTS "2.6.0.0") From f82d5c52b34b74304a53116ed9cd0aeef9071a02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 19 May 2022 12:46:15 +0200 Subject: [PATCH 03/22] Refactored Lightning infill to get rid of unnecessary std::list and std::unordered_map, which was slow. --- .../Fill/Lightning/DistanceField.cpp | 21 ++-- .../Fill/Lightning/DistanceField.hpp | 98 +++++++++++++++++-- src/libslic3r/Fill/Lightning/Layer.cpp | 5 +- 3 files changed, 105 insertions(+), 19 deletions(-) diff --git a/src/libslic3r/Fill/Lightning/DistanceField.cpp b/src/libslic3r/Fill/Lightning/DistanceField.cpp index 308ca41c60..d956c4e230 100644 --- a/src/libslic3r/Fill/Lightning/DistanceField.cpp +++ b/src/libslic3r/Fill/Lightning/DistanceField.cpp @@ -35,16 +35,18 @@ DistanceField::DistanceField(const coord_t& radius, const Polygons& current_outl assert(m_unsupported_points_bbox.contains(p)); } } - m_unsupported_points.sort([&radius](const UnsupportedCell &a, const UnsupportedCell &b) { + std::stable_sort(m_unsupported_points.begin(), m_unsupported_points.end(), [&radius](const UnsupportedCell &a, const UnsupportedCell &b) { constexpr coord_t prime_for_hash = 191; return std::abs(b.dist_to_boundary - a.dist_to_boundary) > radius ? a.dist_to_boundary < b.dist_to_boundary : (PointHash{}(a.loc) % prime_for_hash) < (PointHash{}(b.loc) % prime_for_hash); }); - for (auto it = m_unsupported_points.begin(); it != m_unsupported_points.end(); ++it) { - UnsupportedCell& cell = *it; - m_unsupported_points_grid.emplace(this->to_grid_point(cell.loc), it); - } + + m_unsupported_points_erased.resize(m_unsupported_points.size()); + std::fill(m_unsupported_points_erased.begin(), m_unsupported_points_erased.end(), false); + + m_unsupported_points_grid.initialize(m_unsupported_points, [&self = std::as_const(*this)](const Point &p) -> Point { return self.to_grid_point(p); }); + // Because the distance between two points is at least one axis equal to m_cell_size, every cell // in m_unsupported_points_grid contains exactly one point. assert(m_unsupported_points.size() == m_unsupported_points_grid.size()); @@ -96,12 +98,11 @@ void DistanceField::update(const Point& to_node, const Point& added_leaf) } // Inside a circle at the end of the new leaf, or inside a rotated rectangle. // Remove unsupported leafs at this grid location. - if (auto it = m_unsupported_points_grid.find(grid_addr); it != m_unsupported_points_grid.end()) { - std::list::iterator& list_it = it->second; - UnsupportedCell& cell = *list_it; + if (const size_t cell_idx = m_unsupported_points_grid.find_cell_idx(grid_addr); cell_idx != std::numeric_limits::max()) { + const UnsupportedCell &cell = m_unsupported_points[cell_idx]; if ((cell.loc - added_leaf).cast().squaredNorm() <= m_supporting_radius2) { - m_unsupported_points.erase(list_it); - m_unsupported_points_grid.erase(it); + m_unsupported_points_erased[cell_idx] = true; + m_unsupported_points_grid.mark_erased(grid_addr); } } } diff --git a/src/libslic3r/Fill/Lightning/DistanceField.hpp b/src/libslic3r/Fill/Lightning/DistanceField.hpp index beb46c5c57..fc25beb62d 100644 --- a/src/libslic3r/Fill/Lightning/DistanceField.hpp +++ b/src/libslic3r/Fill/Lightning/DistanceField.hpp @@ -38,11 +38,17 @@ public: * \return ``true`` if successful, or ``false`` if there are no more points * to consider. */ - bool tryGetNextPoint(Point* p) const { - if (m_unsupported_points.empty()) - return false; - *p = m_unsupported_points.front().loc; - return true; + bool tryGetNextPoint(Point *out_unsupported_location, size_t *out_unsupported_cell_idx, const size_t start_idx = 0) const + { + for (size_t point_idx = start_idx; point_idx < m_unsupported_points.size(); ++point_idx) { + if (!m_unsupported_points_erased[point_idx]) { + *out_unsupported_cell_idx = point_idx; + *out_unsupported_location = m_unsupported_points[point_idx].loc; + return true; + } + } + + return false; } /*! @@ -87,7 +93,8 @@ protected: /*! * Cells which still need to be supported at some point. */ - std::list m_unsupported_points; + std::vector m_unsupported_points; + std::vector m_unsupported_points_erased; /*! * BoundingBox of all points in m_unsupported_points. Used for mapping of sign integer numbers to positive integer numbers. @@ -98,7 +105,84 @@ protected: * Links the unsupported points to a grid point, so that we can quickly look * up the cell belonging to a certain position in the grid. */ - std::unordered_map::iterator, PointHash> m_unsupported_points_grid; + + class UnsupportedPointsGrid + { + public: + UnsupportedPointsGrid() = default; + void initialize(const std::vector &unsupported_points, const std::function &map_cell_to_grid) + { + if (unsupported_points.empty()) + return; + + BoundingBox unsupported_points_bbox; + for (const UnsupportedCell &cell : unsupported_points) + unsupported_points_bbox.merge(cell.loc); + + m_size = unsupported_points.size(); + m_grid_range = BoundingBox(map_cell_to_grid(unsupported_points_bbox.min), map_cell_to_grid(unsupported_points_bbox.max)); + m_grid_size = m_grid_range.size() + Point::Ones(); + + m_data.assign(m_grid_size.y() * m_grid_size.x(), std::numeric_limits::max()); + m_data_erased.assign(m_grid_size.y() * m_grid_size.x(), true); + + for (size_t cell_idx = 0; cell_idx < unsupported_points.size(); ++cell_idx) { + const size_t flat_idx = map_to_flat_array(map_cell_to_grid(unsupported_points[cell_idx].loc)); + assert(m_data[flat_idx] == std::numeric_limits::max()); + m_data[flat_idx] = cell_idx; + m_data_erased[flat_idx] = false; + } + } + + size_t size() const { return m_size; } + + size_t find_cell_idx(const Point &grid_addr) + { + if (!m_grid_range.contains(grid_addr)) + return std::numeric_limits::max(); + + if (const size_t flat_idx = map_to_flat_array(grid_addr); !m_data_erased[flat_idx]) { + assert(m_data[flat_idx] != std::numeric_limits::max()); + return m_data[flat_idx]; + } + + return std::numeric_limits::max(); + } + + void mark_erased(const Point &grid_addr) + { + assert(m_grid_range.contains(grid_addr)); + if (!m_grid_range.contains(grid_addr)) + return; + + const size_t flat_idx = map_to_flat_array(grid_addr); + assert(!m_data_erased[flat_idx] && m_data[flat_idx] != std::numeric_limits::max()); + assert(m_size != 0); + + m_data_erased[flat_idx] = true; + --m_size; + } + + private: + size_t m_size = 0; + + BoundingBox m_grid_range; + Point m_grid_size; + + std::vector m_data; + std::vector m_data_erased; + + inline size_t map_to_flat_array(const Point &loc) const + { + const Point offset_loc = loc - m_grid_range.min; + const size_t flat_idx = m_grid_size.x() * offset_loc.y() + offset_loc.x(); + assert(offset_loc.x() >= 0 && offset_loc.y() >= 0); + assert(flat_idx < m_grid_size.y() * m_grid_size.x()); + return flat_idx; + } + }; + + UnsupportedPointsGrid m_unsupported_points_grid; /*! * Maps the point to the grid coordinates. diff --git a/src/libslic3r/Fill/Lightning/Layer.cpp b/src/libslic3r/Fill/Lightning/Layer.cpp index f3193afe49..e8f954a604 100644 --- a/src/libslic3r/Fill/Lightning/Layer.cpp +++ b/src/libslic3r/Fill/Lightning/Layer.cpp @@ -56,8 +56,9 @@ void Layer::generateNewTrees // Until no more points need to be added to support all: // Determine next point from tree/outline areas via distance-field - Point unsupported_location; - while (distance_field.tryGetNextPoint(&unsupported_location)) { + size_t unsupported_cell_idx = 0; + Point unsupported_location; + while (distance_field.tryGetNextPoint(&unsupported_location, &unsupported_cell_idx, unsupported_cell_idx)) { throw_on_cancel_callback(); GroundingLocation grounding_loc = getBestGroundingLocation( unsupported_location, current_outlines, current_outlines_bbox, outlines_locator, supporting_radius, wall_supporting_radius, tree_node_locator); From 95041751a1dce8a817420cfd117cc6bc0de4df50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 19 May 2022 12:46:41 +0200 Subject: [PATCH 04/22] Refactored Lightning infill before parallelization. --- .../Fill/Lightning/DistanceField.cpp | 11 +++++++--- .../Fill/Lightning/DistanceField.hpp | 1 - src/libslic3r/Fill/Lightning/Layer.cpp | 21 +++++++++---------- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/libslic3r/Fill/Lightning/DistanceField.cpp b/src/libslic3r/Fill/Lightning/DistanceField.cpp index d956c4e230..3602e60aba 100644 --- a/src/libslic3r/Fill/Lightning/DistanceField.cpp +++ b/src/libslic3r/Fill/Lightning/DistanceField.cpp @@ -18,7 +18,12 @@ DistanceField::DistanceField(const coord_t& radius, const Polygons& current_outl m_supporting_radius2 = Slic3r::sqr(int64_t(radius)); // Sample source polygons with a regular grid sampling pattern. for (const ExPolygon &expoly : union_ex(current_overhang)) { - for (const Point &p : sample_grid_pattern(expoly, m_cell_size)) { + const Points sampled_points = sample_grid_pattern(expoly, m_cell_size); + const size_t unsupported_points_prev_size = m_unsupported_points.size(); + m_unsupported_points.resize(unsupported_points_prev_size + sampled_points.size()); + + for (size_t sp_idx = 0; sp_idx < sampled_points.size(); ++sp_idx) { + const Point &sp = sampled_points[sp_idx]; // Find a squared distance to the source expolygon boundary. double d2 = std::numeric_limits::max(); for (size_t icontour = 0; icontour <= expoly.holes.size(); ++icontour) { @@ -26,12 +31,12 @@ DistanceField::DistanceField(const coord_t& radius, const Polygons& current_outl if (contour.size() > 2) { Point prev = contour.points.back(); for (const Point &p2 : contour.points) { - d2 = std::min(d2, Line::distance_to_squared(p, prev, p2)); + d2 = std::min(d2, Line::distance_to_squared(sp, prev, p2)); prev = p2; } } } - m_unsupported_points.emplace_back(p, sqrt(d2)); + m_unsupported_points[unsupported_points_prev_size + sp_idx] = {sp, coord_t(std::sqrt(d2))}; assert(m_unsupported_points_bbox.contains(p)); } } diff --git a/src/libslic3r/Fill/Lightning/DistanceField.hpp b/src/libslic3r/Fill/Lightning/DistanceField.hpp index fc25beb62d..d4a142c056 100644 --- a/src/libslic3r/Fill/Lightning/DistanceField.hpp +++ b/src/libslic3r/Fill/Lightning/DistanceField.hpp @@ -83,7 +83,6 @@ protected: */ struct UnsupportedCell { - UnsupportedCell(const Point &loc, coord_t dist_to_boundary) : loc(loc), dist_to_boundary(dist_to_boundary) {} // The position of the center of this cell. Point loc; // How far this cell is removed from the ``current_outline`` polygon, the edge of the infill area. diff --git a/src/libslic3r/Fill/Lightning/Layer.cpp b/src/libslic3r/Fill/Lightning/Layer.cpp index e8f954a604..8539bb532b 100644 --- a/src/libslic3r/Fill/Lightning/Layer.cpp +++ b/src/libslic3r/Fill/Lightning/Layer.cpp @@ -142,26 +142,25 @@ GroundingLocation Layer::getBestGroundingLocation const auto within_dist = coord_t((node_location - unsupported_location).cast().norm()); - NodeSPtr sub_tree{ nullptr }; - coord_t current_dist = getWeightedDistance(node_location, unsupported_location); + NodeSPtr sub_tree{nullptr}; + coord_t current_dist = getWeightedDistance(node_location, unsupported_location); if (current_dist >= wall_supporting_radius) { // Only reconnect tree roots to other trees if they are not already close to the outlines. const coord_t search_radius = std::min(current_dist, within_dist); BoundingBox region(unsupported_location - Point(search_radius, search_radius), unsupported_location + Point(search_radius + locator_cell_size, search_radius + locator_cell_size)); region.min = to_grid_point(region.min, current_outlines_bbox); region.max = to_grid_point(region.max, current_outlines_bbox); - Point grid_addr; - for (grid_addr.y() = region.min.y(); grid_addr.y() < region.max.y(); ++ grid_addr.y()) - for (grid_addr.x() = region.min.x(); grid_addr.x() < region.max.x(); ++ grid_addr.x()) { - auto it_range = tree_node_locator.equal_range(grid_addr); - for (auto it = it_range.first; it != it_range.second; ++ it) { - auto candidate_sub_tree = it->second.lock(); + + for (coord_t grid_addr_y = region.min.y(); grid_addr_y < region.max.y(); ++grid_addr_y) + for (coord_t grid_addr_x = region.min.x(); grid_addr_x < region.max.x(); ++grid_addr_x) { + const auto it_range = tree_node_locator.equal_range({grid_addr_x, grid_addr_y}); + for (auto it = it_range.first; it != it_range.second; ++it) { + const NodeSPtr candidate_sub_tree = it->second.lock(); if ((candidate_sub_tree && candidate_sub_tree != exclude_tree) && !(exclude_tree && exclude_tree->hasOffspring(candidate_sub_tree)) && !polygonCollidesWithLineSegment(unsupported_location, candidate_sub_tree->getLocation(), outline_locator)) { - const coord_t candidate_dist = candidate_sub_tree->getWeightedDistance(unsupported_location, supporting_radius); - if (candidate_dist < current_dist) { + if (const coord_t candidate_dist = candidate_sub_tree->getWeightedDistance(unsupported_location, supporting_radius); candidate_dist < current_dist) { current_dist = candidate_dist; - sub_tree = candidate_sub_tree; + sub_tree = candidate_sub_tree; } } } From 4bde35cae3d263905887cce48565f8bc4b837cec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 19 May 2022 12:47:25 +0200 Subject: [PATCH 05/22] Parallelized DistanceField::DistanceField() and Layer::getBestGroundingLocation() in Lightning infill. --- .../Fill/Lightning/DistanceField.cpp | 32 +++++++----- src/libslic3r/Fill/Lightning/Layer.cpp | 51 ++++++++++++++----- 2 files changed, 57 insertions(+), 26 deletions(-) diff --git a/src/libslic3r/Fill/Lightning/DistanceField.cpp b/src/libslic3r/Fill/Lightning/DistanceField.cpp index 3602e60aba..ef407664c1 100644 --- a/src/libslic3r/Fill/Lightning/DistanceField.cpp +++ b/src/libslic3r/Fill/Lightning/DistanceField.cpp @@ -5,6 +5,8 @@ #include "../FillRectilinear.hpp" #include "../../ClipperUtils.hpp" +#include + namespace Slic3r::FillLightning { @@ -22,23 +24,25 @@ DistanceField::DistanceField(const coord_t& radius, const Polygons& current_outl const size_t unsupported_points_prev_size = m_unsupported_points.size(); m_unsupported_points.resize(unsupported_points_prev_size + sampled_points.size()); - for (size_t sp_idx = 0; sp_idx < sampled_points.size(); ++sp_idx) { - const Point &sp = sampled_points[sp_idx]; - // Find a squared distance to the source expolygon boundary. - double d2 = std::numeric_limits::max(); - for (size_t icontour = 0; icontour <= expoly.holes.size(); ++icontour) { - const Polygon &contour = icontour == 0 ? expoly.contour : expoly.holes[icontour - 1]; - if (contour.size() > 2) { - Point prev = contour.points.back(); - for (const Point &p2 : contour.points) { - d2 = std::min(d2, Line::distance_to_squared(sp, prev, p2)); - prev = p2; + tbb::parallel_for(tbb::blocked_range(0, sampled_points.size()), [&self = *this, &expoly = std::as_const(expoly), &sampled_points = std::as_const(sampled_points), &unsupported_points_prev_size = std::as_const(unsupported_points_prev_size)](const tbb::blocked_range &range) -> void { + for (size_t sp_idx = range.begin(); sp_idx < range.end(); ++sp_idx) { + const Point &sp = sampled_points[sp_idx]; + // Find a squared distance to the source expolygon boundary. + double d2 = std::numeric_limits::max(); + for (size_t icontour = 0; icontour <= expoly.holes.size(); ++icontour) { + const Polygon &contour = icontour == 0 ? expoly.contour : expoly.holes[icontour - 1]; + if (contour.size() > 2) { + Point prev = contour.points.back(); + for (const Point &p2 : contour.points) { + d2 = std::min(d2, Line::distance_to_squared(sp, prev, p2)); + prev = p2; + } } } + self.m_unsupported_points[unsupported_points_prev_size + sp_idx] = {sp, coord_t(std::sqrt(d2))}; + assert(self.m_unsupported_points_bbox.contains(sp)); } - m_unsupported_points[unsupported_points_prev_size + sp_idx] = {sp, coord_t(std::sqrt(d2))}; - assert(m_unsupported_points_bbox.contains(p)); - } + }); // end of parallel_for } std::stable_sort(m_unsupported_points.begin(), m_unsupported_points.end(), [&radius](const UnsupportedCell &a, const UnsupportedCell &b) { constexpr coord_t prime_for_hash = 191; diff --git a/src/libslic3r/Fill/Lightning/Layer.cpp b/src/libslic3r/Fill/Lightning/Layer.cpp index 8539bb532b..0bd2a65c40 100644 --- a/src/libslic3r/Fill/Lightning/Layer.cpp +++ b/src/libslic3r/Fill/Lightning/Layer.cpp @@ -10,6 +10,10 @@ #include "../../Geometry.hpp" #include "Utils.hpp" +#include +#include +#include + namespace Slic3r::FillLightning { coord_t Layer::getWeightedDistance(const Point& boundary_loc, const Point& unsupported_location) @@ -150,21 +154,44 @@ GroundingLocation Layer::getBestGroundingLocation region.min = to_grid_point(region.min, current_outlines_bbox); region.max = to_grid_point(region.max, current_outlines_bbox); - for (coord_t grid_addr_y = region.min.y(); grid_addr_y < region.max.y(); ++grid_addr_y) - for (coord_t grid_addr_x = region.min.x(); grid_addr_x < region.max.x(); ++grid_addr_x) { - const auto it_range = tree_node_locator.equal_range({grid_addr_x, grid_addr_y}); - for (auto it = it_range.first; it != it_range.second; ++it) { - const NodeSPtr candidate_sub_tree = it->second.lock(); - if ((candidate_sub_tree && candidate_sub_tree != exclude_tree) && - !(exclude_tree && exclude_tree->hasOffspring(candidate_sub_tree)) && - !polygonCollidesWithLineSegment(unsupported_location, candidate_sub_tree->getLocation(), outline_locator)) { - if (const coord_t candidate_dist = candidate_sub_tree->getWeightedDistance(unsupported_location, supporting_radius); candidate_dist < current_dist) { - current_dist = candidate_dist; - sub_tree = candidate_sub_tree; + Point current_dist_grid_addr{std::numeric_limits::lowest(), std::numeric_limits::lowest()}; + std::mutex current_dist_mutex; + tbb::parallel_for(tbb::blocked_range2d(region.min.y(), region.max.y(), region.min.x(), region.max.x()), [¤t_dist, current_dist_copy = current_dist, ¤t_dist_mutex, &sub_tree, ¤t_dist_grid_addr, &exclude_tree = std::as_const(exclude_tree), &outline_locator = std::as_const(outline_locator), &supporting_radius = std::as_const(supporting_radius), &tree_node_locator = std::as_const(tree_node_locator), &unsupported_location = std::as_const(unsupported_location)](const tbb::blocked_range2d &range) -> void { + for (coord_t grid_addr_y = range.rows().begin(); grid_addr_y < range.rows().end(); ++grid_addr_y) + for (coord_t grid_addr_x = range.cols().begin(); grid_addr_x < range.cols().end(); ++grid_addr_x) { + const Point local_grid_addr{grid_addr_x, grid_addr_y}; + NodeSPtr local_sub_tree{nullptr}; + coord_t local_current_dist = current_dist_copy; + const auto it_range = tree_node_locator.equal_range(local_grid_addr); + for (auto it = it_range.first; it != it_range.second; ++it) { + const NodeSPtr candidate_sub_tree = it->second.lock(); + if ((candidate_sub_tree && candidate_sub_tree != exclude_tree) && + !(exclude_tree && exclude_tree->hasOffspring(candidate_sub_tree)) && + !polygonCollidesWithLineSegment(unsupported_location, candidate_sub_tree->getLocation(), outline_locator)) { + if (const coord_t candidate_dist = candidate_sub_tree->getWeightedDistance(unsupported_location, supporting_radius); candidate_dist < local_current_dist) { + local_current_dist = candidate_dist; + local_sub_tree = candidate_sub_tree; + } + } + } + // To always get the same result in a parallel version as in a non-parallel version, + // we need to preserve that for the same current_dist, we select the same sub_tree + // as in the non-parallel version. For this purpose, inside the variable + // current_dist_grid_addr is stored from with 2D grid position assigned sub_tree comes. + // And when there are two sub_tree with the same current_dist, one which will be found + // the first in the non-parallel version is selected. + { + std::lock_guard lock(current_dist_mutex); + if (local_current_dist < current_dist || + (local_current_dist == current_dist && (grid_addr_y < current_dist_grid_addr.y() || + (grid_addr_y == current_dist_grid_addr.y() && grid_addr_x < current_dist_grid_addr.x())))) { + current_dist = local_current_dist; + sub_tree = local_sub_tree; + current_dist_grid_addr = local_grid_addr; } } } - } + }); // end of parallel_for } return ! sub_tree ? From 1582d019fb973c90c9b0e0f270383dc0c9ec543c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 19 May 2022 15:08:50 +0200 Subject: [PATCH 06/22] Fixed another crash in Lightning infill. --- src/libslic3r/Fill/Lightning/Generator.cpp | 2 ++ src/libslic3r/Fill/Lightning/TreeNode.hpp | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/libslic3r/Fill/Lightning/Generator.cpp b/src/libslic3r/Fill/Lightning/Generator.cpp index b4c07a338e..e226fbbab5 100644 --- a/src/libslic3r/Fill/Lightning/Generator.cpp +++ b/src/libslic3r/Fill/Lightning/Generator.cpp @@ -125,6 +125,8 @@ void Generator::generateTrees(const PrintObject &print_object, const std::functi if (const BoundingBox &outlines_locator_bbox = outlines_locator.bbox(); outlines_locator_bbox.defined) below_outlines_bbox.merge(outlines_locator_bbox); + below_outlines_bbox.merge(get_extents(current_lightning_layer.tree_roots).inflated(SCALED_EPSILON)); + outlines_locator.set_bbox(below_outlines_bbox); outlines_locator.create(below_outlines, locator_cell_size); diff --git a/src/libslic3r/Fill/Lightning/TreeNode.hpp b/src/libslic3r/Fill/Lightning/TreeNode.hpp index fdb80d2e6f..81c63f7f66 100644 --- a/src/libslic3r/Fill/Lightning/TreeNode.hpp +++ b/src/libslic3r/Fill/Lightning/TreeNode.hpp @@ -269,6 +269,9 @@ protected: std::optional m_last_grounding_location; // &tree_roots); + #ifdef LIGHTNING_TREE_NODE_DEBUG_OUTPUT friend void export_to_svg(const NodeSPtr &root_node, Slic3r::SVG &svg); friend void export_to_svg(const std::string &path, const Polygons &contour, const std::vector &root_nodes); @@ -278,6 +281,23 @@ protected: bool inside(const Polygons &polygons, const Point &p); bool lineSegmentPolygonsIntersection(const Point& a, const Point& b, const EdgeGrid::Grid& outline_locator, Point& result, coord_t within_max_dist); +inline BoundingBox get_extents(const NodeSPtr &root_node) +{ + BoundingBox bbox; + for (const NodeSPtr &children : root_node->m_children) + bbox.merge(get_extents(children)); + bbox.merge(root_node->getLocation()); + return bbox; +} + +inline BoundingBox get_extents(const std::vector &tree_roots) +{ + BoundingBox bbox; + for (const NodeSPtr &root_node : tree_roots) + bbox.merge(get_extents(root_node)); + return bbox; +} + #ifdef LIGHTNING_TREE_NODE_DEBUG_OUTPUT void export_to_svg(const NodeSPtr &root_node, SVG &svg); void export_to_svg(const std::string &path, const Polygons &contour, const std::vector &root_nodes); From 6365e54b1f98527696a47ea69e09f7d48be63043 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 20 May 2022 10:39:51 +0200 Subject: [PATCH 07/22] Fixed loading of 3mf files containing single volume instances where the volume is shifted with respect to the instance origin --- src/libslic3r/Format/3mf.cpp | 6419 +++++++++++++++++----------------- 1 file changed, 3221 insertions(+), 3198 deletions(-) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 4259562aa8..abe705dc49 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -1,3198 +1,3221 @@ -#include "../libslic3r.h" -#include "../Exception.hpp" -#include "../Model.hpp" -#include "../Utils.hpp" -#include "../LocalesUtils.hpp" -#include "../GCode.hpp" -#include "../Geometry.hpp" -#include "../GCode/ThumbnailData.hpp" -#include "../Semver.hpp" -#include "../Time.hpp" - -#include "../I18N.hpp" - -#include "3mf.hpp" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -namespace pt = boost::property_tree; - -#include -#include -#include "miniz_extension.hpp" - -#include - -// Slightly faster than sprintf("%.9g"), but there is an issue with the karma floating point formatter, -// https://github.com/boostorg/spirit/pull/586 -// where the exported string is one digit shorter than it should be to guarantee lossless round trip. -// The code is left here for the ocasion boost guys improve. -#define EXPORT_3MF_USE_SPIRIT_KARMA_FP 0 - -// VERSION NUMBERS -// 0 : .3mf, files saved by older slic3r or other applications. No version definition in them. -// 1 : Introduction of 3mf versioning. No other change in data saved into 3mf files. -// 2 : Volumes' matrices and source data added to Metadata/Slic3r_PE_model.config file, meshes transformed back to their coordinate system on loading. -// WARNING !! -> the version number has been rolled back to 1 -// the next change should use 3 -const unsigned int VERSION_3MF = 1; -// Allow loading version 2 file as well. -const unsigned int VERSION_3MF_COMPATIBLE = 2; -const char* SLIC3RPE_3MF_VERSION = "slic3rpe:Version3mf"; // definition of the metadata name saved into .model file - -// Painting gizmos data version numbers -// 0 : 3MF files saved by older PrusaSlicer or the painting gizmo wasn't used. No version definition in them. -// 1 : Introduction of painting gizmos data versioning. No other changes in painting gizmos data. -const unsigned int FDM_SUPPORTS_PAINTING_VERSION = 1; -const unsigned int SEAM_PAINTING_VERSION = 1; -const unsigned int MM_PAINTING_VERSION = 1; - -const std::string SLIC3RPE_FDM_SUPPORTS_PAINTING_VERSION = "slic3rpe:FdmSupportsPaintingVersion"; -const std::string SLIC3RPE_SEAM_PAINTING_VERSION = "slic3rpe:SeamPaintingVersion"; -const std::string SLIC3RPE_MM_PAINTING_VERSION = "slic3rpe:MmPaintingVersion"; - -const std::string MODEL_FOLDER = "3D/"; -const std::string MODEL_EXTENSION = ".model"; -const std::string MODEL_FILE = "3D/3dmodel.model"; // << this is the only format of the string which works with CURA -const std::string CONTENT_TYPES_FILE = "[Content_Types].xml"; -const std::string RELATIONSHIPS_FILE = "_rels/.rels"; -const std::string THUMBNAIL_FILE = "Metadata/thumbnail.png"; -const std::string PRINT_CONFIG_FILE = "Metadata/Slic3r_PE.config"; -const std::string MODEL_CONFIG_FILE = "Metadata/Slic3r_PE_model.config"; -const std::string LAYER_HEIGHTS_PROFILE_FILE = "Metadata/Slic3r_PE_layer_heights_profile.txt"; -const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/Prusa_Slicer_layer_config_ranges.xml"; -const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt"; -const std::string SLA_DRAIN_HOLES_FILE = "Metadata/Slic3r_PE_sla_drain_holes.txt"; -const std::string CUSTOM_GCODE_PER_PRINT_Z_FILE = "Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml"; - -static constexpr const char* MODEL_TAG = "model"; -static constexpr const char* RESOURCES_TAG = "resources"; -static constexpr const char* OBJECT_TAG = "object"; -static constexpr const char* MESH_TAG = "mesh"; -static constexpr const char* VERTICES_TAG = "vertices"; -static constexpr const char* VERTEX_TAG = "vertex"; -static constexpr const char* TRIANGLES_TAG = "triangles"; -static constexpr const char* TRIANGLE_TAG = "triangle"; -static constexpr const char* COMPONENTS_TAG = "components"; -static constexpr const char* COMPONENT_TAG = "component"; -static constexpr const char* BUILD_TAG = "build"; -static constexpr const char* ITEM_TAG = "item"; -static constexpr const char* METADATA_TAG = "metadata"; - -static constexpr const char* CONFIG_TAG = "config"; -static constexpr const char* VOLUME_TAG = "volume"; - -static constexpr const char* UNIT_ATTR = "unit"; -static constexpr const char* NAME_ATTR = "name"; -static constexpr const char* TYPE_ATTR = "type"; -static constexpr const char* ID_ATTR = "id"; -static constexpr const char* X_ATTR = "x"; -static constexpr const char* Y_ATTR = "y"; -static constexpr const char* Z_ATTR = "z"; -static constexpr const char* V1_ATTR = "v1"; -static constexpr const char* V2_ATTR = "v2"; -static constexpr const char* V3_ATTR = "v3"; -static constexpr const char* OBJECTID_ATTR = "objectid"; -static constexpr const char* TRANSFORM_ATTR = "transform"; -static constexpr const char* PRINTABLE_ATTR = "printable"; -static constexpr const char* INSTANCESCOUNT_ATTR = "instances_count"; -static constexpr const char* CUSTOM_SUPPORTS_ATTR = "slic3rpe:custom_supports"; -static constexpr const char* CUSTOM_SEAM_ATTR = "slic3rpe:custom_seam"; -static constexpr const char* MMU_SEGMENTATION_ATTR = "slic3rpe:mmu_segmentation"; - -static constexpr const char* KEY_ATTR = "key"; -static constexpr const char* VALUE_ATTR = "value"; -static constexpr const char* FIRST_TRIANGLE_ID_ATTR = "firstid"; -static constexpr const char* LAST_TRIANGLE_ID_ATTR = "lastid"; - -static constexpr const char* OBJECT_TYPE = "object"; -static constexpr const char* VOLUME_TYPE = "volume"; - -static constexpr const char* NAME_KEY = "name"; -static constexpr const char* MODIFIER_KEY = "modifier"; -static constexpr const char* VOLUME_TYPE_KEY = "volume_type"; -static constexpr const char* MATRIX_KEY = "matrix"; -static constexpr const char* SOURCE_FILE_KEY = "source_file"; -static constexpr const char* SOURCE_OBJECT_ID_KEY = "source_object_id"; -static constexpr const char* SOURCE_VOLUME_ID_KEY = "source_volume_id"; -static constexpr const char* SOURCE_OFFSET_X_KEY = "source_offset_x"; -static constexpr const char* SOURCE_OFFSET_Y_KEY = "source_offset_y"; -static constexpr const char* SOURCE_OFFSET_Z_KEY = "source_offset_z"; -static constexpr const char* SOURCE_IN_INCHES_KEY = "source_in_inches"; -static constexpr const char* SOURCE_IN_METERS_KEY = "source_in_meters"; -#if ENABLE_RELOAD_FROM_DISK_REWORK -static constexpr const char* SOURCE_IS_BUILTIN_VOLUME_KEY = "source_is_builtin_volume"; -#endif // ENABLE_RELOAD_FROM_DISK_REWORK - -static constexpr const char* MESH_STAT_EDGES_FIXED = "edges_fixed"; -static constexpr const char* MESH_STAT_DEGENERATED_FACETS = "degenerate_facets"; -static constexpr const char* MESH_STAT_FACETS_REMOVED = "facets_removed"; -static constexpr const char* MESH_STAT_FACETS_RESERVED = "facets_reversed"; -static constexpr const char* MESH_STAT_BACKWARDS_EDGES = "backwards_edges"; - - -const unsigned int VALID_OBJECT_TYPES_COUNT = 1; -const char* VALID_OBJECT_TYPES[] = -{ - "model" -}; - -const char* INVALID_OBJECT_TYPES[] = -{ - "solidsupport", - "support", - "surface", - "other" -}; - -class version_error : public Slic3r::FileIOError -{ -public: - version_error(const std::string& what_arg) : Slic3r::FileIOError(what_arg) {} - version_error(const char* what_arg) : Slic3r::FileIOError(what_arg) {} -}; - -const char* get_attribute_value_charptr(const char** attributes, unsigned int attributes_size, const char* attribute_key) -{ - if ((attributes == nullptr) || (attributes_size == 0) || (attributes_size % 2 != 0) || (attribute_key == nullptr)) - return nullptr; - - for (unsigned int a = 0; a < attributes_size; a += 2) { - if (::strcmp(attributes[a], attribute_key) == 0) - return attributes[a + 1]; - } - - return nullptr; -} - -std::string get_attribute_value_string(const char** attributes, unsigned int attributes_size, const char* attribute_key) -{ - const char* text = get_attribute_value_charptr(attributes, attributes_size, attribute_key); - return (text != nullptr) ? text : ""; -} - -float get_attribute_value_float(const char** attributes, unsigned int attributes_size, const char* attribute_key) -{ - float value = 0.0f; - if (const char *text = get_attribute_value_charptr(attributes, attributes_size, attribute_key); text != nullptr) - fast_float::from_chars(text, text + strlen(text), value); - return value; -} - -int get_attribute_value_int(const char** attributes, unsigned int attributes_size, const char* attribute_key) -{ - int value = 0; - if (const char *text = get_attribute_value_charptr(attributes, attributes_size, attribute_key); text != nullptr) - boost::spirit::qi::parse(text, text + strlen(text), boost::spirit::qi::int_, value); - return value; -} - -bool get_attribute_value_bool(const char** attributes, unsigned int attributes_size, const char* attribute_key) -{ - const char* text = get_attribute_value_charptr(attributes, attributes_size, attribute_key); - return (text != nullptr) ? (bool)::atoi(text) : true; -} - -Slic3r::Transform3d get_transform_from_3mf_specs_string(const std::string& mat_str) -{ - // check: https://3mf.io/3d-manufacturing-format/ or https://github.com/3MFConsortium/spec_core/blob/master/3MF%20Core%20Specification.md - // to see how matrices are stored inside 3mf according to specifications - Slic3r::Transform3d ret = Slic3r::Transform3d::Identity(); - - if (mat_str.empty()) - // empty string means default identity matrix - return ret; - - std::vector mat_elements_str; - boost::split(mat_elements_str, mat_str, boost::is_any_of(" "), boost::token_compress_on); - - unsigned int size = (unsigned int)mat_elements_str.size(); - if (size != 12) - // invalid data, return identity matrix - return ret; - - unsigned int i = 0; - // matrices are stored into 3mf files as 4x3 - // we need to transpose them - for (unsigned int c = 0; c < 4; ++c) { - for (unsigned int r = 0; r < 3; ++r) { - ret(r, c) = ::atof(mat_elements_str[i++].c_str()); - } - } - return ret; -} - -float get_unit_factor(const std::string& unit) -{ - const char* text = unit.c_str(); - - if (::strcmp(text, "micron") == 0) - return 0.001f; - else if (::strcmp(text, "centimeter") == 0) - return 10.0f; - else if (::strcmp(text, "inch") == 0) - return 25.4f; - else if (::strcmp(text, "foot") == 0) - return 304.8f; - else if (::strcmp(text, "meter") == 0) - return 1000.0f; - else - // default "millimeters" (see specification) - return 1.0f; -} - -bool is_valid_object_type(const std::string& type) -{ - // if the type is empty defaults to "model" (see specification) - if (type.empty()) - return true; - - for (unsigned int i = 0; i < VALID_OBJECT_TYPES_COUNT; ++i) { - if (::strcmp(type.c_str(), VALID_OBJECT_TYPES[i]) == 0) - return true; - } - - return false; -} - -namespace Slic3r { - -//! macro used to mark string used at localization, -//! return same string -#define L(s) (s) -#define _(s) Slic3r::I18N::translate(s) - - // Base class with error messages management - class _3MF_Base - { - std::vector m_errors; - - protected: - void add_error(const std::string& error) { m_errors.push_back(error); } - void clear_errors() { m_errors.clear(); } - - public: - void log_errors() - { - for (const std::string& error : m_errors) - BOOST_LOG_TRIVIAL(error) << error; - } - }; - - class _3MF_Importer : public _3MF_Base - { - struct Component - { - int object_id; - Transform3d transform; - - explicit Component(int object_id) - : object_id(object_id) - , transform(Transform3d::Identity()) - { - } - - Component(int object_id, const Transform3d& transform) - : object_id(object_id) - , transform(transform) - { - } - }; - - typedef std::vector ComponentsList; - - struct Geometry - { - std::vector vertices; - std::vector triangles; - std::vector custom_supports; - std::vector custom_seam; - std::vector mmu_segmentation; - - bool empty() { return vertices.empty() || triangles.empty(); } - - void reset() { - vertices.clear(); - triangles.clear(); - custom_supports.clear(); - custom_seam.clear(); - mmu_segmentation.clear(); - } - }; - - struct CurrentObject - { - // ID of the object inside the 3MF file, 1 based. - int id; - // Index of the ModelObject in its respective Model, zero based. - int model_object_idx; - Geometry geometry; - ModelObject* object; - ComponentsList components; - - CurrentObject() { reset(); } - - void reset() { - id = -1; - model_object_idx = -1; - geometry.reset(); - object = nullptr; - components.clear(); - } - }; - - struct CurrentConfig - { - int object_id; - int volume_id; - }; - - struct Instance - { - ModelInstance* instance; - Transform3d transform; - - Instance(ModelInstance* instance, const Transform3d& transform) - : instance(instance) - , transform(transform) - { - } - }; - - struct Metadata - { - std::string key; - std::string value; - - Metadata(const std::string& key, const std::string& value) - : key(key) - , value(value) - { - } - }; - - typedef std::vector MetadataList; - - struct ObjectMetadata - { - struct VolumeMetadata - { - unsigned int first_triangle_id; - unsigned int last_triangle_id; - MetadataList metadata; - RepairedMeshErrors mesh_stats; - - VolumeMetadata(unsigned int first_triangle_id, unsigned int last_triangle_id) - : first_triangle_id(first_triangle_id) - , last_triangle_id(last_triangle_id) - { - } - }; - - typedef std::vector VolumeMetadataList; - - MetadataList metadata; - VolumeMetadataList volumes; - }; - - // Map from a 1 based 3MF object ID to a 0 based ModelObject index inside m_model->objects. - typedef std::map IdToModelObjectMap; - typedef std::map IdToAliasesMap; - typedef std::vector InstancesList; - typedef std::map IdToMetadataMap; - typedef std::map IdToGeometryMap; - typedef std::map> IdToLayerHeightsProfileMap; - typedef std::map IdToLayerConfigRangesMap; - typedef std::map> IdToSlaSupportPointsMap; - typedef std::map> IdToSlaDrainHolesMap; - - // Version of the 3mf file - unsigned int m_version; - bool m_check_version; - - // Semantic version of PrusaSlicer, that generated this 3MF. - boost::optional m_prusaslicer_generator_version; - unsigned int m_fdm_supports_painting_version = 0; - unsigned int m_seam_painting_version = 0; - unsigned int m_mm_painting_version = 0; - - XML_Parser m_xml_parser; - // Error code returned by the application side of the parser. In that case the expat may not reliably deliver the error state - // after returning from XML_Parse() function, thus we keep the error state here. - bool m_parse_error { false }; - std::string m_parse_error_message; - Model* m_model; - float m_unit_factor; - CurrentObject m_curr_object; - IdToModelObjectMap m_objects; - IdToAliasesMap m_objects_aliases; - InstancesList m_instances; - IdToGeometryMap m_geometries; - CurrentConfig m_curr_config; - IdToMetadataMap m_objects_metadata; - IdToLayerHeightsProfileMap m_layer_heights_profiles; - IdToLayerConfigRangesMap m_layer_config_ranges; - IdToSlaSupportPointsMap m_sla_support_points; - IdToSlaDrainHolesMap m_sla_drain_holes; - std::string m_curr_metadata_name; - std::string m_curr_characters; - std::string m_name; - - public: - _3MF_Importer(); - ~_3MF_Importer(); - - bool load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, bool check_version); - unsigned int version() const { return m_version; } - - private: - void _destroy_xml_parser(); - void _stop_xml_parser(const std::string& msg = std::string()); - - bool parse_error() const { return m_parse_error; } - const char* parse_error_message() const { - return m_parse_error ? - // The error was signalled by the user code, not the expat parser. - (m_parse_error_message.empty() ? "Invalid 3MF format" : m_parse_error_message.c_str()) : - // The error was signalled by the expat parser. - XML_ErrorString(XML_GetErrorCode(m_xml_parser)); - } - - bool _load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions); - bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); - void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); - void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions); - void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); - void _extract_sla_drain_holes_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); - - void _extract_custom_gcode_per_print_z_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); - - void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, ConfigSubstitutionContext& subs_context, const std::string& archive_filename); - bool _extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model); - - // handlers to parse the .model file - void _handle_start_model_xml_element(const char* name, const char** attributes); - void _handle_end_model_xml_element(const char* name); - void _handle_model_xml_characters(const XML_Char* s, int len); - - // handlers to parse the MODEL_CONFIG_FILE file - void _handle_start_config_xml_element(const char* name, const char** attributes); - void _handle_end_config_xml_element(const char* name); - - bool _handle_start_model(const char** attributes, unsigned int num_attributes); - bool _handle_end_model(); - - bool _handle_start_resources(const char** attributes, unsigned int num_attributes); - bool _handle_end_resources(); - - bool _handle_start_object(const char** attributes, unsigned int num_attributes); - bool _handle_end_object(); - - bool _handle_start_mesh(const char** attributes, unsigned int num_attributes); - bool _handle_end_mesh(); - - bool _handle_start_vertices(const char** attributes, unsigned int num_attributes); - bool _handle_end_vertices(); - - bool _handle_start_vertex(const char** attributes, unsigned int num_attributes); - bool _handle_end_vertex(); - - bool _handle_start_triangles(const char** attributes, unsigned int num_attributes); - bool _handle_end_triangles(); - - bool _handle_start_triangle(const char** attributes, unsigned int num_attributes); - bool _handle_end_triangle(); - - bool _handle_start_components(const char** attributes, unsigned int num_attributes); - bool _handle_end_components(); - - bool _handle_start_component(const char** attributes, unsigned int num_attributes); - bool _handle_end_component(); - - bool _handle_start_build(const char** attributes, unsigned int num_attributes); - bool _handle_end_build(); - - bool _handle_start_item(const char** attributes, unsigned int num_attributes); - bool _handle_end_item(); - - bool _handle_start_metadata(const char** attributes, unsigned int num_attributes); - bool _handle_end_metadata(); - - bool _create_object_instance(int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter); - - void _apply_transform(ModelInstance& instance, const Transform3d& transform); - - bool _handle_start_config(const char** attributes, unsigned int num_attributes); - bool _handle_end_config(); - - bool _handle_start_config_object(const char** attributes, unsigned int num_attributes); - bool _handle_end_config_object(); - - bool _handle_start_config_volume(const char** attributes, unsigned int num_attributes); - bool _handle_start_config_volume_mesh(const char** attributes, unsigned int num_attributes); - bool _handle_end_config_volume(); - bool _handle_end_config_volume_mesh(); - - bool _handle_start_config_metadata(const char** attributes, unsigned int num_attributes); - bool _handle_end_config_metadata(); - - bool _generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions); - - // callbacks to parse the .model file - static void XMLCALL _handle_start_model_xml_element(void* userData, const char* name, const char** attributes); - static void XMLCALL _handle_end_model_xml_element(void* userData, const char* name); - static void XMLCALL _handle_model_xml_characters(void* userData, const XML_Char* s, int len); - - // callbacks to parse the MODEL_CONFIG_FILE file - static void XMLCALL _handle_start_config_xml_element(void* userData, const char* name, const char** attributes); - static void XMLCALL _handle_end_config_xml_element(void* userData, const char* name); - }; - - _3MF_Importer::_3MF_Importer() - : m_version(0) - , m_check_version(false) - , m_xml_parser(nullptr) - , m_model(nullptr) - , m_unit_factor(1.0f) - , m_curr_metadata_name("") - , m_curr_characters("") - , m_name("") - { - } - - _3MF_Importer::~_3MF_Importer() - { - _destroy_xml_parser(); - } - - bool _3MF_Importer::load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, bool check_version) - { - m_version = 0; - m_fdm_supports_painting_version = 0; - m_seam_painting_version = 0; - m_mm_painting_version = 0; - m_check_version = check_version; - m_model = &model; - m_unit_factor = 1.0f; - m_curr_object.reset(); - m_objects.clear(); - m_objects_aliases.clear(); - m_instances.clear(); - m_geometries.clear(); - m_curr_config.object_id = -1; - m_curr_config.volume_id = -1; - m_objects_metadata.clear(); - m_layer_heights_profiles.clear(); - m_layer_config_ranges.clear(); - m_sla_support_points.clear(); - m_curr_metadata_name.clear(); - m_curr_characters.clear(); - clear_errors(); - - return _load_model_from_file(filename, model, config, config_substitutions); - } - - void _3MF_Importer::_destroy_xml_parser() - { - if (m_xml_parser != nullptr) { - XML_ParserFree(m_xml_parser); - m_xml_parser = nullptr; - } - } - - void _3MF_Importer::_stop_xml_parser(const std::string &msg) - { - assert(! m_parse_error); - assert(m_parse_error_message.empty()); - assert(m_xml_parser != nullptr); - m_parse_error = true; - m_parse_error_message = msg; - XML_StopParser(m_xml_parser, false); - } - - bool _3MF_Importer::_load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions) - { - mz_zip_archive archive; - mz_zip_zero_struct(&archive); - - if (!open_zip_reader(&archive, filename)) { - add_error("Unable to open the file"); - return false; - } - - mz_uint num_entries = mz_zip_reader_get_num_files(&archive); - - mz_zip_archive_file_stat stat; - - m_name = boost::filesystem::path(filename).stem().string(); - - // we first loop the entries to read from the archive the .model file only, in order to extract the version from it - for (mz_uint i = 0; i < num_entries; ++i) { - if (mz_zip_reader_file_stat(&archive, i, &stat)) { - std::string name(stat.m_filename); - std::replace(name.begin(), name.end(), '\\', '/'); - - if (boost::algorithm::istarts_with(name, MODEL_FOLDER) && boost::algorithm::iends_with(name, MODEL_EXTENSION)) { - try - { - // valid model name -> extract model - if (!_extract_model_from_archive(archive, stat)) { - close_zip_reader(&archive); - add_error("Archive does not contain a valid model"); - return false; - } - } - catch (const std::exception& e) - { - // ensure the zip archive is closed and rethrow the exception - close_zip_reader(&archive); - throw Slic3r::FileIOError(e.what()); - } - } - } - } - - // we then loop again the entries to read other files stored in the archive - for (mz_uint i = 0; i < num_entries; ++i) { - if (mz_zip_reader_file_stat(&archive, i, &stat)) { - std::string name(stat.m_filename); - std::replace(name.begin(), name.end(), '\\', '/'); - - if (boost::algorithm::iequals(name, LAYER_HEIGHTS_PROFILE_FILE)) { - // extract slic3r layer heights profile file - _extract_layer_heights_profile_config_from_archive(archive, stat); - } - else if (boost::algorithm::iequals(name, LAYER_CONFIG_RANGES_FILE)) { - // extract slic3r layer config ranges file - _extract_layer_config_ranges_from_archive(archive, stat, config_substitutions); - } - else if (boost::algorithm::iequals(name, SLA_SUPPORT_POINTS_FILE)) { - // extract sla support points file - _extract_sla_support_points_from_archive(archive, stat); - } - else if (boost::algorithm::iequals(name, SLA_DRAIN_HOLES_FILE)) { - // extract sla support points file - _extract_sla_drain_holes_from_archive(archive, stat); - } - else if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE)) { - // extract slic3r print config file - _extract_print_config_from_archive(archive, stat, config, config_substitutions, filename); - } - else if (boost::algorithm::iequals(name, CUSTOM_GCODE_PER_PRINT_Z_FILE)) { - // extract slic3r layer config ranges file - _extract_custom_gcode_per_print_z_from_archive(archive, stat); - } - else if (boost::algorithm::iequals(name, MODEL_CONFIG_FILE)) { - // extract slic3r model config file - if (!_extract_model_config_from_archive(archive, stat, model)) { - close_zip_reader(&archive); - add_error("Archive does not contain a valid model config"); - return false; - } - } - } - } - - close_zip_reader(&archive); - - if (m_version == 0) { - // if the 3mf was not produced by PrusaSlicer and there is more than one instance, - // split the object in as many objects as instances - size_t curr_models_count = m_model->objects.size(); - size_t i = 0; - while (i < curr_models_count) { - ModelObject* model_object = m_model->objects[i]; - if (model_object->instances.size() > 1) { - // select the geometry associated with the original model object - const Geometry* geometry = nullptr; - for (const IdToModelObjectMap::value_type& object : m_objects) { - if (object.second == int(i)) { - IdToGeometryMap::const_iterator obj_geometry = m_geometries.find(object.first); - if (obj_geometry == m_geometries.end()) { - add_error("Unable to find object geometry"); - return false; - } - geometry = &obj_geometry->second; - break; - } - } - - if (geometry == nullptr) { - add_error("Unable to find object geometry"); - return false; - } - - // use the geometry to create the volumes in the new model objects - ObjectMetadata::VolumeMetadataList volumes(1, { 0, (unsigned int)geometry->triangles.size() - 1 }); - - // for each instance after the 1st, create a new model object containing only that instance - // and copy into it the geometry - while (model_object->instances.size() > 1) { - ModelObject* new_model_object = m_model->add_object(*model_object); - new_model_object->clear_instances(); - new_model_object->add_instance(*model_object->instances.back()); - model_object->delete_last_instance(); - if (!_generate_volumes(*new_model_object, *geometry, volumes, config_substitutions)) - return false; - } - } - ++i; - } - } - - for (const IdToModelObjectMap::value_type& object : m_objects) { - if (object.second >= int(m_model->objects.size())) { - add_error("Unable to find object"); - return false; - } - ModelObject* model_object = m_model->objects[object.second]; - IdToGeometryMap::const_iterator obj_geometry = m_geometries.find(object.first); - if (obj_geometry == m_geometries.end()) { - add_error("Unable to find object geometry"); - return false; - } - - // m_layer_heights_profiles are indexed by a 1 based model object index. - IdToLayerHeightsProfileMap::iterator obj_layer_heights_profile = m_layer_heights_profiles.find(object.second + 1); - if (obj_layer_heights_profile != m_layer_heights_profiles.end()) - model_object->layer_height_profile.set(std::move(obj_layer_heights_profile->second)); - - // m_layer_config_ranges are indexed by a 1 based model object index. - IdToLayerConfigRangesMap::iterator obj_layer_config_ranges = m_layer_config_ranges.find(object.second + 1); - if (obj_layer_config_ranges != m_layer_config_ranges.end()) - model_object->layer_config_ranges = std::move(obj_layer_config_ranges->second); - - // m_sla_support_points are indexed by a 1 based model object index. - IdToSlaSupportPointsMap::iterator obj_sla_support_points = m_sla_support_points.find(object.second + 1); - if (obj_sla_support_points != m_sla_support_points.end() && !obj_sla_support_points->second.empty()) { - model_object->sla_support_points = std::move(obj_sla_support_points->second); - model_object->sla_points_status = sla::PointsStatus::UserModified; - } - - IdToSlaDrainHolesMap::iterator obj_drain_holes = m_sla_drain_holes.find(object.second + 1); - if (obj_drain_holes != m_sla_drain_holes.end() && !obj_drain_holes->second.empty()) { - model_object->sla_drain_holes = std::move(obj_drain_holes->second); - } - - ObjectMetadata::VolumeMetadataList volumes; - ObjectMetadata::VolumeMetadataList* volumes_ptr = nullptr; - - IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first); - if (obj_metadata != m_objects_metadata.end()) { - // config data has been found, this model was saved using slic3r pe - - // apply object's name and config data - for (const Metadata& metadata : obj_metadata->second.metadata) { - if (metadata.key == "name") - model_object->name = metadata.value; - else - model_object->config.set_deserialize(metadata.key, metadata.value, config_substitutions); - } - - // select object's detected volumes - volumes_ptr = &obj_metadata->second.volumes; - } - else { - // config data not found, this model was not saved using slic3r pe - - // add the entire geometry as the single volume to generate - volumes.emplace_back(0, (int)obj_geometry->second.triangles.size() - 1); - - // select as volumes - volumes_ptr = &volumes; - } - - if (!_generate_volumes(*model_object, obj_geometry->second, *volumes_ptr, config_substitutions)) - return false; - } - -#if ENABLE_RELOAD_FROM_DISK_REWORK - for (int obj_id = 0; obj_id < int(model.objects.size()); ++obj_id) { - ModelObject* o = model.objects[obj_id]; - for (int vol_id = 0; vol_id < int(o->volumes.size()); ++vol_id) { - ModelVolume* v = o->volumes[vol_id]; - if (v->source.input_file.empty()) - v->source.input_file = v->name.empty() ? filename : v->name; - if (v->source.volume_idx == -1) - v->source.volume_idx = vol_id; - if (v->source.object_idx == -1) - v->source.object_idx = obj_id; - } - } -#else - int object_idx = 0; - for (ModelObject* o : model.objects) { - int volume_idx = 0; - for (ModelVolume* v : o->volumes) { - if (v->source.input_file.empty() && v->type() == ModelVolumeType::MODEL_PART) { - v->source.input_file = filename; - if (v->source.volume_idx == -1) - v->source.volume_idx = volume_idx; - if (v->source.object_idx == -1) - v->source.object_idx = object_idx; - } - ++volume_idx; - } - ++object_idx; - } -#endif // ENABLE_RELOAD_FROM_DISK_REWORK - -// // fixes the min z of the model if negative -// model.adjust_min_z(); - - return true; - } - - bool _3MF_Importer::_extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) - { - if (stat.m_uncomp_size == 0) { - add_error("Found invalid size"); - return false; - } - - _destroy_xml_parser(); - - m_xml_parser = XML_ParserCreate(nullptr); - if (m_xml_parser == nullptr) { - add_error("Unable to create parser"); - return false; - } - - XML_SetUserData(m_xml_parser, (void*)this); - XML_SetElementHandler(m_xml_parser, _3MF_Importer::_handle_start_model_xml_element, _3MF_Importer::_handle_end_model_xml_element); - XML_SetCharacterDataHandler(m_xml_parser, _3MF_Importer::_handle_model_xml_characters); - - struct CallbackData - { - XML_Parser& parser; - _3MF_Importer& importer; - const mz_zip_archive_file_stat& stat; - - CallbackData(XML_Parser& parser, _3MF_Importer& importer, const mz_zip_archive_file_stat& stat) : parser(parser), importer(importer), stat(stat) {} - }; - - CallbackData data(m_xml_parser, *this, stat); - - mz_bool res = 0; - - try - { - res = mz_zip_reader_extract_file_to_callback(&archive, stat.m_filename, [](void* pOpaque, mz_uint64 file_ofs, const void* pBuf, size_t n)->size_t { - CallbackData* data = (CallbackData*)pOpaque; - if (!XML_Parse(data->parser, (const char*)pBuf, (int)n, (file_ofs + n == data->stat.m_uncomp_size) ? 1 : 0) || data->importer.parse_error()) { - char error_buf[1024]; - ::sprintf(error_buf, "Error (%s) while parsing '%s' at line %d", data->importer.parse_error_message(), data->stat.m_filename, (int)XML_GetCurrentLineNumber(data->parser)); - throw Slic3r::FileIOError(error_buf); - } - - return n; - }, &data, 0); - } - catch (const version_error& e) - { - // rethrow the exception - throw Slic3r::FileIOError(e.what()); - } - catch (std::exception& e) - { - add_error(e.what()); - return false; - } - - if (res == 0) { - add_error("Error while extracting model data from zip archive"); - return false; - } - - return true; - } - - void _3MF_Importer::_extract_print_config_from_archive( - mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, - DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, - const std::string& archive_filename) - { - if (stat.m_uncomp_size > 0) { - std::string buffer((size_t)stat.m_uncomp_size, 0); - mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); - if (res == 0) { - add_error("Error while reading config data to buffer"); - return; - } - //FIXME Loading a "will be one day a legacy format" of configuration in a form of a G-code comment. - // Each config line is prefixed with a semicolon (G-code comment), that is ugly. - - // Replacing the legacy function with load_from_ini_string_commented leads to issues when - // parsing 3MFs from before PrusaSlicer 2.0.0 (which can have duplicated entries in the INI. - // See https://github.com/prusa3d/PrusaSlicer/issues/7155. We'll revert it for now. - //config_substitutions.substitutions = config.load_from_ini_string_commented(std::move(buffer), config_substitutions.rule); - ConfigBase::load_from_gcode_string_legacy(config, buffer.data(), config_substitutions); - } - } - - void _3MF_Importer::_extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) - { - if (stat.m_uncomp_size > 0) { - std::string buffer((size_t)stat.m_uncomp_size, 0); - mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); - if (res == 0) { - add_error("Error while reading layer heights profile data to buffer"); - return; - } - - if (buffer.back() == '\n') - buffer.pop_back(); - - std::vector objects; - boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off); - - for (const std::string& object : objects) { - std::vector object_data; - boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off); - if (object_data.size() != 2) { - add_error("Error while reading object data"); - continue; - } - - std::vector object_data_id; - boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off); - if (object_data_id.size() != 2) { - add_error("Error while reading object id"); - continue; - } - - int object_id = std::atoi(object_data_id[1].c_str()); - if (object_id == 0) { - add_error("Found invalid object id"); - continue; - } - - IdToLayerHeightsProfileMap::iterator object_item = m_layer_heights_profiles.find(object_id); - if (object_item != m_layer_heights_profiles.end()) { - add_error("Found duplicated layer heights profile"); - continue; - } - - std::vector object_data_profile; - boost::split(object_data_profile, object_data[1], boost::is_any_of(";"), boost::token_compress_off); - if (object_data_profile.size() <= 4 || object_data_profile.size() % 2 != 0) { - add_error("Found invalid layer heights profile"); - continue; - } - - std::vector profile; - profile.reserve(object_data_profile.size()); - - for (const std::string& value : object_data_profile) { - profile.push_back((coordf_t)std::atof(value.c_str())); - } - - m_layer_heights_profiles.insert({ object_id, profile }); - } - } - } - - void _3MF_Importer::_extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions) - { - if (stat.m_uncomp_size > 0) { - std::string buffer((size_t)stat.m_uncomp_size, 0); - mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); - if (res == 0) { - add_error("Error while reading layer config ranges data to buffer"); - return; - } - - std::istringstream iss(buffer); // wrap returned xml to istringstream - pt::ptree objects_tree; - pt::read_xml(iss, objects_tree); - - for (const auto& object : objects_tree.get_child("objects")) { - pt::ptree object_tree = object.second; - int obj_idx = object_tree.get(".id", -1); - if (obj_idx <= 0) { - add_error("Found invalid object id"); - continue; - } - - IdToLayerConfigRangesMap::iterator object_item = m_layer_config_ranges.find(obj_idx); - if (object_item != m_layer_config_ranges.end()) { - add_error("Found duplicated layer config range"); - continue; - } - - t_layer_config_ranges config_ranges; - - for (const auto& range : object_tree) { - if (range.first != "range") - continue; - pt::ptree range_tree = range.second; - double min_z = range_tree.get(".min_z"); - double max_z = range_tree.get(".max_z"); - - // get Z range information - DynamicPrintConfig config; - - for (const auto& option : range_tree) { - if (option.first != "option") - continue; - std::string opt_key = option.second.get(".opt_key"); - std::string value = option.second.data(); - config.set_deserialize(opt_key, value, config_substitutions); - } - - config_ranges[{ min_z, max_z }].assign_config(std::move(config)); - } - - if (!config_ranges.empty()) - m_layer_config_ranges.insert({ obj_idx, std::move(config_ranges) }); - } - } - } - - void _3MF_Importer::_extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) - { - if (stat.m_uncomp_size > 0) { - std::string buffer((size_t)stat.m_uncomp_size, 0); - mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); - if (res == 0) { - add_error("Error while reading sla support points data to buffer"); - return; - } - - if (buffer.back() == '\n') - buffer.pop_back(); - - std::vector objects; - boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off); - - // Info on format versioning - see 3mf.hpp - int version = 0; - std::string key("support_points_format_version="); - if (!objects.empty() && objects[0].find(key) != std::string::npos) { - objects[0].erase(objects[0].begin(), objects[0].begin() + long(key.size())); // removes the string - version = std::stoi(objects[0]); - objects.erase(objects.begin()); // pop the header - } - - for (const std::string& object : objects) { - std::vector object_data; - boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off); - - if (object_data.size() != 2) { - add_error("Error while reading object data"); - continue; - } - - std::vector object_data_id; - boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off); - if (object_data_id.size() != 2) { - add_error("Error while reading object id"); - continue; - } - - int object_id = std::atoi(object_data_id[1].c_str()); - if (object_id == 0) { - add_error("Found invalid object id"); - continue; - } - - IdToSlaSupportPointsMap::iterator object_item = m_sla_support_points.find(object_id); - if (object_item != m_sla_support_points.end()) { - add_error("Found duplicated SLA support points"); - continue; - } - - std::vector object_data_points; - boost::split(object_data_points, object_data[1], boost::is_any_of(" "), boost::token_compress_off); - - std::vector sla_support_points; - - if (version == 0) { - for (unsigned int i=0; i 0) { - std::string buffer(size_t(stat.m_uncomp_size), 0); - mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); - if (res == 0) { - add_error("Error while reading sla support points data to buffer"); - return; - } - - if (buffer.back() == '\n') - buffer.pop_back(); - - std::vector objects; - boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off); - - // Info on format versioning - see 3mf.hpp - int version = 0; - std::string key("drain_holes_format_version="); - if (!objects.empty() && objects[0].find(key) != std::string::npos) { - objects[0].erase(objects[0].begin(), objects[0].begin() + long(key.size())); // removes the string - version = std::stoi(objects[0]); - objects.erase(objects.begin()); // pop the header - } - - for (const std::string& object : objects) { - std::vector object_data; - boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off); - - if (object_data.size() != 2) { - add_error("Error while reading object data"); - continue; - } - - std::vector object_data_id; - boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off); - if (object_data_id.size() != 2) { - add_error("Error while reading object id"); - continue; - } - - int object_id = std::atoi(object_data_id[1].c_str()); - if (object_id == 0) { - add_error("Found invalid object id"); - continue; - } - - IdToSlaDrainHolesMap::iterator object_item = m_sla_drain_holes.find(object_id); - if (object_item != m_sla_drain_holes.end()) { - add_error("Found duplicated SLA drain holes"); - continue; - } - - std::vector object_data_points; - boost::split(object_data_points, object_data[1], boost::is_any_of(" "), boost::token_compress_off); - - sla::DrainHoles sla_drain_holes; - - if (version == 1) { - for (unsigned int i=0; i 0) { - std::string buffer((size_t)stat.m_uncomp_size, 0); - mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); - if (res == 0) { - add_error("Error while reading custom Gcodes per height data to buffer"); - return; - } - - std::istringstream iss(buffer); // wrap returned xml to istringstream - pt::ptree main_tree; - pt::read_xml(iss, main_tree); - - if (main_tree.front().first != "custom_gcodes_per_print_z") - return; - pt::ptree code_tree = main_tree.front().second; - - m_model->custom_gcode_per_print_z.gcodes.clear(); - - for (const auto& code : code_tree) { - if (code.first == "mode") { - pt::ptree tree = code.second; - std::string mode = tree.get(".value"); - m_model->custom_gcode_per_print_z.mode = mode == CustomGCode::SingleExtruderMode ? CustomGCode::Mode::SingleExtruder : - mode == CustomGCode::MultiAsSingleMode ? CustomGCode::Mode::MultiAsSingle : - CustomGCode::Mode::MultiExtruder; - } - if (code.first != "code") - continue; - - pt::ptree tree = code.second; - double print_z = tree.get (".print_z" ); - int extruder = tree.get (".extruder"); - std::string color = tree.get (".color" ); - - CustomGCode::Type type; - std::string extra; - pt::ptree attr_tree = tree.find("")->second; - if (attr_tree.find("type") == attr_tree.not_found()) { - // It means that data was saved in old version (2.2.0 and older) of PrusaSlicer - // read old data ... - std::string gcode = tree.get (".gcode"); - // ... and interpret them to the new data - type = gcode == "M600" ? CustomGCode::ColorChange : - gcode == "M601" ? CustomGCode::PausePrint : - gcode == "tool_change" ? CustomGCode::ToolChange : CustomGCode::Custom; - extra = type == CustomGCode::PausePrint ? color : - type == CustomGCode::Custom ? gcode : ""; - } - else { - type = static_cast(tree.get(".type")); - extra = tree.get(".extra"); - } - m_model->custom_gcode_per_print_z.gcodes.push_back(CustomGCode::Item{print_z, type, extruder, color, extra}) ; - } - } - } - - void _3MF_Importer::_handle_start_model_xml_element(const char* name, const char** attributes) - { - if (m_xml_parser == nullptr) - return; - - bool res = true; - unsigned int num_attributes = (unsigned int)XML_GetSpecifiedAttributeCount(m_xml_parser); - - if (::strcmp(MODEL_TAG, name) == 0) - res = _handle_start_model(attributes, num_attributes); - else if (::strcmp(RESOURCES_TAG, name) == 0) - res = _handle_start_resources(attributes, num_attributes); - else if (::strcmp(OBJECT_TAG, name) == 0) - res = _handle_start_object(attributes, num_attributes); - else if (::strcmp(MESH_TAG, name) == 0) - res = _handle_start_mesh(attributes, num_attributes); - else if (::strcmp(VERTICES_TAG, name) == 0) - res = _handle_start_vertices(attributes, num_attributes); - else if (::strcmp(VERTEX_TAG, name) == 0) - res = _handle_start_vertex(attributes, num_attributes); - else if (::strcmp(TRIANGLES_TAG, name) == 0) - res = _handle_start_triangles(attributes, num_attributes); - else if (::strcmp(TRIANGLE_TAG, name) == 0) - res = _handle_start_triangle(attributes, num_attributes); - else if (::strcmp(COMPONENTS_TAG, name) == 0) - res = _handle_start_components(attributes, num_attributes); - else if (::strcmp(COMPONENT_TAG, name) == 0) - res = _handle_start_component(attributes, num_attributes); - else if (::strcmp(BUILD_TAG, name) == 0) - res = _handle_start_build(attributes, num_attributes); - else if (::strcmp(ITEM_TAG, name) == 0) - res = _handle_start_item(attributes, num_attributes); - else if (::strcmp(METADATA_TAG, name) == 0) - res = _handle_start_metadata(attributes, num_attributes); - - if (!res) - _stop_xml_parser(); - } - - void _3MF_Importer::_handle_end_model_xml_element(const char* name) - { - if (m_xml_parser == nullptr) - return; - - bool res = true; - - if (::strcmp(MODEL_TAG, name) == 0) - res = _handle_end_model(); - else if (::strcmp(RESOURCES_TAG, name) == 0) - res = _handle_end_resources(); - else if (::strcmp(OBJECT_TAG, name) == 0) - res = _handle_end_object(); - else if (::strcmp(MESH_TAG, name) == 0) - res = _handle_end_mesh(); - else if (::strcmp(VERTICES_TAG, name) == 0) - res = _handle_end_vertices(); - else if (::strcmp(VERTEX_TAG, name) == 0) - res = _handle_end_vertex(); - else if (::strcmp(TRIANGLES_TAG, name) == 0) - res = _handle_end_triangles(); - else if (::strcmp(TRIANGLE_TAG, name) == 0) - res = _handle_end_triangle(); - else if (::strcmp(COMPONENTS_TAG, name) == 0) - res = _handle_end_components(); - else if (::strcmp(COMPONENT_TAG, name) == 0) - res = _handle_end_component(); - else if (::strcmp(BUILD_TAG, name) == 0) - res = _handle_end_build(); - else if (::strcmp(ITEM_TAG, name) == 0) - res = _handle_end_item(); - else if (::strcmp(METADATA_TAG, name) == 0) - res = _handle_end_metadata(); - - if (!res) - _stop_xml_parser(); - } - - void _3MF_Importer::_handle_model_xml_characters(const XML_Char* s, int len) - { - m_curr_characters.append(s, len); - } - - void _3MF_Importer::_handle_start_config_xml_element(const char* name, const char** attributes) - { - if (m_xml_parser == nullptr) - return; - - bool res = true; - unsigned int num_attributes = (unsigned int)XML_GetSpecifiedAttributeCount(m_xml_parser); - - if (::strcmp(CONFIG_TAG, name) == 0) - res = _handle_start_config(attributes, num_attributes); - else if (::strcmp(OBJECT_TAG, name) == 0) - res = _handle_start_config_object(attributes, num_attributes); - else if (::strcmp(VOLUME_TAG, name) == 0) - res = _handle_start_config_volume(attributes, num_attributes); - else if (::strcmp(MESH_TAG, name) == 0) - res = _handle_start_config_volume_mesh(attributes, num_attributes); - else if (::strcmp(METADATA_TAG, name) == 0) - res = _handle_start_config_metadata(attributes, num_attributes); - - if (!res) - _stop_xml_parser(); - } - - void _3MF_Importer::_handle_end_config_xml_element(const char* name) - { - if (m_xml_parser == nullptr) - return; - - bool res = true; - - if (::strcmp(CONFIG_TAG, name) == 0) - res = _handle_end_config(); - else if (::strcmp(OBJECT_TAG, name) == 0) - res = _handle_end_config_object(); - else if (::strcmp(VOLUME_TAG, name) == 0) - res = _handle_end_config_volume(); - else if (::strcmp(MESH_TAG, name) == 0) - res = _handle_end_config_volume_mesh(); - else if (::strcmp(METADATA_TAG, name) == 0) - res = _handle_end_config_metadata(); - - if (!res) - _stop_xml_parser(); - } - - bool _3MF_Importer::_handle_start_model(const char** attributes, unsigned int num_attributes) - { - m_unit_factor = get_unit_factor(get_attribute_value_string(attributes, num_attributes, UNIT_ATTR)); - return true; - } - - bool _3MF_Importer::_handle_end_model() - { - // deletes all non-built or non-instanced objects - for (const IdToModelObjectMap::value_type& object : m_objects) { - if (object.second >= int(m_model->objects.size())) { - add_error("Unable to find object"); - return false; - } - ModelObject *model_object = m_model->objects[object.second]; - if (model_object != nullptr && model_object->instances.size() == 0) - m_model->delete_object(model_object); - } - - if (m_version == 0) { - // if the 3mf was not produced by PrusaSlicer and there is only one object, - // set the object name to match the filename - if (m_model->objects.size() == 1) - m_model->objects.front()->name = m_name; - } - - // applies instances' matrices - for (Instance& instance : m_instances) { - if (instance.instance != nullptr && instance.instance->get_object() != nullptr) - // apply the transform to the instance - _apply_transform(*instance.instance, instance.transform); - } - - return true; - } - - bool _3MF_Importer::_handle_start_resources(const char** attributes, unsigned int num_attributes) - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_end_resources() - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_start_object(const char** attributes, unsigned int num_attributes) - { - // reset current data - m_curr_object.reset(); - - if (is_valid_object_type(get_attribute_value_string(attributes, num_attributes, TYPE_ATTR))) { - // create new object (it may be removed later if no instances are generated from it) - m_curr_object.model_object_idx = (int)m_model->objects.size(); - m_curr_object.object = m_model->add_object(); - if (m_curr_object.object == nullptr) { - add_error("Unable to create object"); - return false; - } - - // set object data - m_curr_object.object->name = get_attribute_value_string(attributes, num_attributes, NAME_ATTR); - if (m_curr_object.object->name.empty()) - m_curr_object.object->name = m_name + "_" + std::to_string(m_model->objects.size()); - - m_curr_object.id = get_attribute_value_int(attributes, num_attributes, ID_ATTR); - } - - return true; - } - - bool _3MF_Importer::_handle_end_object() - { - if (m_curr_object.object != nullptr) { - if (m_curr_object.geometry.empty()) { - // no geometry defined - // remove the object from the model - m_model->delete_object(m_curr_object.object); - - if (m_curr_object.components.empty()) { - // no components defined -> invalid object, delete it - IdToModelObjectMap::iterator object_item = m_objects.find(m_curr_object.id); - if (object_item != m_objects.end()) - m_objects.erase(object_item); - - IdToAliasesMap::iterator alias_item = m_objects_aliases.find(m_curr_object.id); - if (alias_item != m_objects_aliases.end()) - m_objects_aliases.erase(alias_item); - } - else - // adds components to aliases - m_objects_aliases.insert({ m_curr_object.id, m_curr_object.components }); - } - else { - // geometry defined, store it for later use - m_geometries.insert({ m_curr_object.id, std::move(m_curr_object.geometry) }); - - // stores the object for later use - if (m_objects.find(m_curr_object.id) == m_objects.end()) { - m_objects.insert({ m_curr_object.id, m_curr_object.model_object_idx }); - m_objects_aliases.insert({ m_curr_object.id, { 1, Component(m_curr_object.id) } }); // aliases itself - } - else { - add_error("Found object with duplicate id"); - return false; - } - } - } - - return true; - } - - bool _3MF_Importer::_handle_start_mesh(const char** attributes, unsigned int num_attributes) - { - // reset current geometry - m_curr_object.geometry.reset(); - return true; - } - - bool _3MF_Importer::_handle_end_mesh() - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_start_vertices(const char** attributes, unsigned int num_attributes) - { - // reset current vertices - m_curr_object.geometry.vertices.clear(); - return true; - } - - bool _3MF_Importer::_handle_end_vertices() - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_start_vertex(const char** attributes, unsigned int num_attributes) - { - // appends the vertex coordinates - // missing values are set equal to ZERO - m_curr_object.geometry.vertices.emplace_back( - m_unit_factor * get_attribute_value_float(attributes, num_attributes, X_ATTR), - m_unit_factor * get_attribute_value_float(attributes, num_attributes, Y_ATTR), - m_unit_factor * get_attribute_value_float(attributes, num_attributes, Z_ATTR)); - return true; - } - - bool _3MF_Importer::_handle_end_vertex() - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_start_triangles(const char** attributes, unsigned int num_attributes) - { - // reset current triangles - m_curr_object.geometry.triangles.clear(); - return true; - } - - bool _3MF_Importer::_handle_end_triangles() - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_start_triangle(const char** attributes, unsigned int num_attributes) - { - // we are ignoring the following attributes: - // p1 - // p2 - // p3 - // pid - // see specifications - - // appends the triangle's vertices indices - // missing values are set equal to ZERO - m_curr_object.geometry.triangles.emplace_back( - get_attribute_value_int(attributes, num_attributes, V1_ATTR), - get_attribute_value_int(attributes, num_attributes, V2_ATTR), - get_attribute_value_int(attributes, num_attributes, V3_ATTR)); - - m_curr_object.geometry.custom_supports.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR)); - m_curr_object.geometry.custom_seam.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR)); - m_curr_object.geometry.mmu_segmentation.push_back(get_attribute_value_string(attributes, num_attributes, MMU_SEGMENTATION_ATTR)); - return true; - } - - bool _3MF_Importer::_handle_end_triangle() - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_start_components(const char** attributes, unsigned int num_attributes) - { - // reset current components - m_curr_object.components.clear(); - return true; - } - - bool _3MF_Importer::_handle_end_components() - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_start_component(const char** attributes, unsigned int num_attributes) - { - int object_id = get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR); - Transform3d transform = get_transform_from_3mf_specs_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR)); - - IdToModelObjectMap::iterator object_item = m_objects.find(object_id); - if (object_item == m_objects.end()) { - IdToAliasesMap::iterator alias_item = m_objects_aliases.find(object_id); - if (alias_item == m_objects_aliases.end()) { - add_error("Found component with invalid object id"); - return false; - } - } - - m_curr_object.components.emplace_back(object_id, transform); - - return true; - } - - bool _3MF_Importer::_handle_end_component() - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_start_build(const char** attributes, unsigned int num_attributes) - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_end_build() - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_start_item(const char** attributes, unsigned int num_attributes) - { - // we are ignoring the following attributes - // thumbnail - // partnumber - // pid - // pindex - // see specifications - - int object_id = get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR); - Transform3d transform = get_transform_from_3mf_specs_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR)); - int printable = get_attribute_value_bool(attributes, num_attributes, PRINTABLE_ATTR); - - return _create_object_instance(object_id, transform, printable, 1); - } - - bool _3MF_Importer::_handle_end_item() - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_start_metadata(const char** attributes, unsigned int num_attributes) - { - m_curr_characters.clear(); - - std::string name = get_attribute_value_string(attributes, num_attributes, NAME_ATTR); - if (!name.empty()) - m_curr_metadata_name = name; - - return true; - } - - inline static void check_painting_version(unsigned int loaded_version, unsigned int highest_supported_version, const std::string &error_msg) - { - if (loaded_version > highest_supported_version) - throw version_error(error_msg); - } - - bool _3MF_Importer::_handle_end_metadata() - { - if (m_curr_metadata_name == SLIC3RPE_3MF_VERSION) { - m_version = (unsigned int)atoi(m_curr_characters.c_str()); - if (m_check_version && (m_version > VERSION_3MF_COMPATIBLE)) { - // std::string msg = _(L("The selected 3mf file has been saved with a newer version of " + std::string(SLIC3R_APP_NAME) + " and is not compatible.")); - // throw version_error(msg.c_str()); - const std::string msg = (boost::format(_(L("The selected 3mf file has been saved with a newer version of %1% and is not compatible."))) % std::string(SLIC3R_APP_NAME)).str(); - throw version_error(msg); - } - } else if (m_curr_metadata_name == "Application") { - // Generator application of the 3MF. - // SLIC3R_APP_KEY - SLIC3R_VERSION - if (boost::starts_with(m_curr_characters, "PrusaSlicer-")) - m_prusaslicer_generator_version = Semver::parse(m_curr_characters.substr(12)); - } else if (m_curr_metadata_name == SLIC3RPE_FDM_SUPPORTS_PAINTING_VERSION) { - m_fdm_supports_painting_version = (unsigned int) atoi(m_curr_characters.c_str()); - check_painting_version(m_fdm_supports_painting_version, FDM_SUPPORTS_PAINTING_VERSION, - _(L("The selected 3MF contains FDM supports painted object using a newer version of PrusaSlicer and is not compatible."))); - } else if (m_curr_metadata_name == SLIC3RPE_SEAM_PAINTING_VERSION) { - m_seam_painting_version = (unsigned int) atoi(m_curr_characters.c_str()); - check_painting_version(m_seam_painting_version, SEAM_PAINTING_VERSION, - _(L("The selected 3MF contains seam painted object using a newer version of PrusaSlicer and is not compatible."))); - } else if (m_curr_metadata_name == SLIC3RPE_MM_PAINTING_VERSION) { - m_mm_painting_version = (unsigned int) atoi(m_curr_characters.c_str()); - check_painting_version(m_mm_painting_version, MM_PAINTING_VERSION, - _(L("The selected 3MF contains multi-material painted object using a newer version of PrusaSlicer and is not compatible."))); - } - - return true; - } - - bool _3MF_Importer::_create_object_instance(int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter) - { - static const unsigned int MAX_RECURSIONS = 10; - - // escape from circular aliasing - if (recur_counter > MAX_RECURSIONS) { - add_error("Too many recursions"); - return false; - } - - IdToAliasesMap::iterator it = m_objects_aliases.find(object_id); - if (it == m_objects_aliases.end()) { - add_error("Found item with invalid object id"); - return false; - } - - if (it->second.size() == 1 && it->second[0].object_id == object_id) { - // aliasing to itself - - IdToModelObjectMap::iterator object_item = m_objects.find(object_id); - if (object_item == m_objects.end() || object_item->second == -1) { - add_error("Found invalid object"); - return false; - } - else { - ModelInstance* instance = m_model->objects[object_item->second]->add_instance(); - if (instance == nullptr) { - add_error("Unable to add object instance"); - return false; - } - instance->printable = printable; - - m_instances.emplace_back(instance, transform); - } - } - else { - // recursively process nested components - for (const Component& component : it->second) { - if (!_create_object_instance(component.object_id, transform * component.transform, printable, recur_counter + 1)) - return false; - } - } - - return true; - } - - void _3MF_Importer::_apply_transform(ModelInstance& instance, const Transform3d& transform) - { - Slic3r::Geometry::Transformation t(transform); - // invalid scale value, return - if (!t.get_scaling_factor().all()) - return; - - instance.set_transformation(t); - } - - bool _3MF_Importer::_handle_start_config(const char** attributes, unsigned int num_attributes) - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_end_config() - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_start_config_object(const char** attributes, unsigned int num_attributes) - { - int object_id = get_attribute_value_int(attributes, num_attributes, ID_ATTR); - IdToMetadataMap::iterator object_item = m_objects_metadata.find(object_id); - if (object_item != m_objects_metadata.end()) { - add_error("Found duplicated object id"); - return false; - } - - // Added because of github #3435, currently not used by PrusaSlicer - // int instances_count_id = get_attribute_value_int(attributes, num_attributes, INSTANCESCOUNT_ATTR); - - m_objects_metadata.insert({ object_id, ObjectMetadata() }); - m_curr_config.object_id = object_id; - return true; - } - - bool _3MF_Importer::_handle_end_config_object() - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_start_config_volume(const char** attributes, unsigned int num_attributes) - { - IdToMetadataMap::iterator object = m_objects_metadata.find(m_curr_config.object_id); - if (object == m_objects_metadata.end()) { - add_error("Cannot assign volume to a valid object"); - return false; - } - - m_curr_config.volume_id = (int)object->second.volumes.size(); - - unsigned int first_triangle_id = (unsigned int)get_attribute_value_int(attributes, num_attributes, FIRST_TRIANGLE_ID_ATTR); - unsigned int last_triangle_id = (unsigned int)get_attribute_value_int(attributes, num_attributes, LAST_TRIANGLE_ID_ATTR); - - object->second.volumes.emplace_back(first_triangle_id, last_triangle_id); - return true; - } - - bool _3MF_Importer::_handle_start_config_volume_mesh(const char** attributes, unsigned int num_attributes) - { - IdToMetadataMap::iterator object = m_objects_metadata.find(m_curr_config.object_id); - if (object == m_objects_metadata.end()) { - add_error("Cannot assign volume mesh to a valid object"); - return false; - } - if (object->second.volumes.empty()) { - add_error("Cannot assign mesh to a valid olume"); - return false; - } - - ObjectMetadata::VolumeMetadata& volume = object->second.volumes.back(); - - int edges_fixed = get_attribute_value_int(attributes, num_attributes, MESH_STAT_EDGES_FIXED ); - int degenerate_facets = get_attribute_value_int(attributes, num_attributes, MESH_STAT_DEGENERATED_FACETS); - int facets_removed = get_attribute_value_int(attributes, num_attributes, MESH_STAT_FACETS_REMOVED ); - int facets_reversed = get_attribute_value_int(attributes, num_attributes, MESH_STAT_FACETS_RESERVED ); - int backwards_edges = get_attribute_value_int(attributes, num_attributes, MESH_STAT_BACKWARDS_EDGES ); - - volume.mesh_stats = { edges_fixed, degenerate_facets, facets_removed, facets_reversed, backwards_edges }; - - return true; - } - - bool _3MF_Importer::_handle_end_config_volume() - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_end_config_volume_mesh() - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_start_config_metadata(const char** attributes, unsigned int num_attributes) - { - IdToMetadataMap::iterator object = m_objects_metadata.find(m_curr_config.object_id); - if (object == m_objects_metadata.end()) { - add_error("Cannot assign metadata to valid object id"); - return false; - } - - std::string type = get_attribute_value_string(attributes, num_attributes, TYPE_ATTR); - std::string key = get_attribute_value_string(attributes, num_attributes, KEY_ATTR); - std::string value = get_attribute_value_string(attributes, num_attributes, VALUE_ATTR); - - if (type == OBJECT_TYPE) - object->second.metadata.emplace_back(key, value); - else if (type == VOLUME_TYPE) { - if (size_t(m_curr_config.volume_id) < object->second.volumes.size()) - object->second.volumes[m_curr_config.volume_id].metadata.emplace_back(key, value); - } - else { - add_error("Found invalid metadata type"); - return false; - } - - return true; - } - - bool _3MF_Importer::_handle_end_config_metadata() - { - // do nothing - return true; - } - - bool _3MF_Importer::_generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions) - { - if (!object.volumes.empty()) { - add_error("Found invalid volumes count"); - return false; - } - - unsigned int geo_tri_count = (unsigned int)geometry.triangles.size(); - unsigned int renamed_volumes_count = 0; - - for (const ObjectMetadata::VolumeMetadata& volume_data : volumes) { - if (geo_tri_count <= volume_data.first_triangle_id || geo_tri_count <= volume_data.last_triangle_id || volume_data.last_triangle_id < volume_data.first_triangle_id) { - add_error("Found invalid triangle id"); - return false; - } - - Transform3d volume_matrix_to_object = Transform3d::Identity(); - bool has_transform = false; - // extract the volume transformation from the volume's metadata, if present - for (const Metadata& metadata : volume_data.metadata) { - if (metadata.key == MATRIX_KEY) { - volume_matrix_to_object = Slic3r::Geometry::transform3d_from_string(metadata.value); - has_transform = ! volume_matrix_to_object.isApprox(Transform3d::Identity(), 1e-10); - break; - } - } - - // splits volume out of imported geometry - indexed_triangle_set its; - its.indices.assign(geometry.triangles.begin() + volume_data.first_triangle_id, geometry.triangles.begin() + volume_data.last_triangle_id + 1); - const size_t triangles_count = its.indices.size(); - if (triangles_count == 0) { - add_error("An empty triangle mesh found"); - return false; - } - - { - int min_id = its.indices.front()[0]; - int max_id = min_id; - for (const Vec3i& face : its.indices) { - for (const int tri_id : face) { - if (tri_id < 0 || tri_id >= int(geometry.vertices.size())) { - add_error("Found invalid vertex id"); - return false; - } - min_id = std::min(min_id, tri_id); - max_id = std::max(max_id, tri_id); - } - } - its.vertices.assign(geometry.vertices.begin() + min_id, geometry.vertices.begin() + max_id + 1); - - // rebase indices to the current vertices list - for (Vec3i& face : its.indices) - for (int& tri_id : face) - tri_id -= min_id; - } - - if (m_prusaslicer_generator_version && - *m_prusaslicer_generator_version >= *Semver::parse("2.4.0-alpha1") && - *m_prusaslicer_generator_version < *Semver::parse("2.4.0-alpha3")) - // PrusaSlicer 2.4.0-alpha2 contained a bug, where all vertices of a single object were saved for each volume the object contained. - // Remove the vertices, that are not referenced by any face. - its_compactify_vertices(its, true); - - TriangleMesh triangle_mesh(std::move(its), volume_data.mesh_stats); - - if (m_version == 0) { - // if the 3mf was not produced by PrusaSlicer and there is only one instance, - // bake the transformation into the geometry to allow the reload from disk command - // to work properly - if (object.instances.size() == 1) { - triangle_mesh.transform(object.instances.front()->get_transformation().get_matrix(), false); - object.instances.front()->set_transformation(Slic3r::Geometry::Transformation()); - //FIXME do the mesh fixing? - } - } - if (triangle_mesh.volume() < 0) - triangle_mesh.flip_triangles(); - - ModelVolume* volume = object.add_volume(std::move(triangle_mesh)); - // stores the volume matrix taken from the metadata, if present - if (has_transform) - volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object); - - // recreate custom supports, seam and mmu segmentation from previously loaded attribute - volume->supported_facets.reserve(triangles_count); - volume->seam_facets.reserve(triangles_count); - volume->mmu_segmentation_facets.reserve(triangles_count); - for (size_t i=0; isupported_facets.set_triangle_from_string(i, geometry.custom_supports[index]); - if (! geometry.custom_seam[index].empty()) - volume->seam_facets.set_triangle_from_string(i, geometry.custom_seam[index]); - if (! geometry.mmu_segmentation[index].empty()) - volume->mmu_segmentation_facets.set_triangle_from_string(i, geometry.mmu_segmentation[index]); - } - volume->supported_facets.shrink_to_fit(); - volume->seam_facets.shrink_to_fit(); - volume->mmu_segmentation_facets.shrink_to_fit(); - - // apply the remaining volume's metadata - for (const Metadata& metadata : volume_data.metadata) { - if (metadata.key == NAME_KEY) - volume->name = metadata.value; - else if ((metadata.key == MODIFIER_KEY) && (metadata.value == "1")) - volume->set_type(ModelVolumeType::PARAMETER_MODIFIER); - else if (metadata.key == VOLUME_TYPE_KEY) - volume->set_type(ModelVolume::type_from_string(metadata.value)); - else if (metadata.key == SOURCE_FILE_KEY) - volume->source.input_file = metadata.value; - else if (metadata.key == SOURCE_OBJECT_ID_KEY) - volume->source.object_idx = ::atoi(metadata.value.c_str()); - else if (metadata.key == SOURCE_VOLUME_ID_KEY) - volume->source.volume_idx = ::atoi(metadata.value.c_str()); - else if (metadata.key == SOURCE_OFFSET_X_KEY) - volume->source.mesh_offset.x() = ::atof(metadata.value.c_str()); - else if (metadata.key == SOURCE_OFFSET_Y_KEY) - volume->source.mesh_offset.y() = ::atof(metadata.value.c_str()); - else if (metadata.key == SOURCE_OFFSET_Z_KEY) - volume->source.mesh_offset.z() = ::atof(metadata.value.c_str()); - else if (metadata.key == SOURCE_IN_INCHES_KEY) - volume->source.is_converted_from_inches = metadata.value == "1"; - else if (metadata.key == SOURCE_IN_METERS_KEY) - volume->source.is_converted_from_meters = metadata.value == "1"; -#if ENABLE_RELOAD_FROM_DISK_REWORK - else if (metadata.key == SOURCE_IS_BUILTIN_VOLUME_KEY) - volume->source.is_from_builtin_objects = metadata.value == "1"; -#endif // ENABLE_RELOAD_FROM_DISK_REWORK - else - volume->config.set_deserialize(metadata.key, metadata.value, config_substitutions); - } - - // this may happen for 3mf saved by 3rd part softwares - if (volume->name.empty()) { - volume->name = object.name; - if (renamed_volumes_count > 0) - volume->name += "_" + std::to_string(renamed_volumes_count + 1); - ++renamed_volumes_count; - } - } - - return true; - } - - void XMLCALL _3MF_Importer::_handle_start_model_xml_element(void* userData, const char* name, const char** attributes) - { - _3MF_Importer* importer = (_3MF_Importer*)userData; - if (importer != nullptr) - importer->_handle_start_model_xml_element(name, attributes); - } - - void XMLCALL _3MF_Importer::_handle_end_model_xml_element(void* userData, const char* name) - { - _3MF_Importer* importer = (_3MF_Importer*)userData; - if (importer != nullptr) - importer->_handle_end_model_xml_element(name); - } - - void XMLCALL _3MF_Importer::_handle_model_xml_characters(void* userData, const XML_Char* s, int len) - { - _3MF_Importer* importer = (_3MF_Importer*)userData; - if (importer != nullptr) - importer->_handle_model_xml_characters(s, len); - } - - void XMLCALL _3MF_Importer::_handle_start_config_xml_element(void* userData, const char* name, const char** attributes) - { - _3MF_Importer* importer = (_3MF_Importer*)userData; - if (importer != nullptr) - importer->_handle_start_config_xml_element(name, attributes); - } - - void XMLCALL _3MF_Importer::_handle_end_config_xml_element(void* userData, const char* name) - { - _3MF_Importer* importer = (_3MF_Importer*)userData; - if (importer != nullptr) - importer->_handle_end_config_xml_element(name); - } - - class _3MF_Exporter : public _3MF_Base - { - struct BuildItem - { - unsigned int id; - Transform3d transform; - bool printable; - - BuildItem(unsigned int id, const Transform3d& transform, const bool printable) - : id(id) - , transform(transform) - , printable(printable) - { - } - }; - - struct Offsets - { - unsigned int first_vertex_id; - unsigned int first_triangle_id; - unsigned int last_triangle_id; - - Offsets(unsigned int first_vertex_id) - : first_vertex_id(first_vertex_id) - , first_triangle_id(-1) - , last_triangle_id(-1) - { - } - }; - - typedef std::map VolumeToOffsetsMap; - - struct ObjectData - { - ModelObject* object; - VolumeToOffsetsMap volumes_offsets; - - explicit ObjectData(ModelObject* object) - : object(object) - { - } - }; - - typedef std::vector BuildItemsList; - typedef std::map IdToObjectDataMap; - - bool m_fullpath_sources{ true }; - bool m_zip64 { true }; - - public: - bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64); - - private: - bool _save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data); - bool _add_content_types_file_to_archive(mz_zip_archive& archive); - bool _add_thumbnail_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data); - bool _add_relationships_file_to_archive(mz_zip_archive& archive); - bool _add_model_file_to_archive(const std::string& filename, mz_zip_archive& archive, const Model& model, IdToObjectDataMap& objects_data); - bool _add_object_to_model_stream(mz_zip_writer_staged_context &context, unsigned int& object_id, ModelObject& object, BuildItemsList& build_items, VolumeToOffsetsMap& volumes_offsets); - bool _add_mesh_to_object_stream(mz_zip_writer_staged_context &context, ModelObject& object, VolumeToOffsetsMap& volumes_offsets); - bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items); - bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model); - bool _add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model); - bool _add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model); - bool _add_sla_drain_holes_file_to_archive(mz_zip_archive& archive, Model& model); - bool _add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config); - bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, const IdToObjectDataMap &objects_data); - bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config); - }; - - bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64) - { - clear_errors(); - m_fullpath_sources = fullpath_sources; - m_zip64 = zip64; - return _save_model_to_file(filename, model, config, thumbnail_data); - } - - bool _3MF_Exporter::_save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data) - { - mz_zip_archive archive; - mz_zip_zero_struct(&archive); - - if (!open_zip_writer(&archive, filename)) { - add_error("Unable to open the file"); - return false; - } - - // Adds content types file ("[Content_Types].xml";). - // The content of this file is the same for each PrusaSlicer 3mf. - if (!_add_content_types_file_to_archive(archive)) { - close_zip_writer(&archive); - boost::filesystem::remove(filename); - return false; - } - - if (thumbnail_data != nullptr && thumbnail_data->is_valid()) { - // Adds the file Metadata/thumbnail.png. - if (!_add_thumbnail_file_to_archive(archive, *thumbnail_data)) { - close_zip_writer(&archive); - boost::filesystem::remove(filename); - return false; - } - } - - // Adds relationships file ("_rels/.rels"). - // The content of this file is the same for each PrusaSlicer 3mf. - // The relationshis file contains a reference to the geometry file "3D/3dmodel.model", the name was chosen to be compatible with CURA. - if (!_add_relationships_file_to_archive(archive)) { - close_zip_writer(&archive); - boost::filesystem::remove(filename); - return false; - } - - // Adds model file ("3D/3dmodel.model"). - // This is the one and only file that contains all the geometry (vertices and triangles) of all ModelVolumes. - IdToObjectDataMap objects_data; - if (!_add_model_file_to_archive(filename, archive, model, objects_data)) { - close_zip_writer(&archive); - boost::filesystem::remove(filename); - return false; - } - - // Adds layer height profile file ("Metadata/Slic3r_PE_layer_heights_profile.txt"). - // All layer height profiles of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model. - // The index differes from the index of an object ID of an object instance of a 3MF file! - if (!_add_layer_height_profile_file_to_archive(archive, model)) { - close_zip_writer(&archive); - boost::filesystem::remove(filename); - return false; - } - - // Adds layer config ranges file ("Metadata/Slic3r_PE_layer_config_ranges.txt"). - // All layer height profiles of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model. - // The index differes from the index of an object ID of an object instance of a 3MF file! - if (!_add_layer_config_ranges_file_to_archive(archive, model)) { - close_zip_writer(&archive); - boost::filesystem::remove(filename); - return false; - } - - // Adds sla support points file ("Metadata/Slic3r_PE_sla_support_points.txt"). - // All sla support points of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model. - // The index differes from the index of an object ID of an object instance of a 3MF file! - if (!_add_sla_support_points_file_to_archive(archive, model)) { - close_zip_writer(&archive); - boost::filesystem::remove(filename); - return false; - } - - if (!_add_sla_drain_holes_file_to_archive(archive, model)) { - close_zip_writer(&archive); - boost::filesystem::remove(filename); - return false; - } - - - // Adds custom gcode per height file ("Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml"). - // All custom gcode per height of whole Model are stored here - if (!_add_custom_gcode_per_print_z_file_to_archive(archive, model, config)) { - close_zip_writer(&archive); - boost::filesystem::remove(filename); - return false; - } - - // Adds slic3r print config file ("Metadata/Slic3r_PE.config"). - // This file contains the content of FullPrintConfing / SLAFullPrintConfig. - if (config != nullptr) { - if (!_add_print_config_file_to_archive(archive, *config)) { - close_zip_writer(&archive); - boost::filesystem::remove(filename); - return false; - } - } - - // Adds slic3r model config file ("Metadata/Slic3r_PE_model.config"). - // This file contains all the attributes of all ModelObjects and their ModelVolumes (names, parameter overrides). - // As there is just a single Indexed Triangle Set data stored per ModelObject, offsets of volumes into their respective Indexed Triangle Set data - // is stored here as well. - if (!_add_model_config_file_to_archive(archive, model, objects_data)) { - close_zip_writer(&archive); - boost::filesystem::remove(filename); - return false; - } - - if (!mz_zip_writer_finalize_archive(&archive)) { - close_zip_writer(&archive); - boost::filesystem::remove(filename); - add_error("Unable to finalize the archive"); - return false; - } - - close_zip_writer(&archive); - - return true; - } - - bool _3MF_Exporter::_add_content_types_file_to_archive(mz_zip_archive& archive) - { - std::stringstream stream; - stream << "\n"; - stream << "\n"; - stream << " \n"; - stream << " \n"; - stream << " \n"; - stream << ""; - - std::string out = stream.str(); - - if (!mz_zip_writer_add_mem(&archive, CONTENT_TYPES_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { - add_error("Unable to add content types file to archive"); - return false; - } - - return true; - } - - bool _3MF_Exporter::_add_thumbnail_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data) - { - bool res = false; - - size_t png_size = 0; - void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)thumbnail_data.pixels.data(), thumbnail_data.width, thumbnail_data.height, 4, &png_size, MZ_DEFAULT_LEVEL, 1); - if (png_data != nullptr) { - res = mz_zip_writer_add_mem(&archive, THUMBNAIL_FILE.c_str(), (const void*)png_data, png_size, MZ_DEFAULT_COMPRESSION); - mz_free(png_data); - } - - if (!res) - add_error("Unable to add thumbnail file to archive"); - - return res; - } - - bool _3MF_Exporter::_add_relationships_file_to_archive(mz_zip_archive& archive) - { - std::stringstream stream; - stream << "\n"; - stream << "\n"; - stream << " \n"; - stream << " \n"; - stream << ""; - - std::string out = stream.str(); - - if (!mz_zip_writer_add_mem(&archive, RELATIONSHIPS_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { - add_error("Unable to add relationships file to archive"); - return false; - } - - return true; - } - - static void reset_stream(std::stringstream &stream) - { - stream.str(""); - stream.clear(); - // https://en.cppreference.com/w/cpp/types/numeric_limits/max_digits10 - // Conversion of a floating-point value to text and back is exact as long as at least max_digits10 were used (9 for float, 17 for double). - // It is guaranteed to produce the same floating-point value, even though the intermediate text representation is not exact. - // The default value of std::stream precision is 6 digits only! - stream << std::setprecision(std::numeric_limits::max_digits10); - } - - bool _3MF_Exporter::_add_model_file_to_archive(const std::string& filename, mz_zip_archive& archive, const Model& model, IdToObjectDataMap& objects_data) - { - mz_zip_writer_staged_context context; - if (!mz_zip_writer_add_staged_open(&archive, &context, MODEL_FILE.c_str(), - m_zip64 ? - // Maximum expected and allowed 3MF file size is 16GiB. - // This switches the ZIP file to a 64bit mode, which adds a tiny bit of overhead to file records. - (uint64_t(1) << 30) * 16 : - // Maximum expected 3MF file size is 4GB-1. This is a workaround for interoperability with Windows 10 3D model fixing API, see - // GH issue #6193. - (uint64_t(1) << 32) - 1, - nullptr, nullptr, 0, MZ_DEFAULT_COMPRESSION, nullptr, 0, nullptr, 0)) { - add_error("Unable to add model file to archive"); - return false; - } - - { - std::stringstream stream; - reset_stream(stream); - stream << "\n"; - stream << "<" << MODEL_TAG << " unit=\"millimeter\" xml:lang=\"en-US\" xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\" xmlns:slic3rpe=\"http://schemas.slic3r.org/3mf/2017/06\">\n"; - stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_3MF_VERSION << "\">" << VERSION_3MF << "\n"; - - if (model.is_fdm_support_painted()) - stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_FDM_SUPPORTS_PAINTING_VERSION << "\">" << FDM_SUPPORTS_PAINTING_VERSION << "\n"; - - if (model.is_seam_painted()) - stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_SEAM_PAINTING_VERSION << "\">" << SEAM_PAINTING_VERSION << "\n"; - - if (model.is_mm_painted()) - stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_MM_PAINTING_VERSION << "\">" << MM_PAINTING_VERSION << "\n"; - - std::string name = xml_escape(boost::filesystem::path(filename).stem().string()); - stream << " <" << METADATA_TAG << " name=\"Title\">" << name << "\n"; - stream << " <" << METADATA_TAG << " name=\"Designer\">" << "\n"; - stream << " <" << METADATA_TAG << " name=\"Description\">" << name << "\n"; - stream << " <" << METADATA_TAG << " name=\"Copyright\">" << "\n"; - stream << " <" << METADATA_TAG << " name=\"LicenseTerms\">" << "\n"; - stream << " <" << METADATA_TAG << " name=\"Rating\">" << "\n"; - std::string date = Slic3r::Utils::utc_timestamp(Slic3r::Utils::get_current_time_utc()); - // keep only the date part of the string - date = date.substr(0, 10); - stream << " <" << METADATA_TAG << " name=\"CreationDate\">" << date << "\n"; - stream << " <" << METADATA_TAG << " name=\"ModificationDate\">" << date << "\n"; - stream << " <" << METADATA_TAG << " name=\"Application\">" << SLIC3R_APP_KEY << "-" << SLIC3R_VERSION << "\n"; - stream << " <" << RESOURCES_TAG << ">\n"; - std::string buf = stream.str(); - if (! buf.empty() && ! mz_zip_writer_add_staged_data(&context, buf.data(), buf.size())) { - add_error("Unable to add model file to archive"); - return false; - } - } - - // Instance transformations, indexed by the 3MF object ID (which is a linear serialization of all instances of all ModelObjects). - BuildItemsList build_items; - - // The object_id here is a one based identifier of the first instance of a ModelObject in the 3MF file, where - // all the object instances of all ModelObjects are stored and indexed in a 1 based linear fashion. - // Therefore the list of object_ids here may not be continuous. - unsigned int object_id = 1; - for (ModelObject* obj : model.objects) { - if (obj == nullptr) - continue; - - // Index of an object in the 3MF file corresponding to the 1st instance of a ModelObject. - unsigned int curr_id = object_id; - IdToObjectDataMap::iterator object_it = objects_data.insert({ curr_id, ObjectData(obj) }).first; - // Store geometry of all ModelVolumes contained in a single ModelObject into a single 3MF indexed triangle set object. - // object_it->second.volumes_offsets will contain the offsets of the ModelVolumes in that single indexed triangle set. - // object_id will be increased to point to the 1st instance of the next ModelObject. - if (!_add_object_to_model_stream(context, object_id, *obj, build_items, object_it->second.volumes_offsets)) { - add_error("Unable to add object to archive"); - mz_zip_writer_add_staged_finish(&context); - return false; - } - } - - { - std::stringstream stream; - reset_stream(stream); - stream << " \n"; - - // Store the transformations of all the ModelInstances of all ModelObjects, indexed in a linear fashion. - if (!_add_build_to_model_stream(stream, build_items)) { - add_error("Unable to add build to archive"); - mz_zip_writer_add_staged_finish(&context); - return false; - } - - stream << "\n"; - - std::string buf = stream.str(); - - if ((! buf.empty() && ! mz_zip_writer_add_staged_data(&context, buf.data(), buf.size())) || - ! mz_zip_writer_add_staged_finish(&context)) { - add_error("Unable to add model file to archive"); - return false; - } - } - - return true; - } - - bool _3MF_Exporter::_add_object_to_model_stream(mz_zip_writer_staged_context &context, unsigned int& object_id, ModelObject& object, BuildItemsList& build_items, VolumeToOffsetsMap& volumes_offsets) - { - std::stringstream stream; - reset_stream(stream); - unsigned int id = 0; - for (const ModelInstance* instance : object.instances) { - assert(instance != nullptr); - if (instance == nullptr) - continue; - - unsigned int instance_id = object_id + id; - stream << " <" << OBJECT_TAG << " id=\"" << instance_id << "\" type=\"model\">\n"; - - if (id == 0) { - std::string buf = stream.str(); - reset_stream(stream); - if ((! buf.empty() && ! mz_zip_writer_add_staged_data(&context, buf.data(), buf.size())) || - ! _add_mesh_to_object_stream(context, object, volumes_offsets)) { - add_error("Unable to add mesh to archive"); - return false; - } - } - else { - stream << " <" << COMPONENTS_TAG << ">\n"; - stream << " <" << COMPONENT_TAG << " objectid=\"" << object_id << "\"/>\n"; - stream << " \n"; - } - - Transform3d t = instance->get_matrix(); - // instance_id is just a 1 indexed index in build_items. - assert(instance_id == build_items.size() + 1); - build_items.emplace_back(instance_id, t, instance->printable); - - stream << " \n"; - - ++id; - } - - object_id += id; - std::string buf = stream.str(); - return buf.empty() || mz_zip_writer_add_staged_data(&context, buf.data(), buf.size()); - } - -#if EXPORT_3MF_USE_SPIRIT_KARMA_FP - template - struct coordinate_policy_fixed : boost::spirit::karma::real_policies - { - static int floatfield(Num n) { return fmtflags::fixed; } - // Number of decimal digits to maintain float accuracy when storing into a text file and parsing back. - static unsigned precision(Num /* n */) { return std::numeric_limits::max_digits10 + 1; } - // No trailing zeros, thus for fmtflags::fixed usually much less than max_digits10 decimal numbers will be produced. - static bool trailing_zeros(Num /* n */) { return false; } - }; - template - struct coordinate_policy_scientific : coordinate_policy_fixed - { - static int floatfield(Num n) { return fmtflags::scientific; } - }; - // Define a new generator type based on the new coordinate policy. - using coordinate_type_fixed = boost::spirit::karma::real_generator>; - using coordinate_type_scientific = boost::spirit::karma::real_generator>; -#endif // EXPORT_3MF_USE_SPIRIT_KARMA_FP - - bool _3MF_Exporter::_add_mesh_to_object_stream(mz_zip_writer_staged_context &context, ModelObject& object, VolumeToOffsetsMap& volumes_offsets) - { - std::string output_buffer; - output_buffer += " <"; - output_buffer += MESH_TAG; - output_buffer += ">\n <"; - output_buffer += VERTICES_TAG; - output_buffer += ">\n"; - - auto flush = [this, &output_buffer, &context](bool force = false) { - if ((force && ! output_buffer.empty()) || output_buffer.size() >= 65536 * 16) { - if (! mz_zip_writer_add_staged_data(&context, output_buffer.data(), output_buffer.size())) { - add_error("Error during writing or compression"); - return false; - } - output_buffer.clear(); - } - return true; - }; - - auto format_coordinate = [](float f, char *buf) -> char* { - assert(is_decimal_separator_point()); -#if EXPORT_3MF_USE_SPIRIT_KARMA_FP - // Slightly faster than sprintf("%.9g"), but there is an issue with the karma floating point formatter, - // https://github.com/boostorg/spirit/pull/586 - // where the exported string is one digit shorter than it should be to guarantee lossless round trip. - // The code is left here for the ocasion boost guys improve. - coordinate_type_fixed const coordinate_fixed = coordinate_type_fixed(); - coordinate_type_scientific const coordinate_scientific = coordinate_type_scientific(); - // Format "f" in a fixed format. - char *ptr = buf; - boost::spirit::karma::generate(ptr, coordinate_fixed, f); - // Format "f" in a scientific format. - char *ptr2 = ptr; - boost::spirit::karma::generate(ptr2, coordinate_scientific, f); - // Return end of the shorter string. - auto len2 = ptr2 - ptr; - if (ptr - buf > len2) { - // Move the shorter scientific form to the front. - memcpy(buf, ptr, len2); - ptr = buf + len2; - } - // Return pointer to the end. - return ptr; -#else - // Round-trippable float, shortest possible. - return buf + sprintf(buf, "%.9g", f); -#endif - }; - - char buf[256]; - unsigned int vertices_count = 0; - for (ModelVolume* volume : object.volumes) { - if (volume == nullptr) - continue; - - volumes_offsets.insert({ volume, Offsets(vertices_count) }); - - const indexed_triangle_set &its = volume->mesh().its; - if (its.vertices.empty()) { - add_error("Found invalid mesh"); - return false; - } - - vertices_count += (int)its.vertices.size(); - - const Transform3d& matrix = volume->get_matrix(); - - for (size_t i = 0; i < its.vertices.size(); ++i) { - Vec3f v = (matrix * its.vertices[i].cast()).cast(); - char *ptr = buf; - boost::spirit::karma::generate(ptr, boost::spirit::lit(" <") << VERTEX_TAG << " x=\""); - ptr = format_coordinate(v.x(), ptr); - boost::spirit::karma::generate(ptr, "\" y=\""); - ptr = format_coordinate(v.y(), ptr); - boost::spirit::karma::generate(ptr, "\" z=\""); - ptr = format_coordinate(v.z(), ptr); - boost::spirit::karma::generate(ptr, "\"/>\n"); - *ptr = '\0'; - output_buffer += buf; - if (! flush()) - return false; - } - } - - output_buffer += " \n <"; - output_buffer += TRIANGLES_TAG; - output_buffer += ">\n"; - - unsigned int triangles_count = 0; - for (ModelVolume* volume : object.volumes) { - if (volume == nullptr) - continue; - - bool is_left_handed = volume->is_left_handed(); - VolumeToOffsetsMap::iterator volume_it = volumes_offsets.find(volume); - assert(volume_it != volumes_offsets.end()); - - const indexed_triangle_set &its = volume->mesh().its; - - // updates triangle offsets - volume_it->second.first_triangle_id = triangles_count; - triangles_count += (int)its.indices.size(); - volume_it->second.last_triangle_id = triangles_count - 1; - - for (int i = 0; i < int(its.indices.size()); ++ i) { - { - const Vec3i &idx = its.indices[i]; - char *ptr = buf; - boost::spirit::karma::generate(ptr, boost::spirit::lit(" <") << TRIANGLE_TAG << - " v1=\"" << boost::spirit::int_ << - "\" v2=\"" << boost::spirit::int_ << - "\" v3=\"" << boost::spirit::int_ << "\"", - idx[is_left_handed ? 2 : 0] + volume_it->second.first_vertex_id, - idx[1] + volume_it->second.first_vertex_id, - idx[is_left_handed ? 0 : 2] + volume_it->second.first_vertex_id); - *ptr = '\0'; - output_buffer += buf; - } - - std::string custom_supports_data_string = volume->supported_facets.get_triangle_as_string(i); - if (! custom_supports_data_string.empty()) { - output_buffer += " "; - output_buffer += CUSTOM_SUPPORTS_ATTR; - output_buffer += "=\""; - output_buffer += custom_supports_data_string; - output_buffer += "\""; - } - - std::string custom_seam_data_string = volume->seam_facets.get_triangle_as_string(i); - if (! custom_seam_data_string.empty()) { - output_buffer += " "; - output_buffer += CUSTOM_SEAM_ATTR; - output_buffer += "=\""; - output_buffer += custom_seam_data_string; - output_buffer += "\""; - } - - std::string mmu_painting_data_string = volume->mmu_segmentation_facets.get_triangle_as_string(i); - if (! mmu_painting_data_string.empty()) { - output_buffer += " "; - output_buffer += MMU_SEGMENTATION_ATTR; - output_buffer += "=\""; - output_buffer += mmu_painting_data_string; - output_buffer += "\""; - } - - output_buffer += "/>\n"; - - if (! flush()) - return false; - } - } - - output_buffer += " \n \n"; - - // Force flush. - return flush(true); - } - - bool _3MF_Exporter::_add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items) - { - // This happens for empty projects - if (build_items.size() == 0) { - add_error("No build item found"); - return true; - } - - stream << " <" << BUILD_TAG << ">\n"; - - for (const BuildItem& item : build_items) { - stream << " <" << ITEM_TAG << " " << OBJECTID_ATTR << "=\"" << item.id << "\" " << TRANSFORM_ATTR << "=\""; - for (unsigned c = 0; c < 4; ++c) { - for (unsigned r = 0; r < 3; ++r) { - stream << item.transform(r, c); - if (r != 2 || c != 3) - stream << " "; - } - } - stream << "\" " << PRINTABLE_ATTR << "=\"" << item.printable << "\"/>\n"; - } - - stream << " \n"; - - return true; - } - - bool _3MF_Exporter::_add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model) - { - assert(is_decimal_separator_point()); - std::string out = ""; - char buffer[1024]; - - unsigned int count = 0; - for (const ModelObject* object : model.objects) { - ++count; - const std::vector& layer_height_profile = object->layer_height_profile.get(); - if (layer_height_profile.size() >= 4 && layer_height_profile.size() % 2 == 0) { - sprintf(buffer, "object_id=%d|", count); - out += buffer; - - // Store the layer height profile as a single semicolon separated list. - for (size_t i = 0; i < layer_height_profile.size(); ++i) { - sprintf(buffer, (i == 0) ? "%f" : ";%f", layer_height_profile[i]); - out += buffer; - } - - out += "\n"; - } - } - - if (!out.empty()) { - if (!mz_zip_writer_add_mem(&archive, LAYER_HEIGHTS_PROFILE_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { - add_error("Unable to add layer heights profile file to archive"); - return false; - } - } - - return true; - } - - bool _3MF_Exporter::_add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model) - { - std::string out = ""; - pt::ptree tree; - - unsigned int object_cnt = 0; - for (const ModelObject* object : model.objects) { - object_cnt++; - const t_layer_config_ranges& ranges = object->layer_config_ranges; - if (!ranges.empty()) - { - pt::ptree& obj_tree = tree.add("objects.object",""); - - obj_tree.put(".id", object_cnt); - - // Store the layer config ranges. - for (const auto& range : ranges) { - pt::ptree& range_tree = obj_tree.add("range", ""); - - // store minX and maxZ - range_tree.put(".min_z", range.first.first); - range_tree.put(".max_z", range.first.second); - - // store range configuration - const ModelConfig& config = range.second; - for (const std::string& opt_key : config.keys()) { - pt::ptree& opt_tree = range_tree.add("option", config.opt_serialize(opt_key)); - opt_tree.put(".opt_key", opt_key); - } - } - } - } - - if (!tree.empty()) { - std::ostringstream oss; - pt::write_xml(oss, tree); - out = oss.str(); - - // Post processing("beautification") of the output string for a better preview - boost::replace_all(out, ">\n \n \n ", ">\n "); - boost::replace_all(out, ">", ">\n "); - // OR just - boost::replace_all(out, "><", ">\n<"); - } - - if (!out.empty()) { - if (!mz_zip_writer_add_mem(&archive, LAYER_CONFIG_RANGES_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { - add_error("Unable to add layer heights profile file to archive"); - return false; - } - } - - return true; - } - - bool _3MF_Exporter::_add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model) - { - assert(is_decimal_separator_point()); - std::string out = ""; - char buffer[1024]; - - unsigned int count = 0; - for (const ModelObject* object : model.objects) { - ++count; - const std::vector& sla_support_points = object->sla_support_points; - if (!sla_support_points.empty()) { - sprintf(buffer, "object_id=%d|", count); - out += buffer; - - // Store the layer height profile as a single space separated list. - for (size_t i = 0; i < sla_support_points.size(); ++i) { - sprintf(buffer, (i==0 ? "%f %f %f %f %f" : " %f %f %f %f %f"), sla_support_points[i].pos(0), sla_support_points[i].pos(1), sla_support_points[i].pos(2), sla_support_points[i].head_front_radius, (float)sla_support_points[i].is_new_island); - out += buffer; - } - out += "\n"; - } - } - - if (!out.empty()) { - // Adds version header at the beginning: - out = std::string("support_points_format_version=") + std::to_string(support_points_format_version) + std::string("\n") + out; - - if (!mz_zip_writer_add_mem(&archive, SLA_SUPPORT_POINTS_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { - add_error("Unable to add sla support points file to archive"); - return false; - } - } - return true; - } - - bool _3MF_Exporter::_add_sla_drain_holes_file_to_archive(mz_zip_archive& archive, Model& model) - { - assert(is_decimal_separator_point()); - const char *const fmt = "object_id=%d|"; - std::string out; - - unsigned int count = 0; - for (const ModelObject* object : model.objects) { - ++count; - sla::DrainHoles drain_holes = object->sla_drain_holes; - - // The holes were placed 1mm above the mesh in the first implementation. - // This was a bad idea and the reference point was changed in 2.3 so - // to be on the mesh exactly. The elevated position is still saved - // in 3MFs for compatibility reasons. - for (sla::DrainHole& hole : drain_holes) { - hole.pos -= hole.normal.normalized(); - hole.height += 1.f; - } - - if (!drain_holes.empty()) { - out += string_printf(fmt, count); - - // Store the layer height profile as a single space separated list. - for (size_t i = 0; i < drain_holes.size(); ++i) - out += string_printf((i == 0 ? "%f %f %f %f %f %f %f %f" : " %f %f %f %f %f %f %f %f"), - drain_holes[i].pos(0), - drain_holes[i].pos(1), - drain_holes[i].pos(2), - drain_holes[i].normal(0), - drain_holes[i].normal(1), - drain_holes[i].normal(2), - drain_holes[i].radius, - drain_holes[i].height); - - out += "\n"; - } - } - - if (!out.empty()) { - // Adds version header at the beginning: - out = std::string("drain_holes_format_version=") + std::to_string(drain_holes_format_version) + std::string("\n") + out; - - if (!mz_zip_writer_add_mem(&archive, SLA_DRAIN_HOLES_FILE.c_str(), static_cast(out.data()), out.length(), mz_uint(MZ_DEFAULT_COMPRESSION))) { - add_error("Unable to add sla support points file to archive"); - return false; - } - } - return true; - } - - bool _3MF_Exporter::_add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config) - { - assert(is_decimal_separator_point()); - char buffer[1024]; - sprintf(buffer, "; %s\n\n", header_slic3r_generated().c_str()); - std::string out = buffer; - - for (const std::string &key : config.keys()) - if (key != "compatible_printers") - out += "; " + key + " = " + config.opt_serialize(key) + "\n"; - - if (!out.empty()) { - if (!mz_zip_writer_add_mem(&archive, PRINT_CONFIG_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { - add_error("Unable to add print config file to archive"); - return false; - } - } - - return true; - } - - bool _3MF_Exporter::_add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, const IdToObjectDataMap &objects_data) - { - std::stringstream stream; - // Store mesh transformation in full precision, as the volumes are stored transformed and they need to be transformed back - // when loaded as accurately as possible. - stream << std::setprecision(std::numeric_limits::max_digits10); - stream << "\n"; - stream << "<" << CONFIG_TAG << ">\n"; - - for (const IdToObjectDataMap::value_type& obj_metadata : objects_data) { - const ModelObject* obj = obj_metadata.second.object; - if (obj != nullptr) { - // Output of instances count added because of github #3435, currently not used by PrusaSlicer - stream << " <" << OBJECT_TAG << " " << ID_ATTR << "=\"" << obj_metadata.first << "\" " << INSTANCESCOUNT_ATTR << "=\"" << obj->instances.size() << "\">\n"; - - // stores object's name - if (!obj->name.empty()) - stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << OBJECT_TYPE << "\" " << KEY_ATTR << "=\"name\" " << VALUE_ATTR << "=\"" << xml_escape(obj->name) << "\"/>\n"; - - // stores object's config data - for (const std::string& key : obj->config.keys()) { - stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << OBJECT_TYPE << "\" " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << obj->config.opt_serialize(key) << "\"/>\n"; - } - - for (const ModelVolume* volume : obj_metadata.second.object->volumes) { - if (volume != nullptr) { - const VolumeToOffsetsMap& offsets = obj_metadata.second.volumes_offsets; - VolumeToOffsetsMap::const_iterator it = offsets.find(volume); - if (it != offsets.end()) { - // stores volume's offsets - stream << " <" << VOLUME_TAG << " "; - stream << FIRST_TRIANGLE_ID_ATTR << "=\"" << it->second.first_triangle_id << "\" "; - stream << LAST_TRIANGLE_ID_ATTR << "=\"" << it->second.last_triangle_id << "\">\n"; - - // stores volume's name - if (!volume->name.empty()) - stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << NAME_KEY << "\" " << VALUE_ATTR << "=\"" << xml_escape(volume->name) << "\"/>\n"; - - // stores volume's modifier field (legacy, to support old slicers) - if (volume->is_modifier()) - stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << MODIFIER_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n"; - // stores volume's type (overrides the modifier field above) - stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << VOLUME_TYPE_KEY << "\" " << - VALUE_ATTR << "=\"" << ModelVolume::type_to_string(volume->type()) << "\"/>\n"; - - // stores volume's local matrix - stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << MATRIX_KEY << "\" " << VALUE_ATTR << "=\""; - const Transform3d matrix = volume->get_matrix() * volume->source.transform.get_matrix(); - for (int r = 0; r < 4; ++r) { - for (int c = 0; c < 4; ++c) { - stream << matrix(r, c); - if (r != 3 || c != 3) - stream << " "; - } - } - stream << "\"/>\n"; - - // stores volume's source data - { - std::string input_file = xml_escape(m_fullpath_sources ? volume->source.input_file : boost::filesystem::path(volume->source.input_file).filename().string()); - std::string prefix = std::string(" <") + METADATA_TAG + " " + TYPE_ATTR + "=\"" + VOLUME_TYPE + "\" " + KEY_ATTR + "=\""; - if (! volume->source.input_file.empty()) { - stream << prefix << SOURCE_FILE_KEY << "\" " << VALUE_ATTR << "=\"" << input_file << "\"/>\n"; - stream << prefix << SOURCE_OBJECT_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.object_idx << "\"/>\n"; - stream << prefix << SOURCE_VOLUME_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.volume_idx << "\"/>\n"; - stream << prefix << SOURCE_OFFSET_X_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(0) << "\"/>\n"; - stream << prefix << SOURCE_OFFSET_Y_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(1) << "\"/>\n"; - stream << prefix << SOURCE_OFFSET_Z_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(2) << "\"/>\n"; - } - assert(! volume->source.is_converted_from_inches || ! volume->source.is_converted_from_meters); - if (volume->source.is_converted_from_inches) - stream << prefix << SOURCE_IN_INCHES_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n"; - else if (volume->source.is_converted_from_meters) - stream << prefix << SOURCE_IN_METERS_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n"; -#if ENABLE_RELOAD_FROM_DISK_REWORK - if (volume->source.is_from_builtin_objects) - stream << prefix << SOURCE_IS_BUILTIN_VOLUME_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n"; -#endif // ENABLE_RELOAD_FROM_DISK_REWORK - } - - // stores volume's config data - for (const std::string& key : volume->config.keys()) { - stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << volume->config.opt_serialize(key) << "\"/>\n"; - } - - // stores mesh's statistics - const RepairedMeshErrors& stats = volume->mesh().stats().repaired_errors; - stream << " <" << MESH_TAG << " "; - stream << MESH_STAT_EDGES_FIXED << "=\"" << stats.edges_fixed << "\" "; - stream << MESH_STAT_DEGENERATED_FACETS << "=\"" << stats.degenerate_facets << "\" "; - stream << MESH_STAT_FACETS_REMOVED << "=\"" << stats.facets_removed << "\" "; - stream << MESH_STAT_FACETS_RESERVED << "=\"" << stats.facets_reversed << "\" "; - stream << MESH_STAT_BACKWARDS_EDGES << "=\"" << stats.backwards_edges << "\"/>\n"; - - stream << " \n"; - } - } - } - - stream << " \n"; - } - } - - stream << "\n"; - - std::string out = stream.str(); - - if (!mz_zip_writer_add_mem(&archive, MODEL_CONFIG_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { - add_error("Unable to add model config file to archive"); - return false; - } - - return true; - } - -bool _3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config) -{ - std::string out = ""; - - if (!model.custom_gcode_per_print_z.gcodes.empty()) { - pt::ptree tree; - pt::ptree& main_tree = tree.add("custom_gcodes_per_print_z", ""); - - for (const CustomGCode::Item& code : model.custom_gcode_per_print_z.gcodes) { - pt::ptree& code_tree = main_tree.add("code", ""); - - // store data of custom_gcode_per_print_z - code_tree.put(".print_z" , code.print_z ); - code_tree.put(".type" , static_cast(code.type)); - code_tree.put(".extruder" , code.extruder ); - code_tree.put(".color" , code.color ); - code_tree.put(".extra" , code.extra ); - - // add gcode field data for the old version of the PrusaSlicer - std::string gcode = code.type == CustomGCode::ColorChange ? config->opt_string("color_change_gcode") : - code.type == CustomGCode::PausePrint ? config->opt_string("pause_print_gcode") : - code.type == CustomGCode::Template ? config->opt_string("template_custom_gcode") : - code.type == CustomGCode::ToolChange ? "tool_change" : code.extra; - code_tree.put(".gcode" , gcode ); - } - - pt::ptree& mode_tree = main_tree.add("mode", ""); - // store mode of a custom_gcode_per_print_z - mode_tree.put(".value", model.custom_gcode_per_print_z.mode == CustomGCode::Mode::SingleExtruder ? CustomGCode::SingleExtruderMode : - model.custom_gcode_per_print_z.mode == CustomGCode::Mode::MultiAsSingle ? CustomGCode::MultiAsSingleMode : - CustomGCode::MultiExtruderMode); - - if (!tree.empty()) { - std::ostringstream oss; - boost::property_tree::write_xml(oss, tree); - out = oss.str(); - - // Post processing("beautification") of the output string - boost::replace_all(out, "><", ">\n<"); - } - } - - if (!out.empty()) { - if (!mz_zip_writer_add_mem(&archive, CUSTOM_GCODE_PER_PRINT_Z_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { - add_error("Unable to add custom Gcodes per print_z file to archive"); - return false; - } - } - - return true; -} - -// Perform conversions based on the config values available. -//FIXME provide a version of PrusaSlicer that stored the project file (3MF). -static void handle_legacy_project_loaded(unsigned int version_project_file, DynamicPrintConfig& config) -{ - if (! config.has("brim_separation")) { - if (auto *opt_elephant_foot = config.option("elefant_foot_compensation", false); opt_elephant_foot) { - // Conversion from older PrusaSlicer which applied brim separation equal to elephant foot compensation. - auto *opt_brim_separation = config.option("brim_separation", true); - opt_brim_separation->value = opt_elephant_foot->value; - } - } -} - -bool is_project_3mf(const std::string& filename) -{ - mz_zip_archive archive; - mz_zip_zero_struct(&archive); - - if (!open_zip_reader(&archive, filename)) - return false; - - mz_uint num_entries = mz_zip_reader_get_num_files(&archive); - - // loop the entries to search for config - mz_zip_archive_file_stat stat; - bool config_found = false; - for (mz_uint i = 0; i < num_entries; ++i) { - if (mz_zip_reader_file_stat(&archive, i, &stat)) { - std::string name(stat.m_filename); - std::replace(name.begin(), name.end(), '\\', '/'); - - if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE)) { - config_found = true; - break; - } - } - } - - close_zip_reader(&archive); - - return config_found; -} - -bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model* model, bool check_version) -{ - if (path == nullptr || model == nullptr) - return false; - - // All import should use "C" locales for number formatting. - CNumericLocalesSetter locales_setter; - _3MF_Importer importer; - bool res = importer.load_model_from_file(path, *model, config, config_substitutions, check_version); - importer.log_errors(); - handle_legacy_project_loaded(importer.version(), config); - return res; -} - -bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64) -{ - // All export should use "C" locales for number formatting. - CNumericLocalesSetter locales_setter; - - if (path == nullptr || model == nullptr) - return false; - - _3MF_Exporter exporter; - bool res = exporter.save_model_to_file(path, *model, config, fullpath_sources, thumbnail_data, zip64); - if (!res) - exporter.log_errors(); - - return res; -} -} // namespace Slic3r +#include "../libslic3r.h" +#include "../Exception.hpp" +#include "../Model.hpp" +#include "../Utils.hpp" +#include "../LocalesUtils.hpp" +#include "../GCode.hpp" +#include "../Geometry.hpp" +#include "../GCode/ThumbnailData.hpp" +#include "../Semver.hpp" +#include "../Time.hpp" + +#include "../I18N.hpp" + +#include "3mf.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +namespace pt = boost::property_tree; + +#include +#include +#include "miniz_extension.hpp" + +#include + +// Slightly faster than sprintf("%.9g"), but there is an issue with the karma floating point formatter, +// https://github.com/boostorg/spirit/pull/586 +// where the exported string is one digit shorter than it should be to guarantee lossless round trip. +// The code is left here for the ocasion boost guys improve. +#define EXPORT_3MF_USE_SPIRIT_KARMA_FP 0 + +// VERSION NUMBERS +// 0 : .3mf, files saved by older slic3r or other applications. No version definition in them. +// 1 : Introduction of 3mf versioning. No other change in data saved into 3mf files. +// 2 : Volumes' matrices and source data added to Metadata/Slic3r_PE_model.config file, meshes transformed back to their coordinate system on loading. +// WARNING !! -> the version number has been rolled back to 1 +// the next change should use 3 +const unsigned int VERSION_3MF = 1; +// Allow loading version 2 file as well. +const unsigned int VERSION_3MF_COMPATIBLE = 2; +const char* SLIC3RPE_3MF_VERSION = "slic3rpe:Version3mf"; // definition of the metadata name saved into .model file + +// Painting gizmos data version numbers +// 0 : 3MF files saved by older PrusaSlicer or the painting gizmo wasn't used. No version definition in them. +// 1 : Introduction of painting gizmos data versioning. No other changes in painting gizmos data. +const unsigned int FDM_SUPPORTS_PAINTING_VERSION = 1; +const unsigned int SEAM_PAINTING_VERSION = 1; +const unsigned int MM_PAINTING_VERSION = 1; + +const std::string SLIC3RPE_FDM_SUPPORTS_PAINTING_VERSION = "slic3rpe:FdmSupportsPaintingVersion"; +const std::string SLIC3RPE_SEAM_PAINTING_VERSION = "slic3rpe:SeamPaintingVersion"; +const std::string SLIC3RPE_MM_PAINTING_VERSION = "slic3rpe:MmPaintingVersion"; + +const std::string MODEL_FOLDER = "3D/"; +const std::string MODEL_EXTENSION = ".model"; +const std::string MODEL_FILE = "3D/3dmodel.model"; // << this is the only format of the string which works with CURA +const std::string CONTENT_TYPES_FILE = "[Content_Types].xml"; +const std::string RELATIONSHIPS_FILE = "_rels/.rels"; +const std::string THUMBNAIL_FILE = "Metadata/thumbnail.png"; +const std::string PRINT_CONFIG_FILE = "Metadata/Slic3r_PE.config"; +const std::string MODEL_CONFIG_FILE = "Metadata/Slic3r_PE_model.config"; +const std::string LAYER_HEIGHTS_PROFILE_FILE = "Metadata/Slic3r_PE_layer_heights_profile.txt"; +const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/Prusa_Slicer_layer_config_ranges.xml"; +const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt"; +const std::string SLA_DRAIN_HOLES_FILE = "Metadata/Slic3r_PE_sla_drain_holes.txt"; +const std::string CUSTOM_GCODE_PER_PRINT_Z_FILE = "Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml"; + +static constexpr const char* MODEL_TAG = "model"; +static constexpr const char* RESOURCES_TAG = "resources"; +static constexpr const char* OBJECT_TAG = "object"; +static constexpr const char* MESH_TAG = "mesh"; +static constexpr const char* VERTICES_TAG = "vertices"; +static constexpr const char* VERTEX_TAG = "vertex"; +static constexpr const char* TRIANGLES_TAG = "triangles"; +static constexpr const char* TRIANGLE_TAG = "triangle"; +static constexpr const char* COMPONENTS_TAG = "components"; +static constexpr const char* COMPONENT_TAG = "component"; +static constexpr const char* BUILD_TAG = "build"; +static constexpr const char* ITEM_TAG = "item"; +static constexpr const char* METADATA_TAG = "metadata"; + +static constexpr const char* CONFIG_TAG = "config"; +static constexpr const char* VOLUME_TAG = "volume"; + +static constexpr const char* UNIT_ATTR = "unit"; +static constexpr const char* NAME_ATTR = "name"; +static constexpr const char* TYPE_ATTR = "type"; +static constexpr const char* ID_ATTR = "id"; +static constexpr const char* X_ATTR = "x"; +static constexpr const char* Y_ATTR = "y"; +static constexpr const char* Z_ATTR = "z"; +static constexpr const char* V1_ATTR = "v1"; +static constexpr const char* V2_ATTR = "v2"; +static constexpr const char* V3_ATTR = "v3"; +static constexpr const char* OBJECTID_ATTR = "objectid"; +static constexpr const char* TRANSFORM_ATTR = "transform"; +static constexpr const char* PRINTABLE_ATTR = "printable"; +static constexpr const char* INSTANCESCOUNT_ATTR = "instances_count"; +static constexpr const char* CUSTOM_SUPPORTS_ATTR = "slic3rpe:custom_supports"; +static constexpr const char* CUSTOM_SEAM_ATTR = "slic3rpe:custom_seam"; +static constexpr const char* MMU_SEGMENTATION_ATTR = "slic3rpe:mmu_segmentation"; + +static constexpr const char* KEY_ATTR = "key"; +static constexpr const char* VALUE_ATTR = "value"; +static constexpr const char* FIRST_TRIANGLE_ID_ATTR = "firstid"; +static constexpr const char* LAST_TRIANGLE_ID_ATTR = "lastid"; + +static constexpr const char* OBJECT_TYPE = "object"; +static constexpr const char* VOLUME_TYPE = "volume"; + +static constexpr const char* NAME_KEY = "name"; +static constexpr const char* MODIFIER_KEY = "modifier"; +static constexpr const char* VOLUME_TYPE_KEY = "volume_type"; +static constexpr const char* MATRIX_KEY = "matrix"; +static constexpr const char* SOURCE_FILE_KEY = "source_file"; +static constexpr const char* SOURCE_OBJECT_ID_KEY = "source_object_id"; +static constexpr const char* SOURCE_VOLUME_ID_KEY = "source_volume_id"; +static constexpr const char* SOURCE_OFFSET_X_KEY = "source_offset_x"; +static constexpr const char* SOURCE_OFFSET_Y_KEY = "source_offset_y"; +static constexpr const char* SOURCE_OFFSET_Z_KEY = "source_offset_z"; +static constexpr const char* SOURCE_IN_INCHES_KEY = "source_in_inches"; +static constexpr const char* SOURCE_IN_METERS_KEY = "source_in_meters"; +#if ENABLE_RELOAD_FROM_DISK_REWORK +static constexpr const char* SOURCE_IS_BUILTIN_VOLUME_KEY = "source_is_builtin_volume"; +#endif // ENABLE_RELOAD_FROM_DISK_REWORK + +static constexpr const char* MESH_STAT_EDGES_FIXED = "edges_fixed"; +static constexpr const char* MESH_STAT_DEGENERATED_FACETS = "degenerate_facets"; +static constexpr const char* MESH_STAT_FACETS_REMOVED = "facets_removed"; +static constexpr const char* MESH_STAT_FACETS_RESERVED = "facets_reversed"; +static constexpr const char* MESH_STAT_BACKWARDS_EDGES = "backwards_edges"; + + +const unsigned int VALID_OBJECT_TYPES_COUNT = 1; +const char* VALID_OBJECT_TYPES[] = +{ + "model" +}; + +const char* INVALID_OBJECT_TYPES[] = +{ + "solidsupport", + "support", + "surface", + "other" +}; + +class version_error : public Slic3r::FileIOError +{ +public: + version_error(const std::string& what_arg) : Slic3r::FileIOError(what_arg) {} + version_error(const char* what_arg) : Slic3r::FileIOError(what_arg) {} +}; + +const char* get_attribute_value_charptr(const char** attributes, unsigned int attributes_size, const char* attribute_key) +{ + if ((attributes == nullptr) || (attributes_size == 0) || (attributes_size % 2 != 0) || (attribute_key == nullptr)) + return nullptr; + + for (unsigned int a = 0; a < attributes_size; a += 2) { + if (::strcmp(attributes[a], attribute_key) == 0) + return attributes[a + 1]; + } + + return nullptr; +} + +std::string get_attribute_value_string(const char** attributes, unsigned int attributes_size, const char* attribute_key) +{ + const char* text = get_attribute_value_charptr(attributes, attributes_size, attribute_key); + return (text != nullptr) ? text : ""; +} + +float get_attribute_value_float(const char** attributes, unsigned int attributes_size, const char* attribute_key) +{ + float value = 0.0f; + if (const char *text = get_attribute_value_charptr(attributes, attributes_size, attribute_key); text != nullptr) + fast_float::from_chars(text, text + strlen(text), value); + return value; +} + +int get_attribute_value_int(const char** attributes, unsigned int attributes_size, const char* attribute_key) +{ + int value = 0; + if (const char *text = get_attribute_value_charptr(attributes, attributes_size, attribute_key); text != nullptr) + boost::spirit::qi::parse(text, text + strlen(text), boost::spirit::qi::int_, value); + return value; +} + +bool get_attribute_value_bool(const char** attributes, unsigned int attributes_size, const char* attribute_key) +{ + const char* text = get_attribute_value_charptr(attributes, attributes_size, attribute_key); + return (text != nullptr) ? (bool)::atoi(text) : true; +} + +Slic3r::Transform3d get_transform_from_3mf_specs_string(const std::string& mat_str) +{ + // check: https://3mf.io/3d-manufacturing-format/ or https://github.com/3MFConsortium/spec_core/blob/master/3MF%20Core%20Specification.md + // to see how matrices are stored inside 3mf according to specifications + Slic3r::Transform3d ret = Slic3r::Transform3d::Identity(); + + if (mat_str.empty()) + // empty string means default identity matrix + return ret; + + std::vector mat_elements_str; + boost::split(mat_elements_str, mat_str, boost::is_any_of(" "), boost::token_compress_on); + + unsigned int size = (unsigned int)mat_elements_str.size(); + if (size != 12) + // invalid data, return identity matrix + return ret; + + unsigned int i = 0; + // matrices are stored into 3mf files as 4x3 + // we need to transpose them + for (unsigned int c = 0; c < 4; ++c) { + for (unsigned int r = 0; r < 3; ++r) { + ret(r, c) = ::atof(mat_elements_str[i++].c_str()); + } + } + return ret; +} + +float get_unit_factor(const std::string& unit) +{ + const char* text = unit.c_str(); + + if (::strcmp(text, "micron") == 0) + return 0.001f; + else if (::strcmp(text, "centimeter") == 0) + return 10.0f; + else if (::strcmp(text, "inch") == 0) + return 25.4f; + else if (::strcmp(text, "foot") == 0) + return 304.8f; + else if (::strcmp(text, "meter") == 0) + return 1000.0f; + else + // default "millimeters" (see specification) + return 1.0f; +} + +bool is_valid_object_type(const std::string& type) +{ + // if the type is empty defaults to "model" (see specification) + if (type.empty()) + return true; + + for (unsigned int i = 0; i < VALID_OBJECT_TYPES_COUNT; ++i) { + if (::strcmp(type.c_str(), VALID_OBJECT_TYPES[i]) == 0) + return true; + } + + return false; +} + +namespace Slic3r { + +//! macro used to mark string used at localization, +//! return same string +#define L(s) (s) +#define _(s) Slic3r::I18N::translate(s) + + // Base class with error messages management + class _3MF_Base + { + std::vector m_errors; + + protected: + void add_error(const std::string& error) { m_errors.push_back(error); } + void clear_errors() { m_errors.clear(); } + + public: + void log_errors() + { + for (const std::string& error : m_errors) + BOOST_LOG_TRIVIAL(error) << error; + } + }; + + class _3MF_Importer : public _3MF_Base + { + struct Component + { + int object_id; + Transform3d transform; + + explicit Component(int object_id) + : object_id(object_id) + , transform(Transform3d::Identity()) + { + } + + Component(int object_id, const Transform3d& transform) + : object_id(object_id) + , transform(transform) + { + } + }; + + typedef std::vector ComponentsList; + + struct Geometry + { + std::vector vertices; + std::vector triangles; + std::vector custom_supports; + std::vector custom_seam; + std::vector mmu_segmentation; + + bool empty() { return vertices.empty() || triangles.empty(); } + + void reset() { + vertices.clear(); + triangles.clear(); + custom_supports.clear(); + custom_seam.clear(); + mmu_segmentation.clear(); + } + }; + + struct CurrentObject + { + // ID of the object inside the 3MF file, 1 based. + int id; + // Index of the ModelObject in its respective Model, zero based. + int model_object_idx; + Geometry geometry; + ModelObject* object; + ComponentsList components; + + CurrentObject() { reset(); } + + void reset() { + id = -1; + model_object_idx = -1; + geometry.reset(); + object = nullptr; + components.clear(); + } + }; + + struct CurrentConfig + { + int object_id; + int volume_id; + }; + + struct Instance + { + ModelInstance* instance; + Transform3d transform; + + Instance(ModelInstance* instance, const Transform3d& transform) + : instance(instance) + , transform(transform) + { + } + }; + + struct Metadata + { + std::string key; + std::string value; + + Metadata(const std::string& key, const std::string& value) + : key(key) + , value(value) + { + } + }; + + typedef std::vector MetadataList; + + struct ObjectMetadata + { + struct VolumeMetadata + { + unsigned int first_triangle_id; + unsigned int last_triangle_id; + MetadataList metadata; + RepairedMeshErrors mesh_stats; + + VolumeMetadata(unsigned int first_triangle_id, unsigned int last_triangle_id) + : first_triangle_id(first_triangle_id) + , last_triangle_id(last_triangle_id) + { + } + }; + + typedef std::vector VolumeMetadataList; + + MetadataList metadata; + VolumeMetadataList volumes; + }; + + // Map from a 1 based 3MF object ID to a 0 based ModelObject index inside m_model->objects. + typedef std::map IdToModelObjectMap; + typedef std::map IdToAliasesMap; + typedef std::vector InstancesList; + typedef std::map IdToMetadataMap; + typedef std::map IdToGeometryMap; + typedef std::map> IdToLayerHeightsProfileMap; + typedef std::map IdToLayerConfigRangesMap; + typedef std::map> IdToSlaSupportPointsMap; + typedef std::map> IdToSlaDrainHolesMap; + + // Version of the 3mf file + unsigned int m_version; + bool m_check_version; + + // Semantic version of PrusaSlicer, that generated this 3MF. + boost::optional m_prusaslicer_generator_version; + unsigned int m_fdm_supports_painting_version = 0; + unsigned int m_seam_painting_version = 0; + unsigned int m_mm_painting_version = 0; + + XML_Parser m_xml_parser; + // Error code returned by the application side of the parser. In that case the expat may not reliably deliver the error state + // after returning from XML_Parse() function, thus we keep the error state here. + bool m_parse_error { false }; + std::string m_parse_error_message; + Model* m_model; + float m_unit_factor; + CurrentObject m_curr_object; + IdToModelObjectMap m_objects; + IdToAliasesMap m_objects_aliases; + InstancesList m_instances; + IdToGeometryMap m_geometries; + CurrentConfig m_curr_config; + IdToMetadataMap m_objects_metadata; + IdToLayerHeightsProfileMap m_layer_heights_profiles; + IdToLayerConfigRangesMap m_layer_config_ranges; + IdToSlaSupportPointsMap m_sla_support_points; + IdToSlaDrainHolesMap m_sla_drain_holes; + std::string m_curr_metadata_name; + std::string m_curr_characters; + std::string m_name; + + public: + _3MF_Importer(); + ~_3MF_Importer(); + + bool load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, bool check_version); + unsigned int version() const { return m_version; } + + private: + void _destroy_xml_parser(); + void _stop_xml_parser(const std::string& msg = std::string()); + + bool parse_error() const { return m_parse_error; } + const char* parse_error_message() const { + return m_parse_error ? + // The error was signalled by the user code, not the expat parser. + (m_parse_error_message.empty() ? "Invalid 3MF format" : m_parse_error_message.c_str()) : + // The error was signalled by the expat parser. + XML_ErrorString(XML_GetErrorCode(m_xml_parser)); + } + + bool _load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions); + bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); + void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); + void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions); + void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); + void _extract_sla_drain_holes_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); + + void _extract_custom_gcode_per_print_z_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); + + void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, ConfigSubstitutionContext& subs_context, const std::string& archive_filename); + bool _extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model); + + // handlers to parse the .model file + void _handle_start_model_xml_element(const char* name, const char** attributes); + void _handle_end_model_xml_element(const char* name); + void _handle_model_xml_characters(const XML_Char* s, int len); + + // handlers to parse the MODEL_CONFIG_FILE file + void _handle_start_config_xml_element(const char* name, const char** attributes); + void _handle_end_config_xml_element(const char* name); + + bool _handle_start_model(const char** attributes, unsigned int num_attributes); + bool _handle_end_model(); + + bool _handle_start_resources(const char** attributes, unsigned int num_attributes); + bool _handle_end_resources(); + + bool _handle_start_object(const char** attributes, unsigned int num_attributes); + bool _handle_end_object(); + + bool _handle_start_mesh(const char** attributes, unsigned int num_attributes); + bool _handle_end_mesh(); + + bool _handle_start_vertices(const char** attributes, unsigned int num_attributes); + bool _handle_end_vertices(); + + bool _handle_start_vertex(const char** attributes, unsigned int num_attributes); + bool _handle_end_vertex(); + + bool _handle_start_triangles(const char** attributes, unsigned int num_attributes); + bool _handle_end_triangles(); + + bool _handle_start_triangle(const char** attributes, unsigned int num_attributes); + bool _handle_end_triangle(); + + bool _handle_start_components(const char** attributes, unsigned int num_attributes); + bool _handle_end_components(); + + bool _handle_start_component(const char** attributes, unsigned int num_attributes); + bool _handle_end_component(); + + bool _handle_start_build(const char** attributes, unsigned int num_attributes); + bool _handle_end_build(); + + bool _handle_start_item(const char** attributes, unsigned int num_attributes); + bool _handle_end_item(); + + bool _handle_start_metadata(const char** attributes, unsigned int num_attributes); + bool _handle_end_metadata(); + + bool _create_object_instance(int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter); + + void _apply_transform(ModelInstance& instance, const Transform3d& transform); + + bool _handle_start_config(const char** attributes, unsigned int num_attributes); + bool _handle_end_config(); + + bool _handle_start_config_object(const char** attributes, unsigned int num_attributes); + bool _handle_end_config_object(); + + bool _handle_start_config_volume(const char** attributes, unsigned int num_attributes); + bool _handle_start_config_volume_mesh(const char** attributes, unsigned int num_attributes); + bool _handle_end_config_volume(); + bool _handle_end_config_volume_mesh(); + + bool _handle_start_config_metadata(const char** attributes, unsigned int num_attributes); + bool _handle_end_config_metadata(); + + bool _generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions); + + // callbacks to parse the .model file + static void XMLCALL _handle_start_model_xml_element(void* userData, const char* name, const char** attributes); + static void XMLCALL _handle_end_model_xml_element(void* userData, const char* name); + static void XMLCALL _handle_model_xml_characters(void* userData, const XML_Char* s, int len); + + // callbacks to parse the MODEL_CONFIG_FILE file + static void XMLCALL _handle_start_config_xml_element(void* userData, const char* name, const char** attributes); + static void XMLCALL _handle_end_config_xml_element(void* userData, const char* name); + }; + + _3MF_Importer::_3MF_Importer() + : m_version(0) + , m_check_version(false) + , m_xml_parser(nullptr) + , m_model(nullptr) + , m_unit_factor(1.0f) + , m_curr_metadata_name("") + , m_curr_characters("") + , m_name("") + { + } + + _3MF_Importer::~_3MF_Importer() + { + _destroy_xml_parser(); + } + + bool _3MF_Importer::load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, bool check_version) + { + m_version = 0; + m_fdm_supports_painting_version = 0; + m_seam_painting_version = 0; + m_mm_painting_version = 0; + m_check_version = check_version; + m_model = &model; + m_unit_factor = 1.0f; + m_curr_object.reset(); + m_objects.clear(); + m_objects_aliases.clear(); + m_instances.clear(); + m_geometries.clear(); + m_curr_config.object_id = -1; + m_curr_config.volume_id = -1; + m_objects_metadata.clear(); + m_layer_heights_profiles.clear(); + m_layer_config_ranges.clear(); + m_sla_support_points.clear(); + m_curr_metadata_name.clear(); + m_curr_characters.clear(); + clear_errors(); + + return _load_model_from_file(filename, model, config, config_substitutions); + } + + void _3MF_Importer::_destroy_xml_parser() + { + if (m_xml_parser != nullptr) { + XML_ParserFree(m_xml_parser); + m_xml_parser = nullptr; + } + } + + void _3MF_Importer::_stop_xml_parser(const std::string &msg) + { + assert(! m_parse_error); + assert(m_parse_error_message.empty()); + assert(m_xml_parser != nullptr); + m_parse_error = true; + m_parse_error_message = msg; + XML_StopParser(m_xml_parser, false); + } + + bool _3MF_Importer::_load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions) + { + mz_zip_archive archive; + mz_zip_zero_struct(&archive); + + if (!open_zip_reader(&archive, filename)) { + add_error("Unable to open the file"); + return false; + } + + mz_uint num_entries = mz_zip_reader_get_num_files(&archive); + + mz_zip_archive_file_stat stat; + + m_name = boost::filesystem::path(filename).stem().string(); + + // we first loop the entries to read from the archive the .model file only, in order to extract the version from it + for (mz_uint i = 0; i < num_entries; ++i) { + if (mz_zip_reader_file_stat(&archive, i, &stat)) { + std::string name(stat.m_filename); + std::replace(name.begin(), name.end(), '\\', '/'); + + if (boost::algorithm::istarts_with(name, MODEL_FOLDER) && boost::algorithm::iends_with(name, MODEL_EXTENSION)) { + try + { + // valid model name -> extract model + if (!_extract_model_from_archive(archive, stat)) { + close_zip_reader(&archive); + add_error("Archive does not contain a valid model"); + return false; + } + } + catch (const std::exception& e) + { + // ensure the zip archive is closed and rethrow the exception + close_zip_reader(&archive); + throw Slic3r::FileIOError(e.what()); + } + } + } + } + + // we then loop again the entries to read other files stored in the archive + for (mz_uint i = 0; i < num_entries; ++i) { + if (mz_zip_reader_file_stat(&archive, i, &stat)) { + std::string name(stat.m_filename); + std::replace(name.begin(), name.end(), '\\', '/'); + + if (boost::algorithm::iequals(name, LAYER_HEIGHTS_PROFILE_FILE)) { + // extract slic3r layer heights profile file + _extract_layer_heights_profile_config_from_archive(archive, stat); + } + else if (boost::algorithm::iequals(name, LAYER_CONFIG_RANGES_FILE)) { + // extract slic3r layer config ranges file + _extract_layer_config_ranges_from_archive(archive, stat, config_substitutions); + } + else if (boost::algorithm::iequals(name, SLA_SUPPORT_POINTS_FILE)) { + // extract sla support points file + _extract_sla_support_points_from_archive(archive, stat); + } + else if (boost::algorithm::iequals(name, SLA_DRAIN_HOLES_FILE)) { + // extract sla support points file + _extract_sla_drain_holes_from_archive(archive, stat); + } + else if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE)) { + // extract slic3r print config file + _extract_print_config_from_archive(archive, stat, config, config_substitutions, filename); + } + else if (boost::algorithm::iequals(name, CUSTOM_GCODE_PER_PRINT_Z_FILE)) { + // extract slic3r layer config ranges file + _extract_custom_gcode_per_print_z_from_archive(archive, stat); + } + else if (boost::algorithm::iequals(name, MODEL_CONFIG_FILE)) { + // extract slic3r model config file + if (!_extract_model_config_from_archive(archive, stat, model)) { + close_zip_reader(&archive); + add_error("Archive does not contain a valid model config"); + return false; + } + } + } + } + + close_zip_reader(&archive); + + if (m_version == 0) { + // if the 3mf was not produced by PrusaSlicer and there is more than one instance, + // split the object in as many objects as instances + size_t curr_models_count = m_model->objects.size(); + size_t i = 0; + while (i < curr_models_count) { + ModelObject* model_object = m_model->objects[i]; + if (model_object->instances.size() > 1) { + // select the geometry associated with the original model object + const Geometry* geometry = nullptr; + for (const IdToModelObjectMap::value_type& object : m_objects) { + if (object.second == int(i)) { + IdToGeometryMap::const_iterator obj_geometry = m_geometries.find(object.first); + if (obj_geometry == m_geometries.end()) { + add_error("Unable to find object geometry"); + return false; + } + geometry = &obj_geometry->second; + break; + } + } + + if (geometry == nullptr) { + add_error("Unable to find object geometry"); + return false; + } + + // use the geometry to create the volumes in the new model objects + ObjectMetadata::VolumeMetadataList volumes(1, { 0, (unsigned int)geometry->triangles.size() - 1 }); + + // for each instance after the 1st, create a new model object containing only that instance + // and copy into it the geometry + while (model_object->instances.size() > 1) { + ModelObject* new_model_object = m_model->add_object(*model_object); + new_model_object->clear_instances(); + new_model_object->add_instance(*model_object->instances.back()); + model_object->delete_last_instance(); + if (!_generate_volumes(*new_model_object, *geometry, volumes, config_substitutions)) + return false; + } + } + ++i; + } + } + + for (const IdToModelObjectMap::value_type& object : m_objects) { + if (object.second >= int(m_model->objects.size())) { + add_error("Unable to find object"); + return false; + } + ModelObject* model_object = m_model->objects[object.second]; + IdToGeometryMap::const_iterator obj_geometry = m_geometries.find(object.first); + if (obj_geometry == m_geometries.end()) { + add_error("Unable to find object geometry"); + return false; + } + + // m_layer_heights_profiles are indexed by a 1 based model object index. + IdToLayerHeightsProfileMap::iterator obj_layer_heights_profile = m_layer_heights_profiles.find(object.second + 1); + if (obj_layer_heights_profile != m_layer_heights_profiles.end()) + model_object->layer_height_profile.set(std::move(obj_layer_heights_profile->second)); + + // m_layer_config_ranges are indexed by a 1 based model object index. + IdToLayerConfigRangesMap::iterator obj_layer_config_ranges = m_layer_config_ranges.find(object.second + 1); + if (obj_layer_config_ranges != m_layer_config_ranges.end()) + model_object->layer_config_ranges = std::move(obj_layer_config_ranges->second); + + // m_sla_support_points are indexed by a 1 based model object index. + IdToSlaSupportPointsMap::iterator obj_sla_support_points = m_sla_support_points.find(object.second + 1); + if (obj_sla_support_points != m_sla_support_points.end() && !obj_sla_support_points->second.empty()) { + model_object->sla_support_points = std::move(obj_sla_support_points->second); + model_object->sla_points_status = sla::PointsStatus::UserModified; + } + + IdToSlaDrainHolesMap::iterator obj_drain_holes = m_sla_drain_holes.find(object.second + 1); + if (obj_drain_holes != m_sla_drain_holes.end() && !obj_drain_holes->second.empty()) { + model_object->sla_drain_holes = std::move(obj_drain_holes->second); + } + + ObjectMetadata::VolumeMetadataList volumes; + ObjectMetadata::VolumeMetadataList* volumes_ptr = nullptr; + + IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first); + if (obj_metadata != m_objects_metadata.end()) { + // config data has been found, this model was saved using slic3r pe + + // apply object's name and config data + for (const Metadata& metadata : obj_metadata->second.metadata) { + if (metadata.key == "name") + model_object->name = metadata.value; + else + model_object->config.set_deserialize(metadata.key, metadata.value, config_substitutions); + } + + // select object's detected volumes + volumes_ptr = &obj_metadata->second.volumes; + } + else { + // config data not found, this model was not saved using slic3r pe + + // add the entire geometry as the single volume to generate + volumes.emplace_back(0, (int)obj_geometry->second.triangles.size() - 1); + + // select as volumes + volumes_ptr = &volumes; + } + + if (!_generate_volumes(*model_object, obj_geometry->second, *volumes_ptr, config_substitutions)) + return false; + } + + // If instances contain a single volume, the volume offset should be 0,0,0 + // This equals to say that instance world position and volume world position should match + // Correct all instances/volumes for which this does not hold + for (int obj_id = 0; obj_id < int(model.objects.size()); ++obj_id) { + ModelObject* o = model.objects[obj_id]; + if (o->volumes.size() == 1) { + ModelVolume* v = o->volumes.front(); + const Slic3r::Geometry::Transformation& first_inst_trafo = o->instances.front()->get_transformation(); + const Vec3d world_vol_offset = (first_inst_trafo * v->get_transformation()).get_offset(); + const Vec3d world_inst_offset = first_inst_trafo.get_offset(); + + if (!world_vol_offset.isApprox(world_inst_offset)) { + const Slic3r::Geometry::Transformation& vol_trafo = v->get_transformation(); + for (int inst_id = 0; inst_id < int(o->instances.size()); ++inst_id) { + ModelInstance* i = o->instances[inst_id]; + const Slic3r::Geometry::Transformation& inst_trafo = i->get_transformation(); + i->set_offset((inst_trafo * vol_trafo).get_offset()); + } + v->set_offset(Vec3d::Zero()); + } + } + } + +#if ENABLE_RELOAD_FROM_DISK_REWORK + for (int obj_id = 0; obj_id < int(model.objects.size()); ++obj_id) { + ModelObject* o = model.objects[obj_id]; + for (int vol_id = 0; vol_id < int(o->volumes.size()); ++vol_id) { + ModelVolume* v = o->volumes[vol_id]; + if (v->source.input_file.empty()) + v->source.input_file = v->name.empty() ? filename : v->name; + if (v->source.volume_idx == -1) + v->source.volume_idx = vol_id; + if (v->source.object_idx == -1) + v->source.object_idx = obj_id; + } + } +#else + int object_idx = 0; + for (ModelObject* o : model.objects) { + int volume_idx = 0; + for (ModelVolume* v : o->volumes) { + if (v->source.input_file.empty() && v->type() == ModelVolumeType::MODEL_PART) { + v->source.input_file = filename; + if (v->source.volume_idx == -1) + v->source.volume_idx = volume_idx; + if (v->source.object_idx == -1) + v->source.object_idx = object_idx; + } + ++volume_idx; + } + ++object_idx; + } +#endif // ENABLE_RELOAD_FROM_DISK_REWORK + +// // fixes the min z of the model if negative +// model.adjust_min_z(); + + return true; + } + + bool _3MF_Importer::_extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) + { + if (stat.m_uncomp_size == 0) { + add_error("Found invalid size"); + return false; + } + + _destroy_xml_parser(); + + m_xml_parser = XML_ParserCreate(nullptr); + if (m_xml_parser == nullptr) { + add_error("Unable to create parser"); + return false; + } + + XML_SetUserData(m_xml_parser, (void*)this); + XML_SetElementHandler(m_xml_parser, _3MF_Importer::_handle_start_model_xml_element, _3MF_Importer::_handle_end_model_xml_element); + XML_SetCharacterDataHandler(m_xml_parser, _3MF_Importer::_handle_model_xml_characters); + + struct CallbackData + { + XML_Parser& parser; + _3MF_Importer& importer; + const mz_zip_archive_file_stat& stat; + + CallbackData(XML_Parser& parser, _3MF_Importer& importer, const mz_zip_archive_file_stat& stat) : parser(parser), importer(importer), stat(stat) {} + }; + + CallbackData data(m_xml_parser, *this, stat); + + mz_bool res = 0; + + try + { + res = mz_zip_reader_extract_file_to_callback(&archive, stat.m_filename, [](void* pOpaque, mz_uint64 file_ofs, const void* pBuf, size_t n)->size_t { + CallbackData* data = (CallbackData*)pOpaque; + if (!XML_Parse(data->parser, (const char*)pBuf, (int)n, (file_ofs + n == data->stat.m_uncomp_size) ? 1 : 0) || data->importer.parse_error()) { + char error_buf[1024]; + ::sprintf(error_buf, "Error (%s) while parsing '%s' at line %d", data->importer.parse_error_message(), data->stat.m_filename, (int)XML_GetCurrentLineNumber(data->parser)); + throw Slic3r::FileIOError(error_buf); + } + + return n; + }, &data, 0); + } + catch (const version_error& e) + { + // rethrow the exception + throw Slic3r::FileIOError(e.what()); + } + catch (std::exception& e) + { + add_error(e.what()); + return false; + } + + if (res == 0) { + add_error("Error while extracting model data from zip archive"); + return false; + } + + return true; + } + + void _3MF_Importer::_extract_print_config_from_archive( + mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, + DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, + const std::string& archive_filename) + { + if (stat.m_uncomp_size > 0) { + std::string buffer((size_t)stat.m_uncomp_size, 0); + mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); + if (res == 0) { + add_error("Error while reading config data to buffer"); + return; + } + //FIXME Loading a "will be one day a legacy format" of configuration in a form of a G-code comment. + // Each config line is prefixed with a semicolon (G-code comment), that is ugly. + + // Replacing the legacy function with load_from_ini_string_commented leads to issues when + // parsing 3MFs from before PrusaSlicer 2.0.0 (which can have duplicated entries in the INI. + // See https://github.com/prusa3d/PrusaSlicer/issues/7155. We'll revert it for now. + //config_substitutions.substitutions = config.load_from_ini_string_commented(std::move(buffer), config_substitutions.rule); + ConfigBase::load_from_gcode_string_legacy(config, buffer.data(), config_substitutions); + } + } + + void _3MF_Importer::_extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) + { + if (stat.m_uncomp_size > 0) { + std::string buffer((size_t)stat.m_uncomp_size, 0); + mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); + if (res == 0) { + add_error("Error while reading layer heights profile data to buffer"); + return; + } + + if (buffer.back() == '\n') + buffer.pop_back(); + + std::vector objects; + boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off); + + for (const std::string& object : objects) { + std::vector object_data; + boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off); + if (object_data.size() != 2) { + add_error("Error while reading object data"); + continue; + } + + std::vector object_data_id; + boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off); + if (object_data_id.size() != 2) { + add_error("Error while reading object id"); + continue; + } + + int object_id = std::atoi(object_data_id[1].c_str()); + if (object_id == 0) { + add_error("Found invalid object id"); + continue; + } + + IdToLayerHeightsProfileMap::iterator object_item = m_layer_heights_profiles.find(object_id); + if (object_item != m_layer_heights_profiles.end()) { + add_error("Found duplicated layer heights profile"); + continue; + } + + std::vector object_data_profile; + boost::split(object_data_profile, object_data[1], boost::is_any_of(";"), boost::token_compress_off); + if (object_data_profile.size() <= 4 || object_data_profile.size() % 2 != 0) { + add_error("Found invalid layer heights profile"); + continue; + } + + std::vector profile; + profile.reserve(object_data_profile.size()); + + for (const std::string& value : object_data_profile) { + profile.push_back((coordf_t)std::atof(value.c_str())); + } + + m_layer_heights_profiles.insert({ object_id, profile }); + } + } + } + + void _3MF_Importer::_extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions) + { + if (stat.m_uncomp_size > 0) { + std::string buffer((size_t)stat.m_uncomp_size, 0); + mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); + if (res == 0) { + add_error("Error while reading layer config ranges data to buffer"); + return; + } + + std::istringstream iss(buffer); // wrap returned xml to istringstream + pt::ptree objects_tree; + pt::read_xml(iss, objects_tree); + + for (const auto& object : objects_tree.get_child("objects")) { + pt::ptree object_tree = object.second; + int obj_idx = object_tree.get(".id", -1); + if (obj_idx <= 0) { + add_error("Found invalid object id"); + continue; + } + + IdToLayerConfigRangesMap::iterator object_item = m_layer_config_ranges.find(obj_idx); + if (object_item != m_layer_config_ranges.end()) { + add_error("Found duplicated layer config range"); + continue; + } + + t_layer_config_ranges config_ranges; + + for (const auto& range : object_tree) { + if (range.first != "range") + continue; + pt::ptree range_tree = range.second; + double min_z = range_tree.get(".min_z"); + double max_z = range_tree.get(".max_z"); + + // get Z range information + DynamicPrintConfig config; + + for (const auto& option : range_tree) { + if (option.first != "option") + continue; + std::string opt_key = option.second.get(".opt_key"); + std::string value = option.second.data(); + config.set_deserialize(opt_key, value, config_substitutions); + } + + config_ranges[{ min_z, max_z }].assign_config(std::move(config)); + } + + if (!config_ranges.empty()) + m_layer_config_ranges.insert({ obj_idx, std::move(config_ranges) }); + } + } + } + + void _3MF_Importer::_extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) + { + if (stat.m_uncomp_size > 0) { + std::string buffer((size_t)stat.m_uncomp_size, 0); + mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); + if (res == 0) { + add_error("Error while reading sla support points data to buffer"); + return; + } + + if (buffer.back() == '\n') + buffer.pop_back(); + + std::vector objects; + boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off); + + // Info on format versioning - see 3mf.hpp + int version = 0; + std::string key("support_points_format_version="); + if (!objects.empty() && objects[0].find(key) != std::string::npos) { + objects[0].erase(objects[0].begin(), objects[0].begin() + long(key.size())); // removes the string + version = std::stoi(objects[0]); + objects.erase(objects.begin()); // pop the header + } + + for (const std::string& object : objects) { + std::vector object_data; + boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off); + + if (object_data.size() != 2) { + add_error("Error while reading object data"); + continue; + } + + std::vector object_data_id; + boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off); + if (object_data_id.size() != 2) { + add_error("Error while reading object id"); + continue; + } + + int object_id = std::atoi(object_data_id[1].c_str()); + if (object_id == 0) { + add_error("Found invalid object id"); + continue; + } + + IdToSlaSupportPointsMap::iterator object_item = m_sla_support_points.find(object_id); + if (object_item != m_sla_support_points.end()) { + add_error("Found duplicated SLA support points"); + continue; + } + + std::vector object_data_points; + boost::split(object_data_points, object_data[1], boost::is_any_of(" "), boost::token_compress_off); + + std::vector sla_support_points; + + if (version == 0) { + for (unsigned int i=0; i 0) { + std::string buffer(size_t(stat.m_uncomp_size), 0); + mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); + if (res == 0) { + add_error("Error while reading sla support points data to buffer"); + return; + } + + if (buffer.back() == '\n') + buffer.pop_back(); + + std::vector objects; + boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off); + + // Info on format versioning - see 3mf.hpp + int version = 0; + std::string key("drain_holes_format_version="); + if (!objects.empty() && objects[0].find(key) != std::string::npos) { + objects[0].erase(objects[0].begin(), objects[0].begin() + long(key.size())); // removes the string + version = std::stoi(objects[0]); + objects.erase(objects.begin()); // pop the header + } + + for (const std::string& object : objects) { + std::vector object_data; + boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off); + + if (object_data.size() != 2) { + add_error("Error while reading object data"); + continue; + } + + std::vector object_data_id; + boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off); + if (object_data_id.size() != 2) { + add_error("Error while reading object id"); + continue; + } + + int object_id = std::atoi(object_data_id[1].c_str()); + if (object_id == 0) { + add_error("Found invalid object id"); + continue; + } + + IdToSlaDrainHolesMap::iterator object_item = m_sla_drain_holes.find(object_id); + if (object_item != m_sla_drain_holes.end()) { + add_error("Found duplicated SLA drain holes"); + continue; + } + + std::vector object_data_points; + boost::split(object_data_points, object_data[1], boost::is_any_of(" "), boost::token_compress_off); + + sla::DrainHoles sla_drain_holes; + + if (version == 1) { + for (unsigned int i=0; i 0) { + std::string buffer((size_t)stat.m_uncomp_size, 0); + mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); + if (res == 0) { + add_error("Error while reading custom Gcodes per height data to buffer"); + return; + } + + std::istringstream iss(buffer); // wrap returned xml to istringstream + pt::ptree main_tree; + pt::read_xml(iss, main_tree); + + if (main_tree.front().first != "custom_gcodes_per_print_z") + return; + pt::ptree code_tree = main_tree.front().second; + + m_model->custom_gcode_per_print_z.gcodes.clear(); + + for (const auto& code : code_tree) { + if (code.first == "mode") { + pt::ptree tree = code.second; + std::string mode = tree.get(".value"); + m_model->custom_gcode_per_print_z.mode = mode == CustomGCode::SingleExtruderMode ? CustomGCode::Mode::SingleExtruder : + mode == CustomGCode::MultiAsSingleMode ? CustomGCode::Mode::MultiAsSingle : + CustomGCode::Mode::MultiExtruder; + } + if (code.first != "code") + continue; + + pt::ptree tree = code.second; + double print_z = tree.get (".print_z" ); + int extruder = tree.get (".extruder"); + std::string color = tree.get (".color" ); + + CustomGCode::Type type; + std::string extra; + pt::ptree attr_tree = tree.find("")->second; + if (attr_tree.find("type") == attr_tree.not_found()) { + // It means that data was saved in old version (2.2.0 and older) of PrusaSlicer + // read old data ... + std::string gcode = tree.get (".gcode"); + // ... and interpret them to the new data + type = gcode == "M600" ? CustomGCode::ColorChange : + gcode == "M601" ? CustomGCode::PausePrint : + gcode == "tool_change" ? CustomGCode::ToolChange : CustomGCode::Custom; + extra = type == CustomGCode::PausePrint ? color : + type == CustomGCode::Custom ? gcode : ""; + } + else { + type = static_cast(tree.get(".type")); + extra = tree.get(".extra"); + } + m_model->custom_gcode_per_print_z.gcodes.push_back(CustomGCode::Item{print_z, type, extruder, color, extra}) ; + } + } + } + + void _3MF_Importer::_handle_start_model_xml_element(const char* name, const char** attributes) + { + if (m_xml_parser == nullptr) + return; + + bool res = true; + unsigned int num_attributes = (unsigned int)XML_GetSpecifiedAttributeCount(m_xml_parser); + + if (::strcmp(MODEL_TAG, name) == 0) + res = _handle_start_model(attributes, num_attributes); + else if (::strcmp(RESOURCES_TAG, name) == 0) + res = _handle_start_resources(attributes, num_attributes); + else if (::strcmp(OBJECT_TAG, name) == 0) + res = _handle_start_object(attributes, num_attributes); + else if (::strcmp(MESH_TAG, name) == 0) + res = _handle_start_mesh(attributes, num_attributes); + else if (::strcmp(VERTICES_TAG, name) == 0) + res = _handle_start_vertices(attributes, num_attributes); + else if (::strcmp(VERTEX_TAG, name) == 0) + res = _handle_start_vertex(attributes, num_attributes); + else if (::strcmp(TRIANGLES_TAG, name) == 0) + res = _handle_start_triangles(attributes, num_attributes); + else if (::strcmp(TRIANGLE_TAG, name) == 0) + res = _handle_start_triangle(attributes, num_attributes); + else if (::strcmp(COMPONENTS_TAG, name) == 0) + res = _handle_start_components(attributes, num_attributes); + else if (::strcmp(COMPONENT_TAG, name) == 0) + res = _handle_start_component(attributes, num_attributes); + else if (::strcmp(BUILD_TAG, name) == 0) + res = _handle_start_build(attributes, num_attributes); + else if (::strcmp(ITEM_TAG, name) == 0) + res = _handle_start_item(attributes, num_attributes); + else if (::strcmp(METADATA_TAG, name) == 0) + res = _handle_start_metadata(attributes, num_attributes); + + if (!res) + _stop_xml_parser(); + } + + void _3MF_Importer::_handle_end_model_xml_element(const char* name) + { + if (m_xml_parser == nullptr) + return; + + bool res = true; + + if (::strcmp(MODEL_TAG, name) == 0) + res = _handle_end_model(); + else if (::strcmp(RESOURCES_TAG, name) == 0) + res = _handle_end_resources(); + else if (::strcmp(OBJECT_TAG, name) == 0) + res = _handle_end_object(); + else if (::strcmp(MESH_TAG, name) == 0) + res = _handle_end_mesh(); + else if (::strcmp(VERTICES_TAG, name) == 0) + res = _handle_end_vertices(); + else if (::strcmp(VERTEX_TAG, name) == 0) + res = _handle_end_vertex(); + else if (::strcmp(TRIANGLES_TAG, name) == 0) + res = _handle_end_triangles(); + else if (::strcmp(TRIANGLE_TAG, name) == 0) + res = _handle_end_triangle(); + else if (::strcmp(COMPONENTS_TAG, name) == 0) + res = _handle_end_components(); + else if (::strcmp(COMPONENT_TAG, name) == 0) + res = _handle_end_component(); + else if (::strcmp(BUILD_TAG, name) == 0) + res = _handle_end_build(); + else if (::strcmp(ITEM_TAG, name) == 0) + res = _handle_end_item(); + else if (::strcmp(METADATA_TAG, name) == 0) + res = _handle_end_metadata(); + + if (!res) + _stop_xml_parser(); + } + + void _3MF_Importer::_handle_model_xml_characters(const XML_Char* s, int len) + { + m_curr_characters.append(s, len); + } + + void _3MF_Importer::_handle_start_config_xml_element(const char* name, const char** attributes) + { + if (m_xml_parser == nullptr) + return; + + bool res = true; + unsigned int num_attributes = (unsigned int)XML_GetSpecifiedAttributeCount(m_xml_parser); + + if (::strcmp(CONFIG_TAG, name) == 0) + res = _handle_start_config(attributes, num_attributes); + else if (::strcmp(OBJECT_TAG, name) == 0) + res = _handle_start_config_object(attributes, num_attributes); + else if (::strcmp(VOLUME_TAG, name) == 0) + res = _handle_start_config_volume(attributes, num_attributes); + else if (::strcmp(MESH_TAG, name) == 0) + res = _handle_start_config_volume_mesh(attributes, num_attributes); + else if (::strcmp(METADATA_TAG, name) == 0) + res = _handle_start_config_metadata(attributes, num_attributes); + + if (!res) + _stop_xml_parser(); + } + + void _3MF_Importer::_handle_end_config_xml_element(const char* name) + { + if (m_xml_parser == nullptr) + return; + + bool res = true; + + if (::strcmp(CONFIG_TAG, name) == 0) + res = _handle_end_config(); + else if (::strcmp(OBJECT_TAG, name) == 0) + res = _handle_end_config_object(); + else if (::strcmp(VOLUME_TAG, name) == 0) + res = _handle_end_config_volume(); + else if (::strcmp(MESH_TAG, name) == 0) + res = _handle_end_config_volume_mesh(); + else if (::strcmp(METADATA_TAG, name) == 0) + res = _handle_end_config_metadata(); + + if (!res) + _stop_xml_parser(); + } + + bool _3MF_Importer::_handle_start_model(const char** attributes, unsigned int num_attributes) + { + m_unit_factor = get_unit_factor(get_attribute_value_string(attributes, num_attributes, UNIT_ATTR)); + return true; + } + + bool _3MF_Importer::_handle_end_model() + { + // deletes all non-built or non-instanced objects + for (const IdToModelObjectMap::value_type& object : m_objects) { + if (object.second >= int(m_model->objects.size())) { + add_error("Unable to find object"); + return false; + } + ModelObject *model_object = m_model->objects[object.second]; + if (model_object != nullptr && model_object->instances.size() == 0) + m_model->delete_object(model_object); + } + + if (m_version == 0) { + // if the 3mf was not produced by PrusaSlicer and there is only one object, + // set the object name to match the filename + if (m_model->objects.size() == 1) + m_model->objects.front()->name = m_name; + } + + // applies instances' matrices + for (Instance& instance : m_instances) { + if (instance.instance != nullptr && instance.instance->get_object() != nullptr) + // apply the transform to the instance + _apply_transform(*instance.instance, instance.transform); + } + + return true; + } + + bool _3MF_Importer::_handle_start_resources(const char** attributes, unsigned int num_attributes) + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_end_resources() + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_start_object(const char** attributes, unsigned int num_attributes) + { + // reset current data + m_curr_object.reset(); + + if (is_valid_object_type(get_attribute_value_string(attributes, num_attributes, TYPE_ATTR))) { + // create new object (it may be removed later if no instances are generated from it) + m_curr_object.model_object_idx = (int)m_model->objects.size(); + m_curr_object.object = m_model->add_object(); + if (m_curr_object.object == nullptr) { + add_error("Unable to create object"); + return false; + } + + // set object data + m_curr_object.object->name = get_attribute_value_string(attributes, num_attributes, NAME_ATTR); + if (m_curr_object.object->name.empty()) + m_curr_object.object->name = m_name + "_" + std::to_string(m_model->objects.size()); + + m_curr_object.id = get_attribute_value_int(attributes, num_attributes, ID_ATTR); + } + + return true; + } + + bool _3MF_Importer::_handle_end_object() + { + if (m_curr_object.object != nullptr) { + if (m_curr_object.geometry.empty()) { + // no geometry defined + // remove the object from the model + m_model->delete_object(m_curr_object.object); + + if (m_curr_object.components.empty()) { + // no components defined -> invalid object, delete it + IdToModelObjectMap::iterator object_item = m_objects.find(m_curr_object.id); + if (object_item != m_objects.end()) + m_objects.erase(object_item); + + IdToAliasesMap::iterator alias_item = m_objects_aliases.find(m_curr_object.id); + if (alias_item != m_objects_aliases.end()) + m_objects_aliases.erase(alias_item); + } + else + // adds components to aliases + m_objects_aliases.insert({ m_curr_object.id, m_curr_object.components }); + } + else { + // geometry defined, store it for later use + m_geometries.insert({ m_curr_object.id, std::move(m_curr_object.geometry) }); + + // stores the object for later use + if (m_objects.find(m_curr_object.id) == m_objects.end()) { + m_objects.insert({ m_curr_object.id, m_curr_object.model_object_idx }); + m_objects_aliases.insert({ m_curr_object.id, { 1, Component(m_curr_object.id) } }); // aliases itself + } + else { + add_error("Found object with duplicate id"); + return false; + } + } + } + + return true; + } + + bool _3MF_Importer::_handle_start_mesh(const char** attributes, unsigned int num_attributes) + { + // reset current geometry + m_curr_object.geometry.reset(); + return true; + } + + bool _3MF_Importer::_handle_end_mesh() + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_start_vertices(const char** attributes, unsigned int num_attributes) + { + // reset current vertices + m_curr_object.geometry.vertices.clear(); + return true; + } + + bool _3MF_Importer::_handle_end_vertices() + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_start_vertex(const char** attributes, unsigned int num_attributes) + { + // appends the vertex coordinates + // missing values are set equal to ZERO + m_curr_object.geometry.vertices.emplace_back( + m_unit_factor * get_attribute_value_float(attributes, num_attributes, X_ATTR), + m_unit_factor * get_attribute_value_float(attributes, num_attributes, Y_ATTR), + m_unit_factor * get_attribute_value_float(attributes, num_attributes, Z_ATTR)); + return true; + } + + bool _3MF_Importer::_handle_end_vertex() + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_start_triangles(const char** attributes, unsigned int num_attributes) + { + // reset current triangles + m_curr_object.geometry.triangles.clear(); + return true; + } + + bool _3MF_Importer::_handle_end_triangles() + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_start_triangle(const char** attributes, unsigned int num_attributes) + { + // we are ignoring the following attributes: + // p1 + // p2 + // p3 + // pid + // see specifications + + // appends the triangle's vertices indices + // missing values are set equal to ZERO + m_curr_object.geometry.triangles.emplace_back( + get_attribute_value_int(attributes, num_attributes, V1_ATTR), + get_attribute_value_int(attributes, num_attributes, V2_ATTR), + get_attribute_value_int(attributes, num_attributes, V3_ATTR)); + + m_curr_object.geometry.custom_supports.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR)); + m_curr_object.geometry.custom_seam.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR)); + m_curr_object.geometry.mmu_segmentation.push_back(get_attribute_value_string(attributes, num_attributes, MMU_SEGMENTATION_ATTR)); + return true; + } + + bool _3MF_Importer::_handle_end_triangle() + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_start_components(const char** attributes, unsigned int num_attributes) + { + // reset current components + m_curr_object.components.clear(); + return true; + } + + bool _3MF_Importer::_handle_end_components() + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_start_component(const char** attributes, unsigned int num_attributes) + { + int object_id = get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR); + Transform3d transform = get_transform_from_3mf_specs_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR)); + + IdToModelObjectMap::iterator object_item = m_objects.find(object_id); + if (object_item == m_objects.end()) { + IdToAliasesMap::iterator alias_item = m_objects_aliases.find(object_id); + if (alias_item == m_objects_aliases.end()) { + add_error("Found component with invalid object id"); + return false; + } + } + + m_curr_object.components.emplace_back(object_id, transform); + + return true; + } + + bool _3MF_Importer::_handle_end_component() + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_start_build(const char** attributes, unsigned int num_attributes) + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_end_build() + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_start_item(const char** attributes, unsigned int num_attributes) + { + // we are ignoring the following attributes + // thumbnail + // partnumber + // pid + // pindex + // see specifications + + int object_id = get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR); + Transform3d transform = get_transform_from_3mf_specs_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR)); + int printable = get_attribute_value_bool(attributes, num_attributes, PRINTABLE_ATTR); + + return _create_object_instance(object_id, transform, printable, 1); + } + + bool _3MF_Importer::_handle_end_item() + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_start_metadata(const char** attributes, unsigned int num_attributes) + { + m_curr_characters.clear(); + + std::string name = get_attribute_value_string(attributes, num_attributes, NAME_ATTR); + if (!name.empty()) + m_curr_metadata_name = name; + + return true; + } + + inline static void check_painting_version(unsigned int loaded_version, unsigned int highest_supported_version, const std::string &error_msg) + { + if (loaded_version > highest_supported_version) + throw version_error(error_msg); + } + + bool _3MF_Importer::_handle_end_metadata() + { + if (m_curr_metadata_name == SLIC3RPE_3MF_VERSION) { + m_version = (unsigned int)atoi(m_curr_characters.c_str()); + if (m_check_version && (m_version > VERSION_3MF_COMPATIBLE)) { + // std::string msg = _(L("The selected 3mf file has been saved with a newer version of " + std::string(SLIC3R_APP_NAME) + " and is not compatible.")); + // throw version_error(msg.c_str()); + const std::string msg = (boost::format(_(L("The selected 3mf file has been saved with a newer version of %1% and is not compatible."))) % std::string(SLIC3R_APP_NAME)).str(); + throw version_error(msg); + } + } else if (m_curr_metadata_name == "Application") { + // Generator application of the 3MF. + // SLIC3R_APP_KEY - SLIC3R_VERSION + if (boost::starts_with(m_curr_characters, "PrusaSlicer-")) + m_prusaslicer_generator_version = Semver::parse(m_curr_characters.substr(12)); + } else if (m_curr_metadata_name == SLIC3RPE_FDM_SUPPORTS_PAINTING_VERSION) { + m_fdm_supports_painting_version = (unsigned int) atoi(m_curr_characters.c_str()); + check_painting_version(m_fdm_supports_painting_version, FDM_SUPPORTS_PAINTING_VERSION, + _(L("The selected 3MF contains FDM supports painted object using a newer version of PrusaSlicer and is not compatible."))); + } else if (m_curr_metadata_name == SLIC3RPE_SEAM_PAINTING_VERSION) { + m_seam_painting_version = (unsigned int) atoi(m_curr_characters.c_str()); + check_painting_version(m_seam_painting_version, SEAM_PAINTING_VERSION, + _(L("The selected 3MF contains seam painted object using a newer version of PrusaSlicer and is not compatible."))); + } else if (m_curr_metadata_name == SLIC3RPE_MM_PAINTING_VERSION) { + m_mm_painting_version = (unsigned int) atoi(m_curr_characters.c_str()); + check_painting_version(m_mm_painting_version, MM_PAINTING_VERSION, + _(L("The selected 3MF contains multi-material painted object using a newer version of PrusaSlicer and is not compatible."))); + } + + return true; + } + + bool _3MF_Importer::_create_object_instance(int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter) + { + static const unsigned int MAX_RECURSIONS = 10; + + // escape from circular aliasing + if (recur_counter > MAX_RECURSIONS) { + add_error("Too many recursions"); + return false; + } + + IdToAliasesMap::iterator it = m_objects_aliases.find(object_id); + if (it == m_objects_aliases.end()) { + add_error("Found item with invalid object id"); + return false; + } + + if (it->second.size() == 1 && it->second[0].object_id == object_id) { + // aliasing to itself + + IdToModelObjectMap::iterator object_item = m_objects.find(object_id); + if (object_item == m_objects.end() || object_item->second == -1) { + add_error("Found invalid object"); + return false; + } + else { + ModelInstance* instance = m_model->objects[object_item->second]->add_instance(); + if (instance == nullptr) { + add_error("Unable to add object instance"); + return false; + } + instance->printable = printable; + + m_instances.emplace_back(instance, transform); + } + } + else { + // recursively process nested components + for (const Component& component : it->second) { + if (!_create_object_instance(component.object_id, transform * component.transform, printable, recur_counter + 1)) + return false; + } + } + + return true; + } + + void _3MF_Importer::_apply_transform(ModelInstance& instance, const Transform3d& transform) + { + Slic3r::Geometry::Transformation t(transform); + // invalid scale value, return + if (!t.get_scaling_factor().all()) + return; + + instance.set_transformation(t); + } + + bool _3MF_Importer::_handle_start_config(const char** attributes, unsigned int num_attributes) + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_end_config() + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_start_config_object(const char** attributes, unsigned int num_attributes) + { + int object_id = get_attribute_value_int(attributes, num_attributes, ID_ATTR); + IdToMetadataMap::iterator object_item = m_objects_metadata.find(object_id); + if (object_item != m_objects_metadata.end()) { + add_error("Found duplicated object id"); + return false; + } + + // Added because of github #3435, currently not used by PrusaSlicer + // int instances_count_id = get_attribute_value_int(attributes, num_attributes, INSTANCESCOUNT_ATTR); + + m_objects_metadata.insert({ object_id, ObjectMetadata() }); + m_curr_config.object_id = object_id; + return true; + } + + bool _3MF_Importer::_handle_end_config_object() + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_start_config_volume(const char** attributes, unsigned int num_attributes) + { + IdToMetadataMap::iterator object = m_objects_metadata.find(m_curr_config.object_id); + if (object == m_objects_metadata.end()) { + add_error("Cannot assign volume to a valid object"); + return false; + } + + m_curr_config.volume_id = (int)object->second.volumes.size(); + + unsigned int first_triangle_id = (unsigned int)get_attribute_value_int(attributes, num_attributes, FIRST_TRIANGLE_ID_ATTR); + unsigned int last_triangle_id = (unsigned int)get_attribute_value_int(attributes, num_attributes, LAST_TRIANGLE_ID_ATTR); + + object->second.volumes.emplace_back(first_triangle_id, last_triangle_id); + return true; + } + + bool _3MF_Importer::_handle_start_config_volume_mesh(const char** attributes, unsigned int num_attributes) + { + IdToMetadataMap::iterator object = m_objects_metadata.find(m_curr_config.object_id); + if (object == m_objects_metadata.end()) { + add_error("Cannot assign volume mesh to a valid object"); + return false; + } + if (object->second.volumes.empty()) { + add_error("Cannot assign mesh to a valid olume"); + return false; + } + + ObjectMetadata::VolumeMetadata& volume = object->second.volumes.back(); + + int edges_fixed = get_attribute_value_int(attributes, num_attributes, MESH_STAT_EDGES_FIXED ); + int degenerate_facets = get_attribute_value_int(attributes, num_attributes, MESH_STAT_DEGENERATED_FACETS); + int facets_removed = get_attribute_value_int(attributes, num_attributes, MESH_STAT_FACETS_REMOVED ); + int facets_reversed = get_attribute_value_int(attributes, num_attributes, MESH_STAT_FACETS_RESERVED ); + int backwards_edges = get_attribute_value_int(attributes, num_attributes, MESH_STAT_BACKWARDS_EDGES ); + + volume.mesh_stats = { edges_fixed, degenerate_facets, facets_removed, facets_reversed, backwards_edges }; + + return true; + } + + bool _3MF_Importer::_handle_end_config_volume() + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_end_config_volume_mesh() + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_start_config_metadata(const char** attributes, unsigned int num_attributes) + { + IdToMetadataMap::iterator object = m_objects_metadata.find(m_curr_config.object_id); + if (object == m_objects_metadata.end()) { + add_error("Cannot assign metadata to valid object id"); + return false; + } + + std::string type = get_attribute_value_string(attributes, num_attributes, TYPE_ATTR); + std::string key = get_attribute_value_string(attributes, num_attributes, KEY_ATTR); + std::string value = get_attribute_value_string(attributes, num_attributes, VALUE_ATTR); + + if (type == OBJECT_TYPE) + object->second.metadata.emplace_back(key, value); + else if (type == VOLUME_TYPE) { + if (size_t(m_curr_config.volume_id) < object->second.volumes.size()) + object->second.volumes[m_curr_config.volume_id].metadata.emplace_back(key, value); + } + else { + add_error("Found invalid metadata type"); + return false; + } + + return true; + } + + bool _3MF_Importer::_handle_end_config_metadata() + { + // do nothing + return true; + } + + bool _3MF_Importer::_generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions) + { + if (!object.volumes.empty()) { + add_error("Found invalid volumes count"); + return false; + } + + unsigned int geo_tri_count = (unsigned int)geometry.triangles.size(); + unsigned int renamed_volumes_count = 0; + + for (const ObjectMetadata::VolumeMetadata& volume_data : volumes) { + if (geo_tri_count <= volume_data.first_triangle_id || geo_tri_count <= volume_data.last_triangle_id || volume_data.last_triangle_id < volume_data.first_triangle_id) { + add_error("Found invalid triangle id"); + return false; + } + + Transform3d volume_matrix_to_object = Transform3d::Identity(); + bool has_transform = false; + // extract the volume transformation from the volume's metadata, if present + for (const Metadata& metadata : volume_data.metadata) { + if (metadata.key == MATRIX_KEY) { + volume_matrix_to_object = Slic3r::Geometry::transform3d_from_string(metadata.value); + has_transform = ! volume_matrix_to_object.isApprox(Transform3d::Identity(), 1e-10); + break; + } + } + + // splits volume out of imported geometry + indexed_triangle_set its; + its.indices.assign(geometry.triangles.begin() + volume_data.first_triangle_id, geometry.triangles.begin() + volume_data.last_triangle_id + 1); + const size_t triangles_count = its.indices.size(); + if (triangles_count == 0) { + add_error("An empty triangle mesh found"); + return false; + } + + { + int min_id = its.indices.front()[0]; + int max_id = min_id; + for (const Vec3i& face : its.indices) { + for (const int tri_id : face) { + if (tri_id < 0 || tri_id >= int(geometry.vertices.size())) { + add_error("Found invalid vertex id"); + return false; + } + min_id = std::min(min_id, tri_id); + max_id = std::max(max_id, tri_id); + } + } + its.vertices.assign(geometry.vertices.begin() + min_id, geometry.vertices.begin() + max_id + 1); + + // rebase indices to the current vertices list + for (Vec3i& face : its.indices) + for (int& tri_id : face) + tri_id -= min_id; + } + + if (m_prusaslicer_generator_version && + *m_prusaslicer_generator_version >= *Semver::parse("2.4.0-alpha1") && + *m_prusaslicer_generator_version < *Semver::parse("2.4.0-alpha3")) + // PrusaSlicer 2.4.0-alpha2 contained a bug, where all vertices of a single object were saved for each volume the object contained. + // Remove the vertices, that are not referenced by any face. + its_compactify_vertices(its, true); + + TriangleMesh triangle_mesh(std::move(its), volume_data.mesh_stats); + + if (m_version == 0) { + // if the 3mf was not produced by PrusaSlicer and there is only one instance, + // bake the transformation into the geometry to allow the reload from disk command + // to work properly + if (object.instances.size() == 1) { + triangle_mesh.transform(object.instances.front()->get_transformation().get_matrix(), false); + object.instances.front()->set_transformation(Slic3r::Geometry::Transformation()); + //FIXME do the mesh fixing? + } + } + if (triangle_mesh.volume() < 0) + triangle_mesh.flip_triangles(); + + ModelVolume* volume = object.add_volume(std::move(triangle_mesh)); + // stores the volume matrix taken from the metadata, if present + if (has_transform) + volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object); + + // recreate custom supports, seam and mmu segmentation from previously loaded attribute + volume->supported_facets.reserve(triangles_count); + volume->seam_facets.reserve(triangles_count); + volume->mmu_segmentation_facets.reserve(triangles_count); + for (size_t i=0; isupported_facets.set_triangle_from_string(i, geometry.custom_supports[index]); + if (! geometry.custom_seam[index].empty()) + volume->seam_facets.set_triangle_from_string(i, geometry.custom_seam[index]); + if (! geometry.mmu_segmentation[index].empty()) + volume->mmu_segmentation_facets.set_triangle_from_string(i, geometry.mmu_segmentation[index]); + } + volume->supported_facets.shrink_to_fit(); + volume->seam_facets.shrink_to_fit(); + volume->mmu_segmentation_facets.shrink_to_fit(); + + // apply the remaining volume's metadata + for (const Metadata& metadata : volume_data.metadata) { + if (metadata.key == NAME_KEY) + volume->name = metadata.value; + else if ((metadata.key == MODIFIER_KEY) && (metadata.value == "1")) + volume->set_type(ModelVolumeType::PARAMETER_MODIFIER); + else if (metadata.key == VOLUME_TYPE_KEY) + volume->set_type(ModelVolume::type_from_string(metadata.value)); + else if (metadata.key == SOURCE_FILE_KEY) + volume->source.input_file = metadata.value; + else if (metadata.key == SOURCE_OBJECT_ID_KEY) + volume->source.object_idx = ::atoi(metadata.value.c_str()); + else if (metadata.key == SOURCE_VOLUME_ID_KEY) + volume->source.volume_idx = ::atoi(metadata.value.c_str()); + else if (metadata.key == SOURCE_OFFSET_X_KEY) + volume->source.mesh_offset.x() = ::atof(metadata.value.c_str()); + else if (metadata.key == SOURCE_OFFSET_Y_KEY) + volume->source.mesh_offset.y() = ::atof(metadata.value.c_str()); + else if (metadata.key == SOURCE_OFFSET_Z_KEY) + volume->source.mesh_offset.z() = ::atof(metadata.value.c_str()); + else if (metadata.key == SOURCE_IN_INCHES_KEY) + volume->source.is_converted_from_inches = metadata.value == "1"; + else if (metadata.key == SOURCE_IN_METERS_KEY) + volume->source.is_converted_from_meters = metadata.value == "1"; +#if ENABLE_RELOAD_FROM_DISK_REWORK + else if (metadata.key == SOURCE_IS_BUILTIN_VOLUME_KEY) + volume->source.is_from_builtin_objects = metadata.value == "1"; +#endif // ENABLE_RELOAD_FROM_DISK_REWORK + else + volume->config.set_deserialize(metadata.key, metadata.value, config_substitutions); + } + + // this may happen for 3mf saved by 3rd part softwares + if (volume->name.empty()) { + volume->name = object.name; + if (renamed_volumes_count > 0) + volume->name += "_" + std::to_string(renamed_volumes_count + 1); + ++renamed_volumes_count; + } + } + + return true; + } + + void XMLCALL _3MF_Importer::_handle_start_model_xml_element(void* userData, const char* name, const char** attributes) + { + _3MF_Importer* importer = (_3MF_Importer*)userData; + if (importer != nullptr) + importer->_handle_start_model_xml_element(name, attributes); + } + + void XMLCALL _3MF_Importer::_handle_end_model_xml_element(void* userData, const char* name) + { + _3MF_Importer* importer = (_3MF_Importer*)userData; + if (importer != nullptr) + importer->_handle_end_model_xml_element(name); + } + + void XMLCALL _3MF_Importer::_handle_model_xml_characters(void* userData, const XML_Char* s, int len) + { + _3MF_Importer* importer = (_3MF_Importer*)userData; + if (importer != nullptr) + importer->_handle_model_xml_characters(s, len); + } + + void XMLCALL _3MF_Importer::_handle_start_config_xml_element(void* userData, const char* name, const char** attributes) + { + _3MF_Importer* importer = (_3MF_Importer*)userData; + if (importer != nullptr) + importer->_handle_start_config_xml_element(name, attributes); + } + + void XMLCALL _3MF_Importer::_handle_end_config_xml_element(void* userData, const char* name) + { + _3MF_Importer* importer = (_3MF_Importer*)userData; + if (importer != nullptr) + importer->_handle_end_config_xml_element(name); + } + + class _3MF_Exporter : public _3MF_Base + { + struct BuildItem + { + unsigned int id; + Transform3d transform; + bool printable; + + BuildItem(unsigned int id, const Transform3d& transform, const bool printable) + : id(id) + , transform(transform) + , printable(printable) + { + } + }; + + struct Offsets + { + unsigned int first_vertex_id; + unsigned int first_triangle_id; + unsigned int last_triangle_id; + + Offsets(unsigned int first_vertex_id) + : first_vertex_id(first_vertex_id) + , first_triangle_id(-1) + , last_triangle_id(-1) + { + } + }; + + typedef std::map VolumeToOffsetsMap; + + struct ObjectData + { + ModelObject* object; + VolumeToOffsetsMap volumes_offsets; + + explicit ObjectData(ModelObject* object) + : object(object) + { + } + }; + + typedef std::vector BuildItemsList; + typedef std::map IdToObjectDataMap; + + bool m_fullpath_sources{ true }; + bool m_zip64 { true }; + + public: + bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64); + + private: + bool _save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data); + bool _add_content_types_file_to_archive(mz_zip_archive& archive); + bool _add_thumbnail_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data); + bool _add_relationships_file_to_archive(mz_zip_archive& archive); + bool _add_model_file_to_archive(const std::string& filename, mz_zip_archive& archive, const Model& model, IdToObjectDataMap& objects_data); + bool _add_object_to_model_stream(mz_zip_writer_staged_context &context, unsigned int& object_id, ModelObject& object, BuildItemsList& build_items, VolumeToOffsetsMap& volumes_offsets); + bool _add_mesh_to_object_stream(mz_zip_writer_staged_context &context, ModelObject& object, VolumeToOffsetsMap& volumes_offsets); + bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items); + bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model); + bool _add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model); + bool _add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model); + bool _add_sla_drain_holes_file_to_archive(mz_zip_archive& archive, Model& model); + bool _add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config); + bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, const IdToObjectDataMap &objects_data); + bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config); + }; + + bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64) + { + clear_errors(); + m_fullpath_sources = fullpath_sources; + m_zip64 = zip64; + return _save_model_to_file(filename, model, config, thumbnail_data); + } + + bool _3MF_Exporter::_save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data) + { + mz_zip_archive archive; + mz_zip_zero_struct(&archive); + + if (!open_zip_writer(&archive, filename)) { + add_error("Unable to open the file"); + return false; + } + + // Adds content types file ("[Content_Types].xml";). + // The content of this file is the same for each PrusaSlicer 3mf. + if (!_add_content_types_file_to_archive(archive)) { + close_zip_writer(&archive); + boost::filesystem::remove(filename); + return false; + } + + if (thumbnail_data != nullptr && thumbnail_data->is_valid()) { + // Adds the file Metadata/thumbnail.png. + if (!_add_thumbnail_file_to_archive(archive, *thumbnail_data)) { + close_zip_writer(&archive); + boost::filesystem::remove(filename); + return false; + } + } + + // Adds relationships file ("_rels/.rels"). + // The content of this file is the same for each PrusaSlicer 3mf. + // The relationshis file contains a reference to the geometry file "3D/3dmodel.model", the name was chosen to be compatible with CURA. + if (!_add_relationships_file_to_archive(archive)) { + close_zip_writer(&archive); + boost::filesystem::remove(filename); + return false; + } + + // Adds model file ("3D/3dmodel.model"). + // This is the one and only file that contains all the geometry (vertices and triangles) of all ModelVolumes. + IdToObjectDataMap objects_data; + if (!_add_model_file_to_archive(filename, archive, model, objects_data)) { + close_zip_writer(&archive); + boost::filesystem::remove(filename); + return false; + } + + // Adds layer height profile file ("Metadata/Slic3r_PE_layer_heights_profile.txt"). + // All layer height profiles of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model. + // The index differes from the index of an object ID of an object instance of a 3MF file! + if (!_add_layer_height_profile_file_to_archive(archive, model)) { + close_zip_writer(&archive); + boost::filesystem::remove(filename); + return false; + } + + // Adds layer config ranges file ("Metadata/Slic3r_PE_layer_config_ranges.txt"). + // All layer height profiles of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model. + // The index differes from the index of an object ID of an object instance of a 3MF file! + if (!_add_layer_config_ranges_file_to_archive(archive, model)) { + close_zip_writer(&archive); + boost::filesystem::remove(filename); + return false; + } + + // Adds sla support points file ("Metadata/Slic3r_PE_sla_support_points.txt"). + // All sla support points of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model. + // The index differes from the index of an object ID of an object instance of a 3MF file! + if (!_add_sla_support_points_file_to_archive(archive, model)) { + close_zip_writer(&archive); + boost::filesystem::remove(filename); + return false; + } + + if (!_add_sla_drain_holes_file_to_archive(archive, model)) { + close_zip_writer(&archive); + boost::filesystem::remove(filename); + return false; + } + + + // Adds custom gcode per height file ("Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml"). + // All custom gcode per height of whole Model are stored here + if (!_add_custom_gcode_per_print_z_file_to_archive(archive, model, config)) { + close_zip_writer(&archive); + boost::filesystem::remove(filename); + return false; + } + + // Adds slic3r print config file ("Metadata/Slic3r_PE.config"). + // This file contains the content of FullPrintConfing / SLAFullPrintConfig. + if (config != nullptr) { + if (!_add_print_config_file_to_archive(archive, *config)) { + close_zip_writer(&archive); + boost::filesystem::remove(filename); + return false; + } + } + + // Adds slic3r model config file ("Metadata/Slic3r_PE_model.config"). + // This file contains all the attributes of all ModelObjects and their ModelVolumes (names, parameter overrides). + // As there is just a single Indexed Triangle Set data stored per ModelObject, offsets of volumes into their respective Indexed Triangle Set data + // is stored here as well. + if (!_add_model_config_file_to_archive(archive, model, objects_data)) { + close_zip_writer(&archive); + boost::filesystem::remove(filename); + return false; + } + + if (!mz_zip_writer_finalize_archive(&archive)) { + close_zip_writer(&archive); + boost::filesystem::remove(filename); + add_error("Unable to finalize the archive"); + return false; + } + + close_zip_writer(&archive); + + return true; + } + + bool _3MF_Exporter::_add_content_types_file_to_archive(mz_zip_archive& archive) + { + std::stringstream stream; + stream << "\n"; + stream << "\n"; + stream << " \n"; + stream << " \n"; + stream << " \n"; + stream << ""; + + std::string out = stream.str(); + + if (!mz_zip_writer_add_mem(&archive, CONTENT_TYPES_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { + add_error("Unable to add content types file to archive"); + return false; + } + + return true; + } + + bool _3MF_Exporter::_add_thumbnail_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data) + { + bool res = false; + + size_t png_size = 0; + void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)thumbnail_data.pixels.data(), thumbnail_data.width, thumbnail_data.height, 4, &png_size, MZ_DEFAULT_LEVEL, 1); + if (png_data != nullptr) { + res = mz_zip_writer_add_mem(&archive, THUMBNAIL_FILE.c_str(), (const void*)png_data, png_size, MZ_DEFAULT_COMPRESSION); + mz_free(png_data); + } + + if (!res) + add_error("Unable to add thumbnail file to archive"); + + return res; + } + + bool _3MF_Exporter::_add_relationships_file_to_archive(mz_zip_archive& archive) + { + std::stringstream stream; + stream << "\n"; + stream << "\n"; + stream << " \n"; + stream << " \n"; + stream << ""; + + std::string out = stream.str(); + + if (!mz_zip_writer_add_mem(&archive, RELATIONSHIPS_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { + add_error("Unable to add relationships file to archive"); + return false; + } + + return true; + } + + static void reset_stream(std::stringstream &stream) + { + stream.str(""); + stream.clear(); + // https://en.cppreference.com/w/cpp/types/numeric_limits/max_digits10 + // Conversion of a floating-point value to text and back is exact as long as at least max_digits10 were used (9 for float, 17 for double). + // It is guaranteed to produce the same floating-point value, even though the intermediate text representation is not exact. + // The default value of std::stream precision is 6 digits only! + stream << std::setprecision(std::numeric_limits::max_digits10); + } + + bool _3MF_Exporter::_add_model_file_to_archive(const std::string& filename, mz_zip_archive& archive, const Model& model, IdToObjectDataMap& objects_data) + { + mz_zip_writer_staged_context context; + if (!mz_zip_writer_add_staged_open(&archive, &context, MODEL_FILE.c_str(), + m_zip64 ? + // Maximum expected and allowed 3MF file size is 16GiB. + // This switches the ZIP file to a 64bit mode, which adds a tiny bit of overhead to file records. + (uint64_t(1) << 30) * 16 : + // Maximum expected 3MF file size is 4GB-1. This is a workaround for interoperability with Windows 10 3D model fixing API, see + // GH issue #6193. + (uint64_t(1) << 32) - 1, + nullptr, nullptr, 0, MZ_DEFAULT_COMPRESSION, nullptr, 0, nullptr, 0)) { + add_error("Unable to add model file to archive"); + return false; + } + + { + std::stringstream stream; + reset_stream(stream); + stream << "\n"; + stream << "<" << MODEL_TAG << " unit=\"millimeter\" xml:lang=\"en-US\" xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\" xmlns:slic3rpe=\"http://schemas.slic3r.org/3mf/2017/06\">\n"; + stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_3MF_VERSION << "\">" << VERSION_3MF << "\n"; + + if (model.is_fdm_support_painted()) + stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_FDM_SUPPORTS_PAINTING_VERSION << "\">" << FDM_SUPPORTS_PAINTING_VERSION << "\n"; + + if (model.is_seam_painted()) + stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_SEAM_PAINTING_VERSION << "\">" << SEAM_PAINTING_VERSION << "\n"; + + if (model.is_mm_painted()) + stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_MM_PAINTING_VERSION << "\">" << MM_PAINTING_VERSION << "\n"; + + std::string name = xml_escape(boost::filesystem::path(filename).stem().string()); + stream << " <" << METADATA_TAG << " name=\"Title\">" << name << "\n"; + stream << " <" << METADATA_TAG << " name=\"Designer\">" << "\n"; + stream << " <" << METADATA_TAG << " name=\"Description\">" << name << "\n"; + stream << " <" << METADATA_TAG << " name=\"Copyright\">" << "\n"; + stream << " <" << METADATA_TAG << " name=\"LicenseTerms\">" << "\n"; + stream << " <" << METADATA_TAG << " name=\"Rating\">" << "\n"; + std::string date = Slic3r::Utils::utc_timestamp(Slic3r::Utils::get_current_time_utc()); + // keep only the date part of the string + date = date.substr(0, 10); + stream << " <" << METADATA_TAG << " name=\"CreationDate\">" << date << "\n"; + stream << " <" << METADATA_TAG << " name=\"ModificationDate\">" << date << "\n"; + stream << " <" << METADATA_TAG << " name=\"Application\">" << SLIC3R_APP_KEY << "-" << SLIC3R_VERSION << "\n"; + stream << " <" << RESOURCES_TAG << ">\n"; + std::string buf = stream.str(); + if (! buf.empty() && ! mz_zip_writer_add_staged_data(&context, buf.data(), buf.size())) { + add_error("Unable to add model file to archive"); + return false; + } + } + + // Instance transformations, indexed by the 3MF object ID (which is a linear serialization of all instances of all ModelObjects). + BuildItemsList build_items; + + // The object_id here is a one based identifier of the first instance of a ModelObject in the 3MF file, where + // all the object instances of all ModelObjects are stored and indexed in a 1 based linear fashion. + // Therefore the list of object_ids here may not be continuous. + unsigned int object_id = 1; + for (ModelObject* obj : model.objects) { + if (obj == nullptr) + continue; + + // Index of an object in the 3MF file corresponding to the 1st instance of a ModelObject. + unsigned int curr_id = object_id; + IdToObjectDataMap::iterator object_it = objects_data.insert({ curr_id, ObjectData(obj) }).first; + // Store geometry of all ModelVolumes contained in a single ModelObject into a single 3MF indexed triangle set object. + // object_it->second.volumes_offsets will contain the offsets of the ModelVolumes in that single indexed triangle set. + // object_id will be increased to point to the 1st instance of the next ModelObject. + if (!_add_object_to_model_stream(context, object_id, *obj, build_items, object_it->second.volumes_offsets)) { + add_error("Unable to add object to archive"); + mz_zip_writer_add_staged_finish(&context); + return false; + } + } + + { + std::stringstream stream; + reset_stream(stream); + stream << " \n"; + + // Store the transformations of all the ModelInstances of all ModelObjects, indexed in a linear fashion. + if (!_add_build_to_model_stream(stream, build_items)) { + add_error("Unable to add build to archive"); + mz_zip_writer_add_staged_finish(&context); + return false; + } + + stream << "\n"; + + std::string buf = stream.str(); + + if ((! buf.empty() && ! mz_zip_writer_add_staged_data(&context, buf.data(), buf.size())) || + ! mz_zip_writer_add_staged_finish(&context)) { + add_error("Unable to add model file to archive"); + return false; + } + } + + return true; + } + + bool _3MF_Exporter::_add_object_to_model_stream(mz_zip_writer_staged_context &context, unsigned int& object_id, ModelObject& object, BuildItemsList& build_items, VolumeToOffsetsMap& volumes_offsets) + { + std::stringstream stream; + reset_stream(stream); + unsigned int id = 0; + for (const ModelInstance* instance : object.instances) { + assert(instance != nullptr); + if (instance == nullptr) + continue; + + unsigned int instance_id = object_id + id; + stream << " <" << OBJECT_TAG << " id=\"" << instance_id << "\" type=\"model\">\n"; + + if (id == 0) { + std::string buf = stream.str(); + reset_stream(stream); + if ((! buf.empty() && ! mz_zip_writer_add_staged_data(&context, buf.data(), buf.size())) || + ! _add_mesh_to_object_stream(context, object, volumes_offsets)) { + add_error("Unable to add mesh to archive"); + return false; + } + } + else { + stream << " <" << COMPONENTS_TAG << ">\n"; + stream << " <" << COMPONENT_TAG << " objectid=\"" << object_id << "\"/>\n"; + stream << " \n"; + } + + Transform3d t = instance->get_matrix(); + // instance_id is just a 1 indexed index in build_items. + assert(instance_id == build_items.size() + 1); + build_items.emplace_back(instance_id, t, instance->printable); + + stream << " \n"; + + ++id; + } + + object_id += id; + std::string buf = stream.str(); + return buf.empty() || mz_zip_writer_add_staged_data(&context, buf.data(), buf.size()); + } + +#if EXPORT_3MF_USE_SPIRIT_KARMA_FP + template + struct coordinate_policy_fixed : boost::spirit::karma::real_policies + { + static int floatfield(Num n) { return fmtflags::fixed; } + // Number of decimal digits to maintain float accuracy when storing into a text file and parsing back. + static unsigned precision(Num /* n */) { return std::numeric_limits::max_digits10 + 1; } + // No trailing zeros, thus for fmtflags::fixed usually much less than max_digits10 decimal numbers will be produced. + static bool trailing_zeros(Num /* n */) { return false; } + }; + template + struct coordinate_policy_scientific : coordinate_policy_fixed + { + static int floatfield(Num n) { return fmtflags::scientific; } + }; + // Define a new generator type based on the new coordinate policy. + using coordinate_type_fixed = boost::spirit::karma::real_generator>; + using coordinate_type_scientific = boost::spirit::karma::real_generator>; +#endif // EXPORT_3MF_USE_SPIRIT_KARMA_FP + + bool _3MF_Exporter::_add_mesh_to_object_stream(mz_zip_writer_staged_context &context, ModelObject& object, VolumeToOffsetsMap& volumes_offsets) + { + std::string output_buffer; + output_buffer += " <"; + output_buffer += MESH_TAG; + output_buffer += ">\n <"; + output_buffer += VERTICES_TAG; + output_buffer += ">\n"; + + auto flush = [this, &output_buffer, &context](bool force = false) { + if ((force && ! output_buffer.empty()) || output_buffer.size() >= 65536 * 16) { + if (! mz_zip_writer_add_staged_data(&context, output_buffer.data(), output_buffer.size())) { + add_error("Error during writing or compression"); + return false; + } + output_buffer.clear(); + } + return true; + }; + + auto format_coordinate = [](float f, char *buf) -> char* { + assert(is_decimal_separator_point()); +#if EXPORT_3MF_USE_SPIRIT_KARMA_FP + // Slightly faster than sprintf("%.9g"), but there is an issue with the karma floating point formatter, + // https://github.com/boostorg/spirit/pull/586 + // where the exported string is one digit shorter than it should be to guarantee lossless round trip. + // The code is left here for the ocasion boost guys improve. + coordinate_type_fixed const coordinate_fixed = coordinate_type_fixed(); + coordinate_type_scientific const coordinate_scientific = coordinate_type_scientific(); + // Format "f" in a fixed format. + char *ptr = buf; + boost::spirit::karma::generate(ptr, coordinate_fixed, f); + // Format "f" in a scientific format. + char *ptr2 = ptr; + boost::spirit::karma::generate(ptr2, coordinate_scientific, f); + // Return end of the shorter string. + auto len2 = ptr2 - ptr; + if (ptr - buf > len2) { + // Move the shorter scientific form to the front. + memcpy(buf, ptr, len2); + ptr = buf + len2; + } + // Return pointer to the end. + return ptr; +#else + // Round-trippable float, shortest possible. + return buf + sprintf(buf, "%.9g", f); +#endif + }; + + char buf[256]; + unsigned int vertices_count = 0; + for (ModelVolume* volume : object.volumes) { + if (volume == nullptr) + continue; + + volumes_offsets.insert({ volume, Offsets(vertices_count) }); + + const indexed_triangle_set &its = volume->mesh().its; + if (its.vertices.empty()) { + add_error("Found invalid mesh"); + return false; + } + + vertices_count += (int)its.vertices.size(); + + const Transform3d& matrix = volume->get_matrix(); + + for (size_t i = 0; i < its.vertices.size(); ++i) { + Vec3f v = (matrix * its.vertices[i].cast()).cast(); + char *ptr = buf; + boost::spirit::karma::generate(ptr, boost::spirit::lit(" <") << VERTEX_TAG << " x=\""); + ptr = format_coordinate(v.x(), ptr); + boost::spirit::karma::generate(ptr, "\" y=\""); + ptr = format_coordinate(v.y(), ptr); + boost::spirit::karma::generate(ptr, "\" z=\""); + ptr = format_coordinate(v.z(), ptr); + boost::spirit::karma::generate(ptr, "\"/>\n"); + *ptr = '\0'; + output_buffer += buf; + if (! flush()) + return false; + } + } + + output_buffer += " \n <"; + output_buffer += TRIANGLES_TAG; + output_buffer += ">\n"; + + unsigned int triangles_count = 0; + for (ModelVolume* volume : object.volumes) { + if (volume == nullptr) + continue; + + bool is_left_handed = volume->is_left_handed(); + VolumeToOffsetsMap::iterator volume_it = volumes_offsets.find(volume); + assert(volume_it != volumes_offsets.end()); + + const indexed_triangle_set &its = volume->mesh().its; + + // updates triangle offsets + volume_it->second.first_triangle_id = triangles_count; + triangles_count += (int)its.indices.size(); + volume_it->second.last_triangle_id = triangles_count - 1; + + for (int i = 0; i < int(its.indices.size()); ++ i) { + { + const Vec3i &idx = its.indices[i]; + char *ptr = buf; + boost::spirit::karma::generate(ptr, boost::spirit::lit(" <") << TRIANGLE_TAG << + " v1=\"" << boost::spirit::int_ << + "\" v2=\"" << boost::spirit::int_ << + "\" v3=\"" << boost::spirit::int_ << "\"", + idx[is_left_handed ? 2 : 0] + volume_it->second.first_vertex_id, + idx[1] + volume_it->second.first_vertex_id, + idx[is_left_handed ? 0 : 2] + volume_it->second.first_vertex_id); + *ptr = '\0'; + output_buffer += buf; + } + + std::string custom_supports_data_string = volume->supported_facets.get_triangle_as_string(i); + if (! custom_supports_data_string.empty()) { + output_buffer += " "; + output_buffer += CUSTOM_SUPPORTS_ATTR; + output_buffer += "=\""; + output_buffer += custom_supports_data_string; + output_buffer += "\""; + } + + std::string custom_seam_data_string = volume->seam_facets.get_triangle_as_string(i); + if (! custom_seam_data_string.empty()) { + output_buffer += " "; + output_buffer += CUSTOM_SEAM_ATTR; + output_buffer += "=\""; + output_buffer += custom_seam_data_string; + output_buffer += "\""; + } + + std::string mmu_painting_data_string = volume->mmu_segmentation_facets.get_triangle_as_string(i); + if (! mmu_painting_data_string.empty()) { + output_buffer += " "; + output_buffer += MMU_SEGMENTATION_ATTR; + output_buffer += "=\""; + output_buffer += mmu_painting_data_string; + output_buffer += "\""; + } + + output_buffer += "/>\n"; + + if (! flush()) + return false; + } + } + + output_buffer += " \n \n"; + + // Force flush. + return flush(true); + } + + bool _3MF_Exporter::_add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items) + { + // This happens for empty projects + if (build_items.size() == 0) { + add_error("No build item found"); + return true; + } + + stream << " <" << BUILD_TAG << ">\n"; + + for (const BuildItem& item : build_items) { + stream << " <" << ITEM_TAG << " " << OBJECTID_ATTR << "=\"" << item.id << "\" " << TRANSFORM_ATTR << "=\""; + for (unsigned c = 0; c < 4; ++c) { + for (unsigned r = 0; r < 3; ++r) { + stream << item.transform(r, c); + if (r != 2 || c != 3) + stream << " "; + } + } + stream << "\" " << PRINTABLE_ATTR << "=\"" << item.printable << "\"/>\n"; + } + + stream << " \n"; + + return true; + } + + bool _3MF_Exporter::_add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model) + { + assert(is_decimal_separator_point()); + std::string out = ""; + char buffer[1024]; + + unsigned int count = 0; + for (const ModelObject* object : model.objects) { + ++count; + const std::vector& layer_height_profile = object->layer_height_profile.get(); + if (layer_height_profile.size() >= 4 && layer_height_profile.size() % 2 == 0) { + sprintf(buffer, "object_id=%d|", count); + out += buffer; + + // Store the layer height profile as a single semicolon separated list. + for (size_t i = 0; i < layer_height_profile.size(); ++i) { + sprintf(buffer, (i == 0) ? "%f" : ";%f", layer_height_profile[i]); + out += buffer; + } + + out += "\n"; + } + } + + if (!out.empty()) { + if (!mz_zip_writer_add_mem(&archive, LAYER_HEIGHTS_PROFILE_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { + add_error("Unable to add layer heights profile file to archive"); + return false; + } + } + + return true; + } + + bool _3MF_Exporter::_add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model) + { + std::string out = ""; + pt::ptree tree; + + unsigned int object_cnt = 0; + for (const ModelObject* object : model.objects) { + object_cnt++; + const t_layer_config_ranges& ranges = object->layer_config_ranges; + if (!ranges.empty()) + { + pt::ptree& obj_tree = tree.add("objects.object",""); + + obj_tree.put(".id", object_cnt); + + // Store the layer config ranges. + for (const auto& range : ranges) { + pt::ptree& range_tree = obj_tree.add("range", ""); + + // store minX and maxZ + range_tree.put(".min_z", range.first.first); + range_tree.put(".max_z", range.first.second); + + // store range configuration + const ModelConfig& config = range.second; + for (const std::string& opt_key : config.keys()) { + pt::ptree& opt_tree = range_tree.add("option", config.opt_serialize(opt_key)); + opt_tree.put(".opt_key", opt_key); + } + } + } + } + + if (!tree.empty()) { + std::ostringstream oss; + pt::write_xml(oss, tree); + out = oss.str(); + + // Post processing("beautification") of the output string for a better preview + boost::replace_all(out, ">\n \n \n ", ">\n "); + boost::replace_all(out, ">", ">\n "); + // OR just + boost::replace_all(out, "><", ">\n<"); + } + + if (!out.empty()) { + if (!mz_zip_writer_add_mem(&archive, LAYER_CONFIG_RANGES_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { + add_error("Unable to add layer heights profile file to archive"); + return false; + } + } + + return true; + } + + bool _3MF_Exporter::_add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model) + { + assert(is_decimal_separator_point()); + std::string out = ""; + char buffer[1024]; + + unsigned int count = 0; + for (const ModelObject* object : model.objects) { + ++count; + const std::vector& sla_support_points = object->sla_support_points; + if (!sla_support_points.empty()) { + sprintf(buffer, "object_id=%d|", count); + out += buffer; + + // Store the layer height profile as a single space separated list. + for (size_t i = 0; i < sla_support_points.size(); ++i) { + sprintf(buffer, (i==0 ? "%f %f %f %f %f" : " %f %f %f %f %f"), sla_support_points[i].pos(0), sla_support_points[i].pos(1), sla_support_points[i].pos(2), sla_support_points[i].head_front_radius, (float)sla_support_points[i].is_new_island); + out += buffer; + } + out += "\n"; + } + } + + if (!out.empty()) { + // Adds version header at the beginning: + out = std::string("support_points_format_version=") + std::to_string(support_points_format_version) + std::string("\n") + out; + + if (!mz_zip_writer_add_mem(&archive, SLA_SUPPORT_POINTS_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { + add_error("Unable to add sla support points file to archive"); + return false; + } + } + return true; + } + + bool _3MF_Exporter::_add_sla_drain_holes_file_to_archive(mz_zip_archive& archive, Model& model) + { + assert(is_decimal_separator_point()); + const char *const fmt = "object_id=%d|"; + std::string out; + + unsigned int count = 0; + for (const ModelObject* object : model.objects) { + ++count; + sla::DrainHoles drain_holes = object->sla_drain_holes; + + // The holes were placed 1mm above the mesh in the first implementation. + // This was a bad idea and the reference point was changed in 2.3 so + // to be on the mesh exactly. The elevated position is still saved + // in 3MFs for compatibility reasons. + for (sla::DrainHole& hole : drain_holes) { + hole.pos -= hole.normal.normalized(); + hole.height += 1.f; + } + + if (!drain_holes.empty()) { + out += string_printf(fmt, count); + + // Store the layer height profile as a single space separated list. + for (size_t i = 0; i < drain_holes.size(); ++i) + out += string_printf((i == 0 ? "%f %f %f %f %f %f %f %f" : " %f %f %f %f %f %f %f %f"), + drain_holes[i].pos(0), + drain_holes[i].pos(1), + drain_holes[i].pos(2), + drain_holes[i].normal(0), + drain_holes[i].normal(1), + drain_holes[i].normal(2), + drain_holes[i].radius, + drain_holes[i].height); + + out += "\n"; + } + } + + if (!out.empty()) { + // Adds version header at the beginning: + out = std::string("drain_holes_format_version=") + std::to_string(drain_holes_format_version) + std::string("\n") + out; + + if (!mz_zip_writer_add_mem(&archive, SLA_DRAIN_HOLES_FILE.c_str(), static_cast(out.data()), out.length(), mz_uint(MZ_DEFAULT_COMPRESSION))) { + add_error("Unable to add sla support points file to archive"); + return false; + } + } + return true; + } + + bool _3MF_Exporter::_add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config) + { + assert(is_decimal_separator_point()); + char buffer[1024]; + sprintf(buffer, "; %s\n\n", header_slic3r_generated().c_str()); + std::string out = buffer; + + for (const std::string &key : config.keys()) + if (key != "compatible_printers") + out += "; " + key + " = " + config.opt_serialize(key) + "\n"; + + if (!out.empty()) { + if (!mz_zip_writer_add_mem(&archive, PRINT_CONFIG_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { + add_error("Unable to add print config file to archive"); + return false; + } + } + + return true; + } + + bool _3MF_Exporter::_add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, const IdToObjectDataMap &objects_data) + { + std::stringstream stream; + // Store mesh transformation in full precision, as the volumes are stored transformed and they need to be transformed back + // when loaded as accurately as possible. + stream << std::setprecision(std::numeric_limits::max_digits10); + stream << "\n"; + stream << "<" << CONFIG_TAG << ">\n"; + + for (const IdToObjectDataMap::value_type& obj_metadata : objects_data) { + const ModelObject* obj = obj_metadata.second.object; + if (obj != nullptr) { + // Output of instances count added because of github #3435, currently not used by PrusaSlicer + stream << " <" << OBJECT_TAG << " " << ID_ATTR << "=\"" << obj_metadata.first << "\" " << INSTANCESCOUNT_ATTR << "=\"" << obj->instances.size() << "\">\n"; + + // stores object's name + if (!obj->name.empty()) + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << OBJECT_TYPE << "\" " << KEY_ATTR << "=\"name\" " << VALUE_ATTR << "=\"" << xml_escape(obj->name) << "\"/>\n"; + + // stores object's config data + for (const std::string& key : obj->config.keys()) { + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << OBJECT_TYPE << "\" " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << obj->config.opt_serialize(key) << "\"/>\n"; + } + + for (const ModelVolume* volume : obj_metadata.second.object->volumes) { + if (volume != nullptr) { + const VolumeToOffsetsMap& offsets = obj_metadata.second.volumes_offsets; + VolumeToOffsetsMap::const_iterator it = offsets.find(volume); + if (it != offsets.end()) { + // stores volume's offsets + stream << " <" << VOLUME_TAG << " "; + stream << FIRST_TRIANGLE_ID_ATTR << "=\"" << it->second.first_triangle_id << "\" "; + stream << LAST_TRIANGLE_ID_ATTR << "=\"" << it->second.last_triangle_id << "\">\n"; + + // stores volume's name + if (!volume->name.empty()) + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << NAME_KEY << "\" " << VALUE_ATTR << "=\"" << xml_escape(volume->name) << "\"/>\n"; + + // stores volume's modifier field (legacy, to support old slicers) + if (volume->is_modifier()) + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << MODIFIER_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n"; + // stores volume's type (overrides the modifier field above) + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << VOLUME_TYPE_KEY << "\" " << + VALUE_ATTR << "=\"" << ModelVolume::type_to_string(volume->type()) << "\"/>\n"; + + // stores volume's local matrix + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << MATRIX_KEY << "\" " << VALUE_ATTR << "=\""; + const Transform3d matrix = volume->get_matrix() * volume->source.transform.get_matrix(); + for (int r = 0; r < 4; ++r) { + for (int c = 0; c < 4; ++c) { + stream << matrix(r, c); + if (r != 3 || c != 3) + stream << " "; + } + } + stream << "\"/>\n"; + + // stores volume's source data + { + std::string input_file = xml_escape(m_fullpath_sources ? volume->source.input_file : boost::filesystem::path(volume->source.input_file).filename().string()); + std::string prefix = std::string(" <") + METADATA_TAG + " " + TYPE_ATTR + "=\"" + VOLUME_TYPE + "\" " + KEY_ATTR + "=\""; + if (! volume->source.input_file.empty()) { + stream << prefix << SOURCE_FILE_KEY << "\" " << VALUE_ATTR << "=\"" << input_file << "\"/>\n"; + stream << prefix << SOURCE_OBJECT_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.object_idx << "\"/>\n"; + stream << prefix << SOURCE_VOLUME_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.volume_idx << "\"/>\n"; + stream << prefix << SOURCE_OFFSET_X_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(0) << "\"/>\n"; + stream << prefix << SOURCE_OFFSET_Y_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(1) << "\"/>\n"; + stream << prefix << SOURCE_OFFSET_Z_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(2) << "\"/>\n"; + } + assert(! volume->source.is_converted_from_inches || ! volume->source.is_converted_from_meters); + if (volume->source.is_converted_from_inches) + stream << prefix << SOURCE_IN_INCHES_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n"; + else if (volume->source.is_converted_from_meters) + stream << prefix << SOURCE_IN_METERS_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n"; +#if ENABLE_RELOAD_FROM_DISK_REWORK + if (volume->source.is_from_builtin_objects) + stream << prefix << SOURCE_IS_BUILTIN_VOLUME_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n"; +#endif // ENABLE_RELOAD_FROM_DISK_REWORK + } + + // stores volume's config data + for (const std::string& key : volume->config.keys()) { + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << volume->config.opt_serialize(key) << "\"/>\n"; + } + + // stores mesh's statistics + const RepairedMeshErrors& stats = volume->mesh().stats().repaired_errors; + stream << " <" << MESH_TAG << " "; + stream << MESH_STAT_EDGES_FIXED << "=\"" << stats.edges_fixed << "\" "; + stream << MESH_STAT_DEGENERATED_FACETS << "=\"" << stats.degenerate_facets << "\" "; + stream << MESH_STAT_FACETS_REMOVED << "=\"" << stats.facets_removed << "\" "; + stream << MESH_STAT_FACETS_RESERVED << "=\"" << stats.facets_reversed << "\" "; + stream << MESH_STAT_BACKWARDS_EDGES << "=\"" << stats.backwards_edges << "\"/>\n"; + + stream << " \n"; + } + } + } + + stream << " \n"; + } + } + + stream << "\n"; + + std::string out = stream.str(); + + if (!mz_zip_writer_add_mem(&archive, MODEL_CONFIG_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { + add_error("Unable to add model config file to archive"); + return false; + } + + return true; + } + +bool _3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config) +{ + std::string out = ""; + + if (!model.custom_gcode_per_print_z.gcodes.empty()) { + pt::ptree tree; + pt::ptree& main_tree = tree.add("custom_gcodes_per_print_z", ""); + + for (const CustomGCode::Item& code : model.custom_gcode_per_print_z.gcodes) { + pt::ptree& code_tree = main_tree.add("code", ""); + + // store data of custom_gcode_per_print_z + code_tree.put(".print_z" , code.print_z ); + code_tree.put(".type" , static_cast(code.type)); + code_tree.put(".extruder" , code.extruder ); + code_tree.put(".color" , code.color ); + code_tree.put(".extra" , code.extra ); + + // add gcode field data for the old version of the PrusaSlicer + std::string gcode = code.type == CustomGCode::ColorChange ? config->opt_string("color_change_gcode") : + code.type == CustomGCode::PausePrint ? config->opt_string("pause_print_gcode") : + code.type == CustomGCode::Template ? config->opt_string("template_custom_gcode") : + code.type == CustomGCode::ToolChange ? "tool_change" : code.extra; + code_tree.put(".gcode" , gcode ); + } + + pt::ptree& mode_tree = main_tree.add("mode", ""); + // store mode of a custom_gcode_per_print_z + mode_tree.put(".value", model.custom_gcode_per_print_z.mode == CustomGCode::Mode::SingleExtruder ? CustomGCode::SingleExtruderMode : + model.custom_gcode_per_print_z.mode == CustomGCode::Mode::MultiAsSingle ? CustomGCode::MultiAsSingleMode : + CustomGCode::MultiExtruderMode); + + if (!tree.empty()) { + std::ostringstream oss; + boost::property_tree::write_xml(oss, tree); + out = oss.str(); + + // Post processing("beautification") of the output string + boost::replace_all(out, "><", ">\n<"); + } + } + + if (!out.empty()) { + if (!mz_zip_writer_add_mem(&archive, CUSTOM_GCODE_PER_PRINT_Z_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { + add_error("Unable to add custom Gcodes per print_z file to archive"); + return false; + } + } + + return true; +} + +// Perform conversions based on the config values available. +//FIXME provide a version of PrusaSlicer that stored the project file (3MF). +static void handle_legacy_project_loaded(unsigned int version_project_file, DynamicPrintConfig& config) +{ + if (! config.has("brim_separation")) { + if (auto *opt_elephant_foot = config.option("elefant_foot_compensation", false); opt_elephant_foot) { + // Conversion from older PrusaSlicer which applied brim separation equal to elephant foot compensation. + auto *opt_brim_separation = config.option("brim_separation", true); + opt_brim_separation->value = opt_elephant_foot->value; + } + } +} + +bool is_project_3mf(const std::string& filename) +{ + mz_zip_archive archive; + mz_zip_zero_struct(&archive); + + if (!open_zip_reader(&archive, filename)) + return false; + + mz_uint num_entries = mz_zip_reader_get_num_files(&archive); + + // loop the entries to search for config + mz_zip_archive_file_stat stat; + bool config_found = false; + for (mz_uint i = 0; i < num_entries; ++i) { + if (mz_zip_reader_file_stat(&archive, i, &stat)) { + std::string name(stat.m_filename); + std::replace(name.begin(), name.end(), '\\', '/'); + + if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE)) { + config_found = true; + break; + } + } + } + + close_zip_reader(&archive); + + return config_found; +} + +bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model* model, bool check_version) +{ + if (path == nullptr || model == nullptr) + return false; + + // All import should use "C" locales for number formatting. + CNumericLocalesSetter locales_setter; + _3MF_Importer importer; + bool res = importer.load_model_from_file(path, *model, config, config_substitutions, check_version); + importer.log_errors(); + handle_legacy_project_loaded(importer.version(), config); + return res; +} + +bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64) +{ + // All export should use "C" locales for number formatting. + CNumericLocalesSetter locales_setter; + + if (path == nullptr || model == nullptr) + return false; + + _3MF_Exporter exporter; + bool res = exporter.save_model_to_file(path, *model, config, fullpath_sources, thumbnail_data, zip64); + if (!res) + exporter.log_errors(); + + return res; +} +} // namespace Slic3r From f8ec5fc9e78c851952ef4c9bfa479afdd4841e73 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 23 May 2022 10:55:23 +0200 Subject: [PATCH 08/22] Revert CMAKE_FIND_PACKAGE_PREFER_CONFIG as its from cmake > 3.13 Do the overriding in the appropriate find modules in cmake/modules --- CMakeLists.txt | 21 +---- cmake/modules/FindCURL.cmake | 155 ++++++++++++++++++++--------------- 2 files changed, 89 insertions(+), 87 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 002cd34567..4267de2fb9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,14 +43,6 @@ set(SLIC3R_GTK "2" CACHE STRING "GTK version to use with wxWidgets on Linux") set(IS_CROSS_COMPILE FALSE) -if (SLIC3R_STATIC) - # Prefer config scripts over find modules. This is helpful when building with - # the static dependencies. Many libraries have their own export scripts - # while having a Find module in standard cmake installation. - # (e.g. CURL) - set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON) -endif () - if (APPLE) set(CMAKE_FIND_FRAMEWORK LAST) set(CMAKE_FIND_APPBUNDLE LAST) @@ -458,19 +450,10 @@ if (NOT EIGEN3_FOUND) endif () include_directories(BEFORE SYSTEM ${EIGEN3_INCLUDE_DIR}) -# Find expat or use bundled version -# Always use the system libexpat on Linux. - +# Find expat. We have our overriden FindEXPAT which exports libexpat target +# no matter what. find_package(EXPAT REQUIRED) -add_library(libexpat INTERFACE) - -if (TARGET EXPAT::EXPAT ) - target_link_libraries(libexpat INTERFACE EXPAT::EXPAT) -elseif(TARGET expat::expat) - target_link_libraries(libexpat INTERFACE expat::expat) -endif () - find_package(PNG REQUIRED) set(OpenGL_GL_PREFERENCE "LEGACY") diff --git a/cmake/modules/FindCURL.cmake b/cmake/modules/FindCURL.cmake index e0deafa45c..ced5911347 100644 --- a/cmake/modules/FindCURL.cmake +++ b/cmake/modules/FindCURL.cmake @@ -30,82 +30,101 @@ # ``CURL_VERSION_STRING`` # The version of curl found. -# Look for the header file. -find_path(CURL_INCLUDE_DIR NAMES curl/curl.h) -mark_as_advanced(CURL_INCLUDE_DIR) +# First, prefer config scripts +set(_q "") +if(CURL_FIND_QUIETLY) + set(_q QUIET) +endif() +find_package(CURL ${CURL_FIND_VERSION} CONFIG ${_q}) -if(NOT CURL_LIBRARY) - # Look for the library (sorted from most current/relevant entry to least). - find_library(CURL_LIBRARY_RELEASE NAMES - curl - # Windows MSVC prebuilts: - curllib - libcurl_imp - curllib_static - # Windows older "Win32 - MSVC" prebuilts (libcurl.lib, e.g. libcurl-7.15.5-win32-msvc.zip): - libcurl - # Static library on Windows - libcurl_a - ) - mark_as_advanced(CURL_LIBRARY_RELEASE) - - find_library(CURL_LIBRARY_DEBUG NAMES - # Windows MSVC CMake builds in debug configuration on vcpkg: - libcurl-d_imp - libcurl-d - # Static library on Windows, compiled in debug mode - libcurl_a_debug - ) - mark_as_advanced(CURL_LIBRARY_DEBUG) - - include(${CMAKE_CURRENT_LIST_DIR}/SelectLibraryConfigurations_SLIC3R.cmake) - select_library_configurations_SLIC3R(CURL) +if(NOT CURL_FIND_QUIETLY) + if (NOT CURL_FOUND) + message(STATUS "Falling back to MODULE search for CURL...") + else() + message(STATUS "CURL found in ${CURL_DIR}") + endif() endif() -if(CURL_INCLUDE_DIR) - foreach(_curl_version_header curlver.h curl.h) - if(EXISTS "${CURL_INCLUDE_DIR}/curl/${_curl_version_header}") - file(STRINGS "${CURL_INCLUDE_DIR}/curl/${_curl_version_header}" curl_version_str REGEX "^#define[\t ]+LIBCURL_VERSION[\t ]+\".*\"") +if (NOT CURL_FOUND) - string(REGEX REPLACE "^#define[\t ]+LIBCURL_VERSION[\t ]+\"([^\"]*)\".*" "\\1" CURL_VERSION_STRING "${curl_version_str}") - unset(curl_version_str) - break() - endif() - endforeach() -endif() + # Look for the header file. + find_path(CURL_INCLUDE_DIR NAMES curl/curl.h) + mark_as_advanced(CURL_INCLUDE_DIR) -include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs_SLIC3R.cmake) -FIND_PACKAGE_HANDLE_STANDARD_ARGS_SLIC3R(CURL - REQUIRED_VARS CURL_LIBRARY CURL_INCLUDE_DIR - VERSION_VAR CURL_VERSION_STRING) + if(NOT CURL_LIBRARY) + # Look for the library (sorted from most current/relevant entry to least). + find_library(CURL_LIBRARY_RELEASE NAMES + curl + # Windows MSVC prebuilts: + curllib + libcurl_imp + curllib_static + # Windows older "Win32 - MSVC" prebuilts (libcurl.lib, e.g. libcurl-7.15.5-win32-msvc.zip): + libcurl + # Static library on Windows + libcurl_a + ) + mark_as_advanced(CURL_LIBRARY_RELEASE) -if(CURL_FOUND) - set(CURL_LIBRARIES ${CURL_LIBRARY}) - set(CURL_INCLUDE_DIRS ${CURL_INCLUDE_DIR}) + find_library(CURL_LIBRARY_DEBUG NAMES + # Windows MSVC CMake builds in debug configuration on vcpkg: + libcurl-d_imp + libcurl-d + # Static library on Windows, compiled in debug mode + libcurl_a_debug + ) + mark_as_advanced(CURL_LIBRARY_DEBUG) - if(NOT TARGET CURL::libcurl) - add_library(CURL::libcurl UNKNOWN IMPORTED) - set_target_properties(CURL::libcurl PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${CURL_INCLUDE_DIRS}") + include(${CMAKE_CURRENT_LIST_DIR}/SelectLibraryConfigurations_SLIC3R.cmake) + select_library_configurations_SLIC3R(CURL) + endif() - if(EXISTS "${CURL_LIBRARY}") + if(CURL_INCLUDE_DIR) + foreach(_curl_version_header curlver.h curl.h) + if(EXISTS "${CURL_INCLUDE_DIR}/curl/${_curl_version_header}") + file(STRINGS "${CURL_INCLUDE_DIR}/curl/${_curl_version_header}" curl_version_str REGEX "^#define[\t ]+LIBCURL_VERSION[\t ]+\".*\"") + + string(REGEX REPLACE "^#define[\t ]+LIBCURL_VERSION[\t ]+\"([^\"]*)\".*" "\\1" CURL_VERSION_STRING "${curl_version_str}") + unset(curl_version_str) + break() + endif() + endforeach() + endif() + + include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs_SLIC3R.cmake) + FIND_PACKAGE_HANDLE_STANDARD_ARGS_SLIC3R(CURL + REQUIRED_VARS CURL_LIBRARY CURL_INCLUDE_DIR + VERSION_VAR CURL_VERSION_STRING) + + if(CURL_FOUND) + set(CURL_LIBRARIES ${CURL_LIBRARY}) + set(CURL_INCLUDE_DIRS ${CURL_INCLUDE_DIR}) + + if(NOT TARGET CURL::libcurl) + add_library(CURL::libcurl UNKNOWN IMPORTED) set_target_properties(CURL::libcurl PROPERTIES - IMPORTED_LINK_INTERFACE_LANGUAGES "C" - IMPORTED_LOCATION "${CURL_LIBRARY}") - endif() - if(CURL_LIBRARY_RELEASE) - set_property(TARGET CURL::libcurl APPEND PROPERTY - IMPORTED_CONFIGURATIONS RELEASE) - set_target_properties(CURL::libcurl PROPERTIES - IMPORTED_LINK_INTERFACE_LANGUAGES "C" - IMPORTED_LOCATION_RELEASE "${CURL_LIBRARY_RELEASE}") - endif() - if(CURL_LIBRARY_DEBUG) - set_property(TARGET CURL::libcurl APPEND PROPERTY - IMPORTED_CONFIGURATIONS DEBUG) - set_target_properties(CURL::libcurl PROPERTIES - IMPORTED_LINK_INTERFACE_LANGUAGES "C" - IMPORTED_LOCATION_DEBUG "${CURL_LIBRARY_DEBUG}") + INTERFACE_INCLUDE_DIRECTORIES "${CURL_INCLUDE_DIRS}") + + if(EXISTS "${CURL_LIBRARY}") + set_target_properties(CURL::libcurl PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${CURL_LIBRARY}") + endif() + if(CURL_LIBRARY_RELEASE) + set_property(TARGET CURL::libcurl APPEND PROPERTY + IMPORTED_CONFIGURATIONS RELEASE) + set_target_properties(CURL::libcurl PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION_RELEASE "${CURL_LIBRARY_RELEASE}") + endif() + if(CURL_LIBRARY_DEBUG) + set_property(TARGET CURL::libcurl APPEND PROPERTY + IMPORTED_CONFIGURATIONS DEBUG) + set_target_properties(CURL::libcurl PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION_DEBUG "${CURL_LIBRARY_DEBUG}") + endif() endif() endif() -endif() + +endif (NOT CURL_FOUND) From 6047eec609cf38b5c4f951ea11dc6e66b30c8359 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 24 May 2022 10:20:02 +0200 Subject: [PATCH 09/22] When, on Windows, the application tries to automatically switch to MESA OpenGL library and the system opengl32.dll is not unloaded, prompt the user with a dialog asking to rerun using the --sw-renderer option --- src/PrusaSlicer_app_msvc.cpp | 49 +++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/src/PrusaSlicer_app_msvc.cpp b/src/PrusaSlicer_app_msvc.cpp index 2ccf2f1ffd..83f24c3075 100644 --- a/src/PrusaSlicer_app_msvc.cpp +++ b/src/PrusaSlicer_app_msvc.cpp @@ -66,16 +66,25 @@ public: return this->success; } - void unload_opengl_dll() + bool unload_opengl_dll() { - if (this->hOpenGL) { - BOOL released = FreeLibrary(this->hOpenGL); - if (released) - printf("System OpenGL library released\n"); + if (this->hOpenGL != nullptr) { + if (::FreeLibrary(this->hOpenGL) != FALSE) { + if (::GetModuleHandle(L"opengl32.dll") == nullptr) { + printf("System OpenGL library successfully released\n"); + this->hOpenGL = nullptr; + return true; + } + else + printf("System OpenGL library released but not removed\n"); + } else printf("System OpenGL library NOT released\n"); - this->hOpenGL = nullptr; + + return false; } + + return true; } bool is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const @@ -270,20 +279,26 @@ int wmain(int argc, wchar_t **argv) // https://wiki.qt.io/Cross_compiling_Mesa_for_Windows // http://download.qt.io/development_releases/prebuilt/llvmpipe/windows/ if (load_mesa) { - opengl_version_check.unload_opengl_dll(); - wchar_t path_to_mesa[MAX_PATH + 1] = { 0 }; - wcscpy(path_to_mesa, path_to_exe); - wcscat(path_to_mesa, L"mesa\\opengl32.dll"); - printf("Loading MESA OpenGL library: %S\n", path_to_mesa); - HINSTANCE hInstance_OpenGL = LoadLibraryExW(path_to_mesa, nullptr, 0); - if (hInstance_OpenGL == nullptr) { - printf("MESA OpenGL library was not loaded\n"); - } else - printf("MESA OpenGL library was loaded sucessfully\n"); + bool res = opengl_version_check.unload_opengl_dll(); + if (!res) { + MessageBox(nullptr, L"PrusaSlicer was unable to automatically switch to MESA OpenGL library\nPlease, try to run the application using the '--sw-renderer' option.\n", + L"PrusaSlicer Warning", MB_OK); + return -1; + } + else { + wchar_t path_to_mesa[MAX_PATH + 1] = { 0 }; + wcscpy(path_to_mesa, path_to_exe); + wcscat(path_to_mesa, L"mesa\\opengl32.dll"); + printf("Loading MESA OpenGL library: %S\n", path_to_mesa); + HINSTANCE hInstance_OpenGL = LoadLibraryExW(path_to_mesa, nullptr, 0); + if (hInstance_OpenGL == nullptr) + printf("MESA OpenGL library was not loaded\n"); + else + printf("MESA OpenGL library was loaded sucessfully\n"); + } } #endif /* SLIC3R_GUI */ - wchar_t path_to_slic3r[MAX_PATH + 1] = { 0 }; wcscpy(path_to_slic3r, path_to_exe); wcscat(path_to_slic3r, L"PrusaSlicer.dll"); From ed482316ee22dd3888a73548696f3af3ea51cda3 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 25 May 2022 09:36:52 +0200 Subject: [PATCH 10/22] Revert of 39cefdad89f34741b4c6b17a4e42823a57a76c9c --- src/libslic3r/Technologies.hpp | 2 -- src/slic3r/GUI/ConfigWizard.cpp | 47 +++++++++--------------------- src/slic3r/GUI/GUI_App.cpp | 51 ++++++++++----------------------- src/slic3r/GUI/Preferences.cpp | 25 ++++++---------- 4 files changed, 38 insertions(+), 87 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 934d1b978f..a89a7c8b01 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -81,8 +81,6 @@ #define ENABLE_USED_FILAMENT_POST_PROCESS (1 && ENABLE_2_5_0_ALPHA1) // Enable gizmo grabbers to share common models #define ENABLE_GIZMO_GRABBER_REFACTOR (1 && ENABLE_2_5_0_ALPHA1) -// Disable association to 3mf and stl files if the application is run on Windows 8 or later -#define ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER (1 && ENABLE_2_5_0_ALPHA1) #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index b9dcc1a4f6..cbee620137 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -1934,10 +1934,7 @@ void ConfigWizard::priv::load_pages() index->add_page(page_update); index->add_page(page_reload_from_disk); #ifdef _WIN32 -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - if (page_files_association != nullptr) -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - index->add_page(page_files_association); + index->add_page(page_files_association); #endif // _WIN32 index->add_page(page_mode); @@ -2750,32 +2747,20 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese app_config->set("export_sources_full_pathnames", page_reload_from_disk->full_pathnames ? "1" : "0"); #ifdef _WIN32 -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - if (page_files_association != nullptr) { -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - app_config->set("associate_3mf", page_files_association->associate_3mf() ? "1" : "0"); - app_config->set("associate_stl", page_files_association->associate_stl() ? "1" : "0"); -// app_config->set("associate_gcode", page_files_association->associate_gcode() ? "1" : "0"); + app_config->set("associate_3mf", page_files_association->associate_3mf() ? "1" : "0"); + app_config->set("associate_stl", page_files_association->associate_stl() ? "1" : "0"); +// app_config->set("associate_gcode", page_files_association->associate_gcode() ? "1" : "0"); - if (wxGetApp().is_editor()) { - if (page_files_association->associate_3mf()) - wxGetApp().associate_3mf_files(); - if (page_files_association->associate_stl()) - wxGetApp().associate_stl_files(); - } -// else { -// if (page_files_association->associate_gcode()) -// wxGetApp().associate_gcode_files(); -// } -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + if (wxGetApp().is_editor()) { + if (page_files_association->associate_3mf()) + wxGetApp().associate_3mf_files(); + if (page_files_association->associate_stl()) + wxGetApp().associate_stl_files(); } - else { - app_config->set("associate_3mf", "0"); - app_config->set("associate_stl", "0"); -// app_config->set("associate_gcode", "0"); - } -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - +// else { +// if (page_files_association->associate_gcode()) +// wxGetApp().associate_gcode_files(); +// } #endif // _WIN32 page_mode->serialize_mode(app_config); @@ -2935,11 +2920,7 @@ ConfigWizard::ConfigWizard(wxWindow *parent) p->add_page(p->page_update = new PageUpdate(this)); p->add_page(p->page_reload_from_disk = new PageReloadFromDisk(this)); #ifdef _WIN32 -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - // file association is not possible anymore starting with Win 8 - if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - p->add_page(p->page_files_association = new PageFilesAssociation(this)); + p->add_page(p->page_files_association = new PageFilesAssociation(this)); #endif // _WIN32 p->add_page(p->page_mode = new PageMode(this)); p->add_page(p->page_firmware = new PageFirmware(this)); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index af9b63acd4..c871befbf7 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1203,17 +1203,10 @@ bool GUI_App::on_init_inner() if (is_editor()) { #ifdef __WXMSW__ -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - // file association is not possible anymore starting with Win 8 - if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) { -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - if (app_config->get("associate_3mf") == "1") - associate_3mf_files(); - if (app_config->get("associate_stl") == "1") - associate_stl_files(); -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - } -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + if (app_config->get("associate_3mf") == "1") + associate_3mf_files(); + if (app_config->get("associate_stl") == "1") + associate_stl_files(); #endif // __WXMSW__ preset_updater = new PresetUpdater(); @@ -1251,15 +1244,8 @@ bool GUI_App::on_init_inner() } else { #ifdef __WXMSW__ -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - // file association is not possible anymore starting with Win 8 - if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) { -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - if (app_config->get("associate_gcode") == "1") - associate_gcode_files(); -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - } -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + if (app_config->get("associate_gcode") == "1") + associate_gcode_files(); #endif // __WXMSW__ } @@ -2432,23 +2418,16 @@ void GUI_App::open_preferences(const std::string& highlight_option /*= std::stri this->plater_->refresh_print(); #ifdef _WIN32 -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - // file association is not possible anymore starting with Win 8 - if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) { -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - if (is_editor()) { - if (app_config->get("associate_3mf") == "1") - associate_3mf_files(); - if (app_config->get("associate_stl") == "1") - associate_stl_files(); - } - else { - if (app_config->get("associate_gcode") == "1") - associate_gcode_files(); - } -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + if (is_editor()) { + if (app_config->get("associate_3mf") == "1") + associate_3mf_files(); + if (app_config->get("associate_stl") == "1") + associate_stl_files(); + } + else { + if (app_config->get("associate_gcode") == "1") + associate_gcode_files(); } -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER #endif // _WIN32 if (mainframe->preferences_dialog->settings_layout_changed()) { diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 3da843f894..1124c45520 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -230,23 +230,16 @@ void PreferencesDialog::build() app_config->get("export_sources_full_pathnames") == "1"); #ifdef _WIN32 -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - // file association is not possible anymore starting with Win 8 - if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) { -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - // Please keep in sync with ConfigWizard - append_bool_option(m_optgroup_general, "associate_3mf", - L("Associate .3mf files to PrusaSlicer"), - L("If enabled, sets PrusaSlicer as default application to open .3mf files."), - app_config->get("associate_3mf") == "1"); + // Please keep in sync with ConfigWizard + append_bool_option(m_optgroup_general, "associate_3mf", + L("Associate .3mf files to PrusaSlicer"), + L("If enabled, sets PrusaSlicer as default application to open .3mf files."), + app_config->get("associate_3mf") == "1"); - append_bool_option(m_optgroup_general, "associate_stl", - L("Associate .stl files to PrusaSlicer"), - L("If enabled, sets PrusaSlicer as default application to open .stl files."), - app_config->get("associate_stl") == "1"); -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - } -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + append_bool_option(m_optgroup_general, "associate_stl", + L("Associate .stl files to PrusaSlicer"), + L("If enabled, sets PrusaSlicer as default application to open .stl files."), + app_config->get("associate_stl") == "1"); #endif // _WIN32 m_optgroup_general->append_separator(); From fcd3966a5b2f3b6c2d314946db995c300af2b7c0 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 25 May 2022 11:01:48 +0200 Subject: [PATCH 11/22] Fixed crash when opening the preference dialog in GCodeViewer --- src/slic3r/GUI/Preferences.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 1124c45520..a4317d8f45 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -79,9 +79,11 @@ void PreferencesDialog::show(const std::string& highlight_opt_key /*= std::strin m_custom_toolbar_size = atoi(get_app_config()->get("custom_toolbar_size").c_str()); m_use_custom_toolbar_size = get_app_config()->get("use_custom_toolbar_size") == "1"; - // update colors for color pickers - update_color(m_sys_colour, wxGetApp().get_label_clr_sys()); - update_color(m_mod_colour, wxGetApp().get_label_clr_modified()); + if (wxGetApp().is_editor()) { + // update colors for color pickers + update_color(m_sys_colour, wxGetApp().get_label_clr_sys()); + update_color(m_mod_colour, wxGetApp().get_label_clr_modified()); + } this->ShowModal(); } From 1b09628b0d224bacb94eb5ed3c9ebefbef9aa62b Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 25 May 2022 13:14:33 +0200 Subject: [PATCH 12/22] #8332 - File association on Windows: reimplemented to allow setting PrusaSlicer as default application for .3mf and .stl files and GCodeViewer as default application for .gcode files --- src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/GUI_App.cpp | 104 +------ src/slic3r/Utils/WinRegistry.cpp | 490 +++++++++++++++++++++++++++++++ src/slic3r/Utils/WinRegistry.hpp | 23 ++ 4 files changed, 519 insertions(+), 100 deletions(-) create mode 100644 src/slic3r/Utils/WinRegistry.cpp create mode 100644 src/slic3r/Utils/WinRegistry.hpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index ed994be184..09c2098d9b 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -246,6 +246,8 @@ set(SLIC3R_GUI_SOURCES Utils/TCPConsole.hpp Utils/MKS.cpp Utils/MKS.hpp + Utils/WinRegistry.cpp + Utils/WinRegistry.hpp ) if (APPLE) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index c871befbf7..b688e11f5a 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -59,6 +59,7 @@ #include "../Utils/Process.hpp" #include "../Utils/MacDarkMode.hpp" #include "../Utils/AppUpdater.hpp" +#include "../Utils/WinRegistry.hpp" #include "slic3r/Config/Snapshot.hpp" #include "ConfigSnapshotDialog.hpp" #include "FirmwareDialog.hpp" @@ -3135,119 +3136,22 @@ bool GUI_App::open_browser_with_warning_dialog(const wxString& url, wxWindow* pa #ifdef __WXMSW__ -static bool set_into_win_registry(HKEY hkeyHive, const wchar_t* pszVar, const wchar_t* pszValue) -{ - // see as reference: https://stackoverflow.com/questions/20245262/c-program-needs-an-file-association - wchar_t szValueCurrent[1000]; - DWORD dwType; - DWORD dwSize = sizeof(szValueCurrent); - - int iRC = ::RegGetValueW(hkeyHive, pszVar, nullptr, RRF_RT_ANY, &dwType, szValueCurrent, &dwSize); - - bool bDidntExist = iRC == ERROR_FILE_NOT_FOUND; - - if ((iRC != ERROR_SUCCESS) && !bDidntExist) - // an error occurred - return false; - - if (!bDidntExist) { - if (dwType != REG_SZ) - // invalid type - return false; - - if (::wcscmp(szValueCurrent, pszValue) == 0) - // value already set - return false; - } - - DWORD dwDisposition; - HKEY hkey; - iRC = ::RegCreateKeyExW(hkeyHive, pszVar, 0, 0, 0, KEY_ALL_ACCESS, nullptr, &hkey, &dwDisposition); - bool ret = false; - if (iRC == ERROR_SUCCESS) { - iRC = ::RegSetValueExW(hkey, L"", 0, REG_SZ, (BYTE*)pszValue, (::wcslen(pszValue) + 1) * sizeof(wchar_t)); - if (iRC == ERROR_SUCCESS) - ret = true; - } - - RegCloseKey(hkey); - return ret; -} - void GUI_App::associate_3mf_files() { - wchar_t app_path[MAX_PATH]; - ::GetModuleFileNameW(nullptr, app_path, sizeof(app_path)); - - std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\""; - std::wstring prog_id = L"Prusa.Slicer.1"; - std::wstring prog_desc = L"PrusaSlicer"; - std::wstring prog_command = prog_path + L" \"%1\""; - std::wstring reg_base = L"Software\\Classes"; - std::wstring reg_extension = reg_base + L"\\.3mf"; - std::wstring reg_prog_id = reg_base + L"\\" + prog_id; - std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command"; - - bool is_new = false; - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str()); - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str()); - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str()); - - if (is_new) - // notify Windows only when any of the values gets changed - ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); + associate_file_type(L".3mf", L"Prusa.Slicer.1", L"PrusaSlicer", true); } void GUI_App::associate_stl_files() { - wchar_t app_path[MAX_PATH]; - ::GetModuleFileNameW(nullptr, app_path, sizeof(app_path)); - - std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\""; - std::wstring prog_id = L"Prusa.Slicer.1"; - std::wstring prog_desc = L"PrusaSlicer"; - std::wstring prog_command = prog_path + L" \"%1\""; - std::wstring reg_base = L"Software\\Classes"; - std::wstring reg_extension = reg_base + L"\\.stl"; - std::wstring reg_prog_id = reg_base + L"\\" + prog_id; - std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command"; - - bool is_new = false; - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str()); - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str()); - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str()); - - if (is_new) - // notify Windows only when any of the values gets changed - ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); + associate_file_type(L".stl", L"Prusa.Slicer.1", L"PrusaSlicer", true); } void GUI_App::associate_gcode_files() { - wchar_t app_path[MAX_PATH]; - ::GetModuleFileNameW(nullptr, app_path, sizeof(app_path)); - - std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\""; - std::wstring prog_id = L"PrusaSlicer.GCodeViewer.1"; - std::wstring prog_desc = L"PrusaSlicerGCodeViewer"; - std::wstring prog_command = prog_path + L" \"%1\""; - std::wstring reg_base = L"Software\\Classes"; - std::wstring reg_extension = reg_base + L"\\.gcode"; - std::wstring reg_prog_id = reg_base + L"\\" + prog_id; - std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command"; - - bool is_new = false; - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str()); - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str()); - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str()); - - if (is_new) - // notify Windows only when any of the values gets changed - ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); + associate_file_type(L".gcode", L"PrusaSlicer.GCodeViewer.1", L"PrusaSlicerGCodeViewer", true); } #endif // __WXMSW__ - void GUI_App::on_version_read(wxCommandEvent& evt) { app_config->set("version_online", into_u8(evt.GetString())); diff --git a/src/slic3r/Utils/WinRegistry.cpp b/src/slic3r/Utils/WinRegistry.cpp new file mode 100644 index 0000000000..068a7fff17 --- /dev/null +++ b/src/slic3r/Utils/WinRegistry.cpp @@ -0,0 +1,490 @@ +#include "libslic3r/Technologies.hpp" +#include "WinRegistry.hpp" + +#ifdef _WIN32 +#include +#include +#include +#include + +namespace Slic3r { + +// Helper class which automatically closes the handle when +// going out of scope +class AutoHandle +{ + HANDLE m_handle{ nullptr }; + +public: + explicit AutoHandle(HANDLE handle) : m_handle(handle) {} + ~AutoHandle() { if (m_handle != nullptr) ::CloseHandle(m_handle); } + HANDLE get() { return m_handle; } +}; + +// Helper class which automatically closes the key when +// going out of scope +class AutoRegKey +{ + HKEY m_key{ nullptr }; + +public: + explicit AutoRegKey(HKEY key) : m_key(key) {} + ~AutoRegKey() { if (m_key != nullptr) ::RegCloseKey(m_key); } + HKEY get() { return m_key; } +}; + +// returns true if the given value is set/modified into Windows registry +static bool set_into_win_registry(HKEY hkeyHive, const wchar_t* pszVar, const wchar_t* pszValue) +{ + // see as reference: https://stackoverflow.com/questions/20245262/c-program-needs-an-file-association + wchar_t szValueCurrent[1000]; + DWORD dwType; + DWORD dwSize = sizeof(szValueCurrent); + + LSTATUS iRC = ::RegGetValueW(hkeyHive, pszVar, nullptr, RRF_RT_ANY, &dwType, szValueCurrent, &dwSize); + + const bool bDidntExist = iRC == ERROR_FILE_NOT_FOUND; + + if (iRC != ERROR_SUCCESS && !bDidntExist) + // an error occurred + return false; + + if (!bDidntExist) { + if (dwType != REG_SZ) + // invalid type + return false; + + if (::wcscmp(szValueCurrent, pszValue) == 0) + // value already set + return false; + } + + DWORD dwDisposition; + HKEY hkey; + iRC = ::RegCreateKeyExW(hkeyHive, pszVar, 0, 0, 0, KEY_ALL_ACCESS, nullptr, &hkey, &dwDisposition); + bool ret = false; + if (iRC == ERROR_SUCCESS) { + iRC = ::RegSetValueExW(hkey, L"", 0, REG_SZ, (BYTE*)pszValue, (::wcslen(pszValue) + 1) * sizeof(wchar_t)); + if (iRC == ERROR_SUCCESS) + ret = true; + } + + RegCloseKey(hkey); + return ret; +} + +static std::wstring get_current_user_string_sid() +{ + HANDLE rawProcessToken; + if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, + &rawProcessToken)) + return L""; + + AutoHandle processToken(rawProcessToken); + + DWORD userSize = 0; + if (!(!::GetTokenInformation(processToken.get(), TokenUser, nullptr, 0, &userSize) && + GetLastError() == ERROR_INSUFFICIENT_BUFFER)) + return L""; + + std::vector userBytes(userSize); + if (!::GetTokenInformation(processToken.get(), TokenUser, userBytes.data(), userSize, &userSize)) + return L""; + + wchar_t* rawSid = nullptr; + if (!::ConvertSidToStringSidW(reinterpret_cast(userBytes.data())->User.Sid, &rawSid)) + return L""; + + return rawSid; +} + +/* + * Create the string which becomes the input to the UserChoice hash. + * + * @see generate_user_choice_hash() for parameters. + * + * @return The formatted string, empty string on failure. + * + * NOTE: This uses the format as of Windows 10 20H2 (latest as of this writing), + * used at least since 1803. + * There was at least one older version, not currently supported: On Win10 RTM + * (build 10240, aka 1507) the hash function is the same, but the timestamp and + * User Experience string aren't included; instead (for protocols) the string + * ends with the exe path. The changelog of SetUserFTA suggests the algorithm + * changed in 1703, so there may be two versions: before 1703, and 1703 to now. + */ +static std::wstring format_user_choice_string(const wchar_t* aExt, const wchar_t* aUserSid, const wchar_t* aProgId, SYSTEMTIME aTimestamp) +{ + aTimestamp.wSecond = 0; + aTimestamp.wMilliseconds = 0; + + FILETIME fileTime = { 0 }; + if (!::SystemTimeToFileTime(&aTimestamp, &fileTime)) + return L""; + + // This string is built into Windows as part of the UserChoice hash algorithm. + // It might vary across Windows SKUs (e.g. Windows 10 vs. Windows Server), or + // across builds of the same SKU, but this is the only currently known + // version. There isn't any known way of deriving it, so we assume this + // constant value. If we are wrong, we will not be able to generate correct + // UserChoice hashes. + const wchar_t* userExperience = + L"User Choice set via Windows User Experience " + L"{D18B6DD5-6124-4341-9318-804003BAFA0B}"; + + const wchar_t* userChoiceFmt = + L"%s%s%s" + L"%08lx" + L"%08lx" + L"%s"; + + int userChoiceLen = _scwprintf(userChoiceFmt, aExt, aUserSid, aProgId, + fileTime.dwHighDateTime, fileTime.dwLowDateTime, userExperience); + userChoiceLen += 1; // _scwprintf does not include the terminator + + std::wstring userChoice(userChoiceLen, L'\0'); + _snwprintf_s(userChoice.data(), userChoiceLen, _TRUNCATE, userChoiceFmt, aExt, + aUserSid, aProgId, fileTime.dwHighDateTime, fileTime.dwLowDateTime, userExperience); + + ::CharLowerW(userChoice.data()); + return userChoice; +} + +// @return The MD5 hash of the input, nullptr on failure. +static std::vector cng_md5(const unsigned char* bytes, ULONG bytesLen) { + constexpr ULONG MD5_BYTES = 16; + constexpr ULONG MD5_DWORDS = MD5_BYTES / sizeof(DWORD); + std::vector hash; + + BCRYPT_ALG_HANDLE hAlg = nullptr; + if (NT_SUCCESS(::BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_MD5_ALGORITHM, nullptr, 0))) { + BCRYPT_HASH_HANDLE hHash = nullptr; + // As of Windows 7 the hash handle will manage its own object buffer when + // pbHashObject is nullptr and cbHashObject is 0. + if (NT_SUCCESS(::BCryptCreateHash(hAlg, &hHash, nullptr, 0, nullptr, 0, 0))) { + // BCryptHashData promises not to modify pbInput. + if (NT_SUCCESS(::BCryptHashData(hHash, const_cast(bytes), bytesLen, 0))) { + hash.resize(MD5_DWORDS); + if (!NT_SUCCESS(::BCryptFinishHash(hHash, reinterpret_cast(hash.data()), + MD5_DWORDS * sizeof(DWORD), 0))) + hash.clear(); + } + ::BCryptDestroyHash(hHash); + } + ::BCryptCloseAlgorithmProvider(hAlg, 0); + } + + return hash; +} + +static inline DWORD word_swap(DWORD v) +{ + return (v >> 16) | (v << 16); +} + +// @return The input bytes encoded as base64, nullptr on failure. +static std::wstring crypto_api_base64_encode(const unsigned char* bytes, DWORD bytesLen) { + DWORD base64Len = 0; + if (!::CryptBinaryToStringW(bytes, bytesLen, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, nullptr, &base64Len)) + return L""; + + std::wstring base64(base64Len, L'\0'); + if (!::CryptBinaryToStringW(bytes, bytesLen, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, base64.data(), &base64Len)) + return L""; + + return base64; +} + +/* + * Generate the UserChoice Hash. + * + * This implementation is based on the references listed above. + * It is organized to show the logic as clearly as possible, but at some + * point the reasoning is just "this is how it works". + * + * @param inputString A null-terminated string to hash. + * + * @return The base64-encoded hash, or empty string on failure. + */ +static std::wstring hash_string(const wchar_t* inputString) +{ + auto inputBytes = reinterpret_cast(inputString); + int inputByteCount = (::lstrlenW(inputString) + 1) * sizeof(wchar_t); + + constexpr size_t DWORDS_PER_BLOCK = 2; + constexpr size_t BLOCK_SIZE = sizeof(DWORD) * DWORDS_PER_BLOCK; + // Incomplete blocks are ignored. + int blockCount = inputByteCount / BLOCK_SIZE; + + if (blockCount == 0) + return L""; + + // Compute an MD5 hash. md5[0] and md5[1] will be used as constant multipliers + // in the scramble below. + auto md5 = cng_md5(inputBytes, inputByteCount); + if (md5.empty()) + return L""; + + // The following loop effectively computes two checksums, scrambled like a + // hash after every DWORD is added. + + // Constant multipliers for the scramble, one set for each DWORD in a block. + const DWORD C0s[DWORDS_PER_BLOCK][5] = { + {md5[0] | 1, 0xCF98B111uL, 0x87085B9FuL, 0x12CEB96DuL, 0x257E1D83uL}, + {md5[1] | 1, 0xA27416F5uL, 0xD38396FFuL, 0x7C932B89uL, 0xBFA49F69uL} }; + const DWORD C1s[DWORDS_PER_BLOCK][5] = { + {md5[0] | 1, 0xEF0569FBuL, 0x689B6B9FuL, 0x79F8A395uL, 0xC3EFEA97uL}, + {md5[1] | 1, 0xC31713DBuL, 0xDDCD1F0FuL, 0x59C3AF2DuL, 0x35BD1EC9uL} }; + + // The checksums. + DWORD h0 = 0; + DWORD h1 = 0; + // Accumulated total of the checksum after each DWORD. + DWORD h0Acc = 0; + DWORD h1Acc = 0; + + for (int i = 0; i < blockCount; ++i) { + for (int j = 0; j < DWORDS_PER_BLOCK; ++j) { + const DWORD* C0 = C0s[j]; + const DWORD* C1 = C1s[j]; + + DWORD input; + memcpy(&input, &inputBytes[(i * DWORDS_PER_BLOCK + j) * sizeof(DWORD)], sizeof(DWORD)); + + h0 += input; + // Scramble 0 + h0 *= C0[0]; + h0 = word_swap(h0) * C0[1]; + h0 = word_swap(h0) * C0[2]; + h0 = word_swap(h0) * C0[3]; + h0 = word_swap(h0) * C0[4]; + h0Acc += h0; + + h1 += input; + // Scramble 1 + h1 = word_swap(h1) * C1[1] + h1 * C1[0]; + h1 = (h1 >> 16) * C1[2] + h1 * C1[3]; + h1 = word_swap(h1) * C1[4] + h1; + h1Acc += h1; + } + } + + DWORD hash[2] = { h0 ^ h1, h0Acc ^ h1Acc }; + return crypto_api_base64_encode(reinterpret_cast(hash), sizeof(hash)); +} + +static std::wstring generate_user_choice_hash(const wchar_t* aExt, const wchar_t* aUserSid, const wchar_t* aProgId, SYSTEMTIME aTimestamp) +{ + const std::wstring userChoice = format_user_choice_string(aExt, aUserSid, aProgId, aTimestamp); + if (userChoice.empty()) + return L""; + + return hash_string(userChoice.c_str()); +} + +static bool add_milliseconds_to_system_time(SYSTEMTIME& aSystemTime, ULONGLONG aIncrementMS) +{ + FILETIME fileTime; + ULARGE_INTEGER fileTimeInt; + if (!::SystemTimeToFileTime(&aSystemTime, &fileTime)) + return false; + + fileTimeInt.LowPart = fileTime.dwLowDateTime; + fileTimeInt.HighPart = fileTime.dwHighDateTime; + + // FILETIME is in units of 100ns. + fileTimeInt.QuadPart += aIncrementMS * 1000 * 10; + + fileTime.dwLowDateTime = fileTimeInt.LowPart; + fileTime.dwHighDateTime = fileTimeInt.HighPart; + SYSTEMTIME tmpSystemTime; + if (!::FileTimeToSystemTime(&fileTime, &tmpSystemTime)) + return false; + + aSystemTime = tmpSystemTime; + return true; +} + +// Compare two SYSTEMTIMEs as FILETIME after clearing everything +// below minutes. +static bool check_equal_minutes(SYSTEMTIME aSystemTime1, SYSTEMTIME aSystemTime2) +{ + aSystemTime1.wSecond = 0; + aSystemTime1.wMilliseconds = 0; + + aSystemTime2.wSecond = 0; + aSystemTime2.wMilliseconds = 0; + + FILETIME fileTime1; + FILETIME fileTime2; + if (!::SystemTimeToFileTime(&aSystemTime1, &fileTime1) || !::SystemTimeToFileTime(&aSystemTime2, &fileTime2)) + return false; + + return (fileTime1.dwLowDateTime == fileTime2.dwLowDateTime) && (fileTime1.dwHighDateTime == fileTime2.dwHighDateTime); +} + +static std::wstring get_association_key_path(const wchar_t* aExt) +{ + const wchar_t* keyPathFmt; + if (aExt[0] == L'.') + keyPathFmt = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\%s"; + else + keyPathFmt = L"SOFTWARE\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\%s"; + + int keyPathLen = _scwprintf(keyPathFmt, aExt); + keyPathLen += 1; // _scwprintf does not include the terminator + + std::wstring keyPath(keyPathLen, '\0'); + _snwprintf_s(keyPath.data(), keyPathLen, _TRUNCATE, keyPathFmt, aExt); + return keyPath; +} + +/* + * Set an association with a UserChoice key + * + * Removes the old key, creates a new one with ProgID and Hash set to + * enable a new asociation. + * + * @param aExt File type or protocol to associate + * @param aProgID ProgID to use for the asociation + * + * @return true if successful, false on error. + */ +static bool set_user_choice(const wchar_t* aExt, const wchar_t* aProgID) { + + const std::wstring aSid = get_current_user_string_sid(); + if (aSid.empty()) + return false; + + SYSTEMTIME hashTimestamp; + ::GetSystemTime(&hashTimestamp); + std::wstring hash = generate_user_choice_hash(aExt, aSid.c_str(), aProgID, hashTimestamp); + if (hash.empty()) + return false; + + // The hash changes at the end of each minute, so check that the hash should + // be the same by the time we're done writing. + const ULONGLONG kWriteTimingThresholdMilliseconds = 100; + // Generating the hash could have taken some time, so start from now. + SYSTEMTIME writeEndTimestamp; + ::GetSystemTime(&writeEndTimestamp); + if (!add_milliseconds_to_system_time(writeEndTimestamp, +kWriteTimingThresholdMilliseconds)) + return false; + + if (!check_equal_minutes(hashTimestamp, writeEndTimestamp)) { + ::Sleep(kWriteTimingThresholdMilliseconds * 2); + + // For consistency, use the current time. + ::GetSystemTime(&hashTimestamp); + hash = generate_user_choice_hash(aExt, aSid.c_str(), aProgID, hashTimestamp); + if (hash.empty()) + return false; + } + + const std::wstring assocKeyPath = get_association_key_path(aExt); + if (assocKeyPath.empty()) + return false; + + LSTATUS ls; + HKEY rawAssocKey; + ls = ::RegOpenKeyExW(HKEY_CURRENT_USER, assocKeyPath.data(), 0, KEY_READ | KEY_WRITE, &rawAssocKey); + if (ls != ERROR_SUCCESS) + return false; + + AutoRegKey assocKey(rawAssocKey); + + HKEY currUserChoiceKey; + ls = ::RegOpenKeyExW(assocKey.get(), L"UserChoice", 0, KEY_READ, &currUserChoiceKey); + if (ls == ERROR_SUCCESS) { + ::RegCloseKey(currUserChoiceKey); + // When Windows creates this key, it is read-only (Deny Set Value), so we need + // to delete it first. + // We don't set any similar special permissions. + ls = ::RegDeleteKeyW(assocKey.get(), L"UserChoice"); + if (ls != ERROR_SUCCESS) + return false; + } + + HKEY rawUserChoiceKey; + ls = ::RegCreateKeyExW(assocKey.get(), L"UserChoice", 0, nullptr, + 0 /* options */, KEY_READ | KEY_WRITE, + 0 /* security attributes */, &rawUserChoiceKey, + nullptr); + if (ls != ERROR_SUCCESS) + return false; + + AutoRegKey userChoiceKey(rawUserChoiceKey); + DWORD progIdByteCount = (::lstrlenW(aProgID) + 1) * sizeof(wchar_t); + ls = ::RegSetValueExW(userChoiceKey.get(), L"ProgID", 0, REG_SZ, reinterpret_cast(aProgID), progIdByteCount); + if (ls != ERROR_SUCCESS) + return false; + + DWORD hashByteCount = (::lstrlenW(hash.data()) + 1) * sizeof(wchar_t); + ls = ::RegSetValueExW(userChoiceKey.get(), L"Hash", 0, REG_SZ, reinterpret_cast(hash.data()), hashByteCount); + if (ls != ERROR_SUCCESS) + return false; + + return true; +} + +static bool set_as_default_per_file_type(const std::wstring& extension, const std::wstring& prog_id) +{ + const std::wstring reg_extension = get_association_key_path(extension.c_str()); + if (reg_extension.empty()) + return false; + + bool needs_update = true; + bool modified = false; + HKEY rawAssocKey = nullptr; + LSTATUS res = ::RegOpenKeyExW(HKEY_CURRENT_USER, reg_extension.c_str(), 0, KEY_READ, &rawAssocKey); + AutoRegKey assoc_key(rawAssocKey); + if (res == ERROR_SUCCESS) { + DWORD data_size_bytes = 0; + res = ::RegGetValueW(assoc_key.get(), L"UserChoice", L"ProgId", RRF_RT_REG_SZ, nullptr, nullptr, &data_size_bytes); + if (res == ERROR_SUCCESS) { + // +1 in case dataSizeBytes was odd, +1 to ensure termination + DWORD data_size_chars = (data_size_bytes / sizeof(wchar_t)) + 2; + std::wstring curr_prog_id(data_size_chars, L'\0'); + res = ::RegGetValueW(assoc_key.get(), L"UserChoice", L"ProgId", RRF_RT_REG_SZ, nullptr, curr_prog_id.data(), &data_size_bytes); + if (res == ERROR_SUCCESS) { + const std::wstring::size_type pos = curr_prog_id.find_first_of(L'\0'); + if (pos != std::wstring::npos) + curr_prog_id = curr_prog_id.substr(0, pos); + needs_update = !boost::algorithm::iequals(curr_prog_id, prog_id); + } + } + } + + if (needs_update) + modified = set_user_choice(extension.c_str(), prog_id.c_str()); + + return modified; +} + +void associate_file_type(const std::wstring& extension, const std::wstring& prog_id, const std::wstring& prog_desc, bool set_as_default) +{ + assert(!extension.empty() && extension.front() == L'.'); + + const std::wstring reg_extension = L"SOFTWARE\\Classes\\" + extension; + const std::wstring reg_prog_id = L"SOFTWARE\\Classes\\" + prog_id; + const std::wstring reg_prog_id_command = L"SOFTWARE\\Classes\\" + prog_id + +L"\\Shell\\Open\\Command"; + + wchar_t app_path[1040]; + ::GetModuleFileNameW(nullptr, app_path, sizeof(app_path)); + const std::wstring prog_command = L"\"" + std::wstring(app_path) + L"\"" + L" \"%1\""; + + bool modified = false; + modified |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str()); + modified |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str()); + modified |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str()); + if (set_as_default) + modified |= set_as_default_per_file_type(extension, prog_id); + + // notify Windows only when any of the values gets changed + if (modified) + ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); +} + +} // namespace Slic3r + +#endif // _WIN32 diff --git a/src/slic3r/Utils/WinRegistry.hpp b/src/slic3r/Utils/WinRegistry.hpp new file mode 100644 index 0000000000..bc0875d32d --- /dev/null +++ b/src/slic3r/Utils/WinRegistry.hpp @@ -0,0 +1,23 @@ +#ifndef slic3r_Utils_WinRegistry_hpp_ +#define slic3r_Utils_WinRegistry_hpp_ + +#ifdef _WIN32 + +#include + +namespace Slic3r { + +// Creates a Windows registry key for the files with the given 'extension' and associates them to the application 'prog_id'. +// If 'set_as_default' is true, the application 'prog_id' is set ad default application for the file type 'extension'. +// The file type registration implementation is based on code taken from: +// https://stackoverflow.com/questions/20245262/c-program-needs-an-file-association +// The set as default implementation is based on code taken from: +// https://hg.mozilla.org/mozilla-central/rev/e928b3e95a6c3b7257d0ba475fc2303bfbad1874 +// https://hg.mozilla.org/releases/mozilla-release/diff/7e775ce432b599c6daf7ac379aa42f1e9b3b33ed/browser/components/shell/WindowsUserChoice.cpp +void associate_file_type(const std::wstring& extension, const std::wstring& prog_id, const std::wstring& prog_desc, bool set_as_default); + +} // namespace Slic3r + +#endif // _WIN32 + +#endif // slic3r_Utils_WinRegistry_hpp_ From a115702289f399f14bd914dc98ac4795e3cce39c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 27 May 2022 13:22:08 +0200 Subject: [PATCH 13/22] Ignore CLion IDE files in all subdirectories. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c4df3f3f8e..704289a220 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,4 @@ local-lib build-linux/* deps/build-linux/* **/.DS_Store -/.idea/ +**/.idea/ From 98928935877b3ec25a62c8fec1fc28642fff6b23 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 27 May 2022 15:33:03 +0200 Subject: [PATCH 14/22] Add UIThreadWorker for debugging and profiling purposes --- src/slic3r/CMakeLists.txt | 1 + src/slic3r/GUI/Jobs/PlaterWorker.hpp | 6 ++ src/slic3r/GUI/Jobs/UIThreadWorker.hpp | 114 ++++++++++++++++++++++++ src/slic3r/GUI/Plater.cpp | 3 + tests/slic3rutils/slic3r_jobs_tests.cpp | 40 ++++++--- 5 files changed, 150 insertions(+), 14 deletions(-) create mode 100644 src/slic3r/GUI/Jobs/UIThreadWorker.hpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 09c2098d9b..78e73ba9aa 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -172,6 +172,7 @@ set(SLIC3R_GUI_SOURCES GUI/Jobs/Worker.hpp GUI/Jobs/BoostThreadWorker.hpp GUI/Jobs/BoostThreadWorker.cpp + GUI/Jobs/UIThreadWorker.hpp GUI/Jobs/BusyCursorJob.hpp GUI/Jobs/PlaterWorker.hpp GUI/Jobs/ArrangeJob.hpp diff --git a/src/slic3r/GUI/Jobs/PlaterWorker.hpp b/src/slic3r/GUI/Jobs/PlaterWorker.hpp index 5735902728..58bd1ec32b 100644 --- a/src/slic3r/GUI/Jobs/PlaterWorker.hpp +++ b/src/slic3r/GUI/Jobs/PlaterWorker.hpp @@ -40,6 +40,12 @@ class PlaterWorker: public Worker { { wxWakeUpIdle(); ctl.update_status(st, msg); + + // If the worker is not using additional threads, the UI + // is refreshed with this call. If the worker is running + // in it's own thread, the yield should not have any + // visible effects. + wxYieldIfNeeded(); } bool was_canceled() const override { return ctl.was_canceled(); } diff --git a/src/slic3r/GUI/Jobs/UIThreadWorker.hpp b/src/slic3r/GUI/Jobs/UIThreadWorker.hpp new file mode 100644 index 0000000000..aa946036e3 --- /dev/null +++ b/src/slic3r/GUI/Jobs/UIThreadWorker.hpp @@ -0,0 +1,114 @@ +#ifndef UITHREADWORKER_HPP +#define UITHREADWORKER_HPP + +#include +#include + +#include "Worker.hpp" +#include "ProgressIndicator.hpp" + +namespace Slic3r { namespace GUI { + +// Implementation of a worker which does not create any additional threads. +class UIThreadWorker : public Worker, private Job::Ctl { + std::queue, std::deque>> m_jobqueue; + std::shared_ptr m_progress; + bool m_running = false; + bool m_canceled = false; + + void process_front() + { + std::unique_ptr job; + + if (!m_jobqueue.empty()) { + job = std::move(m_jobqueue.front()); + m_jobqueue.pop(); + } + + if (job) { + std::exception_ptr eptr; + m_running = true; + + try { + job->process(*this); + } catch (...) { + eptr= std::current_exception(); + } + + m_running = false; + + job->finalize(m_canceled, eptr); + + m_canceled = false; + } + } + +protected: + // Implement Job::Ctl interface: + + void update_status(int st, const std::string &msg = "") override + { + if (m_progress) { + m_progress->set_progress(st); + m_progress->set_status_text(msg.c_str()); + } + } + + bool was_canceled() const override { return m_canceled; } + + std::future call_on_main_thread(std::function fn) override + { + return std::async(std::launch::deferred, [fn]{ fn(); }); + } + +public: + explicit UIThreadWorker(std::shared_ptr pri, + const std::string & /*name*/ = "") + : m_progress{pri} + {} + + UIThreadWorker() = default; + + bool push(std::unique_ptr job) override + { + m_canceled = false; + m_jobqueue.push(std::move(job)); + + return bool(job); + } + + bool is_idle() const override { return !m_running; } + + void cancel() override { m_canceled = true; } + + void cancel_all() override + { + m_canceled = true; + process_front(); + while (!m_jobqueue.empty()) m_jobqueue.pop(); + } + + void process_events() override { + while (!m_jobqueue.empty()) + process_front(); + } + + bool wait_for_current_job(unsigned /*timeout_ms*/ = 0) override { + process_front(); + + return true; + } + + bool wait_for_idle(unsigned /*timeout_ms*/ = 0) override { + process_events(); + + return true; + } + + ProgressIndicator * get_pri() { return m_progress.get(); } + const ProgressIndicator * get_pri() const { return m_progress.get(); } +}; + +}} // namespace Slic3r::GUI + +#endif // UITHREADWORKER_HPP diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 0456936e44..30c30d2ece 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1648,6 +1648,9 @@ struct Plater::priv // objects would be frozen for the user. In case of arrange, an animation // could be shown, or with the optimize orientations, partial results // could be displayed. + // + // UIThreadWorker can be used as a replacement for BoostThreadWorker if + // no additional worker threads are desired (useful for debugging or profiling) PlaterWorker m_worker; SLAImportDialog * m_sla_import_dlg; diff --git a/tests/slic3rutils/slic3r_jobs_tests.cpp b/tests/slic3rutils/slic3r_jobs_tests.cpp index d31b07349a..d4b912b84f 100644 --- a/tests/slic3rutils/slic3r_jobs_tests.cpp +++ b/tests/slic3rutils/slic3r_jobs_tests.cpp @@ -4,10 +4,9 @@ #include #include "slic3r/GUI/Jobs/BoostThreadWorker.hpp" +#include "slic3r/GUI/Jobs/UIThreadWorker.hpp" #include "slic3r/GUI/Jobs/ProgressIndicator.hpp" -//#include - struct Progress: Slic3r::ProgressIndicator { int range = 100; int pr = 0; @@ -19,17 +18,30 @@ struct Progress: Slic3r::ProgressIndicator { int get_range() const override { return range; } }; -TEST_CASE("nullptr job should be ignored", "[Jobs]") { - Slic3r::GUI::BoostThreadWorker worker{std::make_unique()}; +using TestClasses = std::tuple< Slic3r::GUI::UIThreadWorker, Slic3r::GUI::BoostThreadWorker >; + +TEMPLATE_LIST_TEST_CASE("Empty worker should not do anything", "[Jobs]", TestClasses) { + TestType worker{std::make_unique()}; + + REQUIRE(worker.is_idle()); + + worker.wait_for_current_job(); + worker.process_events(); + + REQUIRE(worker.is_idle()); +} + +TEMPLATE_LIST_TEST_CASE("nullptr job should be ignored", "[Jobs]", TestClasses) { + TestType worker{std::make_unique()}; worker.push(nullptr); REQUIRE(worker.is_idle()); } -TEST_CASE("State should not be idle while running a job", "[Jobs]") { +TEMPLATE_LIST_TEST_CASE("State should not be idle while running a job", "[Jobs]", TestClasses) { using namespace Slic3r; using namespace Slic3r::GUI; - BoostThreadWorker worker{std::make_unique(), "worker_thread"}; + TestType worker{std::make_unique(), "worker_thread"}; queue_job(worker, [&worker](Job::Ctl &ctl) { ctl.call_on_main_thread([&worker] { @@ -42,11 +54,11 @@ TEST_CASE("State should not be idle while running a job", "[Jobs]") { REQUIRE(worker.is_idle()); } -TEST_CASE("Status messages should be received by the main thread during job execution", "[Jobs]") { +TEMPLATE_LIST_TEST_CASE("Status messages should be received by the main thread during job execution", "[Jobs]", TestClasses) { using namespace Slic3r; using namespace Slic3r::GUI; auto pri = std::make_shared(); - BoostThreadWorker worker{pri}; + TestType worker{pri}; queue_job(worker, [](Job::Ctl &ctl){ for (int s = 0; s <= 100; ++s) { @@ -60,12 +72,12 @@ TEST_CASE("Status messages should be received by the main thread during job exec REQUIRE(pri->statustxt == "Running"); } -TEST_CASE("Cancellation should be recognized be the worker", "[Jobs]") { +TEMPLATE_LIST_TEST_CASE("Cancellation should be recognized be the worker", "[Jobs]", TestClasses) { using namespace Slic3r; using namespace Slic3r::GUI; auto pri = std::make_shared(); - BoostThreadWorker worker{pri}; + TestType worker{pri}; queue_job( worker, @@ -88,12 +100,12 @@ TEST_CASE("Cancellation should be recognized be the worker", "[Jobs]") { REQUIRE(pri->pr != 100); } -TEST_CASE("cancel_all should remove all pending jobs", "[Jobs]") { +TEMPLATE_LIST_TEST_CASE("cancel_all should remove all pending jobs", "[Jobs]", TestClasses) { using namespace Slic3r; using namespace Slic3r::GUI; auto pri = std::make_shared(); - BoostThreadWorker worker{pri}; + TestType worker{pri}; std::array jobres = {false, false, false, false}; @@ -125,12 +137,12 @@ TEST_CASE("cancel_all should remove all pending jobs", "[Jobs]") { REQUIRE(jobres[3] == false); } -TEST_CASE("Exception should be properly forwarded to finalize()", "[Jobs]") { +TEMPLATE_LIST_TEST_CASE("Exception should be properly forwarded to finalize()", "[Jobs]", TestClasses) { using namespace Slic3r; using namespace Slic3r::GUI; auto pri = std::make_shared(); - BoostThreadWorker worker{pri}; + TestType worker{pri}; queue_job( worker, [](Job::Ctl &) { throw std::runtime_error("test"); }, From 6c284882ba2e85bf397d4a5ab68a8e560621a9d3 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 27 May 2022 15:54:26 +0200 Subject: [PATCH 15/22] Fix cancellation from UI for UIThreadWorker --- src/slic3r/GUI/Jobs/UIThreadWorker.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Jobs/UIThreadWorker.hpp b/src/slic3r/GUI/Jobs/UIThreadWorker.hpp index aa946036e3..2477489f75 100644 --- a/src/slic3r/GUI/Jobs/UIThreadWorker.hpp +++ b/src/slic3r/GUI/Jobs/UIThreadWorker.hpp @@ -65,7 +65,10 @@ public: explicit UIThreadWorker(std::shared_ptr pri, const std::string & /*name*/ = "") : m_progress{pri} - {} + { + if (m_progress) + m_progress->set_cancel_callback([this](){ cancel(); }); + } UIThreadWorker() = default; From b00c5504639bf8dae84f24e41438a3a964b6e466 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 27 May 2022 15:55:25 +0200 Subject: [PATCH 16/22] Do not show legend and bottom slider when loading an invalid gcode file into GCodeViewer --- src/slic3r/GUI/GCodeViewer.cpp | 12 +++++++----- src/slic3r/GUI/GUI_Preview.cpp | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 2134a795ff..1e28d12871 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -991,11 +991,13 @@ void GCodeViewer::render() render_toolpaths(); render_shells(); float legend_height = 0.0f; - render_legend(legend_height); - if (m_sequential_view.current.last != m_sequential_view.endpoints.last) { - m_sequential_view.marker.set_world_position(m_sequential_view.current_position); - m_sequential_view.marker.set_world_offset(m_sequential_view.current_offset); - m_sequential_view.render(legend_height); + if (!m_layers.empty()) { + render_legend(legend_height); + if (m_sequential_view.current.last != m_sequential_view.endpoints.last) { + m_sequential_view.marker.set_world_position(m_sequential_view.current_position); + m_sequential_view.marker.set_world_offset(m_sequential_view.current_offset); + m_sequential_view.render(legend_height); + } } #if ENABLE_GCODE_VIEWER_STATISTICS render_statistics(); diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 9f62fa0b85..6fff561cef 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -954,7 +954,7 @@ void Preview::load_print_as_fff(bool keep_z_range) } GCodeViewer::EViewType gcode_view_type = m_canvas->get_gcode_view_preview_type(); - bool gcode_preview_data_valid = !m_gcode_result->moves.empty(); + bool gcode_preview_data_valid = !m_gcode_result->moves.empty() && !m_canvas->get_gcode_layers_zs().empty(); // Collect colors per extruder. std::vector colors; From 6ab8e3a1388cd5e17d7c0a20b6a148918071d772 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 27 May 2022 16:09:32 +0200 Subject: [PATCH 17/22] Fix id_idle() for UIThreadWorker --- src/slic3r/GUI/Jobs/UIThreadWorker.hpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/Jobs/UIThreadWorker.hpp b/src/slic3r/GUI/Jobs/UIThreadWorker.hpp index 2477489f75..b8ac914fd7 100644 --- a/src/slic3r/GUI/Jobs/UIThreadWorker.hpp +++ b/src/slic3r/GUI/Jobs/UIThreadWorker.hpp @@ -67,7 +67,7 @@ public: : m_progress{pri} { if (m_progress) - m_progress->set_cancel_callback([this](){ cancel(); }); + m_progress->set_cancel_callback([this]() { cancel(); }); } UIThreadWorker() = default; @@ -75,12 +75,14 @@ public: bool push(std::unique_ptr job) override { m_canceled = false; - m_jobqueue.push(std::move(job)); + + if (job) + m_jobqueue.push(std::move(job)); return bool(job); } - bool is_idle() const override { return !m_running; } + bool is_idle() const override { return !m_running && m_jobqueue.empty(); } void cancel() override { m_canceled = true; } @@ -88,7 +90,9 @@ public: { m_canceled = true; process_front(); - while (!m_jobqueue.empty()) m_jobqueue.pop(); + + while (!m_jobqueue.empty()) + m_jobqueue.pop(); } void process_events() override { From c5e3a565117ef714485b132f990b15a251b01e99 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 30 May 2022 10:01:47 +0200 Subject: [PATCH 18/22] Further fix to is_idle() and rethrow unhandled exception after finalize In UIThreadWorker --- src/slic3r/GUI/Jobs/UIThreadWorker.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Jobs/UIThreadWorker.hpp b/src/slic3r/GUI/Jobs/UIThreadWorker.hpp index b8ac914fd7..610d205cf9 100644 --- a/src/slic3r/GUI/Jobs/UIThreadWorker.hpp +++ b/src/slic3r/GUI/Jobs/UIThreadWorker.hpp @@ -35,10 +35,14 @@ class UIThreadWorker : public Worker, private Job::Ctl { eptr= std::current_exception(); } - m_running = false; - job->finalize(m_canceled, eptr); + // Unhandled exceptions are rethrown without mercy. + if (eptr) + std::rethrow_exception(eptr); + + m_running = false; + m_canceled = false; } } From 4326e083eb127663016a6b8a4d65121dccda94db Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 30 May 2022 11:15:23 +0200 Subject: [PATCH 19/22] Fix sla rotation gizmo menu not being remembered --- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index b4b9e07770..6401990192 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -758,7 +758,9 @@ GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_fil GLGizmoRotate(parent, GLGizmoRotate::X), GLGizmoRotate(parent, GLGizmoRotate::Y), GLGizmoRotate(parent, GLGizmoRotate::Z) }) -{} +{ + load_rotoptimize_state(); +} bool GLGizmoRotate3D::on_mouse(const wxMouseEvent &mouse_event) { From 78124689c5aa3f885f3905d9bfbc0bbae55c356e Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 30 May 2022 11:17:34 +0200 Subject: [PATCH 20/22] Fix excessive uptates to UI in sla rotation optimization --- src/libslic3r/SLA/Rotfinder.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/SLA/Rotfinder.cpp b/src/libslic3r/SLA/Rotfinder.cpp index 847e638e6f..16cd28130a 100644 --- a/src/libslic3r/SLA/Rotfinder.cpp +++ b/src/libslic3r/SLA/Rotfinder.cpp @@ -288,7 +288,7 @@ template struct RotfinderBoilerplate { static constexpr unsigned MAX_TRIES = MAX_ITER; - int status = 0; + int status = 0, prev_status = 0; TriangleMesh mesh; unsigned max_tries; const RotOptimizeParams ¶ms; @@ -314,13 +314,20 @@ struct RotfinderBoilerplate { RotfinderBoilerplate(const ModelObject &mo, const RotOptimizeParams &p) : mesh{get_mesh_to_rotate(mo)} - , params{p} , max_tries(p.accuracy() * MAX_TRIES) - { + , params{p} + {} + void statusfn() { + int s = status * 100 / max_tries; + if (s != prev_status) { + params.statuscb()(s); + prev_status = s; + } + + ++status; } - void statusfn() { params.statuscb()(++status * 100.0 / max_tries); } bool stopcond() { return ! params.statuscb()(-1); } }; From e8753ee8cd7bb4ffab9a0681bc904ca90e5ff60f Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 12 Jan 2022 11:29:38 +0100 Subject: [PATCH 21/22] Tech ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE - 1st installment - Copies custom bed texture and model files to 'data_dir()\printer' folder, if needed, and updates the printer config accordingly Fixed conflicts after rebase with master --- src/libslic3r/PresetBundle.cpp | 35 ++++++++++++++++++++++++++++++++- src/libslic3r/PresetBundle.hpp | 6 ++++++ src/libslic3r/Technologies.hpp | 3 ++- src/slic3r/GUI/ConfigWizard.cpp | 4 ++++ 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 85bcd69ba5..e744d72ca7 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -1,7 +1,7 @@ #include -#include "PresetBundle.hpp" #include "libslic3r.h" +#include "PresetBundle.hpp" #include "Utils.hpp" #include "Model.hpp" #include "format.hpp" @@ -453,6 +453,11 @@ void PresetBundle::save_changes_for_preset(const std::string& new_name, Preset:: presets.get_edited_preset().config.apply_only(presets.get_selected_preset().config, unselected_options); } +#if ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE + if (type == Preset::TYPE_PRINTER) + copy_bed_model_and_texture_if_needed(presets.get_edited_preset().config); +#endif // ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE + // Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini presets.save_current_preset(new_name); // Mark the print & filament enabled if they are compatible with the currently selected preset. @@ -1860,4 +1865,32 @@ void PresetBundle::set_default_suppressed(bool default_suppressed) printers.set_default_suppressed(default_suppressed); } +#if ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE +void copy_bed_model_and_texture_if_needed(DynamicPrintConfig& config) +{ + const boost::filesystem::path user_dir = boost::filesystem::absolute(boost::filesystem::path(data_dir()) / "printer").make_preferred(); + const boost::filesystem::path res_dir = boost::filesystem::absolute(boost::filesystem::path(resources_dir()) / "profiles").make_preferred(); + + auto do_copy = [&user_dir, &res_dir](ConfigOptionString* cfg, const std::string& type) { + if (cfg == nullptr || cfg->value.empty()) + return; + + const boost::filesystem::path src_dir = boost::filesystem::absolute(boost::filesystem::path(cfg->value)).make_preferred().parent_path(); + if (src_dir != user_dir && src_dir.parent_path() != res_dir) { + const std::string dst_value = (user_dir / boost::filesystem::path(cfg->value).filename()).string(); + std::string error; + if (copy_file_inner(cfg->value, dst_value, error) == SUCCESS) + cfg->value = dst_value; + else { + BOOST_LOG_TRIVIAL(error) << "Copying from " << cfg->value << " to " << dst_value << " failed. Unable to set custom bed " << type << ". [" << error << "]"; + cfg->value = ""; + } + } + }; + + do_copy(config.option("bed_custom_texture"), "texture"); + do_copy(config.option("bed_custom_model"), "model"); +} +#endif // ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE + } // namespace Slic3r diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 2a5ce6839c..b36f6ed6e5 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -178,6 +178,12 @@ private: ENABLE_ENUM_BITMASK_OPERATORS(PresetBundle::LoadConfigBundleAttribute) +#if ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE +// Copies bed texture and model files to 'data_dir()\printer' folder, if needed +// and updates the config accordingly +extern void copy_bed_model_and_texture_if_needed(DynamicPrintConfig& config); +#endif // ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE + } // namespace Slic3r #endif /* slic3r_PresetBundle_hpp_ */ diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index a89a7c8b01..9f41196a14 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -81,6 +81,7 @@ #define ENABLE_USED_FILAMENT_POST_PROCESS (1 && ENABLE_2_5_0_ALPHA1) // Enable gizmo grabbers to share common models #define ENABLE_GIZMO_GRABBER_REFACTOR (1 && ENABLE_2_5_0_ALPHA1) - +// Enable copy of custom bed model and texture +#define ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE (1 && ENABLE_2_5_0_ALPHA1) #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index cbee620137..080de997ee 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -2780,6 +2780,10 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese page_diams->apply_custom_config(*custom_config); page_temps->apply_custom_config(*custom_config); +#if ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE + copy_bed_model_and_texture_if_needed(*custom_config); +#endif // ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE + const std::string profile_name = page_custom->profile_name(); preset_bundle->load_config_from_wizard(profile_name, *custom_config); } From 4dee789e9e8aa55e3d9e9c03619767b500b74d18 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 31 May 2022 08:39:15 +0200 Subject: [PATCH 22/22] Follow-up of b00c5504639bf8dae84f24e41438a3a964b6e466 - More robust fix for: Do not show legend and bottom slider when loading an invalid gcode file into GCodeViewer --- src/slic3r/GUI/GUI_Preview.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 6fff561cef..88bd42a66f 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -954,7 +954,7 @@ void Preview::load_print_as_fff(bool keep_z_range) } GCodeViewer::EViewType gcode_view_type = m_canvas->get_gcode_view_preview_type(); - bool gcode_preview_data_valid = !m_gcode_result->moves.empty() && !m_canvas->get_gcode_layers_zs().empty(); + bool gcode_preview_data_valid = !m_gcode_result->moves.empty(); // Collect colors per extruder. std::vector colors; @@ -983,10 +983,11 @@ void Preview::load_print_as_fff(bool keep_z_range) if (gcode_preview_data_valid) { // Load the real G-code preview. m_canvas->load_gcode_preview(*m_gcode_result, colors); - m_left_sizer->Show(m_bottom_toolbar_panel); m_left_sizer->Layout(); Refresh(); zs = m_canvas->get_gcode_layers_zs(); + if (!zs.empty()) + m_left_sizer->Show(m_bottom_toolbar_panel); m_loaded = true; } else if (wxGetApp().is_editor()) {