mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-11-02 20:51:23 -07:00 
			
		
		
		
	Fixed conflicts after merge with master
This commit is contained in:
		
						commit
						9ba5568539
					
				
					 53 changed files with 2444 additions and 897 deletions
				
			
		| 
						 | 
				
			
			@ -126,7 +126,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out)
 | 
			
		|||
        Polygons surfaces_polygons = to_polygons(surfaces);
 | 
			
		||||
        Polygons collapsed = diff(
 | 
			
		||||
            surfaces_polygons,
 | 
			
		||||
            offset2(surfaces_polygons, -distance_between_surfaces/2, +distance_between_surfaces/2),
 | 
			
		||||
            offset2(surfaces_polygons, (float)-distance_between_surfaces/2, (float)+distance_between_surfaces/2),
 | 
			
		||||
            true);
 | 
			
		||||
        Polygons to_subtract;
 | 
			
		||||
        to_subtract.reserve(collapsed.size() + number_polygons(surfaces));
 | 
			
		||||
| 
						 | 
				
			
			@ -137,7 +137,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out)
 | 
			
		|||
        surfaces_append(
 | 
			
		||||
            surfaces,
 | 
			
		||||
            intersection_ex(
 | 
			
		||||
                offset(collapsed, distance_between_surfaces),
 | 
			
		||||
                offset(collapsed, (float)distance_between_surfaces),
 | 
			
		||||
                to_subtract,
 | 
			
		||||
                true),
 | 
			
		||||
            stInternalSolid);
 | 
			
		||||
| 
						 | 
				
			
			@ -219,14 +219,14 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out)
 | 
			
		|||
        f->z = layerm.layer()->print_z;
 | 
			
		||||
        f->angle = float(Geometry::deg2rad(layerm.region()->config().fill_angle.value));
 | 
			
		||||
        // Maximum length of the perimeter segment linking two infill lines.
 | 
			
		||||
        f->link_max_length = scale_(link_max_length);
 | 
			
		||||
        f->link_max_length = (coord_t)scale_(link_max_length);
 | 
			
		||||
        // Used by the concentric infill pattern to clip the loops to create extrusion paths.
 | 
			
		||||
        f->loop_clipping = scale_(flow.nozzle_diameter) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER;
 | 
			
		||||
        f->loop_clipping = coord_t(scale_(flow.nozzle_diameter) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER);
 | 
			
		||||
//        f->layer_height = h;
 | 
			
		||||
 | 
			
		||||
        // apply half spacing using this flow's own spacing and generate infill
 | 
			
		||||
        FillParams params;
 | 
			
		||||
        params.density = 0.01 * density;
 | 
			
		||||
        params.density = float(0.01 * density);
 | 
			
		||||
//        params.dont_adjust = true;
 | 
			
		||||
        params.dont_adjust = false;
 | 
			
		||||
        Polylines polylines = f->fill_surface(&surface, params);
 | 
			
		||||
| 
						 | 
				
			
			@ -240,7 +240,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out)
 | 
			
		|||
            // so we can safely ignore the slight variation that might have
 | 
			
		||||
            // been applied to $f->flow_spacing
 | 
			
		||||
        } else {
 | 
			
		||||
            flow = Flow::new_from_spacing(f->spacing, flow.nozzle_diameter, h, is_bridge || f->use_bridge_flow());
 | 
			
		||||
            flow = Flow::new_from_spacing(f->spacing, flow.nozzle_diameter, (float)h, is_bridge || f->use_bridge_flow());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Save into layer.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,10 +11,16 @@
 | 
			
		|||
#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>
 | 
			
		||||
 | 
			
		||||
#include <boost/property_tree/ptree.hpp>
 | 
			
		||||
#include <boost/property_tree/xml_parser.hpp>
 | 
			
		||||
#include <boost/foreach.hpp>
 | 
			
		||||
namespace pt = boost::property_tree;
 | 
			
		||||
 | 
			
		||||
#include <expat.h>
 | 
			
		||||
#include <Eigen/Dense>
 | 
			
		||||
#include "miniz_extension.hpp"
 | 
			
		||||
| 
						 | 
				
			
			@ -33,6 +39,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/Prusa_Slicer_layer_config_ranges.xml";
 | 
			
		||||
const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt";
 | 
			
		||||
 | 
			
		||||
const char* MODEL_TAG = "model";
 | 
			
		||||
| 
						 | 
				
			
			@ -331,6 +338,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 +355,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 +374,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 +486,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 +557,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 +608,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 +790,66 @@ 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;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            std::istringstream iss(buffer); // wrap returned xml to istringstream
 | 
			
		||||
            pt::ptree objects_tree;
 | 
			
		||||
            pt::read_xml(iss, objects_tree);
 | 
			
		||||
 | 
			
		||||
            for (const auto& object : objects_tree.get_child("objects"))
 | 
			
		||||
            {
 | 
			
		||||
                pt::ptree object_tree = object.second;
 | 
			
		||||
                int obj_idx = object_tree.get<int>("<xmlattr>.id", -1);
 | 
			
		||||
                if (obj_idx <= 0) {
 | 
			
		||||
                    add_error("Found invalid object id");
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                IdToLayerConfigRangesMap::iterator object_item = m_layer_config_ranges.find(obj_idx);
 | 
			
		||||
                if (object_item != m_layer_config_ranges.end()) {
 | 
			
		||||
                    add_error("Found duplicated layer config range");
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                t_layer_config_ranges config_ranges;
 | 
			
		||||
 | 
			
		||||
                for (const auto& range : object_tree)
 | 
			
		||||
                {
 | 
			
		||||
                    if (range.first != "range")
 | 
			
		||||
                        continue;
 | 
			
		||||
                    pt::ptree range_tree = range.second;
 | 
			
		||||
                    double min_z = range_tree.get<double>("<xmlattr>.min_z");
 | 
			
		||||
                    double max_z = range_tree.get<double>("<xmlattr>.max_z");
 | 
			
		||||
 | 
			
		||||
                    // get Z range information
 | 
			
		||||
                    DynamicPrintConfig& config = config_ranges[{ min_z, max_z }];
 | 
			
		||||
 | 
			
		||||
                    for (const auto& option : range_tree)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (option.first != "option")
 | 
			
		||||
                            continue;
 | 
			
		||||
                        std::string opt_key = option.second.get<std::string>("<xmlattr>.opt_key");
 | 
			
		||||
                        std::string value = option.second.data();
 | 
			
		||||
 | 
			
		||||
                        config.set_deserialize(opt_key, value);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (!config_ranges.empty())
 | 
			
		||||
                    m_layer_config_ranges.insert(IdToLayerConfigRangesMap::value_type(obj_idx, 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 +1705,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 +1766,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!
 | 
			
		||||
| 
						 | 
				
			
			@ -1895,7 +1987,7 @@ namespace Slic3r {
 | 
			
		|||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            vertices_count += its.vertices.size();
 | 
			
		||||
            vertices_count += (int)its.vertices.size();
 | 
			
		||||
 | 
			
		||||
            const Transform3d& matrix = volume->get_matrix();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1925,7 +2017,7 @@ namespace Slic3r {
 | 
			
		|||
 | 
			
		||||
            // updates triangle offsets
 | 
			
		||||
            volume_it->second.first_triangle_id = triangles_count;
 | 
			
		||||
            triangles_count += its.indices.size();
 | 
			
		||||
            triangles_count += (int)its.indices.size();
 | 
			
		||||
            volume_it->second.last_triangle_id = triangles_count - 1;
 | 
			
		||||
 | 
			
		||||
            for (size_t i = 0; i < its.indices.size(); ++ i)
 | 
			
		||||
| 
						 | 
				
			
			@ -2013,6 +2105,70 @@ namespace Slic3r {
 | 
			
		|||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool _3MF_Exporter::_add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model)
 | 
			
		||||
    {
 | 
			
		||||
        std::string out = "";
 | 
			
		||||
        pt::ptree tree;
 | 
			
		||||
 | 
			
		||||
        unsigned int object_cnt = 0;
 | 
			
		||||
        for (const ModelObject* object : model.objects)
 | 
			
		||||
        {
 | 
			
		||||
            object_cnt++;
 | 
			
		||||
            const t_layer_config_ranges& ranges = object->layer_config_ranges;
 | 
			
		||||
            if (!ranges.empty())
 | 
			
		||||
            {
 | 
			
		||||
                pt::ptree& obj_tree = tree.add("objects.object","");
 | 
			
		||||
 | 
			
		||||
                obj_tree.put("<xmlattr>.id", object_cnt);
 | 
			
		||||
 | 
			
		||||
                // Store the layer config ranges.
 | 
			
		||||
                for (const auto& range : ranges)
 | 
			
		||||
                {
 | 
			
		||||
                    pt::ptree& range_tree = obj_tree.add("range", "");
 | 
			
		||||
 | 
			
		||||
                    // store minX and maxZ
 | 
			
		||||
                    range_tree.put("<xmlattr>.min_z", range.first.first);
 | 
			
		||||
                    range_tree.put("<xmlattr>.max_z", range.first.second);
 | 
			
		||||
 | 
			
		||||
                    // store range configuration
 | 
			
		||||
                    const DynamicPrintConfig& config = range.second;
 | 
			
		||||
                    for (const std::string& opt_key : config.keys())
 | 
			
		||||
                    {
 | 
			
		||||
                        pt::ptree& opt_tree = range_tree.add("option", config.serialize(opt_key));
 | 
			
		||||
                        opt_tree.put("<xmlattr>.opt_key", opt_key);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!tree.empty())
 | 
			
		||||
        {
 | 
			
		||||
            std::ostringstream oss;
 | 
			
		||||
            boost::property_tree::write_xml(oss, tree);
 | 
			
		||||
            out = oss.str();
 | 
			
		||||
 | 
			
		||||
            // Post processing("beautification") of the output string for a better preview
 | 
			
		||||
            boost::replace_all(out, "><object",      ">\n <object");
 | 
			
		||||
            boost::replace_all(out, "><range",       ">\n  <range");
 | 
			
		||||
            boost::replace_all(out, "><option",      ">\n   <option");
 | 
			
		||||
            boost::replace_all(out, "></range>",     ">\n  </range>");
 | 
			
		||||
            boost::replace_all(out, "></object>",    ">\n </object>");
 | 
			
		||||
            // OR just 
 | 
			
		||||
            boost::replace_all(out, "><",            ">\n<"); 
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!out.empty())
 | 
			
		||||
        {
 | 
			
		||||
            if (!mz_zip_writer_add_mem(&archive, LAYER_CONFIG_RANGES_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION))
 | 
			
		||||
            {
 | 
			
		||||
                add_error("Unable to add layer heights profile file to archive");
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool _3MF_Exporter::_add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model)
 | 
			
		||||
    {
 | 
			
		||||
        std::string out = "";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -189,7 +192,7 @@ struct AMFParserContext
 | 
			
		|||
    };
 | 
			
		||||
 | 
			
		||||
    // Version of the amf file
 | 
			
		||||
    unsigned int m_version;
 | 
			
		||||
    unsigned int             m_version;
 | 
			
		||||
    // Current Expat XML parser instance.
 | 
			
		||||
    XML_Parser               m_parser;
 | 
			
		||||
    // Model to receive objects extracted from an AMF file.
 | 
			
		||||
| 
						 | 
				
			
			@ -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) {
 | 
			
		||||
| 
						 | 
				
			
			@ -598,7 +616,7 @@ void AMFParserContext::endElement(const char * /* name */)
 | 
			
		|||
                    if (end != nullptr)
 | 
			
		||||
	                    *end = 0;
 | 
			
		||||
 | 
			
		||||
                    point(coord_idx) = atof(p);
 | 
			
		||||
                    point(coord_idx) = float(atof(p));
 | 
			
		||||
                    if (++coord_idx == 5) {
 | 
			
		||||
                        m_object->sla_support_points.push_back(sla::SupportPoint(point));
 | 
			
		||||
                        coord_idx = 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_range") == 0) {
 | 
			
		||||
                // Parse object's layer_height_range, 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_range\">";
 | 
			
		||||
                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.
 | 
			
		||||
| 
						 | 
				
			
			@ -941,7 +994,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config)
 | 
			
		|||
                stream << "           </coordinates>\n";
 | 
			
		||||
                stream << "         </vertex>\n";
 | 
			
		||||
            }
 | 
			
		||||
            num_vertices += its.vertices.size();
 | 
			
		||||
            num_vertices += (int)its.vertices.size();
 | 
			
		||||
        }
 | 
			
		||||
        stream << "      </vertices>\n";
 | 
			
		||||
        for (size_t i_volume = 0; i_volume < object->volumes.size(); ++i_volume) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1405,7 +1405,9 @@ void GCode::process_layer(
 | 
			
		|||
        m_colorprint_heights.erase(m_colorprint_heights.begin());
 | 
			
		||||
        colorprint_change = true;
 | 
			
		||||
    }
 | 
			
		||||
    if (colorprint_change && print.extruders().size()==1)
 | 
			
		||||
 | 
			
		||||
    // we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count
 | 
			
		||||
    if (colorprint_change && print./*extruders()*/config().nozzle_diameter.size()==1)
 | 
			
		||||
        gcode += "M600\n";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -629,7 +629,7 @@ ModelObject& ModelObject::assign_copy(const ModelObject &rhs)
 | 
			
		|||
    this->config                      = rhs.config;
 | 
			
		||||
    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;
 | 
			
		||||
| 
						 | 
				
			
			@ -665,7 +665,7 @@ ModelObject& ModelObject::assign_copy(ModelObject &&rhs)
 | 
			
		|||
    this->config                      = std::move(rhs.config);
 | 
			
		||||
    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);
 | 
			
		||||
| 
						 | 
				
			
			@ -1845,7 +1845,7 @@ bool model_volume_list_changed(const ModelObject &model_object_old, const ModelO
 | 
			
		|||
        if (!mv_old.get_matrix().isApprox(mv_new.get_matrix()))
 | 
			
		||||
            return true;
 | 
			
		||||
 | 
			
		||||
        ++i_old;
 | 
			
		||||
        ++ i_old;
 | 
			
		||||
        ++ i_new;
 | 
			
		||||
    }
 | 
			
		||||
    for (; i_old < model_object_old.volumes.size(); ++ i_old) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -179,8 +179,8 @@ public:
 | 
			
		|||
    ModelVolumePtrs         volumes;
 | 
			
		||||
    // Configuration parameters specific to a single ModelObject, overriding the global Slic3r settings.
 | 
			
		||||
    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 + optional parameter overrides.
 | 
			
		||||
    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;
 | 
			
		||||
| 
						 | 
				
			
			@ -597,8 +597,8 @@ public:
 | 
			
		|||
    Model() {}
 | 
			
		||||
    ~Model() { this->clear_objects(); this->clear_materials(); }
 | 
			
		||||
 | 
			
		||||
    /* To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" */
 | 
			
		||||
    /* (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics). */
 | 
			
		||||
    // To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision"
 | 
			
		||||
    // (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics).
 | 
			
		||||
    Model(const Model &rhs) : ModelBase(-1) { this->assign_copy(rhs); }
 | 
			
		||||
    explicit Model(Model &&rhs) : ModelBase(-1) { this->assign_copy(std::move(rhs)); }
 | 
			
		||||
    Model& operator=(const Model &rhs) { this->assign_copy(rhs); return *this; }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,6 +12,8 @@
 | 
			
		|||
 | 
			
		||||
//#include "PrintExport.hpp"
 | 
			
		||||
 | 
			
		||||
#include <float.h>
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <limits>
 | 
			
		||||
#include <unordered_set>
 | 
			
		||||
| 
						 | 
				
			
			@ -41,36 +43,6 @@ void Print::clear()
 | 
			
		|||
    m_model.clear_objects();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Only used by the Perl test cases.
 | 
			
		||||
void Print::reload_object(size_t /* idx */)
 | 
			
		||||
{
 | 
			
		||||
	ModelObjectPtrs model_objects;
 | 
			
		||||
	{
 | 
			
		||||
		tbb::mutex::scoped_lock lock(this->state_mutex());
 | 
			
		||||
        // The following call should stop background processing if it is running.
 | 
			
		||||
        this->invalidate_all_steps();
 | 
			
		||||
		/* TODO: this method should check whether the per-object config and per-material configs
 | 
			
		||||
			have changed in such a way that regions need to be rearranged or we can just apply
 | 
			
		||||
			the diff and invalidate something.  Same logic as apply()
 | 
			
		||||
			For now we just re-add all objects since we haven't implemented this incremental logic yet.
 | 
			
		||||
			This should also check whether object volumes (parts) have changed. */
 | 
			
		||||
		// collect all current model objects
 | 
			
		||||
		model_objects.reserve(m_objects.size());
 | 
			
		||||
		for (PrintObject *object : m_objects)
 | 
			
		||||
			model_objects.push_back(object->model_object());
 | 
			
		||||
		// remove our print objects
 | 
			
		||||
		for (PrintObject *object : m_objects)
 | 
			
		||||
			delete object;
 | 
			
		||||
		m_objects.clear();
 | 
			
		||||
		for (PrintRegion *region : m_regions)
 | 
			
		||||
			delete region;
 | 
			
		||||
		m_regions.clear();
 | 
			
		||||
	}
 | 
			
		||||
	// re-add model objects
 | 
			
		||||
    for (ModelObject *mo : model_objects)
 | 
			
		||||
        this->add_model_object(mo);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PrintRegion* Print::add_region()
 | 
			
		||||
{
 | 
			
		||||
    m_regions.emplace_back(new PrintRegion(this));
 | 
			
		||||
| 
						 | 
				
			
			@ -335,7 +307,7 @@ unsigned int Print::num_object_instances() const
 | 
			
		|||
{
 | 
			
		||||
	unsigned int instances = 0;
 | 
			
		||||
    for (const PrintObject *print_object : m_objects)
 | 
			
		||||
        instances += print_object->copies().size();
 | 
			
		||||
        instances += (unsigned int)print_object->copies().size();
 | 
			
		||||
    return instances;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -358,198 +330,6 @@ double Print::max_allowed_layer_height() const
 | 
			
		|||
    return nozzle_diameter_max;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Caller is responsible for supplying models whose objects don't collide
 | 
			
		||||
// and have explicit instance positions.
 | 
			
		||||
void Print::add_model_object(ModelObject* model_object, int idx)
 | 
			
		||||
{
 | 
			
		||||
	tbb::mutex::scoped_lock lock(this->state_mutex());
 | 
			
		||||
    // Add a copy of this ModelObject to this Print.
 | 
			
		||||
    m_model.objects.emplace_back(ModelObject::new_copy(*model_object));
 | 
			
		||||
    m_model.objects.back()->set_model(&m_model);
 | 
			
		||||
    // Initialize a new print object and store it at the given position.
 | 
			
		||||
    PrintObject *object = new PrintObject(this, model_object, true);
 | 
			
		||||
    if (idx != -1) {
 | 
			
		||||
        delete m_objects[idx];
 | 
			
		||||
        m_objects[idx] = object;
 | 
			
		||||
    } else
 | 
			
		||||
        m_objects.emplace_back(object);
 | 
			
		||||
    // Invalidate all print steps.
 | 
			
		||||
    this->invalidate_all_steps();
 | 
			
		||||
 | 
			
		||||
    // Set the transformation matrix without translation from the first instance.
 | 
			
		||||
    if (! model_object->instances.empty()) {
 | 
			
		||||
        // Trafo and bounding box, both in world coordinate system.
 | 
			
		||||
        Transform3d   trafo = model_object->instances.front()->get_matrix();
 | 
			
		||||
        BoundingBoxf3 bbox  = model_object->instance_bounding_box(0);
 | 
			
		||||
        // Now shift the object up to align it with the print bed.
 | 
			
		||||
        trafo.data()[14] -= bbox.min(2);
 | 
			
		||||
		// and reset the XY translation.
 | 
			
		||||
		trafo.data()[12] = 0;
 | 
			
		||||
		trafo.data()[13] = 0;
 | 
			
		||||
		object->set_trafo(trafo);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    size_t volume_id = 0;
 | 
			
		||||
    for (const ModelVolume *volume : model_object->volumes) {
 | 
			
		||||
        if (! volume->is_model_part() && ! volume->is_modifier())
 | 
			
		||||
            continue;
 | 
			
		||||
        // Get the config applied to this volume.
 | 
			
		||||
        PrintRegionConfig config = PrintObject::region_config_from_model_volume(m_default_region_config, *volume, 99999);
 | 
			
		||||
        // Find an existing print region with the same config.
 | 
			
		||||
        size_t region_id = size_t(-1);
 | 
			
		||||
        for (size_t i = 0; i < m_regions.size(); ++ i)
 | 
			
		||||
            if (config.equals(m_regions[i]->config())) {
 | 
			
		||||
                region_id = i;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        // If no region exists with the same config, create a new one.
 | 
			
		||||
        if (region_id == size_t(-1)) {
 | 
			
		||||
            region_id = m_regions.size();
 | 
			
		||||
            this->add_region(config);
 | 
			
		||||
        }
 | 
			
		||||
        // Assign volume to a region.
 | 
			
		||||
        object->add_region_volume(region_id, volume_id);
 | 
			
		||||
        ++ volume_id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Apply config to print object.
 | 
			
		||||
    object->config_apply(this->default_object_config());
 | 
			
		||||
    {
 | 
			
		||||
        //normalize_and_apply_config(object->config(), model_object->config);
 | 
			
		||||
        DynamicPrintConfig src_normalized(model_object->config);
 | 
			
		||||
        src_normalized.normalize();
 | 
			
		||||
        object->config_apply(src_normalized, true);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// This function is only called through the Perl-C++ binding from the unit tests, should be
 | 
			
		||||
// removed when unit tests are rewritten to C++.
 | 
			
		||||
bool Print::apply_config_perl_tests_only(DynamicPrintConfig config)
 | 
			
		||||
{
 | 
			
		||||
	tbb::mutex::scoped_lock lock(this->state_mutex());
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // Perl unit tests were failing in case the preset was not normalized (e.g. https://github.com/prusa3d/PrusaSlicer/issues/2288 was caused
 | 
			
		||||
    // by too short max_layer_height vector. Calling the necessary function Preset::normalize(...) is not currently possible because there is no
 | 
			
		||||
    // access to preset. This should be solved when the unit tests are rewritten to C++. For now we just copy-pasted code from Preset.cpp
 | 
			
		||||
    // to make sure the unit tests pass (functions set_num_extruders and nozzle_options()).
 | 
			
		||||
    auto *nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(config.option("nozzle_diameter", true));
 | 
			
		||||
    assert(nozzle_diameter != nullptr);
 | 
			
		||||
    const auto &defaults = FullPrintConfig::defaults();
 | 
			
		||||
    for (const std::string &key : { "nozzle_diameter", "min_layer_height", "max_layer_height", "extruder_offset",
 | 
			
		||||
                                    "retract_length", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_speed", "deretract_speed",
 | 
			
		||||
                                    "retract_before_wipe", "retract_restart_extra", "retract_before_travel", "wipe",
 | 
			
		||||
                                    "retract_layer_change", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour" })
 | 
			
		||||
    {
 | 
			
		||||
        auto *opt = config.option(key, true);
 | 
			
		||||
        assert(opt != nullptr);
 | 
			
		||||
        assert(opt->is_vector());
 | 
			
		||||
        unsigned int num_extruders = (unsigned int)nozzle_diameter->values.size();
 | 
			
		||||
        static_cast<ConfigOptionVectorBase*>(opt)->resize(num_extruders, defaults.option(key));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // we get a copy of the config object so we can modify it safely
 | 
			
		||||
    config.normalize();
 | 
			
		||||
    
 | 
			
		||||
    // apply variables to placeholder parser
 | 
			
		||||
	this->placeholder_parser().apply_config(config);
 | 
			
		||||
    
 | 
			
		||||
    // handle changes to print config
 | 
			
		||||
    t_config_option_keys print_diff = m_config.diff(config);
 | 
			
		||||
    m_config.apply_only(config, print_diff, true);
 | 
			
		||||
    bool invalidated = this->invalidate_state_by_config_options(print_diff);
 | 
			
		||||
    
 | 
			
		||||
    // handle changes to object config defaults
 | 
			
		||||
    m_default_object_config.apply(config, true);
 | 
			
		||||
    for (PrintObject *object : m_objects) {
 | 
			
		||||
        // we don't assume that config contains a full ObjectConfig,
 | 
			
		||||
        // so we base it on the current print-wise default
 | 
			
		||||
        PrintObjectConfig new_config = this->default_object_config();
 | 
			
		||||
        // we override the new config with object-specific options
 | 
			
		||||
        normalize_and_apply_config(new_config, object->model_object()->config);
 | 
			
		||||
        // check whether the new config is different from the current one
 | 
			
		||||
        t_config_option_keys diff = object->config().diff(new_config);
 | 
			
		||||
        object->config_apply_only(new_config, diff, true);
 | 
			
		||||
        invalidated |= object->invalidate_state_by_config_options(diff);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // handle changes to regions config defaults
 | 
			
		||||
    m_default_region_config.apply(config, true);
 | 
			
		||||
    
 | 
			
		||||
    // All regions now have distinct settings.
 | 
			
		||||
    // Check whether applying the new region config defaults we'd get different regions.
 | 
			
		||||
    bool rearrange_regions = false;
 | 
			
		||||
    {
 | 
			
		||||
        // Collect the already visited region configs into other_region_configs,
 | 
			
		||||
        // so one may check for duplicates.
 | 
			
		||||
        std::vector<PrintRegionConfig> other_region_configs;
 | 
			
		||||
        for (size_t region_id = 0; region_id < m_regions.size(); ++ region_id) {
 | 
			
		||||
            PrintRegion       ®ion = *m_regions[region_id];
 | 
			
		||||
            PrintRegionConfig  this_region_config;
 | 
			
		||||
            bool               this_region_config_set = false;
 | 
			
		||||
            for (PrintObject *object : m_objects) {
 | 
			
		||||
                if (region_id < object->region_volumes.size()) {
 | 
			
		||||
                    for (int volume_id : object->region_volumes[region_id]) {
 | 
			
		||||
                        const ModelVolume &volume = *object->model_object()->volumes[volume_id];
 | 
			
		||||
                        if (this_region_config_set) {
 | 
			
		||||
                            // If the new config for this volume differs from the other
 | 
			
		||||
                            // volume configs currently associated to this region, it means
 | 
			
		||||
                            // the region subdivision does not make sense anymore.
 | 
			
		||||
                            if (! this_region_config.equals(PrintObject::region_config_from_model_volume(m_default_region_config, volume, 99999))) {
 | 
			
		||||
                                rearrange_regions = true;
 | 
			
		||||
                                goto exit_for_rearrange_regions;
 | 
			
		||||
                            }
 | 
			
		||||
                        } else {
 | 
			
		||||
                            this_region_config = PrintObject::region_config_from_model_volume(m_default_region_config, volume, 99999);
 | 
			
		||||
                            this_region_config_set = true;
 | 
			
		||||
                        }
 | 
			
		||||
                        for (const PrintRegionConfig &cfg : other_region_configs) {
 | 
			
		||||
                            // If the new config for this volume equals any of the other
 | 
			
		||||
                            // volume configs that are not currently associated to this
 | 
			
		||||
                            // region, it means the region subdivision does not make
 | 
			
		||||
                            // sense anymore.
 | 
			
		||||
                            if (cfg.equals(this_region_config)) {
 | 
			
		||||
                                rearrange_regions = true;
 | 
			
		||||
                                goto exit_for_rearrange_regions;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (this_region_config_set) {
 | 
			
		||||
                t_config_option_keys diff = region.config().diff(this_region_config);
 | 
			
		||||
                if (! diff.empty()) {
 | 
			
		||||
                    region.config_apply_only(this_region_config, diff, false);
 | 
			
		||||
                    for (PrintObject *object : m_objects)
 | 
			
		||||
                        if (region_id < object->region_volumes.size() && ! object->region_volumes[region_id].empty())
 | 
			
		||||
                            invalidated |= object->invalidate_state_by_config_options(diff);
 | 
			
		||||
                }
 | 
			
		||||
                other_region_configs.emplace_back(std::move(this_region_config));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
exit_for_rearrange_regions:
 | 
			
		||||
    
 | 
			
		||||
    if (rearrange_regions) {
 | 
			
		||||
        // The current subdivision of regions does not make sense anymore.
 | 
			
		||||
        // We need to remove all objects and re-add them.
 | 
			
		||||
        ModelObjectPtrs model_objects;
 | 
			
		||||
        model_objects.reserve(m_objects.size());
 | 
			
		||||
        for (PrintObject *object : m_objects)
 | 
			
		||||
            model_objects.push_back(object->model_object());
 | 
			
		||||
        this->clear();
 | 
			
		||||
        for (ModelObject *mo : model_objects)
 | 
			
		||||
            this->add_model_object(mo);
 | 
			
		||||
        invalidated = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (PrintObject *object : m_objects)
 | 
			
		||||
        object->update_slicing_parameters();
 | 
			
		||||
 | 
			
		||||
    return invalidated;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Add or remove support modifier ModelVolumes from model_object_dst to match the ModelVolumes of model_object_new
 | 
			
		||||
// in the exact order and with the same IDs.
 | 
			
		||||
// It is expected, that the model_object_dst already contains the non-support volumes of model_object_new in the correct order.
 | 
			
		||||
| 
						 | 
				
			
			@ -620,6 +400,20 @@ static inline void model_volume_list_copy_configs(ModelObject &model_object_dst,
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline void layer_height_ranges_copy_configs(t_layer_config_ranges &lr_dst, const t_layer_config_ranges &lr_src)
 | 
			
		||||
{
 | 
			
		||||
    assert(lr_dst.size() == lr_src.size());
 | 
			
		||||
    auto it_src = lr_src.cbegin();
 | 
			
		||||
    for (auto &kvp_dst : lr_dst) {
 | 
			
		||||
        const auto &kvp_src = *it_src ++;
 | 
			
		||||
        assert(std::abs(kvp_dst.first.first  - kvp_src.first.first ) <= EPSILON);
 | 
			
		||||
        assert(std::abs(kvp_dst.first.second - kvp_src.first.second) <= EPSILON);
 | 
			
		||||
        // Layer heights are allowed do differ in case the layer height table is being overriden by the smooth profile.
 | 
			
		||||
        // assert(std::abs(kvp_dst.second.option("layer_height")->getFloat() - kvp_src.second.option("layer_height")->getFloat()) <= EPSILON);
 | 
			
		||||
        kvp_dst.second = kvp_src.second;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline bool transform3d_lower(const Transform3d &lhs, const Transform3d &rhs) 
 | 
			
		||||
{
 | 
			
		||||
    typedef Transform3d::Scalar T;
 | 
			
		||||
| 
						 | 
				
			
			@ -674,6 +468,23 @@ static std::vector<PrintInstances> print_objects_from_model_object(const ModelOb
 | 
			
		|||
    return std::vector<PrintInstances>(trafos.begin(), trafos.end());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Compare just the layer ranges and their layer heights, not the associated configs.
 | 
			
		||||
// Ignore the layer heights if check_layer_heights is false.
 | 
			
		||||
bool layer_height_ranges_equal(const t_layer_config_ranges &lr1, const t_layer_config_ranges &lr2, bool check_layer_height)
 | 
			
		||||
{
 | 
			
		||||
    if (lr1.size() != lr2.size())
 | 
			
		||||
        return false;
 | 
			
		||||
    auto it2 = lr2.begin();
 | 
			
		||||
    for (const auto &kvp1 : lr1) {
 | 
			
		||||
        const auto &kvp2 = *it2 ++;
 | 
			
		||||
        if (std::abs(kvp1.first.first  - kvp2.first.first ) > EPSILON ||
 | 
			
		||||
            std::abs(kvp1.first.second - kvp2.first.second) > EPSILON ||
 | 
			
		||||
            (check_layer_height && std::abs(kvp1.second.option("layer_height")->getFloat() - kvp2.second.option("layer_height")->getFloat()) > EPSILON))
 | 
			
		||||
            return false;
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &config_in)
 | 
			
		||||
{
 | 
			
		||||
#ifdef _DEBUG
 | 
			
		||||
| 
						 | 
				
			
			@ -724,6 +535,50 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co
 | 
			
		|||
    // Handle changes to regions config defaults
 | 
			
		||||
    m_default_region_config.apply_only(config, region_diff, true);
 | 
			
		||||
    
 | 
			
		||||
    class LayerRanges
 | 
			
		||||
    {
 | 
			
		||||
    public:
 | 
			
		||||
        LayerRanges() {}
 | 
			
		||||
        // Convert input config ranges into continuous non-overlapping sorted vector of intervals and their configs.
 | 
			
		||||
        void assign(const t_layer_config_ranges &in) {
 | 
			
		||||
            m_ranges.clear();
 | 
			
		||||
            m_ranges.reserve(in.size());
 | 
			
		||||
            // Input ranges are sorted lexicographically. First range trims the other ranges.
 | 
			
		||||
            coordf_t last_z = 0;
 | 
			
		||||
            for (const std::pair<const t_layer_height_range, DynamicPrintConfig> &range : in) {
 | 
			
		||||
//            for (auto &range : in) {
 | 
			
		||||
			if (range.first.second > last_z) {
 | 
			
		||||
                    coordf_t min_z = std::max(range.first.first, 0.);
 | 
			
		||||
                    if (min_z > last_z + EPSILON) {
 | 
			
		||||
                        m_ranges.emplace_back(t_layer_height_range(last_z, min_z), nullptr);
 | 
			
		||||
                        last_z = min_z;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (range.first.second > last_z + EPSILON) {
 | 
			
		||||
						const DynamicPrintConfig* cfg = &range.second;
 | 
			
		||||
                        m_ranges.emplace_back(t_layer_height_range(last_z, range.first.second), cfg);
 | 
			
		||||
                        last_z = range.first.second;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (m_ranges.empty())
 | 
			
		||||
                m_ranges.emplace_back(t_layer_height_range(0, DBL_MAX), nullptr);
 | 
			
		||||
            else if (m_ranges.back().second == nullptr)
 | 
			
		||||
                m_ranges.back().first.second = DBL_MAX;
 | 
			
		||||
            else
 | 
			
		||||
                m_ranges.emplace_back(t_layer_height_range(m_ranges.back().first.second, DBL_MAX), nullptr);
 | 
			
		||||
        }
 | 
			
		||||
        const DynamicPrintConfig* config(const t_layer_height_range &range) const {
 | 
			
		||||
            auto it = std::lower_bound(m_ranges.begin(), m_ranges.end(), std::make_pair< t_layer_height_range, const DynamicPrintConfig*>(t_layer_height_range(range.first - EPSILON, range.second - EPSILON), nullptr));
 | 
			
		||||
            assert(it != m_ranges.end());
 | 
			
		||||
            assert(it == m_ranges.end() || std::abs(it->first.first  - range.first ) < EPSILON);
 | 
			
		||||
            assert(it == m_ranges.end() || std::abs(it->first.second - range.second) < EPSILON);
 | 
			
		||||
            return (it == m_ranges.end()) ? nullptr : it->second;
 | 
			
		||||
        }
 | 
			
		||||
        std::vector<std::pair<t_layer_height_range, const DynamicPrintConfig*>>::const_iterator begin() const { return m_ranges.cbegin(); }
 | 
			
		||||
        std::vector<std::pair<t_layer_height_range, const DynamicPrintConfig*>>::const_iterator end() const { return m_ranges.cend(); }
 | 
			
		||||
    private:
 | 
			
		||||
        std::vector<std::pair<t_layer_height_range, const DynamicPrintConfig*>> m_ranges;
 | 
			
		||||
    };
 | 
			
		||||
    struct ModelObjectStatus {
 | 
			
		||||
        enum Status {
 | 
			
		||||
            Unknown,
 | 
			
		||||
| 
						 | 
				
			
			@ -733,8 +588,9 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co
 | 
			
		|||
            Deleted,
 | 
			
		||||
        };
 | 
			
		||||
        ModelObjectStatus(ModelID id, Status status = Unknown) : id(id), status(status) {}
 | 
			
		||||
        ModelID                 id;
 | 
			
		||||
        Status                  status;
 | 
			
		||||
        ModelID     id;
 | 
			
		||||
        Status      status;
 | 
			
		||||
        LayerRanges layer_ranges;
 | 
			
		||||
        // Search by id.
 | 
			
		||||
        bool operator<(const ModelObjectStatus &rhs) const { return id < rhs.id; }
 | 
			
		||||
    };
 | 
			
		||||
| 
						 | 
				
			
			@ -861,21 +717,23 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co
 | 
			
		|||
        auto it_status = model_object_status.find(ModelObjectStatus(model_object.id()));
 | 
			
		||||
        assert(it_status != model_object_status.end());
 | 
			
		||||
        assert(it_status->status != ModelObjectStatus::Deleted);
 | 
			
		||||
		const ModelObject& model_object_new = *model.objects[idx_model_object];
 | 
			
		||||
		const_cast<ModelObjectStatus&>(*it_status).layer_ranges.assign(model_object_new.layer_config_ranges);
 | 
			
		||||
        if (it_status->status == ModelObjectStatus::New)
 | 
			
		||||
            // PrintObject instances will be added in the next loop.
 | 
			
		||||
            continue;
 | 
			
		||||
        // Update the ModelObject instance, possibly invalidate the linked PrintObjects.
 | 
			
		||||
        assert(it_status->status == ModelObjectStatus::Old || it_status->status == ModelObjectStatus::Moved);
 | 
			
		||||
        const ModelObject &model_object_new = *model.objects[idx_model_object];
 | 
			
		||||
        // Check whether a model part volume was added or removed, their transformations or order changed.
 | 
			
		||||
        // Only volume IDs, volume types and their order are checked, configuration and other parameters are NOT checked.
 | 
			
		||||
        bool model_parts_differ         = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::MODEL_PART);
 | 
			
		||||
        bool modifiers_differ           = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::PARAMETER_MODIFIER);
 | 
			
		||||
        bool support_blockers_differ    = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_BLOCKER);
 | 
			
		||||
        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_profile       != model_object_new.layer_height_profile) {
 | 
			
		||||
            model_object.layer_height_profile       != model_object_new.layer_height_profile ||
 | 
			
		||||
            ! layer_height_ranges_equal(model_object.layer_config_ranges, model_object_new.layer_config_ranges, model_object_new.layer_height_profile.empty())) {
 | 
			
		||||
            // 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()));
 | 
			
		||||
            for (auto it = range.first; it != range.second; ++ it) {
 | 
			
		||||
| 
						 | 
				
			
			@ -915,7 +773,8 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co
 | 
			
		|||
            //FIXME What to do with m_material_id?
 | 
			
		||||
			model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::MODEL_PART);
 | 
			
		||||
			model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::PARAMETER_MODIFIER);
 | 
			
		||||
            // Copy the ModelObject name, input_file and instances. The instances will compared against PrintObject instances in the next step.
 | 
			
		||||
            layer_height_ranges_copy_configs(model_object.layer_config_ranges /* dst */, model_object_new.layer_config_ranges /* src */);
 | 
			
		||||
            // Copy the ModelObject name, input_file and instances. The instances will be compared against PrintObject instances in the next step.
 | 
			
		||||
            model_object.name       = model_object_new.name;
 | 
			
		||||
            model_object.input_file = model_object_new.input_file;
 | 
			
		||||
            model_object.clear_instances();
 | 
			
		||||
| 
						 | 
				
			
			@ -1027,19 +886,27 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co
 | 
			
		|||
        PrintRegionConfig  this_region_config;
 | 
			
		||||
        bool               this_region_config_set = false;
 | 
			
		||||
        for (PrintObject *print_object : m_objects) {
 | 
			
		||||
            const LayerRanges *layer_ranges;
 | 
			
		||||
            {
 | 
			
		||||
                auto it_status = model_object_status.find(ModelObjectStatus(print_object->model_object()->id()));
 | 
			
		||||
                assert(it_status != model_object_status.end());
 | 
			
		||||
                assert(it_status->status != ModelObjectStatus::Deleted);
 | 
			
		||||
                layer_ranges = &it_status->layer_ranges;
 | 
			
		||||
            }
 | 
			
		||||
            if (region_id < print_object->region_volumes.size()) {
 | 
			
		||||
                for (int volume_id : print_object->region_volumes[region_id]) {
 | 
			
		||||
                    const ModelVolume &volume = *print_object->model_object()->volumes[volume_id];
 | 
			
		||||
                for (const std::pair<t_layer_height_range, int> &volume_and_range : print_object->region_volumes[region_id]) {
 | 
			
		||||
                    const ModelVolume        &volume             = *print_object->model_object()->volumes[volume_and_range.second];
 | 
			
		||||
                    const DynamicPrintConfig *layer_range_config = layer_ranges->config(volume_and_range.first);
 | 
			
		||||
                    if (this_region_config_set) {
 | 
			
		||||
                        // If the new config for this volume differs from the other
 | 
			
		||||
                        // volume configs currently associated to this region, it means
 | 
			
		||||
                        // the region subdivision does not make sense anymore.
 | 
			
		||||
                        if (! this_region_config.equals(PrintObject::region_config_from_model_volume(m_default_region_config, volume, num_extruders)))
 | 
			
		||||
                        if (! this_region_config.equals(PrintObject::region_config_from_model_volume(m_default_region_config, layer_range_config, volume, num_extruders)))
 | 
			
		||||
                            // Regions were split. Reset this print_object.
 | 
			
		||||
                            goto print_object_end;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        this_region_config = PrintObject::region_config_from_model_volume(m_default_region_config, volume, num_extruders);
 | 
			
		||||
						for (size_t i = 0; i < region_id; ++i) {
 | 
			
		||||
                        this_region_config = PrintObject::region_config_from_model_volume(m_default_region_config, layer_range_config, volume, num_extruders);
 | 
			
		||||
						for (size_t i = 0; i < region_id; ++ i) {
 | 
			
		||||
							const PrintRegion ®ion_other = *m_regions[i];
 | 
			
		||||
							if (region_other.m_refcnt != 0 && region_other.config().equals(this_region_config))
 | 
			
		||||
								// Regions were merged. Reset this print_object.
 | 
			
		||||
| 
						 | 
				
			
			@ -1054,7 +921,7 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co
 | 
			
		|||
            update_apply_status(print_object->invalidate_all_steps());
 | 
			
		||||
            // Decrease the references to regions from this volume.
 | 
			
		||||
            int ireg = 0;
 | 
			
		||||
            for (const std::vector<int> &volumes : print_object->region_volumes) {
 | 
			
		||||
            for (const std::vector<std::pair<t_layer_height_range, int>> &volumes : print_object->region_volumes) {
 | 
			
		||||
                if (! volumes.empty())
 | 
			
		||||
                    -- m_regions[ireg]->m_refcnt;
 | 
			
		||||
                ++ ireg;
 | 
			
		||||
| 
						 | 
				
			
			@ -1076,52 +943,65 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co
 | 
			
		|||
    for (size_t idx_print_object = 0; idx_print_object < m_objects.size(); ++ idx_print_object) {
 | 
			
		||||
        PrintObject        &print_object0 = *m_objects[idx_print_object];
 | 
			
		||||
        const ModelObject  &model_object  = *print_object0.model_object();
 | 
			
		||||
        std::vector<int>    map_volume_to_region(model_object.volumes.size(), -1);
 | 
			
		||||
        const LayerRanges *layer_ranges;
 | 
			
		||||
        {
 | 
			
		||||
            auto it_status = model_object_status.find(ModelObjectStatus(model_object.id()));
 | 
			
		||||
            assert(it_status != model_object_status.end());
 | 
			
		||||
            assert(it_status->status != ModelObjectStatus::Deleted);
 | 
			
		||||
            layer_ranges = &it_status->layer_ranges;
 | 
			
		||||
        }
 | 
			
		||||
        std::vector<int>   regions_in_object;
 | 
			
		||||
        regions_in_object.reserve(64);
 | 
			
		||||
        for (size_t i = idx_print_object; i < m_objects.size() && m_objects[i]->model_object() == &model_object; ++ i) {
 | 
			
		||||
            PrintObject &print_object = *m_objects[i];
 | 
			
		||||
			bool         fresh = print_object.region_volumes.empty();
 | 
			
		||||
            unsigned int volume_id = 0;
 | 
			
		||||
            unsigned int idx_region_in_object = 0;
 | 
			
		||||
            for (const ModelVolume *volume : model_object.volumes) {
 | 
			
		||||
                if (! volume->is_model_part() && ! volume->is_modifier()) {
 | 
			
		||||
					++ volume_id;
 | 
			
		||||
					continue;
 | 
			
		||||
				}
 | 
			
		||||
                int region_id = -1;
 | 
			
		||||
                if (&print_object == &print_object0) {
 | 
			
		||||
                    // Get the config applied to this volume.
 | 
			
		||||
                    PrintRegionConfig config = PrintObject::region_config_from_model_volume(m_default_region_config, *volume, num_extruders);
 | 
			
		||||
                    // Find an existing print region with the same config.
 | 
			
		||||
					int idx_empty_slot = -1;
 | 
			
		||||
					for (int i = 0; i < (int)m_regions.size(); ++ i) {
 | 
			
		||||
						if (m_regions[i]->m_refcnt == 0) {
 | 
			
		||||
                            if (idx_empty_slot == -1)
 | 
			
		||||
                                idx_empty_slot = i;
 | 
			
		||||
                        } else if (config.equals(m_regions[i]->config())) {
 | 
			
		||||
                            region_id = i;
 | 
			
		||||
                            break;
 | 
			
		||||
                // Filter the layer ranges, so they do not overlap and they contain at least a single layer.
 | 
			
		||||
                // Now insert a volume with a layer range to its own region.
 | 
			
		||||
                for (auto it_range = layer_ranges->begin(); it_range != layer_ranges->end(); ++ it_range) {
 | 
			
		||||
                    int region_id = -1;
 | 
			
		||||
                    if (&print_object == &print_object0) {
 | 
			
		||||
                        // Get the config applied to this volume.
 | 
			
		||||
                        PrintRegionConfig config = PrintObject::region_config_from_model_volume(m_default_region_config, it_range->second, *volume, num_extruders);
 | 
			
		||||
                        // Find an existing print region with the same config.
 | 
			
		||||
    					int idx_empty_slot = -1;
 | 
			
		||||
    					for (int i = 0; i < (int)m_regions.size(); ++ i) {
 | 
			
		||||
    						if (m_regions[i]->m_refcnt == 0) {
 | 
			
		||||
                                if (idx_empty_slot == -1)
 | 
			
		||||
                                    idx_empty_slot = i;
 | 
			
		||||
                            } else if (config.equals(m_regions[i]->config())) {
 | 
			
		||||
                                region_id = i;
 | 
			
		||||
                                break;
 | 
			
		||||
                            }
 | 
			
		||||
    					}
 | 
			
		||||
                        // If no region exists with the same config, create a new one.
 | 
			
		||||
    					if (region_id == -1) {
 | 
			
		||||
    						if (idx_empty_slot == -1) {
 | 
			
		||||
    							region_id = (int)m_regions.size();
 | 
			
		||||
    							this->add_region(config);
 | 
			
		||||
    						} else {
 | 
			
		||||
    							region_id = idx_empty_slot;
 | 
			
		||||
                                m_regions[region_id]->set_config(std::move(config));
 | 
			
		||||
    						}
 | 
			
		||||
                        }
 | 
			
		||||
					}
 | 
			
		||||
                    // If no region exists with the same config, create a new one.
 | 
			
		||||
					if (region_id == -1) {
 | 
			
		||||
						if (idx_empty_slot == -1) {
 | 
			
		||||
							region_id = (int)m_regions.size();
 | 
			
		||||
							this->add_region(config);
 | 
			
		||||
						} else {
 | 
			
		||||
							region_id = idx_empty_slot;
 | 
			
		||||
                            m_regions[region_id]->set_config(std::move(config));
 | 
			
		||||
						}
 | 
			
		||||
                    }
 | 
			
		||||
                    map_volume_to_region[volume_id] = region_id;
 | 
			
		||||
                } else
 | 
			
		||||
                    region_id = map_volume_to_region[volume_id];
 | 
			
		||||
                // Assign volume to a region.
 | 
			
		||||
				if (fresh) {
 | 
			
		||||
					if (region_id >= print_object.region_volumes.size() || print_object.region_volumes[region_id].empty())
 | 
			
		||||
						++ m_regions[region_id]->m_refcnt;
 | 
			
		||||
					print_object.add_region_volume(region_id, volume_id);
 | 
			
		||||
				}
 | 
			
		||||
                ++ volume_id;
 | 
			
		||||
            }
 | 
			
		||||
                        regions_in_object.emplace_back(region_id);
 | 
			
		||||
                    } else
 | 
			
		||||
                        region_id = regions_in_object[idx_region_in_object ++];
 | 
			
		||||
                    // Assign volume to a region.
 | 
			
		||||
    				if (fresh) {
 | 
			
		||||
    					if (region_id >= print_object.region_volumes.size() || print_object.region_volumes[region_id].empty())
 | 
			
		||||
    						++ m_regions[region_id]->m_refcnt;
 | 
			
		||||
    					print_object.add_region_volume(region_id, volume_id, it_range->first);
 | 
			
		||||
    				}
 | 
			
		||||
                }
 | 
			
		||||
				++ volume_id;
 | 
			
		||||
			}
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1175,9 +1055,9 @@ std::string Print::validate() const
 | 
			
		|||
                Polygon        convex_hull0    = offset(
 | 
			
		||||
                    print_object->model_object()->convex_hull_2d(
 | 
			
		||||
                        Geometry::assemble_transform(Vec3d::Zero(), rotation, model_instance0->get_scaling_factor(), model_instance0->get_mirror())),
 | 
			
		||||
                    scale_(m_config.extruder_clearance_radius.value) / 2., jtRound, scale_(0.1)).front();
 | 
			
		||||
                    float(scale_(0.5 * m_config.extruder_clearance_radius.value)), jtRound, float(scale_(0.1))).front();
 | 
			
		||||
                // Now we check that no instance of convex_hull intersects any of the previously checked object instances.
 | 
			
		||||
                for (const Point © : print_object->m_copies) {
 | 
			
		||||
                for (const Point © : print_object->copies()) {
 | 
			
		||||
                    Polygon convex_hull = convex_hull0;
 | 
			
		||||
                    convex_hull.translate(copy);
 | 
			
		||||
                    if (! intersection(convex_hulls_other, convex_hull).empty())
 | 
			
		||||
| 
						 | 
				
			
			@ -1227,7 +1107,7 @@ 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_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;
 | 
			
		||||
| 
						 | 
				
			
			@ -1435,9 +1315,9 @@ Flow Print::brim_flow() const
 | 
			
		|||
       generation as well. */
 | 
			
		||||
    return Flow::new_from_config_width(
 | 
			
		||||
        frPerimeter,
 | 
			
		||||
        width, 
 | 
			
		||||
        m_config.nozzle_diameter.get_at(m_regions.front()->config().perimeter_extruder-1),
 | 
			
		||||
        this->skirt_first_layer_height(),
 | 
			
		||||
		width,
 | 
			
		||||
        (float)m_config.nozzle_diameter.get_at(m_regions.front()->config().perimeter_extruder-1),
 | 
			
		||||
		(float)this->skirt_first_layer_height(),
 | 
			
		||||
        0
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1457,9 +1337,9 @@ Flow Print::skirt_flow() const
 | 
			
		|||
       generation as well. */
 | 
			
		||||
    return Flow::new_from_config_width(
 | 
			
		||||
        frPerimeter,
 | 
			
		||||
        width, 
 | 
			
		||||
        m_config.nozzle_diameter.get_at(m_objects.front()->config().support_material_extruder-1),
 | 
			
		||||
        this->skirt_first_layer_height(),
 | 
			
		||||
		width,
 | 
			
		||||
		(float)m_config.nozzle_diameter.get_at(m_objects.front()->config().support_material_extruder-1),
 | 
			
		||||
		(float)this->skirt_first_layer_height(),
 | 
			
		||||
        0
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1634,20 +1514,20 @@ void Print::_make_skirt()
 | 
			
		|||
 | 
			
		||||
    // Initial offset of the brim inner edge from the object (possible with a support & raft).
 | 
			
		||||
    // The skirt will touch the brim if the brim is extruded.
 | 
			
		||||
    Flow brim_flow = this->brim_flow();
 | 
			
		||||
    Flow   brim_flow = this->brim_flow();
 | 
			
		||||
    double actual_brim_width = brim_flow.spacing() * floor(m_config.brim_width.value / brim_flow.spacing());
 | 
			
		||||
    coord_t distance = scale_(std::max(m_config.skirt_distance.value, actual_brim_width) - spacing/2.);
 | 
			
		||||
    auto   distance = float(scale_(std::max(m_config.skirt_distance.value, actual_brim_width) - spacing/2.));
 | 
			
		||||
    // Draw outlines from outside to inside.
 | 
			
		||||
    // Loop while we have less skirts than required or any extruder hasn't reached the min length if any.
 | 
			
		||||
    std::vector<coordf_t> extruded_length(extruders.size(), 0.);
 | 
			
		||||
    for (int i = n_skirts, extruder_idx = 0; i > 0; -- i) {
 | 
			
		||||
        this->throw_if_canceled();
 | 
			
		||||
        // Offset the skirt outside.
 | 
			
		||||
        distance += coord_t(scale_(spacing));
 | 
			
		||||
        distance += float(scale_(spacing));
 | 
			
		||||
        // Generate the skirt centerline.
 | 
			
		||||
        Polygon loop;
 | 
			
		||||
        {
 | 
			
		||||
            Polygons loops = offset(convex_hull, distance, ClipperLib::jtRound, scale_(0.1));
 | 
			
		||||
            Polygons loops = offset(convex_hull, distance, ClipperLib::jtRound, float(scale_(0.1)));
 | 
			
		||||
            Geometry::simplify_polygons(loops, scale_(0.05), &loops);
 | 
			
		||||
			if (loops.empty())
 | 
			
		||||
				break;
 | 
			
		||||
| 
						 | 
				
			
			@ -1658,9 +1538,9 @@ void Print::_make_skirt()
 | 
			
		|||
        eloop.paths.emplace_back(ExtrusionPath(
 | 
			
		||||
            ExtrusionPath(
 | 
			
		||||
                erSkirt,
 | 
			
		||||
                mm3_per_mm,         // this will be overridden at G-code export time
 | 
			
		||||
                (float)mm3_per_mm,         // this will be overridden at G-code export time
 | 
			
		||||
                flow.width,
 | 
			
		||||
                first_layer_height  // this will be overridden at G-code export time
 | 
			
		||||
				(float)first_layer_height  // this will be overridden at G-code export time
 | 
			
		||||
            )));
 | 
			
		||||
        eloop.paths.back().polyline = loop.split_at_first_point();
 | 
			
		||||
        m_skirt.append(eloop);
 | 
			
		||||
| 
						 | 
				
			
			@ -1786,7 +1666,7 @@ void Print::_make_wipe_tower()
 | 
			
		|||
                // Insert the new support layer.
 | 
			
		||||
                double height    = lt.print_z - m_wipe_tower_data.tool_ordering.layer_tools()[i-1].print_z;
 | 
			
		||||
                //FIXME the support layer ID is set to -1, as Vojtech hopes it is not being used anyway.
 | 
			
		||||
                it_layer = m_objects.front()->insert_support_layer(it_layer, size_t(-1), height, lt.print_z, lt.print_z - 0.5 * height);
 | 
			
		||||
                it_layer = m_objects.front()->insert_support_layer(it_layer, -1, height, lt.print_z, lt.print_z - 0.5 * height);
 | 
			
		||||
                ++ it_layer;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -1813,19 +1693,19 @@ void Print::_make_wipe_tower()
 | 
			
		|||
            WipeTowerPrusaMM::parse_material(m_config.filament_type.get_at(i).c_str()),
 | 
			
		||||
            m_config.temperature.get_at(i),
 | 
			
		||||
            m_config.first_layer_temperature.get_at(i),
 | 
			
		||||
            m_config.filament_loading_speed.get_at(i),
 | 
			
		||||
            m_config.filament_loading_speed_start.get_at(i),
 | 
			
		||||
            m_config.filament_unloading_speed.get_at(i),
 | 
			
		||||
            m_config.filament_unloading_speed_start.get_at(i),
 | 
			
		||||
            m_config.filament_toolchange_delay.get_at(i),
 | 
			
		||||
			(float)m_config.filament_loading_speed.get_at(i),
 | 
			
		||||
			(float)m_config.filament_loading_speed_start.get_at(i),
 | 
			
		||||
			(float)m_config.filament_unloading_speed.get_at(i),
 | 
			
		||||
			(float)m_config.filament_unloading_speed_start.get_at(i),
 | 
			
		||||
			(float)m_config.filament_toolchange_delay.get_at(i),
 | 
			
		||||
            m_config.filament_cooling_moves.get_at(i),
 | 
			
		||||
            m_config.filament_cooling_initial_speed.get_at(i),
 | 
			
		||||
            m_config.filament_cooling_final_speed.get_at(i),
 | 
			
		||||
			(float)m_config.filament_cooling_initial_speed.get_at(i),
 | 
			
		||||
			(float)m_config.filament_cooling_final_speed.get_at(i),
 | 
			
		||||
            m_config.filament_ramming_parameters.get_at(i),
 | 
			
		||||
            m_config.nozzle_diameter.get_at(i));
 | 
			
		||||
			(float)m_config.nozzle_diameter.get_at(i));
 | 
			
		||||
 | 
			
		||||
    m_wipe_tower_data.priming = Slic3r::make_unique<WipeTower::ToolChangeResult>(
 | 
			
		||||
        wipe_tower.prime(this->skirt_first_layer_height(), m_wipe_tower_data.tool_ordering.all_extruders(), false));
 | 
			
		||||
        wipe_tower.prime((float)this->skirt_first_layer_height(), m_wipe_tower_data.tool_ordering.all_extruders(), false));
 | 
			
		||||
 | 
			
		||||
    // Lets go through the wipe tower layers and determine pairs of extruder changes for each
 | 
			
		||||
    // to pass to wipe_tower (so that it can use it for planning the layout of the tower)
 | 
			
		||||
| 
						 | 
				
			
			@ -1834,21 +1714,21 @@ void Print::_make_wipe_tower()
 | 
			
		|||
        for (auto &layer_tools : m_wipe_tower_data.tool_ordering.layer_tools()) { // for all layers
 | 
			
		||||
            if (!layer_tools.has_wipe_tower) continue;
 | 
			
		||||
            bool first_layer = &layer_tools == &m_wipe_tower_data.tool_ordering.front();
 | 
			
		||||
            wipe_tower.plan_toolchange(layer_tools.print_z, layer_tools.wipe_tower_layer_height, current_extruder_id, current_extruder_id,false);
 | 
			
		||||
            wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_extruder_id, current_extruder_id, false);
 | 
			
		||||
            for (const auto extruder_id : layer_tools.extruders) {
 | 
			
		||||
                if ((first_layer && extruder_id == m_wipe_tower_data.tool_ordering.all_extruders().back()) || extruder_id != current_extruder_id) {
 | 
			
		||||
                    float volume_to_wipe = wipe_volumes[current_extruder_id][extruder_id];             // total volume to wipe after this toolchange
 | 
			
		||||
                    // Not all of that can be used for infill purging:
 | 
			
		||||
                    volume_to_wipe -= m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id);
 | 
			
		||||
                    volume_to_wipe -= (float)m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id);
 | 
			
		||||
 | 
			
		||||
                    // try to assign some infills/objects for the wiping:
 | 
			
		||||
                    volume_to_wipe = layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, current_extruder_id, extruder_id, volume_to_wipe);
 | 
			
		||||
 | 
			
		||||
                    // add back the minimal amount toforce on the wipe tower:
 | 
			
		||||
                    volume_to_wipe += m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id);
 | 
			
		||||
                    volume_to_wipe += (float)m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id);
 | 
			
		||||
 | 
			
		||||
                    // request a toolchange at the wipe tower with at least volume_to_wipe purging amount
 | 
			
		||||
                    wipe_tower.plan_toolchange(layer_tools.print_z, layer_tools.wipe_tower_layer_height, current_extruder_id, extruder_id,
 | 
			
		||||
                    wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_extruder_id, extruder_id,
 | 
			
		||||
                                               first_layer && extruder_id == m_wipe_tower_data.tool_ordering.all_extruders().back(), volume_to_wipe);
 | 
			
		||||
                    current_extruder_id = extruder_id;
 | 
			
		||||
                }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -80,8 +80,8 @@ private: // Prevents erroneous use by other classes.
 | 
			
		|||
    typedef PrintObjectBaseWithState<Print, PrintObjectStep, posCount> Inherited;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    // vector of (vectors of volume ids), indexed by region_id
 | 
			
		||||
    std::vector<std::vector<int>> region_volumes;
 | 
			
		||||
    // vector of (layer height ranges and vectors of volume ids), indexed by region_id
 | 
			
		||||
    std::vector<std::vector<std::pair<t_layer_height_range, int>>> region_volumes;
 | 
			
		||||
 | 
			
		||||
    // this is set to true when LayerRegion->slices is split in top/internal/bottom
 | 
			
		||||
    // so that next call to make_perimeters() performs a union() before computing loops
 | 
			
		||||
| 
						 | 
				
			
			@ -99,10 +99,10 @@ public:
 | 
			
		|||
    BoundingBox bounding_box() const { return BoundingBox(Point(0,0), to_2d(this->size)); }
 | 
			
		||||
 | 
			
		||||
    // adds region_id, too, if necessary
 | 
			
		||||
    void add_region_volume(unsigned int region_id, int volume_id) {
 | 
			
		||||
    void add_region_volume(unsigned int region_id, int volume_id, const t_layer_height_range &layer_range) {
 | 
			
		||||
        if (region_id >= region_volumes.size())
 | 
			
		||||
			region_volumes.resize(region_id + 1);
 | 
			
		||||
        region_volumes[region_id].emplace_back(volume_id);
 | 
			
		||||
        region_volumes[region_id].emplace_back(layer_range, volume_id);
 | 
			
		||||
    }
 | 
			
		||||
    // This is the *total* layer count (including support layers)
 | 
			
		||||
    // this value is not supposed to be compared with Layer::id
 | 
			
		||||
| 
						 | 
				
			
			@ -141,8 +141,9 @@ public:
 | 
			
		|||
    void slice();
 | 
			
		||||
 | 
			
		||||
    // Helpers to slice support enforcer / blocker meshes by the support generator.
 | 
			
		||||
    std::vector<ExPolygons>     slice_support_enforcers() const;
 | 
			
		||||
    std::vector<ExPolygons>     slice_support_blockers() const;
 | 
			
		||||
    std::vector<ExPolygons>     slice_support_volumes(const ModelVolumeType &model_volume_type) const;
 | 
			
		||||
    std::vector<ExPolygons>     slice_support_blockers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_BLOCKER); }
 | 
			
		||||
    std::vector<ExPolygons>     slice_support_enforcers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_ENFORCER); }
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
    // to be called from Print only.
 | 
			
		||||
| 
						 | 
				
			
			@ -165,7 +166,7 @@ protected:
 | 
			
		|||
    void                    update_slicing_parameters();
 | 
			
		||||
 | 
			
		||||
    static PrintObjectConfig object_config_from_model_object(const PrintObjectConfig &default_object_config, const ModelObject &object, size_t num_extruders);
 | 
			
		||||
    static PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &default_region_config, const ModelVolume &volume, size_t num_extruders);
 | 
			
		||||
    static PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &default_region_config, const DynamicPrintConfig *layer_range_config, const ModelVolume &volume, size_t num_extruders);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    void make_perimeters();
 | 
			
		||||
| 
						 | 
				
			
			@ -201,9 +202,11 @@ private:
 | 
			
		|||
    LayerPtrs                               m_layers;
 | 
			
		||||
    SupportLayerPtrs                        m_support_layers;
 | 
			
		||||
 | 
			
		||||
    std::vector<ExPolygons> _slice_region(size_t region_id, const std::vector<float> &z, bool modifier);
 | 
			
		||||
    std::vector<ExPolygons> _slice_volumes(const std::vector<float> &z, const std::vector<const ModelVolume*> &volumes) const;
 | 
			
		||||
    std::vector<ExPolygons> _slice_volume(const std::vector<float> &z, const ModelVolume &volume) const;
 | 
			
		||||
    std::vector<ExPolygons> slice_region(size_t region_id, const std::vector<float> &z) const;
 | 
			
		||||
    std::vector<ExPolygons> slice_modifiers(size_t region_id, const std::vector<float> &z) const;
 | 
			
		||||
    std::vector<ExPolygons> slice_volumes(const std::vector<float> &z, const std::vector<const ModelVolume*> &volumes) const;
 | 
			
		||||
    std::vector<ExPolygons> slice_volume(const std::vector<float> &z, const ModelVolume &volume) const;
 | 
			
		||||
    std::vector<ExPolygons> slice_volume(const std::vector<float> &z, const std::vector<t_layer_height_range> &ranges, const ModelVolume &volume) const;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct WipeTowerData
 | 
			
		||||
| 
						 | 
				
			
			@ -291,11 +294,6 @@ public:
 | 
			
		|||
 | 
			
		||||
    ApplyStatus         apply(const Model &model, const DynamicPrintConfig &config) override;
 | 
			
		||||
 | 
			
		||||
    // The following three methods are used by the Perl tests only. Get rid of them!
 | 
			
		||||
    void                reload_object(size_t idx);
 | 
			
		||||
    void                add_model_object(ModelObject* model_object, int idx = -1);
 | 
			
		||||
    bool                apply_config_perl_tests_only(DynamicPrintConfig config);
 | 
			
		||||
 | 
			
		||||
    void                process() override;
 | 
			
		||||
    // Exports G-code into a file name based on the path_template, returns the file path of the generated G-code file.
 | 
			
		||||
    // If preview_data is not null, the preview_data is filled in for the G-code visualization (not used by the command line Slic3r).
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -49,7 +49,7 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, bool add_insta
 | 
			
		|||
    {
 | 
			
		||||
        // Translate meshes so that our toolpath generation algorithms work with smaller
 | 
			
		||||
        // XY coordinates; this translation is an optimization and not strictly required.
 | 
			
		||||
        // A cloned mesh will be aligned to 0 before slicing in _slice_region() since we
 | 
			
		||||
        // A cloned mesh will be aligned to 0 before slicing in slice_region() since we
 | 
			
		||||
        // don't assume it's already aligned and we don't alter the original position in model.
 | 
			
		||||
        // We store the XY translation so that we can place copies correctly in the output G-code
 | 
			
		||||
        // (copies are expressed in G-code coordinates and this translation is not publicly exposed).
 | 
			
		||||
| 
						 | 
				
			
			@ -590,7 +590,12 @@ bool PrintObject::invalidate_step(PrintObjectStep step)
 | 
			
		|||
 | 
			
		||||
bool PrintObject::invalidate_all_steps()
 | 
			
		||||
{
 | 
			
		||||
    return Inherited::invalidate_all_steps() | m_print->invalidate_all_steps();
 | 
			
		||||
	// First call the "invalidate" functions, which may cancel background processing.
 | 
			
		||||
    bool result = Inherited::invalidate_all_steps() | m_print->invalidate_all_steps();
 | 
			
		||||
	// Then reset some of the depending values.
 | 
			
		||||
	this->m_slicing_params.valid = false;
 | 
			
		||||
	this->region_volumes.clear();
 | 
			
		||||
	return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool PrintObject::has_support_material() const
 | 
			
		||||
| 
						 | 
				
			
			@ -1354,10 +1359,12 @@ PrintObjectConfig PrintObject::object_config_from_model_object(const PrintObject
 | 
			
		|||
    return config;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PrintRegionConfig PrintObject::region_config_from_model_volume(const PrintRegionConfig &default_region_config, const ModelVolume &volume, size_t num_extruders)
 | 
			
		||||
PrintRegionConfig PrintObject::region_config_from_model_volume(const PrintRegionConfig &default_region_config, const DynamicPrintConfig *layer_range_config, const ModelVolume &volume, size_t num_extruders)
 | 
			
		||||
{
 | 
			
		||||
    PrintRegionConfig config = default_region_config;
 | 
			
		||||
    normalize_and_apply_config(config, volume.get_object()->config);
 | 
			
		||||
    if (layer_range_config != nullptr)
 | 
			
		||||
    	normalize_and_apply_config(config, *layer_range_config);
 | 
			
		||||
    normalize_and_apply_config(config, volume.config);
 | 
			
		||||
    if (! volume.material_id().empty())
 | 
			
		||||
        normalize_and_apply_config(config, volume.material()->config);
 | 
			
		||||
| 
						 | 
				
			
			@ -1375,28 +1382,37 @@ void PrintObject::update_slicing_parameters()
 | 
			
		|||
            this->print()->config(), m_config, unscale<double>(this->size(2)), this->object_extruders());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full_config, const ModelObject &model_object, float object_max_z)
 | 
			
		||||
SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig& full_config, const ModelObject& model_object, float object_max_z)
 | 
			
		||||
{
 | 
			
		||||
    PrintConfig         print_config;
 | 
			
		||||
    PrintObjectConfig   object_config;
 | 
			
		||||
    PrintRegionConfig   default_region_config;
 | 
			
		||||
    print_config .apply(full_config, true);
 | 
			
		||||
    object_config.apply(full_config, true);
 | 
			
		||||
    default_region_config.apply(full_config, true);
 | 
			
		||||
    size_t              num_extruders = print_config.nozzle_diameter.size();
 | 
			
		||||
    object_config = object_config_from_model_object(object_config, model_object, num_extruders);
 | 
			
		||||
	PrintConfig         print_config;
 | 
			
		||||
	PrintObjectConfig   object_config;
 | 
			
		||||
	PrintRegionConfig   default_region_config;
 | 
			
		||||
	print_config.apply(full_config, true);
 | 
			
		||||
	object_config.apply(full_config, true);
 | 
			
		||||
	default_region_config.apply(full_config, true);
 | 
			
		||||
	size_t              num_extruders = print_config.nozzle_diameter.size();
 | 
			
		||||
	object_config = object_config_from_model_object(object_config, model_object, num_extruders);
 | 
			
		||||
 | 
			
		||||
    std::vector<unsigned int> object_extruders;
 | 
			
		||||
    for (const ModelVolume *model_volume : model_object.volumes)
 | 
			
		||||
        if (model_volume->is_model_part())
 | 
			
		||||
            PrintRegion::collect_object_printing_extruders(
 | 
			
		||||
                print_config,
 | 
			
		||||
                region_config_from_model_volume(default_region_config, *model_volume, num_extruders),
 | 
			
		||||
                object_extruders);
 | 
			
		||||
	std::vector<unsigned int> object_extruders;
 | 
			
		||||
	for (const ModelVolume* model_volume : model_object.volumes)
 | 
			
		||||
		if (model_volume->is_model_part()) {
 | 
			
		||||
			PrintRegion::collect_object_printing_extruders(
 | 
			
		||||
				print_config,
 | 
			
		||||
				region_config_from_model_volume(default_region_config, nullptr, *model_volume, num_extruders),
 | 
			
		||||
				object_extruders);
 | 
			
		||||
			for (const std::pair<const t_layer_height_range, DynamicPrintConfig> &range_and_config : model_object.layer_config_ranges)
 | 
			
		||||
				if (range_and_config.second.has("perimeter_extruder") ||
 | 
			
		||||
					range_and_config.second.has("infill_extruder") ||
 | 
			
		||||
					range_and_config.second.has("solid_infill_extruder"))
 | 
			
		||||
					PrintRegion::collect_object_printing_extruders(
 | 
			
		||||
						print_config,
 | 
			
		||||
						region_config_from_model_volume(default_region_config, &range_and_config.second, *model_volume, num_extruders),
 | 
			
		||||
						object_extruders);
 | 
			
		||||
		}
 | 
			
		||||
    sort_remove_duplicates(object_extruders);
 | 
			
		||||
 | 
			
		||||
    if (object_max_z <= 0.f)
 | 
			
		||||
        object_max_z = model_object.raw_bounding_box().size().z();
 | 
			
		||||
        object_max_z = (float)model_object.raw_bounding_box().size().z();
 | 
			
		||||
	return SlicingParameters::create_from_config(print_config, object_config, object_max_z, object_extruders);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1430,12 +1446,12 @@ bool PrintObject::update_layer_height_profile(const ModelObject &model_object, c
 | 
			
		|||
        layer_height_profile.clear();
 | 
			
		||||
 | 
			
		||||
    if (layer_height_profile.empty()) {
 | 
			
		||||
        if (0)
 | 
			
		||||
    	if (0)
 | 
			
		||||
//        if (this->layer_height_profile.empty())
 | 
			
		||||
            layer_height_profile = layer_height_profile_adaptive(slicing_parameters, model_object.layer_height_ranges, model_object.volumes);
 | 
			
		||||
        	layer_height_profile = layer_height_profile_adaptive(slicing_parameters, model_object.layer_config_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_config_ranges);     // #ys_FIXME_experiment
 | 
			
		||||
       	updated = true;
 | 
			
		||||
    }
 | 
			
		||||
    return updated;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1489,22 +1505,28 @@ void PrintObject::_slice(const std::vector<coordf_t> &layer_height_profile)
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    // Count model parts and modifier meshes, check whether the model parts are of the same region.
 | 
			
		||||
    int              single_volume_region = -2; // not set yet
 | 
			
		||||
    int              all_volumes_single_region = -2; // not set yet
 | 
			
		||||
    bool 			 has_z_ranges  = false;
 | 
			
		||||
	size_t           num_volumes   = 0;
 | 
			
		||||
    size_t           num_modifiers = 0;
 | 
			
		||||
    std::vector<int> map_volume_to_region(this->model_object()->volumes.size());
 | 
			
		||||
    for (int region_id = 0; region_id < (int)this->region_volumes.size(); ++ region_id) {
 | 
			
		||||
        for (int volume_id : this->region_volumes[region_id]) {
 | 
			
		||||
		int last_volume_id = -1;
 | 
			
		||||
        for (const std::pair<t_layer_height_range, int> &volume_and_range : this->region_volumes[region_id]) {
 | 
			
		||||
			const int		   volume_id    = volume_and_range.second;
 | 
			
		||||
			const ModelVolume *model_volume = this->model_object()->volumes[volume_id];
 | 
			
		||||
            if (model_volume->is_model_part()) {
 | 
			
		||||
                map_volume_to_region[volume_id] = region_id;
 | 
			
		||||
                if (single_volume_region == -2)
 | 
			
		||||
                    // first model volume met
 | 
			
		||||
                    single_volume_region = region_id;
 | 
			
		||||
                else if (single_volume_region != region_id)
 | 
			
		||||
                    // multiple volumes met and they are not equal
 | 
			
		||||
                    single_volume_region = -1;
 | 
			
		||||
				++ num_volumes;
 | 
			
		||||
				if (last_volume_id == volume_id) {
 | 
			
		||||
					has_z_ranges = true;
 | 
			
		||||
				} else {
 | 
			
		||||
					last_volume_id = volume_id;
 | 
			
		||||
					if (all_volumes_single_region == -2)
 | 
			
		||||
						// first model volume met
 | 
			
		||||
						all_volumes_single_region = region_id;
 | 
			
		||||
					else if (all_volumes_single_region != region_id)
 | 
			
		||||
						// multiple volumes met and they are not equal
 | 
			
		||||
						all_volumes_single_region = -1;
 | 
			
		||||
					++ num_volumes;
 | 
			
		||||
				}
 | 
			
		||||
            } else if (model_volume->is_modifier())
 | 
			
		||||
                ++ num_modifiers;
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -1514,13 +1536,13 @@ void PrintObject::_slice(const std::vector<coordf_t> &layer_height_profile)
 | 
			
		|||
    // Slice all non-modifier volumes.
 | 
			
		||||
    bool clipped  = false;
 | 
			
		||||
    bool upscaled = false;
 | 
			
		||||
    if (! m_config.clip_multipart_objects.value || single_volume_region >= 0) {
 | 
			
		||||
    if (! has_z_ranges && (! m_config.clip_multipart_objects.value || all_volumes_single_region >= 0)) {
 | 
			
		||||
        // Cheap path: Slice regions without mutual clipping.
 | 
			
		||||
        // The cheap path is possible if no clipping is allowed or if slicing volumes of just a single region.
 | 
			
		||||
        for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) {
 | 
			
		||||
            BOOST_LOG_TRIVIAL(debug) << "Slicing objects - region " << region_id;
 | 
			
		||||
            // slicing in parallel
 | 
			
		||||
            std::vector<ExPolygons> expolygons_by_layer = this->_slice_region(region_id, slice_zs, false);
 | 
			
		||||
            std::vector<ExPolygons> expolygons_by_layer = this->slice_region(region_id, slice_zs);
 | 
			
		||||
            m_print->throw_if_canceled();
 | 
			
		||||
            BOOST_LOG_TRIVIAL(debug) << "Slicing objects - append slices " << region_id << " start";
 | 
			
		||||
            for (size_t layer_id = 0; layer_id < expolygons_by_layer.size(); ++ layer_id)
 | 
			
		||||
| 
						 | 
				
			
			@ -1541,15 +1563,29 @@ void PrintObject::_slice(const std::vector<coordf_t> &layer_height_profile)
 | 
			
		|||
        };
 | 
			
		||||
        std::vector<SlicedVolume> sliced_volumes;
 | 
			
		||||
        sliced_volumes.reserve(num_volumes);
 | 
			
		||||
		for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id)
 | 
			
		||||
			for (int volume_id : this->region_volumes[region_id]) {
 | 
			
		||||
		for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) {
 | 
			
		||||
			const std::vector<std::pair<t_layer_height_range, int>> &volumes_and_ranges = this->region_volumes[region_id];
 | 
			
		||||
			for (size_t i = 0; i < volumes_and_ranges.size(); ) {
 | 
			
		||||
				int 			   volume_id    = volumes_and_ranges[i].second;
 | 
			
		||||
				const ModelVolume *model_volume = this->model_object()->volumes[volume_id];
 | 
			
		||||
				if (model_volume->is_model_part()) {
 | 
			
		||||
					BOOST_LOG_TRIVIAL(debug) << "Slicing objects - volume " << volume_id;
 | 
			
		||||
					// Find the ranges of this volume. Ranges in volumes_and_ranges must not overlap for a single volume.
 | 
			
		||||
					std::vector<t_layer_height_range> ranges;
 | 
			
		||||
					ranges.emplace_back(volumes_and_ranges[i].first);
 | 
			
		||||
					size_t j = i + 1;
 | 
			
		||||
					for (; j < volumes_and_ranges.size() && volume_id == volumes_and_ranges[j].second; ++ j)
 | 
			
		||||
						if (! ranges.empty() && std::abs(ranges.back().second - volumes_and_ranges[j].first.first) < EPSILON)
 | 
			
		||||
							ranges.back().second = volumes_and_ranges[j].first.second;
 | 
			
		||||
						else
 | 
			
		||||
							ranges.emplace_back(volumes_and_ranges[j].first);
 | 
			
		||||
                    // slicing in parallel
 | 
			
		||||
					sliced_volumes.emplace_back(volume_id, map_volume_to_region[volume_id], this->_slice_volume(slice_zs, *model_volume));
 | 
			
		||||
				}
 | 
			
		||||
					sliced_volumes.emplace_back(volume_id, (int)region_id, this->slice_volume(slice_zs, ranges, *model_volume));
 | 
			
		||||
					i = j;
 | 
			
		||||
				} else
 | 
			
		||||
					++ i;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
        // Second clip the volumes in the order they are presented at the user interface.
 | 
			
		||||
        BOOST_LOG_TRIVIAL(debug) << "Slicing objects - parallel clipping - start";
 | 
			
		||||
        tbb::parallel_for(
 | 
			
		||||
| 
						 | 
				
			
			@ -1603,7 +1639,7 @@ void PrintObject::_slice(const std::vector<coordf_t> &layer_height_profile)
 | 
			
		|||
        for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) {
 | 
			
		||||
            BOOST_LOG_TRIVIAL(debug) << "Slicing modifier volumes - region " << region_id;
 | 
			
		||||
            // slicing in parallel
 | 
			
		||||
            std::vector<ExPolygons> expolygons_by_layer = this->_slice_region(region_id, slice_zs, true);
 | 
			
		||||
            std::vector<ExPolygons> expolygons_by_layer = this->slice_modifiers(region_id, slice_zs);
 | 
			
		||||
            m_print->throw_if_canceled();
 | 
			
		||||
            if (expolygons_by_layer.empty())
 | 
			
		||||
                continue;
 | 
			
		||||
| 
						 | 
				
			
			@ -1619,7 +1655,7 @@ void PrintObject::_slice(const std::vector<coordf_t> &layer_height_profile)
 | 
			
		|||
                            Layer       *layer = m_layers[layer_id];
 | 
			
		||||
                            LayerRegion *layerm = layer->m_regions[region_id];
 | 
			
		||||
                            LayerRegion *other_layerm = layer->m_regions[other_region_id];
 | 
			
		||||
                            if (layerm == nullptr || other_layerm == nullptr)
 | 
			
		||||
                            if (layerm == nullptr || other_layerm == nullptr || other_layerm->slices.empty() || expolygons_by_layer[layer_id].empty())
 | 
			
		||||
                                continue;
 | 
			
		||||
                            Polygons other_slices = to_polygons(other_layerm->slices);
 | 
			
		||||
                            ExPolygons my_parts = intersection_ex(other_slices, to_polygons(expolygons_by_layer[layer_id]));
 | 
			
		||||
| 
						 | 
				
			
			@ -1752,46 +1788,127 @@ end:
 | 
			
		|||
    BOOST_LOG_TRIVIAL(debug) << "Slicing objects - make_slices in parallel - end";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<ExPolygons> PrintObject::_slice_region(size_t region_id, const std::vector<float> &z, bool modifier)
 | 
			
		||||
// To be used only if there are no layer span specific configurations applied, which would lead to z ranges being generated for this region.
 | 
			
		||||
std::vector<ExPolygons> PrintObject::slice_region(size_t region_id, const std::vector<float> &z) const
 | 
			
		||||
{
 | 
			
		||||
    std::vector<const ModelVolume*> volumes;
 | 
			
		||||
	std::vector<const ModelVolume*> volumes;
 | 
			
		||||
    if (region_id < this->region_volumes.size()) {
 | 
			
		||||
        for (int volume_id : this->region_volumes[region_id]) {
 | 
			
		||||
            const ModelVolume *volume = this->model_object()->volumes[volume_id];
 | 
			
		||||
            if (modifier ? volume->is_modifier() : volume->is_model_part())
 | 
			
		||||
                volumes.emplace_back(volume);
 | 
			
		||||
        }
 | 
			
		||||
		for (const std::pair<t_layer_height_range, int> &volume_and_range : this->region_volumes[region_id]) {
 | 
			
		||||
			const ModelVolume *volume = this->model_object()->volumes[volume_and_range.second];
 | 
			
		||||
			if (volume->is_model_part())
 | 
			
		||||
				volumes.emplace_back(volume);
 | 
			
		||||
		}
 | 
			
		||||
    }
 | 
			
		||||
    return this->_slice_volumes(z, volumes);
 | 
			
		||||
	return this->slice_volumes(z, volumes);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<ExPolygons> PrintObject::slice_support_enforcers() const
 | 
			
		||||
// Z ranges are not applicable to modifier meshes, therefore a sinle volume will be found in volume_and_range at most once.
 | 
			
		||||
std::vector<ExPolygons> PrintObject::slice_modifiers(size_t region_id, const std::vector<float> &slice_zs) const
 | 
			
		||||
{
 | 
			
		||||
	std::vector<ExPolygons> out;
 | 
			
		||||
    if (region_id < this->region_volumes.size())
 | 
			
		||||
    {
 | 
			
		||||
		std::vector<std::vector<t_layer_height_range>> volume_ranges;
 | 
			
		||||
		const std::vector<std::pair<t_layer_height_range, int>> &volumes_and_ranges = this->region_volumes[region_id];
 | 
			
		||||
		volume_ranges.reserve(volumes_and_ranges.size());
 | 
			
		||||
		for (size_t i = 0; i < volumes_and_ranges.size(); ) {
 | 
			
		||||
			int 			   volume_id    = volumes_and_ranges[i].second;
 | 
			
		||||
			const ModelVolume *model_volume = this->model_object()->volumes[volume_id];
 | 
			
		||||
			if (model_volume->is_modifier()) {
 | 
			
		||||
				std::vector<t_layer_height_range> ranges;
 | 
			
		||||
				ranges.emplace_back(volumes_and_ranges[i].first);
 | 
			
		||||
				size_t j = i + 1;
 | 
			
		||||
				for (; j < volumes_and_ranges.size() && volume_id == volumes_and_ranges[j].second; ++ j) {
 | 
			
		||||
					if (! ranges.empty() && std::abs(ranges.back().second - volumes_and_ranges[j].first.first) < EPSILON)
 | 
			
		||||
						ranges.back().second = volumes_and_ranges[j].first.second;
 | 
			
		||||
					else
 | 
			
		||||
						ranges.emplace_back(volumes_and_ranges[j].first);
 | 
			
		||||
				}
 | 
			
		||||
				volume_ranges.emplace_back(std::move(ranges));
 | 
			
		||||
				i = j;
 | 
			
		||||
			} else
 | 
			
		||||
				++ i;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (! volume_ranges.empty()) 
 | 
			
		||||
		{
 | 
			
		||||
			bool equal_ranges = true;
 | 
			
		||||
			for (size_t i = 1; i < volume_ranges.size(); ++ i) {
 | 
			
		||||
				assert(! volume_ranges[i].empty());
 | 
			
		||||
				if (volume_ranges.front() != volume_ranges[i]) {
 | 
			
		||||
					equal_ranges = false;
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (equal_ranges && volume_ranges.front().size() == 1 && volume_ranges.front().front() == t_layer_height_range(0, DBL_MAX)) {
 | 
			
		||||
				// No modifier in this region was split to layer spans.
 | 
			
		||||
				std::vector<const ModelVolume*> volumes;
 | 
			
		||||
				for (const std::pair<t_layer_height_range, int> &volume_and_range : this->region_volumes[region_id]) {
 | 
			
		||||
					const ModelVolume *volume = this->model_object()->volumes[volume_and_range.second];
 | 
			
		||||
					if (volume->is_modifier())
 | 
			
		||||
						volumes.emplace_back(volume);
 | 
			
		||||
				}
 | 
			
		||||
				out = this->slice_volumes(slice_zs, volumes);
 | 
			
		||||
			} else {
 | 
			
		||||
				// Some modifier in this region was split to layer spans.
 | 
			
		||||
				std::vector<char> merge;
 | 
			
		||||
				for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) {
 | 
			
		||||
					const std::vector<std::pair<t_layer_height_range, int>> &volumes_and_ranges = this->region_volumes[region_id];
 | 
			
		||||
					for (size_t i = 0; i < volumes_and_ranges.size(); ) {
 | 
			
		||||
						int 			   volume_id    = volumes_and_ranges[i].second;
 | 
			
		||||
						const ModelVolume *model_volume = this->model_object()->volumes[volume_id];
 | 
			
		||||
						if (model_volume->is_modifier()) {
 | 
			
		||||
							BOOST_LOG_TRIVIAL(debug) << "Slicing modifiers - volume " << volume_id;
 | 
			
		||||
							// Find the ranges of this volume. Ranges in volumes_and_ranges must not overlap for a single volume.
 | 
			
		||||
							std::vector<t_layer_height_range> ranges;
 | 
			
		||||
							ranges.emplace_back(volumes_and_ranges[i].first);
 | 
			
		||||
							size_t j = i + 1;
 | 
			
		||||
							for (; j < volumes_and_ranges.size() && volume_id == volumes_and_ranges[j].second; ++ j)
 | 
			
		||||
								ranges.emplace_back(volumes_and_ranges[j].first);
 | 
			
		||||
			                // slicing in parallel
 | 
			
		||||
			                std::vector<ExPolygons> this_slices = this->slice_volume(slice_zs, ranges, *model_volume);
 | 
			
		||||
			                if (out.empty()) {
 | 
			
		||||
			                	out = std::move(this_slices);
 | 
			
		||||
			                	merge.assign(out.size(), false);
 | 
			
		||||
			                } else {
 | 
			
		||||
			                	for (size_t i = 0; i < out.size(); ++ i)
 | 
			
		||||
			                		if (! this_slices[i].empty())
 | 
			
		||||
			                			if (! out[i].empty()) {
 | 
			
		||||
			                				append(out[i], this_slices[i]);
 | 
			
		||||
			                				merge[i] = true;
 | 
			
		||||
			                			} else
 | 
			
		||||
			                				out[i] = std::move(this_slices[i]);
 | 
			
		||||
			                }
 | 
			
		||||
							i = j;
 | 
			
		||||
						} else
 | 
			
		||||
							++ i;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				for (size_t i = 0; i < merge.size(); ++ i)
 | 
			
		||||
					if (merge[i])
 | 
			
		||||
						out[i] = union_ex(out[i]);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<ExPolygons> PrintObject::slice_support_volumes(const ModelVolumeType &model_volume_type) const
 | 
			
		||||
{
 | 
			
		||||
    std::vector<const ModelVolume*> volumes;
 | 
			
		||||
    for (const ModelVolume *volume : this->model_object()->volumes)
 | 
			
		||||
        if (volume->is_support_enforcer())
 | 
			
		||||
        if (volume->type() == model_volume_type)
 | 
			
		||||
            volumes.emplace_back(volume);
 | 
			
		||||
    std::vector<float> zs;
 | 
			
		||||
    zs.reserve(this->layers().size());
 | 
			
		||||
    for (const Layer *l : this->layers())
 | 
			
		||||
        zs.emplace_back((float)l->slice_z);
 | 
			
		||||
    return this->_slice_volumes(zs, volumes);
 | 
			
		||||
    return this->slice_volumes(zs, volumes);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<ExPolygons> PrintObject::slice_support_blockers() const
 | 
			
		||||
{
 | 
			
		||||
    std::vector<const ModelVolume*> volumes;
 | 
			
		||||
    for (const ModelVolume *volume : this->model_object()->volumes)
 | 
			
		||||
        if (volume->is_support_blocker())
 | 
			
		||||
            volumes.emplace_back(volume);
 | 
			
		||||
    std::vector<float> zs;
 | 
			
		||||
    zs.reserve(this->layers().size());
 | 
			
		||||
    for (const Layer *l : this->layers())
 | 
			
		||||
        zs.emplace_back((float)l->slice_z);
 | 
			
		||||
    return this->_slice_volumes(zs, volumes);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<ExPolygons> PrintObject::_slice_volumes(const std::vector<float> &z, const std::vector<const ModelVolume*> &volumes) const
 | 
			
		||||
std::vector<ExPolygons> PrintObject::slice_volumes(const std::vector<float> &z, const std::vector<const ModelVolume*> &volumes) const
 | 
			
		||||
{
 | 
			
		||||
    std::vector<ExPolygons> layers;
 | 
			
		||||
    if (! volumes.empty()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1828,34 +1945,71 @@ std::vector<ExPolygons> PrintObject::_slice_volumes(const std::vector<float> &z,
 | 
			
		|||
    return layers;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<ExPolygons> PrintObject::_slice_volume(const std::vector<float> &z, const ModelVolume &volume) const
 | 
			
		||||
std::vector<ExPolygons> PrintObject::slice_volume(const std::vector<float> &z, const ModelVolume &volume) const
 | 
			
		||||
{
 | 
			
		||||
    std::vector<ExPolygons> layers;
 | 
			
		||||
    // Compose mesh.
 | 
			
		||||
    //FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them.
 | 
			
		||||
    TriangleMesh mesh(volume.mesh());
 | 
			
		||||
    mesh.transform(volume.get_matrix(), true);
 | 
			
		||||
	if (mesh.repaired) {
 | 
			
		||||
		//FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it.
 | 
			
		||||
		stl_check_facets_exact(&mesh.stl);
 | 
			
		||||
    if (! z.empty()) {
 | 
			
		||||
	    // Compose mesh.
 | 
			
		||||
	    //FIXME better to split the mesh into separate shells, perform slicing over each shell separately and then to use a Boolean operation to merge them.
 | 
			
		||||
	    TriangleMesh mesh(volume.mesh());
 | 
			
		||||
	    mesh.transform(volume.get_matrix(), true);
 | 
			
		||||
		if (mesh.repaired) {
 | 
			
		||||
			//FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it.
 | 
			
		||||
			stl_check_facets_exact(&mesh.stl);
 | 
			
		||||
		}
 | 
			
		||||
	    if (mesh.stl.stats.number_of_facets > 0) {
 | 
			
		||||
	        mesh.transform(m_trafo, true);
 | 
			
		||||
	        // apply XY shift
 | 
			
		||||
	        mesh.translate(- unscale<float>(m_copies_shift(0)), - unscale<float>(m_copies_shift(1)), 0);
 | 
			
		||||
	        // perform actual slicing
 | 
			
		||||
	        TriangleMeshSlicer mslicer;
 | 
			
		||||
	        const Print *print = this->print();
 | 
			
		||||
	        auto callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();});
 | 
			
		||||
	        // TriangleMeshSlicer needs the shared vertices.
 | 
			
		||||
	        mesh.require_shared_vertices();
 | 
			
		||||
	        mslicer.init(&mesh, callback);
 | 
			
		||||
	        mslicer.slice(z, float(m_config.slice_closing_radius.value), &layers, callback);
 | 
			
		||||
	        m_print->throw_if_canceled();
 | 
			
		||||
	    }
 | 
			
		||||
	}
 | 
			
		||||
    if (mesh.stl.stats.number_of_facets > 0) {
 | 
			
		||||
        mesh.transform(m_trafo, true);
 | 
			
		||||
        // apply XY shift
 | 
			
		||||
        mesh.translate(- unscale<float>(m_copies_shift(0)), - unscale<float>(m_copies_shift(1)), 0);
 | 
			
		||||
        // perform actual slicing
 | 
			
		||||
        TriangleMeshSlicer mslicer;
 | 
			
		||||
        const Print *print = this->print();
 | 
			
		||||
        auto callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();});
 | 
			
		||||
        // TriangleMeshSlicer needs the shared vertices.
 | 
			
		||||
        mesh.require_shared_vertices();
 | 
			
		||||
        mslicer.init(&mesh, callback);
 | 
			
		||||
        mslicer.slice(z, float(m_config.slice_closing_radius.value), &layers, callback);
 | 
			
		||||
        m_print->throw_if_canceled();
 | 
			
		||||
    }
 | 
			
		||||
    return layers;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Filter the zs not inside the ranges. The ranges are closed at the botton and open at the top, they are sorted lexicographically and non overlapping.
 | 
			
		||||
std::vector<ExPolygons> PrintObject::slice_volume(const std::vector<float> &z, const std::vector<t_layer_height_range> &ranges, const ModelVolume &volume) const
 | 
			
		||||
{
 | 
			
		||||
	std::vector<ExPolygons> out;
 | 
			
		||||
	if (! z.empty() && ! ranges.empty()) {
 | 
			
		||||
		if (ranges.size() == 1 && z.front() >= ranges.front().first && z.back() < ranges.front().second) {
 | 
			
		||||
			// All layers fit into a single range.
 | 
			
		||||
			out = this->slice_volume(z, volume);
 | 
			
		||||
		} else {
 | 
			
		||||
			std::vector<float> 					   z_filtered;
 | 
			
		||||
			std::vector<std::pair<size_t, size_t>> n_filtered;
 | 
			
		||||
			z_filtered.reserve(z.size());
 | 
			
		||||
			n_filtered.reserve(2 * ranges.size());
 | 
			
		||||
			size_t i = 0;
 | 
			
		||||
			for (const t_layer_height_range &range : ranges) {
 | 
			
		||||
				for (; i < z.size() && z[i] < range.first; ++ i) ;
 | 
			
		||||
				size_t first = i;
 | 
			
		||||
				for (; i < z.size() && z[i] < range.second; ++ i)
 | 
			
		||||
					z_filtered.emplace_back(z[i]);
 | 
			
		||||
				if (i > first)
 | 
			
		||||
					n_filtered.emplace_back(std::make_pair(first, i));
 | 
			
		||||
			}
 | 
			
		||||
			if (! n_filtered.empty()) {
 | 
			
		||||
				std::vector<ExPolygons> layers = this->slice_volume(z_filtered, volume);
 | 
			
		||||
				out.assign(z.size(), ExPolygons());
 | 
			
		||||
				i = 0;
 | 
			
		||||
				for (const std::pair<size_t, size_t> &span : n_filtered)
 | 
			
		||||
					for (size_t j = span.first; j < span.second; ++ j)
 | 
			
		||||
						out[j] = std::move(layers[i ++]);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string PrintObject::_fix_slicing_errors()
 | 
			
		||||
{
 | 
			
		||||
    // Collect layers with slicing errors.
 | 
			
		||||
| 
						 | 
				
			
			@ -2119,7 +2273,7 @@ void PrintObject::clip_fill_surfaces()
 | 
			
		|||
            //Should the pw not be half of the current value?
 | 
			
		||||
            float pw = FLT_MAX;
 | 
			
		||||
            for (const LayerRegion *layerm : layer->m_regions)
 | 
			
		||||
                pw = std::min<float>(pw, layerm->flow(frPerimeter).scaled_width());
 | 
			
		||||
                pw = std::min(pw, (float)layerm->flow(frPerimeter).scaled_width());
 | 
			
		||||
            // Append such thick perimeters to the areas that need support
 | 
			
		||||
            polygons_append(overhangs, offset2(perimeters, -pw, +pw));
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -153,24 +153,33 @@ SlicingParameters SlicingParameters::create_from_config(
 | 
			
		|||
    return params;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Convert layer_height_ranges to layer_height_profile. Both are referenced to z=0, meaning the raft layers are not accounted for
 | 
			
		||||
std::vector<std::pair<t_layer_height_range, coordf_t>> layer_height_ranges(const t_layer_config_ranges &config_ranges)
 | 
			
		||||
{
 | 
			
		||||
	std::vector<std::pair<t_layer_height_range, coordf_t>> out;
 | 
			
		||||
	out.reserve(config_ranges.size());
 | 
			
		||||
	for (const auto &kvp : config_ranges)
 | 
			
		||||
		out.emplace_back(kvp.first, kvp.second.option("layer_height")->getFloat());
 | 
			
		||||
	return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Convert layer_config_ranges to layer_height_profile. Both are referenced to z=0, meaning the raft layers are not accounted for
 | 
			
		||||
// 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_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_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_config_ranges::const_iterator it_range = layer_config_ranges.begin(); it_range != layer_config_ranges.end(); ++ it_range) {
 | 
			
		||||
        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.option("layer_height")->getFloat();
 | 
			
		||||
        if (! ranges_non_overlapping.empty())
 | 
			
		||||
            // Trim current low with the last high.
 | 
			
		||||
            lo = std::max(lo, ranges_non_overlapping.back().first.second);
 | 
			
		||||
| 
						 | 
				
			
			@ -219,7 +228,7 @@ std::vector<coordf_t> layer_height_profile_from_ranges(
 | 
			
		|||
// Fill layer_height_profile by heights ensuring a prescribed maximum cusp height.
 | 
			
		||||
std::vector<coordf_t> layer_height_profile_adaptive(
 | 
			
		||||
    const SlicingParameters     &slicing_params,
 | 
			
		||||
    const t_layer_height_ranges &layer_height_ranges,
 | 
			
		||||
    const t_layer_config_ranges & /* layer_config_ranges */,
 | 
			
		||||
    const ModelVolumePtrs		&volumes)
 | 
			
		||||
{
 | 
			
		||||
    // 1) Initialize the SlicingAdaptive class with the object meshes.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,6 +11,8 @@
 | 
			
		|||
 | 
			
		||||
#include "libslic3r.h"
 | 
			
		||||
#include "Utils.hpp"
 | 
			
		||||
#include "PrintConfig.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Slic3r
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -128,15 +130,17 @@ 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<std::pair<t_layer_height_range, coordf_t>> layer_height_ranges(const t_layer_config_ranges &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_config_ranges &layer_config_ranges);
 | 
			
		||||
 | 
			
		||||
extern std::vector<coordf_t> layer_height_profile_adaptive(
 | 
			
		||||
    const SlicingParameters     &slicing_params,
 | 
			
		||||
    const t_layer_height_ranges &layer_height_ranges,
 | 
			
		||||
    const t_layer_config_ranges &layer_config_ranges,
 | 
			
		||||
    const ModelVolumePtrs       &volumes);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -829,7 +829,7 @@ namespace SupportMaterialInternal {
 | 
			
		|||
        assert(expansion_scaled >= 0.f);
 | 
			
		||||
        for (const ExtrusionPath &ep : loop.paths)
 | 
			
		||||
            if (ep.role() == erOverhangPerimeter && ! ep.polyline.empty()) {
 | 
			
		||||
                float exp = 0.5f * scale_(ep.width) + expansion_scaled;
 | 
			
		||||
                float exp = 0.5f * (float)scale_(ep.width) + expansion_scaled;
 | 
			
		||||
                if (ep.is_closed()) {
 | 
			
		||||
                    if (ep.size() >= 3) {
 | 
			
		||||
                        // This is a complete loop.
 | 
			
		||||
| 
						 | 
				
			
			@ -2214,7 +2214,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf
 | 
			
		|||
        // Expand the bases of the support columns in the 1st layer.
 | 
			
		||||
        columns_base->polygons = diff(
 | 
			
		||||
            offset(columns_base->polygons, inflate_factor_1st_layer),
 | 
			
		||||
            offset(m_object->layers().front()->slices.expolygons, scale_(m_gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS));
 | 
			
		||||
            offset(m_object->layers().front()->slices.expolygons, (float)scale_(m_gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS));
 | 
			
		||||
        if (contacts != nullptr)
 | 
			
		||||
            columns_base->polygons = diff(columns_base->polygons, interface_polygons);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -3226,7 +3226,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
 | 
			
		|||
                    // TODO: use brim ordering algorithm
 | 
			
		||||
                    Polygons to_infill_polygons = to_polygons(to_infill);
 | 
			
		||||
                    // TODO: use offset2_ex()
 | 
			
		||||
                    to_infill = offset_ex(to_infill, - 0.4 * float(flow.scaled_spacing()));
 | 
			
		||||
                    to_infill = offset_ex(to_infill, - 0.4f * float(flow.scaled_spacing()));
 | 
			
		||||
                    extrusion_entities_append_paths(
 | 
			
		||||
                        base_layer.extrusions, 
 | 
			
		||||
                        to_polylines(std::move(to_infill_polygons)),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -547,9 +547,9 @@ TriangleMesh TriangleMesh::convex_hull_3d() const
 | 
			
		|||
#if REALfloat
 | 
			
		||||
	    	qhull.runQhull("", 3, (int)this->its.vertices.size(), (const realT*)(this->its.vertices.front().data()), "Qt");
 | 
			
		||||
#else
 | 
			
		||||
	    	src_vertices.reserve(this->its.vertices() * 3);
 | 
			
		||||
	    	src_vertices.reserve(this->its.vertices.size() * 3);
 | 
			
		||||
	    	// We will now fill the vector with input points for computation:
 | 
			
		||||
			for (const stl_vertex &v : ths->its.vertices.size())
 | 
			
		||||
			for (const stl_vertex &v : this->its.vertices)
 | 
			
		||||
				for (int i = 0; i < 3; ++ i)
 | 
			
		||||
		        	src_vertices.emplace_back(v(i));
 | 
			
		||||
	        qhull.runQhull("", 3, (int)src_vertices.size() / 3, src_vertices.data(), "Qt");
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -627,12 +627,12 @@ void Bed3D::render_prusa_shader(bool transparent) const
 | 
			
		|||
        if (position_id != -1)
 | 
			
		||||
        {
 | 
			
		||||
            glsafe(::glEnableVertexAttribArray(position_id));
 | 
			
		||||
            glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)m_triangles.get_position_offset()));
 | 
			
		||||
            glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)m_triangles.get_position_offset()));
 | 
			
		||||
        }
 | 
			
		||||
        if (tex_coords_id != -1)
 | 
			
		||||
        {
 | 
			
		||||
            glsafe(::glEnableVertexAttribArray(tex_coords_id));
 | 
			
		||||
            glsafe(::glVertexAttribPointer(tex_coords_id, 2, GL_FLOAT, GL_FALSE, stride, (GLvoid*)m_triangles.get_tex_coords_offset()));
 | 
			
		||||
            glsafe(::glVertexAttribPointer(tex_coords_id, 2, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)m_triangles.get_tex_coords_offset()));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)m_triangles.get_vertices_count()));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -44,8 +44,8 @@ public:
 | 
			
		|||
    const float* get_vertices_data() const;
 | 
			
		||||
    unsigned int get_vertices_data_size() const { return (unsigned int)m_vertices.size() * get_vertex_data_size(); }
 | 
			
		||||
    unsigned int get_vertex_data_size() const { return (unsigned int)(5 * sizeof(float)); }
 | 
			
		||||
    unsigned int get_position_offset() const { return 0; }
 | 
			
		||||
    unsigned int get_tex_coords_offset() const { return (unsigned int)(3 * sizeof(float)); }
 | 
			
		||||
    size_t get_position_offset() const { return 0; }
 | 
			
		||||
    size_t get_tex_coords_offset() const { return (size_t)(3 * sizeof(float)); }
 | 
			
		||||
    unsigned int get_vertices_count() const { return (unsigned int)m_vertices.size(); }
 | 
			
		||||
#else
 | 
			
		||||
    const float* get_vertices() const { return m_vertices.data(); }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -53,15 +53,12 @@ void BedShapeDialog::on_dpi_changed(const wxRect &suggested_rect)
 | 
			
		|||
 | 
			
		||||
void BedShapePanel::build_panel(ConfigOptionPoints* default_pt)
 | 
			
		||||
{
 | 
			
		||||
//  on_change(nullptr);
 | 
			
		||||
 | 
			
		||||
	auto box = new wxStaticBox(this, wxID_ANY, _(L("Shape")));
 | 
			
		||||
	auto sbsizer = new wxStaticBoxSizer(box, wxVERTICAL);
 | 
			
		||||
    auto sbsizer = new wxStaticBoxSizer(wxVERTICAL, this, _(L("Shape")));
 | 
			
		||||
 | 
			
		||||
	// shape options
 | 
			
		||||
    m_shape_options_book = new wxChoicebook(this, wxID_ANY, wxDefaultPosition, 
 | 
			
		||||
                           wxSize(25*wxGetApp().em_unit(), -1), wxCHB_TOP);
 | 
			
		||||
	sbsizer->Add(m_shape_options_book);
 | 
			
		||||
    sbsizer->Add(m_shape_options_book);
 | 
			
		||||
 | 
			
		||||
	auto optgroup = init_shape_options_page(_(L("Rectangular")));
 | 
			
		||||
		ConfigOptionDef def;
 | 
			
		||||
| 
						 | 
				
			
			@ -92,13 +89,15 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt)
 | 
			
		|||
		Line line{ "", "" };
 | 
			
		||||
		line.full_width = 1;
 | 
			
		||||
		line.widget = [this](wxWindow* parent) {
 | 
			
		||||
			auto btn = new wxButton(parent, wxID_ANY, _(L("Load shape from STL...")), wxDefaultPosition, wxDefaultSize);
 | 
			
		||||
			
 | 
			
		||||
			auto sizer = new wxBoxSizer(wxHORIZONTAL);
 | 
			
		||||
			sizer->Add(btn);
 | 
			
		||||
            auto shape_btn = new wxButton(parent, wxID_ANY, _(L("Load shape from STL...")));
 | 
			
		||||
            wxSizer* shape_sizer = new wxBoxSizer(wxHORIZONTAL);
 | 
			
		||||
            shape_sizer->Add(shape_btn, 1, wxEXPAND);
 | 
			
		||||
 | 
			
		||||
			btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e)
 | 
			
		||||
			{
 | 
			
		||||
            wxSizer* sizer = new wxBoxSizer(wxVERTICAL);
 | 
			
		||||
            sizer->Add(shape_sizer, 1, wxEXPAND);
 | 
			
		||||
 | 
			
		||||
            shape_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e)
 | 
			
		||||
            {
 | 
			
		||||
				load_stl();
 | 
			
		||||
			}));
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -106,8 +105,8 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt)
 | 
			
		|||
		};
 | 
			
		||||
		optgroup->append_line(line);
 | 
			
		||||
 | 
			
		||||
	Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent e)
 | 
			
		||||
	{
 | 
			
		||||
    Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent& e)
 | 
			
		||||
    {
 | 
			
		||||
		update_shape();
 | 
			
		||||
	}));
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -117,8 +116,8 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt)
 | 
			
		|||
 | 
			
		||||
	// main sizer
 | 
			
		||||
	auto top_sizer = new wxBoxSizer(wxHORIZONTAL);
 | 
			
		||||
	top_sizer->Add(sbsizer, 0, wxEXPAND | wxLeft | wxTOP | wxBOTTOM, 10);
 | 
			
		||||
	if (m_canvas)
 | 
			
		||||
    top_sizer->Add(sbsizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 10);
 | 
			
		||||
    if (m_canvas)
 | 
			
		||||
		top_sizer->Add(m_canvas, 1, wxEXPAND | wxALL, 10) ;
 | 
			
		||||
 | 
			
		||||
	SetSizerAndFit(top_sizer);
 | 
			
		||||
| 
						 | 
				
			
			@ -135,8 +134,7 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt)
 | 
			
		|||
// Create a panel for a rectangular / circular / custom bed shape.
 | 
			
		||||
ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(const wxString& title)
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
	auto panel = new wxPanel(m_shape_options_book);
 | 
			
		||||
    auto panel = new wxPanel(m_shape_options_book);
 | 
			
		||||
	ConfigOptionsGroupShp optgroup;
 | 
			
		||||
	optgroup = std::make_shared<ConfigOptionsGroup>(panel, _(L("Settings")));
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -234,8 +232,8 @@ void BedShapePanel::set_shape(ConfigOptionPoints* points)
 | 
			
		|||
	// This is a custom bed shape, use the polygon provided.
 | 
			
		||||
	m_shape_options_book->SetSelection(SHAPE_CUSTOM);
 | 
			
		||||
	// Copy the polygon to the canvas, make a copy of the array.
 | 
			
		||||
	m_canvas->m_bed_shape = points->values;
 | 
			
		||||
	update_shape();
 | 
			
		||||
    m_loaded_bed_shape = points->values;
 | 
			
		||||
    update_shape();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BedShapePanel::update_preview()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -113,11 +113,19 @@ wxString Field::get_tooltip_text(const wxString& default_string)
 | 
			
		|||
	wxString tooltip_text("");
 | 
			
		||||
	wxString tooltip = _(m_opt.tooltip);
 | 
			
		||||
    edit_tooltip(tooltip);
 | 
			
		||||
 | 
			
		||||
    std::string opt_id = m_opt_id;
 | 
			
		||||
    auto hash_pos = opt_id.find("#");
 | 
			
		||||
    if (hash_pos != std::string::npos) {
 | 
			
		||||
        opt_id.replace(hash_pos, 1,"[");
 | 
			
		||||
        opt_id += "]";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	if (tooltip.length() > 0)
 | 
			
		||||
        tooltip_text = tooltip + "\n" + _(L("default value")) + "\t: " +
 | 
			
		||||
        (boost::iends_with(m_opt_id, "_gcode") ? "\n" : "") + default_string +
 | 
			
		||||
        (boost::iends_with(m_opt_id, "_gcode") ? "" : "\n") + 
 | 
			
		||||
        _(L("parameter name")) + "\t: " + m_opt_id;
 | 
			
		||||
        (boost::iends_with(opt_id, "_gcode") ? "\n" : "") + default_string +
 | 
			
		||||
        (boost::iends_with(opt_id, "_gcode") ? "" : "\n") + 
 | 
			
		||||
        _(L("parameter name")) + "\t: " + opt_id;
 | 
			
		||||
 | 
			
		||||
	return tooltip_text;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -912,7 +912,8 @@ GLCanvas3D::LegendTexture::LegendTexture()
 | 
			
		|||
void GLCanvas3D::LegendTexture::fill_color_print_legend_values(const GCodePreviewData& preview_data, const GLCanvas3D& canvas, 
 | 
			
		||||
                                                               std::vector<std::pair<double, double>>& cp_legend_values)
 | 
			
		||||
{
 | 
			
		||||
    if (preview_data.extrusion.view_type == GCodePreviewData::Extrusion::ColorPrint) 
 | 
			
		||||
    if (preview_data.extrusion.view_type == GCodePreviewData::Extrusion::ColorPrint && 
 | 
			
		||||
        wxGetApp().extruders_edited_cnt() == 1) // show color change legend only for single-material presets
 | 
			
		||||
    {
 | 
			
		||||
        auto& config = wxGetApp().preset_bundle->project_config;
 | 
			
		||||
        const std::vector<double>& color_print_values = config.option<ConfigOptionFloats>("colorprint_heights")->values;
 | 
			
		||||
| 
						 | 
				
			
			@ -3326,6 +3327,12 @@ void GLCanvas3D::handle_sidebar_focus_event(const std::string& opt_key, bool foc
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLCanvas3D::handle_layers_data_focus_event(const t_layer_height_range range, const EditorType type)
 | 
			
		||||
{
 | 
			
		||||
    std::string field = "layer_" + std::to_string(type) + "_" + std::to_string(range.first) + "_" + std::to_string(range.second);
 | 
			
		||||
    handle_sidebar_focus_event(field, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLCanvas3D::update_ui_from_settings()
 | 
			
		||||
{
 | 
			
		||||
    m_camera.set_type(wxGetApp().app_config->get("use_perspective_camera"));
 | 
			
		||||
| 
						 | 
				
			
			@ -4264,12 +4271,9 @@ void GLCanvas3D::_render_sla_slices() const
 | 
			
		|||
 | 
			
		||||
void GLCanvas3D::_render_selection_sidebar_hints() const
 | 
			
		||||
{
 | 
			
		||||
    m_shader.start_using();
 | 
			
		||||
    m_selection.render_sidebar_hints(m_sidebar_field);
 | 
			
		||||
    m_shader.stop_using();
 | 
			
		||||
    m_selection.render_sidebar_hints(m_sidebar_field, m_shader);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void GLCanvas3D::_update_volumes_hover_state() const
 | 
			
		||||
{
 | 
			
		||||
    for (GLVolume* v : m_volumes.volumes)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,6 +12,7 @@
 | 
			
		|||
#include "Camera.hpp"
 | 
			
		||||
#include "Selection.hpp"
 | 
			
		||||
#include "Gizmos/GLGizmosManager.hpp"
 | 
			
		||||
#include "GUI_ObjectLayers.hpp"
 | 
			
		||||
 | 
			
		||||
#include <float.h>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -607,6 +608,7 @@ public:
 | 
			
		|||
    void reset_all_gizmos() { m_gizmos.reset_all_states(); }
 | 
			
		||||
 | 
			
		||||
    void handle_sidebar_focus_event(const std::string& opt_key, bool focus_on);
 | 
			
		||||
    void handle_layers_data_focus_event(const t_layer_height_range range, const EditorType type);
 | 
			
		||||
 | 
			
		||||
    void update_ui_from_settings();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -934,6 +934,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_;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -156,6 +156,7 @@ public:
 | 
			
		|||
    ObjectManipulation* obj_manipul();
 | 
			
		||||
    ObjectSettings*     obj_settings();
 | 
			
		||||
    ObjectList*         obj_list();
 | 
			
		||||
    ObjectLayers*       obj_layers();
 | 
			
		||||
    Plater*             plater();
 | 
			
		||||
    std::vector<ModelObject*> *model_objects();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										341
									
								
								src/slic3r/GUI/GUI_ObjectLayers.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										341
									
								
								src/slic3r/GUI/GUI_ObjectLayers.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,341 @@
 | 
			
		|||
#include "GUI_ObjectLayers.hpp"
 | 
			
		||||
#include "GUI_ObjectList.hpp"
 | 
			
		||||
 | 
			
		||||
#include "OptionsGroup.hpp"
 | 
			
		||||
#include "PresetBundle.hpp"
 | 
			
		||||
#include "libslic3r/Model.hpp"
 | 
			
		||||
#include "GLCanvas3D.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_data = [range, this](const EditorType type)
 | 
			
		||||
    {
 | 
			
		||||
        m_selectable_range = range;
 | 
			
		||||
        m_selection_type = type;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    auto update_focus_data = [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(this, double_to_string(range.first), etMinZ,
 | 
			
		||||
                                       set_focus_data, [range, update_focus_data, this](coordf_t min_z, bool enter_pressed)
 | 
			
		||||
    {
 | 
			
		||||
        if (fabs(min_z - range.first) < EPSILON) {
 | 
			
		||||
            m_selection_type = etUndef;
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // data for next focusing
 | 
			
		||||
        coordf_t max_z = min_z < range.second ? range.second : min_z + 0.5;
 | 
			
		||||
        const t_layer_height_range& new_range = { min_z, max_z };
 | 
			
		||||
        update_focus_data(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(this, double_to_string(range.second), etMaxZ,
 | 
			
		||||
                                  set_focus_data, [range, update_focus_data, 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 };
 | 
			
		||||
        update_focus_data(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(this,
 | 
			
		||||
                                double_to_string(m_object->layer_config_ranges[range].option("layer_height")->getFloat()),
 | 
			
		||||
                             etLayerHeight, set_focus_data, [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::update_scene_from_editor_selection() const
 | 
			
		||||
{
 | 
			
		||||
    // needed to show the visual hints in 3D scene
 | 
			
		||||
    wxGetApp().plater()->canvas3D()->handle_layers_data_focus_event(m_selectable_range, m_selection_type);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectLayers::reset_selection()
 | 
			
		||||
{
 | 
			
		||||
    m_selectable_range = { 0.0, 0.0 };
 | 
			
		||||
    m_selection_type = etLayerHeight;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
LayerRangeEditor::LayerRangeEditor( ObjectLayers* parent,
 | 
			
		||||
                                    const wxString& value,
 | 
			
		||||
                                    EditorType type,
 | 
			
		||||
                                    std::function<void(EditorType)> set_focus_data_fn,
 | 
			
		||||
                                    std::function<bool(coordf_t, bool enter_pressed)>   edit_fn
 | 
			
		||||
                                    ) :
 | 
			
		||||
    m_valid_value(value),
 | 
			
		||||
    m_type(type),
 | 
			
		||||
    m_set_focus_data(set_focus_data_fn),
 | 
			
		||||
    wxTextCtrl(parent->m_parent, wxID_ANY, value, wxDefaultPosition, 
 | 
			
		||||
               wxSize(8 * em_unit(parent->m_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_data();
 | 
			
		||||
#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());
 | 
			
		||||
 | 
			
		||||
    this->Bind(wxEVT_SET_FOCUS, [this, parent](wxFocusEvent& e)
 | 
			
		||||
    {
 | 
			
		||||
        set_focus_data();
 | 
			
		||||
        parent->update_scene_from_editor_selection();
 | 
			
		||||
        e.Skip();
 | 
			
		||||
    }, this->GetId());
 | 
			
		||||
 | 
			
		||||
#ifdef __WXGTK__ // Workaround! To take information about selectable range
 | 
			
		||||
    this->Bind(wxEVT_LEFT_DOWN, [this](wxEvent& e)
 | 
			
		||||
    {
 | 
			
		||||
        set_focus_data();
 | 
			
		||||
        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 
 | 
			
		||||
							
								
								
									
										88
									
								
								src/slic3r/GUI/GUI_ObjectLayers.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								src/slic3r/GUI/GUI_ObjectLayers.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,88 @@
 | 
			
		|||
#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;
 | 
			
		||||
 | 
			
		||||
class ObjectLayers;
 | 
			
		||||
 | 
			
		||||
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_data;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    LayerRangeEditor(   ObjectLayers* parent,
 | 
			
		||||
                        const wxString& value = wxEmptyString,
 | 
			
		||||
                        EditorType type = etUndef,
 | 
			
		||||
                        std::function<void(EditorType)>     set_focus_data_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_data() const { m_set_focus_data(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        update_scene_from_editor_selection() const;
 | 
			
		||||
 | 
			
		||||
    void        UpdateAndShow(const bool show) override;
 | 
			
		||||
    void        msw_rescale();
 | 
			
		||||
    void        reset_selection();
 | 
			
		||||
    void        set_selectable_range(const t_layer_height_range& range) { m_selectable_range = range; }
 | 
			
		||||
 | 
			
		||||
    friend class LayerRangeEditor;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}}
 | 
			
		||||
 | 
			
		||||
#endif // slic3r_GUI_ObjectLayers_hpp_
 | 
			
		||||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			@ -569,9 +578,75 @@ void ObjectList::selection_changed()
 | 
			
		|||
        wxPostEvent(this, event);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (const wxDataViewItem item = GetSelection())
 | 
			
		||||
    {
 | 
			
		||||
        const ItemType type = m_objects_model->GetItemType(item);
 | 
			
		||||
        // to correct visual hints for layers editing on the Scene
 | 
			
		||||
        if (type & (itLayer|itLayerRoot)) {
 | 
			
		||||
            wxGetApp().obj_layers()->reset_selection();
 | 
			
		||||
            
 | 
			
		||||
            if (type & itLayerRoot)
 | 
			
		||||
                wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false);
 | 
			
		||||
            else {
 | 
			
		||||
                wxGetApp().obj_layers()->set_selectable_range(m_objects_model->GetLayerRangeByItem(item));
 | 
			
		||||
                wxGetApp().obj_layers()->update_scene_from_editor_selection();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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 +728,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 +774,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 +789,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 +819,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 +1125,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 +1239,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 +1409,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 +1442,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();
 | 
			
		||||
| 
						 | 
				
			
			@ -1576,38 +1688,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)
 | 
			
		||||
| 
						 | 
				
			
			@ -1624,6 +1750,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)
 | 
			
		||||
| 
						 | 
				
			
			@ -1719,6 +1863,70 @@ 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, 2.0f }] = get_default_layer_config(obj_idx);
 | 
			
		||||
 | 
			
		||||
        // create layer root item
 | 
			
		||||
        layers_item = add_layer_root_item(obj_item);
 | 
			
		||||
    }
 | 
			
		||||
    if (!layers_item.IsOk())
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    // to correct visual hints for layers editing on the Scene, reset previous selection
 | 
			
		||||
    wxGetApp().obj_layers()->reset_selection();
 | 
			
		||||
    wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false);
 | 
			
		||||
 | 
			
		||||
    // 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();
 | 
			
		||||
| 
						 | 
				
			
			@ -1788,6 +1996,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();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1810,36 +2019,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);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -1859,11 +2079,18 @@ 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().plater()->canvas3D()->handle_sidebar_focus_event("", false);
 | 
			
		||||
    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();
 | 
			
		||||
| 
						 | 
				
			
			@ -1909,6 +2136,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__
 | 
			
		||||
| 
						 | 
				
			
			@ -2053,16 +2283,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 + 2.0f };
 | 
			
		||||
        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()
 | 
			
		||||
| 
						 | 
				
			
			@ -2092,11 +2502,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()));
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -2217,22 +2628,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
 | 
			
		||||
| 
						 | 
				
			
			@ -2240,7 +2647,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);
 | 
			
		||||
| 
						 | 
				
			
			@ -2303,11 +2710,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);
 | 
			
		||||
| 
						 | 
				
			
			@ -2326,8 +2735,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
 | 
			
		||||
| 
						 | 
				
			
			@ -2338,33 +2748,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" +
 | 
			
		||||
| 
						 | 
				
			
			@ -2401,7 +2815,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());
 | 
			
		||||
| 
						 | 
				
			
			@ -2410,8 +2824,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
 | 
			
		||||
| 
						 | 
				
			
			@ -2576,6 +2992,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);
 | 
			
		||||
| 
						 | 
				
			
			@ -2749,7 +3246,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();
 | 
			
		||||
| 
						 | 
				
			
			@ -2862,5 +3360,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 
 | 
			
		||||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
                }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -541,6 +541,26 @@ void Preview::on_checkbox_shells(wxCommandEvent& evt)
 | 
			
		|||
    refresh_print();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Preview::update_view_type()
 | 
			
		||||
{
 | 
			
		||||
    const DynamicPrintConfig& config = wxGetApp().preset_bundle->project_config;
 | 
			
		||||
 | 
			
		||||
    const wxString& choice = !config.option<ConfigOptionFloats>("colorprint_heights")->values.empty() && 
 | 
			
		||||
                             wxGetApp().extruders_edited_cnt()==1 ? 
 | 
			
		||||
                                _(L("Color Print")) :
 | 
			
		||||
                                config.option<ConfigOptionFloats>("wiping_volumes_matrix")->values.size() > 1 ?
 | 
			
		||||
                                    _(L("Tool")) : 
 | 
			
		||||
                                    _(L("Feature type"));
 | 
			
		||||
 | 
			
		||||
    int type = m_choice_view_type->FindString(choice);
 | 
			
		||||
    if (m_choice_view_type->GetSelection() != type) {
 | 
			
		||||
        m_choice_view_type->SetSelection(type);
 | 
			
		||||
        if (0 <= type && type < (int)GCodePreviewData::Extrusion::Num_View_Types)
 | 
			
		||||
            m_gcode_preview_data->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)type;
 | 
			
		||||
        m_preferred_color_mode = "feature";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Preview::create_double_slider()
 | 
			
		||||
{
 | 
			
		||||
    m_slider = new DoubleSlider(this, wxID_ANY, 0, 0, 0, 100);
 | 
			
		||||
| 
						 | 
				
			
			@ -553,23 +573,13 @@ void Preview::create_double_slider()
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
    Bind(wxCUSTOMEVT_TICKSCHANGED, [this](wxEvent&) {
 | 
			
		||||
            auto& config = wxGetApp().preset_bundle->project_config;
 | 
			
		||||
            ((config.option<ConfigOptionFloats>("colorprint_heights"))->values) = (m_slider->GetTicksValues());
 | 
			
		||||
            m_schedule_background_process();
 | 
			
		||||
        wxGetApp().preset_bundle->project_config.option<ConfigOptionFloats>("colorprint_heights")->values = m_slider->GetTicksValues();
 | 
			
		||||
        m_schedule_background_process();
 | 
			
		||||
 | 
			
		||||
            const wxString& choise = !config.option<ConfigOptionFloats>("colorprint_heights")->values.empty() ? _(L("Color Print")) :
 | 
			
		||||
                                      config.option<ConfigOptionFloats>("wiping_volumes_matrix")->values.size() > 1  ? 
 | 
			
		||||
                                      _(L("Tool")) : _(L("Feature type"));
 | 
			
		||||
        update_view_type();
 | 
			
		||||
 | 
			
		||||
            int type = m_choice_view_type->FindString(choise);
 | 
			
		||||
            if (m_choice_view_type->GetSelection() != type) {
 | 
			
		||||
                m_choice_view_type->SetSelection(type);
 | 
			
		||||
                if ((0 <= type) && (type < (int)GCodePreviewData::Extrusion::Num_View_Types))
 | 
			
		||||
                    m_gcode_preview_data->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)type;
 | 
			
		||||
                m_preferred_color_mode = "feature";
 | 
			
		||||
            }
 | 
			
		||||
            reload_print();
 | 
			
		||||
        });
 | 
			
		||||
        reload_print();
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Find an index of a value in a sorted vector, which is in <z-eps, z+eps>.
 | 
			
		||||
| 
						 | 
				
			
			@ -787,9 +797,14 @@ void Preview::load_print_as_fff(bool keep_z_range)
 | 
			
		|||
            // Load the real G-code preview.
 | 
			
		||||
            m_canvas->load_gcode_preview(*m_gcode_preview_data, colors);
 | 
			
		||||
            m_loaded = true;
 | 
			
		||||
        } else
 | 
			
		||||
        } else {
 | 
			
		||||
            // disable color change information for multi-material presets
 | 
			
		||||
            if (wxGetApp().extruders_edited_cnt() > 1)
 | 
			
		||||
                color_print_values.clear();
 | 
			
		||||
 | 
			
		||||
            // Load the initial preview based on slices, not the final G-code.
 | 
			
		||||
            m_canvas->load_preview(colors, color_print_values);
 | 
			
		||||
        }
 | 
			
		||||
        show_hide_ui_elements(gcode_preview_data_valid ? "full" : "simple");
 | 
			
		||||
        // recalculates zs and update sliders accordingly
 | 
			
		||||
        std::vector<double> zs = m_canvas->get_current_print_zs(true);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -127,6 +127,8 @@ public:
 | 
			
		|||
    void move_double_slider(wxKeyEvent& evt);
 | 
			
		||||
    void edit_double_slider(wxKeyEvent& evt);
 | 
			
		||||
 | 
			
		||||
    void update_view_type();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    bool init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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), 
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -50,6 +50,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"
 | 
			
		||||
| 
						 | 
				
			
			@ -618,6 +619,7 @@ struct Sidebar::priv
 | 
			
		|||
    ObjectList          *object_list{ nullptr };
 | 
			
		||||
    ObjectManipulation  *object_manipulation{ nullptr };
 | 
			
		||||
    ObjectSettings      *object_settings{ nullptr };
 | 
			
		||||
    ObjectLayers        *object_layers{ nullptr };
 | 
			
		||||
    ObjectInfo *object_info;
 | 
			
		||||
    SlicedInfo *sliced_info;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -641,6 +643,9 @@ Sidebar::priv::~priv()
 | 
			
		|||
 | 
			
		||||
    if (frequently_changed_parameters != nullptr)
 | 
			
		||||
        delete frequently_changed_parameters;
 | 
			
		||||
 | 
			
		||||
    if (object_layers != nullptr)
 | 
			
		||||
        delete object_layers;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Sidebar::priv::show_preset_comboboxes()
 | 
			
		||||
| 
						 | 
				
			
			@ -749,6 +754,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);
 | 
			
		||||
| 
						 | 
				
			
			@ -942,6 +952,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();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -963,6 +974,11 @@ ObjectSettings* Sidebar::obj_settings()
 | 
			
		|||
    return p->object_settings;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ObjectLayers* Sidebar::obj_layers()
 | 
			
		||||
{
 | 
			
		||||
    return p->object_layers;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
wxScrolledWindow* Sidebar::scrolled_panel()
 | 
			
		||||
{
 | 
			
		||||
    return p->scrolled;
 | 
			
		||||
| 
						 | 
				
			
			@ -2177,9 +2193,6 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs &mode
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        object->ensure_on_bed();
 | 
			
		||||
 | 
			
		||||
        // print.auto_assign_extruders(object);
 | 
			
		||||
        // print.add_model_object(object);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
#ifdef AUTOPLACEMENT_ON_LOAD
 | 
			
		||||
| 
						 | 
				
			
			@ -2949,8 +2962,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)
 | 
			
		||||
| 
						 | 
				
			
			@ -3295,6 +3314,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;
 | 
			
		||||
| 
						 | 
				
			
			@ -3947,6 +3970,9 @@ void Plater::reslice()
 | 
			
		|||
    }
 | 
			
		||||
    else if (!p->background_process.empty() && !p->background_process.idle())
 | 
			
		||||
        p->show_action_buttons(true);
 | 
			
		||||
 | 
			
		||||
    // update type of preview
 | 
			
		||||
    p->preview->update_view_type();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Plater::reslice_SLA_supports(const ModelObject &object)
 | 
			
		||||
| 
						 | 
				
			
			@ -4250,6 +4276,11 @@ void Plater::msw_rescale()
 | 
			
		|||
    GetParent()->Layout();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const Camera& Plater::get_camera() const
 | 
			
		||||
{
 | 
			
		||||
    return p->camera;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Plater::can_delete() const { return p->can_delete(); }
 | 
			
		||||
bool Plater::can_delete_all() const { return p->can_delete_all(); }
 | 
			
		||||
bool Plater::can_increase_instances() const { return p->can_increase_instances(); }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -218,6 +220,8 @@ public:
 | 
			
		|||
 | 
			
		||||
    void msw_rescale();
 | 
			
		||||
 | 
			
		||||
    const Camera& get_camera() const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    struct priv;
 | 
			
		||||
    std::unique_ptr<priv> p;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -824,11 +824,25 @@ const Preset* PresetCollection::get_selected_preset_parent() const
 | 
			
		|||
    if (this->get_selected_idx() == -1)
 | 
			
		||||
        // This preset collection has no preset activated yet. Only the get_edited_preset() is valid.
 | 
			
		||||
        return nullptr;
 | 
			
		||||
    const std::string &inherits = this->get_edited_preset().inherits();
 | 
			
		||||
//    const std::string &inherits = this->get_edited_preset().inherits();
 | 
			
		||||
//    if (inherits.empty())
 | 
			
		||||
//		return this->get_selected_preset().is_system ? &this->get_selected_preset() : nullptr; 
 | 
			
		||||
 | 
			
		||||
    std::string inherits = this->get_edited_preset().inherits();
 | 
			
		||||
    if (inherits.empty())
 | 
			
		||||
		return this->get_selected_preset().is_system ? &this->get_selected_preset() : nullptr; 
 | 
			
		||||
    {
 | 
			
		||||
		if (this->get_selected_preset().is_system || this->get_selected_preset().is_default) 
 | 
			
		||||
            return &this->get_selected_preset();
 | 
			
		||||
        if (this->get_selected_preset().is_external)
 | 
			
		||||
            return nullptr;
 | 
			
		||||
        
 | 
			
		||||
        inherits = m_type != Preset::Type::TYPE_PRINTER ? "- default -" :
 | 
			
		||||
                   this->get_edited_preset().printer_technology() == ptFFF ? 
 | 
			
		||||
                   "- default FFF -" : "- default SLA -" ;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const Preset* preset = this->find_preset(inherits, false);
 | 
			
		||||
    return (preset == nullptr || preset->is_default || preset->is_external) ? nullptr : preset;
 | 
			
		||||
    return (preset == nullptr/* || preset->is_default*/ || preset->is_external) ? nullptr : preset;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const Preset* PresetCollection::get_preset_parent(const Preset& child) const
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,8 @@
 | 
			
		|||
#include "GUI_ObjectManipulation.hpp"
 | 
			
		||||
#include "GUI_ObjectList.hpp"
 | 
			
		||||
#include "Gizmos/GLGizmoBase.hpp"
 | 
			
		||||
#include "slic3r/GUI/3DScene.hpp"
 | 
			
		||||
#include "3DScene.hpp"
 | 
			
		||||
#include "Camera.hpp"
 | 
			
		||||
 | 
			
		||||
#include <GL/glew.h>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -331,6 +332,9 @@ void Selection::clear()
 | 
			
		|||
 | 
			
		||||
    // resets the cache in the sidebar
 | 
			
		||||
    wxGetApp().obj_manipul()->reset_cache();
 | 
			
		||||
 | 
			
		||||
    // #et_FIXME fake KillFocus from sidebar
 | 
			
		||||
    wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Update the selection based on the new instance IDs.
 | 
			
		||||
| 
						 | 
				
			
			@ -1070,61 +1074,68 @@ void Selection::render_center(bool gizmo_is_dragging) const
 | 
			
		|||
}
 | 
			
		||||
#endif // ENABLE_RENDER_SELECTION_CENTER
 | 
			
		||||
 | 
			
		||||
void Selection::render_sidebar_hints(const std::string& sidebar_field) const
 | 
			
		||||
void Selection::render_sidebar_hints(const std::string& sidebar_field, const Shader& shader) const
 | 
			
		||||
{
 | 
			
		||||
    if (sidebar_field.empty())
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
 | 
			
		||||
    glsafe(::glEnable(GL_DEPTH_TEST));
 | 
			
		||||
    if (!boost::starts_with(sidebar_field, "layer"))
 | 
			
		||||
    {
 | 
			
		||||
        shader.start_using();
 | 
			
		||||
        glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
 | 
			
		||||
        glsafe(::glEnable(GL_LIGHTING));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    glsafe(::glEnable(GL_LIGHTING));
 | 
			
		||||
    glsafe(::glEnable(GL_DEPTH_TEST));
 | 
			
		||||
 | 
			
		||||
    glsafe(::glPushMatrix());
 | 
			
		||||
 | 
			
		||||
    const Vec3d& center = get_bounding_box().center();
 | 
			
		||||
 | 
			
		||||
    if (is_single_full_instance() && ! wxGetApp().obj_manipul()->get_world_coordinates())
 | 
			
		||||
    if (!boost::starts_with(sidebar_field, "layer"))
 | 
			
		||||
    {
 | 
			
		||||
        glsafe(::glTranslated(center(0), center(1), center(2)));
 | 
			
		||||
        if (!boost::starts_with(sidebar_field, "position"))
 | 
			
		||||
        const Vec3d& center = get_bounding_box().center();
 | 
			
		||||
 | 
			
		||||
        if (is_single_full_instance() && !wxGetApp().obj_manipul()->get_world_coordinates())
 | 
			
		||||
        {
 | 
			
		||||
            Transform3d orient_matrix = Transform3d::Identity();
 | 
			
		||||
            if (boost::starts_with(sidebar_field, "scale"))
 | 
			
		||||
                orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
 | 
			
		||||
            else if (boost::starts_with(sidebar_field, "rotation"))
 | 
			
		||||
            glsafe(::glTranslated(center(0), center(1), center(2)));
 | 
			
		||||
            if (!boost::starts_with(sidebar_field, "position"))
 | 
			
		||||
            {
 | 
			
		||||
                if (boost::ends_with(sidebar_field, "x"))
 | 
			
		||||
                Transform3d orient_matrix = Transform3d::Identity();
 | 
			
		||||
                if (boost::starts_with(sidebar_field, "scale"))
 | 
			
		||||
                    orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
 | 
			
		||||
                else if (boost::ends_with(sidebar_field, "y"))
 | 
			
		||||
                else if (boost::starts_with(sidebar_field, "rotation"))
 | 
			
		||||
                {
 | 
			
		||||
                    const Vec3d& rotation = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation();
 | 
			
		||||
                    if (rotation(0) == 0.0)
 | 
			
		||||
                    if (boost::ends_with(sidebar_field, "x"))
 | 
			
		||||
                        orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
 | 
			
		||||
                    else
 | 
			
		||||
                        orient_matrix.rotate(Eigen::AngleAxisd(rotation(2), Vec3d::UnitZ()));
 | 
			
		||||
                    else if (boost::ends_with(sidebar_field, "y"))
 | 
			
		||||
                    {
 | 
			
		||||
                        const Vec3d& rotation = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation();
 | 
			
		||||
                        if (rotation(0) == 0.0)
 | 
			
		||||
                            orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
 | 
			
		||||
                        else
 | 
			
		||||
                            orient_matrix.rotate(Eigen::AngleAxisd(rotation(2), Vec3d::UnitZ()));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                glsafe(::glMultMatrixd(orient_matrix.data()));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else if (is_single_volume() || is_single_modifier())
 | 
			
		||||
        {
 | 
			
		||||
            glsafe(::glTranslated(center(0), center(1), center(2)));
 | 
			
		||||
            Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
 | 
			
		||||
            if (!boost::starts_with(sidebar_field, "position"))
 | 
			
		||||
                orient_matrix = orient_matrix * (*m_volumes)[*m_list.begin()]->get_volume_transformation().get_matrix(true, false, true, true);
 | 
			
		||||
 | 
			
		||||
            glsafe(::glMultMatrixd(orient_matrix.data()));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    else if (is_single_volume() || is_single_modifier())
 | 
			
		||||
    {
 | 
			
		||||
        glsafe(::glTranslated(center(0), center(1), center(2)));
 | 
			
		||||
        Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
 | 
			
		||||
        if (!boost::starts_with(sidebar_field, "position"))
 | 
			
		||||
            orient_matrix = orient_matrix * (*m_volumes)[*m_list.begin()]->get_volume_transformation().get_matrix(true, false, true, true);
 | 
			
		||||
 | 
			
		||||
        glsafe(::glMultMatrixd(orient_matrix.data()));
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        glsafe(::glTranslated(center(0), center(1), center(2)));
 | 
			
		||||
        if (requires_local_axes())
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
 | 
			
		||||
            glsafe(::glMultMatrixd(orient_matrix.data()));
 | 
			
		||||
            glsafe(::glTranslated(center(0), center(1), center(2)));
 | 
			
		||||
            if (requires_local_axes())
 | 
			
		||||
            {
 | 
			
		||||
                Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
 | 
			
		||||
                glsafe(::glMultMatrixd(orient_matrix.data()));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1136,10 +1147,16 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) const
 | 
			
		|||
        render_sidebar_scale_hints(sidebar_field);
 | 
			
		||||
    else if (boost::starts_with(sidebar_field, "size"))
 | 
			
		||||
        render_sidebar_size_hints(sidebar_field);
 | 
			
		||||
    else if (boost::starts_with(sidebar_field, "layer"))
 | 
			
		||||
        render_sidebar_layers_hints(sidebar_field);
 | 
			
		||||
 | 
			
		||||
    glsafe(::glPopMatrix());
 | 
			
		||||
 | 
			
		||||
    glsafe(::glDisable(GL_LIGHTING));
 | 
			
		||||
    if (!boost::starts_with(sidebar_field, "layer"))
 | 
			
		||||
    {
 | 
			
		||||
        glsafe(::glDisable(GL_LIGHTING));
 | 
			
		||||
        shader.stop_using();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Selection::requires_local_axes() const
 | 
			
		||||
| 
						 | 
				
			
			@ -1163,7 +1180,7 @@ 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_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;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1709,6 +1726,78 @@ void Selection::render_sidebar_size_hints(const std::string& sidebar_field) cons
 | 
			
		|||
    render_sidebar_scale_hints(sidebar_field);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) const
 | 
			
		||||
{
 | 
			
		||||
    static const double Margin = 10.0;
 | 
			
		||||
 | 
			
		||||
    std::string field = sidebar_field;
 | 
			
		||||
 | 
			
		||||
    // extract max_z
 | 
			
		||||
    std::string::size_type pos = field.rfind("_");
 | 
			
		||||
    if (pos == std::string::npos)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    double max_z = std::stod(field.substr(pos + 1));
 | 
			
		||||
 | 
			
		||||
    // extract min_z
 | 
			
		||||
    field = field.substr(0, pos);
 | 
			
		||||
    pos = field.rfind("_");
 | 
			
		||||
    if (pos == std::string::npos)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    double min_z = std::stod(field.substr(pos + 1));
 | 
			
		||||
 | 
			
		||||
    // extract type
 | 
			
		||||
    field = field.substr(0, pos);
 | 
			
		||||
    pos = field.rfind("_");
 | 
			
		||||
    if (pos == std::string::npos)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    int type = std::stoi(field.substr(pos + 1));
 | 
			
		||||
 | 
			
		||||
    const BoundingBoxf3& box = get_bounding_box();
 | 
			
		||||
 | 
			
		||||
    const float min_x = box.min(0) - Margin;
 | 
			
		||||
    const float max_x = box.max(0) + Margin;
 | 
			
		||||
    const float min_y = box.min(1) - Margin;
 | 
			
		||||
    const float max_y = box.max(1) + Margin;
 | 
			
		||||
 | 
			
		||||
    // view dependend order of rendering to keep correct transparency
 | 
			
		||||
    bool camera_on_top = wxGetApp().plater()->get_camera().get_theta() <= 90.0f;
 | 
			
		||||
    float z1 = camera_on_top ? min_z : max_z;
 | 
			
		||||
    float z2 = camera_on_top ? max_z : min_z;
 | 
			
		||||
 | 
			
		||||
    glsafe(::glEnable(GL_DEPTH_TEST));
 | 
			
		||||
    glsafe(::glDisable(GL_CULL_FACE));
 | 
			
		||||
    glsafe(::glEnable(GL_BLEND));
 | 
			
		||||
    glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
 | 
			
		||||
 | 
			
		||||
    ::glBegin(GL_QUADS);
 | 
			
		||||
    if ((camera_on_top && (type == 1)) || (!camera_on_top && (type == 2)))
 | 
			
		||||
        ::glColor4f(1.0f, 0.38f, 0.0f, 1.0f);
 | 
			
		||||
    else
 | 
			
		||||
        ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f);
 | 
			
		||||
    ::glVertex3f(min_x, min_y, z1);
 | 
			
		||||
    ::glVertex3f(max_x, min_y, z1);
 | 
			
		||||
    ::glVertex3f(max_x, max_y, z1);
 | 
			
		||||
    ::glVertex3f(min_x, max_y, z1);
 | 
			
		||||
    glsafe(::glEnd());
 | 
			
		||||
 | 
			
		||||
    ::glBegin(GL_QUADS);
 | 
			
		||||
    if ((camera_on_top && (type == 2)) || (!camera_on_top && (type == 1)))
 | 
			
		||||
        ::glColor4f(1.0f, 0.38f, 0.0f, 1.0f);
 | 
			
		||||
    else
 | 
			
		||||
        ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f);
 | 
			
		||||
    ::glVertex3f(min_x, min_y, z2);
 | 
			
		||||
    ::glVertex3f(max_x, min_y, z2);
 | 
			
		||||
    ::glVertex3f(max_x, max_y, z2);
 | 
			
		||||
    ::glVertex3f(min_x, max_y, z2);
 | 
			
		||||
    glsafe(::glEnd());
 | 
			
		||||
 | 
			
		||||
    glsafe(::glEnable(GL_CULL_FACE));
 | 
			
		||||
    glsafe(::glDisable(GL_BLEND));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Selection::render_sidebar_position_hint(Axis axis) const
 | 
			
		||||
{
 | 
			
		||||
    m_arrow.set_color(AXES_COLOR[axis], 3);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,8 +11,8 @@ typedef class GLUquadric GLUquadricObj;
 | 
			
		|||
#endif // ENABLE_RENDER_SELECTION_CENTER
 | 
			
		||||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
class Shader;
 | 
			
		||||
namespace GUI {
 | 
			
		||||
 | 
			
		||||
class TransformationType
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
| 
						 | 
				
			
			@ -302,7 +302,7 @@ public:
 | 
			
		|||
#if ENABLE_RENDER_SELECTION_CENTER
 | 
			
		||||
    void render_center(bool gizmo_is_dragging) const;
 | 
			
		||||
#endif // ENABLE_RENDER_SELECTION_CENTER
 | 
			
		||||
    void render_sidebar_hints(const std::string& sidebar_field) const;
 | 
			
		||||
    void render_sidebar_hints(const std::string& sidebar_field, const Shader& shader) const;
 | 
			
		||||
 | 
			
		||||
    bool requires_local_axes() const;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -332,6 +332,7 @@ private:
 | 
			
		|||
    void render_sidebar_rotation_hints(const std::string& sidebar_field) const;
 | 
			
		||||
    void render_sidebar_scale_hints(const std::string& sidebar_field) const;
 | 
			
		||||
    void render_sidebar_size_hints(const std::string& sidebar_field) const;
 | 
			
		||||
    void render_sidebar_layers_hints(const std::string& sidebar_field) const;
 | 
			
		||||
    void render_sidebar_position_hint(Axis axis) const;
 | 
			
		||||
    void render_sidebar_rotation_hint(Axis axis) const;
 | 
			
		||||
    void render_sidebar_scale_hint(Axis axis) const;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -423,7 +423,7 @@ void Tab::update_changed_ui()
 | 
			
		|||
		const ScalableBitmap *sys_icon =	&m_bmp_value_lock;
 | 
			
		||||
		const ScalableBitmap *icon =		&m_bmp_value_revert;
 | 
			
		||||
 | 
			
		||||
		const wxColour *color =		&m_sys_label_clr;
 | 
			
		||||
		const wxColour *color =		m_is_default_preset ? &m_default_text_clr : &m_sys_label_clr;
 | 
			
		||||
 | 
			
		||||
		const wxString *sys_tt =	&m_tt_value_lock;
 | 
			
		||||
		const wxString *tt =		&m_tt_value_revert;
 | 
			
		||||
| 
						 | 
				
			
			@ -590,7 +590,7 @@ void Tab::update_changed_tree_ui()
 | 
			
		|||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			const wxColor *clr = sys_page		?	&m_sys_label_clr :
 | 
			
		||||
			const wxColor *clr = sys_page		?	(m_is_default_preset ? &m_default_text_clr : &m_sys_label_clr) :
 | 
			
		||||
								 modified_page	?	&m_modified_label_clr : 
 | 
			
		||||
													&m_default_text_clr;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2584,11 +2584,14 @@ void Tab::load_current_preset()
 | 
			
		|||
    // Reload preset pages with the new configuration values.
 | 
			
		||||
    reload_config();
 | 
			
		||||
 | 
			
		||||
	m_bmp_non_system = m_presets->get_selected_preset_parent() ? &m_bmp_value_unlock : &m_bmp_white_bullet;
 | 
			
		||||
	m_ttg_non_system = m_presets->get_selected_preset_parent() ? &m_ttg_value_unlock : &m_ttg_white_bullet_ns;
 | 
			
		||||
	m_tt_non_system = m_presets->get_selected_preset_parent()  ? &m_tt_value_unlock  : &m_ttg_white_bullet_ns;
 | 
			
		||||
    const Preset* selected_preset_parent = m_presets->get_selected_preset_parent();
 | 
			
		||||
    m_is_default_preset = selected_preset_parent != nullptr && selected_preset_parent->is_default;
 | 
			
		||||
 | 
			
		||||
	m_undo_to_sys_btn->Enable(!preset.is_default);
 | 
			
		||||
	m_bmp_non_system = selected_preset_parent ? &m_bmp_value_unlock : &m_bmp_white_bullet;
 | 
			
		||||
	m_ttg_non_system = selected_preset_parent ? &m_ttg_value_unlock : &m_ttg_white_bullet_ns;
 | 
			
		||||
	m_tt_non_system  = selected_preset_parent ? &m_tt_value_unlock  : &m_ttg_white_bullet_ns;
 | 
			
		||||
 | 
			
		||||
//	m_undo_to_sys_btn->Enable(!preset.is_default);
 | 
			
		||||
 | 
			
		||||
#if 0
 | 
			
		||||
	// use CallAfter because some field triggers schedule on_change calls using CallAfter,
 | 
			
		||||
| 
						 | 
				
			
			@ -3174,18 +3177,18 @@ void Tab::fill_icon_descriptions()
 | 
			
		|||
{
 | 
			
		||||
	m_icon_descriptions.emplace_back(&m_bmp_value_lock, L("LOCKED LOCK"),
 | 
			
		||||
        // TRN Description for "LOCKED LOCK"
 | 
			
		||||
		L("indicates that the settings are the same as the system values for the current option group"));
 | 
			
		||||
		L("indicates that the settings are the same as the system (or default) values for the current option group"));
 | 
			
		||||
 | 
			
		||||
    m_icon_descriptions.emplace_back(&m_bmp_value_unlock, L("UNLOCKED LOCK"),
 | 
			
		||||
        // TRN Description for "UNLOCKED LOCK"
 | 
			
		||||
		L("indicates that some settings were changed and are not equal to the system values for "
 | 
			
		||||
		L("indicates that some settings were changed and are not equal to the system (or default) values for "
 | 
			
		||||
		"the current option group.\n"
 | 
			
		||||
		"Click the UNLOCKED LOCK icon to reset all settings for current option group to "
 | 
			
		||||
		"the system values."));
 | 
			
		||||
		"the system (or default) values."));
 | 
			
		||||
 | 
			
		||||
    m_icon_descriptions.emplace_back(&m_bmp_white_bullet, L("WHITE BULLET"),
 | 
			
		||||
        // TRN Description for "WHITE BULLET"
 | 
			
		||||
        L("for the left button: \tindicates a non-system preset,\n"
 | 
			
		||||
        L("for the left button: \tindicates a non-system (or non-default) preset,\n"
 | 
			
		||||
		"for the right button: \tindicates that the settings hasn't been modified."));
 | 
			
		||||
 | 
			
		||||
    m_icon_descriptions.emplace_back(&m_bmp_value_revert, L("BACK ARROW"),
 | 
			
		||||
| 
						 | 
				
			
			@ -3198,29 +3201,14 @@ void Tab::fill_icon_descriptions()
 | 
			
		|||
 | 
			
		||||
void Tab::set_tooltips_text()
 | 
			
		||||
{
 | 
			
		||||
// 	m_undo_to_sys_btn->SetToolTip(_(L(	"LOCKED LOCK icon indicates that the settings are the same as the system values "
 | 
			
		||||
// 										"for the current option group.\n"
 | 
			
		||||
// 										"UNLOCKED LOCK icon indicates that some settings were changed and are not equal "
 | 
			
		||||
// 										"to the system values for the current option group.\n"
 | 
			
		||||
// 										"WHITE BULLET icon indicates a non system preset.\n\n"
 | 
			
		||||
// 										"Click the UNLOCKED LOCK icon to reset all settings for current option group to "
 | 
			
		||||
// 										"the system values.")));
 | 
			
		||||
// 
 | 
			
		||||
// 	m_undo_btn->SetToolTip(_(L(	"WHITE BULLET icon indicates that the settings are the same as in the last saved"
 | 
			
		||||
// 								"preset  for the current option group.\n"
 | 
			
		||||
// 								"BACK ARROW icon indicates that the settings were changed and are not equal to "
 | 
			
		||||
// 								"the last saved preset for the current option group.\n\n"
 | 
			
		||||
// 								"Click the BACK ARROW icon to reset all settings for the current option group to "
 | 
			
		||||
// 								"the last saved preset.")));
 | 
			
		||||
 | 
			
		||||
	// --- Tooltip text for reset buttons (for whole options group)
 | 
			
		||||
	// Text to be shown on the "Revert to system" aka "Lock to system" button next to each input field.
 | 
			
		||||
	m_ttg_value_lock =		_(L("LOCKED LOCK icon indicates that the settings are the same as the system values "
 | 
			
		||||
	m_ttg_value_lock =		_(L("LOCKED LOCK icon indicates that the settings are the same as the system (or default) values "
 | 
			
		||||
								"for the current option group"));
 | 
			
		||||
	m_ttg_value_unlock =	_(L("UNLOCKED LOCK icon indicates that some settings were changed and are not equal "
 | 
			
		||||
								"to the system values for the current option group.\n"
 | 
			
		||||
								"Click to reset all settings for current option group to the system values."));
 | 
			
		||||
	m_ttg_white_bullet_ns =	_(L("WHITE BULLET icon indicates a non system preset."));
 | 
			
		||||
								"to the system (or default) values for the current option group.\n"
 | 
			
		||||
								"Click to reset all settings for current option group to the system (or default) values."));
 | 
			
		||||
	m_ttg_white_bullet_ns =	_(L("WHITE BULLET icon indicates a non system (or non default) preset."));
 | 
			
		||||
	m_ttg_non_system =		&m_ttg_white_bullet_ns;
 | 
			
		||||
	// Text to be shown on the "Undo user changes" button next to each input field.
 | 
			
		||||
	m_ttg_white_bullet =	_(L("WHITE BULLET icon indicates that the settings are the same as in the last saved "
 | 
			
		||||
| 
						 | 
				
			
			@ -3231,10 +3219,10 @@ void Tab::set_tooltips_text()
 | 
			
		|||
 | 
			
		||||
	// --- Tooltip text for reset buttons (for each option in group)
 | 
			
		||||
	// Text to be shown on the "Revert to system" aka "Lock to system" button next to each input field.
 | 
			
		||||
	m_tt_value_lock =		_(L("LOCKED LOCK icon indicates that the value is the same as the system value."));
 | 
			
		||||
	m_tt_value_lock =		_(L("LOCKED LOCK icon indicates that the value is the same as the system (or default) value."));
 | 
			
		||||
	m_tt_value_unlock =		_(L("UNLOCKED LOCK icon indicates that the value was changed and is not equal "
 | 
			
		||||
								"to the system value.\n"
 | 
			
		||||
								"Click to reset current value to the system value."));
 | 
			
		||||
								"to the system (or default) value.\n"
 | 
			
		||||
								"Click to reset current value to the system (or default) value."));
 | 
			
		||||
	// 	m_tt_white_bullet_ns=	_(L("WHITE BULLET icon indicates a non system preset."));
 | 
			
		||||
	m_tt_non_system =		&m_ttg_white_bullet_ns;
 | 
			
		||||
	// Text to be shown on the "Undo user changes" button next to each input field.
 | 
			
		||||
| 
						 | 
				
			
			@ -3488,9 +3476,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--;
 | 
			
		||||
| 
						 | 
				
			
			@ -3588,9 +3576,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");
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -142,6 +142,12 @@ protected:
 | 
			
		|||
	PresetDependencies 	m_compatible_printers;
 | 
			
		||||
	PresetDependencies 	m_compatible_prints;
 | 
			
		||||
 | 
			
		||||
    /* Indicates, that default preset or preset inherited from default is selected
 | 
			
		||||
     * This value is used for a options color updating 
 | 
			
		||||
     * (use green color only for options, which values are equal to system values)
 | 
			
		||||
     */
 | 
			
		||||
    bool                    m_is_default_preset {false};
 | 
			
		||||
 | 
			
		||||
	ScalableButton*			m_undo_btn;
 | 
			
		||||
	ScalableButton*			m_undo_to_sys_btn;
 | 
			
		||||
	ScalableButton*			m_question_btn;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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())
 | 
			
		||||
| 
						 | 
				
			
			@ -2027,6 +2225,9 @@ void DoubleSlider::draw_thumbs(wxDC& dc, const wxCoord& lower_pos, const wxCoord
 | 
			
		|||
 | 
			
		||||
void DoubleSlider::draw_ticks(wxDC& dc)
 | 
			
		||||
{
 | 
			
		||||
    if (!m_is_enabled_tick_manipulation)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    dc.SetPen(m_is_enabled_tick_manipulation ? DARK_GREY_PEN : LIGHT_GREY_PEN );
 | 
			
		||||
    int height, width;
 | 
			
		||||
    get_size(&width, &height);
 | 
			
		||||
| 
						 | 
				
			
			@ -2044,6 +2245,9 @@ void DoubleSlider::draw_ticks(wxDC& dc)
 | 
			
		|||
 | 
			
		||||
void DoubleSlider::draw_colored_band(wxDC& dc)
 | 
			
		||||
{
 | 
			
		||||
    if (!m_is_enabled_tick_manipulation)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    int height, width;
 | 
			
		||||
    get_size(&width, &height);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2113,7 +2317,7 @@ void DoubleSlider::draw_one_layer_icon(wxDC& dc)
 | 
			
		|||
 | 
			
		||||
void DoubleSlider::draw_revert_icon(wxDC& dc)
 | 
			
		||||
{
 | 
			
		||||
    if (m_ticks.empty())
 | 
			
		||||
    if (m_ticks.empty() || !m_is_enabled_tick_manipulation)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    int width, height;
 | 
			
		||||
| 
						 | 
				
			
			@ -2218,7 +2422,7 @@ void DoubleSlider::OnLeftDown(wxMouseEvent& event)
 | 
			
		|||
        m_selection == ssLower ? correct_lower_value() : correct_higher_value();
 | 
			
		||||
        if (!m_selection) m_selection = ssHigher;
 | 
			
		||||
    }
 | 
			
		||||
    else if (is_point_in_rect(pos, m_rect_revert_icon)) {
 | 
			
		||||
    else if (is_point_in_rect(pos, m_rect_revert_icon) && m_is_enabled_tick_manipulation) {
 | 
			
		||||
        // discard all color changes
 | 
			
		||||
        SetLowerValue(m_min_value);
 | 
			
		||||
        SetHigherValue(m_max_value);
 | 
			
		||||
| 
						 | 
				
			
			@ -2647,7 +2851,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());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// ----------------------------------------------------------------------------
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -402,15 +402,8 @@ Updates PresetUpdater::priv::get_config_updates() const
 | 
			
		|||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		copy_file_fix(idx.path(), bundle_path_idx);
 | 
			
		||||
 | 
			
		||||
		const auto ver_current = idx.find(vp.config_version);
 | 
			
		||||
		const bool ver_current_found = ver_current != idx.end();
 | 
			
		||||
		if (! ver_current_found) {
 | 
			
		||||
			auto message = (boost::format("Preset bundle `%1%` version not found in index: %2%") % idx.vendor() % vp.config_version.to_string()).str();
 | 
			
		||||
			BOOST_LOG_TRIVIAL(error) << message;
 | 
			
		||||
			GUI::show_error(nullptr, GUI::from_u8(message));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		BOOST_LOG_TRIVIAL(debug) << boost::format("Vendor: %1%, version installed: %2%%3%, version cached: %4%")
 | 
			
		||||
			% vp.name
 | 
			
		||||
| 
						 | 
				
			
			@ -418,6 +411,13 @@ Updates PresetUpdater::priv::get_config_updates() const
 | 
			
		|||
			% (ver_current_found ? "" : " (not found in index!)")
 | 
			
		||||
			% recommended->config_version.to_string();
 | 
			
		||||
 | 
			
		||||
		if (! ver_current_found) {
 | 
			
		||||
			auto message = (boost::format("Preset bundle `%1%` version not found in index: %2%") % idx.vendor() % vp.config_version.to_string()).str();
 | 
			
		||||
			BOOST_LOG_TRIVIAL(error) << message;
 | 
			
		||||
			GUI::show_error(nullptr, GUI::from_u8(message));
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (ver_current_found && !ver_current->is_current_slic3r_supported()) {
 | 
			
		||||
			BOOST_LOG_TRIVIAL(warning) << "Current Slic3r incompatible with installed bundle: " << bundle_path.string();
 | 
			
		||||
			updates.incompats.emplace_back(std::move(bundle_path), *ver_current, vp.name);
 | 
			
		||||
| 
						 | 
				
			
			@ -459,10 +459,16 @@ Updates PresetUpdater::priv::get_config_updates() const
 | 
			
		|||
					found = true;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if (! found)
 | 
			
		||||
 | 
			
		||||
			if (found) {
 | 
			
		||||
				// 'Install' the index in the vendor directory. This is used to memoize
 | 
			
		||||
				// offered updates and to not offer the same update again if it was cancelled by the user.
 | 
			
		||||
				copy_file_fix(idx.path(), bundle_path_idx);
 | 
			
		||||
			} else {
 | 
			
		||||
				BOOST_LOG_TRIVIAL(warning) << boost::format("Index for vendor %1% indicates update (%2%) but the new bundle was found neither in cache nor resources")
 | 
			
		||||
					% idx.vendor()
 | 
			
		||||
					% recommended->config_version.to_string();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue