diff --git a/cmake/modules/FindOpenVDB.cmake b/cmake/modules/FindOpenVDB.cmake index 3e9e5f082a..bddc346b9f 100644 --- a/cmake/modules/FindOpenVDB.cmake +++ b/cmake/modules/FindOpenVDB.cmake @@ -233,11 +233,14 @@ foreach(COMPONENT ${OpenVDB_FIND_COMPONENTS}) ) if (_is_multi) - list(APPEND OpenVDB_LIB_COMPONENTS ${OpenVDB_${COMPONENT}_LIBRARY_RELEASE} ${OpenVDB_${COMPONENT}_LIBRARY_DEBUG}) + list(APPEND OpenVDB_LIB_COMPONENTS ${OpenVDB_${COMPONENT}_LIBRARY_RELEASE}) + if (MSVC OR OpenVDB_${COMPONENT}_LIBRARY_DEBUG) + list(APPEND OpenVDB_LIB_COMPONENTS ${OpenVDB_${COMPONENT}_LIBRARY_DEBUG}) + endif () list(FIND CMAKE_CONFIGURATION_TYPES "Debug" _has_debug) - if(OpenVDB_${COMPONENT}_LIBRARY_RELEASE AND (_has_debug LESS 0 OR OpenVDB_${COMPONENT}_LIBRARY_DEBUG)) + if(OpenVDB_${COMPONENT}_LIBRARY_RELEASE AND (NOT MSVC OR _has_debug LESS 0 OR OpenVDB_${COMPONENT}_LIBRARY_DEBUG)) set(OpenVDB_${COMPONENT}_FOUND TRUE) else() set(OpenVDB_${COMPONENT}_FOUND FALSE) @@ -518,12 +521,19 @@ list(REMOVE_DUPLICATES OpenVDB_LIBRARY_DIRS) foreach(COMPONENT ${OpenVDB_FIND_COMPONENTS}) if(NOT TARGET OpenVDB::${COMPONENT}) + if (${COMPONENT} STREQUAL openvdb) + include (${CMAKE_CURRENT_LIST_DIR}/CheckAtomic.cmake) + set(_LINK_LIBS ${_OPENVDB_VISIBLE_DEPENDENCIES} ${CMAKE_REQUIRED_LIBRARIES}) + else () + set(_LINK_LIBS _OPENVDB_VISIBLE_DEPENDENCIES) + endif () + add_library(OpenVDB::${COMPONENT} UNKNOWN IMPORTED) set_target_properties(OpenVDB::${COMPONENT} PROPERTIES INTERFACE_COMPILE_OPTIONS "${OpenVDB_DEFINITIONS}" INTERFACE_INCLUDE_DIRECTORIES "${OpenVDB_INCLUDE_DIR}" IMPORTED_LINK_DEPENDENT_LIBRARIES "${_OPENVDB_HIDDEN_DEPENDENCIES}" # non visible deps - INTERFACE_LINK_LIBRARIES "${_OPENVDB_VISIBLE_DEPENDENCIES}" # visible deps (headers) + INTERFACE_LINK_LIBRARIES "${_LINK_LIBS}" # visible deps (headers) INTERFACE_COMPILE_FEATURES cxx_std_11 IMPORTED_LOCATION "${OpenVDB_${COMPONENT}_LIBRARY}" ) @@ -531,8 +541,13 @@ foreach(COMPONENT ${OpenVDB_FIND_COMPONENTS}) if (_is_multi) set_target_properties(OpenVDB::${COMPONENT} PROPERTIES IMPORTED_LOCATION_RELEASE "${OpenVDB_${COMPONENT}_LIBRARY_RELEASE}" - IMPORTED_LOCATION_DEBUG "${OpenVDB_${COMPONENT}_LIBRARY_DEBUG}" ) + + if (MSVC OR OpenVDB_${COMPONENT}_LIBRARY_DEBUG) + set_target_properties(OpenVDB::${COMPONENT} PROPERTIES + IMPORTED_LOCATION_DEBUG "${OpenVDB_${COMPONENT}_LIBRARY_DEBUG}" + ) + endif () endif () if (OPENVDB_USE_STATIC_LIBS) diff --git a/resources/profiles/Creality.idx b/resources/profiles/Creality.idx new file mode 100644 index 0000000000..d877ae7c9b --- /dev/null +++ b/resources/profiles/Creality.idx @@ -0,0 +1,7 @@ +min_slic3r_version = 2.2.0-alpha3 +0.0.2-alpha0 Print bed textures are now configurable from the Preset Bundle. Requires PrusaSlicer 2.2.0-alpha3 and newer. +# The following line (max_slic3r_version) forces the users of PrusaSlicer 2.2.0-alpha3 and newer to update the profiles to 1.1.1-alpha3 and newer, +# so they will see the print bed. +max_slic3r_version = 2.2.0-alpha2 +min_slic3r_version = 2.2.0-alpha0 +0.0.1 Initial version diff --git a/resources/profiles/Creality.ini b/resources/profiles/Creality.ini index 181bb4f4ed..4f8dbe572d 100644 --- a/resources/profiles/Creality.ini +++ b/resources/profiles/Creality.ini @@ -5,7 +5,7 @@ name = Creality # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the PrusaSlicer configuration to be downgraded. -config_version = 0.0.1 +config_version = 0.0.2-alpha0 # Where to get the updates from? config_update_url = http://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Creality/ # changelog_url = http://files.prusa3d.com/?latest=slicer-profiles&lng=%1% diff --git a/src/libigl/igl/copyleft/cgal/SelfIntersectMesh.h b/src/libigl/igl/copyleft/cgal/SelfIntersectMesh.h index 5a70fc39e7..39a4e1a7e3 100644 --- a/src/libigl/igl/copyleft/cgal/SelfIntersectMesh.h +++ b/src/libigl/igl/copyleft/cgal/SelfIntersectMesh.h @@ -635,13 +635,30 @@ inline bool igl::copyleft::cgal::SelfIntersectMesh< { using namespace std; + auto opposite_vertex = [](const Index a0, const Index a1) { + // get opposite index of A + int a2=-1; + for(int c=0;c<3;++c) + if(c!=a0 && c!=a1) { + a2 = c; + break; + } + assert(a2 != -1); + return a2; + }; + // must be co-planar - if( - A.supporting_plane() != B.supporting_plane() && - A.supporting_plane() != B.supporting_plane().opposite()) - { + Index a2 = opposite_vertex(shared[0].first, shared[1].first); + if (! B.supporting_plane().has_on(A.vertex(a2))) return false; - } + + Index b2 = opposite_vertex(shared[0].second, shared[1].second); + + if (int(CGAL::coplanar_orientation(A.vertex(shared[0].first), A.vertex(shared[1].first), A.vertex(a2))) * + int(CGAL::coplanar_orientation(B.vertex(shared[0].second), B.vertex(shared[1].second), B.vertex(b2))) < 0) + // There is certainly no self intersection as the non-shared triangle vertices lie on opposite sides of the shared edge. + return false; + // Since A and B are non-degenerate the intersection must be a polygon // (triangle). Either // - the vertex of A (B) opposite the shared edge of lies on B (A), or @@ -650,22 +667,10 @@ inline bool igl::copyleft::cgal::SelfIntersectMesh< // Determine if the vertex opposite edge (a0,a1) in triangle A lies in // (intersects) triangle B const auto & opposite_point_inside = []( - const Triangle_3 & A, const Index a0, const Index a1, const Triangle_3 & B) + const Triangle_3 & A, const Index a2, const Triangle_3 & B) -> bool { - // get opposite index - Index a2 = -1; - for(int c = 0;c<3;c++) - { - if(c != a0 && c != a1) - { - a2 = c; - break; - } - } - assert(a2 != -1); - bool ret = CGAL::do_intersect(A.vertex(a2),B); - return ret; + return CGAL::do_intersect(A.vertex(a2),B); }; // Determine if edge opposite vertex va in triangle A intersects edge @@ -681,8 +686,8 @@ inline bool igl::copyleft::cgal::SelfIntersectMesh< }; if( - !opposite_point_inside(A,shared[0].first,shared[1].first,B) && - !opposite_point_inside(B,shared[0].second,shared[1].second,A) && + !opposite_point_inside(A,a2,B) && + !opposite_point_inside(B,b2,A) && !opposite_edges_intersect(A,shared[0].first,B,shared[1].second) && !opposite_edges_intersect(A,shared[1].first,B,shared[0].second)) { @@ -936,4 +941,4 @@ inline void igl::copyleft::cgal::SelfIntersectMesh< //process_chunk(0, candidate_triangle_pairs.size()); } -#endif +#endif \ No newline at end of file diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 6d5b3e5f56..73b7df5bc0 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -233,15 +233,30 @@ cmake_policy(SET CMP0011 NEW) find_package(CGAL REQUIRED) cmake_policy(POP) -add_library(libslic3r_cgal OBJECT MeshBoolean.cpp MeshBoolean.hpp) -target_include_directories(libslic3r_cgal PRIVATE - ${CMAKE_CURRENT_BINARY_DIR} - $ - $) -target_compile_definitions(libslic3r_cgal PRIVATE - $) -target_compile_options(libslic3r_cgal PRIVATE - $) +add_library(libslic3r_cgal STATIC MeshBoolean.cpp MeshBoolean.hpp) +target_include_directories(libslic3r_cgal PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + +# Reset compile options of libslic3r_cgal. Despite it being linked privately, CGAL options +# (-frounding-math) still propagate to dependent libs which is not desired. +get_target_property(_cgal_tgt CGAL::CGAL ALIASED_TARGET) +if (NOT TARGET ${_cgal_tgt}) + set (_cgal_tgt CGAL::CGAL) +endif () +get_target_property(_opts ${_cgal_tgt} INTERFACE_COMPILE_OPTIONS) +if (_opts) + set(_opts_bad "${_opts}") + set(_opts_good "${_opts}") + list(FILTER _opts_bad INCLUDE REGEX frounding-math) + list(FILTER _opts_good EXCLUDE REGEX frounding-math) + set_target_properties(${_cgal_tgt} PROPERTIES INTERFACE_COMPILE_OPTIONS "${_opts_good}") + target_compile_options(libslic3r_cgal PRIVATE "${_opts_bad}") +endif() + +target_link_libraries(libslic3r_cgal PRIVATE ${_cgal_tgt} libigl) + +if (MSVC AND "${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") # 32 bit MSVC workaround + target_compile_definitions(libslic3r_cgal PRIVATE CGAL_DO_NOT_USE_MPZF) +endif () encoding_check(libslic3r) @@ -263,7 +278,7 @@ target_link_libraries(libslic3r qhull semver TBB::tbb - $ + libslic3r_cgal ${CMAKE_DL_LIBS} ) @@ -282,5 +297,3 @@ endif() if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY) add_precompiled_header(libslic3r pchheader.hpp FORCEINCLUDE) endif () - -target_sources(libslic3r PRIVATE $) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index cc83461d36..a53b6bd7c2 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -958,7 +958,7 @@ namespace DoExport { skirts.emplace_back(std::move(s)); } ooze_prevention.enable = true; - ooze_prevention.standby_points = offset(Slic3r::Geometry::convex_hull(skirts), scale_(3.f)).front().equally_spaced_points(scale_(10.)); + ooze_prevention.standby_points = offset(Slic3r::Geometry::convex_hull(skirts), float(scale_(3.))).front().equally_spaced_points(float(scale_(10.))); #if 0 require "Slic3r/SVG.pm"; Slic3r::SVG::output( @@ -1091,7 +1091,7 @@ namespace DoExport { static inline std::vector sort_object_instances_by_max_z(const Print &print) { std::vector objects(print.objects().begin(), print.objects().end()); - std::sort(objects.begin(), objects.end(), [](const PrintObject *po1, const PrintObject *po2) { return po1->size(2) < po2->size(2); }); + std::sort(objects.begin(), objects.end(), [](const PrintObject *po1, const PrintObject *po2) { return po1->size()(2) < po2->size()(2); }); std::vector instances; instances.reserve(objects.size()); for (const PrintObject *object : objects) diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index 63ff6fb09d..3402d2c856 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -252,46 +252,6 @@ template struct remove_cvref template using remove_cvref_t = typename remove_cvref::type; -template using DefaultContainer = std::vector; - -/// Exactly like Matlab https://www.mathworks.com/help/matlab/ref/linspace.html -template class Container = DefaultContainer> -inline Container> linspace(const T &start, - const T &stop, - const I &n) -{ - Container> vals(n, T()); - - T stride = (stop - start) / n; - size_t i = 0; - std::generate(vals.begin(), vals.end(), [&i, start, stride] { - return start + i++ * stride; - }); - - return vals; -} - -/// A set of equidistant values starting from 'start' (inclusive), ending -/// in the closest multiple of 'stride' less than or equal to 'end' and -/// leaving 'stride' space between each value. -/// Very similar to Matlab [start:stride:end] notation. -template class Container = DefaultContainer> -inline Container> grid(const T &start, - const T &stop, - const T &stride) -{ - Container> - vals(size_t(std::ceil((stop - start) / stride)), T()); - - int i = 0; - std::generate(vals.begin(), vals.end(), [&i, start, stride] { - return start + i++ * stride; - }); - - return vals; -} - - // A shorter C++14 style form of the enable_if metafunction template using enable_if_t = typename std::enable_if::type; @@ -392,6 +352,56 @@ inline IntegerOnly> reserve_vector(I capacity) return ret; } +/// Exactly like Matlab https://www.mathworks.com/help/matlab/ref/linspace.html +template +inline std::vector linspace_vector(const ArithmeticOnly &start, + const T &stop, + const IntegerOnly &n) +{ + std::vector vals(n, T()); + + T stride = (stop - start) / n; + size_t i = 0; + std::generate(vals.begin(), vals.end(), [&i, start, stride] { + return start + i++ * stride; + }); + + return vals; +} + +template +inline std::array, N> linspace_array(const T &start, const T &stop) +{ + std::array vals = {T()}; + + T stride = (stop - start) / N; + size_t i = 0; + std::generate(vals.begin(), vals.end(), [&i, start, stride] { + return start + i++ * stride; + }); + + return vals; +} + +/// A set of equidistant values starting from 'start' (inclusive), ending +/// in the closest multiple of 'stride' less than or equal to 'end' and +/// leaving 'stride' space between each value. +/// Very similar to Matlab [start:stride:end] notation. +template +inline std::vector> grid(const T &start, + const T &stop, + const T &stride) +{ + std::vector vals(size_t(std::ceil((stop - start) / stride)), T()); + + int i = 0; + std::generate(vals.begin(), vals.end(), [&i, start, stride] { + return start + i++ * stride; + }); + + return vals; +} + } // namespace Slic3r #endif // MTUTILS_HPP diff --git a/src/libslic3r/MeshBoolean.cpp b/src/libslic3r/MeshBoolean.cpp index 734bfaca89..1c848eb5f8 100644 --- a/src/libslic3r/MeshBoolean.cpp +++ b/src/libslic3r/MeshBoolean.cpp @@ -111,7 +111,7 @@ static TriangleMesh cgal_to_triangle_mesh(const _CGALMesh &cgalmesh) auto vtc = cgalmesh.vertices_around_face(cgalmesh.halfedge(face)); int i = 0; Vec3crd trface; - for (auto v : vtc) trface(i++) = int(v.idx()); + for (auto v : vtc) trface(i++) = static_cast(v); facets.emplace_back(trface); } diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index c5c457e8f1..d73d8148b0 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -907,10 +907,8 @@ const BoundingBoxf3& ModelObject::raw_bounding_box() const const Transform3d& inst_matrix = this->instances.front()->get_transformation().get_matrix(true); for (const ModelVolume *v : this->volumes) - { if (v->is_model_part()) m_raw_bounding_box.merge(v->mesh().transformed_bounding_box(inst_matrix * v->get_matrix())); - } } return m_raw_bounding_box; } @@ -1115,7 +1113,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b if (keep_upper) { upper->set_model(nullptr); upper->sla_support_points.clear(); - lower->sla_drain_holes.clear(); + upper->sla_drain_holes.clear(); upper->sla_points_status = sla::PointsStatus::NoPoints; upper->clear_volumes(); upper->input_file = ""; diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index e0859e81dd..2fbd584610 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -674,6 +674,7 @@ public: set_rotation(Z, rotation); set_offset(X, unscale(offs(X))); set_offset(Y, unscale(offs(Y))); + this->object->invalidate_bounding_box(); } protected: diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index cc0554bd5f..3131fd3d10 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -836,7 +836,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ // Update the ModelObject instance, possibly invalidate the linked PrintObjects. assert(it_status->status == ModelObjectStatus::Old || it_status->status == ModelObjectStatus::Moved); // Check whether a model part volume was added or removed, their transformations or order changed. - // Only volume IDs, volume types and their order are checked, configuration and other parameters are NOT checked. + // Only volume IDs, volume types, transformation matrices and their order are checked, configuration and other parameters are NOT checked. bool model_parts_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::MODEL_PART); bool modifiers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::PARAMETER_MODIFIER); bool support_blockers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_BLOCKER); @@ -899,10 +899,14 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ model_object.instances.emplace_back(new ModelInstance(*model_instance)); model_object.instances.back()->set_model_object(&model_object); } - } else { - // Just synchronize the content of the instances. This avoids memory allocation and it does not invalidate ModelInstance pointers, - // which may be accessed by G-code export in the meanwhile to deduce sequential print order. - auto new_instance = model_object_new.instances.begin(); + } else if (! std::equal(model_object.instances.begin(), model_object.instances.end(), model_object_new.instances.begin(), + [](auto l, auto r){ return l->print_volume_state == r->print_volume_state && l->printable == r->printable && + l->get_transformation().get_matrix().isApprox(r->get_transformation().get_matrix()); })) { + // If some of the instances changed, the bounding box of the updated ModelObject is likely no more valid. + // This is safe as the ModelObject's bounding box is only accessed from this function, which is called from the main thread only. + model_object.invalidate_bounding_box(); + // Synchronize the content of instances. + auto new_instance = model_object_new.instances.begin(); for (auto old_instance = model_object.instances.begin(); old_instance != model_object.instances.end(); ++ old_instance, ++ new_instance) { (*old_instance)->set_transformation((*new_instance)->get_transformation()); (*old_instance)->print_volume_state = (*new_instance)->print_volume_state; @@ -1197,7 +1201,7 @@ std::string Print::validate() const { std::vector object_height; for (const PrintObject *object : m_objects) - object_height.insert(object_height.end(), object->instances().size(), object->size(2)); + object_height.insert(object_height.end(), object->instances().size(), object->size()(2)); std::sort(object_height.begin(), object_height.end()); // Ignore the tallest *copy* (this is why we repeat height for all of them): // it will be printed as last one so its height doesn't matter. @@ -1429,7 +1433,7 @@ BoundingBox Print::bounding_box() const for (const PrintObject *object : m_objects) for (const PrintInstance &instance : object->instances()) { bb.merge(instance.shift); - bb.merge(instance.shift + to_2d(object->size)); + bb.merge(instance.shift + to_2d(object->size())); } return bb; } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 2d83da43de..75807cddad 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -120,17 +120,17 @@ public: // so that next call to make_perimeters() performs a union() before computing loops bool typed_slices; - Vec3crd size; // XYZ in scaled coordinates - + // XYZ in scaled coordinates + const Vec3crd& size() const { return m_size; } const PrintObjectConfig& config() const { return m_config; } const LayerPtrs& layers() const { return m_layers; } const SupportLayerPtrs& support_layers() const { return m_support_layers; } const Transform3d& trafo() const { return m_trafo; } const PrintInstances& instances() const { return m_instances; } - const Point instance_center(size_t idx) const { return m_instances[idx].shift + m_copies_shift + Point(this->size.x() / 2, this->size.y() / 2); } + const Point instance_center(size_t idx) const { return m_instances[idx].shift + m_copies_shift + Point(this->size().x() / 2, this->size().y() / 2); } // since the object is aligned to origin, bounding box coincides with size - BoundingBox bounding_box() const { return BoundingBox(Point(0,0), to_2d(this->size)); } + BoundingBox bounding_box() const { return BoundingBox(Point(0,0), to_2d(this->size())); } // adds region_id, too, if necessary void add_region_volume(unsigned int region_id, int volume_id, const t_layer_height_range &layer_range) { @@ -235,6 +235,8 @@ private: void combine_infill(); void _generate_support_material(); + // XYZ in scaled coordinates + Vec3crd m_size; PrintObjectConfig m_config; // Translation in Z + Rotation + Scaling / Mirroring. Transform3d m_trafo = Transform3d::Identity(); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index adae28e4b1..5dcaf8dfbd 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -43,7 +43,7 @@ namespace Slic3r { PrintObject::PrintObject(Print* print, ModelObject* model_object, bool add_instances) : PrintObjectBaseWithState(print, model_object), typed_slices(false), - size(Vec3crd::Zero()) + m_size(Vec3crd::Zero()) { // Compute the translation to be applied to our meshes so that we work with smaller coordinates { @@ -56,7 +56,7 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, bool add_insta const BoundingBoxf3 modobj_bbox = model_object->raw_bounding_box(); m_copies_shift = Point::new_scale(modobj_bbox.min(0), modobj_bbox.min(1)); // Scale the object size and store it - this->size = (modobj_bbox.size() * (1. / SCALING_FACTOR)).cast(); + this->m_size = (modobj_bbox.size() * (1. / SCALING_FACTOR)).cast(); } if (add_instances) { @@ -73,6 +73,8 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, bool add_insta PrintBase::ApplyStatus PrintObject::set_instances(PrintInstances &&instances) { + for (PrintInstance &i : instances) + i.shift += m_copies_shift; // Invalidate and set copies. PrintBase::ApplyStatus status = PrintBase::APPLY_STATUS_UNCHANGED; bool equal_length = instances.size() == m_instances.size(); @@ -83,7 +85,7 @@ PrintBase::ApplyStatus PrintObject::set_instances(PrintInstances &&instances) if (m_print->invalidate_steps({ psSkirt, psBrim, psGCodeExport }) || (! equal_length && m_print->invalidate_step(psWipeTower))) status = PrintBase::APPLY_STATUS_INVALIDATED; - m_instances = instances; + m_instances = std::move(instances); for (PrintInstance &i : m_instances) i.print_object = this; } @@ -1448,7 +1450,7 @@ void PrintObject::update_slicing_parameters() { if (! m_slicing_params.valid) m_slicing_params = SlicingParameters::create_from_config( - this->print()->config(), m_config, unscale(this->size(2)), this->object_extruders()); + this->print()->config(), m_config, unscale(this->size()(2)), this->object_extruders()); } SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig& full_config, const ModelObject& model_object, float object_max_z) diff --git a/src/libslic3r/SLA/Common.cpp b/src/libslic3r/SLA/Common.cpp index 3d31c55226..d2aac18fda 100644 --- a/src/libslic3r/SLA/Common.cpp +++ b/src/libslic3r/SLA/Common.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #ifdef _MSC_VER #pragma warning(pop) @@ -194,17 +195,12 @@ public: #endif /* SLIC3R_SLA_NEEDS_WINDTREE */ }; -EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) { - static const double dEPS = 1e-6; - +static const constexpr double MESH_EPS = 1e-6; + +void to_eigen_mesh(const TriangleMesh &tmesh, Eigen::MatrixXd &V, Eigen::MatrixXi &F) +{ const stl_file& stl = tmesh.stl; - auto&& bb = tmesh.bounding_box(); - m_ground_level += bb.min(Z); - - Eigen::MatrixXd V; - Eigen::MatrixXi F; - V.resize(3*stl.stats.number_of_facets, 3); F.resize(stl.stats.number_of_facets, 3); for (unsigned int i = 0; i < stl.stats.number_of_facets; ++i) { @@ -217,9 +213,37 @@ EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) { F(i, 2) = int(3*i+2); } - // We will convert this to a proper 3d mesh with no duplicate points. - Eigen::VectorXi SVI, SVJ; - igl::remove_duplicate_vertices(V, F, dEPS, m_V, SVI, SVJ, m_F); + if (!tmesh.has_shared_vertices()) + { + Eigen::MatrixXd rV; + Eigen::MatrixXi rF; + // We will convert this to a proper 3d mesh with no duplicate points. + Eigen::VectorXi SVI, SVJ; + igl::remove_duplicate_vertices(V, F, MESH_EPS, rV, SVI, SVJ, rF); + V = std::move(rV); + F = std::move(rF); + } +} + +void to_triangle_mesh(const Eigen::MatrixXd &V, const Eigen::MatrixXi &F, TriangleMesh &out) +{ + Pointf3s points(size_t(V.rows())); + std::vector facets(size_t(F.rows())); + + for (Eigen::Index i = 0; i < V.rows(); ++i) + points[size_t(i)] = V.row(i); + + for (Eigen::Index i = 0; i < F.rows(); ++i) + facets[size_t(i)] = F.row(i); + + out = {points, facets}; +} + +EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) { + auto&& bb = tmesh.bounding_box(); + m_ground_level += bb.min(Z); + + to_eigen_mesh(tmesh, m_V, m_F); // Build the AABB accelaration tree m_aabb->init(m_V, m_F); @@ -262,6 +286,10 @@ EigenMesh3D &EigenMesh3D::operator=(const EigenMesh3D &other) m_aabb.reset(new AABBImpl(*other.m_aabb)); return *this; } +EigenMesh3D &EigenMesh3D::operator=(EigenMesh3D &&other) = default; + +EigenMesh3D::EigenMesh3D(EigenMesh3D &&other) = default; + EigenMesh3D::hit_result EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const { diff --git a/src/libslic3r/SLA/EigenMesh3D.hpp b/src/libslic3r/SLA/EigenMesh3D.hpp index 8d5b3b8dfd..e8b869bb46 100644 --- a/src/libslic3r/SLA/EigenMesh3D.hpp +++ b/src/libslic3r/SLA/EigenMesh3D.hpp @@ -12,6 +12,9 @@ namespace sla { struct Contour3D; +void to_eigen_mesh(const TriangleMesh &mesh, Eigen::MatrixXd &V, Eigen::MatrixXi &F); +void to_triangle_mesh(const Eigen::MatrixXd &V, const Eigen::MatrixXi &F, TriangleMesh &); + /// An index-triangle structure for libIGL functions. Also serves as an /// alternative (raw) input format for the SLASupportTree. // Implemented in libslic3r/SLA/Common.cpp @@ -30,11 +33,15 @@ class EigenMesh3D { public: - EigenMesh3D(const TriangleMesh&); + explicit EigenMesh3D(const TriangleMesh&); + explicit EigenMesh3D(const Contour3D &other); + EigenMesh3D(const EigenMesh3D& other); - EigenMesh3D(const Contour3D &other); EigenMesh3D& operator=(const EigenMesh3D&); + EigenMesh3D(EigenMesh3D &&other); + EigenMesh3D& operator=(EigenMesh3D &&other); + ~EigenMesh3D(); inline double ground_level() const { return m_ground_level + m_gnd_offset; } @@ -70,9 +77,6 @@ public: inline bool is_valid() const { return m_mesh != nullptr; } inline bool is_hit() const { return !std::isinf(m_t); } - // Hit_result can decay into a double as the hit distance. - inline operator double() const { return distance(); } - inline const Vec3d& normal() const { assert(is_valid()); return m_normal; diff --git a/src/libslic3r/SLA/RasterWriter.cpp b/src/libslic3r/SLA/RasterWriter.cpp index 238120dda0..13aef7d8a3 100644 --- a/src/libslic3r/SLA/RasterWriter.cpp +++ b/src/libslic3r/SLA/RasterWriter.cpp @@ -1,3 +1,5 @@ +#include + #include #include "libslic3r/PrintConfig.hpp" @@ -12,14 +14,16 @@ namespace Slic3r { namespace sla { -std::string RasterWriter::createIniContent(const std::string& projectname) const +void RasterWriter::write_ini(const std::map &m, std::string &ini) +{ + for (auto ¶m : m) ini += param.first + " = " + param.second + "\n"; +} + +std::string RasterWriter::create_ini_content(const std::string& projectname) const { std::string out("action = print\njobDir = "); out += projectname + "\n"; - - for (auto ¶m : m_config) - out += param.first + " = " + param.second + "\n"; - + write_ini(m_config, out); return out; } @@ -53,7 +57,12 @@ void RasterWriter::save(Zipper &zipper, const std::string &prjname) zipper.add_entry("config.ini"); - zipper << createIniContent(project); + zipper << create_ini_content(project); + + zipper.add_entry("prusaslicer.ini"); + std::string prusaslicer_ini; + write_ini(m_slicer_config, prusaslicer_ini); + zipper << prusaslicer_ini; for(unsigned i = 0; i < m_layers_rst.size(); i++) { @@ -89,6 +98,29 @@ std::string get_cfg_value(const DynamicPrintConfig &cfg, const std::string &key) return ret; } +void append_full_config(const DynamicPrintConfig &cfg, std::map &keys) +{ + using namespace std::literals::string_view_literals; + + // Sorted list of config keys, which shall not be stored into the ini. + static constexpr auto banned_keys = { + "compatible_printers"sv, + "compatible_prints"sv, + "print_host"sv, + "printhost_apikey"sv, + "printhost_cafile"sv + }; + + assert(std::is_sorted(banned_keys.begin(), banned_keys.end())); + auto is_banned = [](const std::string &key) { + return std::binary_search(banned_keys.begin(), banned_keys.end(), key); + }; + + for (const std::string &key : cfg.keys()) + if (! is_banned(key) && ! cfg.option(key)->is_nil()) + keys[key] = cfg.opt_serialize(key); +} + } // namespace void RasterWriter::set_config(const DynamicPrintConfig &cfg) @@ -101,9 +133,9 @@ void RasterWriter::set_config(const DynamicPrintConfig &cfg) m_config["printerVariant"] = get_cfg_value(cfg, "printer_variant"); m_config["printerProfile"] = get_cfg_value(cfg, "printer_settings_id"); m_config["printProfile"] = get_cfg_value(cfg, "sla_print_settings_id"); - m_config["fileCreationTimestamp"] = Utils::utc_timestamp(); m_config["prusaSlicerVersion"] = SLIC3R_BUILD_ID; + append_full_config(cfg, m_slicer_config); } void RasterWriter::set_statistics(const PrintStatistics &stats) diff --git a/src/libslic3r/SLA/RasterWriter.hpp b/src/libslic3r/SLA/RasterWriter.hpp index a472e44525..75162893de 100644 --- a/src/libslic3r/SLA/RasterWriter.hpp +++ b/src/libslic3r/SLA/RasterWriter.hpp @@ -66,8 +66,10 @@ private: double m_gamma; std::map m_config; + std::map m_slicer_config; - std::string createIniContent(const std::string& projectname) const; + static void write_ini(const std::map &m, std::string &ini); + std::string create_ini_content(const std::string& projectname) const; public: diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp index 45fef58cd0..5d60d75136 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp @@ -166,190 +166,182 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, return pc == ABORT; } +// Give points on a 3D ring with given center, radius and orientation +// method based on: +// https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space +template +class PointRing { + std::array m_phis; + + // Two vectors that will be perpendicular to each other and to the + // axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a + // placeholder. + // a and b vectors are perpendicular to the ring direction and to each other. + // Together they define the plane where we have to iterate with the + // given angles in the 'm_phis' vector + Vec3d a = {0, 1, 0}, b; + double m_radius = 0.; + + static inline bool constexpr is_one(double val) + { + return std::abs(std::abs(val) - 1) < 1e-20; + } + +public: + + PointRing(const Vec3d &n) + { + m_phis = linspace_array(0., 2 * PI); + + // We have to address the case when the direction vector v (same as + // dir) is coincident with one of the world axes. In this case two of + // its components will be completely zero and one is 1.0. Our method + // becomes dangerous here due to division with zero. Instead, vector + // 'a' can be an element-wise rotated version of 'v' + if(is_one(n(X)) || is_one(n(Y)) || is_one(n(Z))) { + a = {n(Z), n(X), n(Y)}; + b = {n(Y), n(Z), n(X)}; + } + else { + a(Z) = -(n(Y)*a(Y)) / n(Z); a.normalize(); + b = a.cross(n); + } + } + + Vec3d get(size_t idx, const Vec3d src, double r) const + { + double phi = m_phis[idx]; + double sinphi = std::sin(phi); + double cosphi = std::cos(phi); + + double rpscos = r * cosphi; + double rpssin = r * sinphi; + + // Point on the sphere + return {src(X) + rpscos * a(X) + rpssin * b(X), + src(Y) + rpscos * a(Y) + rpssin * b(Y), + src(Z) + rpscos * a(Z) + rpssin * b(Z)}; + } +}; + +template +static Hit min_hit(const C &hits) +{ + auto mit = std::min_element(hits.begin(), hits.end(), + [](const Hit &h1, const Hit &h2) { + return h1.distance() < h2.distance(); + }); + + return *mit; +} + EigenMesh3D::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect( const Vec3d &s, const Vec3d &dir, double r_pin, double r_back, double width) { static const size_t SAMPLES = 8; - // method based on: - // https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space + // Move away slightly from the touching point to avoid raycasting on the + // inner surface of the mesh. + + const double& sd = m_cfg.safety_distance_mm; + + auto& m = m_mesh; + using HitResult = EigenMesh3D::hit_result; + + // Hit results + std::array hits; + + struct Rings { + double rpin; + double rback; + Vec3d spin; + Vec3d sback; + PointRing ring; + + Vec3d backring(size_t idx) { return ring.get(idx, sback, rback); } + Vec3d pinring(size_t idx) { return ring.get(idx, spin, rpin); } + } rings {r_pin + sd, r_back + sd, s, s + width * dir, dir}; // We will shoot multiple rays from the head pinpoint in the direction // of the pinhead robe (side) surface. The result will be the smallest // hit distance. - // Move away slightly from the touching point to avoid raycasting on the - // inner surface of the mesh. - Vec3d v = dir; // Our direction (axis) - Vec3d c = s + width * dir; - const double& sd = m_cfg.safety_distance_mm; + ccr::enumerate(hits.begin(), hits.end(), + [&m, &rings, sd](HitResult &hit, size_t i) { - // Two vectors that will be perpendicular to each other and to the - // axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a - // placeholder. - Vec3d a(0, 1, 0), b; + // Point on the circle on the pin sphere + Vec3d ps = rings.pinring(i); + // This is the point on the circle on the back sphere + Vec3d p = rings.backring(i); + + // Point ps is not on mesh but can be inside or + // outside as well. This would cause many problems + // with ray-casting. To detect the position we will + // use the ray-casting result (which has an is_inside + // predicate). - // The portions of the circle (the head-back circle) for which we will - // shoot rays. - std::array phis; - for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size(); + Vec3d n = (p - ps).normalized(); + auto q = m.query_ray_hit(ps + sd * n, n); - auto& m = m_mesh; - using HitResult = EigenMesh3D::hit_result; + if (q.is_inside()) { // the hit is inside the model + if (q.distance() > rings.rpin) { + // If we are inside the model and the hit + // distance is bigger than our pin circle + // diameter, it probably indicates that the + // support point was already inside the + // model, or there is really no space + // around the point. We will assign a zero + // hit distance to these cases which will + // enforce the function return value to be + // an invalid ray with zero hit distance. + // (see min_element at the end) + hit = HitResult(0.0); + } else { + // re-cast the ray from the outside of the + // object. The starting point has an offset + // of 2*safety_distance because the + // original ray has also had an offset + auto q2 = m.query_ray_hit(ps + (q.distance() + 2 * sd) * n, n); + hit = q2; + } + } else + hit = q; + }); - // Hit results - std::array hits; - - // We have to address the case when the direction vector v (same as - // dir) is coincident with one of the world axes. In this case two of - // its components will be completely zero and one is 1.0. Our method - // becomes dangerous here due to division with zero. Instead, vector - // 'a' can be an element-wise rotated version of 'v' - auto chk1 = [] (double val) { - return std::abs(std::abs(val) - 1) < 1e-20; - }; - - if(chk1(v(X)) || chk1(v(Y)) || chk1(v(Z))) { - a = {v(Z), v(X), v(Y)}; - b = {v(Y), v(Z), v(X)}; - } - else { - a(Z) = -(v(Y)*a(Y)) / v(Z); a.normalize(); - b = a.cross(v); - } - - // Now a and b vectors are perpendicular to v and to each other. - // Together they define the plane where we have to iterate with the - // given angles in the 'phis' vector - ccr::enumerate( - phis.begin(), phis.end(), - [&hits, &m, sd, r_pin, r_back, s, a, b, c](double phi, size_t i) { - double sinphi = std::sin(phi); - double cosphi = std::cos(phi); - - // Let's have a safety coefficient for the radiuses. - double rpscos = (sd + r_pin) * cosphi; - double rpssin = (sd + r_pin) * sinphi; - double rpbcos = (sd + r_back) * cosphi; - double rpbsin = (sd + r_back) * sinphi; - - // Point on the circle on the pin sphere - Vec3d ps(s(X) + rpscos * a(X) + rpssin * b(X), - s(Y) + rpscos * a(Y) + rpssin * b(Y), - s(Z) + rpscos * a(Z) + rpssin * b(Z)); - - // Point ps is not on mesh but can be inside or - // outside as well. This would cause many problems - // with ray-casting. To detect the position we will - // use the ray-casting result (which has an is_inside - // predicate). - - // This is the point on the circle on the back sphere - Vec3d p(c(X) + rpbcos * a(X) + rpbsin * b(X), - c(Y) + rpbcos * a(Y) + rpbsin * b(Y), - c(Z) + rpbcos * a(Z) + rpbsin * b(Z)); - - Vec3d n = (p - ps).normalized(); - auto q = m.query_ray_hit(ps + sd * n, n); - - if (q.is_inside()) { // the hit is inside the model - if (q.distance() > r_pin + sd) { - // If we are inside the model and the hit - // distance is bigger than our pin circle - // diameter, it probably indicates that the - // support point was already inside the - // model, or there is really no space - // around the point. We will assign a zero - // hit distance to these cases which will - // enforce the function return value to be - // an invalid ray with zero hit distance. - // (see min_element at the end) - hits[i] = HitResult(0.0); - } else { - // re-cast the ray from the outside of the - // object. The starting point has an offset - // of 2*safety_distance because the - // original ray has also had an offset - auto q2 = m.query_ray_hit( - ps + (q.distance() + 2 * sd) * n, n); - hits[i] = q2; - } - } else - hits[i] = q; - }); - - auto mit = std::min_element(hits.begin(), hits.end()); - - return *mit; + return min_hit(hits); } EigenMesh3D::hit_result SupportTreeBuildsteps::bridge_mesh_intersect( - const Vec3d &s, const Vec3d &dir, double r, bool ins_check) + const Vec3d &src, const Vec3d &dir, double r, bool ins_check) { static const size_t SAMPLES = 8; + PointRing ring{dir}; - // helper vector calculations - Vec3d a(0, 1, 0), b; - const double& sd = m_cfg.safety_distance_mm; - - // INFO: for explanation of the method used here, see the previous - // method's comments. - - auto chk1 = [] (double val) { - return std::abs(std::abs(val) - 1) < 1e-20; - }; - - if(chk1(dir(X)) || chk1(dir(Y)) || chk1(dir(Z))) { - a = {dir(Z), dir(X), dir(Y)}; - b = {dir(Y), dir(Z), dir(X)}; - } - else { - a(Z) = -(dir(Y)*a(Y)) / dir(Z); a.normalize(); - b = a.cross(dir); - } - - // circle portions - std::array phis; - for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size(); - - auto& m = m_mesh; - using HitResult = EigenMesh3D::hit_result; + using Hit = EigenMesh3D::hit_result; // Hit results - std::array hits; + std::array hits; - ccr::enumerate( - phis.begin(), phis.end(), - [&m, a, b, sd, dir, r, s, ins_check, &hits] (double phi, size_t i) { - double sinphi = std::sin(phi); - double cosphi = std::cos(phi); - - // Let's have a safety coefficient for the radiuses. - double rcos = (sd + r) * cosphi; - double rsin = (sd + r) * sinphi; - - // Point on the circle on the pin sphere - Vec3d p (s(X) + rcos * a(X) + rsin * b(X), - s(Y) + rcos * a(Y) + rsin * b(Y), - s(Z) + rcos * a(Z) + rsin * b(Z)); - - auto hr = m.query_ray_hit(p + sd*dir, dir); - - if(ins_check && hr.is_inside()) { - if(hr.distance() > 2 * r + sd) hits[i] = HitResult(0.0); - else { - // re-cast the ray from the outside of the object - auto hr2 = - m.query_ray_hit(p + (hr.distance() + 2*sd)*dir, dir); - - hits[i] = hr2; - } - } else hits[i] = hr; - }); + ccr::enumerate(hits.begin(), hits.end(), + [this, r, src, ins_check, &ring, dir] (Hit &hit, size_t i) { + + const double sd = m_cfg.safety_distance_mm; + + // Point on the circle on the pin sphere + Vec3d p = ring.get(i, src, r + sd); + + auto hr = m_mesh.query_ray_hit(p + sd * dir, dir); + + if(ins_check && hr.is_inside()) { + if(hr.distance() > 2 * r + sd) hit = Hit(0.0); + else { + // re-cast the ray from the outside of the object + hit = m_mesh.query_ray_hit(p + (hr.distance() + 2 * sd) * dir, dir); + } + } else hit = hr; + }); - auto mit = std::min_element(hits.begin(), hits.end()); - - return *mit; + return min_hit(hits); } bool SupportTreeBuildsteps::interconnect(const Pillar &pillar, @@ -419,7 +411,7 @@ bool SupportTreeBuildsteps::interconnect(const Pillar &pillar, // TODO: This is a workaround to not have a faulty last bridge while(ej(Z) >= eupper(Z) /*endz*/) { - if(bridge_mesh_intersect(sj, dirv(sj, ej), pillar.r) >= bridge_distance) + if(bridge_mesh_distance(sj, dirv(sj, ej), pillar.r) >= bridge_distance) { m_builder.add_crossbridge(sj, ej, pillar.r); was_connected = true; @@ -430,7 +422,7 @@ bool SupportTreeBuildsteps::interconnect(const Pillar &pillar, Vec3d sjback(ej(X), ej(Y), sj(Z)); Vec3d ejback(sj(X), sj(Y), ej(Z)); if (sjback(Z) <= slower(Z) && ejback(Z) >= eupper(Z) && - bridge_mesh_intersect(sjback, dirv(sjback, ejback), + bridge_mesh_distance(sjback, dirv(sjback, ejback), pillar.r) >= bridge_distance) { // need to check collision for the cross stick m_builder.add_crossbridge(sjback, ejback, pillar.r); @@ -487,7 +479,7 @@ bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head, bridgestart(Z) -= zdiff; touchjp(Z) = Zdown; - double t = bridge_mesh_intersect(headjp, {0,0,-1}, r); + double t = bridge_mesh_distance(headjp, DOWN, r); // We can't insert a pillar under the source head to connect // with the nearby pillar's starting junction @@ -505,8 +497,7 @@ bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head, double minz = m_builder.ground_level + 2 * m_cfg.head_width_mm; if(bridgeend(Z) < minz) return false; - double t = bridge_mesh_intersect(bridgestart, - dirv(bridgestart, bridgeend), r); + double t = bridge_mesh_distance(bridgestart, dirv(bridgestart, bridgeend), r); // Cannot insert the bridge. (further search might not worth the hassle) if(t < distance(bridgestart, bridgeend)) return false; @@ -633,7 +624,7 @@ void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, }; // We have to check if the bridge is feasible. - if (bridge_mesh_intersect(jp, dir, radius) < (endp - jp).norm()) + if (bridge_mesh_distance(jp, dir, radius) < (endp - jp).norm()) abort_in_shame(); else { // If the new endpoint is below ground, do not make a pillar @@ -764,7 +755,7 @@ void SupportTreeBuildsteps::filter() { auto dir = spheric_to_dir(plr, azm).normalized(); - double score = pinhead_mesh_intersect( + double score = pinhead_mesh_distance( hp, dir, pin_r, m_cfg.head_back_radius_mm, w); return score; @@ -950,11 +941,11 @@ bool SupportTreeBuildsteps::connect_to_ground(Head &head, const Vec3d &dir) { auto hjp = head.junction_point(); double r = head.r_back_mm; - double t = bridge_mesh_intersect(hjp, dir, head.r_back_mm); + double t = bridge_mesh_distance(hjp, dir, head.r_back_mm); double d = 0, tdown = 0; t = std::min(t, m_cfg.max_bridge_length_mm); - while (d < t && !std::isinf(tdown = bridge_mesh_intersect(hjp + d * dir, DOWN, r))) + while (d < t && !std::isinf(tdown = bridge_mesh_distance(hjp + d * dir, DOWN, r))) d += r; if(!std::isinf(tdown)) return false; @@ -989,7 +980,7 @@ bool SupportTreeBuildsteps::connect_to_ground(Head &head) auto oresult = solver.optimize_max( [this, hjp, r_back](double plr, double azm) { Vec3d n = spheric_to_dir(plr, azm).normalized(); - return bridge_mesh_intersect(hjp, n, r_back); + return bridge_mesh_distance(hjp, n, r_back); }, initvals(polar, azimuth), // let's start with what we have bound(3*PI/4, PI), // Must not exceed the slope limit @@ -1259,9 +1250,8 @@ void SupportTreeBuildsteps::interconnect_pillars() m_pillar_index.insert(pp.endpoint(), unsigned(pp.id)); m_builder.add_junction(s, pillar().r); - double t = bridge_mesh_intersect(pillarsp, - dirv(pillarsp, s), - pillar().r); + double t = bridge_mesh_distance(pillarsp, dirv(pillarsp, s), + pillar().r); if (distance(pillarsp, s) < t) m_builder.add_bridge(pillarsp, s, pillar().r); @@ -1312,8 +1302,8 @@ void SupportTreeBuildsteps::routing_headless() Vec3d sj = sp + R * n; // stick start point // This is only for checking - double idist = bridge_mesh_intersect(sph, DOWN, R, true); - double realdist = ray_mesh_intersect(sj, DOWN); + double idist = bridge_mesh_distance(sph, DOWN, R, true); + double realdist = ray_mesh_intersect(sj, DOWN).distance(); double dist = realdist; if (std::isinf(dist)) dist = sph(Z) - m_builder.ground_level; diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp index 9533049b60..cfe78fe97a 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp @@ -206,10 +206,10 @@ class SupportTreeBuildsteps { // When bridging heads to pillars... TODO: find a cleaner solution ccr::BlockingMutex m_bridge_mutex; - inline double ray_mesh_intersect(const Vec3d& s, - const Vec3d& dir) + inline EigenMesh3D::hit_result ray_mesh_intersect(const Vec3d& s, + const Vec3d& dir) { - return m_mesh.query_ray_hit(s, dir).distance(); + return m_mesh.query_ray_hit(s, dir); } // This function will test if a future pinhead would not collide with the @@ -229,6 +229,11 @@ class SupportTreeBuildsteps { double r_pin, double r_back, double width); + + template + inline double pinhead_mesh_distance(Args&&...args) { + return pinhead_mesh_intersect(std::forward(args)...).distance(); + } // Checking bridge (pillar and stick as well) intersection with the model. // If the function is used for headless sticks, the ins_check parameter @@ -243,6 +248,11 @@ class SupportTreeBuildsteps { const Vec3d& dir, double r, bool ins_check = false); + + template + inline double bridge_mesh_distance(Args&&...args) { + return bridge_mesh_intersect(std::forward(args)...).distance(); + } // Helper function for interconnecting two pillars with zig-zag bridges. bool interconnect(const Pillar& pillar, const Pillar& nextpillar); diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 1935e5e319..e98e0edc41 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -678,7 +678,7 @@ void SLAPrint::process() // We want to first process all objects... std::vector level1_obj_steps = { - slaposHollowing, slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposPad + slaposHollowing, slaposDrillHoles, slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposPad }; // and then slice all supports to allow preview to be displayed ASAP @@ -984,10 +984,10 @@ bool SLAPrintObject::invalidate_step(SLAPrintObjectStep step) // propagate to dependent steps if (step == slaposHollowing) { invalidated |= this->invalidate_all_steps(); - } else if (step == slaposObjectSlice) { - invalidated |= this->invalidate_steps({ slaposDrillHolesIfHollowed, slaposSupportPoints, slaposSupportTree, slaposPad, slaposSliceSupports }); + } else if (step == slaposDrillHoles) { + invalidated |= this->invalidate_steps({ slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposPad, slaposSliceSupports }); invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval); - } else if (step == slaposDrillHolesIfHollowed) { + } else if (step == slaposObjectSlice) { invalidated |= this->invalidate_steps({ slaposSupportPoints, slaposSupportTree, slaposPad, slaposSliceSupports }); invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval); } else if (step == slaposSupportPoints) { @@ -1095,8 +1095,6 @@ const ExPolygons &SliceRecord::get_slice(SliceOrigin o) const const std::vector& v = o == soModel? m_po->get_model_slices() : m_po->get_support_slices(); - if(idx >= v.size()) return EMPTY_SLICE; - return idx >= v.size() ? EMPTY_SLICE : v[idx]; } diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 871fb3de43..d2f1212776 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -20,8 +20,8 @@ enum SLAPrintStep : unsigned int { enum SLAPrintObjectStep : unsigned int { slaposHollowing, + slaposDrillHoles, slaposObjectSlice, - slaposDrillHolesIfHollowed, slaposSupportPoints, slaposSupportTree, slaposPad, @@ -138,9 +138,9 @@ public: // Returns the current layer height float layer_height() const { return m_height; } - bool is_valid() const { return ! std::isnan(m_slice_z); } + bool is_valid() const { return m_po && ! std::isnan(m_slice_z); } - const SLAPrintObject* print_obj() const { assert(m_po); return m_po; } + const SLAPrintObject* print_obj() const { return m_po; } // Methods for setting the indices into the slice vectors. void set_model_slice_idx(const SLAPrintObject &po, size_t id) { diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 8c0aac6fa7..4bb6be7b02 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -26,9 +26,9 @@ namespace Slic3r { namespace { const std::array OBJ_STEP_LEVELS = { - 5, // slaposHollowing, - 20, // slaposObjectSlice, - 5, // slaposDrillHolesIfHollowed + 10, // slaposHollowing, + 10, // slaposDrillHolesIfHollowed + 10, // slaposObjectSlice, 20, // slaposSupportPoints, 10, // slaposSupportTree, 10, // slaposPad, @@ -38,9 +38,9 @@ const std::array OBJ_STEP_LEVELS = { std::string OBJ_STEP_LABELS(size_t idx) { switch (idx) { - case slaposHollowing: return L("Hollowing and drilling holes"); + case slaposHollowing: return L("Hollowing model"); + case slaposDrillHoles: return L("Drilling holes into hollowed model."); case slaposObjectSlice: return L("Slicing model"); - case slaposDrillHolesIfHollowed: return L("Drilling holes into hollowed model."); case slaposSupportPoints: return L("Generating support points"); case slaposSupportTree: return L("Generating support tree"); case slaposPad: return L("Generating pad"); @@ -80,70 +80,70 @@ SLAPrint::Steps::Steps(SLAPrint *print) void SLAPrint::Steps::hollow_model(SLAPrintObject &po) { po.m_hollowing_data.reset(); - bool drilling_needed = ! po.m_model_object->sla_drain_holes.empty(); - // If the mesh is broken, stop immediately, even before hollowing. - //if (drilling_needed && po.transformed_mesh().needed_repair()) - // throw std::runtime_error(L("The mesh appears to be too broken " - // "to drill holes into it reliably.")); - - if (! po.m_config.hollowing_enable.getBool()) - BOOST_LOG_TRIVIAL(info) << "Skipping hollowing step!"; - else { - BOOST_LOG_TRIVIAL(info) << "Performing hollowing step!"; - - double thickness = po.m_config.hollowing_min_thickness.getFloat(); - double quality = po.m_config.hollowing_quality.getFloat(); - double closing_d = po.m_config.hollowing_closing_distance.getFloat(); - sla::HollowingConfig hlwcfg{thickness, quality, closing_d}; - auto meshptr = generate_interior(po.transformed_mesh(), hlwcfg); - - if (meshptr->empty()) - BOOST_LOG_TRIVIAL(warning) << "Hollowed interior is empty!"; - else { - po.m_hollowing_data.reset(new SLAPrintObject::HollowingData()); - po.m_hollowing_data->interior = *meshptr; - auto &hollowed_mesh = po.m_hollowing_data->hollow_mesh_with_holes; - hollowed_mesh = po.transformed_mesh(); - hollowed_mesh.merge(po.m_hollowing_data->interior); - hollowed_mesh.require_shared_vertices(); - } + if (! po.m_config.hollowing_enable.getBool()) { + BOOST_LOG_TRIVIAL(info) << "Skipping hollowing step!"; + return; } + + BOOST_LOG_TRIVIAL(info) << "Performing hollowing step!"; - // Drill holes into the hollowed/original mesh. - if (! drilling_needed) - BOOST_LOG_TRIVIAL(info) << "Drilling skipped (no holes)."; + double thickness = po.m_config.hollowing_min_thickness.getFloat(); + double quality = po.m_config.hollowing_quality.getFloat(); + double closing_d = po.m_config.hollowing_closing_distance.getFloat(); + sla::HollowingConfig hlwcfg{thickness, quality, closing_d}; + auto meshptr = generate_interior(po.transformed_mesh(), hlwcfg); + + if (meshptr->empty()) + BOOST_LOG_TRIVIAL(warning) << "Hollowed interior is empty!"; else { - BOOST_LOG_TRIVIAL(info) << "Drilling drainage holes."; - sla::DrainHoles drainholes = po.transformed_drainhole_points(); - - TriangleMesh holes_mesh; - - for (const sla::DrainHole &holept : drainholes) - holes_mesh.merge(sla::to_triangle_mesh(holept.to_mesh())); - - holes_mesh.require_shared_vertices(); - MeshBoolean::self_union(holes_mesh); - - // If there is no hollowed mesh yet, copy the original mesh. - if (! po.m_hollowing_data) { - po.m_hollowing_data.reset(new SLAPrintObject::HollowingData()); - po.m_hollowing_data->hollow_mesh_with_holes = po.transformed_mesh(); - } - - TriangleMesh &hollowed_mesh = po.m_hollowing_data->hollow_mesh_with_holes; - hollowed_mesh = po.get_mesh_to_print(); - try { - MeshBoolean::cgal::minus(hollowed_mesh, holes_mesh); - } - catch (const std::runtime_error& ex) { - throw std::runtime_error(L("Drilling holes into the mesh failed. " - "This is usually caused by broken model. Try to fix it first.")); - } + po.m_hollowing_data.reset(new SLAPrintObject::HollowingData()); + po.m_hollowing_data->interior = *meshptr; + auto &hollowed_mesh = po.m_hollowing_data->hollow_mesh_with_holes; + hollowed_mesh = po.transformed_mesh(); + hollowed_mesh.merge(po.m_hollowing_data->interior); hollowed_mesh.require_shared_vertices(); } } +void SLAPrint::Steps::drill_holes(SLAPrintObject &po) +{ + // Drill holes into the hollowed/original mesh. + if (po.m_model_object->sla_drain_holes.empty()) { + BOOST_LOG_TRIVIAL(info) << "Drilling skipped (no holes)."; + return; + } + + BOOST_LOG_TRIVIAL(info) << "Drilling drainage holes."; + sla::DrainHoles drainholes = po.transformed_drainhole_points(); + + TriangleMesh holes_mesh; + + for (const sla::DrainHole &holept : drainholes) + holes_mesh.merge(sla::to_triangle_mesh(holept.to_mesh())); + + holes_mesh.require_shared_vertices(); + MeshBoolean::cgal::self_union(holes_mesh); //FIXME-fix and use the cgal version + + // If there is no hollowed mesh yet, copy the original mesh. + if (! po.m_hollowing_data) { + po.m_hollowing_data.reset(new SLAPrintObject::HollowingData()); + po.m_hollowing_data->hollow_mesh_with_holes = po.transformed_mesh(); + } + + TriangleMesh &hollowed_mesh = po.m_hollowing_data->hollow_mesh_with_holes; + + try { + MeshBoolean::cgal::minus(hollowed_mesh, holes_mesh); + } catch (const std::runtime_error &ex) { + throw std::runtime_error(L( + "Drilling holes into the mesh failed. " + "This is usually caused by broken model. Try to fix it first.")); + } + + hollowed_mesh.require_shared_vertices(); +} + // The slicing will be performed on an imaginary 1D grid which starts from // the bottom of the bounding box created around the supported model. So // the first layer which is usually thicker will be part of the supports @@ -478,14 +478,16 @@ static ClipperPolygons polydiff(const ClipperPolygons &subjects, const ClipperPo } // get polygons for all instances in the object -static ClipperPolygons get_all_polygons( - const ExPolygons & input_polygons, - const std::vector &instances, - bool is_lefthanded) +static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o) { namespace sl = libnest2d::sl; + if (!record.print_obj()) return {}; + ClipperPolygons polygons; + auto &input_polygons = record.get_slice(o); + auto &instances = record.print_obj()->instances(); + bool is_lefthanded = record.print_obj()->is_left_handed(); polygons.reserve(input_polygons.size() * instances.size()); for (const ExPolygon& polygon : input_polygons) { @@ -558,6 +560,12 @@ void SLAPrint::Steps::initialize_printer_input() coord_t gndlvl = o->get_slice_index().front().print_level() - ilhs; for(const SliceRecord& slicerecord : o->get_slice_index()) { + if (!slicerecord.is_valid()) + throw std::runtime_error( + L("There are unprintable objects. Try to " + "adjust support settings to make the " + "objects printable.")); + coord_t lvlid = slicerecord.print_level() - gndlvl; // Neat trick to round the layer levels to the grid. @@ -660,22 +668,13 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { supports_polygons.reserve(c); for(const SliceRecord& record : layer.slices()) { - const SLAPrintObject *po = record.print_obj(); - const ExPolygons &modelslices = record.get_slice(soModel); - - bool is_lefth = record.print_obj()->is_left_handed(); - if (!modelslices.empty()) { - ClipperPolygons v = get_all_polygons(modelslices, po->instances(), is_lefth); - for(ClipperPolygon& p_tmp : v) model_polygons.emplace_back(std::move(p_tmp)); - } - - const ExPolygons &supportslices = record.get_slice(soSupport); - - if (!supportslices.empty()) { - ClipperPolygons v = get_all_polygons(supportslices, po->instances(), is_lefth); - for(ClipperPolygon& p_tmp : v) supports_polygons.emplace_back(std::move(p_tmp)); - } + ClipperPolygons modelslices = get_all_polygons(record, soModel); + for(ClipperPolygon& p_tmp : modelslices) model_polygons.emplace_back(std::move(p_tmp)); + + ClipperPolygons supportslices = get_all_polygons(record, soSupport); + for(ClipperPolygon& p_tmp : supportslices) supports_polygons.emplace_back(std::move(p_tmp)); + } model_polygons = polyunion(model_polygons); @@ -864,8 +863,8 @@ void SLAPrint::Steps::execute(SLAPrintObjectStep step, SLAPrintObject &obj) { switch(step) { case slaposHollowing: hollow_model(obj); break; + case slaposDrillHoles: drill_holes(obj); break; case slaposObjectSlice: slice_model(obj); break; - case slaposDrillHolesIfHollowed: break; case slaposSupportPoints: support_points(obj); break; case slaposSupportTree: support_tree(obj); break; case slaposPad: generate_pad(obj); break; diff --git a/src/libslic3r/SLAPrintSteps.hpp b/src/libslic3r/SLAPrintSteps.hpp index c62558671c..ad099e0e75 100644 --- a/src/libslic3r/SLAPrintSteps.hpp +++ b/src/libslic3r/SLAPrintSteps.hpp @@ -44,6 +44,7 @@ public: Steps(SLAPrint *print); void hollow_model(SLAPrintObject &po); + void drill_holes (SLAPrintObject &po); void slice_model(SLAPrintObject& po); void support_points(SLAPrintObject& po); void support_tree(SLAPrintObject& po); diff --git a/src/libslic3r/SimplifyMeshImpl.hpp b/src/libslic3r/SimplifyMeshImpl.hpp index 6add08930c..4b6b0f5cbc 100644 --- a/src/libslic3r/SimplifyMeshImpl.hpp +++ b/src/libslic3r/SimplifyMeshImpl.hpp @@ -21,8 +21,10 @@ #include #include #include +#include #ifndef NDEBUG +#include #include #endif @@ -63,7 +65,7 @@ namespace implementation { template using enable_if_t = typename std::enable_if::type; -// Meta predicates for floating, 'scaled coord' and generic arithmetic types +// Meta predicates for floating, integer and generic arithmetic types template using FloatingOnly = enable_if_t::value, O>; @@ -82,41 +84,15 @@ struct remove_cvref { template< class T > using remove_cvref_t = typename remove_cvref::type; -struct DOut { -#ifndef NDEBUG - std::ostream& out = std::cout; -#endif -}; - -template -inline DOut&& operator<<( DOut&& out, T&& d) { -#ifndef NDEBUG - out.out << d; -#endif - return std::move(out); -} - -inline DOut dout() { return DOut(); } - template FloatingOnly is_approx(T val, T ref) { return std::abs(val - ref) < 1e-8; } template IntegerOnly is_approx(T val, T ref) { val == ref; } -template class SymetricMatrix { +template class SymetricMatrix { + static const constexpr size_t N = 10; public: explicit SymetricMatrix(ArithmeticOnly c = T()) { std::fill(m, m + N, c); } - SymetricMatrix(T m11, T m12, T m13, T m14, - T m22, T m23, T m24, - T m33, T m34, - T m44) - { - m[0] = m11; m[1] = m12; m[2] = m13; m[3] = m14; - m[4] = m22; m[5] = m23; m[6] = m24; - m[7] = m33; m[8] = m34; - m[9] = m44; - } - // Make plane SymetricMatrix(T a, T b, T c, T d) { @@ -140,21 +116,16 @@ public: return det; } - const SymetricMatrix operator+(const SymetricMatrix& n) const + const SymetricMatrix& operator+=(const SymetricMatrix& n) { - return SymetricMatrix(m[0] + n[0], m[1] + n[1], m[2] + n[2], m[3]+n[3], - m[4] + n[4], m[5] + n[5], m[6] + n[6], - m[7] + n[7], m[8] + n[8], - m[9] + n[9]); + for (size_t i = 0; i < N; ++i) m[i] += n[i]; + return *this; } - SymetricMatrix& operator+=(const SymetricMatrix& n) + SymetricMatrix operator+(const SymetricMatrix& n) { - m[0]+=n[0]; m[1]+=n[1]; m[2]+=n[2]; m[3]+=n[3]; - m[4]+=n[4]; m[5]+=n[5]; m[6]+=n[6]; m[7]+=n[7]; - m[8]+=n[8]; m[9]+=n[9]; - - return *this; + SymetricMatrix self = *this; + return self += n; } T m[N]; @@ -349,10 +320,10 @@ public: } - void simplify_mesh_lossless(); + template void simplify_mesh_lossless(ProgressFn &&fn); + void simplify_mesh_lossless() { simplify_mesh_lossless([](int){}); } }; - template void SimplifiableMesh::compact_faces() { auto it = std::remove_if(m_faceinfo.begin(), m_faceinfo.end(), @@ -604,7 +575,7 @@ bool SimplifiableMesh::flipped(const Vertex & p, } template -void SimplifiableMesh::simplify_mesh_lossless() +template void SimplifiableMesh::simplify_mesh_lossless(Fn &&fn) { // init for (FaceInfo &fi : m_faceinfo) fi.deleted = false; @@ -628,7 +599,7 @@ void SimplifiableMesh::simplify_mesh_lossless() // double threshold = std::numeric_limits::epsilon(); //1.0E-3 EPS; // Really? (tm) - dout() << "lossless iteration " << iteration << "\n"; + fn(iteration); for (FaceInfo &fi : m_faceinfo) { if (fi.err[3] > threshold || fi.deleted || fi.dirty) continue; diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index 06c4358099..bc6aa20fc5 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -93,8 +93,6 @@ namespace PerlUtils { extern std::string path_to_parent_path(const char *src); }; -std::string string_printf(const char *format, ...); - // Standard "generated by Slic3r version xxx timestamp xxx" header string, // to be placed at the top of Slic3r generated files. std::string header_slic3r_generated(); diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 0454644bb9..7d9558f1e5 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -231,16 +231,17 @@ static inline bool is_approx(Number value, Number test_value) } template -std::string string_printf(const char *const fmt, Args &&...args) +std::string string_printf(const char *const _fmt, Args &&...args) { static const size_t INITIAL_LEN = 1024; std::vector buffer(INITIAL_LEN, '\0'); - int bufflen = snprintf(buffer.data(), INITIAL_LEN - 1, fmt, std::forward(args)...); + auto fmt = std::string("%s") + _fmt; + int bufflen = snprintf(buffer.data(), INITIAL_LEN - 1, fmt.c_str(), "", std::forward(args)...); if (bufflen >= int(INITIAL_LEN)) { buffer.resize(size_t(bufflen) + 1); - snprintf(buffer.data(), buffer.size(), fmt, std::forward(args)...); + snprintf(buffer.data(), buffer.size(), fmt.c_str(), "", std::forward(args)...); } return std::string(buffer.begin(), buffer.begin() + bufflen); diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index f91d32d288..9f0afa0614 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -577,24 +577,6 @@ namespace PerlUtils { std::string path_to_parent_path(const char *src) { return boost::filesystem::path(src).parent_path().string(); } }; - -std::string string_printf(const char *format, ...) -{ - va_list args1; - va_start(args1, format); - va_list args2; - va_copy(args2, args1); - - size_t needed_size = ::vsnprintf(nullptr, 0, format, args1) + 1; - va_end(args1); - - std::string res(needed_size, '\0'); - ::vsnprintf(&res.front(), res.size(), format, args2); - va_end(args2); - - return res; -} - std::string header_slic3r_generated() { return std::string("generated by " SLIC3R_APP_NAME " " SLIC3R_VERSION " on " ) + Utils::utc_timestamp(); diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 4e8edf7aa2..918a2c0512 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -147,6 +147,8 @@ set(SLIC3R_GUI_SOURCES GUI/Mouse3DController.hpp GUI/DoubleSlider.cpp GUI/DoubleSlider.hpp + GUI/ObjectDataViewModel.cpp + GUI/ObjectDataViewModel.hpp Utils/Http.cpp Utils/Http.hpp Utils/FixModelByWin10.cpp diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index f6b1719dbf..9f36ab5371 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -10,7 +10,7 @@ #include "libslic3r/SLAPrint.hpp" #include "libslic3r/Slicing.hpp" #include "libslic3r/GCode/Analyzer.hpp" -#include "slic3r/GUI/PresetBundle.hpp" +#include "slic3r/GUI/BitmapCache.hpp" #include "libslic3r/Format/STL.hpp" #include "libslic3r/Utils.hpp" @@ -792,14 +792,14 @@ void GLVolumeCollection::update_colors_by_extruder(const DynamicPrintConfig* con for (unsigned int i = 0; i < colors_count; ++i) { const std::string& txt_color = config->opt_string("extruder_colour", i); - if (PresetBundle::parse_color(txt_color, rgb)) + if (Slic3r::GUI::BitmapCache::parse_color(txt_color, rgb)) { colors[i].set(txt_color, rgb); } else { const std::string& txt_color = config->opt_string("filament_colour", i); - if (PresetBundle::parse_color(txt_color, rgb)) + if (Slic3r::GUI::BitmapCache::parse_color(txt_color, rgb)) colors[i].set(txt_color, rgb); } } diff --git a/src/slic3r/GUI/BitmapCache.cpp b/src/slic3r/GUI/BitmapCache.cpp index 7322a88d1c..8627ef4cb6 100644 --- a/src/slic3r/GUI/BitmapCache.cpp +++ b/src/slic3r/GUI/BitmapCache.cpp @@ -1,6 +1,9 @@ #include "BitmapCache.hpp" #include "libslic3r/Utils.hpp" +#include "../Utils/MacDarkMode.hpp" +#include "GUI.hpp" + #include #if ! defined(WIN32) && ! defined(__APPLE__) @@ -20,6 +23,16 @@ namespace Slic3r { namespace GUI { +BitmapCache::BitmapCache() +{ +#ifdef __APPLE__ + // Note: win->GetContentScaleFactor() is not used anymore here because it tends to + // return bogus results quite often (such as 1.0 on Retina or even 0.0). + // We're using the max scaling factor across all screens because it's very likely to be good enough. + m_scale = mac_max_scaling_factor(); +#endif +} + void BitmapCache::clear() { for (std::pair &bitmap : m_map) @@ -55,6 +68,14 @@ wxBitmap* BitmapCache::insert(const std::string &bitmap_key, size_t width, size_ auto it = m_map.find(bitmap_key); if (it == m_map.end()) { bitmap = new wxBitmap(width, height); +#ifdef __APPLE__ + // Contrary to intuition, the `scale` argument isn't "please scale this to such and such" + // but rather "the wxImage is sized for backing scale such and such". + // So, We need to let the Mac OS wxBitmap implementation + // know that the image may already be scaled appropriately for Retina, + // and thereby that it's not supposed to upscale it. + bitmap->CreateScaled(width, height, -1, m_scale); +#endif m_map[bitmap_key] = bitmap; } else { bitmap = it->second; @@ -100,8 +121,13 @@ wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap *beg size_t width = 0; size_t height = 0; for (const wxBitmap *bmp = begin; bmp != end; ++ bmp) { +#ifdef __APPLE__ + width += bmp->GetScaledWidth(); + height = std::max(height, bmp->GetScaledHeight()); +#else width += bmp->GetWidth(); height = std::max(height, bmp->GetHeight()); +#endif } #ifdef BROKEN_ALPHA @@ -167,7 +193,12 @@ wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap *beg for (const wxBitmap *bmp = begin; bmp != end; ++ bmp) { if (bmp->GetWidth() > 0) memDC.DrawBitmap(*bmp, x, 0, true); +#ifdef __APPLE__ + // we should "move" with step equal to non-scaled width + x += bmp->GetScaledWidth(); +#else x += bmp->GetWidth(); +#endif } memDC.SelectObject(wxNullBitmap); return bitmap; @@ -175,7 +206,7 @@ wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap *beg #endif } -wxBitmap* BitmapCache::insert_raw_rgba(const std::string &bitmap_key, unsigned width, unsigned height, const unsigned char *raw_data, float scale /* = 1.0f */, const bool grayscale/* = false*/) +wxBitmap* BitmapCache::insert_raw_rgba(const std::string &bitmap_key, unsigned width, unsigned height, const unsigned char *raw_data, const bool grayscale/* = false*/) { wxImage image(width, height); image.InitAlpha(); @@ -192,7 +223,7 @@ wxBitmap* BitmapCache::insert_raw_rgba(const std::string &bitmap_key, unsigned w if (grayscale) image = image.ConvertToGreyscale(m_gs, m_gs, m_gs); - return this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image), scale)); + return this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image), m_scale)); } wxBitmap* BitmapCache::load_png(const std::string &bitmap_name, unsigned width, unsigned height, @@ -227,12 +258,12 @@ wxBitmap* BitmapCache::load_png(const std::string &bitmap_name, unsigned width, } wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned target_width, unsigned target_height, - float scale /* = 1.0f */, const bool grayscale/* = false*/, const bool dark_mode/* = false*/) + const bool grayscale/* = false*/, const bool dark_mode/* = false*/) { std::string bitmap_key = bitmap_name + ( target_height !=0 ? "-h" + std::to_string(target_height) : "-w" + std::to_string(target_width)) - + (scale != 1.0f ? "-s" + std::to_string(scale) : "") + + (m_scale != 1.0f ? "-s" + std::to_string(m_scale) : "") + (grayscale ? "-gs" : ""); /* For the Dark mode of any platform, we should draw icons in respect to OS background @@ -272,7 +303,7 @@ wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned target_ if (image == nullptr) return nullptr; - target_height != 0 ? target_height *= scale : target_width *= scale; + target_height != 0 ? target_height *= m_scale : target_width *= m_scale; float svg_scale = target_height != 0 ? (float)target_height / image->height : target_width != 0 ? @@ -297,11 +328,16 @@ wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned target_ ::nsvgDeleteRasterizer(rast); ::nsvgDelete(image); - return this->insert_raw_rgba(bitmap_key, width, height, data.data(), scale, grayscale); + return this->insert_raw_rgba(bitmap_key, width, height, data.data(), grayscale); } -wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency) +//we make scaled solid bitmaps only for the cases, when its will be used with scaled SVG icon in one output bitmap +wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency, bool suppress_scaling/* = false*/) { + double scale = suppress_scaling ? 1.0f : m_scale; + width *= scale; + height *= scale; + wxImage image(width, height); image.InitAlpha(); unsigned char* imgdata = image.GetData(); @@ -312,7 +348,32 @@ wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsi *imgdata ++ = b; *imgalpha ++ = transparency; } - return wxImage_to_wxBitmap_with_alpha(std::move(image)); + return wxImage_to_wxBitmap_with_alpha(std::move(image), scale); +} + + +static inline int hex_digit_to_int(const char c) +{ + return + (c >= '0' && c <= '9') ? int(c - '0') : + (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 : + (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1; +} + +bool BitmapCache::parse_color(const std::string& scolor, unsigned char* rgb_out) +{ + rgb_out[0] = rgb_out[1] = rgb_out[2] = 0; + if (scolor.size() != 7 || scolor.front() != '#') + return false; + const char* c = scolor.data() + 1; + for (size_t i = 0; i < 3; ++i) { + int digit1 = hex_digit_to_int(*c++); + int digit2 = hex_digit_to_int(*c++); + if (digit1 == -1 || digit2 == -1) + return false; + rgb_out[i] = (unsigned char)(digit1 * 16 + digit2); + } + return true; } } // namespace GUI diff --git a/src/slic3r/GUI/BitmapCache.hpp b/src/slic3r/GUI/BitmapCache.hpp index 041e7d8920..dd3e6ffc0d 100644 --- a/src/slic3r/GUI/BitmapCache.hpp +++ b/src/slic3r/GUI/BitmapCache.hpp @@ -1,24 +1,22 @@ #ifndef SLIC3R_GUI_BITMAP_CACHE_HPP #define SLIC3R_GUI_BITMAP_CACHE_HPP +#include + #include #ifndef WX_PRECOMP #include #endif -#include "libslic3r/libslic3r.h" -#include "libslic3r/Config.hpp" - -#include "GUI.hpp" - namespace Slic3r { namespace GUI { class BitmapCache { public: - BitmapCache() {} + BitmapCache(); ~BitmapCache() { clear(); } void clear(); + double scale() { return m_scale; } wxBitmap* find(const std::string &name) { auto it = m_map.find(name); return (it == m_map.end()) ? nullptr : it->second; } const wxBitmap* find(const std::string &name) const { return const_cast(this)->find(name); } @@ -29,20 +27,23 @@ public: wxBitmap* insert(const std::string &name, const wxBitmap &bmp, const wxBitmap &bmp2, const wxBitmap &bmp3); wxBitmap* insert(const std::string &name, const std::vector &bmps) { return this->insert(name, &bmps.front(), &bmps.front() + bmps.size()); } wxBitmap* insert(const std::string &name, const wxBitmap *begin, const wxBitmap *end); - wxBitmap* insert_raw_rgba(const std::string &bitmap_key, unsigned width, unsigned height, const unsigned char *raw_data, float scale = 1.0f, const bool grayscale = false); + wxBitmap* insert_raw_rgba(const std::string &bitmap_key, unsigned width, unsigned height, const unsigned char *raw_data, const bool grayscale = false); // Load png from resources/icons. bitmap_key is given without the .png suffix. Bitmap will be rescaled to provided height/width if nonzero. wxBitmap* load_png(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, const bool grayscale = false); // Load svg from resources/icons. bitmap_key is given without the .svg suffix. SVG will be rasterized to provided height/width. - wxBitmap* load_svg(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, float scale = 1.0f, const bool grayscale = false, const bool dark_mode = false); + wxBitmap* load_svg(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, const bool grayscale = false, const bool dark_mode = false); - static wxBitmap mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency); - static wxBitmap mksolid(size_t width, size_t height, const unsigned char rgb[3]) { return mksolid(width, height, rgb[0], rgb[1], rgb[2], wxALPHA_OPAQUE); } - static wxBitmap mkclear(size_t width, size_t height) { return mksolid(width, height, 0, 0, 0, wxALPHA_TRANSPARENT); } + /*static */wxBitmap mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency, bool suppress_scaling = false); + /*static */wxBitmap mksolid(size_t width, size_t height, const unsigned char rgb[3], bool suppress_scaling = false) { return mksolid(width, height, rgb[0], rgb[1], rgb[2], wxALPHA_OPAQUE); } + /*static */wxBitmap mkclear(size_t width, size_t height) { return mksolid(width, height, 0, 0, 0, wxALPHA_TRANSPARENT); } + + static bool parse_color(const std::string& scolor, unsigned char* rgb_out); private: std::map m_map; - double m_gs = 0.2; // value, used for image.ConvertToGreyscale(m_gs, m_gs, m_gs) + double m_gs = 0.2; // value, used for image.ConvertToGreyscale(m_gs, m_gs, m_gs) + double m_scale = 1.0; // value, used for correct scaling of SVG icons on Retina display }; } // GUI diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp index c21947d585..a4eccf050b 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -14,7 +14,9 @@ #include +#if !ENABLE_6DOF_CAMERA static const float GIMBALL_LOCK_THETA_MAX = 180.0f; +#endif // !ENABLE_6DOF_CAMERA // phi / theta angles to orient the camera. static const float VIEW_DEFAULT[2] = { 45.0f, 45.0f }; @@ -66,13 +68,10 @@ std::string Camera::get_type_as_string() const { switch (m_type) { - case Unknown: - return "unknown"; - case Perspective: - return "perspective"; + case Unknown: return "unknown"; + case Perspective: return "perspective"; default: - case Ortho: - return "orthographic"; + case Ortho: return "orthographic"; }; } @@ -88,10 +87,7 @@ void Camera::set_type(EType type) void Camera::set_type(const std::string& type) { - if (type == "1") - set_type(Perspective); - else - set_type(Ortho); + set_type((type == "1") ? Perspective : Ortho); } void Camera::select_next_type() @@ -157,17 +153,17 @@ void Camera::select_view(const std::string& direction) if (direction == "iso") set_default_orientation(); else if (direction == "left") - m_view_matrix = look_at(m_target - m_distance * Vec3d::UnitX(), m_target, Vec3d::UnitZ()); + look_at(m_target - m_distance * Vec3d::UnitX(), m_target, Vec3d::UnitZ()); else if (direction == "right") - m_view_matrix = look_at(m_target + m_distance * Vec3d::UnitX(), m_target, Vec3d::UnitZ()); + look_at(m_target + m_distance * Vec3d::UnitX(), m_target, Vec3d::UnitZ()); else if (direction == "top") - m_view_matrix = look_at(m_target + m_distance * Vec3d::UnitZ(), m_target, Vec3d::UnitY()); + look_at(m_target + m_distance * Vec3d::UnitZ(), m_target, Vec3d::UnitY()); else if (direction == "bottom") - m_view_matrix = look_at(m_target - m_distance * Vec3d::UnitZ(), m_target, -Vec3d::UnitY()); + look_at(m_target - m_distance * Vec3d::UnitZ(), m_target, -Vec3d::UnitY()); else if (direction == "front") - m_view_matrix = look_at(m_target - m_distance * Vec3d::UnitY(), m_target, Vec3d::UnitZ()); + look_at(m_target - m_distance * Vec3d::UnitY(), m_target, Vec3d::UnitZ()); else if (direction == "rear") - m_view_matrix = look_at(m_target + m_distance * Vec3d::UnitY(), m_target, Vec3d::UnitZ()); + look_at(m_target + m_distance * Vec3d::UnitY(), m_target, Vec3d::UnitZ()); } #else bool Camera::select_view(const std::string& direction) @@ -244,17 +240,27 @@ void Camera::apply_view_matrix() const void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double far_z) const { +#if !ENABLE_6DOF_CAMERA set_distance(DefaultDistance); +#endif // !ENABLE_6DOF_CAMERA double w = 0.0; double h = 0.0; +#if ENABLE_6DOF_CAMERA + double old_distance = m_distance; + m_frustrum_zs = calc_tight_frustrum_zs_around(box); + if (m_distance != old_distance) + // the camera has been moved re-apply view matrix + apply_view_matrix(); +#else while (true) { m_frustrum_zs = calc_tight_frustrum_zs_around(box); +#endif // !ENABLE_6DOF_CAMERA if (near_z > 0.0) - m_frustrum_zs.first = std::min(m_frustrum_zs.first, near_z); + m_frustrum_zs.first = std::max(std::min(m_frustrum_zs.first, near_z), FrustrumMinNearZ); if (far_z > 0.0) m_frustrum_zs.second = std::max(m_frustrum_zs.second, far_z); @@ -262,12 +268,9 @@ void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double fa w = 0.5 * (double)m_viewport[2]; h = 0.5 * (double)m_viewport[3]; - if (m_zoom != 0.0) - { - double inv_zoom = 1.0 / m_zoom; - w *= inv_zoom; - h *= inv_zoom; - } + double inv_zoom = get_inv_zoom(); + w *= inv_zoom; + h *= inv_zoom; switch (m_type) { @@ -288,6 +291,7 @@ void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double fa } } +#if !ENABLE_6DOF_CAMERA if (m_type == Perspective) { double fov_deg = Geometry::rad2deg(2.0 * std::atan(h / m_frustrum_zs.first)); @@ -307,6 +311,7 @@ void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double fa else break; } +#endif // !ENABLE_6DOF_CAMERA glsafe(::glMatrixMode(GL_PROJECTION)); glsafe(::glLoadIdentity()); @@ -331,14 +336,22 @@ void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double fa } #if ENABLE_THUMBNAIL_GENERATOR +#if ENABLE_6DOF_CAMERA +void Camera::zoom_to_box(const BoundingBoxf3& box, double margin_factor) +#else void Camera::zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h, double margin_factor) +#endif // ENABLE_6DOF_CAMERA #else void Camera::zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h) #endif // ENABLE_THUMBNAIL_GENERATOR { // Calculate the zoom factor needed to adjust the view around the given box. #if ENABLE_THUMBNAIL_GENERATOR +#if ENABLE_6DOF_CAMERA + double zoom = calc_zoom_to_bounding_box_factor(box, margin_factor); +#else double zoom = calc_zoom_to_bounding_box_factor(box, canvas_w, canvas_h, margin_factor); +#endif // ENABLE_6DOF_CAMERA #else double zoom = calc_zoom_to_bounding_box_factor(box, canvas_w, canvas_h); #endif // ENABLE_THUMBNAIL_GENERATOR @@ -355,10 +368,18 @@ void Camera::zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h) } #if ENABLE_THUMBNAIL_GENERATOR +#if ENABLE_6DOF_CAMERA +void Camera::zoom_to_volumes(const GLVolumePtrs& volumes, double margin_factor) +#else void Camera::zoom_to_volumes(const GLVolumePtrs& volumes, int canvas_w, int canvas_h, double margin_factor) +#endif // ENABLE_6DOF_CAMERA { Vec3d center; +#if ENABLE_6DOF_CAMERA + double zoom = calc_zoom_to_volumes_factor(volumes, center, margin_factor); +#else double zoom = calc_zoom_to_volumes_factor(volumes, canvas_w, canvas_h, center, margin_factor); +#endif // ENABLE_6DOF_CAMERA if (zoom > 0.0) { m_zoom = zoom; @@ -396,6 +417,7 @@ void Camera::debug_render() const float deltaZ = farZ - nearZ; float zoom = (float)m_zoom; float fov = (float)get_fov(); + std::arrayviewport = get_viewport(); float gui_scale = (float)get_gui_scale(); ImGui::InputText("Type", type.data(), type.length(), ImGuiInputTextFlags_ReadOnly); @@ -415,6 +437,8 @@ void Camera::debug_render() const ImGui::InputFloat("Zoom", &zoom, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); ImGui::InputFloat("Fov", &fov, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); ImGui::Separator(); + ImGui::InputInt4("Viewport", viewport.data(), ImGuiInputTextFlags_ReadOnly); + ImGui::Separator(); ImGui::InputFloat("GUI scale", &gui_scale, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); imgui.end(); } @@ -434,10 +458,31 @@ void Camera::translate_world(const Vec3d& displacement) void Camera::rotate_on_sphere(double delta_azimut_rad, double delta_zenit_rad) { + // FIXME -> The following is a HACK !!! + // When the value of the zenit rotation is large enough, the following call to rotate() shows + // numerical instability introducing some scaling into m_view_matrix (verified by checking + // that the camera space unit vectors are no more unit). + // See also https://dev.prusa3d.com/browse/SPE-1082 + // We split the zenit rotation into a set of smaller rotations which are then applied. + static const double MAX_ALLOWED = Geometry::deg2rad(0.1); + unsigned int zenit_steps_count = 1 + (unsigned int)(std::abs(delta_zenit_rad) / MAX_ALLOWED); + double zenit_step = delta_zenit_rad / (double)zenit_steps_count; + Vec3d target = m_target; translate_world(-target); - m_view_matrix.rotate(Eigen::AngleAxisd(delta_zenit_rad, get_dir_right())); - m_view_matrix.rotate(Eigen::AngleAxisd(delta_azimut_rad, Vec3d::UnitZ())); + + if (zenit_step != 0.0) + { + Vec3d right = get_dir_right(); + for (unsigned int i = 0; i < zenit_steps_count; ++i) + { + m_view_matrix.rotate(Eigen::AngleAxisd(zenit_step, right)); + } + } + + if (delta_azimut_rad != 0.0) + m_view_matrix.rotate(Eigen::AngleAxisd(delta_azimut_rad, Vec3d::UnitZ())); + translate_world(target); } @@ -457,49 +502,77 @@ void Camera::rotate_local_around_pivot(const Vec3d& rotation_rad, const Vec3d& p m_view_matrix.rotate(Eigen::AngleAxisd(rotation_rad(2), get_dir_forward())); translate_world(center); } +#endif // ENABLE_6DOF_CAMERA double Camera::min_zoom() const { - return 0.7 * calc_zoom_to_bounding_box_factor(m_scene_box, (int)m_viewport[2], (int)m_viewport[3]); -} +#if ENABLE_6DOF_CAMERA + return 0.7 * calc_zoom_to_bounding_box_factor(m_scene_box); +#else + return 0.7 * calc_zoom_to_bounding_box_factor(m_scene_box, m_viewport[2], m_viewport[3]); #endif // ENABLE_6DOF_CAMERA +} std::pair Camera::calc_tight_frustrum_zs_around(const BoundingBoxf3& box) const { std::pair ret; + auto& [near_z, far_z] = ret; +#if !ENABLE_6DOF_CAMERA while (true) { +#endif // !ENABLE_6DOF_CAMERA // box in eye space BoundingBoxf3 eye_box = box.transformed(m_view_matrix); - ret.first = -eye_box.max(2); - ret.second = -eye_box.min(2); + near_z = -eye_box.max(2); + far_z = -eye_box.min(2); // apply margin - ret.first -= FrustrumZMargin; - ret.second += FrustrumZMargin; + near_z -= FrustrumZMargin; + far_z += FrustrumZMargin; // ensure min size - if (ret.second - ret.first < FrustrumMinZRange) + if (far_z - near_z < FrustrumMinZRange) { - double mid_z = 0.5 * (ret.first + ret.second); + double mid_z = 0.5 * (near_z + far_z); double half_size = 0.5 * FrustrumMinZRange; - ret.first = mid_z - half_size; - ret.second = mid_z + half_size; + near_z = mid_z - half_size; + far_z = mid_z + half_size; } - if (ret.first >= FrustrumMinNearZ) +#if ENABLE_6DOF_CAMERA + if (near_z < FrustrumMinNearZ) + { + float delta = FrustrumMinNearZ - near_z; + set_distance(m_distance + delta); + near_z += delta; + far_z += delta; + } + else if ((near_z > 2.0 * FrustrumMinNearZ) && (m_distance > DefaultDistance)) + { + float delta = m_distance - DefaultDistance; + set_distance(DefaultDistance); + near_z -= delta; + far_z -= delta; + } +#else + if (near_z >= FrustrumMinNearZ) break; - // ensure min Near Z - set_distance(m_distance + FrustrumMinNearZ - ret.first); + // ensure min near z + set_distance(m_distance + FrustrumMinNearZ - near_z); } +#endif // ENABLE_6DOF_CAMERA return ret; } #if ENABLE_THUMBNAIL_GENERATOR +#if ENABLE_6DOF_CAMERA +double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, double margin_factor) const +#else double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h, double margin_factor) const +#endif // ENABLE_6DOF_CAMERA #else double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h) const #endif // ENABLE_THUMBNAIL_GENERATOR @@ -511,8 +584,10 @@ double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int ca // project the box vertices on a plane perpendicular to the camera forward axis // then calculates the vertices coordinate on this plane along the camera xy axes +#if !ENABLE_6DOF_CAMERA // ensure that the view matrix is updated apply_view_matrix(); +#endif // !ENABLE_6DOF_CAMERA Vec3d right = get_dir_right(); Vec3d up = get_dir_up(); @@ -569,11 +644,19 @@ double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int ca dx *= margin_factor; dy *= margin_factor; +#if ENABLE_6DOF_CAMERA + return std::min((double)m_viewport[2] / dx, (double)m_viewport[3] / dy); +#else return std::min((double)canvas_w / dx, (double)canvas_h / dy); +#endif // ENABLE_6DOF_CAMERA } #if ENABLE_THUMBNAIL_GENERATOR +#if ENABLE_6DOF_CAMERA +double Camera::calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, Vec3d& center, double margin_factor) const +#else double Camera::calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, int canvas_w, int canvas_h, Vec3d& center, double margin_factor) const +#endif // ENABLE_6DOF_CAMERA { if (volumes.empty()) return -1.0; @@ -581,8 +664,10 @@ double Camera::calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, int canv // project the volumes vertices on a plane perpendicular to the camera forward axis // then calculates the vertices coordinate on this plane along the camera xy axes +#if !ENABLE_6DOF_CAMERA // ensure that the view matrix is updated apply_view_matrix(); +#endif // !ENABLE_6DOF_CAMERA Vec3d right = get_dir_right(); Vec3d up = get_dir_up(); @@ -634,46 +719,57 @@ double Camera::calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, int canv if ((dx <= 0.0) || (dy <= 0.0)) return -1.0f; +#if ENABLE_6DOF_CAMERA + return std::min((double)m_viewport[2] / dx, (double)m_viewport[3] / dy); +#else return std::min((double)canvas_w / dx, (double)canvas_h / dy); +#endif // ENABLE_6DOF_CAMERA } #endif // ENABLE_THUMBNAIL_GENERATOR void Camera::set_distance(double distance) const { +#if ENABLE_6DOF_CAMERA + if (m_distance != distance) + { + m_view_matrix.translate((distance - m_distance) * get_dir_forward()); + m_distance = distance; + } +#else m_distance = distance; apply_view_matrix(); +#endif // ENABLE_6DOF_CAMERA } #if ENABLE_6DOF_CAMERA -Transform3d Camera::look_at(const Vec3d& position, const Vec3d& target, const Vec3d& up) const +void Camera::look_at(const Vec3d& position, const Vec3d& target, const Vec3d& up) { Vec3d unit_z = (position - target).normalized(); Vec3d unit_x = up.cross(unit_z).normalized(); Vec3d unit_y = unit_z.cross(unit_x).normalized(); - Transform3d matrix; + m_target = target; + Vec3d new_position = m_target + m_distance * unit_z; - matrix(0, 0) = unit_x(0); - matrix(0, 1) = unit_x(1); - matrix(0, 2) = unit_x(2); - matrix(0, 3) = -unit_x.dot(position); + m_view_matrix(0, 0) = unit_x(0); + m_view_matrix(0, 1) = unit_x(1); + m_view_matrix(0, 2) = unit_x(2); + m_view_matrix(0, 3) = -unit_x.dot(new_position); - matrix(1, 0) = unit_y(0); - matrix(1, 1) = unit_y(1); - matrix(1, 2) = unit_y(2); - matrix(1, 3) = -unit_y.dot(position); + m_view_matrix(1, 0) = unit_y(0); + m_view_matrix(1, 1) = unit_y(1); + m_view_matrix(1, 2) = unit_y(2); + m_view_matrix(1, 3) = -unit_y.dot(new_position); - matrix(2, 0) = unit_z(0); - matrix(2, 1) = unit_z(1); - matrix(2, 2) = unit_z(2); - matrix(2, 3) = -unit_z.dot(position); + m_view_matrix(2, 0) = unit_z(0); + m_view_matrix(2, 1) = unit_z(1); + m_view_matrix(2, 2) = unit_z(2); + m_view_matrix(2, 3) = -unit_z.dot(new_position); - matrix(3, 0) = 0.0; - matrix(3, 1) = 0.0; - matrix(3, 2) = 0.0; - matrix(3, 3) = 1.0; - - return matrix; + m_view_matrix(3, 0) = 0.0; + m_view_matrix(3, 1) = 0.0; + m_view_matrix(3, 2) = 0.0; + m_view_matrix(3, 3) = 1.0; } void Camera::set_default_orientation() diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp index 2a43361089..b0a646ebfe 100644 --- a/src/slic3r/GUI/Camera.hpp +++ b/src/slic3r/GUI/Camera.hpp @@ -48,11 +48,7 @@ private: mutable double m_gui_scale; mutable std::array m_viewport; -#if ENABLE_6DOF_CAMERA - Transform3d m_view_matrix; -#else mutable Transform3d m_view_matrix; -#endif // ENABLE_6DOF_CAMERA mutable Transform3d m_projection_matrix; mutable std::pair m_frustrum_zs; @@ -71,7 +67,11 @@ public: const Vec3d& get_target() const { return m_target; } void set_target(const Vec3d& target); +#if ENABLE_6DOF_CAMERA + double get_distance() const { return (get_position() - m_target).norm(); } +#else double get_distance() const { return m_distance; } +#endif // ENABLE_6DOF_CAMERA double get_gui_scale() const { return m_gui_scale; } #if !ENABLE_6DOF_CAMERA @@ -115,8 +115,13 @@ public: void apply_projection(const BoundingBoxf3& box, double near_z = -1.0, double far_z = -1.0) const; #if ENABLE_THUMBNAIL_GENERATOR +#if ENABLE_6DOF_CAMERA + void zoom_to_box(const BoundingBoxf3& box, double margin_factor = DefaultZoomToBoxMarginFactor); + void zoom_to_volumes(const GLVolumePtrs& volumes, double margin_factor = DefaultZoomToVolumesMarginFactor); +#else void zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h, double margin_factor = DefaultZoomToBoxMarginFactor); void zoom_to_volumes(const GLVolumePtrs& volumes, int canvas_w, int canvas_h, double margin_factor = DefaultZoomToVolumesMarginFactor); +#endif // ENABLE_6DOF_CAMERA #else void zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h); #endif // ENABLE_THUMBNAIL_GENERATOR @@ -141,25 +146,29 @@ public: // returns true if the camera z axis (forward) is pointing in the negative direction of the world z axis bool is_looking_downward() const { return get_dir_forward().dot(Vec3d::UnitZ()) < 0.0; } - +#endif // ENABLE_6DOF_CAMERA double max_zoom() const { return 100.0; } double min_zoom() const; -#endif // ENABLE_6DOF_CAMERA private: // returns tight values for nearZ and farZ plane around the given bounding box // the camera MUST be outside of the bounding box in eye coordinate of the given box std::pair calc_tight_frustrum_zs_around(const BoundingBoxf3& box) const; #if ENABLE_THUMBNAIL_GENERATOR +#if ENABLE_6DOF_CAMERA + double calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, double margin_factor = DefaultZoomToBoxMarginFactor) const; + double calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, Vec3d& center, double margin_factor = DefaultZoomToVolumesMarginFactor) const; +#else double calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h, double margin_factor = DefaultZoomToBoxMarginFactor) const; double calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, int canvas_w, int canvas_h, Vec3d& center, double margin_factor = DefaultZoomToVolumesMarginFactor) const; +#endif // ENABLE_6DOF_CAMERA #else double calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h) const; #endif // ENABLE_THUMBNAIL_GENERATOR void set_distance(double distance) const; #if ENABLE_6DOF_CAMERA - Transform3d look_at(const Vec3d& position, const Vec3d& target, const Vec3d& up) const; + void look_at(const Vec3d& position, const Vec3d& target, const Vec3d& up); void set_default_orientation(); Vec3d validate_target(const Vec3d& target) const; #endif // ENABLE_6DOF_CAMERA diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index a47d6706c2..4a5bea9a10 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -52,28 +52,26 @@ Control::Control( wxWindow *parent, if (!is_osx) SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX - const float scale_factor = get_svg_scale_factor(this); - m_bmp_thumb_higher = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "right_half_circle.png") : ScalableBitmap(this, "thumb_up")); m_bmp_thumb_lower = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "left_half_circle.png" ) : ScalableBitmap(this, "thumb_down")); - m_thumb_size = m_bmp_thumb_lower.bmp().GetSize()*(1.0/scale_factor); + m_thumb_size = m_bmp_thumb_lower.GetBmpSize(); m_bmp_add_tick_on = ScalableBitmap(this, "colorchange_add"); m_bmp_add_tick_off = ScalableBitmap(this, "colorchange_add_f"); m_bmp_del_tick_on = ScalableBitmap(this, "colorchange_del"); m_bmp_del_tick_off = ScalableBitmap(this, "colorchange_del_f"); - m_tick_icon_dim = int((float)m_bmp_add_tick_on.bmp().GetSize().x / scale_factor); + m_tick_icon_dim = m_bmp_add_tick_on.GetBmpWidth(); m_bmp_one_layer_lock_on = ScalableBitmap(this, "lock_closed"); m_bmp_one_layer_lock_off = ScalableBitmap(this, "lock_closed_f"); m_bmp_one_layer_unlock_on = ScalableBitmap(this, "lock_open"); m_bmp_one_layer_unlock_off = ScalableBitmap(this, "lock_open_f"); - m_lock_icon_dim = int((float)m_bmp_one_layer_lock_on.bmp().GetSize().x / scale_factor); + m_lock_icon_dim = m_bmp_one_layer_lock_on.GetBmpWidth(); m_bmp_revert = ScalableBitmap(this, "undo"); - m_revert_icon_dim = int((float)m_bmp_revert.bmp().GetSize().x / scale_factor); + m_revert_icon_dim = m_bmp_revert.GetBmpWidth(); m_bmp_cog = ScalableBitmap(this, "cog"); - m_cog_icon_dim = int((float)m_bmp_cog.bmp().GetSize().x / scale_factor); + m_cog_icon_dim = m_bmp_cog.GetBmpWidth(); m_selection = ssUndef; m_ticks.set_pause_print_msg(_utf8(L("Place bearings in slots and resume"))); @@ -554,16 +552,9 @@ void Control::draw_ticks(wxDC& dc) // Draw icon for "Pause print" or "Custom Gcode" if (tick.gcode != ColorChangeCode && tick.gcode != ToolChangeCode) - icon = create_scaled_bitmap(this, tick.gcode == PausePrintCode ? "pause_print" : "edit_gcode"); - else - { - if ((tick.gcode == ColorChangeCode && ( - (m_ticks.mode == t_mode::SingleExtruder && m_mode == t_mode::MultiExtruder ) || - (m_ticks.mode == t_mode::MultiExtruder && m_mode == t_mode::SingleExtruder) )) || - (tick.gcode == ToolChangeCode && - (m_ticks.mode == t_mode::MultiAsSingle && m_mode != t_mode::MultiAsSingle ) )) - icon = create_scaled_bitmap(this, "error_tick"); - } + icon = create_scaled_bitmap(tick.gcode == PausePrintCode ? "pause_print" : "edit_gcode"); + else if (m_ticks.is_conflict_tick(tick, m_mode, m_only_extruder, m_values[tick.tick])) + icon = create_scaled_bitmap("error_tick"); if (!icon.IsNull()) { @@ -753,7 +744,7 @@ bool Control::is_point_in_rect(const wxPoint& pt, const wxRect& rect) rect.GetTop() <= pt.y && pt.y <= rect.GetBottom(); } -int Control::is_point_near_tick(const wxPoint& pt) +int Control::get_tick_near_point(const wxPoint& pt) { for (auto tick : m_ticks.ticks) { const wxCoord pos = get_position_from_value(tick.tick); @@ -833,7 +824,7 @@ void Control::OnLeftDown(wxMouseEvent& event) detect_selected_slider(pos); if (!m_selection) { - const int tick_val = is_point_near_tick(pos); + const int tick_val = get_tick_near_point(pos); /* Set current thumb position to the nearest tick (if it is) * OR to a value corresponding to the mouse click * */ @@ -896,20 +887,70 @@ wxString Control::get_tooltip(IconFocus icon_focus) { const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; const auto tick_code_it = m_ticks.ticks.find(TickCode{tick}); - tooltip = tick_code_it == m_ticks.ticks.end() ? (m_mode == t_mode::MultiAsSingle ? - _(L("For add change extruder use left mouse button click")) : - _(L("For add color change use left mouse button click")) ) + "\n" + - _(L("For add another code use right mouse button click")) : - tick_code_it->gcode == ColorChangeCode ? ( m_mode == t_mode::SingleExtruder ? - _(L("For Delete color change use left mouse button click\n" - "For Edit color use right mouse button click")) : - from_u8((boost::format(_utf8(L("Delete color change for Extruder %1%"))) % tick_code_it->extruder).str()) ): - tick_code_it->gcode == PausePrintCode ? - _(L("Delete pause")) : - tick_code_it->gcode == ToolChangeCode ? - from_u8((boost::format(_utf8(L("Delete extruder change to \"%1%\""))) % tick_code_it->extruder).str()) : - from_u8((boost::format(_utf8(L("For Delete \"%1%\" code use left mouse button click\n" - "For Edit \"%1%\" code use right mouse button click"))) % tick_code_it->gcode ).str()); + + /* Note: just on OSX!!! + * Right click event causes a little scrolling. + * So, as a workaround we use Ctrl+LeftMouseClick instead of RightMouseClick + * Show this information in tooltip + * */ + + if (tick_code_it == m_ticks.ticks.end()) // tick doesn't exist + { + // Show mode as a first string of tooltop + tooltip = " " + _(L("Slider(print) mode")) + ": "; + tooltip += (m_mode == t_mode::SingleExtruder ? CustomGCode::SingleExtruderMode : + m_mode == t_mode::MultiAsSingle ? CustomGCode::MultiAsSingleMode : + CustomGCode::MultiExtruderMode ); + tooltip += "\n\n"; + + // Show list of actions with new tick + tooltip += ( m_mode == t_mode::MultiAsSingle ? + _(L("For add change extruder use left mouse button click")) : + _(L("For add color change use left mouse button click")) ) + " " + + _(L("OR pres \"+\" key")) + "\n" + ( + is_osx ? + _(L("For add another code use Ctrl + Left mouse button click")) : + _(L("For add another code use right mouse button click")) ); + } + else // tick exists + { + // Show custom Gcode as a first string of tooltop + tooltip = " "; + tooltip += tick_code_it->gcode == ColorChangeCode ? ( + m_mode == t_mode::SingleExtruder ? + from_u8((boost::format(_utf8(L("Color change (\"%1%\")"))) % tick_code_it->gcode ).str()) : + from_u8((boost::format(_utf8(L("Color change (\"%1%\") for Extruder %2%"))) % + tick_code_it->gcode % tick_code_it->extruder).str()) ) : + tick_code_it->gcode == PausePrintCode ? + from_u8((boost::format(_utf8(L("Pause print (\"%1%\")"))) % tick_code_it->gcode ).str()) : + tick_code_it->gcode == ToolChangeCode ? + from_u8((boost::format(_utf8(L("Extruder(tool) is changed to Extruder \"%1%\""))) % tick_code_it->extruder ).str()) : + from_u8((boost::format(_utf8(L("\"%1%\""))) % tick_code_it->gcode ).str()) ; + + // If tick is marked as a conflict (exclamation icon), + // we should to explain why + ConflictType conflict = m_ticks.is_conflict_tick(*tick_code_it, m_mode, m_only_extruder, m_values[tick]); + if (conflict != ctNone) + tooltip += "\n\n" + _(L("Note")) + "! "; + if (conflict == ctModeConflict) + tooltip += _(L("G-code of this tick has a conflict with slider(print) mode.\n" + "Any its editing will cause a changes of DoubleSlider data.")); + else if (conflict == ctMeaninglessColorChange) + tooltip += _(L("There is a color change for extruder that wouldn't be used till the end of printing.\n" + "This code wouldn't be processed during GCode generation.")); + else if (conflict == ctMeaninglessToolChange) + tooltip += _(L("There is a extruder change to the same extruder.\n" + "This code wouldn't be processed during GCode generation.")); + else if (conflict == ctRedundant) + tooltip += _(L("There is a color change for extruder that has not been used before.\n" + "Check your choice to avoid redundant color changes.")); + + // Show list of actions with existing tick + tooltip += "\n\n" + _(L("For Delete tick use left mouse button click OR pres \"-\" key")) + "\n" + ( + is_osx ? + _(L("For Edit tick use Ctrl + Left mouse button click")) : + _(L("For Edit tick use right mouse button click")) ); + } } return tooltip; @@ -988,7 +1029,7 @@ void Control::append_change_extruder_menu_item(wxMenu* menu, bool switch_current _(L("Change extruder (N/A)")); wxMenuItem* change_extruder_menu_item = menu->AppendSubMenu(change_extruder_menu, change_extruder_menu_name, _(L("Use another extruder"))); - change_extruder_menu_item->SetBitmap(create_scaled_bitmap(this, active_extruders[1] > 0 ? "edit_uni" : "change_extruder")); + change_extruder_menu_item->SetBitmap(create_scaled_bitmap(active_extruders[1] > 0 ? "edit_uni" : "change_extruder")); GUI::wxGetApp().plater()->Bind(wxEVT_UPDATE_UI, [this, change_extruder_menu_item](wxUpdateUIEvent& evt) { enable_menu_item(evt, [this]() {return m_mode == t_mode::MultiAsSingle; }, change_extruder_menu_item, this); }, @@ -1001,7 +1042,8 @@ void Control::append_add_color_change_menu_item(wxMenu* menu, bool switch_curren const int extruders_cnt = GUI::wxGetApp().extruders_edited_cnt(); if (extruders_cnt > 1) { - std::set used_extruders_for_tick = get_used_extruders_for_tick(m_selection == ssLower ? m_lower_value : m_higher_value); + int tick = m_selection == ssLower ? m_lower_value : m_higher_value; + std::set used_extruders_for_tick = m_ticks.get_used_extruders_for_tick(tick, m_only_extruder, m_values[tick]); wxMenu* add_color_change_menu = new wxMenu(); @@ -1014,14 +1056,14 @@ void Control::append_add_color_change_menu_item(wxMenu* menu, bool switch_curren append_menu_item(add_color_change_menu, wxID_ANY, item_name, "", [this, i](wxCommandEvent&) { add_code_as_tick(ColorChangeCode, i); }, "", menu, - [is_used_extruder]() { return is_used_extruder; }, GUI::wxGetApp().plater()); + []() { return true; }, GUI::wxGetApp().plater()); } const wxString menu_name = switch_current_code ? from_u8((boost::format(_utf8(L("Switch code to Color change (%1%) for:"))) % ColorChangeCode).str()) : from_u8((boost::format(_utf8(L("Add color change (%1%) for:"))) % ColorChangeCode).str()); wxMenuItem* add_color_change_menu_item = menu->AppendSubMenu(add_color_change_menu, menu_name, ""); - add_color_change_menu_item->SetBitmap(create_scaled_bitmap(this, "colorchange_add_m")); + add_color_change_menu_item->SetBitmap(create_scaled_bitmap("colorchange_add_m")); } } @@ -1220,7 +1262,7 @@ std::array Control::get_active_extruders_for_tick(int tick) const auto it = m_ticks.ticks.lower_bound(TickCode{tick}); - if (it->tick == tick) // current tick exists + if (it != m_ticks.ticks.end() && it->tick == tick) // current tick exists extruders[1] = it->extruder; while (it != m_ticks.ticks.begin()) { @@ -1235,10 +1277,10 @@ std::array Control::get_active_extruders_for_tick(int tick) const } // Get used extruders for tick. -// Means all extruders(toools) will be used during printing from current tick to the end -std::set Control::get_used_extruders_for_tick(int tick) const +// Means all extruders(tools) which will be used during printing from current tick to the end +std::set TickCodeInfo::get_used_extruders_for_tick(int tick, int only_extruder, double print_z) const { - if (m_mode == t_mode::MultiExtruder) + if (mode == t_mode::MultiExtruder) { // #ys_FIXME: get tool ordering from _correct_ place const ToolOrdering& tool_ordering = GUI::wxGetApp().plater()->fff_print().get_tool_ordering(); @@ -1248,7 +1290,7 @@ std::set Control::get_used_extruders_for_tick(int tick) const std::set used_extruders; - auto it_layer_tools = std::lower_bound(tool_ordering.begin(), tool_ordering.end(), LayerTools(m_values[tick])); + auto it_layer_tools = std::lower_bound(tool_ordering.begin(), tool_ordering.end(), LayerTools(print_z)); for (; it_layer_tools != tool_ordering.end(); ++it_layer_tools) { const std::vector& extruders = it_layer_tools->extruders; @@ -1259,12 +1301,11 @@ std::set Control::get_used_extruders_for_tick(int tick) const return used_extruders; } - const int default_initial_extruder = m_mode == t_mode::MultiAsSingle ? std::max(m_only_extruder, 1) : 1; - if (m_ticks.empty()) + const int default_initial_extruder = mode == t_mode::MultiAsSingle ? std::max(only_extruder, 1) : 1; + if (ticks.empty()) return {default_initial_extruder}; std::set used_extruders; - const std::set& ticks = m_ticks.ticks; auto it_start = ticks.lower_bound(TickCode{tick}); auto it = it_start; @@ -1341,7 +1382,7 @@ void Control::OnRightUp(wxMouseEvent& event) append_menu_item(&menu, wxID_ANY, it->gcode == ColorChangeCode ? _(L("Edit color")) : it->gcode == PausePrintCode ? _(L("Edit pause print message")) : _(L("Edit custom G-code")), "", - [this](wxCommandEvent&) { edit_tick(); }, "edit_uni", &menu); + [this](wxCommandEvent&) { edit_tick(); }, "edit_uni", &menu, []() {return true; }, this); if (it->gcode == ColorChangeCode && m_mode == t_mode::MultiAsSingle) append_change_extruder_menu_item(&menu, true); @@ -1350,7 +1391,7 @@ void Control::OnRightUp(wxMouseEvent& event) it->gcode == ToolChangeCode ? _(L("Delete tool change")) : it->gcode == PausePrintCode ? _(L("Delete pause print")) : _(L("Delete custom G-code")), "", - [this](wxCommandEvent&) { delete_current_tick();}, "colorchange_del_f", &menu); + [this](wxCommandEvent&) { delete_current_tick();}, "colorchange_del_f", &menu, []() {return true; }, this); GUI::wxGetApp().plater()->PopupMenu(&menu); @@ -1379,6 +1420,28 @@ static std::string get_new_color(const std::string& color) return ""; } +// To avoid get an empty string from wxTextEntryDialog +// Let disable OK button, if TextCtrl is empty +static void upgrade_text_entry_dialog(wxTextEntryDialog* dlg) +{ + // detect TextCtrl and OK button + wxTextCtrl* textctrl {nullptr}; + wxWindowList& dlg_items = dlg->GetChildren(); + for (auto item : dlg_items) { + textctrl = dynamic_cast(item); + if (textctrl) + break; + } + + if (!textctrl) + return; + + wxButton* btn_OK = static_cast(dlg->FindWindowById(wxID_OK)); + btn_OK->Bind(wxEVT_UPDATE_UI, [textctrl](wxUpdateUIEvent& evt) { + evt.Enable(!textctrl->IsEmpty()); + }, btn_OK->GetId()); +} + static std::string get_custom_code(const std::string& code_in, double height) { wxString msg_text = from_u8(_utf8(L("Enter custom G-code used on current layer"))) + ":"; @@ -1387,7 +1450,9 @@ static std::string get_custom_code(const std::string& code_in, double height) // get custom gcode wxTextEntryDialog dlg(nullptr, msg_text, msg_header, code_in, wxTextEntryDialogStyle | wxTE_MULTILINE); - if (dlg.ShowModal() != wxID_OK || dlg.GetValue().IsEmpty()) + upgrade_text_entry_dialog(&dlg); + + if (dlg.ShowModal() != wxID_OK) return ""; return dlg.GetValue().ToStdString(); @@ -1401,6 +1466,8 @@ static std::string get_pause_print_msg(const std::string& msg_in, double height) // get custom gcode wxTextEntryDialog dlg(nullptr, msg_text, msg_header, from_u8(msg_in), wxTextEntryDialogStyle); + upgrade_text_entry_dialog(&dlg); + if (dlg.ShowModal() != wxID_OK || dlg.GetValue().IsEmpty()) return ""; @@ -1759,6 +1826,61 @@ bool TickCodeInfo::has_tick_with_code(const std::string& gcode) return false; } +ConflictType TickCodeInfo::is_conflict_tick(const TickCode& tick, t_mode out_mode, int only_extruder, double print_z) +{ + if ((tick.gcode == ColorChangeCode && ( + (mode == t_mode::SingleExtruder && out_mode == t_mode::MultiExtruder ) || + (mode == t_mode::MultiExtruder && out_mode == t_mode::SingleExtruder) )) || + (tick.gcode == ToolChangeCode && + (mode == t_mode::MultiAsSingle && out_mode != t_mode::MultiAsSingle)) ) + return ctModeConflict; + + // check ColorChange tick + if (tick.gcode == ColorChangeCode) + { + // We should mark a tick as a "MeaninglessColorChange", + // if it has a ColorChange for unused extruder from current print to end of the print + std::set used_extruders_for_tick = get_used_extruders_for_tick(tick.tick, only_extruder, print_z); + + if (used_extruders_for_tick.find(tick.extruder) == used_extruders_for_tick.end()) + return ctMeaninglessColorChange; + + // We should mark a tick as a "Redundant", + // if it has a ColorChange for extruder that has not been used before + if (mode == t_mode::MultiAsSingle && tick.extruder != std::max(only_extruder, 1) ) + { + auto it = ticks.lower_bound( tick ); + if (it == ticks.begin() && it->gcode == ToolChangeCode && tick.extruder == it->extruder) + return ctNone; + + while (it != ticks.begin()) { + --it; + if (it->gcode == ToolChangeCode && tick.extruder == it->extruder) + return ctNone; + } + + return ctRedundant; + } + } + + // check ToolChange tick + if (mode == t_mode::MultiAsSingle && tick.gcode == ToolChangeCode) + { + // We should mark a tick as a "MeaninglessToolChange", + // if it has a ToolChange to the same extruder + + auto it = ticks.find(tick); + if (it == ticks.begin()) + return tick.extruder == std::max(only_extruder, 1) ? ctMeaninglessToolChange : ctNone; + + --it; + if (it->gcode == ToolChangeCode && tick.extruder == it->extruder) + return ctMeaninglessToolChange; + } + + return ctNone; +} + } // DoubleSlider } // Slic3r diff --git a/src/slic3r/GUI/DoubleSlider.hpp b/src/slic3r/GUI/DoubleSlider.hpp index 02a98ebb21..f14af621f9 100644 --- a/src/slic3r/GUI/DoubleSlider.hpp +++ b/src/slic3r/GUI/DoubleSlider.hpp @@ -39,6 +39,15 @@ enum IconFocus { ifCog }; +enum ConflictType +{ + ctNone, + ctModeConflict, + ctMeaninglessColorChange, + ctMeaninglessToolChange, + ctRedundant +}; + using t_mode = CustomGCode::Mode; struct TickCode @@ -73,7 +82,13 @@ public: void switch_code(const std::string& code_from, const std::string& code_to); bool switch_code_for_tick(std::set::iterator it, const std::string& code_to, const int extruder); void erase_all_ticks_with_code(const std::string& gcode); - bool has_tick_with_code(const std::string& gcode); + + bool has_tick_with_code(const std::string& gcode); + ConflictType is_conflict_tick(const TickCode& tick, t_mode out_mode, int only_extruder, double print_z); + + // Get used extruders for tick. + // Means all extruders(tools) which will be used during printing from current tick to the end + std::set get_used_extruders_for_tick(int tick, int only_extruder, double print_z) const; void suppress_plus (bool suppress) { m_suppress_plus = suppress; } void suppress_minus(bool suppress) { m_suppress_minus = suppress; } @@ -230,7 +245,7 @@ protected: private: bool is_point_in_rect(const wxPoint& pt, const wxRect& rect); - int is_point_near_tick(const wxPoint& pt); + int get_tick_near_point(const wxPoint& pt); double get_scroll_step(); wxString get_label(const SelectedSlider& selection) const; @@ -251,10 +266,6 @@ private: // Use those values to disable selection of active extruders std::array get_active_extruders_for_tick(int tick) const; - // Get used extruders for tick. - // Means all extruders(toools) will be used during printing from current tick to the end - std::set get_used_extruders_for_tick(int tick) const; - void post_ticks_changed_event(const std::string& gcode = ""); bool check_ticks_changed_event(const std::string& gcode); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index e0c7a77a0d..1e90b85c5d 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1256,6 +1256,7 @@ wxDEFINE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, Event); wxDEFINE_EVENT(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, HeightProfileSmoothEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_RELOAD_FROM_DISK, SimpleEvent); #if ENABLE_THUMBNAIL_GENERATOR const double GLCanvas3D::DefaultCameraZoomToBoxMarginFactor = 1.25; @@ -1710,6 +1711,13 @@ void GLCanvas3D::render() } const Size& cnv_size = get_canvas_size(); +#if ENABLE_6DOF_CAMERA + // Probably due to different order of events on Linux/GTK2, when one switched from 3D scene + // to preview, this was called before canvas had its final size. It reported zero width + // and the viewport was set incorrectly, leading to tripping glAsserts further down + // the road (in apply_projection). That's why the minimum size is forced to 10. + m_camera.apply_viewport(0, 0, std::max(10u, (unsigned int)cnv_size.get_width()), std::max(10u, (unsigned int)cnv_size.get_height())); +#endif // ENABLE_6DOF_CAMERA if (m_camera.requires_zoom_to_bed) { @@ -2647,6 +2655,7 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE)); break; case WXK_ESCAPE: { deselect_all(); break; } + case WXK_F5: { post_event(SimpleEvent(EVT_GLCANVAS_RELOAD_FROM_DISK)); break; } case '0': { select_view("iso"); break; } case '1': { select_view("top"); break; } case '2': { select_view("bottom"); break; } @@ -3839,8 +3848,13 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool #if ENABLE_6DOF_CAMERA camera.set_scene_box(scene_bounding_box()); #endif // ENABLE_6DOF_CAMERA +#if ENABLE_6DOF_CAMERA + camera.apply_viewport(0, 0, thumbnail_data.width, thumbnail_data.height); + camera.zoom_to_volumes(visible_volumes); +#else camera.zoom_to_volumes(visible_volumes, thumbnail_data.width, thumbnail_data.height); camera.apply_viewport(0, 0, thumbnail_data.width, thumbnail_data.height); +#endif // ENABLE_6DOF_CAMERA camera.apply_view_matrix(); double near_z = -1.0; @@ -4431,8 +4445,10 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h) // ensures that this canvas is current _set_current(); +#if !ENABLE_6DOF_CAMERA // updates camera m_camera.apply_viewport(0, 0, w, h); +#endif // !ENABLE_6DOF_CAMERA } BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_bed_model) const @@ -4456,8 +4472,12 @@ BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_be #if ENABLE_THUMBNAIL_GENERATOR void GLCanvas3D::_zoom_to_box(const BoundingBoxf3& box, double margin_factor) { +#if ENABLE_6DOF_CAMERA + m_camera.zoom_to_box(box, margin_factor); +#else const Size& cnv_size = get_canvas_size(); m_camera.zoom_to_box(box, cnv_size.get_width(), cnv_size.get_height(), margin_factor); +#endif // ENABLE_6DOF_CAMERA m_dirty = true; } #else diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index a7f272f975..a0e7958897 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -108,6 +108,7 @@ wxDECLARE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, Event); wxDECLARE_EVENT(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, HeightProfileSmoothEvent); +wxDECLARE_EVENT(EVT_GLCANVAS_RELOAD_FROM_DISK, SimpleEvent); class GLCanvas3D { diff --git a/src/slic3r/GUI/GLCanvas3DManager.cpp b/src/slic3r/GUI/GLCanvas3DManager.cpp index 3594e85a42..a5d75d6012 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.cpp +++ b/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 9574cdb8e1..c6f5cfb47f 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -960,8 +960,11 @@ void GUI_App::load_current_presets() this->plater()->set_printer_technology(printer_technology); for (Tab *tab : tabs_list) if (tab->supports_printer_technology(printer_technology)) { - if (tab->type() == Preset::TYPE_PRINTER) + if (tab->type() == Preset::TYPE_PRINTER) { static_cast(tab)->update_pages(); + // Mark the plater to update print bed by tab->load_current_preset() from Plater::on_config_change(). + this->plater()->force_print_bed_update(); + } tab->load_current_preset(); } } diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index b4f20524ef..33ddd475cd 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -90,20 +90,20 @@ ObjectList::ObjectList(wxWindow* parent) : // see note in PresetBundle::load_compatible_bitmaps() // ptFFF - CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap(nullptr, "layers"); - CATEGORY_ICON[L("Infill")] = create_scaled_bitmap(nullptr, "infill"); - CATEGORY_ICON[L("Support material")] = create_scaled_bitmap(nullptr, "support"); - CATEGORY_ICON[L("Speed")] = create_scaled_bitmap(nullptr, "time"); - CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap(nullptr, "funnel"); - CATEGORY_ICON[L("Extrusion Width")] = create_scaled_bitmap(nullptr, "funnel"); - CATEGORY_ICON[L("Wipe options")] = create_scaled_bitmap(nullptr, "funnel"); -// CATEGORY_ICON[L("Skirt and brim")] = create_scaled_bitmap(nullptr, "skirt+brim"); -// CATEGORY_ICON[L("Speed > Acceleration")] = create_scaled_bitmap(nullptr, "time"); - CATEGORY_ICON[L("Advanced")] = create_scaled_bitmap(nullptr, "wrench"); + CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap("layers"); + CATEGORY_ICON[L("Infill")] = create_scaled_bitmap("infill"); + CATEGORY_ICON[L("Support material")] = create_scaled_bitmap("support"); + CATEGORY_ICON[L("Speed")] = create_scaled_bitmap("time"); + CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap("funnel"); + CATEGORY_ICON[L("Extrusion Width")] = create_scaled_bitmap("funnel"); + CATEGORY_ICON[L("Wipe options")] = create_scaled_bitmap("funnel"); +// CATEGORY_ICON[L("Skirt and brim")] = create_scaled_bitmap("skirt+brim"); +// CATEGORY_ICON[L("Speed > Acceleration")] = create_scaled_bitmap("time"); + CATEGORY_ICON[L("Advanced")] = create_scaled_bitmap("wrench"); // ptSLA - CATEGORY_ICON[L("Supports")] = create_scaled_bitmap(nullptr, "support"/*"sla_supports"*/); - CATEGORY_ICON[L("Pad")] = create_scaled_bitmap(nullptr, "pad"); - CATEGORY_ICON[L("Hollowing")] = create_scaled_bitmap(nullptr, "hollowing"); + CATEGORY_ICON[L("Supports")] = create_scaled_bitmap("support"/*"sla_supports"*/); + CATEGORY_ICON[L("Pad")] = create_scaled_bitmap("pad"); + CATEGORY_ICON[L("Hollowing")] = create_scaled_bitmap("hollowing"); } // create control @@ -230,9 +230,9 @@ ObjectList::ObjectList(wxWindow* parent) : // So the postponed EnsureVisible() call is planned for an item, which may not exist at the Idle processing time, if this wxEVT_SIZE // event is succeeded by a delete of the currently active item. We are trying our luck by postponing the wxEVT_SIZE triggered EnsureVisible(), // which seems to be working as of now. - this->CallAfter([this](){ this->EnsureVisible(this->GetCurrentItem()); }); + this->CallAfter([this](){ ensure_current_item_visible(); }); #else - this->EnsureVisible(this->GetCurrentItem()); + ensure_current_item_visible(); #endif e.Skip(); })); @@ -265,7 +265,7 @@ void ObjectList::create_objects_ctrl() // column ItemName(Icon+Text) of the view control: // And Icon can be consisting of several bitmaps - AppendColumn(new wxDataViewColumn(_(L("Name")), new BitmapTextRenderer(), + AppendColumn(new wxDataViewColumn(_(L("Name")), new BitmapTextRenderer(this), colName, 20*em, wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE)); // column PrintableProperty (Icon) of the view control: @@ -559,10 +559,10 @@ void ObjectList::update_name_in_model(const wxDataViewItem& item) const void ObjectList::init_icons() { - m_bmp_solidmesh = ScalableBitmap(nullptr, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART) ].second); - m_bmp_modifiermesh = ScalableBitmap(nullptr, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::PARAMETER_MODIFIER)].second); - m_bmp_support_enforcer = ScalableBitmap(nullptr, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER) ].second); - m_bmp_support_blocker = ScalableBitmap(nullptr, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_BLOCKER) ].second); + m_bmp_solidmesh = ScalableBitmap(this, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART) ].second); + m_bmp_modifiermesh = ScalableBitmap(this, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::PARAMETER_MODIFIER)].second); + m_bmp_support_enforcer = ScalableBitmap(this, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER) ].second); + m_bmp_support_blocker = ScalableBitmap(this, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_BLOCKER) ].second); m_bmp_vector.reserve(4); // bitmaps for different types of parts m_bmp_vector.push_back(&m_bmp_solidmesh.bmp()); @@ -575,12 +575,12 @@ void ObjectList::init_icons() m_objects_model->SetVolumeBitmaps(m_bmp_vector); // init icon for manifold warning - m_bmp_manifold_warning = ScalableBitmap(nullptr, "exclamation"); + m_bmp_manifold_warning = ScalableBitmap(this, "exclamation"); // Set warning bitmap for the model m_objects_model->SetWarningBitmap(&m_bmp_manifold_warning.bmp()); // init bitmap for "Add Settings" context menu - m_bmp_cog = ScalableBitmap(nullptr, "cog"); + m_bmp_cog = ScalableBitmap(this, "cog"); } void ObjectList::msw_rescale_icons() @@ -607,23 +607,20 @@ void ObjectList::msw_rescale_icons() // Update CATEGORY_ICON according to new scale { - // Note: `this` isn't passed to create_scaled_bitmap() here because of bugs in the widget, - // see note in PresetBundle::load_compatible_bitmaps() - // ptFFF - CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap(nullptr, "layers"); - CATEGORY_ICON[L("Infill")] = create_scaled_bitmap(nullptr, "infill"); - CATEGORY_ICON[L("Support material")] = create_scaled_bitmap(nullptr, "support"); - CATEGORY_ICON[L("Speed")] = create_scaled_bitmap(nullptr, "time"); - CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap(nullptr, "funnel"); - CATEGORY_ICON[L("Extrusion Width")] = create_scaled_bitmap(nullptr, "funnel"); - CATEGORY_ICON[L("Wipe options")] = create_scaled_bitmap(nullptr, "funnel"); -// CATEGORY_ICON[L("Skirt and brim")] = create_scaled_bitmap(nullptr, "skirt+brim"); -// CATEGORY_ICON[L("Speed > Acceleration")] = create_scaled_bitmap(nullptr, "time"); - CATEGORY_ICON[L("Advanced")] = create_scaled_bitmap(nullptr, "wrench"); + CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap("layers"); + CATEGORY_ICON[L("Infill")] = create_scaled_bitmap("infill"); + CATEGORY_ICON[L("Support material")] = create_scaled_bitmap("support"); + CATEGORY_ICON[L("Speed")] = create_scaled_bitmap("time"); + CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap("funnel"); + CATEGORY_ICON[L("Extrusion Width")] = create_scaled_bitmap("funnel"); + CATEGORY_ICON[L("Wipe options")] = create_scaled_bitmap("funnel"); +// CATEGORY_ICON[L("Skirt and brim")] = create_scaled_bitmap("skirt+brim"); +// CATEGORY_ICON[L("Speed > Acceleration")] = create_scaled_bitmap("time"); + CATEGORY_ICON[L("Advanced")] = create_scaled_bitmap("wrench"); // ptSLA - CATEGORY_ICON[L("Supports")] = create_scaled_bitmap(nullptr, "support"/*"sla_supports"*/); - CATEGORY_ICON[L("Pad")] = create_scaled_bitmap(nullptr, "pad"); + CATEGORY_ICON[L("Supports")] = create_scaled_bitmap("support"/*"sla_supports"*/); + CATEGORY_ICON[L("Pad")] = create_scaled_bitmap("pad"); } } @@ -1003,14 +1000,13 @@ void ObjectList::OnBeginDrag(wxDataViewEvent &event) const bool mult_sel = multiple_selection(); if ((mult_sel && !selected_instances_of_same_object()) || - (!mult_sel && (GetSelection() != item)) || - m_objects_model->GetParent(item) == wxDataViewItem(nullptr) ) { + (!mult_sel && (GetSelection() != item)) ) { event.Veto(); return; } const ItemType& type = m_objects_model->GetItemType(item); - if (!(type & (itVolume | itInstance))) { + if (!(type & (itVolume | itObject | itInstance))) { event.Veto(); return; } @@ -1024,11 +1020,13 @@ void ObjectList::OnBeginDrag(wxDataViewEvent &event) for (auto sel : sels ) sub_obj_idxs.insert(m_objects_model->GetInstanceIdByItem(sel)); } - else + else if (type & itObject) + m_dragged_data.init(m_objects_model->GetIdByItem(item), type); + else m_dragged_data.init(m_objects_model->GetObjectIdByItem(item), - type&itVolume ? m_objects_model->GetVolumeIdByItem(item) : + type&itVolume ? m_objects_model->GetVolumeIdByItem(item) : m_objects_model->GetInstanceIdByItem(item), - type); + type); /* Under MSW or OSX, DnD moves an item to the place of another selected item * But under GTK, DnD moves an item between another two items. @@ -1049,10 +1047,20 @@ void ObjectList::OnBeginDrag(wxDataViewEvent &event) bool ObjectList::can_drop(const wxDataViewItem& item) const { - return (m_dragged_data.type() == itInstance && !item.IsOk()) || - (m_dragged_data.type() == itVolume && item.IsOk() && - m_objects_model->GetItemType(item) == itVolume && - m_dragged_data.obj_idx() == m_objects_model->GetObjectIdByItem(item)); + // move instance(s) or object on "empty place" of ObjectList + if ( (m_dragged_data.type() & (itInstance | itObject)) && !item.IsOk() ) + return true; + + // type of moved item should be the same as a "destination" item + if (!item.IsOk() || !(m_dragged_data.type() & (itVolume|itObject)) || + m_objects_model->GetItemType(item) != m_dragged_data.type() ) + return false; + + // move volumes inside one object only + if (m_dragged_data.type() & itVolume) + return m_dragged_data.obj_idx() == m_objects_model->GetObjectIdByItem(item); + + return true; } void ObjectList::OnDropPossible(wxDataViewEvent &event) @@ -1082,9 +1090,6 @@ void ObjectList::OnDrop(wxDataViewEvent &event) return; } - const int from_volume_id = m_dragged_data.sub_obj_idx(); - int to_volume_id = m_objects_model->GetVolumeIdByItem(item); - // It looks like a fixed in current version of the wxWidgets // #ifdef __WXGTK__ // /* Under GTK, DnD moves an item between another two items. @@ -1096,14 +1101,33 @@ void ObjectList::OnDrop(wxDataViewEvent &event) take_snapshot(_((m_dragged_data.type() == itVolume) ? L("Volumes in Object reordered") : L("Object reordered"))); - auto& volumes = (*m_objects)[m_dragged_data.obj_idx()]->volumes; - auto delta = to_volume_id < from_volume_id ? -1 : 1; - int cnt = 0; - for (int id = from_volume_id; cnt < abs(from_volume_id - to_volume_id); id += delta, cnt++) - std::swap(volumes[id], volumes[id + delta]); + if (m_dragged_data.type() & itVolume) + { + int from_volume_id = m_dragged_data.sub_obj_idx(); + int to_volume_id = m_objects_model->GetVolumeIdByItem(item); + int delta = to_volume_id < from_volume_id ? -1 : 1; - select_item(m_objects_model->ReorganizeChildren(from_volume_id, to_volume_id, - m_objects_model->GetParent(item))); + auto& volumes = (*m_objects)[m_dragged_data.obj_idx()]->volumes; + + int cnt = 0; + for (int id = from_volume_id; cnt < abs(from_volume_id - to_volume_id); id += delta, cnt++) + std::swap(volumes[id], volumes[id + delta]); + + select_item(m_objects_model->ReorganizeChildren(from_volume_id, to_volume_id, m_objects_model->GetParent(item))); + + } + else if (m_dragged_data.type() & itObject) + { + int from_obj_id = m_dragged_data.obj_idx(); + int to_obj_id = item.IsOk() ? m_objects_model->GetIdByItem(item) : ((int)m_objects->size()-1); + int delta = to_obj_id < from_obj_id ? -1 : 1; + + int cnt = 0; + for (int id = from_obj_id; cnt < abs(from_obj_id - to_obj_id); id += delta, cnt++) + std::swap((*m_objects)[id], (*m_objects)[id + delta]); + + select_item(m_objects_model->ReorganizeObjects(from_obj_id, to_obj_id)); + } changed_object(m_dragged_data.obj_idx()); @@ -1741,7 +1765,8 @@ void ObjectList::create_instance_popupmenu(wxMenu*menu) void ObjectList::create_default_popupmenu(wxMenu*menu) { wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType::INVALID); - append_submenu(menu, sub_menu, wxID_ANY, _(L("Add Shape")), "", "add_part"); + append_submenu(menu, sub_menu, wxID_ANY, _(L("Add Shape")), "", "add_part", + [](){return true;}, this); } wxMenu* ObjectList::create_settings_popupmenu(wxMenu *parent_menu) @@ -3162,7 +3187,7 @@ void ObjectList::update_selections() select_items(sels); // Scroll selected Item in the middle of an object list - this->EnsureVisible(this->GetCurrentItem()); + ensure_current_item_visible(); } void ObjectList::update_selections_on_canvas() diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index a5a72ad8c5..3b51c17613 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -10,6 +11,7 @@ #include "Event.hpp" #include "wxExtensions.hpp" +#include "ObjectDataViewModel.hpp" class wxBoxSizer; class wxBitmapComboBox; @@ -171,6 +173,12 @@ private: SettingsBundle m_freq_settings_sla; #endif + inline void ensure_current_item_visible() + { + if (const auto &item = this->GetCurrentItem()) + this->EnsureVisible(item); + } + public: ObjectList(wxWindow* parent); ~ObjectList(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index e98446749f..52d710249b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -9,6 +9,8 @@ #include #include +#include + #include "slic3r/GUI/GUI_App.hpp" @@ -189,7 +191,7 @@ void GLGizmoCut::update_max_z(const Selection& selection) const void GLGizmoCut::set_cut_z(double cut_z) const { // Clamp the plane to the object's bounding box - m_cut_z = std::max(0.0, std::min(m_max_z, cut_z)); + m_cut_z = std::clamp(cut_z, 0.0, m_max_z); } void GLGizmoCut::perform_cut(const Selection& selection) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 6e5738a422..b6e10861fc 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -25,6 +25,9 @@ class GLGizmoCut : public GLGizmoBase public: GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); + double get_cut_z() const { return m_cut_z; } + void set_cut_z(double cut_z) const; + protected: virtual bool on_init(); virtual void on_load(cereal::BinaryInputArchive& ar) { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); } @@ -40,7 +43,6 @@ protected: private: void update_max_z(const Selection& selection) const; - void set_cut_z(double cut_z) const; void perform_cut(const Selection& selection); double calc_projection(const Linef3& mouse_ray) const; }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index db459dd7d4..ae47fcfc8e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -500,7 +500,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) processed = true; } else if (evt.Dragging() && (m_parent.get_move_volume_id() != -1) && (m_current == SlaSupports || m_current == Hollow)) - // don't allow dragging objects with the Sla gizmo on + // don't allow dragging objects with the Sla gizmo on processed = true; else if (evt.Dragging() && (m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown())) { @@ -557,12 +557,9 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) else if (evt.LeftUp() && is_dragging()) { switch (m_current) { - case Move : m_parent.do_move(L("Gizmo-Move")); - break; - case Scale : m_parent.do_scale(L("Gizmo-Scale")); - break; - case Rotate : m_parent.do_rotate(L("Gizmo-Rotate")); - break; + case Move : m_parent.do_move(L("Gizmo-Move")); break; + case Scale : m_parent.do_scale(L("Gizmo-Scale")); break; + case Rotate : m_parent.do_rotate(L("Gizmo-Rotate")); break; default : break; } @@ -779,6 +776,64 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt) processed = true; } } + else if (m_current == Move) + { + auto do_move = [this, &processed](const Vec3d& displacement) { + Selection& selection = m_parent.get_selection(); + selection.start_dragging(); + selection.translate(displacement); + wxGetApp().obj_manipul()->set_dirty(); + m_parent.do_move(L("Gizmo-Move")); + m_parent.set_as_dirty(); + processed = true; + }; + + switch (keyCode) + { + case WXK_NUMPAD_LEFT: case WXK_LEFT: { do_move(-Vec3d::UnitX()); break; } + case WXK_NUMPAD_RIGHT: case WXK_RIGHT: { do_move(Vec3d::UnitX()); break; } + case WXK_NUMPAD_UP: case WXK_UP: { do_move(Vec3d::UnitY()); break; } + case WXK_NUMPAD_DOWN: case WXK_DOWN: { do_move(-Vec3d::UnitY()); break; } + default: { break; } + } + } + else if (m_current == Rotate) + { + auto do_rotate = [this, &processed](const Vec3d& rotation) { + Selection& selection = m_parent.get_selection(); + selection.start_dragging(); + selection.rotate(rotation, TransformationType(TransformationType::World_Relative_Joint)); + wxGetApp().obj_manipul()->set_dirty(); + m_parent.do_rotate(L("Gizmo-Rotate")); + m_parent.set_as_dirty(); + processed = true; + }; + + switch (keyCode) + { + case WXK_NUMPAD_LEFT: case WXK_LEFT: { do_rotate(Vec3d(0.0, 0.0, 0.5 * M_PI)); break; } + case WXK_NUMPAD_RIGHT: case WXK_RIGHT: { do_rotate(-Vec3d(0.0, 0.0, 0.5 * M_PI)); break; } + case WXK_NUMPAD_UP: case WXK_UP: { do_rotate(Vec3d(0.0, 0.0, 0.25 * M_PI)); break; } + case WXK_NUMPAD_DOWN: case WXK_DOWN: { do_rotate(-Vec3d(0.0, 0.0, 0.25 * M_PI)); break; } + default: { break; } + } + } + else if (m_current == Cut) + { + auto do_move = [this, &processed](double delta_z) { + GLGizmoCut* cut = dynamic_cast(get_current()); + cut->set_cut_z(delta_z + cut->get_cut_z()); + m_parent.set_as_dirty(); + processed = true; + }; + + switch (keyCode) + { + case WXK_NUMPAD_UP: case WXK_UP: { do_move(1.0); break; } + case WXK_NUMPAD_DOWN: case WXK_DOWN: { do_move(-1.0); break; } + default: { break; } + } + } // if (processed) // m_parent.set_cursor(GLCanvas3D::Standard); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index d1466ef0b3..105c6faa5f 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -68,7 +68,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S /* Load default preset bitmaps before a tabpanel initialization, * but after filling of an em_unit value */ - wxGetApp().preset_bundle->load_default_preset_bitmaps(this); + wxGetApp().preset_bundle->load_default_preset_bitmaps(); // initialize tabpanel and menubar init_tabpanel(); @@ -345,7 +345,7 @@ void MainFrame::on_dpi_changed(const wxRect &suggested_rect) /* Load default preset bitmaps before a tabpanel initialization, * but after filling of an em_unit value */ - wxGetApp().preset_bundle->load_default_preset_bitmaps(this); + wxGetApp().preset_bundle->load_default_preset_bitmaps(); // update Plater wxGetApp().plater()->msw_rescale(); @@ -578,6 +578,11 @@ void MainFrame::init_menubar() append_menu_item(editMenu, wxID_ANY, _(L("&Paste")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "V", _(L("Paste clipboard")), [this](wxCommandEvent&) { m_plater->paste_from_clipboard(); }, "paste_menu", nullptr, [this](){return m_plater->can_paste_from_clipboard(); }, this); + + editMenu->AppendSeparator(); + append_menu_item(editMenu, wxID_ANY, _(L("Re&load from disk")) + sep + "F5", + _(L("Reload the plater from disk")), [this](wxCommandEvent&) { m_plater->reload_all_from_disk(); }, + "", nullptr, [this]() {return !m_plater->model().objects.empty(); }, this); } // Window menu @@ -728,7 +733,7 @@ void MainFrame::update_menubar() m_changeable_menu_items[miSend] ->SetItemLabel((is_fff ? _(L("S&end G-code")) : _(L("S&end to print"))) + dots + "\tCtrl+Shift+G"); m_changeable_menu_items[miMaterialTab] ->SetItemLabel((is_fff ? _(L("&Filament Settings Tab")) : _(L("Mate&rial Settings Tab"))) + "\tCtrl+3"); - m_changeable_menu_items[miMaterialTab] ->SetBitmap(create_scaled_bitmap(this, is_fff ? "spool": "resin")); + m_changeable_menu_items[miMaterialTab] ->SetBitmap(create_scaled_bitmap(is_fff ? "spool": "resin")); } // To perform the "Quck Slice", "Quick Slice and Save As", "Repeat last Quick Slice" and "Slice to SVG". diff --git a/src/slic3r/GUI/MsgDialog.cpp b/src/slic3r/GUI/MsgDialog.cpp index d1879e4bab..d4a82a03d6 100644 --- a/src/slic3r/GUI/MsgDialog.cpp +++ b/src/slic3r/GUI/MsgDialog.cpp @@ -53,7 +53,7 @@ MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &he rightsizer->Add(btn_sizer, 0, wxALIGN_RIGHT); if (! bitmap.IsOk()) { - bitmap = create_scaled_bitmap(this, "PrusaSlicer_192px.png", 192); + bitmap = create_scaled_bitmap("PrusaSlicer_192px.png", this, 192); } logo = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap); @@ -99,7 +99,7 @@ ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg) btn_ok->SetFocus(); btn_sizer->Add(btn_ok, 0, wxRIGHT, HORIZ_SPACING); - logo->SetBitmap(create_scaled_bitmap(this, "PrusaSlicer_192px_grayscale.png", 192)); + logo->SetBitmap(create_scaled_bitmap("PrusaSlicer_192px_grayscale.png", this, 192)); SetMaxSize(wxSize(-1, CONTENT_MAX_HEIGHT*wxGetApp().em_unit())); Fit(); diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp new file mode 100644 index 0000000000..b49b27e332 --- /dev/null +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -0,0 +1,1764 @@ +#include "ObjectDataViewModel.hpp" +#include "wxExtensions.hpp" +#include "BitmapCache.hpp" +#include "GUI_App.hpp" +#include "GUI_ObjectList.hpp" +#include "I18N.hpp" + +#include +#include + + +namespace Slic3r { + +namespace GUI { + +wxDEFINE_EVENT(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, wxCommandEvent); + +static wxBitmap get_extruder_color_icon(size_t extruder_idx, bool thin_icon = false) +{ + // Create the bitmap with color bars. + std::vector bmps = get_extruder_color_icons(thin_icon); + if (bmps.empty()) + return wxNullBitmap; + + return *bmps[extruder_idx >= bmps.size() ? 0 : extruder_idx]; +} + +BitmapCache* m_bitmap_cache = nullptr; + +// ***************************************************************************** +// ---------------------------------------------------------------------------- +// ObjectDataViewModelNode +// ---------------------------------------------------------------------------- + +void ObjectDataViewModelNode::init_container() +{ +#ifdef __WXGTK__ + // it's necessary on GTK because of control have to know if this item will be container + // in another case you couldn't to add subitem for this item + // it will be produce "segmentation fault" + m_container = true; +#endif //__WXGTK__ +} + +#define LAYER_ROOT_ICON "edit_layers_all" +#define LAYER_ICON "edit_layers_some" + +ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const ItemType type) : + m_parent(parent), + m_type(type), + m_extruder(wxEmptyString) +{ + if (type == itSettings) + m_name = "Settings to modified"; + else if (type == itInstanceRoot) + m_name = _(L("Instances")); + else if (type == itInstance) + { + m_idx = parent->GetChildCount(); + m_name = wxString::Format(_(L("Instance %d")), m_idx + 1); + + set_action_and_extruder_icons(); + } + else if (type == itLayerRoot) + { + m_bmp = create_scaled_bitmap(LAYER_ROOT_ICON); // FIXME: pass window ptr + m_name = _(L("Layers")); + } + + if (type & (itInstanceRoot | itLayerRoot)) + init_container(); +} + +ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent, + const t_layer_height_range& layer_range, + const int idx /*= -1 */, + const wxString& extruder) : + m_parent(parent), + m_type(itLayer), + m_idx(idx), + m_layer_range(layer_range), + m_extruder(extruder) +{ + const int children_cnt = parent->GetChildCount(); + if (idx < 0) + m_idx = children_cnt; + else + { + // update indexes for another Laeyr Nodes + for (int i = m_idx; i < children_cnt; i++) + parent->GetNthChild(i)->SetIdx(i + 1); + } + const std::string label_range = (boost::format(" %.2f-%.2f ") % layer_range.first % layer_range.second).str(); + m_name = _(L("Range")) + label_range + "(" + _(L("mm")) + ")"; + m_bmp = create_scaled_bitmap(LAYER_ICON); // FIXME: pass window ptr + + set_action_and_extruder_icons(); + init_container(); +} + +#ifndef NDEBUG +bool ObjectDataViewModelNode::valid() +{ + // Verify that the object was not deleted yet. + assert(m_idx >= -1); + return m_idx >= -1; +} +#endif /* NDEBUG */ + +void ObjectDataViewModelNode::set_action_and_extruder_icons() +{ + m_action_icon_name = m_type & itObject ? "advanced_plus" : + m_type & (itVolume | itLayer) ? "cog" : /*m_type & itInstance*/ "set_separate_obj"; + m_action_icon = create_scaled_bitmap(m_action_icon_name); // FIXME: pass window ptr + + if (m_type & itInstance) + return; // don't set colored bitmap for Instance + + // set extruder bitmap + int extruder_idx = atoi(m_extruder.c_str()); + if (extruder_idx > 0) --extruder_idx; + m_extruder_bmp = get_extruder_color_icon(extruder_idx); +} + +void ObjectDataViewModelNode::set_printable_icon(PrintIndicator printable) +{ + m_printable = printable; + m_printable_icon = m_printable == piUndef ? m_empty_bmp : + create_scaled_bitmap(m_printable == piPrintable ? "eye_open.png" : "eye_closed.png"); +} + +void ObjectDataViewModelNode::update_settings_digest_bitmaps() +{ + m_bmp = m_empty_bmp; + + std::map& categories_icon = Slic3r::GUI::wxGetApp().obj_list()->CATEGORY_ICON; + + std::string scaled_bitmap_name = m_name.ToUTF8().data(); + scaled_bitmap_name += "-em" + std::to_string(Slic3r::GUI::wxGetApp().em_unit()); + + wxBitmap *bmp = m_bitmap_cache->find(scaled_bitmap_name); + if (bmp == nullptr) { + std::vector bmps; + for (auto& cat : m_opt_categories) + bmps.emplace_back( categories_icon.find(cat) == categories_icon.end() ? + wxNullBitmap : categories_icon.at(cat)); + bmp = m_bitmap_cache->insert(scaled_bitmap_name, bmps); + } + + m_bmp = *bmp; +} + +bool ObjectDataViewModelNode::update_settings_digest(const std::vector& categories) +{ + if (m_type != itSettings || m_opt_categories == categories) + return false; + + m_opt_categories = categories; + m_name = wxEmptyString; + + for (auto& cat : m_opt_categories) + m_name += _(cat) + "; "; + if (!m_name.IsEmpty()) + m_name.erase(m_name.Length()-2, 2); // Delete last "; " + + update_settings_digest_bitmaps(); + + return true; +} + +void ObjectDataViewModelNode::msw_rescale() +{ + if (!m_action_icon_name.empty()) + m_action_icon = create_scaled_bitmap(m_action_icon_name); + + if (m_printable != piUndef) + m_printable_icon = create_scaled_bitmap(m_printable == piPrintable ? "eye_open.png" : "eye_closed.png"); + + if (!m_opt_categories.empty()) + update_settings_digest_bitmaps(); +} + +bool ObjectDataViewModelNode::SetValue(const wxVariant& variant, unsigned col) +{ + switch (col) + { + case colPrint: + m_printable_icon << variant; + return true; + case colName: { + DataViewBitmapText data; + data << variant; + m_bmp = data.GetBitmap(); + m_name = data.GetText(); + return true; } + case colExtruder: { + DataViewBitmapText data; + data << variant; + m_extruder_bmp = data.GetBitmap(); + m_extruder = data.GetText() == "0" ? _(L("default")) : data.GetText(); + return true; } + case colEditing: + m_action_icon << variant; + return true; + default: + printf("MyObjectTreeModel::SetValue: wrong column"); + } + return false; +} + +void ObjectDataViewModelNode::SetIdx(const int& idx) +{ + m_idx = idx; + // update name if this node is instance + if (m_type == itInstance) + m_name = wxString::Format(_(L("Instance %d")), m_idx + 1); +} + +// ***************************************************************************** +// ---------------------------------------------------------------------------- +// ObjectDataViewModel +// ---------------------------------------------------------------------------- + +static int get_root_idx(ObjectDataViewModelNode *parent_node, const ItemType root_type) +{ + // because of istance_root and layers_root are at the end of the list, so + // start locking from the end + for (int root_idx = parent_node->GetChildCount() - 1; root_idx >= 0; root_idx--) + { + // if there is SettingsItem or VolumeItem, then RootItems don't exist in current ObjectItem + if (parent_node->GetNthChild(root_idx)->GetType() & (itSettings | itVolume)) + break; + if (parent_node->GetNthChild(root_idx)->GetType() & root_type) + return root_idx; + } + + return -1; +} + +ObjectDataViewModel::ObjectDataViewModel() +{ + m_bitmap_cache = new Slic3r::GUI::BitmapCache; +} + +ObjectDataViewModel::~ObjectDataViewModel() +{ + for (auto object : m_objects) + delete object; + delete m_bitmap_cache; + m_bitmap_cache = nullptr; +} + +wxDataViewItem ObjectDataViewModel::Add(const wxString &name, + const int extruder, + const bool has_errors/* = false*/) +{ + const wxString extruder_str = extruder == 0 ? _(L("default")) : wxString::Format("%d", extruder); + auto root = new ObjectDataViewModelNode(name, extruder_str); + // Add error icon if detected auto-repaire + if (has_errors) + root->m_bmp = *m_warning_bmp; + + m_objects.push_back(root); + // notify control + wxDataViewItem child((void*)root); + wxDataViewItem parent((void*)NULL); + + ItemAdded(parent, child); + return child; +} + +wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent_item, + const wxString &name, + const Slic3r::ModelVolumeType volume_type, + const bool has_errors/* = false*/, + const int extruder/* = 0*/, + const bool create_frst_child/* = true*/) +{ + ObjectDataViewModelNode *root = (ObjectDataViewModelNode*)parent_item.GetID(); + if (!root) return wxDataViewItem(0); + + wxString extruder_str = extruder == 0 ? _(L("default")) : wxString::Format("%d", extruder); + + // get insertion position according to the existed Layers and/or Instances Items + int insert_position = get_root_idx(root, itLayerRoot); + if (insert_position < 0) + insert_position = get_root_idx(root, itInstanceRoot); + + const bool obj_errors = root->m_bmp.IsOk(); + + if (create_frst_child && root->m_volumes_cnt == 0) + { + const Slic3r::ModelVolumeType type = Slic3r::ModelVolumeType::MODEL_PART; + const auto node = new ObjectDataViewModelNode(root, root->m_name, GetVolumeIcon(type, obj_errors), extruder_str, 0); + node->m_volume_type = type; + + insert_position < 0 ? root->Append(node) : root->Insert(node, insert_position); + // notify control + const wxDataViewItem child((void*)node); + ItemAdded(parent_item, child); + + root->m_volumes_cnt++; + if (insert_position >= 0) insert_position++; + } + + const auto node = new ObjectDataViewModelNode(root, name, GetVolumeIcon(volume_type, has_errors), extruder_str, root->m_volumes_cnt); + insert_position < 0 ? root->Append(node) : root->Insert(node, insert_position); + + // if part with errors is added, but object wasn't marked, then mark it + if (!obj_errors && has_errors) + root->SetBitmap(*m_warning_bmp); + + // notify control + const wxDataViewItem child((void*)node); + ItemAdded(parent_item, child); + root->m_volumes_cnt++; + + node->m_volume_type = volume_type; + + return child; +} + +wxDataViewItem ObjectDataViewModel::AddSettingsChild(const wxDataViewItem &parent_item) +{ + ObjectDataViewModelNode *root = (ObjectDataViewModelNode*)parent_item.GetID(); + if (!root) return wxDataViewItem(0); + + const auto node = new ObjectDataViewModelNode(root, itSettings); + root->Insert(node, 0); + // notify control + const wxDataViewItem child((void*)node); + ItemAdded(parent_item, child); + return child; +} + +/* return values: + * true => root_node is created and added to the parent_root + * false => root node alredy exists +*/ +static bool append_root_node(ObjectDataViewModelNode *parent_node, + ObjectDataViewModelNode **root_node, + const ItemType root_type) +{ + const int inst_root_id = get_root_idx(parent_node, root_type); + + *root_node = inst_root_id < 0 ? + new ObjectDataViewModelNode(parent_node, root_type) : + parent_node->GetNthChild(inst_root_id); + + if (inst_root_id < 0) { + if ((root_type&itInstanceRoot) || + ( (root_type&itLayerRoot) && get_root_idx(parent_node, itInstanceRoot)<0) ) + parent_node->Append(*root_node); + else if (root_type&itLayerRoot) + parent_node->Insert(*root_node, static_cast(get_root_idx(parent_node, itInstanceRoot))); + return true; + } + + return false; +} + +wxDataViewItem ObjectDataViewModel::AddRoot(const wxDataViewItem &parent_item, ItemType root_type) +{ + ObjectDataViewModelNode *parent_node = (ObjectDataViewModelNode*)parent_item.GetID(); + if (!parent_node) return wxDataViewItem(0); + + // get InstanceRoot node + ObjectDataViewModelNode *root_node { nullptr }; + const bool appended = append_root_node(parent_node, &root_node, root_type); + if (!root_node) return wxDataViewItem(0); + + const wxDataViewItem root_item((void*)root_node); + + if (appended) + ItemAdded(parent_item, root_item);// notify control + return root_item; +} + +wxDataViewItem ObjectDataViewModel::AddInstanceRoot(const wxDataViewItem &parent_item) +{ + return AddRoot(parent_item, itInstanceRoot); +} + +wxDataViewItem ObjectDataViewModel::AddInstanceChild(const wxDataViewItem &parent_item, size_t num) +{ + std::vector print_indicator(num, true); + + // if InstanceRoot item isn't created for this moment + if (!GetInstanceRootItem(parent_item).IsOk()) + // use object's printable state to first instance + print_indicator[0] = IsPrintable(parent_item); + + return wxDataViewItem((void*)AddInstanceChild(parent_item, print_indicator)); +} + +wxDataViewItem ObjectDataViewModel::AddInstanceChild(const wxDataViewItem& parent_item, + const std::vector& print_indicator) +{ + const wxDataViewItem inst_root_item = AddInstanceRoot(parent_item); + if (!inst_root_item) return wxDataViewItem(0); + + ObjectDataViewModelNode* inst_root_node = (ObjectDataViewModelNode*)inst_root_item.GetID(); + + // Add instance nodes + ObjectDataViewModelNode *instance_node = nullptr; + size_t counter = 0; + while (counter < print_indicator.size()) { + instance_node = new ObjectDataViewModelNode(inst_root_node, itInstance); + + instance_node->set_printable_icon(print_indicator[counter] ? piPrintable : piUnprintable); + + inst_root_node->Append(instance_node); + // notify control + const wxDataViewItem instance_item((void*)instance_node); + ItemAdded(inst_root_item, instance_item); + ++counter; + } + + // update object_node printable property + UpdateObjectPrintable(parent_item); + + return wxDataViewItem((void*)instance_node); +} + +void ObjectDataViewModel::UpdateObjectPrintable(wxDataViewItem parent_item) +{ + const wxDataViewItem inst_root_item = GetInstanceRootItem(parent_item); + if (!inst_root_item) + return; + + ObjectDataViewModelNode* inst_root_node = (ObjectDataViewModelNode*)inst_root_item.GetID(); + + const size_t child_cnt = inst_root_node->GetChildren().Count(); + PrintIndicator obj_pi = piUnprintable; + for (size_t i=0; i < child_cnt; i++) + if (inst_root_node->GetNthChild(i)->IsPrintable() & piPrintable) { + obj_pi = piPrintable; + break; + } + // and set printable state for object_node to piUndef + ObjectDataViewModelNode* obj_node = (ObjectDataViewModelNode*)parent_item.GetID(); + obj_node->set_printable_icon(obj_pi); + ItemChanged(parent_item); +} + +// update printable property for all instances from object +void ObjectDataViewModel::UpdateInstancesPrintable(wxDataViewItem parent_item) +{ + const wxDataViewItem inst_root_item = GetInstanceRootItem(parent_item); + if (!inst_root_item) + return; + + ObjectDataViewModelNode* obj_node = (ObjectDataViewModelNode*)parent_item.GetID(); + const PrintIndicator obj_pi = obj_node->IsPrintable(); + + ObjectDataViewModelNode* inst_root_node = (ObjectDataViewModelNode*)inst_root_item.GetID(); + const size_t child_cnt = inst_root_node->GetChildren().Count(); + + for (size_t i=0; i < child_cnt; i++) + { + ObjectDataViewModelNode* inst_node = inst_root_node->GetNthChild(i); + // and set printable state for object_node to piUndef + inst_node->set_printable_icon(obj_pi); + ItemChanged(wxDataViewItem((void*)inst_node)); + } +} + +bool ObjectDataViewModel::IsPrintable(const wxDataViewItem& item) const +{ + ObjectDataViewModelNode* node = (ObjectDataViewModelNode*)item.GetID(); + if (!node) + return false; + + return node->IsPrintable() == piPrintable; +} + +wxDataViewItem ObjectDataViewModel::AddLayersRoot(const wxDataViewItem &parent_item) +{ + return AddRoot(parent_item, itLayerRoot); +} + +wxDataViewItem ObjectDataViewModel::AddLayersChild(const wxDataViewItem &parent_item, + const t_layer_height_range& layer_range, + const int extruder/* = 0*/, + const int index /* = -1*/) +{ + ObjectDataViewModelNode *parent_node = (ObjectDataViewModelNode*)parent_item.GetID(); + if (!parent_node) return wxDataViewItem(0); + + wxString extruder_str = extruder == 0 ? _(L("default")) : wxString::Format("%d", extruder); + + // get LayerRoot node + ObjectDataViewModelNode *layer_root_node; + wxDataViewItem layer_root_item; + + if (parent_node->GetType() & itLayerRoot) { + layer_root_node = parent_node; + layer_root_item = parent_item; + } + else { + const int root_idx = get_root_idx(parent_node, itLayerRoot); + if (root_idx < 0) return wxDataViewItem(0); + layer_root_node = parent_node->GetNthChild(root_idx); + layer_root_item = wxDataViewItem((void*)layer_root_node); + } + + // Add layer node + ObjectDataViewModelNode *layer_node = new ObjectDataViewModelNode(layer_root_node, layer_range, index, extruder_str); + if (index < 0) + layer_root_node->Append(layer_node); + else + layer_root_node->Insert(layer_node, index); + + // notify control + const wxDataViewItem layer_item((void*)layer_node); + ItemAdded(layer_root_item, layer_item); + + return layer_item; +} + +wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) +{ + auto ret_item = wxDataViewItem(0); + ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return ret_item; + + auto node_parent = node->GetParent(); + wxDataViewItem parent(node_parent); + + // first remove the node from the parent's array of children; + // NOTE: MyObjectTreeModelNodePtrArray is only an array of _pointers_ + // thus removing the node from it doesn't result in freeing it + if (node_parent) { + if (node->m_type & (itInstanceRoot|itLayerRoot)) + { + // node can be deleted by the Delete, let's check its type while we safely can + bool is_instance_root = (node->m_type & itInstanceRoot); + + for (int i = int(node->GetChildCount() - 1); i >= (is_instance_root ? 1 : 0); i--) + Delete(wxDataViewItem(node->GetNthChild(i))); + + return parent; + } + + auto id = node_parent->GetChildren().Index(node); + auto idx = node->GetIdx(); + + + if (node->m_type & (itVolume|itLayer)) { + node_parent->m_volumes_cnt--; + DeleteSettings(item); + } + node_parent->GetChildren().Remove(node); + + if (id > 0) { + if (size_t(id) == node_parent->GetChildCount()) id--; + ret_item = wxDataViewItem(node_parent->GetChildren().Item(id)); + } + + //update idx value for remaining child-nodes + auto children = node_parent->GetChildren(); + for (size_t i = 0; i < node_parent->GetChildCount() && idx>=0; i++) + { + auto cur_idx = children[i]->GetIdx(); + if (cur_idx > idx) + children[i]->SetIdx(cur_idx-1); + } + + // if there is last instance item, delete both of it and instance root item + if (node_parent->GetChildCount() == 1 && node_parent->GetNthChild(0)->m_type == itInstance) + { + delete node; + ItemDeleted(parent, item); + + ObjectDataViewModelNode *last_instance_node = node_parent->GetNthChild(0); + PrintIndicator last_instance_printable = last_instance_node->IsPrintable(); + node_parent->GetChildren().Remove(last_instance_node); + delete last_instance_node; + ItemDeleted(parent, wxDataViewItem(last_instance_node)); + + ObjectDataViewModelNode *obj_node = node_parent->GetParent(); + obj_node->set_printable_icon(last_instance_printable); + obj_node->GetChildren().Remove(node_parent); + delete node_parent; + ret_item = wxDataViewItem(obj_node); + +#ifndef __WXGTK__ + if (obj_node->GetChildCount() == 0) + obj_node->m_container = false; +#endif //__WXGTK__ + ItemDeleted(ret_item, wxDataViewItem(node_parent)); + return ret_item; + } + + if (node->m_type & itInstance) + UpdateObjectPrintable(wxDataViewItem(node_parent->GetParent())); + + // if there was last layer item, delete this one and layers root item + if (node_parent->GetChildCount() == 0 && node_parent->m_type == itLayerRoot) + { + ObjectDataViewModelNode *obj_node = node_parent->GetParent(); + obj_node->GetChildren().Remove(node_parent); + delete node_parent; + ret_item = wxDataViewItem(obj_node); + +#ifndef __WXGTK__ + if (obj_node->GetChildCount() == 0) + obj_node->m_container = false; +#endif //__WXGTK__ + ItemDeleted(ret_item, wxDataViewItem(node_parent)); + return ret_item; + } + + // if there is last volume item after deleting, delete this last volume too + if (node_parent->GetChildCount() <= 3) // 3??? #ys_FIXME + { + int vol_cnt = 0; + int vol_idx = 0; + for (size_t i = 0; i < node_parent->GetChildCount(); ++i) { + if (node_parent->GetNthChild(i)->GetType() == itVolume) { + vol_idx = i; + vol_cnt++; + } + if (vol_cnt > 1) + break; + } + + if (vol_cnt == 1) { + delete node; + ItemDeleted(parent, item); + + ObjectDataViewModelNode *last_child_node = node_parent->GetNthChild(vol_idx); + DeleteSettings(wxDataViewItem(last_child_node)); + node_parent->GetChildren().Remove(last_child_node); + node_parent->m_volumes_cnt = 0; + delete last_child_node; + +#ifndef __WXGTK__ + if (node_parent->GetChildCount() == 0) + node_parent->m_container = false; +#endif //__WXGTK__ + ItemDeleted(parent, wxDataViewItem(last_child_node)); + + wxCommandEvent event(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED); + auto it = find(m_objects.begin(), m_objects.end(), node_parent); + event.SetInt(it == m_objects.end() ? -1 : it - m_objects.begin()); + wxPostEvent(m_ctrl, event); + + ret_item = parent; + + return ret_item; + } + } + } + else + { + auto it = find(m_objects.begin(), m_objects.end(), node); + size_t id = it - m_objects.begin(); + if (it != m_objects.end()) + { + // Delete all sub-items + int i = m_objects[id]->GetChildCount() - 1; + while (i >= 0) { + Delete(wxDataViewItem(m_objects[id]->GetNthChild(i))); + i = m_objects[id]->GetChildCount() - 1; + } + m_objects.erase(it); + } + if (id > 0) { + if(id == m_objects.size()) id--; + ret_item = wxDataViewItem(m_objects[id]); + } + } + // free the node + delete node; + + // set m_containet to FALSE if parent has no child + if (node_parent) { +#ifndef __WXGTK__ + if (node_parent->GetChildCount() == 0) + node_parent->m_container = false; +#endif //__WXGTK__ + ret_item = parent; + } + + // notify control + ItemDeleted(parent, item); + return ret_item; +} + +wxDataViewItem ObjectDataViewModel::DeleteLastInstance(const wxDataViewItem &parent_item, size_t num) +{ + auto ret_item = wxDataViewItem(0); + ObjectDataViewModelNode *parent_node = (ObjectDataViewModelNode*)parent_item.GetID(); + if (!parent_node) return ret_item; + + const int inst_root_id = get_root_idx(parent_node, itInstanceRoot); + if (inst_root_id < 0) return ret_item; + + wxDataViewItemArray items; + ObjectDataViewModelNode *inst_root_node = parent_node->GetNthChild(inst_root_id); + const wxDataViewItem inst_root_item((void*)inst_root_node); + + const int inst_cnt = inst_root_node->GetChildCount(); + const bool delete_inst_root_item = inst_cnt - num < 2 ? true : false; + + PrintIndicator last_inst_printable = piUndef; + + int stop = delete_inst_root_item ? 0 : inst_cnt - num; + for (int i = inst_cnt - 1; i >= stop;--i) { + ObjectDataViewModelNode *last_instance_node = inst_root_node->GetNthChild(i); + if (i==0) last_inst_printable = last_instance_node->IsPrintable(); + inst_root_node->GetChildren().Remove(last_instance_node); + delete last_instance_node; + ItemDeleted(inst_root_item, wxDataViewItem(last_instance_node)); + } + + if (delete_inst_root_item) { + ret_item = parent_item; + parent_node->GetChildren().Remove(inst_root_node); + parent_node->set_printable_icon(last_inst_printable); + ItemDeleted(parent_item, inst_root_item); + ItemChanged(parent_item); +#ifndef __WXGTK__ + if (parent_node->GetChildCount() == 0) + parent_node->m_container = false; +#endif //__WXGTK__ + } + + // update object_node printable property + UpdateObjectPrintable(parent_item); + + return ret_item; +} + +void ObjectDataViewModel::DeleteAll() +{ + while (!m_objects.empty()) + { + auto object = m_objects.back(); +// object->RemoveAllChildren(); + Delete(wxDataViewItem(object)); + } +} + +void ObjectDataViewModel::DeleteChildren(wxDataViewItem& parent) +{ + ObjectDataViewModelNode *root = (ObjectDataViewModelNode*)parent.GetID(); + if (!root) // happens if item.IsOk()==false + return; + + // first remove the node from the parent's array of children; + // NOTE: MyObjectTreeModelNodePtrArray is only an array of _pointers_ + // thus removing the node from it doesn't result in freeing it + auto& children = root->GetChildren(); + for (int id = root->GetChildCount() - 1; id >= 0; --id) + { + auto node = children[id]; + auto item = wxDataViewItem(node); + children.RemoveAt(id); + + if (node->m_type == itVolume) + root->m_volumes_cnt--; + + // free the node + delete node; + + // notify control + ItemDeleted(parent, item); + } + + // set m_containet to FALSE if parent has no child +#ifndef __WXGTK__ + root->m_container = false; +#endif //__WXGTK__ +} + +void ObjectDataViewModel::DeleteVolumeChildren(wxDataViewItem& parent) +{ + ObjectDataViewModelNode *root = (ObjectDataViewModelNode*)parent.GetID(); + if (!root) // happens if item.IsOk()==false + return; + + // first remove the node from the parent's array of children; + // NOTE: MyObjectTreeModelNodePtrArray is only an array of _pointers_ + // thus removing the node from it doesn't result in freeing it + auto& children = root->GetChildren(); + for (int id = root->GetChildCount() - 1; id >= 0; --id) + { + auto node = children[id]; + if (node->m_type != itVolume) + continue; + + auto item = wxDataViewItem(node); + DeleteSettings(item); + children.RemoveAt(id); + + // free the node + delete node; + + // notify control + ItemDeleted(parent, item); + } + root->m_volumes_cnt = 0; + + // set m_containet to FALSE if parent has no child +#ifndef __WXGTK__ + root->m_container = false; +#endif //__WXGTK__ +} + +void ObjectDataViewModel::DeleteSettings(const wxDataViewItem& parent) +{ + ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)parent.GetID(); + if (!node) return; + + // if volume has a "settings"item, than delete it before volume deleting + if (node->GetChildCount() > 0 && node->GetNthChild(0)->GetType() == itSettings) { + auto settings_node = node->GetNthChild(0); + auto settings_item = wxDataViewItem(settings_node); + node->GetChildren().RemoveAt(0); + delete settings_node; + ItemDeleted(parent, settings_item); + } +} + +wxDataViewItem ObjectDataViewModel::GetItemById(int obj_idx) +{ + if (size_t(obj_idx) >= m_objects.size()) + { + printf("Error! Out of objects range.\n"); + return wxDataViewItem(0); + } + return wxDataViewItem(m_objects[obj_idx]); +} + + +wxDataViewItem ObjectDataViewModel::GetItemByVolumeId(int obj_idx, int volume_idx) +{ + if (size_t(obj_idx) >= m_objects.size()) { + printf("Error! Out of objects range.\n"); + return wxDataViewItem(0); + } + + auto parent = m_objects[obj_idx]; + if (parent->GetChildCount() == 0 || + (parent->GetChildCount() == 1 && parent->GetNthChild(0)->GetType() & itSettings )) { + if (volume_idx == 0) + return GetItemById(obj_idx); + + printf("Error! Object has no one volume.\n"); + return wxDataViewItem(0); + } + + for (size_t i = 0; i < parent->GetChildCount(); i++) + if (parent->GetNthChild(i)->m_idx == volume_idx && parent->GetNthChild(i)->GetType() & itVolume) + return wxDataViewItem(parent->GetNthChild(i)); + + return wxDataViewItem(0); +} + +wxDataViewItem ObjectDataViewModel::GetItemById(const int obj_idx, const int sub_obj_idx, const ItemType parent_type) +{ + if (size_t(obj_idx) >= m_objects.size()) { + printf("Error! Out of objects range.\n"); + return wxDataViewItem(0); + } + + auto item = GetItemByType(wxDataViewItem(m_objects[obj_idx]), parent_type); + if (!item) + return wxDataViewItem(0); + + auto parent = (ObjectDataViewModelNode*)item.GetID(); + for (size_t i = 0; i < parent->GetChildCount(); i++) + if (parent->GetNthChild(i)->m_idx == sub_obj_idx) + return wxDataViewItem(parent->GetNthChild(i)); + + return wxDataViewItem(0); +} + +wxDataViewItem ObjectDataViewModel::GetItemByInstanceId(int obj_idx, int inst_idx) +{ + return GetItemById(obj_idx, inst_idx, itInstanceRoot); +} + +wxDataViewItem ObjectDataViewModel::GetItemByLayerId(int obj_idx, int layer_idx) +{ + return GetItemById(obj_idx, layer_idx, itLayerRoot); +} + +wxDataViewItem ObjectDataViewModel::GetItemByLayerRange(const int obj_idx, const t_layer_height_range& layer_range) +{ + if (size_t(obj_idx) >= m_objects.size()) { + printf("Error! Out of objects range.\n"); + return wxDataViewItem(0); + } + + auto item = GetItemByType(wxDataViewItem(m_objects[obj_idx]), itLayerRoot); + if (!item) + return wxDataViewItem(0); + + auto parent = (ObjectDataViewModelNode*)item.GetID(); + for (size_t i = 0; i < parent->GetChildCount(); i++) + if (parent->GetNthChild(i)->m_layer_range == layer_range) + return wxDataViewItem(parent->GetNthChild(i)); + + return wxDataViewItem(0); +} + +int ObjectDataViewModel::GetItemIdByLayerRange(const int obj_idx, const t_layer_height_range& layer_range) +{ + wxDataViewItem item = GetItemByLayerRange(obj_idx, layer_range); + if (!item) + return -1; + + return GetLayerIdByItem(item); +} + +int ObjectDataViewModel::GetIdByItem(const wxDataViewItem& item) const +{ + if(!item.IsOk()) + return -1; + + ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); + auto it = find(m_objects.begin(), m_objects.end(), node); + if (it == m_objects.end()) + return -1; + + return it - m_objects.begin(); +} + +int ObjectDataViewModel::GetIdByItemAndType(const wxDataViewItem& item, const ItemType type) const +{ + wxASSERT(item.IsOk()); + + ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); + if (!node || node->m_type != type) + return -1; + return node->GetIdx(); +} + +int ObjectDataViewModel::GetObjectIdByItem(const wxDataViewItem& item) const +{ + return GetIdByItem(GetTopParent(item)); +} + +int ObjectDataViewModel::GetVolumeIdByItem(const wxDataViewItem& item) const +{ + return GetIdByItemAndType(item, itVolume); +} + +int ObjectDataViewModel::GetInstanceIdByItem(const wxDataViewItem& item) const +{ + return GetIdByItemAndType(item, itInstance); +} + +int ObjectDataViewModel::GetLayerIdByItem(const wxDataViewItem& item) const +{ + return GetIdByItemAndType(item, itLayer); +} + +t_layer_height_range ObjectDataViewModel::GetLayerRangeByItem(const wxDataViewItem& item) const +{ + wxASSERT(item.IsOk()); + + ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); + if (!node || node->m_type != itLayer) + return { 0.0f, 0.0f }; + return node->GetLayerRange(); +} + +bool ObjectDataViewModel::UpdateColumValues(unsigned col) +{ + switch (col) + { + case colPrint: + case colName: + case colEditing: + return true; + case colExtruder: + { + wxDataViewItemArray items; + GetAllChildren(wxDataViewItem(nullptr), items); + + if (items.IsEmpty()) return false; + + for (auto item : items) + UpdateExtruderBitmap(item); + + return true; + } + default: + printf("MyObjectTreeModel::SetValue: wrong column"); + } + return false; +} + + +void ObjectDataViewModel::UpdateExtruderBitmap(wxDataViewItem item) +{ + wxString extruder = GetExtruder(item); + if (extruder.IsEmpty()) + return; + + // set extruder bitmap + int extruder_idx = atoi(extruder.c_str()); + if (extruder_idx > 0) --extruder_idx; + + const DataViewBitmapText extruder_val(extruder, get_extruder_color_icon(extruder_idx)); + + wxVariant value; + value << extruder_val; + + SetValue(value, item, colExtruder); +} + +void ObjectDataViewModel::GetItemInfo(const wxDataViewItem& item, ItemType& type, int& obj_idx, int& idx) +{ + wxASSERT(item.IsOk()); + type = itUndef; + + ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); + if (!node || + node->GetIdx() <-1 || + ( node->GetIdx() == -1 && + !(node->GetType() & (itObject | itSettings | itInstanceRoot | itLayerRoot/* | itLayer*/)) + ) + ) + return; + + idx = node->GetIdx(); + type = node->GetType(); + + ObjectDataViewModelNode *parent_node = node->GetParent(); + if (!parent_node) return; + + // get top parent (Object) node + while (parent_node->m_type != itObject) + parent_node = parent_node->GetParent(); + + auto it = find(m_objects.begin(), m_objects.end(), parent_node); + if (it != m_objects.end()) + obj_idx = it - m_objects.begin(); + else + type = itUndef; +} + +int ObjectDataViewModel::GetRowByItem(const wxDataViewItem& item) const +{ + if (m_objects.empty()) + return -1; + + int row_num = 0; + + for (size_t i = 0; i < m_objects.size(); i++) + { + row_num++; + if (item == wxDataViewItem(m_objects[i])) + return row_num; + + for (size_t j = 0; j < m_objects[i]->GetChildCount(); j++) + { + row_num++; + ObjectDataViewModelNode* cur_node = m_objects[i]->GetNthChild(j); + if (item == wxDataViewItem(cur_node)) + return row_num; + + if (cur_node->m_type == itVolume && cur_node->GetChildCount() == 1) + row_num++; + if (cur_node->m_type == itInstanceRoot) + { + row_num++; + for (size_t t = 0; t < cur_node->GetChildCount(); t++) + { + row_num++; + if (item == wxDataViewItem(cur_node->GetNthChild(t))) + return row_num; + } + } + } + } + + return -1; +} + +bool ObjectDataViewModel::InvalidItem(const wxDataViewItem& item) +{ + if (!item) + return true; + + ObjectDataViewModelNode* node = (ObjectDataViewModelNode*)item.GetID(); + if (!node || node->invalid()) + return true; + + return false; +} + +wxString ObjectDataViewModel::GetName(const wxDataViewItem &item) const +{ + ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return wxEmptyString; + + return node->m_name; +} + +wxBitmap& ObjectDataViewModel::GetBitmap(const wxDataViewItem &item) const +{ + ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); + return node->m_bmp; +} + +wxString ObjectDataViewModel::GetExtruder(const wxDataViewItem& item) const +{ + ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return wxEmptyString; + + return node->m_extruder; +} + +int ObjectDataViewModel::GetExtruderNumber(const wxDataViewItem& item) const +{ + ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return 0; + + return atoi(node->m_extruder.c_str()); +} + +void ObjectDataViewModel::GetValue(wxVariant &variant, const wxDataViewItem &item, unsigned int col) const +{ + wxASSERT(item.IsOk()); + + ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); + switch (col) + { + case colPrint: + variant << node->m_printable_icon; + break; + case colName: + variant << DataViewBitmapText(node->m_name, node->m_bmp); + break; + case colExtruder: + variant << DataViewBitmapText(node->m_extruder, node->m_extruder_bmp); + break; + case colEditing: + variant << node->m_action_icon; + break; + default: + ; + } +} + +bool ObjectDataViewModel::SetValue(const wxVariant &variant, const wxDataViewItem &item, unsigned int col) +{ + wxASSERT(item.IsOk()); + + ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); + return node->SetValue(variant, col); +} + +bool ObjectDataViewModel::SetValue(const wxVariant &variant, const int item_idx, unsigned int col) +{ + if (size_t(item_idx) >= m_objects.size()) + return false; + + return m_objects[item_idx]->SetValue(variant, col); +} + +void ObjectDataViewModel::SetExtruder(const wxString& extruder, wxDataViewItem item) +{ + DataViewBitmapText extruder_val; + extruder_val.SetText(extruder); + + // set extruder bitmap + int extruder_idx = atoi(extruder.c_str()); + if (extruder_idx > 0) --extruder_idx; + extruder_val.SetBitmap(get_extruder_color_icon(extruder_idx)); + + wxVariant value; + value << extruder_val; + + SetValue(value, item, colExtruder); +} + +wxDataViewItem ObjectDataViewModel::ReorganizeChildren( const int current_volume_id, + const int new_volume_id, + const wxDataViewItem &parent) +{ + auto ret_item = wxDataViewItem(0); + if (current_volume_id == new_volume_id) + return ret_item; + wxASSERT(parent.IsOk()); + ObjectDataViewModelNode *node_parent = (ObjectDataViewModelNode*)parent.GetID(); + if (!node_parent) // happens if item.IsOk()==false + return ret_item; + + const size_t shift = node_parent->GetChildren().Item(0)->m_type == itSettings ? 1 : 0; + + ObjectDataViewModelNode *deleted_node = node_parent->GetNthChild(current_volume_id+shift); + node_parent->GetChildren().Remove(deleted_node); + ItemDeleted(parent, wxDataViewItem(deleted_node)); + node_parent->Insert(deleted_node, new_volume_id+shift); + ItemAdded(parent, wxDataViewItem(deleted_node)); + + //update volume_id value for child-nodes + auto children = node_parent->GetChildren(); + int id_frst = current_volume_id < new_volume_id ? current_volume_id : new_volume_id; + int id_last = current_volume_id > new_volume_id ? current_volume_id : new_volume_id; + for (int id = id_frst; id <= id_last; ++id) + children[id+shift]->SetIdx(id); + + return wxDataViewItem(node_parent->GetNthChild(new_volume_id+shift)); +} + +wxDataViewItem ObjectDataViewModel::ReorganizeObjects( const int current_id, const int new_id) +{ + if (current_id == new_id) + return wxDataViewItem(nullptr); + + ObjectDataViewModelNode* deleted_node = m_objects[current_id]; + m_objects.erase(m_objects.begin() + current_id); + ItemDeleted(wxDataViewItem(nullptr), wxDataViewItem(deleted_node)); + + m_objects.emplace(m_objects.begin() + new_id, deleted_node); + ItemAdded(wxDataViewItem(nullptr), wxDataViewItem(deleted_node)); + + return wxDataViewItem(deleted_node); +} + +bool ObjectDataViewModel::IsEnabled(const wxDataViewItem &item, unsigned int col) const +{ + wxASSERT(item.IsOk()); + ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); + + // disable extruder selection for the non "itObject|itVolume" item + return !(col == colExtruder && node->m_extruder.IsEmpty()); +} + +wxDataViewItem ObjectDataViewModel::GetParent(const wxDataViewItem &item) const +{ + // the invisible root node has no parent + if (!item.IsOk()) + return wxDataViewItem(0); + + ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); + assert(node != nullptr && node->valid()); + + // objects nodes has no parent too + if (node->m_type == itObject) + return wxDataViewItem(0); + + return wxDataViewItem((void*)node->GetParent()); +} + +wxDataViewItem ObjectDataViewModel::GetTopParent(const wxDataViewItem &item) const +{ + // the invisible root node has no parent + if (!item.IsOk()) + return wxDataViewItem(0); + + ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); + if (node->m_type == itObject) + return item; + + ObjectDataViewModelNode *parent_node = node->GetParent(); + while (parent_node->m_type != itObject) + parent_node = parent_node->GetParent(); + + return wxDataViewItem((void*)parent_node); +} + +bool ObjectDataViewModel::IsContainer(const wxDataViewItem &item) const +{ + // the invisible root node can have children + if (!item.IsOk()) + return true; + + ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); + return node->IsContainer(); +} + +unsigned int ObjectDataViewModel::GetChildren(const wxDataViewItem &parent, wxDataViewItemArray &array) const +{ + ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)parent.GetID(); + if (!node) + { + for (auto object : m_objects) + array.Add(wxDataViewItem((void*)object)); + return m_objects.size(); + } + + if (node->GetChildCount() == 0) + { + return 0; + } + + unsigned int count = node->GetChildren().GetCount(); + for (unsigned int pos = 0; pos < count; pos++) + { + ObjectDataViewModelNode *child = node->GetChildren().Item(pos); + array.Add(wxDataViewItem((void*)child)); + } + + return count; +} + +void ObjectDataViewModel::GetAllChildren(const wxDataViewItem &parent, wxDataViewItemArray &array) const +{ + ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)parent.GetID(); + if (!node) { + for (auto object : m_objects) + array.Add(wxDataViewItem((void*)object)); + } + else if (node->GetChildCount() == 0) + return; + else { + const size_t count = node->GetChildren().GetCount(); + for (size_t pos = 0; pos < count; pos++) { + ObjectDataViewModelNode *child = node->GetChildren().Item(pos); + array.Add(wxDataViewItem((void*)child)); + } + } + + wxDataViewItemArray new_array = array; + for (const auto item : new_array) + { + wxDataViewItemArray children; + GetAllChildren(item, children); + WX_APPEND_ARRAY(array, children); + } +} + +ItemType ObjectDataViewModel::GetItemType(const wxDataViewItem &item) const +{ + if (!item.IsOk()) + return itUndef; + ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); + return node->m_type < 0 ? itUndef : node->m_type; +} + +wxDataViewItem ObjectDataViewModel::GetItemByType(const wxDataViewItem &parent_item, ItemType type) const +{ + if (!parent_item.IsOk()) + return wxDataViewItem(0); + + ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)parent_item.GetID(); + if (node->GetChildCount() == 0) + return wxDataViewItem(0); + + for (size_t i = 0; i < node->GetChildCount(); i++) { + if (node->GetNthChild(i)->m_type == type) + return wxDataViewItem((void*)node->GetNthChild(i)); + } + + return wxDataViewItem(0); +} + +wxDataViewItem ObjectDataViewModel::GetSettingsItem(const wxDataViewItem &item) const +{ + return GetItemByType(item, itSettings); +} + +wxDataViewItem ObjectDataViewModel::GetInstanceRootItem(const wxDataViewItem &item) const +{ + return GetItemByType(item, itInstanceRoot); +} + +wxDataViewItem ObjectDataViewModel::GetLayerRootItem(const wxDataViewItem &item) const +{ + return GetItemByType(item, itLayerRoot); +} + +bool ObjectDataViewModel::IsSettingsItem(const wxDataViewItem &item) const +{ + if (!item.IsOk()) + return false; + ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); + return node->m_type == itSettings; +} + +void ObjectDataViewModel::UpdateSettingsDigest(const wxDataViewItem &item, + const std::vector& categories) +{ + if (!item.IsOk()) return; + ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); + if (!node->update_settings_digest(categories)) + return; + ItemChanged(item); +} + +void ObjectDataViewModel::SetVolumeType(const wxDataViewItem &item, const Slic3r::ModelVolumeType type) +{ + if (!item.IsOk() || GetItemType(item) != itVolume) + return; + + ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); + node->SetBitmap(*m_volume_bmps[int(type)]); + ItemChanged(item); +} + +wxDataViewItem ObjectDataViewModel::SetPrintableState( + PrintIndicator printable, + int obj_idx, + int subobj_idx /* = -1*/, + ItemType subobj_type/* = itInstance*/) +{ + wxDataViewItem item = wxDataViewItem(0); + if (subobj_idx < 0) + item = GetItemById(obj_idx); + else + item = subobj_type&itInstance ? GetItemByInstanceId(obj_idx, subobj_idx) : + GetItemByVolumeId(obj_idx, subobj_idx); + + ObjectDataViewModelNode* node = (ObjectDataViewModelNode*)item.GetID(); + if (!node) + return wxDataViewItem(0); + node->set_printable_icon(printable); + ItemChanged(item); + + if (subobj_idx >= 0) + UpdateObjectPrintable(GetItemById(obj_idx)); + + return item; +} + +wxDataViewItem ObjectDataViewModel::SetObjectPrintableState( + PrintIndicator printable, + wxDataViewItem obj_item) +{ + ObjectDataViewModelNode* node = (ObjectDataViewModelNode*)obj_item.GetID(); + if (!node) + return wxDataViewItem(0); + node->set_printable_icon(printable); + ItemChanged(obj_item); + + UpdateInstancesPrintable(obj_item); + + return obj_item; +} + +void ObjectDataViewModel::Rescale() +{ + wxDataViewItemArray all_items; + GetAllChildren(wxDataViewItem(0), all_items); + + for (wxDataViewItem item : all_items) + { + if (!item.IsOk()) + continue; + + ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); + node->msw_rescale(); + + switch (node->m_type) + { + case itObject: + if (node->m_bmp.IsOk()) node->m_bmp = *m_warning_bmp; + break; + case itVolume: + node->m_bmp = GetVolumeIcon(node->m_volume_type, node->m_bmp.GetWidth() != node->m_bmp.GetHeight()); + break; + case itLayerRoot: + node->m_bmp = create_scaled_bitmap(LAYER_ROOT_ICON); + case itLayer: + node->m_bmp = create_scaled_bitmap(LAYER_ICON); + break; + default: break; + } + + ItemChanged(item); + } +} + +wxBitmap ObjectDataViewModel::GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, const bool is_marked/* = false*/) +{ + if (!is_marked) + return *m_volume_bmps[static_cast(vol_type)]; + + std::string scaled_bitmap_name = "warning" + std::to_string(static_cast(vol_type)); + scaled_bitmap_name += "-em" + std::to_string(Slic3r::GUI::wxGetApp().em_unit()); + + wxBitmap *bmp = m_bitmap_cache->find(scaled_bitmap_name); + if (bmp == nullptr) { + std::vector bmps; + + bmps.emplace_back(*m_warning_bmp); + bmps.emplace_back(*m_volume_bmps[static_cast(vol_type)]); + + bmp = m_bitmap_cache->insert(scaled_bitmap_name, bmps); + } + + return *bmp; +} + +void ObjectDataViewModel::DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object/* = false*/) +{ + if (!item.IsOk()) + return; + + ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); + + if (!node->GetBitmap().IsOk() || !(node->GetType() & (itVolume | itObject))) + return; + + if (node->GetType() & itVolume) { + node->SetBitmap(*m_volume_bmps[static_cast(node->volume_type())]); + return; + } + + node->SetBitmap(wxNullBitmap); + if (unmark_object) + { + wxDataViewItemArray children; + GetChildren(item, children); + for (const wxDataViewItem& child : children) + DeleteWarningIcon(child); + } +} +/* +} +} +*/ +//----------------------------------------------------------------------------- +// DataViewBitmapText +//----------------------------------------------------------------------------- + +wxIMPLEMENT_DYNAMIC_CLASS(DataViewBitmapText, wxObject) + +IMPLEMENT_VARIANT_OBJECT(DataViewBitmapText) + +// --------------------------------------------------------- +// BitmapTextRenderer +// --------------------------------------------------------- + +#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING +BitmapTextRenderer::BitmapTextRenderer(wxDataViewCellMode mode /*= wxDATAVIEW_CELL_EDITABLE*/, + int align /*= wxDVR_DEFAULT_ALIGNMENT*/): +wxDataViewRenderer(wxT("PrusaDataViewBitmapText"), mode, align) +{ + SetMode(mode); + SetAlignment(align); +} +#endif // ENABLE_NONCUSTOM_DATA_VIEW_RENDERING + +bool BitmapTextRenderer::SetValue(const wxVariant &value) +{ + m_value << value; + return true; +} + +bool BitmapTextRenderer::GetValue(wxVariant& WXUNUSED(value)) const +{ + return false; +} + +#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING && wxUSE_ACCESSIBILITY +wxString BitmapTextRenderer::GetAccessibleDescription() const +{ + return m_value.GetText(); +} +#endif // wxUSE_ACCESSIBILITY && ENABLE_NONCUSTOM_DATA_VIEW_RENDERING + +bool BitmapTextRenderer::Render(wxRect rect, wxDC *dc, int state) +{ + int xoffset = 0; + + const wxBitmap& icon = m_value.GetBitmap(); + if (icon.IsOk()) + { +#ifdef __APPLE__ + wxSize icon_sz = icon.GetScaledSize(); +#else + wxSize icon_sz = icon.GetSize(); +#endif + dc->DrawBitmap(icon, rect.x, rect.y + (rect.height - icon_sz.y) / 2); + xoffset = icon_sz.x + 4; + } + + RenderText(m_value.GetText(), xoffset, rect, dc, state); + + return true; +} + +wxSize BitmapTextRenderer::GetSize() const +{ + if (!m_value.GetText().empty()) + { + wxSize size = GetTextExtent(m_value.GetText()); + + if (m_value.GetBitmap().IsOk()) + size.x += m_value.GetBitmap().GetWidth() + 4; + return size; + } + return wxSize(80, 20); +} + + +wxWindow* BitmapTextRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value) +{ + wxDataViewCtrl* const dv_ctrl = GetOwner()->GetOwner(); + ObjectDataViewModel* const model = dynamic_cast(dv_ctrl->GetModel()); + + if ( !(model->GetItemType(dv_ctrl->GetSelection()) & (itVolume | itObject)) ) + return nullptr; + + DataViewBitmapText data; + data << value; + + m_was_unusable_symbol = false; + + wxPoint position = labelRect.GetPosition(); + if (data.GetBitmap().IsOk()) { + const int bmp_width = data.GetBitmap().GetWidth(); + position.x += bmp_width; + labelRect.SetWidth(labelRect.GetWidth() - bmp_width); + } + + wxTextCtrl* text_editor = new wxTextCtrl(parent, wxID_ANY, data.GetText(), + position, labelRect.GetSize(), wxTE_PROCESS_ENTER); + text_editor->SetInsertionPointEnd(); + text_editor->SelectAll(); + + return text_editor; +} + +bool BitmapTextRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) +{ + wxTextCtrl* text_editor = wxDynamicCast(ctrl, wxTextCtrl); + if (!text_editor || text_editor->GetValue().IsEmpty()) + return false; + + std::string chosen_name = Slic3r::normalize_utf8_nfc(text_editor->GetValue().ToUTF8()); + const char* unusable_symbols = "<>:/\\|?*\""; + for (size_t i = 0; i < std::strlen(unusable_symbols); i++) { + if (chosen_name.find_first_of(unusable_symbols[i]) != std::string::npos) { + m_was_unusable_symbol = true; + return false; + } + } + + // The icon can't be edited so get its old value and reuse it. + wxVariant valueOld; + GetView()->GetModel()->GetValue(valueOld, m_item, colName); + + DataViewBitmapText bmpText; + bmpText << valueOld; + + // But replace the text with the value entered by user. + bmpText.SetText(text_editor->GetValue()); + + value << bmpText; + return true; +} + +// ---------------------------------------------------------------------------- +// BitmapChoiceRenderer +// ---------------------------------------------------------------------------- + +bool BitmapChoiceRenderer::SetValue(const wxVariant& value) +{ + m_value << value; + return true; +} + +bool BitmapChoiceRenderer::GetValue(wxVariant& value) const +{ + value << m_value; + return true; +} + +bool BitmapChoiceRenderer::Render(wxRect rect, wxDC* dc, int state) +{ + int xoffset = 0; + + const wxBitmap& icon = m_value.GetBitmap(); + if (icon.IsOk()) + { + dc->DrawBitmap(icon, rect.x, rect.y + (rect.height - icon.GetHeight()) / 2); + xoffset = icon.GetWidth() + 4; + } + + if (rect.height==0) + rect.height= icon.GetHeight(); + RenderText(m_value.GetText(), xoffset, rect, dc, state); + + return true; +} + +wxSize BitmapChoiceRenderer::GetSize() const +{ + wxSize sz = GetTextExtent(m_value.GetText()); + + if (m_value.GetBitmap().IsOk()) + sz.x += m_value.GetBitmap().GetWidth() + 4; + + return sz; +} + + +wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value) +{ + wxDataViewCtrl* const dv_ctrl = GetOwner()->GetOwner(); + ObjectDataViewModel* const model = dynamic_cast(dv_ctrl->GetModel()); + + if (!(model->GetItemType(dv_ctrl->GetSelection()) & (itVolume | itLayer | itObject))) + return nullptr; + + std::vector icons = get_extruder_color_icons(); + if (icons.empty()) + return nullptr; + + DataViewBitmapText data; + data << value; + + auto c_editor = new wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, + labelRect.GetTopLeft(), wxSize(labelRect.GetWidth(), -1), + 0, nullptr , wxCB_READONLY); + + int i=0; + for (wxBitmap* bmp : icons) { + if (i==0) { + c_editor->Append(_(L("default")), *bmp); + ++i; + } + + c_editor->Append(wxString::Format("%d", i), *bmp); + ++i; + } + c_editor->SetSelection(atoi(data.GetText().c_str())); + + // to avoid event propagation to other sidebar items + c_editor->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) { + evt.StopPropagation(); + // FinishEditing grabs new selection and triggers config update. We better call + // it explicitly, automatic update on KILL_FOCUS didn't work on Linux. + this->FinishEditing(); + }); + + return c_editor; +} + +bool BitmapChoiceRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) +{ + wxBitmapComboBox* c = (wxBitmapComboBox*)ctrl; + int selection = c->GetSelection(); + if (selection < 0) + return false; + + DataViewBitmapText bmpText; + + bmpText.SetText(c->GetString(selection)); + bmpText.SetBitmap(c->GetItemBitmap(selection)); + + value << bmpText; + return true; +} + +} // namespace GUI +} // namespace Slic3r + + diff --git a/src/slic3r/GUI/ObjectDataViewModel.hpp b/src/slic3r/GUI/ObjectDataViewModel.hpp new file mode 100644 index 0000000000..3d838cd435 --- /dev/null +++ b/src/slic3r/GUI/ObjectDataViewModel.hpp @@ -0,0 +1,516 @@ +#ifndef slic3r_GUI_ObjectDataViewModel_hpp_ +#define slic3r_GUI_ObjectDataViewModel_hpp_ + +#include + +#include + +namespace Slic3r { + +enum class ModelVolumeType : int; + +namespace GUI { + +typedef double coordf_t; +typedef std::pair t_layer_height_range; + +// ---------------------------------------------------------------------------- +// DataViewBitmapText: helper class used by BitmapTextRenderer +// ---------------------------------------------------------------------------- + +class DataViewBitmapText : public wxObject +{ +public: + DataViewBitmapText( const wxString &text = wxEmptyString, + const wxBitmap& bmp = wxNullBitmap) : + m_text(text), + m_bmp(bmp) + { } + + DataViewBitmapText(const DataViewBitmapText &other) + : wxObject(), + m_text(other.m_text), + m_bmp(other.m_bmp) + { } + + void SetText(const wxString &text) { m_text = text; } + wxString GetText() const { return m_text; } + void SetBitmap(const wxBitmap &bmp) { m_bmp = bmp; } + const wxBitmap &GetBitmap() const { return m_bmp; } + + bool IsSameAs(const DataViewBitmapText& other) const { + return m_text == other.m_text && m_bmp.IsSameAs(other.m_bmp); + } + + bool operator==(const DataViewBitmapText& other) const { + return IsSameAs(other); + } + + bool operator!=(const DataViewBitmapText& other) const { + return !IsSameAs(other); + } + +private: + wxString m_text; + wxBitmap m_bmp; + + wxDECLARE_DYNAMIC_CLASS(DataViewBitmapText); +}; +DECLARE_VARIANT_OBJECT(DataViewBitmapText) + +// ---------------------------------------------------------------------------- +// BitmapTextRenderer +// ---------------------------------------------------------------------------- +#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING +class BitmapTextRenderer : public wxDataViewRenderer +#else +class BitmapTextRenderer : public wxDataViewCustomRenderer +#endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING +{ +public: + BitmapTextRenderer(wxWindow* parent, + wxDataViewCellMode mode = +#ifdef __WXOSX__ + wxDATAVIEW_CELL_INERT +#else + wxDATAVIEW_CELL_EDITABLE +#endif + + , int align = wxDVR_DEFAULT_ALIGNMENT +#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING + ); +#else + ) : + wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align), + m_parent(parent) + {} +#endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING + + bool SetValue(const wxVariant& value); + bool GetValue(wxVariant& value) const; +#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING && wxUSE_ACCESSIBILITY + virtual wxString GetAccessibleDescription() const override; +#endif // wxUSE_ACCESSIBILITY && ENABLE_NONCUSTOM_DATA_VIEW_RENDERING + + virtual bool Render(wxRect cell, wxDC* dc, int state) override; + virtual wxSize GetSize() const override; + + bool HasEditorCtrl() const override + { +#ifdef __WXOSX__ + return false; +#else + return true; +#endif + } + wxWindow* CreateEditorCtrl(wxWindow* parent, + wxRect labelRect, + const wxVariant& value) override; + bool GetValueFromEditorCtrl(wxWindow* ctrl, + wxVariant& value) override; + bool WasCanceled() const { return m_was_unusable_symbol; } + +private: + DataViewBitmapText m_value; + bool m_was_unusable_symbol{ false }; + wxWindow* m_parent{ nullptr }; +}; + + +// ---------------------------------------------------------------------------- +// BitmapChoiceRenderer +// ---------------------------------------------------------------------------- + +class BitmapChoiceRenderer : public wxDataViewCustomRenderer +{ +public: + BitmapChoiceRenderer(wxDataViewCellMode mode = +#ifdef __WXOSX__ + wxDATAVIEW_CELL_INERT +#else + wxDATAVIEW_CELL_EDITABLE +#endif + , int align = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL + ) : wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align) {} + + bool SetValue(const wxVariant& value); + bool GetValue(wxVariant& value) const; + + virtual bool Render(wxRect cell, wxDC* dc, int state) override; + virtual wxSize GetSize() const override; + + bool HasEditorCtrl() const override { return true; } + wxWindow* CreateEditorCtrl(wxWindow* parent, + wxRect labelRect, + const wxVariant& value) override; + bool GetValueFromEditorCtrl(wxWindow* ctrl, + wxVariant& value) override; + +private: + DataViewBitmapText m_value; +}; + + +// ---------------------------------------------------------------------------- +// ObjectDataViewModelNode: a node inside ObjectDataViewModel +// ---------------------------------------------------------------------------- +enum ItemType { + itUndef = 0, + itObject = 1, + itVolume = 2, + itInstanceRoot = 4, + itInstance = 8, + itSettings = 16, + itLayerRoot = 32, + itLayer = 64, +}; + +enum ColumnNumber +{ + colName = 0, // item name + colPrint , // printable property + colExtruder , // extruder selection + colEditing , // item editing +}; + +enum PrintIndicator +{ + piUndef = 0, // no print indicator + piPrintable , // printable + piUnprintable , // unprintable +}; + +class ObjectDataViewModelNode; +WX_DEFINE_ARRAY_PTR(ObjectDataViewModelNode*, MyObjectTreeModelNodePtrArray); + +class ObjectDataViewModelNode +{ + ObjectDataViewModelNode* m_parent; + MyObjectTreeModelNodePtrArray m_children; + wxBitmap m_empty_bmp; + size_t m_volumes_cnt = 0; + std::vector< std::string > m_opt_categories; + t_layer_height_range m_layer_range = { 0.0f, 0.0f }; + + wxString m_name; + wxBitmap& m_bmp = m_empty_bmp; + ItemType m_type; + int m_idx = -1; + bool m_container = false; + wxString m_extruder = "default"; + wxBitmap m_extruder_bmp; + wxBitmap m_action_icon; + PrintIndicator m_printable {piUndef}; + wxBitmap m_printable_icon; + + std::string m_action_icon_name = ""; + ModelVolumeType m_volume_type; + +public: + ObjectDataViewModelNode(const wxString& name, + const wxString& extruder): + m_parent(NULL), + m_name(name), + m_type(itObject), + m_extruder(extruder) + { + set_action_and_extruder_icons(); + init_container(); + } + + ObjectDataViewModelNode(ObjectDataViewModelNode* parent, + const wxString& sub_obj_name, + const wxBitmap& bmp, + const wxString& extruder, + const int idx = -1 ) : + m_parent (parent), + m_name (sub_obj_name), + m_type (itVolume), + m_idx (idx), + m_extruder (extruder) + { + m_bmp = bmp; + set_action_and_extruder_icons(); + init_container(); + } + + ObjectDataViewModelNode(ObjectDataViewModelNode* parent, + const t_layer_height_range& layer_range, + const int idx = -1, + const wxString& extruder = wxEmptyString ); + + ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const ItemType type); + + ~ObjectDataViewModelNode() + { + // free all our children nodes + size_t count = m_children.GetCount(); + for (size_t i = 0; i < count; i++) + { + ObjectDataViewModelNode *child = m_children[i]; + delete child; + } +#ifndef NDEBUG + // Indicate that the object was deleted. + m_idx = -2; +#endif /* NDEBUG */ + } + + void init_container(); + bool IsContainer() const + { + return m_container; + } + + ObjectDataViewModelNode* GetParent() + { + assert(m_parent == nullptr || m_parent->valid()); + return m_parent; + } + MyObjectTreeModelNodePtrArray& GetChildren() + { + return m_children; + } + ObjectDataViewModelNode* GetNthChild(unsigned int n) + { + return m_children.Item(n); + } + void Insert(ObjectDataViewModelNode* child, unsigned int n) + { + if (!m_container) + m_container = true; + m_children.Insert(child, n); + } + void Append(ObjectDataViewModelNode* child) + { + if (!m_container) + m_container = true; + m_children.Add(child); + } + void RemoveAllChildren() + { + if (GetChildCount() == 0) + return; + for (int id = int(GetChildCount()) - 1; id >= 0; --id) + { + if (m_children.Item(id)->GetChildCount() > 0) + m_children[id]->RemoveAllChildren(); + auto node = m_children[id]; + m_children.RemoveAt(id); + delete node; + } + } + + size_t GetChildCount() const + { + return m_children.GetCount(); + } + + bool SetValue(const wxVariant &variant, unsigned int col); + + void SetBitmap(const wxBitmap &icon) { m_bmp = icon; } + const wxBitmap& GetBitmap() const { return m_bmp; } + const wxString& GetName() const { return m_name; } + ItemType GetType() const { return m_type; } + void SetIdx(const int& idx); + int GetIdx() const { return m_idx; } + t_layer_height_range GetLayerRange() const { return m_layer_range; } + PrintIndicator IsPrintable() const { return m_printable; } + + // use this function only for childrens + void AssignAllVal(ObjectDataViewModelNode& from_node) + { + // ! Don't overwrite other values because of equality of this values for all children -- + m_name = from_node.m_name; + m_bmp = from_node.m_bmp; + m_idx = from_node.m_idx; + m_extruder = from_node.m_extruder; + m_type = from_node.m_type; + } + + bool SwapChildrens(int frst_id, int scnd_id) { + if (GetChildCount() < 2 || + frst_id < 0 || (size_t)frst_id >= GetChildCount() || + scnd_id < 0 || (size_t)scnd_id >= GetChildCount()) + return false; + + ObjectDataViewModelNode new_scnd = *GetNthChild(frst_id); + ObjectDataViewModelNode new_frst = *GetNthChild(scnd_id); + + new_scnd.m_idx = m_children.Item(scnd_id)->m_idx; + new_frst.m_idx = m_children.Item(frst_id)->m_idx; + + m_children.Item(frst_id)->AssignAllVal(new_frst); + m_children.Item(scnd_id)->AssignAllVal(new_scnd); + return true; + } + + // Set action icons for node + void set_action_and_extruder_icons(); + // Set printable icon for node + void set_printable_icon(PrintIndicator printable); + + void update_settings_digest_bitmaps(); + bool update_settings_digest(const std::vector& categories); + int volume_type() const { return int(m_volume_type); } + void msw_rescale(); + +#ifndef NDEBUG + bool valid(); +#endif /* NDEBUG */ + bool invalid() const { return m_idx < -1; } + +private: + friend class ObjectDataViewModel; +}; + + +// ---------------------------------------------------------------------------- +// ObjectDataViewModel +// ---------------------------------------------------------------------------- + +// custom message the model sends to associated control to notify a last volume deleted from the object: +wxDECLARE_EVENT(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, wxCommandEvent); + +class ObjectDataViewModel :public wxDataViewModel +{ + std::vector m_objects; + std::vector m_volume_bmps; + wxBitmap* m_warning_bmp { nullptr }; + + wxDataViewCtrl* m_ctrl { nullptr }; + +public: + ObjectDataViewModel(); + ~ObjectDataViewModel(); + + wxDataViewItem Add( const wxString &name, + const int extruder, + const bool has_errors = false); + wxDataViewItem AddVolumeChild( const wxDataViewItem &parent_item, + const wxString &name, + const Slic3r::ModelVolumeType volume_type, + const bool has_errors = false, + const int extruder = 0, + const bool create_frst_child = true); + wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item); + wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, size_t num); + wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, const std::vector& print_indicator); + wxDataViewItem AddLayersRoot(const wxDataViewItem &parent_item); + wxDataViewItem AddLayersChild( const wxDataViewItem &parent_item, + const t_layer_height_range& layer_range, + const int extruder = 0, + const int index = -1); + wxDataViewItem Delete(const wxDataViewItem &item); + wxDataViewItem DeleteLastInstance(const wxDataViewItem &parent_item, size_t num); + void DeleteAll(); + void DeleteChildren(wxDataViewItem& parent); + void DeleteVolumeChildren(wxDataViewItem& parent); + void DeleteSettings(const wxDataViewItem& parent); + wxDataViewItem GetItemById(int obj_idx); + wxDataViewItem GetItemById(const int obj_idx, const int sub_obj_idx, const ItemType parent_type); + wxDataViewItem GetItemByVolumeId(int obj_idx, int volume_idx); + wxDataViewItem GetItemByInstanceId(int obj_idx, int inst_idx); + wxDataViewItem GetItemByLayerId(int obj_idx, int layer_idx); + wxDataViewItem GetItemByLayerRange(const int obj_idx, const t_layer_height_range& layer_range); + int GetItemIdByLayerRange(const int obj_idx, const t_layer_height_range& layer_range); + int GetIdByItem(const wxDataViewItem& item) const; + int GetIdByItemAndType(const wxDataViewItem& item, const ItemType type) const; + int GetObjectIdByItem(const wxDataViewItem& item) const; + int GetVolumeIdByItem(const wxDataViewItem& item) const; + int GetInstanceIdByItem(const wxDataViewItem& item) const; + int GetLayerIdByItem(const wxDataViewItem& item) const; + void GetItemInfo(const wxDataViewItem& item, ItemType& type, int& obj_idx, int& idx); + int GetRowByItem(const wxDataViewItem& item) const; + bool IsEmpty() { return m_objects.empty(); } + bool InvalidItem(const wxDataViewItem& item); + + // helper method for wxLog + + wxString GetName(const wxDataViewItem &item) const; + wxBitmap& GetBitmap(const wxDataViewItem &item) const; + wxString GetExtruder(const wxDataViewItem &item) const; + int GetExtruderNumber(const wxDataViewItem &item) const; + + // helper methods to change the model + + virtual unsigned int GetColumnCount() const override { return 3;} + virtual wxString GetColumnType(unsigned int col) const override{ return wxT("string"); } + + virtual void GetValue( wxVariant &variant, + const wxDataViewItem &item, + unsigned int col) const override; + virtual bool SetValue( const wxVariant &variant, + const wxDataViewItem &item, + unsigned int col) override; + bool SetValue( const wxVariant &variant, + const int item_idx, + unsigned int col); + + void SetExtruder(const wxString& extruder, wxDataViewItem item); + + // For parent move child from cur_volume_id place to new_volume_id + // Remaining items will moved up/down accordingly + wxDataViewItem ReorganizeChildren( const int cur_volume_id, + const int new_volume_id, + const wxDataViewItem &parent); + wxDataViewItem ReorganizeObjects( int current_id, int new_id); + + virtual bool IsEnabled(const wxDataViewItem &item, unsigned int col) const override; + + virtual wxDataViewItem GetParent(const wxDataViewItem &item) const override; + // get object item + wxDataViewItem GetTopParent(const wxDataViewItem &item) const; + virtual bool IsContainer(const wxDataViewItem &item) const override; + virtual unsigned int GetChildren(const wxDataViewItem &parent, + wxDataViewItemArray &array) const override; + void GetAllChildren(const wxDataViewItem &parent,wxDataViewItemArray &array) const; + // Is the container just a header or an item with all columns + // In our case it is an item with all columns + virtual bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; } + + ItemType GetItemType(const wxDataViewItem &item) const ; + wxDataViewItem GetItemByType( const wxDataViewItem &parent_item, + ItemType type) const; + wxDataViewItem GetSettingsItem(const wxDataViewItem &item) const; + wxDataViewItem GetInstanceRootItem(const wxDataViewItem &item) const; + wxDataViewItem GetLayerRootItem(const wxDataViewItem &item) const; + bool IsSettingsItem(const wxDataViewItem &item) const; + void UpdateSettingsDigest( const wxDataViewItem &item, + const std::vector& categories); + + bool IsPrintable(const wxDataViewItem &item) const; + void UpdateObjectPrintable(wxDataViewItem parent_item); + void UpdateInstancesPrintable(wxDataViewItem parent_item); + + void SetVolumeBitmaps(const std::vector& volume_bmps) { m_volume_bmps = volume_bmps; } + void SetWarningBitmap(wxBitmap* bitmap) { m_warning_bmp = bitmap; } + void SetVolumeType(const wxDataViewItem &item, const Slic3r::ModelVolumeType type); + wxDataViewItem SetPrintableState( PrintIndicator printable, int obj_idx, + int subobj_idx = -1, + ItemType subobj_type = itInstance); + wxDataViewItem SetObjectPrintableState(PrintIndicator printable, wxDataViewItem obj_item); + + void SetAssociatedControl(wxDataViewCtrl* ctrl) { m_ctrl = ctrl; } + // Rescale bitmaps for existing Items + void Rescale(); + + wxBitmap GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, + const bool is_marked = false); + void DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object = false); + t_layer_height_range GetLayerRangeByItem(const wxDataViewItem& item) const; + + bool UpdateColumValues(unsigned col); + void UpdateExtruderBitmap(wxDataViewItem item); + +private: + wxDataViewItem AddRoot(const wxDataViewItem& parent_item, const ItemType root_type); + wxDataViewItem AddInstanceRoot(const wxDataViewItem& parent_item); +}; + + +} +} + + +#endif // slic3r_GUI_ObjectDataViewModel_hpp_ diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 37ea24a16c..8afd98d11e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -169,7 +169,7 @@ ObjectInfo::ObjectInfo(wxWindow *parent) : info_manifold_text->SetFont(wxGetApp().small_font()); info_manifold = new wxStaticText(parent, wxID_ANY, ""); info_manifold->SetFont(wxGetApp().small_font()); - manifold_warning_icon = new wxStaticBitmap(parent, wxID_ANY, create_scaled_bitmap(parent, "exclamation")); + manifold_warning_icon = new wxStaticBitmap(parent, wxID_ANY, create_scaled_bitmap("exclamation")); auto *sizer_manifold = new wxBoxSizer(wxHORIZONTAL); sizer_manifold->Add(info_manifold_text, 0); sizer_manifold->Add(manifold_warning_icon, 0, wxLEFT, 2); @@ -188,7 +188,7 @@ void ObjectInfo::show_sizer(bool show) void ObjectInfo::msw_rescale() { - manifold_warning_icon->SetBitmap(create_scaled_bitmap(nullptr, "exclamation")); + manifold_warning_icon->SetBitmap(create_scaled_bitmap("exclamation")); } enum SlicedInfoIdx @@ -258,7 +258,7 @@ void SlicedInfo::SetTextAndShow(SlicedInfoIdx idx, const wxString& text, const w } PresetComboBox::PresetComboBox(wxWindow *parent, Preset::Type preset_type) : -wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(15 * wxGetApp().em_unit(), -1), 0, nullptr, wxCB_READONLY), +PresetBitmapComboBox(parent, wxSize(15 * wxGetApp().em_unit(), -1)), preset_type(preset_type), last_selected(wxNOT_FOUND), m_em_unit(wxGetApp().em_unit()) @@ -1875,6 +1875,7 @@ struct Plater::priv } void export_gcode(fs::path output_path, PrintHostJob upload_job); void reload_from_disk(); + void reload_all_from_disk(); void fix_through_netfabb(const int obj_idx, const int vol_idx = -1); void set_current_panel(wxPanel* panel); @@ -2075,6 +2076,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, [this](SimpleEvent&) { this->view3D->get_canvas3d()->reset_layer_height_profile(); }); view3D_canvas->Bind(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, [this](Event& evt) { this->view3D->get_canvas3d()->adaptive_layer_height_profile(evt.data); }); view3D_canvas->Bind(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, [this](HeightProfileSmoothEvent& evt) { this->view3D->get_canvas3d()->smooth_layer_height_profile(evt.data); }); + view3D_canvas->Bind(EVT_GLCANVAS_RELOAD_FROM_DISK, [this](SimpleEvent&) { if (!this->model.objects.empty()) this->reload_all_from_disk(); }); // 3DScene/Toolbar: view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this); @@ -3447,6 +3449,24 @@ void Plater::priv::reload_from_disk() } } +void Plater::priv::reload_all_from_disk() +{ + Plater::TakeSnapshot snapshot(q, _(L("Reload all from disk"))); + Plater::SuppressSnapshots suppress(q); + + Selection& selection = get_selection(); + Selection::IndicesList curr_idxs = selection.get_volume_idxs(); + // reload from disk uses selection + select_all(); + reload_from_disk(); + // restore previous selection + selection.clear(); + for (unsigned int idx : curr_idxs) + { + selection.add(idx, false); + } +} + void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* = -1*/) { if (obj_idx < 0) @@ -5034,6 +5054,11 @@ void Plater::reload_from_disk() p->reload_from_disk(); } +void Plater::reload_all_from_disk() +{ + p->reload_all_from_disk(); +} + bool Plater::has_toolpaths_to_export() const { return p->preview->get_canvas3d()->has_toolpaths_to_export(); @@ -5376,6 +5401,13 @@ void Plater::force_filament_colors_update() this->p->schedule_background_process(); } +void Plater::force_print_bed_update() +{ + // Fill in the printer model key with something which cannot possibly be valid, so that Plater::on_config_change() will update the print bed + // once a new Printer profile config is loaded. + p->config->opt_string("printer_model", true) = "\x01\x00\x01"; +} + void Plater::on_activate() { #ifdef __linux__ @@ -5392,11 +5424,6 @@ void Plater::on_activate() this->p->show_delayed_error_message(); } -const DynamicPrintConfig* Plater::get_plater_config() const -{ - return p->config; -} - // Get vector of extruder colors considering filament color, if extruder color is undefined. std::vector Plater::get_extruder_colors_from_plater_config() const { diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 9639a16932..568727abf3 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -12,6 +12,7 @@ #include "3DScene.hpp" #include "GLTexture.hpp" +#include "wxExtensions.hpp" class wxButton; class ScalableButton; @@ -49,7 +50,7 @@ using t_optgroups = std::vector >; class Plater; enum class ActionButtonType : int; -class PresetComboBox : public wxBitmapComboBox +class PresetComboBox : public PresetBitmapComboBox { public: PresetComboBox(wxWindow *parent, Preset::Type preset_type); @@ -193,6 +194,7 @@ public: void export_amf(); void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path()); void reload_from_disk(); + void reload_all_from_disk(); bool has_toolpaths_to_export() const; void export_toolpaths_to_obj() const; void hollow(); @@ -227,9 +229,9 @@ public: void on_extruders_change(size_t extruders_count); void on_config_change(const DynamicPrintConfig &config); void force_filament_colors_update(); + void force_print_bed_update(); // On activating the parent window. void on_activate(); - const DynamicPrintConfig* get_plater_config() const; std::vector get_extruder_colors_from_plater_config() const; std::vector get_colors_for_color_print() const; diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index e22675869a..98fcf3f421 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -550,7 +550,6 @@ const std::vector& Preset::sla_printer_options() s_opts = { "printer_technology", "bed_shape", "bed_custom_texture", "bed_custom_model", "max_print_height", - "bed_shape", "max_print_height", "display_width", "display_height", "display_pixels_x", "display_pixels_y", "display_mirror_x", "display_mirror_y", "display_orientation", @@ -874,18 +873,14 @@ bool PresetCollection::delete_preset(const std::string& name) return true; } -void PresetCollection::load_bitmap_default(wxWindow *window, const std::string &file_name) +void PresetCollection::load_bitmap_default(const std::string &file_name) { - // XXX: See note in PresetBundle::load_compatible_bitmaps() - (void)window; - *m_bitmap_main_frame = create_scaled_bitmap(nullptr, file_name); + *m_bitmap_main_frame = create_scaled_bitmap(file_name); } -void PresetCollection::load_bitmap_add(wxWindow *window, const std::string &file_name) +void PresetCollection::load_bitmap_add(const std::string &file_name) { - // XXX: See note in PresetBundle::load_compatible_bitmaps() - (void)window; - *m_bitmap_add = create_scaled_bitmap(nullptr, file_name); + *m_bitmap_add = create_scaled_bitmap(file_name); } const Preset* PresetCollection::get_selected_preset_parent() const diff --git a/src/slic3r/GUI/Preset.hpp b/src/slic3r/GUI/Preset.hpp index c859335200..83970419cc 100644 --- a/src/slic3r/GUI/Preset.hpp +++ b/src/slic3r/GUI/Preset.hpp @@ -313,10 +313,10 @@ public: bool delete_preset(const std::string& name); // Load default bitmap to be placed at the wxBitmapComboBox of a MainFrame. - void load_bitmap_default(wxWindow *window, const std::string &file_name); + void load_bitmap_default(const std::string &file_name); // Load "add new printer" bitmap to be placed at the wxBitmapComboBox of a MainFrame. - void load_bitmap_add(wxWindow *window, const std::string &file_name); + void load_bitmap_add(const std::string &file_name); // Compatible & incompatible marks, to be placed at the wxBitmapComboBox items. void set_bitmap_compatible (const wxBitmap *bmp) { m_bitmap_compatible = bmp; } diff --git a/src/slic3r/GUI/PresetBundle.cpp b/src/slic3r/GUI/PresetBundle.cpp index 6c80a9eab6..0c187e871e 100644 --- a/src/slic3r/GUI/PresetBundle.cpp +++ b/src/slic3r/GUI/PresetBundle.cpp @@ -480,19 +480,12 @@ void PresetBundle::export_selections(AppConfig &config) config.set("presets", "printer", printers.get_selected_preset_name()); } -void PresetBundle::load_compatible_bitmaps(wxWindow *window) +void PresetBundle::load_compatible_bitmaps() { - // We don't actually pass the window pointer here and instead generate - // a low DPI bitmap, because the wxBitmapComboBox and wxDataViewCtrl don't support - // high DPI bitmaps very well, they compute their dimensions wrong. - // TODO: Update this when fixed in wxWidgets - // See also PresetCollection::load_bitmap_default() and PresetCollection::load_bitmap_add() - - (void)window; - *m_bitmapCompatible = create_scaled_bitmap(nullptr, "flag_green"); - *m_bitmapIncompatible = create_scaled_bitmap(nullptr, "flag_red"); - *m_bitmapLock = create_scaled_bitmap(nullptr, "lock_closed"); - *m_bitmapLockOpen = create_scaled_bitmap(nullptr, "lock_open"); + *m_bitmapCompatible = create_scaled_bitmap("flag_green"); + *m_bitmapIncompatible = create_scaled_bitmap("flag_red"); + *m_bitmapLock = create_scaled_bitmap("lock_closed"); + *m_bitmapLockOpen = create_scaled_bitmap("lock_open"); prints .set_bitmap_compatible(m_bitmapCompatible); filaments .set_bitmap_compatible(m_bitmapCompatible); @@ -1536,31 +1529,7 @@ void PresetBundle::set_filament_preset(size_t idx, const std::string &name) filament_presets[idx] = Preset::remove_suffix_modified(name); } -static inline int hex_digit_to_int(const char c) -{ - return - (c >= '0' && c <= '9') ? int(c - '0') : - (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 : - (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1; -} - -bool PresetBundle::parse_color(const std::string &scolor, unsigned char *rgb_out) -{ - rgb_out[0] = rgb_out[1] = rgb_out[2] = 0; - if (scolor.size() != 7 || scolor.front() != '#') - return false; - const char *c = scolor.data() + 1; - for (size_t i = 0; i < 3; ++ i) { - int digit1 = hex_digit_to_int(*c ++); - int digit2 = hex_digit_to_int(*c ++); - if (digit1 == -1 || digit2 == -1) - return false; - rgb_out[i] = (unsigned char)(digit1 * 16 + digit2); - } - return true; -} - -void PresetBundle::load_default_preset_bitmaps(wxWindow *window) +void PresetBundle::load_default_preset_bitmaps() { // Clear bitmap cache, before load new scaled default preset bitmaps m_bitmapCache->clear(); @@ -1570,13 +1539,13 @@ void PresetBundle::load_default_preset_bitmaps(wxWindow *window) this->sla_materials.clear_bitmap_cache(); this->printers.clear_bitmap_cache(); - this->prints.load_bitmap_default(window, "cog"); - this->sla_prints.load_bitmap_default(window, "cog"); - this->filaments.load_bitmap_default(window, "spool.png"); - this->sla_materials.load_bitmap_default(window, "resin"); - this->printers.load_bitmap_default(window, "printer"); - this->printers.load_bitmap_add(window, "add.png"); - this->load_compatible_bitmaps(window); + this->prints.load_bitmap_default("cog"); + this->sla_prints.load_bitmap_default("cog"); + this->filaments.load_bitmap_default("spool.png"); + this->sla_materials.load_bitmap_default("resin"); + this->printers.load_bitmap_default("printer"); + this->printers.load_bitmap_add("add.png"); + this->load_compatible_bitmaps(); } void PresetBundle::update_plater_filament_ui(unsigned int idx_extruder, GUI::PresetComboBox *ui) @@ -1587,7 +1556,7 @@ void PresetBundle::update_plater_filament_ui(unsigned int idx_extruder, GUI::Pre unsigned char rgb[3]; std::string extruder_color = this->printers.get_edited_preset().config.opt_string("extruder_colour", idx_extruder); - if (! parse_color(extruder_color, rgb)) + if (!m_bitmapCache->parse_color(extruder_color, rgb)) // Extruder color is not defined. extruder_color.clear(); @@ -1623,7 +1592,12 @@ void PresetBundle::update_plater_filament_ui(unsigned int idx_extruder, GUI::Pre // To avoid asserts, each added bitmap to wxBitmapCombobox should be the same size, so // set a bitmap height to m_bitmapLock->GetHeight() - const int icon_height = m_bitmapLock->GetHeight();//2 * icon_unit; //16 * scale_f + 0.5f; + // Note, under OSX we should use a ScaledHeight because of Retina scale +#ifdef __APPLE__ + const int icon_height = m_bitmapLock->GetScaledHeight(); +#else + const int icon_height = m_bitmapLock->GetHeight(); +#endif wxString tooltip = ""; @@ -1652,10 +1626,10 @@ void PresetBundle::update_plater_filament_ui(unsigned int idx_extruder, GUI::Pre // Paint a red flag for incompatible presets. bmps.emplace_back(preset.is_compatible ? m_bitmapCache->mkclear(normal_icon_width, icon_height) : *m_bitmapIncompatible); // Paint the color bars. - parse_color(filament_rgb, rgb); + m_bitmapCache->parse_color(filament_rgb, rgb); bmps.emplace_back(m_bitmapCache->mksolid(single_bar ? wide_icon_width : normal_icon_width, icon_height, rgb)); if (! single_bar) { - parse_color(extruder_rgb, rgb); + m_bitmapCache->parse_color(extruder_rgb, rgb); bmps.emplace_back(m_bitmapCache->mksolid(thin_icon_width, icon_height, rgb)); } // Paint a lock at the system presets. diff --git a/src/slic3r/GUI/PresetBundle.hpp b/src/slic3r/GUI/PresetBundle.hpp index ff27d0023d..b818107338 100644 --- a/src/slic3r/GUI/PresetBundle.hpp +++ b/src/slic3r/GUI/PresetBundle.hpp @@ -54,8 +54,7 @@ public: // There will be an entry for each system profile loaded, // and the system profiles will point to the VendorProfile instances owned by PresetBundle::vendors. - // std::set vendors; - VendorMap vendors; + VendorMap vendors; struct ObsoletePresets { std::vector prints; @@ -130,9 +129,7 @@ public: // preset if the current print or filament preset is not compatible. void update_compatible(bool select_other_if_incompatible); - static bool parse_color(const std::string &scolor, unsigned char *rgb_out); - - void load_default_preset_bitmaps(wxWindow *window); + void load_default_preset_bitmaps(); // Set the is_visible flag for printer vendors, printer models and printer variants // based on the user configuration. @@ -161,7 +158,7 @@ private: // If it is not an external config, then the config will be stored into the user profile directory. void load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config); void load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree); - void load_compatible_bitmaps(wxWindow *window); + void load_compatible_bitmaps(); DynamicPrintConfig full_fff_config() const; DynamicPrintConfig full_sla_config() const; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index a34a3e0b75..72e2091670 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -114,7 +114,7 @@ void Tab::create_preset_tab() #endif //__WXOSX__ // preset chooser - m_presets_choice = new wxBitmapComboBox(panel, wxID_ANY, "", wxDefaultPosition, wxSize(35 * m_em_unit, -1), 0, 0, wxCB_READONLY); + m_presets_choice = new PresetBitmapComboBox(panel, wxSize(35 * m_em_unit, -1)); auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); @@ -1690,7 +1690,7 @@ void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup) auto printhost_cafile_browse = [this, optgroup] (wxWindow* parent) { auto btn = new wxButton(parent, wxID_ANY, " " + _(L("Browse"))+" " +dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT); btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); - btn->SetBitmap(create_scaled_bitmap(this, "browse")); + btn->SetBitmap(create_scaled_bitmap("browse")); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn); diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 4345f196ce..cfa5ae56d6 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -120,7 +120,7 @@ protected: Preset::Type m_type; std::string m_name; const wxString m_title; - wxBitmapComboBox* m_presets_choice; + PresetBitmapComboBox* m_presets_choice; ScalableButton* m_btn_save_preset; ScalableButton* m_btn_delete_preset; ScalableButton* m_btn_hide_incompatible_presets; diff --git a/src/slic3r/GUI/UpdateDialogs.cpp b/src/slic3r/GUI/UpdateDialogs.cpp index 00e7725ac1..d5c69be0b7 100644 --- a/src/slic3r/GUI/UpdateDialogs.cpp +++ b/src/slic3r/GUI/UpdateDialogs.cpp @@ -149,7 +149,7 @@ MsgDataIncompatible::MsgDataIncompatible(const std::unordered_mapSetBitmap(create_scaled_bitmap(this, "PrusaSlicer_192px_grayscale.png", 192)); + logo->SetBitmap(create_scaled_bitmap("PrusaSlicer_192px_grayscale.png", this, 192)); auto *text = new wxStaticText(this, wxID_ANY, wxString::Format(_(L( "This version of %s is not compatible with currently installed configuration bundles.\n" diff --git a/src/slic3r/GUI/WipeTowerDialog.cpp b/src/slic3r/GUI/WipeTowerDialog.cpp index 5394225456..970cd85284 100644 --- a/src/slic3r/GUI/WipeTowerDialog.cpp +++ b/src/slic3r/GUI/WipeTowerDialog.cpp @@ -1,7 +1,7 @@ #include #include #include "WipeTowerDialog.hpp" -#include "PresetBundle.hpp" +#include "BitmapCache.hpp" #include "GUI.hpp" #include "I18N.hpp" #include "GUI_App.hpp" @@ -191,7 +191,7 @@ WipingPanel::WipingPanel(wxWindow* parent, const std::vector& matrix, con for (const std::string& color : extruder_colours) { unsigned char rgb[3]; - Slic3r::PresetBundle::parse_color(color, rgb); + Slic3r::GUI::BitmapCache::parse_color(color, rgb); m_colours.push_back(wxColor(rgb[0], rgb[1], rgb[2])); } diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 356dd458fb..399fcdf036 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -3,16 +3,7 @@ #include #include -#include "libslic3r/Utils.hpp" -#include "libslic3r/Model.hpp" -#include "libslic3r/Print.hpp" - #include -#include -#include -#include -#include -#include #include @@ -20,18 +11,10 @@ #include "GUI.hpp" #include "GUI_App.hpp" #include "GUI_ObjectList.hpp" -#include "libslic3r/GCode/PreviewData.hpp" #include "I18N.hpp" #include "GUI_Utils.hpp" -#include "PresetBundle.hpp" -#include "ExtruderSequenceDialog.hpp" #include "../Utils/MacDarkMode.hpp" -using Slic3r::GUI::from_u8; -using Slic3r::GUI::into_u8; - -wxDEFINE_EVENT(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, wxCommandEvent); - #ifndef __WXGTK__// msw_menuitem_bitmaps is used for MSW and OSX static std::map msw_menuitem_bitmaps; #ifdef __WXMSW__ @@ -41,7 +24,7 @@ void msw_rescale_menu(wxMenu* menu) static void run(wxMenuItem* item) { const auto it = msw_menuitem_bitmaps.find(item->GetId()); if (it != msw_menuitem_bitmaps.end()) { - const wxBitmap& item_icon = create_scaled_bitmap(nullptr, it->second); + const wxBitmap& item_icon = create_scaled_bitmap(it->second); if (item_icon.IsOk()) item->SetBitmap(item_icon); } @@ -66,7 +49,7 @@ void enable_menu_item(wxUpdateUIEvent& evt, std::function const cb_condi const auto it = msw_menuitem_bitmaps.find(item->GetId()); if (it != msw_menuitem_bitmaps.end()) { - const wxBitmap& item_icon = create_scaled_bitmap(win, it->second, 16, false, !enable); + const wxBitmap& item_icon = create_scaled_bitmap(it->second, win, 16, !enable); if (item_icon.IsOk()) item->SetBitmap(item_icon); } @@ -108,7 +91,7 @@ wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const if (id == wxID_ANY) id = wxNewId(); - const wxBitmap& bmp = !icon.empty() ? create_scaled_bitmap(parent, icon) : wxNullBitmap; // FIXME: pass window ptr + const wxBitmap& bmp = !icon.empty() ? create_scaled_bitmap(icon) : wxNullBitmap; // FIXME: pass window ptr //#ifdef __WXMSW__ #ifndef __WXGTK__ if (bmp.IsOk()) @@ -126,7 +109,7 @@ wxMenuItem* append_submenu(wxMenu* menu, wxMenu* sub_menu, int id, const wxStrin wxMenuItem* item = new wxMenuItem(menu, id, string, description); if (!icon.empty()) { - item->SetBitmap(create_scaled_bitmap(parent, icon)); // FIXME: pass window ptr + item->SetBitmap(create_scaled_bitmap(icon)); // FIXME: pass window ptr //#ifdef __WXMSW__ #ifndef __WXGTK__ msw_menuitem_bitmaps[id] = icon; @@ -308,6 +291,94 @@ void wxCheckListBoxComboPopup::OnListBoxSelection(wxCommandEvent& evt) } +namespace Slic3r { +namespace GUI { + +// *** PresetBitmapComboBox *** + +/* For PresetBitmapComboBox we use bitmaps that are created from images that are already scaled appropriately for Retina + * (Contrary to the intuition, the `scale` argument for Bitmap's constructor doesn't mean + * "please scale this to such and such" but rather + * "the wxImage is already sized for backing scale such and such". ) + * Unfortunately, the constructor changes the size of wxBitmap too. + * Thus We need to use unscaled size value for bitmaps that we use + * to avoid scaled size of control items. + * For this purpose control drawing methods and + * control size calculation methods (virtual) are overridden. + **/ + +PresetBitmapComboBox::PresetBitmapComboBox(wxWindow* parent, const wxSize& size) : + wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, size, 0, nullptr, wxCB_READONLY) +{} + +#ifdef __APPLE__ +bool PresetBitmapComboBox::OnAddBitmap(const wxBitmap& bitmap) +{ + if (bitmap.IsOk()) + { + // we should use scaled! size values of bitmap + int width = (int)bitmap.GetScaledWidth(); + int height = (int)bitmap.GetScaledHeight(); + + if (m_usedImgSize.x < 0) + { + // If size not yet determined, get it from this image. + m_usedImgSize.x = width; + m_usedImgSize.y = height; + + // Adjust control size to vertically fit the bitmap + wxWindow* ctrl = GetControl(); + ctrl->InvalidateBestSize(); + wxSize newSz = ctrl->GetBestSize(); + wxSize sz = ctrl->GetSize(); + if (newSz.y > sz.y) + ctrl->SetSize(sz.x, newSz.y); + else + DetermineIndent(); + } + + wxCHECK_MSG(width == m_usedImgSize.x && height == m_usedImgSize.y, + false, + "you can only add images of same size"); + + return true; + } + + return false; +} + +void PresetBitmapComboBox::OnDrawItem(wxDC& dc, + const wxRect& rect, + int item, + int flags) const +{ + const wxBitmap& bmp = *(wxBitmap*)m_bitmaps[item]; + if (bmp.IsOk()) + { + // we should use scaled! size values of bitmap + wxCoord w = bmp.GetScaledWidth(); + wxCoord h = bmp.GetScaledHeight(); + + const int imgSpacingLeft = 4; + + // Draw the image centered + dc.DrawBitmap(bmp, + rect.x + (m_usedImgSize.x - w) / 2 + imgSpacingLeft, + rect.y + (rect.height - h) / 2, + true); + } + + wxString text = GetString(item); + if (!text.empty()) + dc.DrawText(text, + rect.x + m_imgAreaWidth + 1, + rect.y + (rect.height - dc.GetCharHeight()) / 2); +} +#endif +} +} + + // *** wxDataViewTreeCtrlComboPopup *** const unsigned int wxDataViewTreeCtrlComboPopup::DefaultWidth = 270; @@ -407,56 +478,24 @@ int em_unit(wxWindow* win) return Slic3r::GUI::wxGetApp().em_unit(); } -float get_svg_scale_factor(wxWindow *win) -{ -#ifdef __APPLE__ - // Note: win->GetContentScaleFactor() is not used anymore here because it tends to - // return bogus results quite often (such as 1.0 on Retina or even 0.0). - // We're using the max scaling factor across all screens because it's very likely to be good enough. - - static float max_scaling_factor = NAN; - if (std::isnan(max_scaling_factor)) { - max_scaling_factor = Slic3r::GUI::mac_max_scaling_factor(); - } - return win != nullptr ? max_scaling_factor : 1.0f; -#else - return 1.0f; -#endif -} - -// If an icon has horizontal orientation (width > height) call this function with is_horizontal = true -wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name_in, - const int px_cnt/* = 16*/, const bool is_horizontal /* = false*/, const bool grayscale/* = false*/) +// win is used to get a correct em_unit value +// It's important for bitmaps of dialogs. +// if win == nullptr, em_unit value of MainFrame will be used +wxBitmap create_scaled_bitmap( const std::string& bmp_name_in, + wxWindow *win/* = nullptr*/, + const int px_cnt/* = 16*/, + const bool grayscale/* = false*/) { static Slic3r::GUI::BitmapCache cache; -#ifdef __APPLE__ - // Note: win->GetContentScaleFactor() is not used anymore here because it tends to - // return bogus results quite often (such as 1.0 on Retina or even 0.0). - // We're using the max scaling factor across all screens because it's very likely to be good enough. - - static float max_scaling_factor = NAN; - if (std::isnan(max_scaling_factor)) { - max_scaling_factor = Slic3r::GUI::mac_max_scaling_factor(); - } - const float scale_factor = win != nullptr ? max_scaling_factor : 1.0f; -#else - (void)(win); - const float scale_factor = 1.0f; -#endif - - unsigned int height, width = height = 0; - unsigned int& scale_base = is_horizontal ? width : height; - - scale_base = (unsigned int)(em_unit(win) * px_cnt * 0.1f + 0.5f); + unsigned int width = 0; + unsigned int height = (unsigned int)(em_unit(win) * px_cnt * 0.1f + 0.5f); std::string bmp_name = bmp_name_in; boost::replace_last(bmp_name, ".png", ""); -// std::string bmp_name = icon_name_respected_to_mode(bmp_name_in); - // Try loading an SVG first, then PNG if SVG is not found: - wxBitmap *bmp = cache.load_svg(bmp_name, width, height, scale_factor, grayscale, Slic3r::GUI::wxGetApp().dark_mode()); + wxBitmap *bmp = cache.load_svg(bmp_name, width, height, grayscale, Slic3r::GUI::wxGetApp().dark_mode()); if (bmp == nullptr) { bmp = cache.load_png(bmp_name, width, height, grayscale); } @@ -469,10 +508,10 @@ wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name_in, return *bmp; } - -Slic3r::GUI::BitmapCache* m_bitmap_cache = nullptr; std::vector get_extruder_color_icons(bool thin_icon/* = false*/) { + static Slic3r::GUI::BitmapCache bmp_cache; + // Create the bitmap with color bars. std::vector bmps; std::vector colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config(); @@ -494,11 +533,12 @@ std::vector get_extruder_color_icons(bool thin_icon/* = false*/) { std::string bitmap_key = color + "-h" + std::to_string(icon_height) + "-w" + std::to_string(icon_width); - wxBitmap* bitmap = m_bitmap_cache->find(bitmap_key); + wxBitmap* bitmap = bmp_cache.find(bitmap_key); if (bitmap == nullptr) { // Paint the color icon. - Slic3r::PresetBundle::parse_color(color, rgb); - bitmap = m_bitmap_cache->insert(bitmap_key, m_bitmap_cache->mksolid(icon_width, icon_height, rgb)); + Slic3r::GUI::BitmapCache::parse_color(color, rgb); + // there is no neede to scale created solid bitmap + bitmap = bmp_cache.insert(bitmap_key, bmp_cache.mksolid(icon_width, icon_height, rgb, true)); } bmps.emplace_back(bitmap); } @@ -507,16 +547,6 @@ std::vector get_extruder_color_icons(bool thin_icon/* = false*/) } -static wxBitmap get_extruder_color_icon(size_t extruder_idx, bool thin_icon = false) -{ - // Create the bitmap with color bars. - std::vector bmps = get_extruder_color_icons(thin_icon); - if (bmps.empty()) - return wxNullBitmap; - - return *bmps[extruder_idx >= bmps.size() ? 0 : extruder_idx]; -} - void apply_extruder_selector(wxBitmapComboBox** ctrl, wxWindow* parent, const std::string& first_item/* = ""*/, @@ -563,1719 +593,6 @@ void apply_extruder_selector(wxBitmapComboBox** ctrl, } -// ***************************************************************************** -// ---------------------------------------------------------------------------- -// ObjectDataViewModelNode -// ---------------------------------------------------------------------------- - -void ObjectDataViewModelNode::init_container() -{ -#ifdef __WXGTK__ - // it's necessary on GTK because of control have to know if this item will be container - // in another case you couldn't to add subitem for this item - // it will be produce "segmentation fault" - m_container = true; -#endif //__WXGTK__ -} - -#define LAYER_ROOT_ICON "edit_layers_all" -#define LAYER_ICON "edit_layers_some" - -ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const ItemType type) : - m_parent(parent), - m_type(type), - m_extruder(wxEmptyString) -{ - if (type == itSettings) - m_name = "Settings to modified"; - else if (type == itInstanceRoot) - m_name = _(L("Instances")); - else if (type == itInstance) - { - m_idx = parent->GetChildCount(); - m_name = wxString::Format(_(L("Instance %d")), m_idx + 1); - - set_action_and_extruder_icons(); - } - else if (type == itLayerRoot) - { - m_bmp = create_scaled_bitmap(nullptr, LAYER_ROOT_ICON); // FIXME: pass window ptr - m_name = _(L("Layers")); - } - - if (type & (itInstanceRoot | itLayerRoot)) - init_container(); -} - -ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent, - const t_layer_height_range& layer_range, - const int idx /*= -1 */, - const wxString& extruder) : - m_parent(parent), - m_type(itLayer), - m_idx(idx), - m_layer_range(layer_range), - m_extruder(extruder) -{ - const int children_cnt = parent->GetChildCount(); - if (idx < 0) - m_idx = children_cnt; - else - { - // update indexes for another Laeyr Nodes - for (int i = m_idx; i < children_cnt; i++) - parent->GetNthChild(i)->SetIdx(i + 1); - } - const std::string label_range = (boost::format(" %.2f-%.2f ") % layer_range.first % layer_range.second).str(); - m_name = _(L("Range")) + label_range + "(" + _(L("mm")) + ")"; - m_bmp = create_scaled_bitmap(nullptr, LAYER_ICON); // FIXME: pass window ptr - - set_action_and_extruder_icons(); - init_container(); -} - -#ifndef NDEBUG -bool ObjectDataViewModelNode::valid() -{ - // Verify that the object was not deleted yet. - assert(m_idx >= -1); - return m_idx >= -1; -} -#endif /* NDEBUG */ - -void ObjectDataViewModelNode::set_action_and_extruder_icons() -{ - m_action_icon_name = m_type & itObject ? "advanced_plus" : - m_type & (itVolume | itLayer) ? "cog" : /*m_type & itInstance*/ "set_separate_obj"; - m_action_icon = create_scaled_bitmap(nullptr, m_action_icon_name); // FIXME: pass window ptr - - if (m_type & itInstance) - return; // don't set colored bitmap for Instance - - // set extruder bitmap - int extruder_idx = atoi(m_extruder.c_str()); - if (extruder_idx > 0) --extruder_idx; - m_extruder_bmp = get_extruder_color_icon(extruder_idx); -} - -void ObjectDataViewModelNode::set_printable_icon(PrintIndicator printable) -{ - m_printable = printable; - m_printable_icon = m_printable == piUndef ? m_empty_bmp : - create_scaled_bitmap(nullptr, m_printable == piPrintable ? "eye_open.png" : "eye_closed.png"); -} - -void ObjectDataViewModelNode::update_settings_digest_bitmaps() -{ - m_bmp = m_empty_bmp; - - std::map& categories_icon = Slic3r::GUI::wxGetApp().obj_list()->CATEGORY_ICON; - - std::string scaled_bitmap_name = m_name.ToUTF8().data(); - scaled_bitmap_name += "-em" + std::to_string(Slic3r::GUI::wxGetApp().em_unit()); - - wxBitmap *bmp = m_bitmap_cache->find(scaled_bitmap_name); - if (bmp == nullptr) { - std::vector bmps; - for (auto& cat : m_opt_categories) - bmps.emplace_back( categories_icon.find(cat) == categories_icon.end() ? - wxNullBitmap : categories_icon.at(cat)); - bmp = m_bitmap_cache->insert(scaled_bitmap_name, bmps); - } - - m_bmp = *bmp; -} - -bool ObjectDataViewModelNode::update_settings_digest(const std::vector& categories) -{ - if (m_type != itSettings || m_opt_categories == categories) - return false; - - m_opt_categories = categories; - m_name = wxEmptyString; - - for (auto& cat : m_opt_categories) - m_name += _(cat) + "; "; - if (!m_name.IsEmpty()) - m_name.erase(m_name.Length()-2, 2); // Delete last "; " - - update_settings_digest_bitmaps(); - - return true; -} - -void ObjectDataViewModelNode::msw_rescale() -{ - if (!m_action_icon_name.empty()) - m_action_icon = create_scaled_bitmap(nullptr, m_action_icon_name); - - if (m_printable != piUndef) - m_printable_icon = create_scaled_bitmap(nullptr, m_printable == piPrintable ? "eye_open.png" : "eye_closed.png"); - - if (!m_opt_categories.empty()) - update_settings_digest_bitmaps(); -} - -bool ObjectDataViewModelNode::SetValue(const wxVariant& variant, unsigned col) -{ - switch (col) - { - case colPrint: - m_printable_icon << variant; - return true; - case colName: { - DataViewBitmapText data; - data << variant; - m_bmp = data.GetBitmap(); - m_name = data.GetText(); - return true; } - case colExtruder: { - DataViewBitmapText data; - data << variant; - m_extruder_bmp = data.GetBitmap(); - m_extruder = data.GetText() == "0" ? _(L("default")) : data.GetText(); - return true; } - case colEditing: - m_action_icon << variant; - return true; - default: - printf("MyObjectTreeModel::SetValue: wrong column"); - } - return false; -} - -void ObjectDataViewModelNode::SetIdx(const int& idx) -{ - m_idx = idx; - // update name if this node is instance - if (m_type == itInstance) - m_name = wxString::Format(_(L("Instance %d")), m_idx + 1); -} - -// ***************************************************************************** -// ---------------------------------------------------------------------------- -// ObjectDataViewModel -// ---------------------------------------------------------------------------- - -static int get_root_idx(ObjectDataViewModelNode *parent_node, const ItemType root_type) -{ - // because of istance_root and layers_root are at the end of the list, so - // start locking from the end - for (int root_idx = parent_node->GetChildCount() - 1; root_idx >= 0; root_idx--) - { - // if there is SettingsItem or VolumeItem, then RootItems don't exist in current ObjectItem - if (parent_node->GetNthChild(root_idx)->GetType() & (itSettings | itVolume)) - break; - if (parent_node->GetNthChild(root_idx)->GetType() & root_type) - return root_idx; - } - - return -1; -} - -ObjectDataViewModel::ObjectDataViewModel() -{ - m_bitmap_cache = new Slic3r::GUI::BitmapCache; -} - -ObjectDataViewModel::~ObjectDataViewModel() -{ - for (auto object : m_objects) - delete object; - delete m_bitmap_cache; - m_bitmap_cache = nullptr; -} - -wxDataViewItem ObjectDataViewModel::Add(const wxString &name, - const int extruder, - const bool has_errors/* = false*/) -{ - const wxString extruder_str = extruder == 0 ? _(L("default")) : wxString::Format("%d", extruder); - auto root = new ObjectDataViewModelNode(name, extruder_str); - // Add error icon if detected auto-repaire - if (has_errors) - root->m_bmp = *m_warning_bmp; - - m_objects.push_back(root); - // notify control - wxDataViewItem child((void*)root); - wxDataViewItem parent((void*)NULL); - - ItemAdded(parent, child); - return child; -} - -wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent_item, - const wxString &name, - const Slic3r::ModelVolumeType volume_type, - const bool has_errors/* = false*/, - const int extruder/* = 0*/, - const bool create_frst_child/* = true*/) -{ - ObjectDataViewModelNode *root = (ObjectDataViewModelNode*)parent_item.GetID(); - if (!root) return wxDataViewItem(0); - - wxString extruder_str = extruder == 0 ? _(L("default")) : wxString::Format("%d", extruder); - - // get insertion position according to the existed Layers and/or Instances Items - int insert_position = get_root_idx(root, itLayerRoot); - if (insert_position < 0) - insert_position = get_root_idx(root, itInstanceRoot); - - const bool obj_errors = root->m_bmp.IsOk(); - - if (create_frst_child && root->m_volumes_cnt == 0) - { - const Slic3r::ModelVolumeType type = Slic3r::ModelVolumeType::MODEL_PART; - const auto node = new ObjectDataViewModelNode(root, root->m_name, GetVolumeIcon(type, obj_errors), extruder_str, 0); - node->m_volume_type = type; - - insert_position < 0 ? root->Append(node) : root->Insert(node, insert_position); - // notify control - const wxDataViewItem child((void*)node); - ItemAdded(parent_item, child); - - root->m_volumes_cnt++; - if (insert_position >= 0) insert_position++; - } - - const auto node = new ObjectDataViewModelNode(root, name, GetVolumeIcon(volume_type, has_errors), extruder_str, root->m_volumes_cnt); - insert_position < 0 ? root->Append(node) : root->Insert(node, insert_position); - - // if part with errors is added, but object wasn't marked, then mark it - if (!obj_errors && has_errors) - root->SetBitmap(*m_warning_bmp); - - // notify control - const wxDataViewItem child((void*)node); - ItemAdded(parent_item, child); - root->m_volumes_cnt++; - - node->m_volume_type = volume_type; - - return child; -} - -wxDataViewItem ObjectDataViewModel::AddSettingsChild(const wxDataViewItem &parent_item) -{ - ObjectDataViewModelNode *root = (ObjectDataViewModelNode*)parent_item.GetID(); - if (!root) return wxDataViewItem(0); - - const auto node = new ObjectDataViewModelNode(root, itSettings); - root->Insert(node, 0); - // notify control - const wxDataViewItem child((void*)node); - ItemAdded(parent_item, child); - return child; -} - -/* return values: - * true => root_node is created and added to the parent_root - * false => root node alredy exists -*/ -static bool append_root_node(ObjectDataViewModelNode *parent_node, - ObjectDataViewModelNode **root_node, - const ItemType root_type) -{ - const int inst_root_id = get_root_idx(parent_node, root_type); - - *root_node = inst_root_id < 0 ? - new ObjectDataViewModelNode(parent_node, root_type) : - parent_node->GetNthChild(inst_root_id); - - if (inst_root_id < 0) { - if ((root_type&itInstanceRoot) || - ( (root_type&itLayerRoot) && get_root_idx(parent_node, itInstanceRoot)<0) ) - parent_node->Append(*root_node); - else if (root_type&itLayerRoot) - parent_node->Insert(*root_node, static_cast(get_root_idx(parent_node, itInstanceRoot))); - return true; - } - - return false; -} - -wxDataViewItem ObjectDataViewModel::AddRoot(const wxDataViewItem &parent_item, ItemType root_type) -{ - ObjectDataViewModelNode *parent_node = (ObjectDataViewModelNode*)parent_item.GetID(); - if (!parent_node) return wxDataViewItem(0); - - // get InstanceRoot node - ObjectDataViewModelNode *root_node { nullptr }; - const bool appended = append_root_node(parent_node, &root_node, root_type); - if (!root_node) return wxDataViewItem(0); - - const wxDataViewItem root_item((void*)root_node); - - if (appended) - ItemAdded(parent_item, root_item);// notify control - return root_item; -} - -wxDataViewItem ObjectDataViewModel::AddInstanceRoot(const wxDataViewItem &parent_item) -{ - return AddRoot(parent_item, itInstanceRoot); -} - -wxDataViewItem ObjectDataViewModel::AddInstanceChild(const wxDataViewItem &parent_item, size_t num) -{ - std::vector print_indicator(num, true); - - // if InstanceRoot item isn't created for this moment - if (!GetInstanceRootItem(parent_item).IsOk()) - // use object's printable state to first instance - print_indicator[0] = IsPrintable(parent_item); - - return wxDataViewItem((void*)AddInstanceChild(parent_item, print_indicator)); -} - -wxDataViewItem ObjectDataViewModel::AddInstanceChild(const wxDataViewItem& parent_item, - const std::vector& print_indicator) -{ - const wxDataViewItem inst_root_item = AddInstanceRoot(parent_item); - if (!inst_root_item) return wxDataViewItem(0); - - ObjectDataViewModelNode* inst_root_node = (ObjectDataViewModelNode*)inst_root_item.GetID(); - - // Add instance nodes - ObjectDataViewModelNode *instance_node = nullptr; - size_t counter = 0; - while (counter < print_indicator.size()) { - instance_node = new ObjectDataViewModelNode(inst_root_node, itInstance); - - instance_node->set_printable_icon(print_indicator[counter] ? piPrintable : piUnprintable); - - inst_root_node->Append(instance_node); - // notify control - const wxDataViewItem instance_item((void*)instance_node); - ItemAdded(inst_root_item, instance_item); - ++counter; - } - - // update object_node printable property - UpdateObjectPrintable(parent_item); - - return wxDataViewItem((void*)instance_node); -} - -void ObjectDataViewModel::UpdateObjectPrintable(wxDataViewItem parent_item) -{ - const wxDataViewItem inst_root_item = GetInstanceRootItem(parent_item); - if (!inst_root_item) - return; - - ObjectDataViewModelNode* inst_root_node = (ObjectDataViewModelNode*)inst_root_item.GetID(); - - const size_t child_cnt = inst_root_node->GetChildren().Count(); - PrintIndicator obj_pi = piUnprintable; - for (size_t i=0; i < child_cnt; i++) - if (inst_root_node->GetNthChild(i)->IsPrintable() & piPrintable) { - obj_pi = piPrintable; - break; - } - // and set printable state for object_node to piUndef - ObjectDataViewModelNode* obj_node = (ObjectDataViewModelNode*)parent_item.GetID(); - obj_node->set_printable_icon(obj_pi); - ItemChanged(parent_item); -} - -// update printable property for all instances from object -void ObjectDataViewModel::UpdateInstancesPrintable(wxDataViewItem parent_item) -{ - const wxDataViewItem inst_root_item = GetInstanceRootItem(parent_item); - if (!inst_root_item) - return; - - ObjectDataViewModelNode* obj_node = (ObjectDataViewModelNode*)parent_item.GetID(); - const PrintIndicator obj_pi = obj_node->IsPrintable(); - - ObjectDataViewModelNode* inst_root_node = (ObjectDataViewModelNode*)inst_root_item.GetID(); - const size_t child_cnt = inst_root_node->GetChildren().Count(); - - for (size_t i=0; i < child_cnt; i++) - { - ObjectDataViewModelNode* inst_node = inst_root_node->GetNthChild(i); - // and set printable state for object_node to piUndef - inst_node->set_printable_icon(obj_pi); - ItemChanged(wxDataViewItem((void*)inst_node)); - } -} - -bool ObjectDataViewModel::IsPrintable(const wxDataViewItem& item) const -{ - ObjectDataViewModelNode* node = (ObjectDataViewModelNode*)item.GetID(); - if (!node) - return false; - - return node->IsPrintable() == piPrintable; -} - -wxDataViewItem ObjectDataViewModel::AddLayersRoot(const wxDataViewItem &parent_item) -{ - return AddRoot(parent_item, itLayerRoot); -} - -wxDataViewItem ObjectDataViewModel::AddLayersChild(const wxDataViewItem &parent_item, - const t_layer_height_range& layer_range, - const int extruder/* = 0*/, - const int index /* = -1*/) -{ - ObjectDataViewModelNode *parent_node = (ObjectDataViewModelNode*)parent_item.GetID(); - if (!parent_node) return wxDataViewItem(0); - - wxString extruder_str = extruder == 0 ? _(L("default")) : wxString::Format("%d", extruder); - - // get LayerRoot node - ObjectDataViewModelNode *layer_root_node; - wxDataViewItem layer_root_item; - - if (parent_node->GetType() & itLayerRoot) { - layer_root_node = parent_node; - layer_root_item = parent_item; - } - else { - const int root_idx = get_root_idx(parent_node, itLayerRoot); - if (root_idx < 0) return wxDataViewItem(0); - layer_root_node = parent_node->GetNthChild(root_idx); - layer_root_item = wxDataViewItem((void*)layer_root_node); - } - - // Add layer node - ObjectDataViewModelNode *layer_node = new ObjectDataViewModelNode(layer_root_node, layer_range, index, extruder_str); - if (index < 0) - layer_root_node->Append(layer_node); - else - layer_root_node->Insert(layer_node, index); - - // notify control - const wxDataViewItem layer_item((void*)layer_node); - ItemAdded(layer_root_item, layer_item); - - return layer_item; -} - -wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) -{ - auto ret_item = wxDataViewItem(0); - ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); - if (!node) // happens if item.IsOk()==false - return ret_item; - - auto node_parent = node->GetParent(); - wxDataViewItem parent(node_parent); - - // first remove the node from the parent's array of children; - // NOTE: MyObjectTreeModelNodePtrArray is only an array of _pointers_ - // thus removing the node from it doesn't result in freeing it - if (node_parent) { - if (node->m_type & (itInstanceRoot|itLayerRoot)) - { - // node can be deleted by the Delete, let's check its type while we safely can - bool is_instance_root = (node->m_type & itInstanceRoot); - - for (int i = int(node->GetChildCount() - 1); i >= (is_instance_root ? 1 : 0); i--) - Delete(wxDataViewItem(node->GetNthChild(i))); - - return parent; - } - - auto id = node_parent->GetChildren().Index(node); - auto idx = node->GetIdx(); - - - if (node->m_type & (itVolume|itLayer)) { - node_parent->m_volumes_cnt--; - DeleteSettings(item); - } - node_parent->GetChildren().Remove(node); - - if (id > 0) { - if (size_t(id) == node_parent->GetChildCount()) id--; - ret_item = wxDataViewItem(node_parent->GetChildren().Item(id)); - } - - //update idx value for remaining child-nodes - auto children = node_parent->GetChildren(); - for (size_t i = 0; i < node_parent->GetChildCount() && idx>=0; i++) - { - auto cur_idx = children[i]->GetIdx(); - if (cur_idx > idx) - children[i]->SetIdx(cur_idx-1); - } - - // if there is last instance item, delete both of it and instance root item - if (node_parent->GetChildCount() == 1 && node_parent->GetNthChild(0)->m_type == itInstance) - { - delete node; - ItemDeleted(parent, item); - - ObjectDataViewModelNode *last_instance_node = node_parent->GetNthChild(0); - PrintIndicator last_instance_printable = last_instance_node->IsPrintable(); - node_parent->GetChildren().Remove(last_instance_node); - delete last_instance_node; - ItemDeleted(parent, wxDataViewItem(last_instance_node)); - - ObjectDataViewModelNode *obj_node = node_parent->GetParent(); - obj_node->set_printable_icon(last_instance_printable); - obj_node->GetChildren().Remove(node_parent); - delete node_parent; - ret_item = wxDataViewItem(obj_node); - -#ifndef __WXGTK__ - if (obj_node->GetChildCount() == 0) - obj_node->m_container = false; -#endif //__WXGTK__ - ItemDeleted(ret_item, wxDataViewItem(node_parent)); - return ret_item; - } - - if (node->m_type & itInstance) - UpdateObjectPrintable(wxDataViewItem(node_parent->GetParent())); - - // if there was last layer item, delete this one and layers root item - if (node_parent->GetChildCount() == 0 && node_parent->m_type == itLayerRoot) - { - ObjectDataViewModelNode *obj_node = node_parent->GetParent(); - obj_node->GetChildren().Remove(node_parent); - delete node_parent; - ret_item = wxDataViewItem(obj_node); - -#ifndef __WXGTK__ - if (obj_node->GetChildCount() == 0) - obj_node->m_container = false; -#endif //__WXGTK__ - ItemDeleted(ret_item, wxDataViewItem(node_parent)); - return ret_item; - } - - // if there is last volume item after deleting, delete this last volume too - if (node_parent->GetChildCount() <= 3) // 3??? #ys_FIXME - { - int vol_cnt = 0; - int vol_idx = 0; - for (size_t i = 0; i < node_parent->GetChildCount(); ++i) { - if (node_parent->GetNthChild(i)->GetType() == itVolume) { - vol_idx = i; - vol_cnt++; - } - if (vol_cnt > 1) - break; - } - - if (vol_cnt == 1) { - delete node; - ItemDeleted(parent, item); - - ObjectDataViewModelNode *last_child_node = node_parent->GetNthChild(vol_idx); - DeleteSettings(wxDataViewItem(last_child_node)); - node_parent->GetChildren().Remove(last_child_node); - node_parent->m_volumes_cnt = 0; - delete last_child_node; - -#ifndef __WXGTK__ - if (node_parent->GetChildCount() == 0) - node_parent->m_container = false; -#endif //__WXGTK__ - ItemDeleted(parent, wxDataViewItem(last_child_node)); - - wxCommandEvent event(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED); - auto it = find(m_objects.begin(), m_objects.end(), node_parent); - event.SetInt(it == m_objects.end() ? -1 : it - m_objects.begin()); - wxPostEvent(m_ctrl, event); - - ret_item = parent; - - return ret_item; - } - } - } - else - { - auto it = find(m_objects.begin(), m_objects.end(), node); - size_t id = it - m_objects.begin(); - if (it != m_objects.end()) - { - // Delete all sub-items - int i = m_objects[id]->GetChildCount() - 1; - while (i >= 0) { - Delete(wxDataViewItem(m_objects[id]->GetNthChild(i))); - i = m_objects[id]->GetChildCount() - 1; - } - m_objects.erase(it); - } - if (id > 0) { - if(id == m_objects.size()) id--; - ret_item = wxDataViewItem(m_objects[id]); - } - } - // free the node - delete node; - - // set m_containet to FALSE if parent has no child - if (node_parent) { -#ifndef __WXGTK__ - if (node_parent->GetChildCount() == 0) - node_parent->m_container = false; -#endif //__WXGTK__ - ret_item = parent; - } - - // notify control - ItemDeleted(parent, item); - return ret_item; -} - -wxDataViewItem ObjectDataViewModel::DeleteLastInstance(const wxDataViewItem &parent_item, size_t num) -{ - auto ret_item = wxDataViewItem(0); - ObjectDataViewModelNode *parent_node = (ObjectDataViewModelNode*)parent_item.GetID(); - if (!parent_node) return ret_item; - - const int inst_root_id = get_root_idx(parent_node, itInstanceRoot); - if (inst_root_id < 0) return ret_item; - - wxDataViewItemArray items; - ObjectDataViewModelNode *inst_root_node = parent_node->GetNthChild(inst_root_id); - const wxDataViewItem inst_root_item((void*)inst_root_node); - - const int inst_cnt = inst_root_node->GetChildCount(); - const bool delete_inst_root_item = inst_cnt - num < 2 ? true : false; - - PrintIndicator last_inst_printable = piUndef; - - int stop = delete_inst_root_item ? 0 : inst_cnt - num; - for (int i = inst_cnt - 1; i >= stop;--i) { - ObjectDataViewModelNode *last_instance_node = inst_root_node->GetNthChild(i); - if (i==0) last_inst_printable = last_instance_node->IsPrintable(); - inst_root_node->GetChildren().Remove(last_instance_node); - delete last_instance_node; - ItemDeleted(inst_root_item, wxDataViewItem(last_instance_node)); - } - - if (delete_inst_root_item) { - ret_item = parent_item; - parent_node->GetChildren().Remove(inst_root_node); - parent_node->set_printable_icon(last_inst_printable); - ItemDeleted(parent_item, inst_root_item); - ItemChanged(parent_item); -#ifndef __WXGTK__ - if (parent_node->GetChildCount() == 0) - parent_node->m_container = false; -#endif //__WXGTK__ - } - - // update object_node printable property - UpdateObjectPrintable(parent_item); - - return ret_item; -} - -void ObjectDataViewModel::DeleteAll() -{ - while (!m_objects.empty()) - { - auto object = m_objects.back(); -// object->RemoveAllChildren(); - Delete(wxDataViewItem(object)); - } -} - -void ObjectDataViewModel::DeleteChildren(wxDataViewItem& parent) -{ - ObjectDataViewModelNode *root = (ObjectDataViewModelNode*)parent.GetID(); - if (!root) // happens if item.IsOk()==false - return; - - // first remove the node from the parent's array of children; - // NOTE: MyObjectTreeModelNodePtrArray is only an array of _pointers_ - // thus removing the node from it doesn't result in freeing it - auto& children = root->GetChildren(); - for (int id = root->GetChildCount() - 1; id >= 0; --id) - { - auto node = children[id]; - auto item = wxDataViewItem(node); - children.RemoveAt(id); - - if (node->m_type == itVolume) - root->m_volumes_cnt--; - - // free the node - delete node; - - // notify control - ItemDeleted(parent, item); - } - - // set m_containet to FALSE if parent has no child -#ifndef __WXGTK__ - root->m_container = false; -#endif //__WXGTK__ -} - -void ObjectDataViewModel::DeleteVolumeChildren(wxDataViewItem& parent) -{ - ObjectDataViewModelNode *root = (ObjectDataViewModelNode*)parent.GetID(); - if (!root) // happens if item.IsOk()==false - return; - - // first remove the node from the parent's array of children; - // NOTE: MyObjectTreeModelNodePtrArray is only an array of _pointers_ - // thus removing the node from it doesn't result in freeing it - auto& children = root->GetChildren(); - for (int id = root->GetChildCount() - 1; id >= 0; --id) - { - auto node = children[id]; - if (node->m_type != itVolume) - continue; - - auto item = wxDataViewItem(node); - DeleteSettings(item); - children.RemoveAt(id); - - // free the node - delete node; - - // notify control - ItemDeleted(parent, item); - } - root->m_volumes_cnt = 0; - - // set m_containet to FALSE if parent has no child -#ifndef __WXGTK__ - root->m_container = false; -#endif //__WXGTK__ -} - -void ObjectDataViewModel::DeleteSettings(const wxDataViewItem& parent) -{ - ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)parent.GetID(); - if (!node) return; - - // if volume has a "settings"item, than delete it before volume deleting - if (node->GetChildCount() > 0 && node->GetNthChild(0)->GetType() == itSettings) { - auto settings_node = node->GetNthChild(0); - auto settings_item = wxDataViewItem(settings_node); - node->GetChildren().RemoveAt(0); - delete settings_node; - ItemDeleted(parent, settings_item); - } -} - -wxDataViewItem ObjectDataViewModel::GetItemById(int obj_idx) -{ - if (size_t(obj_idx) >= m_objects.size()) - { - printf("Error! Out of objects range.\n"); - return wxDataViewItem(0); - } - return wxDataViewItem(m_objects[obj_idx]); -} - - -wxDataViewItem ObjectDataViewModel::GetItemByVolumeId(int obj_idx, int volume_idx) -{ - if (size_t(obj_idx) >= m_objects.size()) { - printf("Error! Out of objects range.\n"); - return wxDataViewItem(0); - } - - auto parent = m_objects[obj_idx]; - if (parent->GetChildCount() == 0 || - (parent->GetChildCount() == 1 && parent->GetNthChild(0)->GetType() & itSettings )) { - if (volume_idx == 0) - return GetItemById(obj_idx); - - printf("Error! Object has no one volume.\n"); - return wxDataViewItem(0); - } - - for (size_t i = 0; i < parent->GetChildCount(); i++) - if (parent->GetNthChild(i)->m_idx == volume_idx && parent->GetNthChild(i)->GetType() & itVolume) - return wxDataViewItem(parent->GetNthChild(i)); - - return wxDataViewItem(0); -} - -wxDataViewItem ObjectDataViewModel::GetItemById(const int obj_idx, const int sub_obj_idx, const ItemType parent_type) -{ - if (size_t(obj_idx) >= m_objects.size()) { - printf("Error! Out of objects range.\n"); - return wxDataViewItem(0); - } - - auto item = GetItemByType(wxDataViewItem(m_objects[obj_idx]), parent_type); - if (!item) - return wxDataViewItem(0); - - auto parent = (ObjectDataViewModelNode*)item.GetID(); - for (size_t i = 0; i < parent->GetChildCount(); i++) - if (parent->GetNthChild(i)->m_idx == sub_obj_idx) - return wxDataViewItem(parent->GetNthChild(i)); - - return wxDataViewItem(0); -} - -wxDataViewItem ObjectDataViewModel::GetItemByInstanceId(int obj_idx, int inst_idx) -{ - return GetItemById(obj_idx, inst_idx, itInstanceRoot); -} - -wxDataViewItem ObjectDataViewModel::GetItemByLayerId(int obj_idx, int layer_idx) -{ - return GetItemById(obj_idx, layer_idx, itLayerRoot); -} - -wxDataViewItem ObjectDataViewModel::GetItemByLayerRange(const int obj_idx, const t_layer_height_range& layer_range) -{ - if (size_t(obj_idx) >= m_objects.size()) { - printf("Error! Out of objects range.\n"); - return wxDataViewItem(0); - } - - auto item = GetItemByType(wxDataViewItem(m_objects[obj_idx]), itLayerRoot); - if (!item) - return wxDataViewItem(0); - - auto parent = (ObjectDataViewModelNode*)item.GetID(); - for (size_t i = 0; i < parent->GetChildCount(); i++) - if (parent->GetNthChild(i)->m_layer_range == layer_range) - return wxDataViewItem(parent->GetNthChild(i)); - - return wxDataViewItem(0); -} - -int ObjectDataViewModel::GetItemIdByLayerRange(const int obj_idx, const t_layer_height_range& layer_range) -{ - wxDataViewItem item = GetItemByLayerRange(obj_idx, layer_range); - if (!item) - return -1; - - return GetLayerIdByItem(item); -} - -int ObjectDataViewModel::GetIdByItem(const wxDataViewItem& item) const -{ - if(!item.IsOk()) - return -1; - - ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); - auto it = find(m_objects.begin(), m_objects.end(), node); - if (it == m_objects.end()) - return -1; - - return it - m_objects.begin(); -} - -int ObjectDataViewModel::GetIdByItemAndType(const wxDataViewItem& item, const ItemType type) const -{ - wxASSERT(item.IsOk()); - - ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); - if (!node || node->m_type != type) - return -1; - return node->GetIdx(); -} - -int ObjectDataViewModel::GetObjectIdByItem(const wxDataViewItem& item) const -{ - return GetIdByItem(GetTopParent(item)); -} - -int ObjectDataViewModel::GetVolumeIdByItem(const wxDataViewItem& item) const -{ - return GetIdByItemAndType(item, itVolume); -} - -int ObjectDataViewModel::GetInstanceIdByItem(const wxDataViewItem& item) const -{ - return GetIdByItemAndType(item, itInstance); -} - -int ObjectDataViewModel::GetLayerIdByItem(const wxDataViewItem& item) const -{ - return GetIdByItemAndType(item, itLayer); -} - -t_layer_height_range ObjectDataViewModel::GetLayerRangeByItem(const wxDataViewItem& item) const -{ - wxASSERT(item.IsOk()); - - ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); - if (!node || node->m_type != itLayer) - return { 0.0f, 0.0f }; - return node->GetLayerRange(); -} - -bool ObjectDataViewModel::UpdateColumValues(unsigned col) -{ - switch (col) - { - case colPrint: - case colName: - case colEditing: - return true; - case colExtruder: - { - wxDataViewItemArray items; - GetAllChildren(wxDataViewItem(nullptr), items); - - if (items.IsEmpty()) return false; - - for (auto item : items) - UpdateExtruderBitmap(item); - - return true; - } - default: - printf("MyObjectTreeModel::SetValue: wrong column"); - } - return false; -} - - -void ObjectDataViewModel::UpdateExtruderBitmap(wxDataViewItem item) -{ - wxString extruder = GetExtruder(item); - if (extruder.IsEmpty()) - return; - - // set extruder bitmap - int extruder_idx = atoi(extruder.c_str()); - if (extruder_idx > 0) --extruder_idx; - - const DataViewBitmapText extruder_val(extruder, get_extruder_color_icon(extruder_idx)); - - wxVariant value; - value << extruder_val; - - SetValue(value, item, colExtruder); -} - -void ObjectDataViewModel::GetItemInfo(const wxDataViewItem& item, ItemType& type, int& obj_idx, int& idx) -{ - wxASSERT(item.IsOk()); - type = itUndef; - - ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); - if (!node || - node->GetIdx() <-1 || - ( node->GetIdx() == -1 && - !(node->GetType() & (itObject | itSettings | itInstanceRoot | itLayerRoot/* | itLayer*/)) - ) - ) - return; - - idx = node->GetIdx(); - type = node->GetType(); - - ObjectDataViewModelNode *parent_node = node->GetParent(); - if (!parent_node) return; - - // get top parent (Object) node - while (parent_node->m_type != itObject) - parent_node = parent_node->GetParent(); - - auto it = find(m_objects.begin(), m_objects.end(), parent_node); - if (it != m_objects.end()) - obj_idx = it - m_objects.begin(); - else - type = itUndef; -} - -int ObjectDataViewModel::GetRowByItem(const wxDataViewItem& item) const -{ - if (m_objects.empty()) - return -1; - - int row_num = 0; - - for (size_t i = 0; i < m_objects.size(); i++) - { - row_num++; - if (item == wxDataViewItem(m_objects[i])) - return row_num; - - for (size_t j = 0; j < m_objects[i]->GetChildCount(); j++) - { - row_num++; - ObjectDataViewModelNode* cur_node = m_objects[i]->GetNthChild(j); - if (item == wxDataViewItem(cur_node)) - return row_num; - - if (cur_node->m_type == itVolume && cur_node->GetChildCount() == 1) - row_num++; - if (cur_node->m_type == itInstanceRoot) - { - row_num++; - for (size_t t = 0; t < cur_node->GetChildCount(); t++) - { - row_num++; - if (item == wxDataViewItem(cur_node->GetNthChild(t))) - return row_num; - } - } - } - } - - return -1; -} - -bool ObjectDataViewModel::InvalidItem(const wxDataViewItem& item) -{ - if (!item) - return true; - - ObjectDataViewModelNode* node = (ObjectDataViewModelNode*)item.GetID(); - if (!node || node->invalid()) - return true; - - return false; -} - -wxString ObjectDataViewModel::GetName(const wxDataViewItem &item) const -{ - ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); - if (!node) // happens if item.IsOk()==false - return wxEmptyString; - - return node->m_name; -} - -wxBitmap& ObjectDataViewModel::GetBitmap(const wxDataViewItem &item) const -{ - ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); - return node->m_bmp; -} - -wxString ObjectDataViewModel::GetExtruder(const wxDataViewItem& item) const -{ - ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); - if (!node) // happens if item.IsOk()==false - return wxEmptyString; - - return node->m_extruder; -} - -int ObjectDataViewModel::GetExtruderNumber(const wxDataViewItem& item) const -{ - ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); - if (!node) // happens if item.IsOk()==false - return 0; - - return atoi(node->m_extruder.c_str()); -} - -void ObjectDataViewModel::GetValue(wxVariant &variant, const wxDataViewItem &item, unsigned int col) const -{ - wxASSERT(item.IsOk()); - - ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); - switch (col) - { - case colPrint: - variant << node->m_printable_icon; - break; - case colName: - variant << DataViewBitmapText(node->m_name, node->m_bmp); - break; - case colExtruder: - variant << DataViewBitmapText(node->m_extruder, node->m_extruder_bmp); - break; - case colEditing: - variant << node->m_action_icon; - break; - default: - ; - } -} - -bool ObjectDataViewModel::SetValue(const wxVariant &variant, const wxDataViewItem &item, unsigned int col) -{ - wxASSERT(item.IsOk()); - - ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); - return node->SetValue(variant, col); -} - -bool ObjectDataViewModel::SetValue(const wxVariant &variant, const int item_idx, unsigned int col) -{ - if (size_t(item_idx) >= m_objects.size()) - return false; - - return m_objects[item_idx]->SetValue(variant, col); -} - -void ObjectDataViewModel::SetExtruder(const wxString& extruder, wxDataViewItem item) -{ - DataViewBitmapText extruder_val; - extruder_val.SetText(extruder); - - // set extruder bitmap - int extruder_idx = atoi(extruder.c_str()); - if (extruder_idx > 0) --extruder_idx; - extruder_val.SetBitmap(get_extruder_color_icon(extruder_idx)); - - wxVariant value; - value << extruder_val; - - SetValue(value, item, colExtruder); -} - -wxDataViewItem ObjectDataViewModel::ReorganizeChildren( const int current_volume_id, - const int new_volume_id, - const wxDataViewItem &parent) -{ - auto ret_item = wxDataViewItem(0); - if (current_volume_id == new_volume_id) - return ret_item; - wxASSERT(parent.IsOk()); - ObjectDataViewModelNode *node_parent = (ObjectDataViewModelNode*)parent.GetID(); - if (!node_parent) // happens if item.IsOk()==false - return ret_item; - - const size_t shift = node_parent->GetChildren().Item(0)->m_type == itSettings ? 1 : 0; - - ObjectDataViewModelNode *deleted_node = node_parent->GetNthChild(current_volume_id+shift); - node_parent->GetChildren().Remove(deleted_node); - ItemDeleted(parent, wxDataViewItem(deleted_node)); - node_parent->Insert(deleted_node, new_volume_id+shift); - ItemAdded(parent, wxDataViewItem(deleted_node)); - const auto settings_item = GetSettingsItem(wxDataViewItem(deleted_node)); - if (settings_item) - ItemAdded(wxDataViewItem(deleted_node), settings_item); - - //update volume_id value for child-nodes - auto children = node_parent->GetChildren(); - int id_frst = current_volume_id < new_volume_id ? current_volume_id : new_volume_id; - int id_last = current_volume_id > new_volume_id ? current_volume_id : new_volume_id; - for (int id = id_frst; id <= id_last; ++id) - children[id+shift]->SetIdx(id); - - return wxDataViewItem(node_parent->GetNthChild(new_volume_id+shift)); -} - -bool ObjectDataViewModel::IsEnabled(const wxDataViewItem &item, unsigned int col) const -{ - wxASSERT(item.IsOk()); - ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); - - // disable extruder selection for the non "itObject|itVolume" item - return !(col == colExtruder && node->m_extruder.IsEmpty()); -} - -wxDataViewItem ObjectDataViewModel::GetParent(const wxDataViewItem &item) const -{ - // the invisible root node has no parent - if (!item.IsOk()) - return wxDataViewItem(0); - - ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); - assert(node != nullptr && node->valid()); - - // objects nodes has no parent too - if (node->m_type == itObject) - return wxDataViewItem(0); - - return wxDataViewItem((void*)node->GetParent()); -} - -wxDataViewItem ObjectDataViewModel::GetTopParent(const wxDataViewItem &item) const -{ - // the invisible root node has no parent - if (!item.IsOk()) - return wxDataViewItem(0); - - ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); - if (node->m_type == itObject) - return item; - - ObjectDataViewModelNode *parent_node = node->GetParent(); - while (parent_node->m_type != itObject) - parent_node = parent_node->GetParent(); - - return wxDataViewItem((void*)parent_node); -} - -bool ObjectDataViewModel::IsContainer(const wxDataViewItem &item) const -{ - // the invisible root node can have children - if (!item.IsOk()) - return true; - - ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); - return node->IsContainer(); -} - -unsigned int ObjectDataViewModel::GetChildren(const wxDataViewItem &parent, wxDataViewItemArray &array) const -{ - ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)parent.GetID(); - if (!node) - { - for (auto object : m_objects) - array.Add(wxDataViewItem((void*)object)); - return m_objects.size(); - } - - if (node->GetChildCount() == 0) - { - return 0; - } - - unsigned int count = node->GetChildren().GetCount(); - for (unsigned int pos = 0; pos < count; pos++) - { - ObjectDataViewModelNode *child = node->GetChildren().Item(pos); - array.Add(wxDataViewItem((void*)child)); - } - - return count; -} - -void ObjectDataViewModel::GetAllChildren(const wxDataViewItem &parent, wxDataViewItemArray &array) const -{ - ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)parent.GetID(); - if (!node) { - for (auto object : m_objects) - array.Add(wxDataViewItem((void*)object)); - } - else if (node->GetChildCount() == 0) - return; - else { - const size_t count = node->GetChildren().GetCount(); - for (size_t pos = 0; pos < count; pos++) { - ObjectDataViewModelNode *child = node->GetChildren().Item(pos); - array.Add(wxDataViewItem((void*)child)); - } - } - - wxDataViewItemArray new_array = array; - for (const auto item : new_array) - { - wxDataViewItemArray children; - GetAllChildren(item, children); - WX_APPEND_ARRAY(array, children); - } -} - -ItemType ObjectDataViewModel::GetItemType(const wxDataViewItem &item) const -{ - if (!item.IsOk()) - return itUndef; - ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); - return node->m_type < 0 ? itUndef : node->m_type; -} - -wxDataViewItem ObjectDataViewModel::GetItemByType(const wxDataViewItem &parent_item, ItemType type) const -{ - if (!parent_item.IsOk()) - return wxDataViewItem(0); - - ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)parent_item.GetID(); - if (node->GetChildCount() == 0) - return wxDataViewItem(0); - - for (size_t i = 0; i < node->GetChildCount(); i++) { - if (node->GetNthChild(i)->m_type == type) - return wxDataViewItem((void*)node->GetNthChild(i)); - } - - return wxDataViewItem(0); -} - -wxDataViewItem ObjectDataViewModel::GetSettingsItem(const wxDataViewItem &item) const -{ - return GetItemByType(item, itSettings); -} - -wxDataViewItem ObjectDataViewModel::GetInstanceRootItem(const wxDataViewItem &item) const -{ - return GetItemByType(item, itInstanceRoot); -} - -wxDataViewItem ObjectDataViewModel::GetLayerRootItem(const wxDataViewItem &item) const -{ - return GetItemByType(item, itLayerRoot); -} - -bool ObjectDataViewModel::IsSettingsItem(const wxDataViewItem &item) const -{ - if (!item.IsOk()) - return false; - ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); - return node->m_type == itSettings; -} - -void ObjectDataViewModel::UpdateSettingsDigest(const wxDataViewItem &item, - const std::vector& categories) -{ - if (!item.IsOk()) return; - ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); - if (!node->update_settings_digest(categories)) - return; - ItemChanged(item); -} - -void ObjectDataViewModel::SetVolumeType(const wxDataViewItem &item, const Slic3r::ModelVolumeType type) -{ - if (!item.IsOk() || GetItemType(item) != itVolume) - return; - - ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); - node->SetBitmap(*m_volume_bmps[int(type)]); - ItemChanged(item); -} - -wxDataViewItem ObjectDataViewModel::SetPrintableState( - PrintIndicator printable, - int obj_idx, - int subobj_idx /* = -1*/, - ItemType subobj_type/* = itInstance*/) -{ - wxDataViewItem item = wxDataViewItem(0); - if (subobj_idx < 0) - item = GetItemById(obj_idx); - else - item = subobj_type&itInstance ? GetItemByInstanceId(obj_idx, subobj_idx) : - GetItemByVolumeId(obj_idx, subobj_idx); - - ObjectDataViewModelNode* node = (ObjectDataViewModelNode*)item.GetID(); - if (!node) - return wxDataViewItem(0); - node->set_printable_icon(printable); - ItemChanged(item); - - if (subobj_idx >= 0) - UpdateObjectPrintable(GetItemById(obj_idx)); - - return item; -} - -wxDataViewItem ObjectDataViewModel::SetObjectPrintableState( - PrintIndicator printable, - wxDataViewItem obj_item) -{ - ObjectDataViewModelNode* node = (ObjectDataViewModelNode*)obj_item.GetID(); - if (!node) - return wxDataViewItem(0); - node->set_printable_icon(printable); - ItemChanged(obj_item); - - UpdateInstancesPrintable(obj_item); - - return obj_item; -} - -void ObjectDataViewModel::Rescale() -{ - wxDataViewItemArray all_items; - GetAllChildren(wxDataViewItem(0), all_items); - - for (wxDataViewItem item : all_items) - { - if (!item.IsOk()) - continue; - - ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); - node->msw_rescale(); - - switch (node->m_type) - { - case itObject: - if (node->m_bmp.IsOk()) node->m_bmp = *m_warning_bmp; - break; - case itVolume: - node->m_bmp = GetVolumeIcon(node->m_volume_type, node->m_bmp.GetWidth() != node->m_bmp.GetHeight()); - break; - case itLayerRoot: - node->m_bmp = create_scaled_bitmap(nullptr, LAYER_ROOT_ICON); // FIXME: pass window ptr - break; - case itLayer: - node->m_bmp = create_scaled_bitmap(nullptr, LAYER_ICON); // FIXME: pass window ptr - break; - default: break; - } - - ItemChanged(item); - } -} - -wxBitmap ObjectDataViewModel::GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, const bool is_marked/* = false*/) -{ - if (!is_marked) - return *m_volume_bmps[static_cast(vol_type)]; - - std::string scaled_bitmap_name = "warning" + std::to_string(static_cast(vol_type)); - scaled_bitmap_name += "-em" + std::to_string(Slic3r::GUI::wxGetApp().em_unit()); - - wxBitmap *bmp = m_bitmap_cache->find(scaled_bitmap_name); - if (bmp == nullptr) { - std::vector bmps; - - bmps.emplace_back(*m_warning_bmp); - bmps.emplace_back(*m_volume_bmps[static_cast(vol_type)]); - - bmp = m_bitmap_cache->insert(scaled_bitmap_name, bmps); - } - - return *bmp; -} - -void ObjectDataViewModel::DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object/* = false*/) -{ - if (!item.IsOk()) - return; - - ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); - - if (!node->GetBitmap().IsOk() || !(node->GetType() & (itVolume | itObject))) - return; - - if (node->GetType() & itVolume) { - node->SetBitmap(*m_volume_bmps[static_cast(node->volume_type())]); - return; - } - - node->SetBitmap(wxNullBitmap); - if (unmark_object) - { - wxDataViewItemArray children; - GetChildren(item, children); - for (const wxDataViewItem& child : children) - DeleteWarningIcon(child); - } -} - -//----------------------------------------------------------------------------- -// DataViewBitmapText -//----------------------------------------------------------------------------- - -wxIMPLEMENT_DYNAMIC_CLASS(DataViewBitmapText, wxObject) - -IMPLEMENT_VARIANT_OBJECT(DataViewBitmapText) - -// --------------------------------------------------------- -// BitmapTextRenderer -// --------------------------------------------------------- - -#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING -BitmapTextRenderer::BitmapTextRenderer(wxDataViewCellMode mode /*= wxDATAVIEW_CELL_EDITABLE*/, - int align /*= wxDVR_DEFAULT_ALIGNMENT*/): -wxDataViewRenderer(wxT("PrusaDataViewBitmapText"), mode, align) -{ - SetMode(mode); - SetAlignment(align); -} -#endif // ENABLE_NONCUSTOM_DATA_VIEW_RENDERING - -bool BitmapTextRenderer::SetValue(const wxVariant &value) -{ - m_value << value; - return true; -} - -bool BitmapTextRenderer::GetValue(wxVariant& WXUNUSED(value)) const -{ - return false; -} - -#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING && wxUSE_ACCESSIBILITY -wxString BitmapTextRenderer::GetAccessibleDescription() const -{ - return m_value.GetText(); -} -#endif // wxUSE_ACCESSIBILITY && ENABLE_NONCUSTOM_DATA_VIEW_RENDERING - -bool BitmapTextRenderer::Render(wxRect rect, wxDC *dc, int state) -{ - int xoffset = 0; - - const wxBitmap& icon = m_value.GetBitmap(); - if (icon.IsOk()) - { - dc->DrawBitmap(icon, rect.x, rect.y + (rect.height - icon.GetHeight()) / 2); - xoffset = icon.GetWidth() + 4; - } - - RenderText(m_value.GetText(), xoffset, rect, dc, state); - - return true; -} - -wxSize BitmapTextRenderer::GetSize() const -{ - if (!m_value.GetText().empty()) - { - wxSize size = GetTextExtent(m_value.GetText()); - - if (m_value.GetBitmap().IsOk()) - size.x += m_value.GetBitmap().GetWidth() + 4; - return size; - } - return wxSize(80, 20); -} - - -wxWindow* BitmapTextRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value) -{ - wxDataViewCtrl* const dv_ctrl = GetOwner()->GetOwner(); - ObjectDataViewModel* const model = dynamic_cast(dv_ctrl->GetModel()); - - if ( !(model->GetItemType(dv_ctrl->GetSelection()) & (itVolume | itObject)) ) - return nullptr; - - DataViewBitmapText data; - data << value; - - m_was_unusable_symbol = false; - - wxPoint position = labelRect.GetPosition(); - if (data.GetBitmap().IsOk()) { - const int bmp_width = data.GetBitmap().GetWidth(); - position.x += bmp_width; - labelRect.SetWidth(labelRect.GetWidth() - bmp_width); - } - - wxTextCtrl* text_editor = new wxTextCtrl(parent, wxID_ANY, data.GetText(), - position, labelRect.GetSize(), wxTE_PROCESS_ENTER); - text_editor->SetInsertionPointEnd(); - text_editor->SelectAll(); - - return text_editor; -} - -bool BitmapTextRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) -{ - wxTextCtrl* text_editor = wxDynamicCast(ctrl, wxTextCtrl); - if (!text_editor || text_editor->GetValue().IsEmpty()) - return false; - - std::string chosen_name = Slic3r::normalize_utf8_nfc(text_editor->GetValue().ToUTF8()); - const char* unusable_symbols = "<>:/\\|?*\""; - for (size_t i = 0; i < std::strlen(unusable_symbols); i++) { - if (chosen_name.find_first_of(unusable_symbols[i]) != std::string::npos) { - m_was_unusable_symbol = true; - return false; - } - } - - // The icon can't be edited so get its old value and reuse it. - wxVariant valueOld; - GetView()->GetModel()->GetValue(valueOld, m_item, colName); - - DataViewBitmapText bmpText; - bmpText << valueOld; - - // But replace the text with the value entered by user. - bmpText.SetText(text_editor->GetValue()); - - value << bmpText; - return true; -} - -// ---------------------------------------------------------------------------- -// BitmapChoiceRenderer -// ---------------------------------------------------------------------------- - -bool BitmapChoiceRenderer::SetValue(const wxVariant& value) -{ - m_value << value; - return true; -} - -bool BitmapChoiceRenderer::GetValue(wxVariant& value) const -{ - value << m_value; - return true; -} - -bool BitmapChoiceRenderer::Render(wxRect rect, wxDC* dc, int state) -{ - int xoffset = 0; - - const wxBitmap& icon = m_value.GetBitmap(); - if (icon.IsOk()) - { - dc->DrawBitmap(icon, rect.x, rect.y + (rect.height - icon.GetHeight()) / 2); - xoffset = icon.GetWidth() + 4; - } - - if (rect.height==0) - rect.height= icon.GetHeight(); - RenderText(m_value.GetText(), xoffset, rect, dc, state); - - return true; -} - -wxSize BitmapChoiceRenderer::GetSize() const -{ - wxSize sz = GetTextExtent(m_value.GetText()); - - if (m_value.GetBitmap().IsOk()) - sz.x += m_value.GetBitmap().GetWidth() + 4; - - return sz; -} - - -wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value) -{ - wxDataViewCtrl* const dv_ctrl = GetOwner()->GetOwner(); - ObjectDataViewModel* const model = dynamic_cast(dv_ctrl->GetModel()); - - if (!(model->GetItemType(dv_ctrl->GetSelection()) & (itVolume | itLayer | itObject))) - return nullptr; - - std::vector icons = get_extruder_color_icons(); - if (icons.empty()) - return nullptr; - - DataViewBitmapText data; - data << value; - - auto c_editor = new wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, - labelRect.GetTopLeft(), wxSize(labelRect.GetWidth(), -1), - 0, nullptr , wxCB_READONLY); - - int i=0; - for (wxBitmap* bmp : icons) { - if (i==0) { - c_editor->Append(_(L("default")), *bmp); - ++i; - } - - c_editor->Append(wxString::Format("%d", i), *bmp); - ++i; - } - c_editor->SetSelection(atoi(data.GetText().c_str())); - - // to avoid event propagation to other sidebar items - c_editor->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) { - evt.StopPropagation(); - // FinishEditing grabs new selection and triggers config update. We better call - // it explicitly, automatic update on KILL_FOCUS didn't work on Linux. - this->FinishEditing(); - }); - - return c_editor; -} - -bool BitmapChoiceRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) -{ - wxBitmapComboBox* c = (wxBitmapComboBox*)ctrl; - int selection = c->GetSelection(); - if (selection < 0) - return false; - - DataViewBitmapText bmpText; - - bmpText.SetText(c->GetString(selection)); - bmpText.SetBitmap(c->GetItemBitmap(selection)); - - value << bmpText; - return true; -} - - // ---------------------------------------------------------------------------- // LockButton // ---------------------------------------------------------------------------- @@ -2466,18 +783,44 @@ void MenuWithSeparators::SetSecondSeparator() // ---------------------------------------------------------------------------- ScalableBitmap::ScalableBitmap( wxWindow *parent, const std::string& icon_name/* = ""*/, - const int px_cnt/* = 16*/, - const bool is_horizontal/* = false*/): + const int px_cnt/* = 16*/): m_parent(parent), m_icon_name(icon_name), - m_px_cnt(px_cnt), m_is_horizontal(is_horizontal) + m_px_cnt(px_cnt) { - m_bmp = create_scaled_bitmap(parent, icon_name, px_cnt, is_horizontal); + m_bmp = create_scaled_bitmap(icon_name, parent, px_cnt); +} + +wxSize ScalableBitmap::GetBmpSize() const +{ +#ifdef __APPLE__ + return m_bmp.GetScaledSize(); +#else + return m_bmp.GetSize(); +#endif +} + +int ScalableBitmap::GetBmpWidth() const +{ +#ifdef __APPLE__ + return m_bmp.GetScaledWidth(); +#else + return m_bmp.GetWidth(); +#endif +} + +int ScalableBitmap::GetBmpHeight() const +{ +#ifdef __APPLE__ + return m_bmp.GetScaledHeight(); +#else + return m_bmp.GetHeight(); +#endif } void ScalableBitmap::msw_rescale() { - m_bmp = create_scaled_bitmap(m_parent, m_icon_name, m_px_cnt, m_is_horizontal); + m_bmp = create_scaled_bitmap(m_icon_name, m_parent, m_px_cnt); } // ---------------------------------------------------------------------------- @@ -2500,7 +843,7 @@ ScalableButton::ScalableButton( wxWindow * parent, SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); #endif // __WXMSW__ - SetBitmap(create_scaled_bitmap(parent, icon_name)); + SetBitmap(create_scaled_bitmap(icon_name, parent)); if (size != wxDefaultSize) { @@ -2518,8 +861,7 @@ ScalableButton::ScalableButton( wxWindow * parent, long style /*= wxBU_EXACTFIT | wxNO_BORDER*/) : m_parent(parent), m_current_icon_name(bitmap.name()), - m_px_cnt(bitmap.px_cnt()), - m_is_horizontal(bitmap.is_horizontal()) + m_px_cnt(bitmap.px_cnt()) { Create(parent, id, label, wxDefaultPosition, wxDefaultSize, style); #ifdef __WXMSW__ @@ -2544,15 +886,18 @@ void ScalableButton::SetBitmapDisabled_(const ScalableBitmap& bmp) int ScalableButton::GetBitmapHeight() { - const float scale_factor = get_svg_scale_factor(m_parent); - return int((float)GetBitmap().GetHeight() / scale_factor); +#ifdef __APPLE__ + return GetBitmap().GetScaledHeight(); +#else + return GetBitmap().GetHeight(); +#endif } void ScalableButton::msw_rescale() { - SetBitmap(create_scaled_bitmap(m_parent, m_current_icon_name, m_px_cnt, m_is_horizontal)); + SetBitmap(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt)); if (!m_disabled_icon_name.empty()) - SetBitmapDisabled(create_scaled_bitmap(m_parent, m_disabled_icon_name, m_px_cnt, m_is_horizontal)); + SetBitmapDisabled(create_scaled_bitmap(m_disabled_icon_name, m_parent, m_px_cnt)); if (m_width > 0 || m_height>0) { diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index a6f8862ac4..55dac54332 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -4,23 +4,14 @@ #include #include #include -#include -#include #include #include #include -#include +#include #include -#include #include -namespace Slic3r { - enum class ModelVolumeType : int; -}; - -typedef double coordf_t; -typedef std::pair t_layer_height_range; #ifdef __WXMSW__ void msw_rescale_menu(wxMenu* menu); @@ -48,15 +39,13 @@ wxMenuItem* append_menu_check_item(wxMenu* menu, int id, const wxString& string, void enable_menu_item(wxUpdateUIEvent& evt, std::function const cb_condition, wxMenuItem* item, wxWindow* win); class wxDialog; -class wxBitmapComboBox; void edit_tooltip(wxString& tooltip); void msw_buttons_rescale(wxDialog* dlg, const int em_unit, const std::vector& btn_ids); int em_unit(wxWindow* win); -float get_svg_scale_factor(wxWindow* win); -wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name, - const int px_cnt = 16, const bool is_horizontal = false, const bool grayscale = false); +wxBitmap create_scaled_bitmap(const std::string& bmp_name, wxWindow *win = nullptr, + const int px_cnt = 16, const bool grayscale = false); std::vector get_extruder_color_icons(bool thin_icon = false); void apply_extruder_selector(wxBitmapComboBox** ctrl, @@ -102,6 +91,37 @@ public: void OnListBoxSelection(wxCommandEvent& evt); }; +namespace Slic3r { +namespace GUI { +// *** PresetBitmapComboBox *** + +// BitmapComboBox used to presets list on Sidebar and Tabs +class PresetBitmapComboBox: public wxBitmapComboBox +{ +public: + PresetBitmapComboBox(wxWindow* parent, const wxSize& size = wxDefaultSize); + ~PresetBitmapComboBox() {} + +#ifdef __APPLE__ +protected: + /* For PresetBitmapComboBox we use bitmaps that are created from images that are already scaled appropriately for Retina + * (Contrary to the intuition, the `scale` argument for Bitmap's constructor doesn't mean + * "please scale this to such and such" but rather + * "the wxImage is already sized for backing scale such and such". ) + * Unfortunately, the constructor changes the size of wxBitmap too. + * Thus We need to use unscaled size value for bitmaps that we use + * to avoid scaled size of control items. + * For this purpose control drawing methods and + * control size calculation methods (virtual) are overridden. + **/ + virtual bool OnAddBitmap(const wxBitmap& bitmap) override; + virtual void OnDrawItem(wxDC& dc, const wxRect& rect, int item, int flags) const override; +#endif +}; + +} +} + // *** wxDataViewTreeCtrlComboBox *** @@ -127,587 +147,6 @@ public: }; -// ---------------------------------------------------------------------------- -// DataViewBitmapText: helper class used by PrusaBitmapTextRenderer -// ---------------------------------------------------------------------------- - -class DataViewBitmapText : public wxObject -{ -public: - DataViewBitmapText( const wxString &text = wxEmptyString, - const wxBitmap& bmp = wxNullBitmap) : - m_text(text), - m_bmp(bmp) - { } - - DataViewBitmapText(const DataViewBitmapText &other) - : wxObject(), - m_text(other.m_text), - m_bmp(other.m_bmp) - { } - - void SetText(const wxString &text) { m_text = text; } - wxString GetText() const { return m_text; } - void SetBitmap(const wxBitmap &bmp) { m_bmp = bmp; } - const wxBitmap &GetBitmap() const { return m_bmp; } - - bool IsSameAs(const DataViewBitmapText& other) const { - return m_text == other.m_text && m_bmp.IsSameAs(other.m_bmp); - } - - bool operator==(const DataViewBitmapText& other) const { - return IsSameAs(other); - } - - bool operator!=(const DataViewBitmapText& other) const { - return !IsSameAs(other); - } - -private: - wxString m_text; - wxBitmap m_bmp; - - wxDECLARE_DYNAMIC_CLASS(DataViewBitmapText); -}; -DECLARE_VARIANT_OBJECT(DataViewBitmapText) - - -// ---------------------------------------------------------------------------- -// ObjectDataViewModelNode: a node inside ObjectDataViewModel -// ---------------------------------------------------------------------------- - -enum ItemType { - itUndef = 0, - itObject = 1, - itVolume = 2, - itInstanceRoot = 4, - itInstance = 8, - itSettings = 16, - itLayerRoot = 32, - itLayer = 64, -}; - -enum ColumnNumber -{ - colName = 0, // item name - colPrint , // printable property - colExtruder , // extruder selection - colEditing , // item editing -}; - -enum PrintIndicator -{ - piUndef = 0, // no print indicator - piPrintable , // printable - piUnprintable , // unprintable -}; - -class ObjectDataViewModelNode; -WX_DEFINE_ARRAY_PTR(ObjectDataViewModelNode*, MyObjectTreeModelNodePtrArray); - -class ObjectDataViewModelNode -{ - ObjectDataViewModelNode* m_parent; - MyObjectTreeModelNodePtrArray m_children; - wxBitmap m_empty_bmp; - size_t m_volumes_cnt = 0; - std::vector< std::string > m_opt_categories; - t_layer_height_range m_layer_range = { 0.0f, 0.0f }; - - wxString m_name; - wxBitmap& m_bmp = m_empty_bmp; - ItemType m_type; - int m_idx = -1; - bool m_container = false; - wxString m_extruder = "default"; - wxBitmap m_extruder_bmp; - wxBitmap m_action_icon; - PrintIndicator m_printable {piUndef}; - wxBitmap m_printable_icon; - - std::string m_action_icon_name = ""; - Slic3r::ModelVolumeType m_volume_type; - -public: - ObjectDataViewModelNode(const wxString &name, - const wxString& extruder): - m_parent(NULL), - m_name(name), - m_type(itObject), - m_extruder(extruder) - { - set_action_and_extruder_icons(); - init_container(); - } - - ObjectDataViewModelNode(ObjectDataViewModelNode* parent, - const wxString& sub_obj_name, - const wxBitmap& bmp, - const wxString& extruder, - const int idx = -1 ) : - m_parent (parent), - m_name (sub_obj_name), - m_type (itVolume), - m_idx (idx), - m_extruder (extruder) - { - m_bmp = bmp; - set_action_and_extruder_icons(); - init_container(); - } - - ObjectDataViewModelNode(ObjectDataViewModelNode* parent, - const t_layer_height_range& layer_range, - const int idx = -1, - const wxString& extruder = wxEmptyString ); - - ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const ItemType type); - - ~ObjectDataViewModelNode() - { - // free all our children nodes - size_t count = m_children.GetCount(); - for (size_t i = 0; i < count; i++) - { - ObjectDataViewModelNode *child = m_children[i]; - delete child; - } -#ifndef NDEBUG - // Indicate that the object was deleted. - m_idx = -2; -#endif /* NDEBUG */ - } - - void init_container(); - bool IsContainer() const - { - return m_container; - } - - ObjectDataViewModelNode* GetParent() - { - assert(m_parent == nullptr || m_parent->valid()); - return m_parent; - } - MyObjectTreeModelNodePtrArray& GetChildren() - { - return m_children; - } - ObjectDataViewModelNode* GetNthChild(unsigned int n) - { - return m_children.Item(n); - } - void Insert(ObjectDataViewModelNode* child, unsigned int n) - { - if (!m_container) - m_container = true; - m_children.Insert(child, n); - } - void Append(ObjectDataViewModelNode* child) - { - if (!m_container) - m_container = true; - m_children.Add(child); - } - void RemoveAllChildren() - { - if (GetChildCount() == 0) - return; - for (int id = int(GetChildCount()) - 1; id >= 0; --id) - { - if (m_children.Item(id)->GetChildCount() > 0) - m_children[id]->RemoveAllChildren(); - auto node = m_children[id]; - m_children.RemoveAt(id); - delete node; - } - } - - size_t GetChildCount() const - { - return m_children.GetCount(); - } - - bool SetValue(const wxVariant &variant, unsigned int col); - - void SetBitmap(const wxBitmap &icon) { m_bmp = icon; } - const wxBitmap& GetBitmap() const { return m_bmp; } - const wxString& GetName() const { return m_name; } - ItemType GetType() const { return m_type; } - void SetIdx(const int& idx); - int GetIdx() const { return m_idx; } - t_layer_height_range GetLayerRange() const { return m_layer_range; } - PrintIndicator IsPrintable() const { return m_printable; } - - // use this function only for childrens - void AssignAllVal(ObjectDataViewModelNode& from_node) - { - // ! Don't overwrite other values because of equality of this values for all children -- - m_name = from_node.m_name; - m_bmp = from_node.m_bmp; - m_idx = from_node.m_idx; - m_extruder = from_node.m_extruder; - m_type = from_node.m_type; - } - - bool SwapChildrens(int frst_id, int scnd_id) { - if (GetChildCount() < 2 || - frst_id < 0 || (size_t)frst_id >= GetChildCount() || - scnd_id < 0 || (size_t)scnd_id >= GetChildCount()) - return false; - - ObjectDataViewModelNode new_scnd = *GetNthChild(frst_id); - ObjectDataViewModelNode new_frst = *GetNthChild(scnd_id); - - new_scnd.m_idx = m_children.Item(scnd_id)->m_idx; - new_frst.m_idx = m_children.Item(frst_id)->m_idx; - - m_children.Item(frst_id)->AssignAllVal(new_frst); - m_children.Item(scnd_id)->AssignAllVal(new_scnd); - return true; - } - - // Set action icons for node - void set_action_and_extruder_icons(); - // Set printable icon for node - void set_printable_icon(PrintIndicator printable); - - void update_settings_digest_bitmaps(); - bool update_settings_digest(const std::vector& categories); - int volume_type() const { return int(m_volume_type); } - void msw_rescale(); - -#ifndef NDEBUG - bool valid(); -#endif /* NDEBUG */ - bool invalid() const { return m_idx < -1; } - -private: - friend class ObjectDataViewModel; -}; - -// ---------------------------------------------------------------------------- -// ObjectDataViewModel -// ---------------------------------------------------------------------------- - -// custom message the model sends to associated control to notify a last volume deleted from the object: -wxDECLARE_EVENT(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, wxCommandEvent); - -class ObjectDataViewModel :public wxDataViewModel -{ - std::vector m_objects; - std::vector m_volume_bmps; - wxBitmap* m_warning_bmp { nullptr }; - - wxDataViewCtrl* m_ctrl { nullptr }; - -public: - ObjectDataViewModel(); - ~ObjectDataViewModel(); - - wxDataViewItem Add( const wxString &name, - const int extruder, - const bool has_errors = false); - wxDataViewItem AddVolumeChild( const wxDataViewItem &parent_item, - const wxString &name, - const Slic3r::ModelVolumeType volume_type, - const bool has_errors = false, - const int extruder = 0, - const bool create_frst_child = true); - wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item); - wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, size_t num); - wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, const std::vector& print_indicator); - wxDataViewItem AddLayersRoot(const wxDataViewItem &parent_item); - wxDataViewItem AddLayersChild( const wxDataViewItem &parent_item, - const t_layer_height_range& layer_range, - const int extruder = 0, - const int index = -1); - wxDataViewItem Delete(const wxDataViewItem &item); - wxDataViewItem DeleteLastInstance(const wxDataViewItem &parent_item, size_t num); - void DeleteAll(); - void DeleteChildren(wxDataViewItem& parent); - void DeleteVolumeChildren(wxDataViewItem& parent); - void DeleteSettings(const wxDataViewItem& parent); - wxDataViewItem GetItemById(int obj_idx); - wxDataViewItem GetItemById(const int obj_idx, const int sub_obj_idx, const ItemType parent_type); - wxDataViewItem GetItemByVolumeId(int obj_idx, int volume_idx); - wxDataViewItem GetItemByInstanceId(int obj_idx, int inst_idx); - wxDataViewItem GetItemByLayerId(int obj_idx, int layer_idx); - wxDataViewItem GetItemByLayerRange(const int obj_idx, const t_layer_height_range& layer_range); - int GetItemIdByLayerRange(const int obj_idx, const t_layer_height_range& layer_range); - int GetIdByItem(const wxDataViewItem& item) const; - int GetIdByItemAndType(const wxDataViewItem& item, const ItemType type) const; - int GetObjectIdByItem(const wxDataViewItem& item) const; - int GetVolumeIdByItem(const wxDataViewItem& item) const; - int GetInstanceIdByItem(const wxDataViewItem& item) const; - int GetLayerIdByItem(const wxDataViewItem& item) const; - void GetItemInfo(const wxDataViewItem& item, ItemType& type, int& obj_idx, int& idx); - int GetRowByItem(const wxDataViewItem& item) const; - bool IsEmpty() { return m_objects.empty(); } - bool InvalidItem(const wxDataViewItem& item); - - // helper method for wxLog - - wxString GetName(const wxDataViewItem &item) const; - wxBitmap& GetBitmap(const wxDataViewItem &item) const; - wxString GetExtruder(const wxDataViewItem &item) const; - int GetExtruderNumber(const wxDataViewItem &item) const; - - // helper methods to change the model - - virtual unsigned int GetColumnCount() const override { return 3;} - virtual wxString GetColumnType(unsigned int col) const override{ return wxT("string"); } - - virtual void GetValue( wxVariant &variant, - const wxDataViewItem &item, - unsigned int col) const override; - virtual bool SetValue( const wxVariant &variant, - const wxDataViewItem &item, - unsigned int col) override; - bool SetValue( const wxVariant &variant, - const int item_idx, - unsigned int col); - - void SetExtruder(const wxString& extruder, wxDataViewItem item); - - // For parent move child from cur_volume_id place to new_volume_id - // Remaining items will moved up/down accordingly - wxDataViewItem ReorganizeChildren( const int cur_volume_id, - const int new_volume_id, - const wxDataViewItem &parent); - - virtual bool IsEnabled(const wxDataViewItem &item, unsigned int col) const override; - - virtual wxDataViewItem GetParent(const wxDataViewItem &item) const override; - // get object item - wxDataViewItem GetTopParent(const wxDataViewItem &item) const; - virtual bool IsContainer(const wxDataViewItem &item) const override; - virtual unsigned int GetChildren(const wxDataViewItem &parent, - wxDataViewItemArray &array) const override; - void GetAllChildren(const wxDataViewItem &parent,wxDataViewItemArray &array) const; - // Is the container just a header or an item with all columns - // In our case it is an item with all columns - virtual bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; } - - ItemType GetItemType(const wxDataViewItem &item) const ; - wxDataViewItem GetItemByType( const wxDataViewItem &parent_item, - ItemType type) const; - wxDataViewItem GetSettingsItem(const wxDataViewItem &item) const; - wxDataViewItem GetInstanceRootItem(const wxDataViewItem &item) const; - wxDataViewItem GetLayerRootItem(const wxDataViewItem &item) const; - bool IsSettingsItem(const wxDataViewItem &item) const; - void UpdateSettingsDigest( const wxDataViewItem &item, - const std::vector& categories); - - bool IsPrintable(const wxDataViewItem &item) const; - void UpdateObjectPrintable(wxDataViewItem parent_item); - void UpdateInstancesPrintable(wxDataViewItem parent_item); - - void SetVolumeBitmaps(const std::vector& volume_bmps) { m_volume_bmps = volume_bmps; } - void SetWarningBitmap(wxBitmap* bitmap) { m_warning_bmp = bitmap; } - void SetVolumeType(const wxDataViewItem &item, const Slic3r::ModelVolumeType type); - wxDataViewItem SetPrintableState( PrintIndicator printable, int obj_idx, - int subobj_idx = -1, - ItemType subobj_type = itInstance); - wxDataViewItem SetObjectPrintableState(PrintIndicator printable, wxDataViewItem obj_item); - - void SetAssociatedControl(wxDataViewCtrl* ctrl) { m_ctrl = ctrl; } - // Rescale bitmaps for existing Items - void Rescale(); - - wxBitmap GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, - const bool is_marked = false); - void DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object = false); - t_layer_height_range GetLayerRangeByItem(const wxDataViewItem& item) const; - - bool UpdateColumValues(unsigned col); - void UpdateExtruderBitmap(wxDataViewItem item); - -private: - wxDataViewItem AddRoot(const wxDataViewItem& parent_item, const ItemType root_type); - wxDataViewItem AddInstanceRoot(const wxDataViewItem& parent_item); -}; - -// ---------------------------------------------------------------------------- -// BitmapTextRenderer -// ---------------------------------------------------------------------------- -#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING -class BitmapTextRenderer : public wxDataViewRenderer -#else -class BitmapTextRenderer : public wxDataViewCustomRenderer -#endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING -{ -public: - BitmapTextRenderer(wxDataViewCellMode mode = -#ifdef __WXOSX__ - wxDATAVIEW_CELL_INERT -#else - wxDATAVIEW_CELL_EDITABLE -#endif - - ,int align = wxDVR_DEFAULT_ALIGNMENT -#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING - ); -#else - ) : wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align) {} -#endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING - - bool SetValue(const wxVariant &value); - bool GetValue(wxVariant &value) const; -#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING && wxUSE_ACCESSIBILITY - virtual wxString GetAccessibleDescription() const override; -#endif // wxUSE_ACCESSIBILITY && ENABLE_NONCUSTOM_DATA_VIEW_RENDERING - - virtual bool Render(wxRect cell, wxDC *dc, int state); - virtual wxSize GetSize() const; - - bool HasEditorCtrl() const override - { -#ifdef __WXOSX__ - return false; -#else - return true; -#endif - } - wxWindow* CreateEditorCtrl(wxWindow* parent, - wxRect labelRect, - const wxVariant& value) override; - bool GetValueFromEditorCtrl( wxWindow* ctrl, - wxVariant& value) override; - bool WasCanceled() const { return m_was_unusable_symbol; } - -private: - DataViewBitmapText m_value; - bool m_was_unusable_symbol {false}; -}; - - -// ---------------------------------------------------------------------------- -// BitmapChoiceRenderer -// ---------------------------------------------------------------------------- - -class BitmapChoiceRenderer : public wxDataViewCustomRenderer -{ -public: - BitmapChoiceRenderer(wxDataViewCellMode mode = -#ifdef __WXOSX__ - wxDATAVIEW_CELL_INERT -#else - wxDATAVIEW_CELL_EDITABLE -#endif - ,int align = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL - ) : wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align) {} - - bool SetValue(const wxVariant& value); - bool GetValue(wxVariant& value) const; - - virtual bool Render(wxRect cell, wxDC* dc, int state); - virtual wxSize GetSize() const; - - bool HasEditorCtrl() const override { return true; } - wxWindow* CreateEditorCtrl(wxWindow* parent, - wxRect labelRect, - const wxVariant& value) override; - bool GetValueFromEditorCtrl( wxWindow* ctrl, - wxVariant& value) override; - -private: - DataViewBitmapText m_value; -}; - - -// ---------------------------------------------------------------------------- -// MyCustomRenderer -// ---------------------------------------------------------------------------- - -class MyCustomRenderer : public wxDataViewCustomRenderer -{ -public: - // This renderer can be either activatable or editable, for demonstration - // purposes. In real programs, you should select whether the user should be - // able to activate or edit the cell and it doesn't make sense to switch - // between the two -- but this is just an example, so it doesn't stop us. - explicit MyCustomRenderer(wxDataViewCellMode mode) - : wxDataViewCustomRenderer("string", mode, wxALIGN_CENTER) - { } - - virtual bool Render(wxRect rect, wxDC *dc, int state) override/*wxOVERRIDE*/ - { - dc->SetBrush(*wxLIGHT_GREY_BRUSH); - dc->SetPen(*wxTRANSPARENT_PEN); - - rect.Deflate(2); - dc->DrawRoundedRectangle(rect, 5); - - RenderText(m_value, - 0, // no offset - wxRect(dc->GetTextExtent(m_value)).CentreIn(rect), - dc, - state); - return true; - } - - virtual bool ActivateCell(const wxRect& WXUNUSED(cell), - wxDataViewModel *WXUNUSED(model), - const wxDataViewItem &WXUNUSED(item), - unsigned int WXUNUSED(col), - const wxMouseEvent *mouseEvent) override/*wxOVERRIDE*/ - { - wxString position; - if (mouseEvent) - position = wxString::Format("via mouse at %d, %d", mouseEvent->m_x, mouseEvent->m_y); - else - position = "from keyboard"; -// wxLogMessage("MyCustomRenderer ActivateCell() %s", position); - return false; - } - - virtual wxSize GetSize() const override/*wxOVERRIDE*/ - { - return wxSize(60, 20); - } - - virtual bool SetValue(const wxVariant &value) override/*wxOVERRIDE*/ - { - m_value = value.GetString(); - return true; - } - - virtual bool GetValue(wxVariant &WXUNUSED(value)) const override/*wxOVERRIDE*/{ return true; } - - virtual bool HasEditorCtrl() const override/*wxOVERRIDE*/{ return true; } - - virtual wxWindow* - CreateEditorCtrl(wxWindow* parent, - wxRect labelRect, - const wxVariant& value) override/*wxOVERRIDE*/ - { - wxTextCtrl* text = new wxTextCtrl(parent, wxID_ANY, value, - labelRect.GetPosition(), - labelRect.GetSize(), - wxTE_PROCESS_ENTER); - text->SetInsertionPointEnd(); - - return text; - } - - virtual bool - GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) override/*wxOVERRIDE*/ - { - wxTextCtrl* text = wxDynamicCast(ctrl, wxTextCtrl); - if (!text) - return false; - - value = text->GetValue(); - - return true; - } - -private: - wxString m_value; -}; - - // ---------------------------------------------------------------------------- // ScalableBitmap // ---------------------------------------------------------------------------- @@ -718,11 +157,14 @@ public: ScalableBitmap() {}; ScalableBitmap( wxWindow *parent, const std::string& icon_name = "", - const int px_cnt = 16, - const bool is_horizontal = false); + const int px_cnt = 16); ~ScalableBitmap() {} + wxSize GetBmpSize() const; + int GetBmpWidth() const; + int GetBmpHeight() const; + void msw_rescale(); const wxBitmap& bmp() const { return m_bmp; } @@ -730,14 +172,12 @@ public: const std::string& name() const{ return m_icon_name; } int px_cnt()const {return m_px_cnt;} - bool is_horizontal()const {return m_is_horizontal;} private: wxWindow* m_parent{ nullptr }; wxBitmap m_bmp = wxBitmap(); std::string m_icon_name = ""; int m_px_cnt {16}; - bool m_is_horizontal {false}; }; @@ -821,7 +261,6 @@ private: // bitmap dimensions int m_px_cnt{ 16 }; - bool m_is_horizontal{ false }; }; diff --git a/tests/sla_print/sla_print_tests.cpp b/tests/sla_print/sla_print_tests.cpp index f34fb90968..32bca4ec0e 100644 --- a/tests/sla_print/sla_print_tests.cpp +++ b/tests/sla_print/sla_print_tests.cpp @@ -26,58 +26,6 @@ const char *const SUPPORT_TEST_MODELS[] = { } // namespace -// Test pair hash for 'nums' random number pairs. -template void test_pairhash() -{ - const constexpr size_t nums = 1000; - I A[nums] = {0}, B[nums] = {0}; - std::unordered_set CH; - std::unordered_map> ints; - - std::random_device rd; - std::mt19937 gen(rd()); - - const I Ibits = int(sizeof(I) * CHAR_BIT); - const II IIbits = int(sizeof(II) * CHAR_BIT); - - int bits = IIbits / 2 < Ibits ? Ibits / 2 : Ibits; - if (std::is_signed::value) bits -= 1; - const I Imin = 0; - const I Imax = I(std::pow(2., bits) - 1); - - std::uniform_int_distribution dis(Imin, Imax); - - for (size_t i = 0; i < nums;) { - I a = dis(gen); - if (CH.find(a) == CH.end()) { CH.insert(a); A[i] = a; ++i; } - } - - for (size_t i = 0; i < nums;) { - I b = dis(gen); - if (CH.find(b) == CH.end()) { CH.insert(b); B[i] = b; ++i; } - } - - for (size_t i = 0; i < nums; ++i) { - I a = A[i], b = B[i]; - - REQUIRE(a != b); - - II hash_ab = sla::pairhash(a, b); - II hash_ba = sla::pairhash(b, a); - REQUIRE(hash_ab == hash_ba); - - auto it = ints.find(hash_ab); - - if (it != ints.end()) { - REQUIRE(( - (it->second.first == a && it->second.second == b) || - (it->second.first == b && it->second.second == a) - )); - } else - ints[hash_ab] = std::make_pair(a, b); - } -} - TEST_CASE("Pillar pairhash should be unique", "[SLASupportGeneration]") { test_pairhash(); test_pairhash(); @@ -225,69 +173,6 @@ TEST_CASE("InitializedRasterShouldBeNONEmpty", "[SLARasterOutput]") { REQUIRE(raster.pixel_dimensions().h_mm == Approx(pixdim.h_mm)); } -using TPixel = uint8_t; -static constexpr const TPixel FullWhite = 255; -static constexpr const TPixel FullBlack = 0; - -template constexpr int arraysize(const A (&)[N]) { return N; } - -static void check_raster_transformations(sla::Raster::Orientation o, - sla::Raster::TMirroring mirroring) -{ - double disp_w = 120., disp_h = 68.; - sla::Raster::Resolution res{2560, 1440}; - sla::Raster::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; - - auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)}); - sla::Raster::Trafo trafo{o, mirroring}; - trafo.origin_x = bb.center().x(); - trafo.origin_y = bb.center().y(); - - sla::Raster raster{res, pixdim, trafo}; - - // create box of size 32x32 pixels (not 1x1 to avoid antialiasing errors) - coord_t pw = 32 * coord_t(std::ceil(scaled(pixdim.w_mm))); - coord_t ph = 32 * coord_t(std::ceil(scaled(pixdim.h_mm))); - ExPolygon box; - box.contour.points = {{-pw, -ph}, {pw, -ph}, {pw, ph}, {-pw, ph}}; - - double tr_x = scaled(20.), tr_y = tr_x; - - box.translate(tr_x, tr_y); - ExPolygon expected_box = box; - - // Now calculate the position of the translated box according to output - // trafo. - if (o == sla::Raster::Orientation::roPortrait) expected_box.rotate(PI / 2.); - - if (mirroring[X]) - for (auto &p : expected_box.contour.points) p.x() = -p.x(); - - if (mirroring[Y]) - for (auto &p : expected_box.contour.points) p.y() = -p.y(); - - raster.draw(box); - - Point expected_coords = expected_box.contour.bounding_box().center(); - double rx = unscaled(expected_coords.x() + bb.center().x()) / pixdim.w_mm; - double ry = unscaled(expected_coords.y() + bb.center().y()) / pixdim.h_mm; - auto w = size_t(std::floor(rx)); - auto h = res.height_px - size_t(std::floor(ry)); - - REQUIRE((w < res.width_px && h < res.height_px)); - - auto px = raster.read_pixel(w, h); - - if (px != FullWhite) { - sla::PNGImage img; - std::fstream outf("out.png", std::ios::out); - - outf << img.serialize(raster); - } - - REQUIRE(px == FullWhite); -} - TEST_CASE("MirroringShouldBeCorrect", "[SLARasterOutput]") { sla::Raster::TMirroring mirrorings[] = {sla::Raster::NoMirror, sla::Raster::MirrorX, @@ -301,54 +186,6 @@ TEST_CASE("MirroringShouldBeCorrect", "[SLARasterOutput]") { check_raster_transformations(orientation, mirror); } -static ExPolygon square_with_hole(double v) -{ - ExPolygon poly; - coord_t V = scaled(v / 2.); - - poly.contour.points = {{-V, -V}, {V, -V}, {V, V}, {-V, V}}; - poly.holes.emplace_back(); - V = V / 2; - poly.holes.front().points = {{-V, V}, {V, V}, {V, -V}, {-V, -V}}; - return poly; -} - -static double pixel_area(TPixel px, const sla::Raster::PixelDim &pxdim) -{ - return (pxdim.h_mm * pxdim.w_mm) * px * 1. / (FullWhite - FullBlack); -} - -static double raster_white_area(const sla::Raster &raster) -{ - if (raster.empty()) return std::nan(""); - - auto res = raster.resolution(); - double a = 0; - - for (size_t x = 0; x < res.width_px; ++x) - for (size_t y = 0; y < res.height_px; ++y) { - auto px = raster.read_pixel(x, y); - a += pixel_area(px, raster.pixel_dimensions()); - } - - return a; -} - -static double predict_error(const ExPolygon &p, const sla::Raster::PixelDim &pd) -{ - auto lines = p.lines(); - double pix_err = pixel_area(FullWhite, pd) / 2.; - - // Worst case is when a line is parallel to the shorter axis of one pixel, - // when the line will be composed of the max number of pixels - double pix_l = std::min(pd.h_mm, pd.w_mm); - - double error = 0.; - for (auto &l : lines) - error += (unscaled(l.length()) / pix_l) * pix_err; - - return error; -} TEST_CASE("RasterizedPolygonAreaShouldMatch", "[SLARasterOutput]") { double disp_w = 120., disp_h = 68.; @@ -388,8 +225,4 @@ TEST_CASE("Triangle mesh conversions should be correct", "[SLAConversions]") std::fstream infile{"extruder_idler_quads.obj", std::ios::in}; cntr.from_obj(infile); } - - - - } diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index 0804adb4f3..44a15ff901 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -292,3 +292,103 @@ void check_validity(const TriangleMesh &input_mesh, int flags) REQUIRE(mesh.is_manifold()); } } + +void check_raster_transformations(sla::Raster::Orientation o, sla::Raster::TMirroring mirroring) +{ + double disp_w = 120., disp_h = 68.; + sla::Raster::Resolution res{2560, 1440}; + sla::Raster::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; + + auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)}); + sla::Raster::Trafo trafo{o, mirroring}; + trafo.origin_x = bb.center().x(); + trafo.origin_y = bb.center().y(); + + sla::Raster raster{res, pixdim, trafo}; + + // create box of size 32x32 pixels (not 1x1 to avoid antialiasing errors) + coord_t pw = 32 * coord_t(std::ceil(scaled(pixdim.w_mm))); + coord_t ph = 32 * coord_t(std::ceil(scaled(pixdim.h_mm))); + ExPolygon box; + box.contour.points = {{-pw, -ph}, {pw, -ph}, {pw, ph}, {-pw, ph}}; + + double tr_x = scaled(20.), tr_y = tr_x; + + box.translate(tr_x, tr_y); + ExPolygon expected_box = box; + + // Now calculate the position of the translated box according to output + // trafo. + if (o == sla::Raster::Orientation::roPortrait) expected_box.rotate(PI / 2.); + + if (mirroring[X]) + for (auto &p : expected_box.contour.points) p.x() = -p.x(); + + if (mirroring[Y]) + for (auto &p : expected_box.contour.points) p.y() = -p.y(); + + raster.draw(box); + + Point expected_coords = expected_box.contour.bounding_box().center(); + double rx = unscaled(expected_coords.x() + bb.center().x()) / pixdim.w_mm; + double ry = unscaled(expected_coords.y() + bb.center().y()) / pixdim.h_mm; + auto w = size_t(std::floor(rx)); + auto h = res.height_px - size_t(std::floor(ry)); + + REQUIRE((w < res.width_px && h < res.height_px)); + + auto px = raster.read_pixel(w, h); + + if (px != FullWhite) { + sla::PNGImage img; + std::fstream outf("out.png", std::ios::out); + + outf << img.serialize(raster); + } + + REQUIRE(px == FullWhite); +} + +ExPolygon square_with_hole(double v) +{ + ExPolygon poly; + coord_t V = scaled(v / 2.); + + poly.contour.points = {{-V, -V}, {V, -V}, {V, V}, {-V, V}}; + poly.holes.emplace_back(); + V = V / 2; + poly.holes.front().points = {{-V, V}, {V, V}, {V, -V}, {-V, -V}}; + return poly; +} + +double raster_white_area(const sla::Raster &raster) +{ + if (raster.empty()) return std::nan(""); + + auto res = raster.resolution(); + double a = 0; + + for (size_t x = 0; x < res.width_px; ++x) + for (size_t y = 0; y < res.height_px; ++y) { + auto px = raster.read_pixel(x, y); + a += pixel_area(px, raster.pixel_dimensions()); + } + + return a; +} + +double predict_error(const ExPolygon &p, const sla::Raster::PixelDim &pd) +{ + auto lines = p.lines(); + double pix_err = pixel_area(FullWhite, pd) / 2.; + + // Worst case is when a line is parallel to the shorter axis of one pixel, + // when the line will be composed of the max number of pixels + double pix_l = std::min(pd.h_mm, pd.w_mm); + + double error = 0.; + for (auto &l : lines) + error += (unscaled(l.length()) / pix_l) * pix_err; + + return error; +} diff --git a/tests/sla_print/sla_test_utils.hpp b/tests/sla_print/sla_test_utils.hpp index dcb4934ef6..f3727bd394 100644 --- a/tests/sla_print/sla_test_utils.hpp +++ b/tests/sla_print/sla_test_utils.hpp @@ -6,6 +6,7 @@ // Debug #include +#include #include "libslic3r/libslic3r.h" #include "libslic3r/Format/OBJ.hpp" @@ -109,4 +110,78 @@ inline void test_support_model_collision( test_support_model_collision(obj_filename, input_supportcfg, hcfg, {}); } +// Test pair hash for 'nums' random number pairs. +template void test_pairhash() +{ + const constexpr size_t nums = 1000; + I A[nums] = {0}, B[nums] = {0}; + std::unordered_set CH; + std::unordered_map> ints; + + std::random_device rd; + std::mt19937 gen(rd()); + + const I Ibits = int(sizeof(I) * CHAR_BIT); + const II IIbits = int(sizeof(II) * CHAR_BIT); + + int bits = IIbits / 2 < Ibits ? Ibits / 2 : Ibits; + if (std::is_signed::value) bits -= 1; + const I Imin = 0; + const I Imax = I(std::pow(2., bits) - 1); + + std::uniform_int_distribution dis(Imin, Imax); + + for (size_t i = 0; i < nums;) { + I a = dis(gen); + if (CH.find(a) == CH.end()) { CH.insert(a); A[i] = a; ++i; } + } + + for (size_t i = 0; i < nums;) { + I b = dis(gen); + if (CH.find(b) == CH.end()) { CH.insert(b); B[i] = b; ++i; } + } + + for (size_t i = 0; i < nums; ++i) { + I a = A[i], b = B[i]; + + REQUIRE(a != b); + + II hash_ab = sla::pairhash(a, b); + II hash_ba = sla::pairhash(b, a); + REQUIRE(hash_ab == hash_ba); + + auto it = ints.find(hash_ab); + + if (it != ints.end()) { + REQUIRE(( + (it->second.first == a && it->second.second == b) || + (it->second.first == b && it->second.second == a) + )); + } else + ints[hash_ab] = std::make_pair(a, b); + } +} + +// SLA Raster test utils: + +using TPixel = uint8_t; +static constexpr const TPixel FullWhite = 255; +static constexpr const TPixel FullBlack = 0; + +template constexpr int arraysize(const A (&)[N]) { return N; } + +void check_raster_transformations(sla::Raster::Orientation o, + sla::Raster::TMirroring mirroring); + +ExPolygon square_with_hole(double v); + +inline double pixel_area(TPixel px, const sla::Raster::PixelDim &pxdim) +{ + return (pxdim.h_mm * pxdim.w_mm) * px * 1. / (FullWhite - FullBlack); +} + +double raster_white_area(const sla::Raster &raster); + +double predict_error(const ExPolygon &p, const sla::Raster::PixelDim &pd); + #endif // SLA_TEST_UTILS_HPP