diff --git a/resources/icons/row.png b/resources/icons/row.png new file mode 100644 index 000000000..18a6034fd Binary files /dev/null and b/resources/icons/row.png differ diff --git a/resources/icons/table.png b/resources/icons/table.png new file mode 100644 index 000000000..3bc0bd32f Binary files /dev/null and b/resources/icons/table.png differ diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 4793586e3..002e125c7 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -11,6 +11,7 @@ #include <boost/algorithm/string/classification.hpp> #include <boost/algorithm/string/split.hpp> #include <boost/algorithm/string/predicate.hpp> +#include <boost/algorithm/string/replace.hpp> #include <boost/filesystem/operations.hpp> #include <boost/nowide/fstream.hpp> #include <boost/nowide/cstdio.hpp> @@ -33,6 +34,7 @@ const std::string RELATIONSHIPS_FILE = "_rels/.rels"; const std::string PRINT_CONFIG_FILE = "Metadata/Slic3r_PE.config"; const std::string MODEL_CONFIG_FILE = "Metadata/Slic3r_PE_model.config"; const std::string LAYER_HEIGHTS_PROFILE_FILE = "Metadata/Slic3r_PE_layer_heights_profile.txt"; +const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/Slic3r_PE_layer_config_ranges.txt"; const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt"; const char* MODEL_TAG = "model"; @@ -331,6 +333,7 @@ namespace Slic3r { typedef std::map<int, ObjectMetadata> IdToMetadataMap; typedef std::map<int, Geometry> IdToGeometryMap; typedef std::map<int, std::vector<coordf_t>> IdToLayerHeightsProfileMap; + typedef std::map<int, t_layer_config_ranges> IdToLayerConfigRangesMap; typedef std::map<int, std::vector<sla::SupportPoint>> IdToSlaSupportPointsMap; // Version of the 3mf file @@ -347,6 +350,7 @@ namespace Slic3r { CurrentConfig m_curr_config; IdToMetadataMap m_objects_metadata; IdToLayerHeightsProfileMap m_layer_heights_profiles; + IdToLayerConfigRangesMap m_layer_config_ranges; IdToSlaSupportPointsMap m_sla_support_points; std::string m_curr_metadata_name; std::string m_curr_characters; @@ -365,6 +369,7 @@ namespace Slic3r { bool _load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config); bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); + void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, const std::string& archive_filename); @@ -476,6 +481,7 @@ namespace Slic3r { m_curr_config.volume_id = -1; m_objects_metadata.clear(); m_layer_heights_profiles.clear(); + m_layer_config_ranges.clear(); m_sla_support_points.clear(); m_curr_metadata_name.clear(); m_curr_characters.clear(); @@ -546,9 +552,14 @@ namespace Slic3r { if (boost::algorithm::iequals(name, LAYER_HEIGHTS_PROFILE_FILE)) { - // extract slic3r lazer heights profile file + // extract slic3r layer heights profile file _extract_layer_heights_profile_config_from_archive(archive, stat); } + if (boost::algorithm::iequals(name, LAYER_CONFIG_RANGES_FILE)) + { + // extract slic3r layer config ranges file + _extract_layer_config_ranges_from_archive(archive, stat); + } else if (boost::algorithm::iequals(name, SLA_SUPPORT_POINTS_FILE)) { // extract sla support points file @@ -592,6 +603,11 @@ namespace Slic3r { if (obj_layer_heights_profile != m_layer_heights_profiles.end()) model_object->layer_height_profile = obj_layer_heights_profile->second; + // m_layer_config_ranges are indexed by a 1 based model object index. + IdToLayerConfigRangesMap::iterator obj_layer_config_ranges = m_layer_config_ranges.find(object.second + 1); + if (obj_layer_config_ranges != m_layer_config_ranges.end()) + model_object->layer_config_ranges = obj_layer_config_ranges->second; + // m_sla_support_points are indexed by a 1 based model object index. IdToSlaSupportPointsMap::iterator obj_sla_support_points = m_sla_support_points.find(object.second + 1); if (obj_sla_support_points != m_sla_support_points.end() && !obj_sla_support_points->second.empty()) { @@ -769,6 +785,115 @@ namespace Slic3r { } } + void _3MF_Importer::_extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) + { + if (stat.m_uncomp_size > 0) + { + std::string buffer((size_t)stat.m_uncomp_size, 0); + mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); + if (res == 0) { + add_error("Error while reading layer config ranges data to buffer"); + return; + } + + if (buffer.back() == '|') + buffer.pop_back(); + + std::vector<std::string> objects; + boost::split(objects, buffer, boost::is_any_of("|"), boost::token_compress_off); + + for (std::string& object : objects) + { + // delete all spaces + boost::replace_all(object, " ", ""); + + std::vector<std::string> object_data; + boost::split(object_data, object, boost::is_any_of("*"), boost::token_compress_off); + /* there should be at least one layer config range in the object + * object_data[0] => object information + * object_data[i>=1] => range information + */ + if (object_data.size() < 2) { + add_error("Error while reading object data"); + continue; + } + + std::vector<std::string> object_data_id; + boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off); + if (object_data_id.size() != 2) { + add_error("Error while reading object id"); + continue; + } + + // get object information + int object_id = std::atoi(object_data_id[1].c_str()); + if (object_id == 0) { + add_error("Found invalid object id"); + continue; + } + + IdToLayerConfigRangesMap::iterator object_item = m_layer_config_ranges.find(object_id); + if (object_item != m_layer_config_ranges.end()) { + add_error("Found duplicated layer config range"); + continue; + } + + t_layer_config_ranges config_ranges; + + // get ranges information + for (size_t i = 1; i < object_data.size(); ++i) + { + if (object_data[i].back() == '\n') + object_data[i].pop_back(); + + std::vector<std::string> range_data; + boost::split(range_data, object_data[i], boost::is_any_of("\n"), boost::token_compress_off); + /* There should be at least two options for layer config range + * range_data[0] => Z range information + * range_data[i>=1] => configuration for the range + */ + if (range_data.size() < 3) { + add_error("Found invalid layer config range"); + continue; + } + + std::vector<std::string> z_range_str; + boost::split(z_range_str, range_data[0], boost::is_any_of("="), boost::token_compress_off); + if (z_range_str.size() != 2) { + add_error("Error while reading layer config range"); + continue; + } + + std::vector<std::string> z_values; + boost::split(z_values, z_range_str[1], boost::is_any_of(";"), boost::token_compress_off); + if (z_values.size() != 2) { + add_error("Found invalid layer config range"); + continue; + } + + // get Z range information + t_layer_height_range z_range = { (coordf_t)std::atof(z_values[0].c_str()) , (coordf_t)std::atof(z_values[1].c_str()) }; + DynamicPrintConfig& config = config_ranges[z_range]; + + // get configuration options for the range + for (size_t j = 1; j < range_data.size(); ++j) + { + std::vector<std::string> key_val; + boost::split(key_val, range_data[j], boost::is_any_of("="), boost::token_compress_off); + if (key_val.size() != 2) { + add_error("Error while reading config value"); + continue; + } + config.set_deserialize(key_val[0], key_val[1]); + } + } + + if (!config_ranges.empty()) + m_layer_config_ranges.insert(IdToLayerConfigRangesMap::value_type(object_id, config_ranges)); + } + } + } + void _3MF_Importer::_extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) { if (stat.m_uncomp_size > 0) @@ -1624,6 +1749,7 @@ namespace Slic3r { bool _add_mesh_to_object_stream(std::stringstream& stream, ModelObject& object, VolumeToOffsetsMap& volumes_offsets); bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items); bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model); + bool _add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config); bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, const IdToObjectDataMap &objects_data); @@ -1684,6 +1810,16 @@ namespace Slic3r { return false; } + // Adds layer config ranges file ("Metadata/Slic3r_PE_layer_config_ranges.txt"). + // All layer height profiles of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model. + // The index differes from the index of an object ID of an object instance of a 3MF file! + if (!_add_layer_config_ranges_file_to_archive(archive, model)) + { + close_zip_writer(&archive); + boost::filesystem::remove(filename); + return false; + } + // Adds sla support points file ("Metadata/Slic3r_PE_sla_support_points.txt"). // All sla support points of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model. // The index differes from the index of an object ID of an object instance of a 3MF file! @@ -2013,6 +2149,50 @@ namespace Slic3r { return true; } + bool _3MF_Exporter::_add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model) + { + std::string out = ""; + char buffer[1024]; + + unsigned int object_cnt = 0; + for (const ModelObject* object : model.objects) + { + object_cnt++; + const t_layer_config_ranges& ranges = object->layer_config_ranges; + if (!ranges.empty()) + { + sprintf(buffer, "object_id=%d\n", object_cnt); + out += buffer; + + // Store the layer config ranges. + for (const auto& range : ranges) + { + // store minX and maxZ + sprintf(buffer, "*z_range = %f;%f\n", range.first.first, range.first.second); + out += buffer; + + // store range configuration + const DynamicPrintConfig& config = range.second; + for (const std::string& key : config.keys()) + out += " " + key + " = " + config.serialize(key) + "\n"; + } + + out += "|"; + } + } + + if (!out.empty()) + { + if (!mz_zip_writer_add_mem(&archive, LAYER_CONFIG_RANGES_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) + { + add_error("Unable to add layer heights profile file to archive"); + return false; + } + } + + return true; + } + bool _3MF_Exporter::_add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model) { std::string out = ""; diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index a33d21c9f..4f0a402ea 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -106,6 +106,9 @@ struct AMFParserContext // amf/material/metadata NODE_TYPE_OBJECT, // amf/object // amf/object/metadata + NODE_TYPE_LAYER_CONFIG, // amf/object/layer_config_ranges + NODE_TYPE_RANGE, // amf/object/layer_config_ranges/range + // amf/object/layer_config_ranges/range/metadata NODE_TYPE_MESH, // amf/object/mesh NODE_TYPE_VERTICES, // amf/object/mesh/vertices NODE_TYPE_VERTEX, // amf/object/mesh/vertices/vertex @@ -260,7 +263,9 @@ void AMFParserContext::startElement(const char *name, const char **atts) m_value[0] = get_attribute(atts, "type"); node_type_new = NODE_TYPE_METADATA; } - } else if (strcmp(name, "mesh") == 0) { + } else if (strcmp(name, "layer_config_ranges") == 0 && m_path[1] == NODE_TYPE_OBJECT) + node_type_new = NODE_TYPE_LAYER_CONFIG; + else if (strcmp(name, "mesh") == 0) { if (m_path[1] == NODE_TYPE_OBJECT) node_type_new = NODE_TYPE_MESH; } else if (strcmp(name, "instance") == 0) { @@ -317,6 +322,10 @@ void AMFParserContext::startElement(const char *name, const char **atts) else if (strcmp(name, "mirrorz") == 0) node_type_new = NODE_TYPE_MIRRORZ; } + else if (m_path[2] == NODE_TYPE_LAYER_CONFIG && strcmp(name, "range") == 0) { + assert(m_object); + node_type_new = NODE_TYPE_RANGE; + } break; case 4: if (m_path[3] == NODE_TYPE_VERTICES) { @@ -334,6 +343,10 @@ void AMFParserContext::startElement(const char *name, const char **atts) } else if (strcmp(name, "triangle") == 0) node_type_new = NODE_TYPE_TRIANGLE; } + else if (m_path[3] == NODE_TYPE_RANGE && strcmp(name, "metadata") == 0) { + m_value[0] = get_attribute(atts, "type"); + node_type_new = NODE_TYPE_METADATA; + } break; case 5: if (strcmp(name, "coordinates") == 0) { @@ -571,8 +584,13 @@ void AMFParserContext::endElement(const char * /* name */) config = &m_material->config; else if (m_path[1] == NODE_TYPE_OBJECT && m_object) config = &m_object->config; - } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume) + } + else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume) config = &m_volume->config; + else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_RANGE && m_object && !m_object->layer_config_ranges.empty()) { + auto it = --m_object->layer_config_ranges.end(); + config = &it->second; + } if (config) config->set_deserialize(opt_key, m_value[1]); } else if (m_path.size() == 3 && m_path[1] == NODE_TYPE_OBJECT && m_object && strcmp(opt_key, "layer_height_profile") == 0) { @@ -609,6 +627,16 @@ void AMFParserContext::endElement(const char * /* name */) } m_object->sla_points_status = sla::PointsStatus::UserModified; } + else if (m_path.size() == 5 && m_path[1] == NODE_TYPE_OBJECT && m_path[3] == NODE_TYPE_RANGE && + m_object && strcmp(opt_key, "layer_height_ranges") == 0) { + // Parse object's layer_height_ranges, a semicolon separated doubles. + char* p = const_cast<char*>(m_value[1].c_str()); + char* end = strchr(p, ';'); + *end = 0; + + const t_layer_height_range range = {double(atof(p)), double(atof(end + 1))}; + m_object->layer_config_ranges[range]; + } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume) { if (strcmp(opt_key, "modifier") == 0) { // Is this volume a modifier volume? @@ -907,6 +935,31 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) } //FIXME Store the layer height ranges (ModelObject::layer_height_ranges) + + // #ys_FIXME_experiment : Try to export layer config range + const t_layer_config_ranges& config_ranges = object->layer_config_ranges; + if (!config_ranges.empty()) + { + // Store the layer config range as a single semicolon separated list. + stream << " <layer_config_ranges>\n"; + size_t layer_counter = 0; + for (auto range : config_ranges) { + stream << " <range id=\"" << layer_counter << "\">\n"; + + stream << " <metadata type=\"slic3r.layer_height_ranges\">"; + stream << range.first.first << ";" << range.first.second << "</metadata>\n"; + + for (const std::string& key : range.second.keys()) + stream << " <metadata type=\"slic3r." << key << "\">" << range.second.serialize(key) << "</metadata>\n"; + + stream << " </range>\n"; + layer_counter++; + } + + stream << " </layer_config_ranges>\n"; + } + + const std::vector<sla::SupportPoint>& sla_support_points = object->sla_support_points; if (!sla_support_points.empty()) { // Store the SLA supports as a single semicolon separated list. diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 8e879a3e6..1f9efbc56 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -594,6 +594,7 @@ ModelObject& ModelObject::assign_copy(const ModelObject &rhs) this->sla_support_points = rhs.sla_support_points; this->sla_points_status = rhs.sla_points_status; this->layer_height_ranges = rhs.layer_height_ranges; + this->layer_config_ranges = rhs.layer_config_ranges; // #ys_FIXME_experiment this->layer_height_profile = rhs.layer_height_profile; this->origin_translation = rhs.origin_translation; m_bounding_box = rhs.m_bounding_box; @@ -630,6 +631,7 @@ ModelObject& ModelObject::assign_copy(ModelObject &&rhs) this->sla_support_points = std::move(rhs.sla_support_points); this->sla_points_status = std::move(rhs.sla_points_status); this->layer_height_ranges = std::move(rhs.layer_height_ranges); + this->layer_config_ranges = std::move(rhs.layer_config_ranges); // #ys_FIXME_experiment this->layer_height_profile = std::move(rhs.layer_height_profile); this->origin_translation = std::move(rhs.origin_translation); m_bounding_box = std::move(rhs.m_bounding_box); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 0fd1140f0..22d43dbc7 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -181,6 +181,8 @@ public: DynamicPrintConfig config; // Variation of a layer thickness for spans of Z coordinates. t_layer_height_ranges layer_height_ranges; + // Variation of a layer thickness for spans of Z coordinates. + t_layer_config_ranges layer_config_ranges; // Profile of increasing z to a layer height, to be linearly interpolated when calculating the layers. // The pairs of <z, layer_height> are packed into a 1D array. std::vector<coordf_t> layer_height_profile; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index f9129f15a..ed4af3995 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -874,7 +874,8 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co bool support_enforcers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_ENFORCER); if (model_parts_differ || modifiers_differ || model_object.origin_translation != model_object_new.origin_translation || - model_object.layer_height_ranges != model_object_new.layer_height_ranges || +// model_object.layer_height_ranges != model_object_new.layer_height_ranges || + model_object.layer_config_ranges != model_object_new.layer_config_ranges || // #ys_FIXME_experiment model_object.layer_height_profile != model_object_new.layer_height_profile) { // The very first step (the slicing step) is invalidated. One may freely remove all associated PrintObjects. auto range = print_object_status.equal_range(PrintObjectStatus(model_object.id())); @@ -1227,7 +1228,8 @@ std::string Print::validate() const bool has_custom_layering = false; std::vector<std::vector<coordf_t>> layer_height_profiles; for (const PrintObject *object : m_objects) { - has_custom_layering = ! object->model_object()->layer_height_ranges.empty() || ! object->model_object()->layer_height_profile.empty(); +// has_custom_layering = ! object->model_object()->layer_height_ranges.empty() || ! object->model_object()->layer_height_profile.empty(); + has_custom_layering = ! object->model_object()->layer_config_ranges.empty() || ! object->model_object()->layer_height_profile.empty(); // #ys_FIXME_experiment if (has_custom_layering) { layer_height_profiles.assign(m_objects.size(), std::vector<coordf_t>()); break; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index d99aceabf..3add19021 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1434,8 +1434,9 @@ bool PrintObject::update_layer_height_profile(const ModelObject &model_object, c // if (this->layer_height_profile.empty()) layer_height_profile = layer_height_profile_adaptive(slicing_parameters, model_object.layer_height_ranges, model_object.volumes); else - layer_height_profile = layer_height_profile_from_ranges(slicing_parameters, model_object.layer_height_ranges); - updated = true; +// layer_height_profile = layer_height_profile_from_ranges(slicing_parameters, model_object.layer_height_ranges); + layer_height_profile = layer_height_profile_from_ranges(slicing_parameters, model_object.layer_config_ranges); // #ys_FIXME_experiment + updated = true; } return updated; } diff --git a/src/libslic3r/Slicing.cpp b/src/libslic3r/Slicing.cpp index e1bb4b313..1c6476eae 100644 --- a/src/libslic3r/Slicing.cpp +++ b/src/libslic3r/Slicing.cpp @@ -157,20 +157,25 @@ SlicingParameters SlicingParameters::create_from_config( // in the height profile and the printed object may be lifted by the raft thickness at the time of the G-code generation. std::vector<coordf_t> layer_height_profile_from_ranges( const SlicingParameters &slicing_params, - const t_layer_height_ranges &layer_height_ranges) +// const t_layer_height_ranges &layer_height_ranges) + const t_layer_config_ranges &layer_config_ranges) // #ys_FIXME_experiment { // 1) If there are any height ranges, trim one by the other to make them non-overlapping. Insert the 1st layer if fixed. std::vector<std::pair<t_layer_height_range,coordf_t>> ranges_non_overlapping; - ranges_non_overlapping.reserve(layer_height_ranges.size() * 4); +// ranges_non_overlapping.reserve(layer_height_ranges.size() * 4); + ranges_non_overlapping.reserve(layer_config_ranges.size() * 4); // #ys_FIXME_experiment if (slicing_params.first_object_layer_height_fixed()) ranges_non_overlapping.push_back(std::pair<t_layer_height_range,coordf_t>( t_layer_height_range(0., slicing_params.first_object_layer_height), slicing_params.first_object_layer_height)); // The height ranges are sorted lexicographically by low / high layer boundaries. - for (t_layer_height_ranges::const_iterator it_range = layer_height_ranges.begin(); it_range != layer_height_ranges.end(); ++ it_range) { +// for (t_layer_height_ranges::const_iterator it_range = layer_height_ranges.begin(); it_range != layer_height_ranges.end(); ++ it_range) { + for (t_layer_config_ranges::const_iterator it_range = layer_config_ranges.begin(); + it_range != layer_config_ranges.end(); ++ it_range) { // #ys_FIXME_experiment coordf_t lo = it_range->first.first; coordf_t hi = std::min(it_range->first.second, slicing_params.object_print_z_height()); - coordf_t height = it_range->second; +// coordf_t height = it_range->second; + coordf_t height = it_range->second.option("layer_height")->getFloat(); // #ys_FIXME_experiment if (! ranges_non_overlapping.empty()) // Trim current low with the last high. lo = std::max(lo, ranges_non_overlapping.back().first.second); diff --git a/src/libslic3r/Slicing.hpp b/src/libslic3r/Slicing.hpp index fa5a12f9c..ea5413e9c 100644 --- a/src/libslic3r/Slicing.hpp +++ b/src/libslic3r/Slicing.hpp @@ -11,6 +11,8 @@ #include "libslic3r.h" #include "Utils.hpp" +#include "PrintConfig.hpp" + namespace Slic3r { @@ -129,10 +131,12 @@ inline bool equal_layering(const SlicingParameters &sp1, const SlicingParameters typedef std::pair<coordf_t,coordf_t> t_layer_height_range; typedef std::map<t_layer_height_range,coordf_t> t_layer_height_ranges; +typedef std::map<t_layer_height_range, DynamicPrintConfig> t_layer_config_ranges; extern std::vector<coordf_t> layer_height_profile_from_ranges( const SlicingParameters &slicing_params, - const t_layer_height_ranges &layer_height_ranges); +// const t_layer_height_ranges &layer_height_ranges); + const t_layer_config_ranges &layer_config_ranges); extern std::vector<coordf_t> layer_height_profile_adaptive( const SlicingParameters &slicing_params, diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 570e23baa..13f563fd0 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -81,6 +81,8 @@ set(SLIC3R_GUI_SOURCES GUI/GUI_ObjectManipulation.hpp GUI/GUI_ObjectSettings.cpp GUI/GUI_ObjectSettings.hpp + GUI/GUI_ObjectLayers.cpp + GUI/GUI_ObjectLayers.hpp GUI/LambdaObjectDialog.cpp GUI/LambdaObjectDialog.hpp GUI/Tab.cpp diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 3aada45f3..fc7052ef2 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -924,6 +924,11 @@ ObjectList* GUI_App::obj_list() return sidebar().obj_list(); } +ObjectLayers* GUI_App::obj_layers() +{ + return sidebar().obj_layers(); +} + Plater* GUI_App::plater() { return plater_; diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 3f8b23e2d..ee8a49d05 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -155,6 +155,7 @@ public: ObjectManipulation* obj_manipul(); ObjectSettings* obj_settings(); ObjectList* obj_list(); + ObjectLayers* obj_layers(); Plater* plater(); std::vector<ModelObject*> *model_objects(); diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp new file mode 100644 index 000000000..12c1022ce --- /dev/null +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -0,0 +1,320 @@ +#include "GUI_ObjectLayers.hpp" +#include "GUI_ObjectList.hpp" + +#include "OptionsGroup.hpp" +#include "PresetBundle.hpp" +#include "libslic3r/Model.hpp" + +#include <boost/algorithm/string.hpp> + +#include "I18N.hpp" + +#include <wx/wupdlock.h> + +namespace Slic3r +{ +namespace GUI +{ + +ObjectLayers::ObjectLayers(wxWindow* parent) : + OG_Settings(parent, true) +{ + m_grid_sizer = new wxFlexGridSizer(3, 5, 5); // "Min Z", "Max Z", "Layer height" & buttons sizer + m_grid_sizer->SetFlexibleDirection(wxHORIZONTAL); + + // Legend for object layers + for (const std::string col : { "Min Z", "Max Z", "Layer height" }) { + auto temp = new wxStaticText(m_parent, wxID_ANY, _(L(col)), wxDefaultPosition, /*size*/wxDefaultSize, wxST_ELLIPSIZE_MIDDLE); + temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + temp->SetBackgroundStyle(wxBG_STYLE_PAINT); + temp->SetFont(wxGetApp().bold_font()); + + m_grid_sizer->Add(temp); + } + + m_og->sizer->Clear(true); + m_og->sizer->Add(m_grid_sizer, 0, wxEXPAND | wxALL, wxOSX ? 0 : 5); + + m_bmp_delete = ScalableBitmap(parent, "remove_copies"/*"cross"*/); + m_bmp_add = ScalableBitmap(parent, "add_copies"); +} + +void ObjectLayers::select_editor(LayerRangeEditor* editor, const bool is_last_edited_range) +{ + if (is_last_edited_range && m_selection_type == editor->type()) { + /* Workaround! Under OSX we should use CallAfter() for SetFocus() after LayerEditors "reorganizations", + * because of selected control's strange behavior: + * cursor is set to the control, but blue border - doesn't. + * And as a result we couldn't edit this control. + * */ +#ifdef __WXOSX__ + wxTheApp->CallAfter([editor]() { +#endif + editor->SetFocus(); + editor->SetInsertionPointEnd(); +#ifdef __WXOSX__ + }); +#endif + } +} + +wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range) +{ + const bool is_last_edited_range = range == m_selectable_range; + + auto set_focus_fn = [range, this](const EditorType type) + { + m_selectable_range = range; + m_selection_type = type; + }; + + auto set_focus = [range, this](const t_layer_height_range& new_range, EditorType type, bool enter_pressed) + { + // change selectable range for new one, if enter was pressed or if same range was selected + if (enter_pressed || m_selectable_range == range) + m_selectable_range = new_range; + if (enter_pressed) + m_selection_type = type; + }; + + // Add control for the "Min Z" + + auto editor = new LayerRangeEditor(m_parent, double_to_string(range.first), etMinZ, + set_focus_fn, [range, set_focus, this](coordf_t min_z, bool enter_pressed) + { + if (fabs(min_z - range.first) < EPSILON || min_z > range.second) { + m_selection_type = etUndef; + return false; + } + + // data for next focusing + const t_layer_height_range& new_range = { min_z, range.second }; + set_focus(new_range, etMinZ, enter_pressed); + + return wxGetApp().obj_list()->edit_layer_range(range, new_range); + }); + + select_editor(editor, is_last_edited_range); + m_grid_sizer->Add(editor); + + // Add control for the "Max Z" + + editor = new LayerRangeEditor(m_parent, double_to_string(range.second), etMaxZ, + set_focus_fn, [range, set_focus, this](coordf_t max_z, bool enter_pressed) + { + if (fabs(max_z - range.second) < EPSILON || range.first > max_z) { + m_selection_type = etUndef; + return false; // LayersList would not be updated/recreated + } + + // data for next focusing + const t_layer_height_range& new_range = { range.first, max_z }; + set_focus(new_range, etMaxZ, enter_pressed); + + return wxGetApp().obj_list()->edit_layer_range(range, new_range); + }); + + select_editor(editor, is_last_edited_range); + m_grid_sizer->Add(editor); + + // Add control for the "Layer height" + + editor = new LayerRangeEditor(m_parent, + double_to_string(m_object->layer_config_ranges[range].option("layer_height")->getFloat()), + etLayerHeight, set_focus_fn, [range, this](coordf_t layer_height, bool) + { + return wxGetApp().obj_list()->edit_layer_range(range, layer_height); + }); + + select_editor(editor, is_last_edited_range); + + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(editor); + m_grid_sizer->Add(sizer); + + return sizer; +} + +void ObjectLayers::create_layers_list() +{ + for (const auto layer : m_object->layer_config_ranges) + { + const t_layer_height_range& range = layer.first; + auto sizer = create_layer(range); + + auto del_btn = new ScalableButton(m_parent, wxID_ANY, m_bmp_delete); + del_btn->SetToolTip(_(L("Remove layer"))); + + sizer->Add(del_btn, 0, wxRIGHT | wxLEFT, em_unit(m_parent)); + + del_btn->Bind(wxEVT_BUTTON, [this, range](wxEvent &event) { + wxGetApp().obj_list()->del_layer_range(range); + }); + + auto add_btn = new ScalableButton(m_parent, wxID_ANY, m_bmp_add); + add_btn->SetToolTip(_(L("Add layer"))); + + sizer->Add(add_btn, 0, wxRIGHT, em_unit(m_parent)); + + add_btn->Bind(wxEVT_BUTTON, [this, range](wxEvent &event) { + wxGetApp().obj_list()->add_layer_range_after_current(range); + }); + } +} + +void ObjectLayers::update_layers_list() +{ + ObjectList* objects_ctrl = wxGetApp().obj_list(); + if (objects_ctrl->multiple_selection()) return; + + const auto item = objects_ctrl->GetSelection(); + if (!item) return; + + const int obj_idx = objects_ctrl->get_selected_obj_idx(); + if (obj_idx < 0) return; + + const ItemType type = objects_ctrl->GetModel()->GetItemType(item); + if (!(type & (itLayerRoot | itLayer))) return; + + m_object = objects_ctrl->object(obj_idx); + if (!m_object || m_object->layer_config_ranges.empty()) return; + + // Delete all controls from options group except of the legends + + const int cols = m_grid_sizer->GetEffectiveColsCount(); + const int rows = m_grid_sizer->GetEffectiveRowsCount(); + for (int idx = cols*rows-1; idx >= cols; idx--) { + wxSizerItem* t = m_grid_sizer->GetItem(idx); + if (t->IsSizer()) + t->GetSizer()->Clear(true); + else + t->DeleteWindows(); + m_grid_sizer->Remove(idx); + } + + // Add new control according to the selected item + + if (type & itLayerRoot) + create_layers_list(); + else + create_layer(objects_ctrl->GetModel()->GetLayerRangeByItem(item)); + + m_parent->Layout(); +} + +void ObjectLayers::UpdateAndShow(const bool show) +{ + if (show) + update_layers_list(); + + OG_Settings::UpdateAndShow(show); +} + +void ObjectLayers::msw_rescale() +{ + m_bmp_delete.msw_rescale(); + m_bmp_add.msw_rescale(); +} + +LayerRangeEditor::LayerRangeEditor( wxWindow* parent, + const wxString& value, + EditorType type, + std::function<void(EditorType)> set_focus_fn, + std::function<bool(coordf_t, bool enter_pressed)> edit_fn + ) : + m_valid_value(value), + m_type(type), + m_set_focus(set_focus_fn), + wxTextCtrl(parent, wxID_ANY, value, wxDefaultPosition, + wxSize(8 * em_unit(parent), wxDefaultCoord), wxTE_PROCESS_ENTER) +{ + this->SetFont(wxGetApp().normal_font()); + + this->Bind(wxEVT_TEXT_ENTER, [this, edit_fn](wxEvent&) + { + m_enter_pressed = true; + // If LayersList wasn't updated/recreated, we can call wxEVT_KILL_FOCUS.Skip() + if (m_type&etLayerHeight) { + if (!edit_fn(get_value(), true)) + SetValue(m_valid_value); + else + m_valid_value = double_to_string(get_value()); + m_call_kill_focus = true; + } + else if (!edit_fn(get_value(), true)) { + SetValue(m_valid_value); + m_call_kill_focus = true; + } + }, this->GetId()); + + this->Bind(wxEVT_KILL_FOCUS, [this, edit_fn](wxFocusEvent& e) + { + if (!m_enter_pressed) { +#ifndef __WXGTK__ + /* Update data for next editor selection. + * But under GTK it lucks like there is no information about selected control at e.GetWindow(), + * so we'll take it from wxEVT_LEFT_DOWN event + * */ + LayerRangeEditor* new_editor = dynamic_cast<LayerRangeEditor*>(e.GetWindow()); + if (new_editor) + new_editor->set_focus(); +#endif // not __WXGTK__ + // If LayersList wasn't updated/recreated, we should call e.Skip() + if (m_type & etLayerHeight) { + if (!edit_fn(get_value(), false)) + SetValue(m_valid_value); + else + m_valid_value = double_to_string(get_value()); + e.Skip(); + } + else if (!edit_fn(get_value(), false)) { + SetValue(m_valid_value); + e.Skip(); + } + } + else if (m_call_kill_focus) { + m_call_kill_focus = false; + e.Skip(); + } + }, this->GetId()); + +#ifdef __WXGTK__ // Workaround! To take information about selectable range + this->Bind(wxEVT_LEFT_DOWN, [this](wxEvent& e) + { + set_focus(); + e.Skip(); + }, this->GetId()); +#endif //__WXGTK__ + + this->Bind(wxEVT_CHAR, ([this](wxKeyEvent& event) + { + // select all text using Ctrl+A + if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL)) + this->SetSelection(-1, -1); //select all + event.Skip(); + })); +} + +coordf_t LayerRangeEditor::get_value() +{ + wxString str = GetValue(); + + coordf_t layer_height; + // Replace the first occurence of comma in decimal number. + str.Replace(",", ".", false); + if (str == ".") + layer_height = 0.0; + else + { + if (!str.ToCDouble(&layer_height) || layer_height < 0.0f) + { + show_error(m_parent, _(L("Invalid numeric input."))); + SetValue(double_to_string(layer_height)); + } + } + + return layer_height; +} + +} //namespace GUI +} //namespace Slic3r \ No newline at end of file diff --git a/src/slic3r/GUI/GUI_ObjectLayers.hpp b/src/slic3r/GUI/GUI_ObjectLayers.hpp new file mode 100644 index 000000000..e3366e03e --- /dev/null +++ b/src/slic3r/GUI/GUI_ObjectLayers.hpp @@ -0,0 +1,80 @@ +#ifndef slic3r_GUI_ObjectLayers_hpp_ +#define slic3r_GUI_ObjectLayers_hpp_ + +#include "GUI_ObjectSettings.hpp" +#include "wxExtensions.hpp" + +#ifdef __WXOSX__ +#include "../libslic3r/PrintConfig.hpp" +#endif + +class wxBoxSizer; + +namespace Slic3r { +class ModelObject; + +namespace GUI { +class ConfigOptionsGroup; + +typedef double coordf_t; +typedef std::pair<coordf_t, coordf_t> t_layer_height_range; + +enum EditorType +{ + etUndef = 0, + etMinZ = 1, + etMaxZ = 2, + etLayerHeight = 4, +}; + +class LayerRangeEditor : public wxTextCtrl +{ + bool m_enter_pressed { false }; + bool m_call_kill_focus { false }; + wxString m_valid_value; + EditorType m_type; + + std::function<void(EditorType)> m_set_focus; + +public: + LayerRangeEditor( wxWindow* parent, + const wxString& value = wxEmptyString, + EditorType type = etUndef, + std::function<void(EditorType)> set_focus_fn = [](EditorType) {;}, + std::function<bool(coordf_t, bool)> edit_fn = [](coordf_t, bool) {return false; } + ); + ~LayerRangeEditor() {} + + EditorType type() const {return m_type;} + void set_focus() const { m_set_focus(m_type);} + +private: + coordf_t get_value(); +}; + +class ObjectLayers : public OG_Settings +{ + ScalableBitmap m_bmp_delete; + ScalableBitmap m_bmp_add; + ModelObject* m_object {nullptr}; + + wxFlexGridSizer* m_grid_sizer; + t_layer_height_range m_selectable_range; + EditorType m_selection_type {etUndef}; + +public: + ObjectLayers(wxWindow* parent); + ~ObjectLayers() {} + + void select_editor(LayerRangeEditor* editor, const bool is_last_edited_range); + wxSizer* create_layer(const t_layer_height_range& range); // without_buttons + void create_layers_list(); + void update_layers_list(); + + void UpdateAndShow(const bool show) override; + void msw_rescale(); +}; + +}} + +#endif // slic3r_GUI_ObjectLayers_hpp_ diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index a95b71bcb..1161c1565 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1,6 +1,7 @@ #include "libslic3r/libslic3r.h" #include "GUI_ObjectList.hpp" #include "GUI_ObjectManipulation.hpp" +#include "GUI_ObjectLayers.hpp" #include "GUI_App.hpp" #include "I18N.hpp" @@ -147,10 +148,10 @@ ObjectList::ObjectList(wxWindow* parent) : wxAcceleratorTable accel(6, entries); SetAcceleratorTable(accel); - this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_COPY)); }, wxID_COPY); - this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_PASTE)); }, wxID_PASTE); - this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->select_item_all_children(); }, wxID_SELECTALL); - this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->remove(); }, wxID_DELETE); + this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->copy(); }, wxID_COPY); + this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->paste(); }, wxID_PASTE); + this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->select_item_all_children(); }, wxID_SELECTALL); + this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->remove(); }, wxID_DELETE); } #else __WXOSX__ Bind(wxEVT_CHAR, [this](wxKeyEvent& event) { key_event(event); }); // doesn't work on OSX @@ -350,12 +351,13 @@ DynamicPrintConfig& ObjectList::get_item_config(const wxDataViewItem& item) cons const ItemType type = m_objects_model->GetItemType(item); const int obj_idx = type & itObject ? m_objects_model->GetIdByItem(item) : - m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); + m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); const int vol_idx = type & itVolume ? m_objects_model->GetVolumeIdByItem(item) : -1; assert(obj_idx >= 0 || ((type & itVolume) && vol_idx >=0)); return type & itVolume ?(*m_objects)[obj_idx]->volumes[vol_idx]->config : + type & itLayer ?(*m_objects)[obj_idx]->layer_config_ranges[m_objects_model->GetLayerRangeByItem(item)] : (*m_objects)[obj_idx]->config; } @@ -441,16 +443,23 @@ void ObjectList::update_extruder_in_config(const wxDataViewItem& item) { if (m_prevent_update_extruder_in_config) return; - if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { + + const ItemType item_type = m_objects_model->GetItemType(item); + if (item_type & itObject) { const int obj_idx = m_objects_model->GetIdByItem(item); m_config = &(*m_objects)[obj_idx]->config; } else { - const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetParent(item)); + const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); + if (item_type & itVolume) + { const int volume_id = m_objects_model->GetVolumeIdByItem(item); if (obj_idx < 0 || volume_id < 0) return; m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; + } + else if (item_type & itLayer) + m_config = &get_item_config(item); } wxVariant variant; @@ -572,6 +581,56 @@ void ObjectList::selection_changed() part_selection_changed(); } +void ObjectList::fill_layer_config_ranges_cache() +{ + wxDataViewItemArray sel_layers; + GetSelections(sel_layers); + + const int obj_idx = m_objects_model->GetObjectIdByItem(sel_layers[0]); + if (obj_idx < 0 || (int)m_objects->size() <= obj_idx) + return; + + const t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; + m_layer_config_ranges_cache.clear(); + + for (const auto layer_item : sel_layers) + if (m_objects_model->GetItemType(layer_item) & itLayer) { + auto range = m_objects_model->GetLayerRangeByItem(layer_item); + auto it = ranges.find(range); + if (it != ranges.end()) + m_layer_config_ranges_cache[it->first] = it->second; + } +} + +void ObjectList::paste_layers_into_list() +{ + const int obj_idx = m_objects_model->GetObjectIdByItem(GetSelection()); + + if (obj_idx < 0 || (int)m_objects->size() <= obj_idx || + m_layer_config_ranges_cache.empty() || printer_technology() == ptSLA) + return; + + const wxDataViewItem object_item = m_objects_model->GetItemById(obj_idx); + wxDataViewItem layers_item = m_objects_model->GetLayerRootItem(object_item); + if (layers_item) + m_objects_model->Delete(layers_item); + + t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; + + // and create Layer item(s) according to the layer_config_ranges + for (const auto range : m_layer_config_ranges_cache) + ranges.emplace(range); + + layers_item = add_layer_root_item(object_item); + + changed_object(obj_idx); + + select_item(layers_item); +#ifndef __WXOSX__ + selection_changed(); +#endif //no __WXOSX__ +} + void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& volumes) { if ((obj_idx < 0) || ((int)m_objects->size() <= obj_idx)) @@ -653,7 +712,7 @@ void ObjectList::OnContextMenu(wxDataViewEvent&) const wxPoint pt = get_mouse_position_in_control(); HitTest(pt, item, col); if (!item) -#ifdef __WXOSX__ // #ys_FIXME temporary workaround for OSX +#ifdef __WXOSX__ // temporary workaround for OSX // after Yosemite OS X version, HitTest return undefined item item = GetSelection(); if (item) @@ -699,10 +758,11 @@ void ObjectList::show_context_menu() if (item) { const ItemType type = m_objects_model->GetItemType(item); - if (!(type & (itObject | itVolume | itInstance))) + if (!(type & (itObject | itVolume | itLayer | itInstance))) return; wxMenu* menu = type & itInstance ? &m_menu_instance : + type & itLayer ? &m_menu_layer : m_objects_model->GetParent(item) != wxDataViewItem(0) ? &m_menu_part : printer_technology() == ptFFF ? &m_menu_object : &m_menu_sla_object; @@ -713,6 +773,22 @@ void ObjectList::show_context_menu() } } +void ObjectList::copy() +{ + if (m_selection_mode & smLayer) + fill_layer_config_ranges_cache(); + else + wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_COPY)); +} + +void ObjectList::paste() +{ + if (!m_layer_config_ranges_cache.empty()) + paste_layers_into_list(); + else + wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_PASTE)); +} + #ifndef __WXOSX__ void ObjectList::key_event(wxKeyEvent& event) { @@ -727,10 +803,10 @@ void ObjectList::key_event(wxKeyEvent& event) } else if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL/*WXK_SHIFT*/)) select_item_all_children(); - else if (wxGetKeyState(wxKeyCode('C')) && wxGetKeyState(WXK_CONTROL)) - wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_COPY)); + else if (wxGetKeyState(wxKeyCode('C')) && wxGetKeyState(WXK_CONTROL)) + copy(); else if (wxGetKeyState(wxKeyCode('V')) && wxGetKeyState(WXK_CONTROL)) - wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_PASTE)); + paste(); else event.Skip(); } @@ -1033,7 +1109,17 @@ void ObjectList::get_settings_choice(const wxString& category_name) void ObjectList::get_freq_settings_choice(const wxString& bundle_name) { - const std::vector<std::string>& options = get_options_for_bundle(bundle_name); + std::vector<std::string> options = get_options_for_bundle(bundle_name); + + /* Because of we couldn't edited layer_height for ItVolume from settings list, + * correct options according to the selected item type : + * remove "layer_height" option + */ + if ((m_objects_model->GetItemType(GetSelection()) & itVolume) && bundle_name == _("Layers and Perimeters")) { + const auto layer_height_it = std::find(options.begin(), options.end(), "layer_height"); + if (layer_height_it != options.end()) + options.erase(layer_height_it); + } assert(m_config); auto opt_keys = m_config->keys(); @@ -1137,6 +1223,12 @@ wxMenuItem* ObjectList::append_menu_item_split(wxMenu* menu) [this]() { return is_splittable(); }, wxGetApp().plater()); } +wxMenuItem* ObjectList::append_menu_item_layers_editing(wxMenu* menu) +{ + return append_menu_item(menu, wxID_ANY, _(L("Edit Layers")), "", + [this](wxCommandEvent&) { layers_editing(); }, "layers", menu); +} + wxMenuItem* ObjectList::append_menu_item_settings(wxMenu* menu_) { MenuWithSeparators* menu = dynamic_cast<MenuWithSeparators*>(menu_); @@ -1301,7 +1393,11 @@ void ObjectList::create_object_popupmenu(wxMenu *menu) append_menu_item_scale_selection_to_fit_print_volume(menu); // Split object to parts - m_menu_item_split = append_menu_item_split(menu); + append_menu_item_split(menu); + menu->AppendSeparator(); + + // Layers Editing for object + append_menu_item_layers_editing(menu); menu->AppendSeparator(); // rest of a object_menu will be added later in: @@ -1330,7 +1426,7 @@ void ObjectList::create_part_popupmenu(wxMenu *menu) append_menu_item_fix_through_netfabb(menu); append_menu_item_export_stl(menu); - m_menu_item_split_part = append_menu_item_split(menu); + append_menu_item_split(menu); // Append change part type menu->AppendSeparator(); @@ -1636,38 +1732,52 @@ void ObjectList::del_subobject_item(wxDataViewItem& item) ItemType type; m_objects_model->GetItemInfo(item, type, obj_idx, idx); - if (type == itUndef) + if (type & itUndef) return; - if (type == itSettings) - del_settings_from_config(); - else if (type == itInstanceRoot && obj_idx != -1) + if (type & itSettings) + del_settings_from_config(m_objects_model->GetParent(item)); + else if (type & itInstanceRoot && obj_idx != -1) del_instances_from_object(obj_idx); + else if (type & itLayerRoot && obj_idx != -1) + del_layers_from_object(obj_idx); + else if (type & itLayer && obj_idx != -1) + del_layer_from_object(obj_idx, m_objects_model->GetLayerRangeByItem(item)); else if (idx == -1) return; else if (!del_subobject_from_object(obj_idx, idx, type)) return; // If last volume item with warning was deleted, unmark object item - if (type == itVolume && (*m_objects)[obj_idx]->get_mesh_errors_count() == 0) + if (type & itVolume && (*m_objects)[obj_idx]->get_mesh_errors_count() == 0) m_objects_model->DeleteWarningIcon(m_objects_model->GetParent(item)); m_objects_model->Delete(item); } -void ObjectList::del_settings_from_config() +void ObjectList::del_settings_from_config(const wxDataViewItem& parent_item) { - auto opt_keys = m_config->keys(); - if (opt_keys.size() == 1 && opt_keys[0] == "extruder") + const bool is_layer_settings = m_objects_model->GetItemType(parent_item) == itLayer; + + const int opt_cnt = m_config->keys().size(); + if (opt_cnt == 1 && m_config->has("extruder") || + is_layer_settings && opt_cnt == 2 && m_config->has("extruder") && m_config->has("layer_height")) return; + int extruder = -1; if (m_config->has("extruder")) extruder = m_config->option<ConfigOptionInt>("extruder")->value; + coordf_t layer_height = 0.0; + if (is_layer_settings) + layer_height = m_config->opt_float("layer_height"); + m_config->clear(); if (extruder >= 0) m_config->set_key_value("extruder", new ConfigOptionInt(extruder)); + if (is_layer_settings) + m_config->set_key_value("layer_height", new ConfigOptionFloat(layer_height)); } void ObjectList::del_instances_from_object(const int obj_idx) @@ -1684,6 +1794,24 @@ void ObjectList::del_instances_from_object(const int obj_idx) changed_object(obj_idx); } +void ObjectList::del_layer_from_object(const int obj_idx, const t_layer_height_range& layer_range) +{ + const auto del_range = object(obj_idx)->layer_config_ranges.find(layer_range); + if (del_range == object(obj_idx)->layer_config_ranges.end()) + return; + + object(obj_idx)->layer_config_ranges.erase(del_range); + + changed_object(obj_idx); +} + +void ObjectList::del_layers_from_object(const int obj_idx) +{ + object(obj_idx)->layer_config_ranges.clear(); + + changed_object(obj_idx); +} + bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, const int type) { if (obj_idx == 1000) @@ -1779,6 +1907,66 @@ void ObjectList::split() changed_object(obj_idx); } +void ObjectList::layers_editing() +{ + const auto item = GetSelection(); + const int obj_idx = get_selected_obj_idx(); + if (!item || obj_idx < 0) + return; + + const wxDataViewItem obj_item = m_objects_model->GetTopParent(item); + wxDataViewItem layers_item = m_objects_model->GetLayerRootItem(obj_item); + + // if it doesn't exist now + if (!layers_item.IsOk()) + { + t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; + + // set some default value + if (ranges.empty()) + ranges[{ 0.0f, 0.6f }] = get_default_layer_config(obj_idx); + + // create layer root item + layers_item = add_layer_root_item(obj_item); + } + if (!layers_item.IsOk()) + return; + + // select LayerRoor item and expand + select_item(layers_item); + Expand(layers_item); +} + +wxDataViewItem ObjectList::add_layer_root_item(const wxDataViewItem obj_item) +{ + const int obj_idx = m_objects_model->GetIdByItem(obj_item); + if (obj_idx < 0 || + object(obj_idx)->layer_config_ranges.empty() || + printer_technology() == ptSLA) + return wxDataViewItem(0); + + // create LayerRoot item + wxDataViewItem layers_item = m_objects_model->AddLayersRoot(obj_item); + + // and create Layer item(s) according to the layer_config_ranges + for (const auto range : object(obj_idx)->layer_config_ranges) + add_layer_item(range.first, layers_item); + + return layers_item; +} + +DynamicPrintConfig ObjectList::get_default_layer_config(const int obj_idx) +{ + DynamicPrintConfig config; + coordf_t layer_height = object(obj_idx)->config.has("layer_height") ? + object(obj_idx)->config.opt_float("layer_height") : + wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_float("layer_height"); + config.set_key_value("layer_height",new ConfigOptionFloat(layer_height)); + config.set_key_value("extruder", new ConfigOptionInt(0)); + + return config; +} + bool ObjectList::get_volume_by_item(const wxDataViewItem& item, ModelVolume*& volume) { auto obj_idx = get_selected_obj_idx(); @@ -1848,6 +2036,7 @@ void ObjectList::part_selection_changed() bool update_and_show_manipulations = false; bool update_and_show_settings = false; + bool update_and_show_layers = false; const auto item = GetSelection(); @@ -1870,36 +2059,47 @@ void ObjectList::part_selection_changed() update_and_show_manipulations = true; } else { - auto parent = m_objects_model->GetParent(item); - // Take ID of the parent object to "inform" perl-side which object have to be selected on the scene - obj_idx = m_objects_model->GetIdByItem(parent); - if (m_objects_model->GetItemType(item) == itSettings) { - if (m_objects_model->GetParent(parent) == wxDataViewItem(0)) { + obj_idx = m_objects_model->GetObjectIdByItem(item); + + const ItemType type = m_objects_model->GetItemType(item); + if (type & itSettings) { + const auto parent = m_objects_model->GetParent(item); + const ItemType parent_type = m_objects_model->GetItemType(parent); + + if (parent_type & itObject) { og_name = _(L("Object Settings to modify")); m_config = &(*m_objects)[obj_idx]->config; } - else { + else if (parent_type & itVolume) { og_name = _(L("Part Settings to modify")); - auto main_parent = m_objects_model->GetParent(parent); - obj_idx = m_objects_model->GetIdByItem(main_parent); - const auto volume_id = m_objects_model->GetVolumeIdByItem(parent); + volume_id = m_objects_model->GetVolumeIdByItem(parent); m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; } + else if (parent_type & itLayer) { + og_name = _(L("Layer range Settings to modify")); + m_config = &get_item_config(parent); + } update_and_show_settings = true; } - else if (m_objects_model->GetItemType(item) == itVolume) { + else if (type & itVolume) { og_name = _(L("Part manipulation")); volume_id = m_objects_model->GetVolumeIdByItem(item); m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; update_and_show_manipulations = true; } - else if (m_objects_model->GetItemType(item) == itInstance) { + else if (type & itInstance) { og_name = _(L("Instance manipulation")); update_and_show_manipulations = true; // fill m_config by object's values - const int obj_idx_ = m_objects_model->GetObjectIdByItem(item); - m_config = &(*m_objects)[obj_idx_]->config; + m_config = &(*m_objects)[obj_idx]->config; + } + else if (type & (itLayerRoot|itLayer)) { + og_name = type & itLayerRoot ? _(L("Layers Editing")) : _(L("Layer Editing")); + update_and_show_layers = true; + + if (type & itLayer) + m_config = &get_item_config(item); } } } @@ -1919,11 +2119,17 @@ void ObjectList::part_selection_changed() if (update_and_show_settings) wxGetApp().obj_settings()->get_og()->set_name(" " + og_name + " "); + if (printer_technology() == ptSLA) + update_and_show_layers = false; + else if (update_and_show_layers) + wxGetApp().obj_layers()->get_og()->set_name(" " + og_name + " "); + Sidebar& panel = wxGetApp().sidebar(); panel.Freeze(); wxGetApp().obj_manipul() ->UpdateAndShow(update_and_show_manipulations); wxGetApp().obj_settings()->UpdateAndShow(update_and_show_settings); + wxGetApp().obj_layers() ->UpdateAndShow(update_and_show_layers); wxGetApp().sidebar().show_info_sizer(); panel.Layout(); @@ -1969,6 +2175,9 @@ void ObjectList::add_object_to_list(size_t obj_idx) Expand(item); } + // Add layers if it has + add_layer_root_item(item); + #ifndef __WXOSX__ selection_changed(); #endif //__WXMSW__ @@ -2113,16 +2322,196 @@ void ObjectList::remove() wxDataViewItemArray sels; GetSelections(sels); + wxDataViewItem parent = wxDataViewItem(0); + for (auto& item : sels) { if (m_objects_model->GetParent(item) == wxDataViewItem(0)) delete_from_model_and_list(itObject, m_objects_model->GetIdByItem(item), -1); else { - if (sels.size() == 1) + if (m_objects_model->GetItemType(item) & itLayer) { + parent = m_objects_model->GetParent(item); + wxDataViewItemArray children; + if (m_objects_model->GetChildren(parent, children) == 1) + parent = m_objects_model->GetTopParent(item); + } + else if (sels.size() == 1) select_item(m_objects_model->GetParent(item)); + del_subobject_item(item); } } + + if (parent) + select_item(parent); +} + +void ObjectList::del_layer_range(const t_layer_height_range& range) +{ + const int obj_idx = get_selected_obj_idx(); + if (obj_idx < 0) return; + + t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; + + wxDataViewItem selectable_item = GetSelection(); + + if (ranges.size() == 1) + selectable_item = m_objects_model->GetParent(selectable_item); + + wxDataViewItem layer_item = m_objects_model->GetItemByLayerRange(obj_idx, range); + del_subobject_item(layer_item); + + select_item(selectable_item); +} + +double get_min_layer_height(const int extruder_idx) +{ + const DynamicPrintConfig& config = wxGetApp().preset_bundle->printers.get_edited_preset().config; + return config.opt_float("min_layer_height", extruder_idx <= 0 ? 0 : extruder_idx-1); +} + +double get_max_layer_height(const int extruder_idx) +{ + const DynamicPrintConfig& config = wxGetApp().preset_bundle->printers.get_edited_preset().config; + return config.opt_float("max_layer_height", extruder_idx <= 0 ? 0 : extruder_idx-1); +} + +void ObjectList::add_layer_range_after_current(const t_layer_height_range& current_range) +{ + const int obj_idx = get_selected_obj_idx(); + if (obj_idx < 0) return; + + const wxDataViewItem layers_item = GetSelection(); + + t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; + + const t_layer_height_range& last_range = (--ranges.end())->first; + + if (current_range == last_range) + { + const t_layer_height_range& new_range = { last_range.second, last_range.second + 0.5f }; + ranges[new_range] = get_default_layer_config(obj_idx); + add_layer_item(new_range, layers_item); + } + else + { + const t_layer_height_range& next_range = (++ranges.find(current_range))->first; + + if (current_range.second > next_range.first) + return; // range division has no sense + + const int layer_idx = m_objects_model->GetItemIdByLayerRange(obj_idx, next_range); + if (layer_idx < 0) + return; + + if (current_range.second == next_range.first) + { + const auto old_config = ranges.at(next_range); + + const coordf_t delta = (next_range.second - next_range.first); + if (delta < get_min_layer_height(old_config.opt_int("extruder"))/*0.05f*/) // next range division has no sense + return; + + const coordf_t midl_layer = next_range.first + 0.5f * delta; + + t_layer_height_range new_range = { midl_layer, next_range.second }; + + // delete old layer + + wxDataViewItem layer_item = m_objects_model->GetItemByLayerRange(obj_idx, next_range); + del_subobject_item(layer_item); + + // create new 2 layers instead of deleted one + + ranges[new_range] = old_config; + add_layer_item(new_range, layers_item, layer_idx); + + new_range = { current_range.second, midl_layer }; + ranges[new_range] = get_default_layer_config(obj_idx); + add_layer_item(new_range, layers_item, layer_idx); + } + else + { + const t_layer_height_range new_range = { current_range.second, next_range.first }; + ranges[new_range] = get_default_layer_config(obj_idx); + add_layer_item(new_range, layers_item, layer_idx); + } + } + + changed_object(obj_idx); + + // select item to update layers sizer + select_item(layers_item); +} + +void ObjectList::add_layer_item(const t_layer_height_range& range, + const wxDataViewItem layers_item, + const int layer_idx /* = -1*/) +{ + const int obj_idx = m_objects_model->GetObjectIdByItem(layers_item); + if (obj_idx < 0) return; + + const DynamicPrintConfig& config = object(obj_idx)->layer_config_ranges[range]; + if (!config.has("extruder")) + return; + + const auto layer_item = m_objects_model->AddLayersChild(layers_item, + range, + config.opt_int("extruder"), + layer_idx); + + if (config.keys().size() > 2) + select_item(m_objects_model->AddSettingsChild(layer_item)); +} + +bool ObjectList::edit_layer_range(const t_layer_height_range& range, coordf_t layer_height) +{ + const int obj_idx = get_selected_obj_idx(); + if (obj_idx < 0) + return false; + + DynamicPrintConfig* config = &object(obj_idx)->layer_config_ranges[range]; + if (fabs(layer_height - config->opt_float("layer_height")) < EPSILON) + return false; + + const int extruder_idx = config->opt_int("extruder"); + + if (layer_height >= get_min_layer_height(extruder_idx) && + layer_height <= get_max_layer_height(extruder_idx)) + { + config->set_key_value("layer_height", new ConfigOptionFloat(layer_height)); + return true; + } + + return false; +} + +bool ObjectList::edit_layer_range(const t_layer_height_range& range, const t_layer_height_range& new_range) +{ + const int obj_idx = get_selected_obj_idx(); + if (obj_idx < 0) return false; + + const ItemType sel_type = m_objects_model->GetItemType(GetSelection()); + + t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; + + const DynamicPrintConfig config = ranges[range]; + + ranges.erase(range); + ranges[new_range] = config; + + wxDataViewItem root_item = m_objects_model->GetLayerRootItem(m_objects_model->GetItemById(obj_idx)); + m_objects_model->DeleteChildren(root_item); + + if (root_item.IsOk()) + // create Layer item(s) according to the layer_config_ranges + for (const auto r : ranges) + add_layer_item(r.first, root_item); + + select_item(sel_type&itLayer ? m_objects_model->GetItemByLayerRange(obj_idx, new_range) : root_item); + Expand(root_item); + + return true; } void ObjectList::init_objects() @@ -2152,11 +2541,12 @@ void ObjectList::update_selections() m_selection_mode = smInstance; // We doesn't update selection if SettingsItem for the current object/part is selected - if (GetSelectedItemsCount() == 1 && m_objects_model->GetItemType(GetSelection()) == itSettings ) +// if (GetSelectedItemsCount() == 1 && m_objects_model->GetItemType(GetSelection()) == itSettings ) + if (GetSelectedItemsCount() == 1 && m_objects_model->GetItemType(GetSelection()) & (itSettings | itLayerRoot | itLayer)) { const auto item = GetSelection(); if (selection.is_single_full_object()) { - if ( m_objects_model->GetIdByItem(m_objects_model->GetParent(item)) == selection.get_object_idx()) + if (m_objects_model->GetObjectIdByItem(item) == selection.get_object_idx()) return; sels.Add(m_objects_model->GetItemById(selection.get_object_idx())); } @@ -2277,22 +2667,18 @@ void ObjectList::update_selections_on_canvas() auto add_to_selection = [this](const wxDataViewItem& item, Selection& selection, int instance_idx, bool as_single_selection) { const ItemType& type = m_objects_model->GetItemType(item); - if ( type == itInstanceRoot || m_objects_model->GetParent(item) == wxDataViewItem(0) ) { - wxDataViewItem obj_item = type == itInstanceRoot ? m_objects_model->GetParent(item) : item; - selection.add_object(m_objects_model->GetIdByItem(obj_item), as_single_selection); - return; - } + const int obj_idx = m_objects_model->GetObjectIdByItem(item); if (type == itVolume) { - const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetParent(item)); const int vol_idx = m_objects_model->GetVolumeIdByItem(item); selection.add_volume(obj_idx, vol_idx, std::max(instance_idx, 0), as_single_selection); } else if (type == itInstance) { - const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); const int inst_idx = m_objects_model->GetInstanceIdByItem(item); selection.add_instance(obj_idx, inst_idx, as_single_selection); } + else + selection.add_object(obj_idx, as_single_selection); }; // stores current instance idx before to clear the selection @@ -2300,7 +2686,7 @@ void ObjectList::update_selections_on_canvas() if (sel_cnt == 1) { wxDataViewItem item = GetSelection(); - if (m_objects_model->GetItemType(item) & (itSettings|itInstanceRoot)) + if (m_objects_model->GetItemType(item) & (itSettings | itInstanceRoot | itLayerRoot | itLayer)) add_to_selection(m_objects_model->GetParent(item), selection, instance_idx, true); else add_to_selection(item, selection, instance_idx, true); @@ -2363,11 +2749,13 @@ void ObjectList::select_item_all_children() } else { const auto item = GetSelection(); - // Some volume(instance) is selected => select all volumes(instances) inside the current object - if (m_objects_model->GetItemType(item) & (itVolume | itInstance)) + const ItemType item_type = m_objects_model->GetItemType(item); + // Some volume/layer/instance is selected => select all volumes/layers/instances inside the current object + if (item_type & (itVolume | itInstance | itLayer)) m_objects_model->GetChildren(m_objects_model->GetParent(item), sels); - m_selection_mode = m_objects_model->GetItemType(item)&itVolume ? smVolume : smInstance; + m_selection_mode = item_type&itVolume ? smVolume : + item_type&itLayer ? smLayer : smInstance; } SetSelections(sels); @@ -2386,8 +2774,9 @@ void ObjectList::update_selection_mode() } const ItemType type = m_objects_model->GetItemType(GetSelection()); - m_selection_mode = type&itSettings ? smUndef : - type&itVolume ? smVolume : smInstance; + m_selection_mode = type & itSettings ? smUndef : + type & itLayer ? smLayer : + type & itVolume ? smVolume : smInstance; } // check last selected item. If is it possible to select it @@ -2398,33 +2787,37 @@ bool ObjectList::check_last_selection(wxString& msg_str) const bool is_shift_pressed = wxGetKeyState(WXK_SHIFT); - /* We can't mix Parts and Objects/Instances. + /* We can't mix Volumes, Layers and Objects/Instances. * So, show information about it */ const ItemType type = m_objects_model->GetItemType(m_last_selected_item); - // check a case of a selection of the Parts from different Objects - bool impossible_multipart_selection = false; - if (type & itVolume && m_selection_mode == smVolume) - { + // check a case of a selection of the same type items from different Objects + auto impossible_multi_selection = [type, this](const ItemType item_type, const SELECTION_MODE selection_mode) { + if (!(type & item_type && m_selection_mode & selection_mode)) + return false; + wxDataViewItemArray sels; GetSelections(sels); - for (const auto& sel: sels) - if (sel != m_last_selected_item && - m_objects_model->GetParent(sel) != m_objects_model->GetParent(m_last_selected_item)) - { - impossible_multipart_selection = true; - break; - } - } + for (const auto& sel : sels) + if (sel != m_last_selected_item && + m_objects_model->GetTopParent(sel) != m_objects_model->GetTopParent(m_last_selected_item)) + return true; - if (impossible_multipart_selection || + return false; + }; + + if (impossible_multi_selection(itVolume, smVolume) || + impossible_multi_selection(itLayer, smLayer ) || type & itSettings || - type & itVolume && m_selection_mode == smInstance || - !(type & itVolume) && m_selection_mode == smVolume) + type & itVolume && !(m_selection_mode & smVolume ) || + type & itLayer && !(m_selection_mode & smLayer ) || + type & itInstance && !(m_selection_mode & smInstance) + ) { // Inform user why selection isn't complited - const wxString item_type = m_selection_mode == smInstance ? _(L("Object or Instance")) : _(L("Part")); + const wxString item_type = m_selection_mode & smInstance ? _(L("Object or Instance")) : + m_selection_mode & smVolume ? _(L("Part")) : _(L("Layer")); msg_str = wxString::Format( _(L("Unsupported selection")) + "\n\n" + _(L("You started your selection with %s Item.")) + "\n" + @@ -2461,7 +2854,7 @@ void ObjectList::fix_multiselection_conflicts() wxDataViewItemArray sels; GetSelections(sels); - if (m_selection_mode == smVolume) + if (m_selection_mode & (smVolume|smLayer)) { // identify correct parent of the initial selected item const wxDataViewItem& parent = m_objects_model->GetParent(m_last_selected_item == sels.front() ? sels.back() : sels.front()); @@ -2470,8 +2863,10 @@ void ObjectList::fix_multiselection_conflicts() wxDataViewItemArray children; // selected volumes from current parent m_objects_model->GetChildren(parent, children); + const ItemType item_type = m_selection_mode & smVolume ? itVolume : itLayer; + for (const auto child : children) - if (IsSelected(child) && m_objects_model->GetItemType(child)&itVolume) + if (IsSelected(child) && m_objects_model->GetItemType(child) & item_type) sels.Add(child); // If some part is selected, unselect all items except of selected parts of the current object @@ -2636,6 +3031,87 @@ void ObjectList::update_settings_items() m_prevent_canvas_selection_update = false; } +// Update settings item for item had it +void ObjectList::update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections) +{ + const wxDataViewItem& settings_item = m_objects_model->GetSettingsItem(item); + select_item(settings_item ? settings_item : m_objects_model->AddSettingsChild(item)); + + // If settings item was deleted from the list, + // it's need to be deleted from selection array, if it was there + if (settings_item != m_objects_model->GetSettingsItem(item) && + selections.Index(settings_item) != wxNOT_FOUND) { + selections.Remove(settings_item); + + // Select item, if settings_item doesn't exist for item anymore, but was selected + if (selections.Index(item) == wxNOT_FOUND) + selections.Add(item); + } +} + +void ObjectList::update_object_list_by_printer_technology() +{ + m_prevent_canvas_selection_update = true; + wxDataViewItemArray sel; + GetSelections(sel); // stash selection + + wxDataViewItemArray object_items; + m_objects_model->GetChildren(wxDataViewItem(0), object_items); + + for (auto& object_item : object_items) { + // Update Settings Item for object + update_settings_item_and_selection(object_item, sel); + + // Update settings for Volumes + wxDataViewItemArray all_object_subitems; + m_objects_model->GetChildren(object_item, all_object_subitems); + for (auto item : all_object_subitems) + if (m_objects_model->GetItemType(item) & itVolume) + // update settings for volume + update_settings_item_and_selection(item, sel); + + // Update Layers Items + wxDataViewItem layers_item = m_objects_model->GetLayerRootItem(object_item); + if (!layers_item) + layers_item = add_layer_root_item(object_item); + else if (printer_technology() == ptSLA) { + // If layers root item will be deleted from the list, so + // it's need to be deleted from selection array, if it was there + wxDataViewItemArray del_items; + bool some_layers_was_selected = false; + m_objects_model->GetAllChildren(layers_item, del_items); + for (auto& del_item:del_items) + if (sel.Index(del_item) != wxNOT_FOUND) { + some_layers_was_selected = true; + sel.Remove(del_item); + } + if (sel.Index(layers_item) != wxNOT_FOUND) { + some_layers_was_selected = true; + sel.Remove(layers_item); + } + + // delete all "layers" items + m_objects_model->Delete(layers_item); + + // Select object_item, if layers_item doesn't exist for item anymore, but was some of layer items was/were selected + if (some_layers_was_selected) + sel.Add(object_item); + } + else { + wxDataViewItemArray all_obj_layers; + m_objects_model->GetChildren(layers_item, all_obj_layers); + + for (auto item : all_obj_layers) + // update settings for layer + update_settings_item_and_selection(item, sel); + } + } + + // restore selection: + SetSelections(sel); + m_prevent_canvas_selection_update = false; +} + void ObjectList::update_object_menu() { append_menu_items_add_volume(&m_menu_object); @@ -2809,7 +3285,8 @@ void ObjectList::msw_rescale() for (MenuWithSeparators* menu : { &m_menu_object, &m_menu_part, &m_menu_sla_object, - &m_menu_instance }) + &m_menu_instance, + &m_menu_layer }) msw_rescale_menu(menu); Layout(); @@ -2922,5 +3399,13 @@ void ObjectList::set_extruder_for_selected_items(const int extruder) const wxGetApp().plater()->update(); } +ModelObject* ObjectList::object(const int obj_idx) const +{ + if (obj_idx < 0) + return nullptr; + + return (*m_objects)[obj_idx]; +} + } //namespace GUI } //namespace Slic3r \ No newline at end of file diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 166606e2e..ed19edd62 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -33,6 +33,10 @@ typedef std::map< std::string, std::vector< std::pair<std::string, std::string> typedef std::vector<ModelVolume*> ModelVolumePtrs; +typedef double coordf_t; +typedef std::pair<coordf_t, coordf_t> t_layer_height_range; +typedef std::map<t_layer_height_range, DynamicPrintConfig> t_layer_config_ranges; + namespace GUI { wxDECLARE_EVENT(EVT_OBJ_LIST_OBJECT_SELECT, SimpleEvent); @@ -64,9 +68,10 @@ class ObjectList : public wxDataViewCtrl { enum SELECTION_MODE { - smUndef, - smVolume, - smInstance + smUndef = 0, + smVolume = 1, + smInstance = 2, + smLayer = 4 } m_selection_mode {smUndef}; struct dragged_item_data @@ -119,12 +124,17 @@ class ObjectList : public wxDataViewCtrl MenuWithSeparators m_menu_part; MenuWithSeparators m_menu_sla_object; MenuWithSeparators m_menu_instance; - wxMenuItem* m_menu_item_split { nullptr }; - wxMenuItem* m_menu_item_split_part { nullptr }; + MenuWithSeparators m_menu_layer; wxMenuItem* m_menu_item_settings { nullptr }; wxMenuItem* m_menu_item_split_instances { nullptr }; - std::vector<wxBitmap*> m_bmp_vector; + ObjectDataViewModel *m_objects_model{ nullptr }; + DynamicPrintConfig *m_config {nullptr}; + std::vector<ModelObject*> *m_objects{ nullptr }; + + std::vector<wxBitmap*> m_bmp_vector; + + t_layer_config_ranges m_layer_config_ranges_cache; int m_selected_object_id = -1; bool m_prevent_list_events = false; // We use this flag to avoid circular event handling Select() @@ -153,11 +163,11 @@ public: std::map<std::string, wxBitmap> CATEGORY_ICON; - ObjectDataViewModel *m_objects_model{ nullptr }; - DynamicPrintConfig *m_config {nullptr}; - - std::vector<ModelObject*> *m_objects{ nullptr }; + ObjectDataViewModel* GetModel() const { return m_objects_model; } + DynamicPrintConfig* config() const { return m_config; } + std::vector<ModelObject*>* objects() const { return m_objects; } + ModelObject* object(const int obj_idx) const ; void create_objects_ctrl(); void create_popup_menus(); @@ -192,6 +202,9 @@ public: void key_event(wxKeyEvent& event); #endif /* __WXOSX__ */ + void copy(); + void paste(); + void get_settings_choice(const wxString& category_name); void get_freq_settings_choice(const wxString& bundle_name); void update_settings_item(); @@ -199,6 +212,7 @@ public: wxMenu* append_submenu_add_generic(wxMenu* menu, const ModelVolumeType type); void append_menu_items_add_volume(wxMenu* menu); wxMenuItem* append_menu_item_split(wxMenu* menu); + wxMenuItem* append_menu_item_layers_editing(wxMenu* menu); wxMenuItem* append_menu_item_settings(wxMenu* menu); wxMenuItem* append_menu_item_change_type(wxMenu* menu); wxMenuItem* append_menu_item_instance_to_object(wxMenu* menu, wxWindow* parent); @@ -222,10 +236,17 @@ public: void load_generic_subobject(const std::string& type_name, const ModelVolumeType type); void del_object(const int obj_idx); void del_subobject_item(wxDataViewItem& item); - void del_settings_from_config(); + void del_settings_from_config(const wxDataViewItem& parent_item); void del_instances_from_object(const int obj_idx); + void del_layer_from_object(const int obj_idx, const t_layer_height_range& layer_range); + void del_layers_from_object(const int obj_idx); bool del_subobject_from_object(const int obj_idx, const int idx, const int type); void split(); + void layers_editing(); + + wxDataViewItem add_layer_root_item(const wxDataViewItem obj_item); + + DynamicPrintConfig get_default_layer_config(const int obj_idx); bool get_volume_by_item(const wxDataViewItem& item, ModelVolume*& volume); bool is_splittable(); bool selected_instances_of_same_object(); @@ -265,6 +286,14 @@ public: // Remove objects/sub-object from the list void remove(); + void del_layer_range(const t_layer_height_range& range); + void add_layer_range_after_current(const t_layer_height_range& current_range); + void add_layer_item (const t_layer_height_range& range, + const wxDataViewItem layers_item, + const int layer_idx = -1); + bool edit_layer_range(const t_layer_height_range& range, coordf_t layer_height); + bool edit_layer_range(const t_layer_height_range& range, + const t_layer_height_range& new_range); void init_objects(); bool multiple_selection() const ; @@ -286,6 +315,8 @@ public: void last_volume_is_deleted(const int obj_idx); bool has_multi_part_objects(); void update_settings_items(); + void update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections); + void update_object_list_by_printer_technology(); void update_object_menu(); void instances_to_separated_object(const int obj_idx, const std::set<int>& inst_idx); @@ -295,6 +326,8 @@ public: void fix_through_netfabb(); void update_item_error_icon(const int obj_idx, int vol_idx) const ; + void fill_layer_config_ranges_cache(); + void paste_layers_into_list(); void paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& volumes); void paste_objects_into_list(const std::vector<size_t>& object_idxs); diff --git a/src/slic3r/GUI/GUI_ObjectSettings.cpp b/src/slic3r/GUI/GUI_ObjectSettings.cpp index 4107e872a..ab2614895 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.cpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.cpp @@ -68,10 +68,12 @@ void ObjectSettings::update_settings_list() m_settings_list_sizer->Clear(true); auto objects_ctrl = wxGetApp().obj_list(); - auto objects_model = wxGetApp().obj_list()->m_objects_model; - auto config = wxGetApp().obj_list()->m_config; + auto objects_model = wxGetApp().obj_list()->GetModel(); + auto config = wxGetApp().obj_list()->config(); const auto item = objects_ctrl->GetSelection(); + const bool is_layers_range_settings = objects_model->GetItemType(objects_model->GetParent(item)) == itLayer; + if (item && !objects_ctrl->multiple_selection() && config && objects_model->IsSettingsItem(item)) { @@ -119,7 +121,8 @@ void ObjectSettings::update_settings_list() } for (auto& cat : cat_options) { - if (cat.second.size() == 1 && cat.second[0] == "extruder") + if (cat.second.size() == 1 && + (cat.second[0] == "extruder" || is_layers_range_settings && cat.second[0] == "layer_height")) continue; auto optgroup = std::make_shared<ConfigOptionsGroup>(m_og->ctrl_parent(), _(cat.first), config, false, extra_column); @@ -129,14 +132,14 @@ void ObjectSettings::update_settings_list() optgroup->m_on_change = [](const t_config_option_key& opt_id, const boost::any& value) { wxGetApp().obj_list()->changed_object(); }; - const bool is_extriders_cat = cat.first == "Extruders"; + const bool is_extruders_cat = cat.first == "Extruders"; for (auto& opt : cat.second) { - if (opt == "extruder") + if (opt == "extruder" || is_layers_range_settings && opt == "layer_height") continue; Option option = optgroup->get_option(opt); option.opt.width = 12; - if (is_extriders_cat) + if (is_extruders_cat) option.opt.max = wxGetApp().extruders_cnt(); optgroup->append_single_option_line(option); } diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 014932900..0ed23889e 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -320,6 +320,17 @@ Line OptionsGroup::create_single_option_line(const Option& option) const { return retval; } +void OptionsGroup::clear_fields_except_of(const std::vector<std::string> left_fields) +{ + auto it = m_fields.begin(); + while (it != m_fields.end()) { + if (std::find(left_fields.begin(), left_fields.end(), it->first) == left_fields.end()) + it = m_fields.erase(it); + else + it++; + } +} + void OptionsGroup::on_set_focus(const std::string& opt_key) { if (m_set_focus != nullptr) diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index 73b2c5110..422a5c2a2 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -160,6 +160,8 @@ public: m_show_modified_btns = show; } + void clear_fields_except_of(const std::vector<std::string> left_fields); + OptionsGroup( wxWindow* _parent, const wxString& title, bool is_tab_opt = false, column_t extra_clmn = nullptr) : m_parent(_parent), title(title), diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 297cc9f2c..e8973f6e3 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -43,6 +43,7 @@ #include "GUI_App.hpp" #include "GUI_ObjectList.hpp" #include "GUI_ObjectManipulation.hpp" +#include "GUI_ObjectLayers.hpp" #include "GUI_Utils.hpp" #include "wxExtensions.hpp" #include "MainFrame.hpp" @@ -611,6 +612,7 @@ struct Sidebar::priv ObjectList *object_list; ObjectManipulation *object_manipulation; ObjectSettings *object_settings; + ObjectLayers *object_layers; ObjectInfo *object_info; SlicedInfo *sliced_info; @@ -729,6 +731,11 @@ Sidebar::Sidebar(Plater *parent) p->object_settings = new ObjectSettings(p->scrolled); p->object_settings->Hide(); p->sizer_params->Add(p->object_settings->get_sizer(), 0, wxEXPAND | wxTOP, margin_5); + + // Object Layers + p->object_layers = new ObjectLayers(p->scrolled); + p->object_layers->Hide(); + p->sizer_params->Add(p->object_layers->get_sizer(), 0, wxEXPAND | wxTOP, margin_5); // Info boxes p->object_info = new ObjectInfo(p->scrolled); @@ -922,6 +929,7 @@ void Sidebar::msw_rescale() p->object_list->msw_rescale(); p->object_manipulation->msw_rescale(); p->object_settings->msw_rescale(); + p->object_layers->msw_rescale(); p->object_info->msw_rescale(); @@ -943,6 +951,11 @@ ObjectSettings* Sidebar::obj_settings() return p->object_settings; } +ObjectLayers* Sidebar::obj_layers() +{ + return p->object_layers; +} + wxScrolledWindow* Sidebar::scrolled_panel() { return p->scrolled; @@ -2737,8 +2750,14 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) // update plater with new config wxGetApp().plater()->on_config_change(wxGetApp().preset_bundle->full_config()); + /* Settings list can be changed after printer preset changing, so + * update all settings items for all item had it. + * Furthermore, Layers editing is implemented only for FFF printers + * and for SLA presets they should be deleted + */ if (preset_type == Preset::TYPE_PRINTER) - wxGetApp().obj_list()->update_settings_items(); +// wxGetApp().obj_list()->update_settings_items(); + wxGetApp().obj_list()->update_object_list_by_printer_technology(); } void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) @@ -3083,6 +3102,10 @@ bool Plater::priv::complit_init_object_menu() [this]() { return can_split() && wxGetApp().get_mode() > comSimple; }, q); object_menu.AppendSeparator(); + // Layers Editing for object + sidebar->obj_list()->append_menu_item_layers_editing(&object_menu); + object_menu.AppendSeparator(); + // "Add (volumes)" popupmenu will be added later in append_menu_items_add_volume() return true; diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 16c9cbe64..3e50797a6 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -33,6 +33,7 @@ class MainFrame; class ConfigOptionsGroup; class ObjectManipulation; class ObjectSettings; +class ObjectLayers; class ObjectList; class GLCanvas3D; @@ -93,6 +94,7 @@ public: ObjectManipulation* obj_manipul(); ObjectList* obj_list(); ObjectSettings* obj_settings(); + ObjectLayers* obj_layers(); wxScrolledWindow* scrolled_panel(); wxPanel* presets_panel(); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 480b59ba0..402b4248f 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1129,7 +1129,8 @@ void Selection::copy_to_clipboard() dst_object->config = src_object->config; dst_object->sla_support_points = src_object->sla_support_points; dst_object->sla_points_status = src_object->sla_points_status; - dst_object->layer_height_ranges = src_object->layer_height_ranges; +// dst_object->layer_height_ranges = src_object->layer_height_ranges; + dst_object->layer_config_ranges = src_object->layer_config_ranges; // #ys_FIXME_experiment dst_object->layer_height_profile = src_object->layer_height_profile; dst_object->origin_translation = src_object->origin_translation; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 6cd32d397..6ab762c61 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3450,9 +3450,9 @@ void TabSLAMaterial::reload_config() void TabSLAMaterial::update() { if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptFFF) - return; // #ys_FIXME + return; -// #ys_FIXME +// #ys_FIXME. Just a template for this function // m_update_cnt++; // ! something to update // m_update_cnt--; @@ -3550,9 +3550,8 @@ void TabSLAPrint::reload_config() void TabSLAPrint::update() { if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptFFF) - return; // #ys_FIXME + return; -// #ys_FIXME m_update_cnt++; double head_penetration = m_config->opt_float("support_head_penetration"); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 06cf9b7d5..c23caa6c8 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -437,27 +437,69 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent m_type(type), m_extruder(wxEmptyString) { - if (type == itSettings) { + if (type == itSettings) m_name = "Settings to modified"; - } - else if (type == itInstanceRoot) { + else if (type == itInstanceRoot) m_name = _(L("Instances")); -#ifdef __WXGTK__ - m_container = true; -#endif //__WXGTK__ - } - else if (type == itInstance) { + else if (type == itInstance) + { m_idx = parent->GetChildCount(); m_name = wxString::Format(_(L("Instance %d")), m_idx + 1); set_action_icon(); } + else if (type == itLayerRoot) + { + m_bmp = create_scaled_bitmap(nullptr, "layers"); // FIXME: pass window ptr + m_name = _(L("Layers")); + } + +#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" + if (type & (itInstanceRoot | itLayerRoot)) + m_container = true; +#endif //__WXGTK__ +} + +ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent, + const t_layer_height_range& layer_range, + const int idx /*= -1 */, + const wxString& extruder) : + m_parent(parent), + m_type(itLayer), + m_idx(idx), + m_layer_range(layer_range), + m_extruder(extruder) +{ + const int children_cnt = parent->GetChildCount(); + if (idx < 0) + m_idx = children_cnt; + else + { + // update indexes for another Laeyr Nodes + for (int i = m_idx; i < children_cnt; i++) + parent->GetNthChild(i)->SetIdx(i + 1); + } + const std::string label_range = (boost::format(" %.2f-%.2f ") % layer_range.first % layer_range.second).str(); + m_name = _(L("Range")) + label_range + "(" + _(L("mm")) + ")"; + m_bmp = create_scaled_bitmap(nullptr, "layers_white"); // FIXME: pass window ptr + +#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__ + + set_action_icon(); } void ObjectDataViewModelNode::set_action_icon() { - m_action_icon_name = m_type == itObject ? "advanced_plus" : - m_type == itVolume ? "cog" : "set_separate_obj"; + m_action_icon_name = m_type & itObject ? "advanced_plus" : + m_type & (itVolume | itLayer) ? "cog" : /*m_type & itInstance*/ "set_separate_obj"; m_action_icon = create_scaled_bitmap(nullptr, m_action_icon_name); // FIXME: pass window ptr } @@ -523,6 +565,22 @@ void ObjectDataViewModelNode::SetIdx(const int& idx) // 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; @@ -567,10 +625,10 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent wxString extruder_str = extruder == 0 ? _(L("default")) : wxString::Format("%d", extruder); - // because of istance_root is a last item of the object - int insert_position = root->GetChildCount() - 1; - if (insert_position < 0 || root->GetNthChild(insert_position)->m_type != itInstanceRoot) - insert_position = -1; + // 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(); @@ -619,15 +677,30 @@ wxDataViewItem ObjectDataViewModel::AddSettingsChild(const wxDataViewItem &paren return child; } -int get_istances_root_idx(ObjectDataViewModelNode *parent_node) +/* 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) { - // because of istance_root is a last item of the object - const int inst_root_idx = parent_node->GetChildCount()-1; + const int inst_root_id = get_root_idx(parent_node, root_type); - if (inst_root_idx < 0 || parent_node->GetNthChild(inst_root_idx)->GetType() == itInstanceRoot) - return inst_root_idx; + *root_node = inst_root_id < 0 ? + new ObjectDataViewModelNode(parent_node, root_type) : + parent_node->GetNthChild(inst_root_id); - return -1; + 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<unsigned int>(get_root_idx(parent_node, itInstanceRoot))); + return true; + } + + return false; } wxDataViewItem ObjectDataViewModel::AddInstanceChild(const wxDataViewItem &parent_item, size_t num) @@ -635,20 +708,15 @@ wxDataViewItem ObjectDataViewModel::AddInstanceChild(const wxDataViewItem &paren ObjectDataViewModelNode *parent_node = (ObjectDataViewModelNode*)parent_item.GetID(); if (!parent_node) return wxDataViewItem(0); - // Check and create/get instances root node - const int inst_root_id = get_istances_root_idx(parent_node); + // get InstanceRoot node + ObjectDataViewModelNode *inst_root_node { nullptr }; - ObjectDataViewModelNode *inst_root_node = inst_root_id < 0 ? - new ObjectDataViewModelNode(parent_node, itInstanceRoot) : - parent_node->GetNthChild(inst_root_id); + const bool appended = append_root_node(parent_node, &inst_root_node, itInstanceRoot); const wxDataViewItem inst_root_item((void*)inst_root_node); + if (!inst_root_node) return wxDataViewItem(0); - if (inst_root_id < 0) { - parent_node->Append(inst_root_node); - // notify control - ItemAdded(parent_item, inst_root_item); -// if (num == 1) num++; - } + if (appended) + ItemAdded(parent_item, inst_root_item);// notify control // Add instance nodes ObjectDataViewModelNode *instance_node = nullptr; @@ -665,6 +733,63 @@ wxDataViewItem ObjectDataViewModel::AddInstanceChild(const wxDataViewItem &paren return wxDataViewItem((void*)instance_node); } +wxDataViewItem ObjectDataViewModel::AddLayersRoot(const wxDataViewItem &parent_item) +{ + ObjectDataViewModelNode *parent_node = (ObjectDataViewModelNode*)parent_item.GetID(); + if (!parent_node) return wxDataViewItem(0); + + // get LayerRoot node + ObjectDataViewModelNode *layer_root_node{ nullptr }; + const bool appended = append_root_node(parent_node, &layer_root_node, itLayerRoot); + if (!layer_root_node) return wxDataViewItem(0); + + const wxDataViewItem layer_root_item((void*)layer_root_node); + + if (appended) + ItemAdded(parent_item, layer_root_item);// notify control + + return layer_root_item; +} + +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); @@ -679,9 +804,9 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) // 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) + if (node->m_type & (itInstanceRoot|itLayerRoot)) { - for (int i = node->GetChildCount() - 1; i > 0; i--) + for (int i = node->GetChildCount() - 1; i >= (node->m_type & itInstanceRoot ? 1 : 0); i--) Delete(wxDataViewItem(node->GetNthChild(i))); return parent; } @@ -690,7 +815,7 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) auto idx = node->GetIdx(); - if (node->m_type == itVolume) { + if (node->m_type & (itVolume|itLayer)) { node_parent->m_volumes_cnt--; DeleteSettings(item); } @@ -726,6 +851,22 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) 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 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; @@ -735,7 +876,7 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) } // if there is last volume item after deleting, delete this last volume too - if (node_parent->GetChildCount() <= 3) + if (node_parent->GetChildCount() <= 3) // 3??? #ys_FIXME { int vol_cnt = 0; int vol_idx = 0; @@ -817,7 +958,7 @@ wxDataViewItem ObjectDataViewModel::DeleteLastInstance(const wxDataViewItem &par ObjectDataViewModelNode *parent_node = (ObjectDataViewModelNode*)parent_item.GetID(); if (!parent_node) return ret_item; - const int inst_root_id = get_istances_root_idx(parent_node); + const int inst_root_id = get_root_idx(parent_node, itInstanceRoot); if (inst_root_id < 0) return ret_item; wxDataViewItemArray items; @@ -974,28 +1115,67 @@ wxDataViewItem ObjectDataViewModel::GetItemByVolumeId(int obj_idx, int volume_id return wxDataViewItem(0); } -wxDataViewItem ObjectDataViewModel::GetItemByInstanceId(int obj_idx, int inst_idx) +wxDataViewItem ObjectDataViewModel::GetItemById(const int obj_idx, const int sub_obj_idx, const ItemType parent_type) { if (obj_idx >= m_objects.size() || obj_idx < 0) { printf("Error! Out of objects range.\n"); return wxDataViewItem(0); } - auto instances_item = GetInstanceRootItem(wxDataViewItem(m_objects[obj_idx])); - if (!instances_item) + auto item = GetItemByType(wxDataViewItem(m_objects[obj_idx]), parent_type); + if (!item) return wxDataViewItem(0); - auto parent = (ObjectDataViewModelNode*)instances_item.GetID();; + auto parent = (ObjectDataViewModelNode*)item.GetID(); for (size_t i = 0; i < parent->GetChildCount(); i++) - if (parent->GetNthChild(i)->m_idx == inst_idx) + 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 (obj_idx >= m_objects.size() || obj_idx < 0) { + 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 { - wxASSERT(item.IsOk()); + if(!item.IsOk()) + return -1; ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); auto it = find(m_objects.begin(), m_objects.end(), node); @@ -1030,13 +1210,28 @@ 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(); +} + 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))) + if (!node || node->GetIdx() <-1 || node->GetIdx() == -1 && !(node->GetType() & (itObject | itSettings | itInstanceRoot | itLayerRoot/* | itLayer*/))) return; idx = node->GetIdx(); @@ -1044,9 +1239,10 @@ void ObjectDataViewModel::GetItemInfo(const wxDataViewItem& item, ItemType& type ObjectDataViewModelNode *parent_node = node->GetParent(); if (!parent_node) return; - if (type == itInstance) - parent_node = node->GetParent()->GetParent(); - if (!parent_node || parent_node->m_type != itObject) { type = itUndef; 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()) @@ -1214,10 +1410,7 @@ wxDataViewItem ObjectDataViewModel::GetTopParent(const wxDataViewItem &item) con ObjectDataViewModelNode *parent_node = node->GetParent(); while (parent_node->m_type != itObject) - { - node = parent_node; - parent_node = node->GetParent(); - } + parent_node = parent_node->GetParent(); return wxDataViewItem((void*)parent_node); } @@ -1318,6 +1511,11 @@ wxDataViewItem ObjectDataViewModel::GetInstanceRootItem(const wxDataViewItem &it 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()) @@ -2573,7 +2771,7 @@ ModeSizer::ModeSizer(wxWindow *parent, int hgap/* = 10*/) : m_mode_btns.push_back(new ModeButton(parent, wxID_ANY, button.second, button.first));; #endif // __WXOSX__ - m_mode_btns.back()->Bind(wxEVT_BUTTON, std::bind(modebtnfn, std::placeholders::_1, m_mode_btns.size() - 1)); + m_mode_btns.back()->Bind(wxEVT_BUTTON, std::bind(modebtnfn, std::placeholders::_1, int(m_mode_btns.size() - 1))); Add(m_mode_btns.back()); } } diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 78fb7be55..1ed5770bb 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -20,6 +20,9 @@ namespace Slic3r { enum class ModelVolumeType : int; }; +typedef double coordf_t; +typedef std::pair<coordf_t, coordf_t> t_layer_height_range; + #ifdef __WXMSW__ void msw_rescale_menu(wxMenu* menu); #else /* __WXMSW__ */ @@ -159,12 +162,14 @@ DECLARE_VARIANT_OBJECT(DataViewBitmapText) // ---------------------------------------------------------------------------- enum ItemType { - itUndef = 0, - itObject = 1, - itVolume = 2, - itInstanceRoot = 4, - itInstance = 8, - itSettings = 16 + itUndef = 0, + itObject = 1, + itVolume = 2, + itInstanceRoot = 4, + itInstance = 8, + itSettings = 16, + itLayerRoot = 32, + itLayer = 64, }; class ObjectDataViewModelNode; @@ -177,6 +182,7 @@ class ObjectDataViewModelNode 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; @@ -229,6 +235,11 @@ public: set_action_icon(); } + 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() @@ -318,6 +329,7 @@ public: 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; } // use this function only for childrens void AssignAllVal(ObjectDataViewModelNode& from_node) @@ -348,7 +360,7 @@ public: } // Set action icons for node - void set_action_icon(); + void set_action_icon(); void update_settings_digest_bitmaps(); bool update_settings_digest(const std::vector<std::string>& categories); @@ -388,6 +400,11 @@ public: const bool create_frst_child = true); wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item); wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, size_t num); + 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(); @@ -395,13 +412,18 @@ public: 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(); } @@ -450,6 +472,7 @@ public: 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<std::string>& categories); @@ -465,6 +488,7 @@ public: 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; }; // ----------------------------------------------------------------------------