diff --git a/resources/profiles/Creality.idx b/resources/profiles/Creality.idx new file mode 100644 index 000000000..d877ae7c9 --- /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 181bb4f4e..4f8dbe572 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/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index cc83461d3..a53b6bd7c 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/Model.cpp b/src/libslic3r/Model.cpp index c5c457e8f..d73d8148b 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/Print.cpp b/src/libslic3r/Print.cpp index cc0554bd5..3131fd3d1 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 2d83da43d..75807cdda 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 4d6df36db..5dcaf8dfb 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) { @@ -1450,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/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index ac3652fee..a70b977b4 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) { diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index ab1685ff8..4ff7837bc 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, diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 1ecefe284..2b4a1d518 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,57 +80,69 @@ SLAPrint::Steps::Steps(SLAPrint *print) void SLAPrint::Steps::hollow_model(SLAPrintObject &po) { po.m_hollowing_data.reset(); - 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 (po.m_model_object->sla_drain_holes.empty()) - 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); //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; - hollowed_mesh = po.get_mesh_to_print(); - MeshBoolean::cgal::minus(hollowed_mesh, holes_mesh); + 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 @@ -850,8 +862,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 c62558671..ad099e0e7 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/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 4e8edf7aa..918a2c051 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 f6b1719db..9f36ab537 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 8d65ae90b..8627ef4cb 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) @@ -49,7 +62,7 @@ static wxBitmap wxImage_to_wxBitmap_with_alpha(wxImage &&image, float scale = 1. #endif } -wxBitmap* BitmapCache::insert(const std::string &bitmap_key, size_t width, size_t height, float scale/* = 1.0f*/) +wxBitmap* BitmapCache::insert(const std::string &bitmap_key, size_t width, size_t height) { wxBitmap *bitmap = nullptr; auto it = m_map.find(bitmap_key); @@ -61,7 +74,7 @@ wxBitmap* BitmapCache::insert(const std::string &bitmap_key, size_t width, size_ // 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, scale); + bitmap->CreateScaled(width, height, -1, m_scale); #endif m_map[bitmap_key] = bitmap; } else { @@ -103,13 +116,18 @@ wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap &bmp return this->insert(bitmap_key, bmps, bmps + 3); } -wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap *begin, const wxBitmap *end, float scale/* = 1.0f*/) +wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap *begin, const wxBitmap *end) { 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 @@ -166,13 +184,7 @@ wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap *beg #else -#ifdef __APPLE__ - // Note, for this moment width and height are scaled, so divide them by scale to avoid one more multiplication inside CreateScaled() - width *= 1.0 / scale; - height *= 1.0 / scale; -#endif - - wxBitmap *bitmap = this->insert(bitmap_key, width, height, scale); + wxBitmap *bitmap = this->insert(bitmap_key, width, height); wxMemoryDC memDC; memDC.SelectObject(*bitmap); memDC.SetBackground(*wxTRANSPARENT_BRUSH); @@ -181,8 +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->GetWidth()/scale; + x += bmp->GetScaledWidth(); +#else + x += bmp->GetWidth(); +#endif } memDC.SelectObject(wxNullBitmap); return bitmap; @@ -190,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(); @@ -207,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, @@ -242,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 @@ -287,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 ? @@ -312,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(); @@ -327,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 49d45c66a..255cf5495 100644 --- a/src/slic3r/GUI/BitmapCache.hpp +++ b/src/slic3r/GUI/BitmapCache.hpp @@ -6,43 +6,42 @@ #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); } - wxBitmap* insert(const std::string &name, size_t width, size_t height, float scale = 1.0f); + wxBitmap* insert(const std::string &name, size_t width, size_t height); wxBitmap* insert(const std::string &name, const wxBitmap &bmp); wxBitmap* insert(const std::string &name, const wxBitmap &bmp, const wxBitmap &bmp2); 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, float scale = 1.0f) { return this->insert(name, &bmps.front(), &bmps.front() + bmps.size(), scale); } - wxBitmap* insert(const std::string &name, const wxBitmap *begin, const wxBitmap *end, float scale = 1.0f); - 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(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, 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/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 47bdec1fb..4a5bea9a1 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,9 +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"); + 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(this, "error_tick"); + icon = create_scaled_bitmap("error_tick"); if (!icon.IsNull()) { @@ -937,9 +935,12 @@ wxString Control::get_tooltip(IconFocus icon_focus) 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 == ctMeaningless) + 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.")); @@ -1028,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); }, @@ -1062,7 +1063,7 @@ void Control::append_add_color_change_menu_item(wxMenu* menu, bool switch_curren 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")); } } @@ -1837,12 +1838,12 @@ ConflictType TickCodeInfo::is_conflict_tick(const TickCode& tick, t_mode out_mod // check ColorChange tick if (tick.gcode == ColorChangeCode) { - // We should mark a tick as a "Meaningless", + // 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 ctMeaningless; + return ctMeaninglessColorChange; // We should mark a tick as a "Redundant", // if it has a ColorChange for extruder that has not been used before @@ -1862,6 +1863,21 @@ ConflictType TickCodeInfo::is_conflict_tick(const TickCode& tick, t_mode out_mod } } + // 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; } diff --git a/src/slic3r/GUI/DoubleSlider.hpp b/src/slic3r/GUI/DoubleSlider.hpp index 064ed0464..f14af621f 100644 --- a/src/slic3r/GUI/DoubleSlider.hpp +++ b/src/slic3r/GUI/DoubleSlider.hpp @@ -43,7 +43,8 @@ enum ConflictType { ctNone, ctModeConflict, - ctMeaningless, + ctMeaninglessColorChange, + ctMeaninglessToolChange, ctRedundant }; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 0bdcb87b5..2989b6dde 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1399,6 +1399,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; @@ -1855,7 +1856,11 @@ void GLCanvas3D::render() const Size& cnv_size = get_canvas_size(); #if ENABLE_6DOF_CAMERA - m_camera.apply_viewport(0, 0, (unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height()); + // 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) @@ -2769,6 +2774,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; } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index eb4aca721..df9e5f152 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/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 25e1a5ee5..33ddd475c 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(this, "layers"); - CATEGORY_ICON[L("Infill")] = create_scaled_bitmap(this, "infill"); - CATEGORY_ICON[L("Support material")] = create_scaled_bitmap(this, "support"); - CATEGORY_ICON[L("Speed")] = create_scaled_bitmap(this, "time"); - CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap(this, "funnel"); - CATEGORY_ICON[L("Extrusion Width")] = create_scaled_bitmap(this, "funnel"); - CATEGORY_ICON[L("Wipe options")] = create_scaled_bitmap(this, "funnel"); -// CATEGORY_ICON[L("Skirt and brim")] = create_scaled_bitmap(this, "skirt+brim"); -// CATEGORY_ICON[L("Speed > Acceleration")] = create_scaled_bitmap(this, "time"); - CATEGORY_ICON[L("Advanced")] = create_scaled_bitmap(this, "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(this, "support"/*"sla_supports"*/); - CATEGORY_ICON[L("Pad")] = create_scaled_bitmap(this, "pad"); - CATEGORY_ICON[L("Hollowing")] = create_scaled_bitmap(this, "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 @@ -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()); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index dc0953d3b..fc02c706e 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -10,6 +10,7 @@ #include "Event.hpp" #include "wxExtensions.hpp" +#include "ObjectDataViewModel.hpp" class wxBoxSizer; class wxBitmapComboBox; diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index d1466ef0b..105c6faa5 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 d1879e4ba..d4a82a03d 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 000000000..b49b27e33 --- /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 000000000..3d838cd43 --- /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 b6ea8cbc3..8afd98d11 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(); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 0d5062993..568727abf 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(); diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index d67abd13b..98fcf3f42 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -873,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 c85933520..83970419c 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 6c80a9eab..0c187e871 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 037c47c5f..b81810733 100644 --- a/src/slic3r/GUI/PresetBundle.hpp +++ b/src/slic3r/GUI/PresetBundle.hpp @@ -129,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. @@ -160,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 a34a3e0b7..72e209167 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 4345f196c..cfa5ae56d 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 00e7725ac..d5c69be0b 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 539422545..970cd8528 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 b710d2cd7..399fcdf03 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, !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,32 +478,16 @@ 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 - (void)(win); - 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 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; - const float scale_factor = get_svg_scale_factor(win); - unsigned int width = 0; unsigned int height = (unsigned int)(em_unit(win) * px_cnt * 0.1f + 0.5f); @@ -440,7 +495,7 @@ wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name_in, boost::replace_last(bmp_name, ".png", ""); // 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); } @@ -453,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(); @@ -478,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); } @@ -491,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/* = ""*/, @@ -547,1723 +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), - m_ctrl(parent->m_ctrl) -{ - 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(m_ctrl, 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), - m_ctrl(parent->m_ctrl) -{ - 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(m_ctrl, 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_ctrl, 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_ctrl, 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, get_svg_scale_factor(m_ctrl)); - } - - 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_ctrl, m_action_icon_name); - - if (m_printable != piUndef) - m_printable_icon = create_scaled_bitmap(m_ctrl, 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, m_ctrl); - // 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(m_ctrl, LAYER_ROOT_ICON); - case itLayer: - node->m_bmp = create_scaled_bitmap(m_ctrl, 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()) - { - float sf = (float)1.0 / get_svg_scale_factor(m_parent); - wxSize icon_sz = icon.GetSize() * sf; - - 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; -} - - // ---------------------------------------------------------------------------- // LockButton // ---------------------------------------------------------------------------- @@ -2458,13 +787,40 @@ ScalableBitmap::ScalableBitmap( wxWindow *parent, m_parent(parent), m_icon_name(icon_name), m_px_cnt(px_cnt) { - m_bmp = create_scaled_bitmap(parent, icon_name, px_cnt); + 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_bmp = create_scaled_bitmap(m_icon_name, m_parent, m_px_cnt); } // ---------------------------------------------------------------------------- @@ -2487,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) { @@ -2530,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)); + 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)); + 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 c3d26c8be..55dac5433 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,14 +39,12 @@ 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, +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); @@ -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,598 +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; - - // pointer to control (is needed to create scaled bitmaps) - wxDataViewCtrl* m_ctrl{ nullptr }; - -public: - ObjectDataViewModelNode(const wxString& name, - const wxString& extruder, - wxDataViewCtrl* ctrl): - m_parent(NULL), - m_name(name), - m_type(itObject), - m_extruder(extruder), - m_ctrl(ctrl) - { - 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_ctrl (parent->m_ctrl) - { - 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( 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); - 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}; - 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); - 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 // ---------------------------------------------------------------------------- @@ -733,6 +161,10 @@ public: ~ScalableBitmap() {} + wxSize GetBmpSize() const; + int GetBmpWidth() const; + int GetBmpHeight() const; + void msw_rescale(); const wxBitmap& bmp() const { return m_bmp; } diff --git a/tests/sla_print/sla_print_tests.cpp b/tests/sla_print/sla_print_tests.cpp index f34fb9096..32bca4ec0 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 0804adb4f..44a15ff90 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 dcb4934ef..f3727bd39 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