mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-11-02 20:51:23 -07:00 
			
		
		
		
	Merge remote-tracking branch 'remotes/origin/vb_undo_redo'
This commit is contained in:
		
						commit
						ab7ecc1819
					
				
					 60 changed files with 2799 additions and 578 deletions
				
			
		| 
						 | 
				
			
			@ -344,6 +344,10 @@ if (NOT GLEW_FOUND)
 | 
			
		|||
endif ()
 | 
			
		||||
include_directories(${GLEW_INCLUDE_DIRS})
 | 
			
		||||
 | 
			
		||||
# Find the Cereal serialization library
 | 
			
		||||
add_library(cereal INTERFACE)
 | 
			
		||||
target_include_directories(cereal INTERFACE include)
 | 
			
		||||
 | 
			
		||||
# l10n
 | 
			
		||||
set(L10N_DIR "${SLIC3R_RESOURCES_DIR}/localization")
 | 
			
		||||
add_custom_target(pot
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										2
									
								
								deps/CMakeLists.txt
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								deps/CMakeLists.txt
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -89,6 +89,7 @@ if (MSVC)
 | 
			
		|||
        dep_libcurl
 | 
			
		||||
        dep_wxwidgets
 | 
			
		||||
        dep_gtest
 | 
			
		||||
        dep_cereal
 | 
			
		||||
        dep_nlopt
 | 
			
		||||
        # dep_qhull # Experimental
 | 
			
		||||
        dep_zlib    # on Windows we still need zlib
 | 
			
		||||
| 
						 | 
				
			
			@ -103,6 +104,7 @@ else()
 | 
			
		|||
        dep_libcurl
 | 
			
		||||
        dep_wxwidgets
 | 
			
		||||
        dep_gtest
 | 
			
		||||
        dep_cereal
 | 
			
		||||
        dep_nlopt
 | 
			
		||||
        dep_qhull
 | 
			
		||||
        # dep_libigl # Not working, static build has different Eigen
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										10
									
								
								deps/deps-unix-common.cmake
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								deps/deps-unix-common.cmake
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -19,6 +19,16 @@ ExternalProject_Add(dep_gtest
 | 
			
		|||
    CMAKE_ARGS -DBUILD_GMOCK=OFF ${DEP_CMAKE_OPTS} -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
ExternalProject_Add(dep_cereal
 | 
			
		||||
    EXCLUDE_FROM_ALL 1
 | 
			
		||||
    URL "https://github.com/USCiLab/cereal/archive/v1.2.2.tar.gz"
 | 
			
		||||
#    URL_HASH SHA256=c6dd7a5701fff8ad5ebb45a3dc8e757e61d52658de3918e38bab233e7fd3b4ae
 | 
			
		||||
    CMAKE_ARGS
 | 
			
		||||
        -DJUST_INSTALL_CEREAL=on
 | 
			
		||||
        -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local
 | 
			
		||||
        ${DEP_CMAKE_OPTS}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
ExternalProject_Add(dep_nlopt
 | 
			
		||||
    EXCLUDE_FROM_ALL 1
 | 
			
		||||
    URL "https://github.com/stevengj/nlopt/archive/v2.5.0.tar.gz"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										14
									
								
								deps/deps-windows.cmake
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								deps/deps-windows.cmake
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -115,6 +115,20 @@ if (${DEP_DEBUG})
 | 
			
		|||
endif ()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
ExternalProject_Add(dep_cereal
 | 
			
		||||
    EXCLUDE_FROM_ALL 1
 | 
			
		||||
    URL "https://github.com/USCiLab/cereal/archive/v1.2.2.tar.gz"
 | 
			
		||||
#    URL_HASH SHA256=c6dd7a5701fff8ad5ebb45a3dc8e757e61d52658de3918e38bab233e7fd3b4ae
 | 
			
		||||
    CMAKE_GENERATOR "${DEP_MSVC_GEN}"
 | 
			
		||||
    CMAKE_GENERATOR_PLATFORM "${DEP_PLATFORM}"
 | 
			
		||||
    CMAKE_ARGS
 | 
			
		||||
        -DJUST_INSTALL_CEREAL=on
 | 
			
		||||
        "-DCMAKE_INSTALL_PREFIX:PATH=${DESTDIR}\\usr\\local"
 | 
			
		||||
    BUILD_COMMAND msbuild /m /P:Configuration=Release INSTALL.vcxproj
 | 
			
		||||
    INSTALL_COMMAND ""
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
ExternalProject_Add(dep_nlopt
 | 
			
		||||
    EXCLUDE_FROM_ALL 1
 | 
			
		||||
    URL "https://github.com/stevengj/nlopt/archive/v2.5.0.tar.gz"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,6 +20,9 @@ You can also customize the bundle output path using the `-DDESTDIR=<some path>`
 | 
			
		|||
**Warning**: Once the dependency bundle is installed in a destdir, the destdir cannot be moved elsewhere.
 | 
			
		||||
(This is because wxWidgets hardcodes the installation path.)
 | 
			
		||||
 | 
			
		||||
FIXME The Cereal serialization library needs a tiny patch on some old OSX clang installations
 | 
			
		||||
https://github.com/USCiLab/cereal/issues/339#issuecomment-246166717
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Building PrusaSlicer
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										12
									
								
								resources/icons/redo.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								resources/icons/redo.svg
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<!-- Generator: Adobe Illustrator 23.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
 | 
			
		||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
 | 
			
		||||
	 viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
 | 
			
		||||
<g id="redo">
 | 
			
		||||
	<path fill="none" stroke="#ED6B21" stroke-width="2" stroke-linecap="round" stroke-miterlimit="10" d="M13.39,11
 | 
			
		||||
		c-0.91,1.78-2.76,3-4.89,3C5.46,14,3,11.54,3,8.5C3,5.46,5.46,3,8.5,3C8.67,3,8.84,3.01,9,3.03"/>
 | 
			
		||||
	
 | 
			
		||||
		<polygon fill="#ED6B21" stroke="#ED6B21" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
 | 
			
		||||
		9,1 9,5 12,3 	"/>
 | 
			
		||||
</g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 734 B  | 
							
								
								
									
										12
									
								
								resources/icons/redo_toolbar.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								resources/icons/redo_toolbar.svg
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<!-- Generator: Adobe Illustrator 23.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
 | 
			
		||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
 | 
			
		||||
	 viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
 | 
			
		||||
<g id="redo">
 | 
			
		||||
	<path fill="none" stroke="#ED6B21" stroke-width="1" stroke-linecap="round" stroke-miterlimit="10" d="M13.39,11
 | 
			
		||||
		c-0.91,1.78-2.76,3-4.89,3C5.46,14,3,11.54,3,8.5C3,5.46,5.46,3,8.5,3C8.67,3,8.84,3.01,9,3.03"/>
 | 
			
		||||
	
 | 
			
		||||
		<polygon fill="#ED6B21" stroke="#ED6B21" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
 | 
			
		||||
		9,1 9,5 12,3 	"/>
 | 
			
		||||
</g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 734 B  | 
							
								
								
									
										12
									
								
								resources/icons/undo_toolbar.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								resources/icons/undo_toolbar.svg
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
 | 
			
		||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
 | 
			
		||||
	 viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
 | 
			
		||||
<g id="undo">
 | 
			
		||||
	<path fill="none" stroke="#ED6B21" stroke-width="1" stroke-linecap="round" stroke-miterlimit="10" d="M3,11
 | 
			
		||||
		c0.91,1.78,2.76,3,4.89,3c3.04,0,5.5-2.46,5.5-5.5c0-3.04-2.46-5.5-5.5-5.5c-0.17,0-0.34,0.01-0.5,0.03"/>
 | 
			
		||||
	
 | 
			
		||||
		<polygon fill="#ED6B21" stroke="#ED6B21" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
 | 
			
		||||
		7.39,1 7.39,5 4.39,3 	"/>
 | 
			
		||||
</g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 746 B  | 
| 
						 | 
				
			
			@ -75,7 +75,7 @@ if (NOT MSVC)
 | 
			
		|||
    set_target_properties(PrusaSlicer PROPERTIES OUTPUT_NAME "prusa-slicer")
 | 
			
		||||
endif ()
 | 
			
		||||
 | 
			
		||||
target_link_libraries(PrusaSlicer libslic3r)
 | 
			
		||||
target_link_libraries(PrusaSlicer libslic3r cereal)
 | 
			
		||||
if (APPLE)
 | 
			
		||||
#    add_compile_options(-stdlib=libc++)
 | 
			
		||||
#    add_definitions(-DBOOST_THREAD_DONT_USE_CHRONO -DBOOST_NO_CXX11_RVALUE_REFERENCES -DBOOST_THREAD_USES_MOVE)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -132,7 +132,7 @@ struct HashTableEdges {
 | 
			
		|||
	~HashTableEdges() {
 | 
			
		||||
#ifndef NDEBUG
 | 
			
		||||
		for (int i = 0; i < this->M; ++ i)
 | 
			
		||||
	    	for (HashEdge *temp = this->heads[i]; this->heads[i] != this->tail; temp = this->heads[i])
 | 
			
		||||
	    	for (HashEdge *temp = this->heads[i]; temp != this->tail; temp = temp->next)
 | 
			
		||||
	        	++ this->freed;
 | 
			
		||||
		this->tail = nullptr;
 | 
			
		||||
#endif /* NDEBUG */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -161,4 +161,12 @@ inline bool empty(const BoundingBox3Base<VT> &bb)
 | 
			
		|||
 | 
			
		||||
} // namespace Slic3r
 | 
			
		||||
 | 
			
		||||
// Serialization through the Cereal library
 | 
			
		||||
namespace cereal {
 | 
			
		||||
	template<class Archive> void serialize(Archive& archive, Slic3r::BoundingBox   &bb) { archive(bb.min, bb.max, bb.defined); }
 | 
			
		||||
	template<class Archive> void serialize(Archive& archive, Slic3r::BoundingBox3  &bb) { archive(bb.min, bb.max, bb.defined); }
 | 
			
		||||
	template<class Archive> void serialize(Archive& archive, Slic3r::BoundingBoxf  &bb) { archive(bb.min, bb.max, bb.defined); }
 | 
			
		||||
	template<class Archive> void serialize(Archive& archive, Slic3r::BoundingBoxf3 &bb) { archive(bb.min, bb.max, bb.defined); }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -113,6 +113,8 @@ add_library(libslic3r STATIC
 | 
			
		|||
    MultiPoint.cpp
 | 
			
		||||
    MultiPoint.hpp
 | 
			
		||||
    MutablePriorityQueue.hpp
 | 
			
		||||
    ObjectID.cpp
 | 
			
		||||
    ObjectID.hpp
 | 
			
		||||
    PerimeterGenerator.cpp
 | 
			
		||||
    PerimeterGenerator.hpp
 | 
			
		||||
    PlaceholderParser.cpp
 | 
			
		||||
| 
						 | 
				
			
			@ -188,6 +190,7 @@ target_include_directories(libslic3r PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${LIBNE
 | 
			
		|||
target_link_libraries(libslic3r
 | 
			
		||||
    libnest2d
 | 
			
		||||
    admesh
 | 
			
		||||
    cereal
 | 
			
		||||
    libigl
 | 
			
		||||
    miniz
 | 
			
		||||
    boost_libs
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -209,6 +209,51 @@ std::vector<std::string> ConfigOptionDef::cli_args(const std::string &key) const
 | 
			
		|||
    return args;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ConfigOption* ConfigOptionDef::create_empty_option() const
 | 
			
		||||
{
 | 
			
		||||
    switch (this->type) {
 | 
			
		||||
    case coFloat:           return new ConfigOptionFloat();
 | 
			
		||||
    case coFloats:          return new ConfigOptionFloats();
 | 
			
		||||
    case coInt:             return new ConfigOptionInt();
 | 
			
		||||
    case coInts:            return new ConfigOptionInts();
 | 
			
		||||
    case coString:          return new ConfigOptionString();
 | 
			
		||||
    case coStrings:         return new ConfigOptionStrings();
 | 
			
		||||
    case coPercent:         return new ConfigOptionPercent();
 | 
			
		||||
    case coPercents:        return new ConfigOptionPercents();
 | 
			
		||||
    case coFloatOrPercent:  return new ConfigOptionFloatOrPercent();
 | 
			
		||||
    case coPoint:           return new ConfigOptionPoint();
 | 
			
		||||
    case coPoints:          return new ConfigOptionPoints();
 | 
			
		||||
    case coPoint3:          return new ConfigOptionPoint3();
 | 
			
		||||
//    case coPoint3s:         return new ConfigOptionPoint3s();
 | 
			
		||||
    case coBool:            return new ConfigOptionBool();
 | 
			
		||||
    case coBools:           return new ConfigOptionBools();
 | 
			
		||||
    case coEnum:            return new ConfigOptionEnumGeneric(this->enum_keys_map);
 | 
			
		||||
    default:                throw std::runtime_error(std::string("Unknown option type for option ") + this->label);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ConfigOption* ConfigOptionDef::create_default_option() const
 | 
			
		||||
{
 | 
			
		||||
    if (this->default_value)
 | 
			
		||||
        return (this->default_value->type() == coEnum) ?
 | 
			
		||||
            // Special case: For a DynamicConfig, convert a templated enum to a generic enum.
 | 
			
		||||
            new ConfigOptionEnumGeneric(this->enum_keys_map, this->default_value->getInt()) :
 | 
			
		||||
            this->default_value->clone();
 | 
			
		||||
	return this->create_empty_option();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Assignment of the serialization IDs is not thread safe. The Defs shall be initialized from the main thread!
 | 
			
		||||
ConfigOptionDef* ConfigDef::add(const t_config_option_key &opt_key, ConfigOptionType type)
 | 
			
		||||
{
 | 
			
		||||
	static size_t serialization_key_ordinal_last = 0;
 | 
			
		||||
    ConfigOptionDef *opt = &this->options[opt_key];
 | 
			
		||||
    opt->opt_key = opt_key;
 | 
			
		||||
    opt->type = type;
 | 
			
		||||
    opt->serialization_key_ordinal = ++ serialization_key_ordinal_last;
 | 
			
		||||
    this->by_serialization_key_ordinal[opt->serialization_key_ordinal] = opt;
 | 
			
		||||
    return opt;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string ConfigOptionDef::nocli = "~~~noCLI";
 | 
			
		||||
 | 
			
		||||
std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults, std::function<bool(const ConfigOptionDef &)> filter) const
 | 
			
		||||
| 
						 | 
				
			
			@ -358,7 +403,7 @@ t_config_option_keys ConfigBase::equal(const ConfigBase &other) const
 | 
			
		|||
    return equal;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string ConfigBase::serialize(const t_config_option_key &opt_key) const
 | 
			
		||||
std::string ConfigBase::opt_serialize(const t_config_option_key &opt_key) const
 | 
			
		||||
{
 | 
			
		||||
    const ConfigOption* opt = this->option(opt_key);
 | 
			
		||||
    assert(opt != nullptr);
 | 
			
		||||
| 
						 | 
				
			
			@ -469,7 +514,7 @@ void ConfigBase::setenv_() const
 | 
			
		|||
        for (size_t i = 0; i < envname.size(); ++i)
 | 
			
		||||
            envname[i] = (envname[i] <= 'z' && envname[i] >= 'a') ? envname[i]-('a'-'A') : envname[i];
 | 
			
		||||
        
 | 
			
		||||
        boost::nowide::setenv(envname.c_str(), this->serialize(*it).c_str(), 1);
 | 
			
		||||
        boost::nowide::setenv(envname.c_str(), this->opt_serialize(*it).c_str(), 1);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -593,16 +638,16 @@ void ConfigBase::save(const std::string &file) const
 | 
			
		|||
    c.open(file, std::ios::out | std::ios::trunc);
 | 
			
		||||
    c << "# " << Slic3r::header_slic3r_generated() << std::endl;
 | 
			
		||||
    for (const std::string &opt_key : this->keys())
 | 
			
		||||
        c << opt_key << " = " << this->serialize(opt_key) << std::endl;
 | 
			
		||||
        c << opt_key << " = " << this->opt_serialize(opt_key) << std::endl;
 | 
			
		||||
    c.close();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DynamicConfig::operator==(const DynamicConfig &rhs) const
 | 
			
		||||
{
 | 
			
		||||
    t_options_map::const_iterator it1     = this->options.begin();
 | 
			
		||||
    t_options_map::const_iterator it1_end = this->options.end();
 | 
			
		||||
    t_options_map::const_iterator it2     = rhs.options.begin();
 | 
			
		||||
    t_options_map::const_iterator it2_end = rhs.options.end();
 | 
			
		||||
    auto it1     = this->options.begin();
 | 
			
		||||
    auto it1_end = this->options.end();
 | 
			
		||||
    auto it2     = rhs.options.begin();
 | 
			
		||||
    auto it2_end = rhs.options.end();
 | 
			
		||||
    for (; it1 != it1_end && it2 != it2_end; ++ it1, ++ it2)
 | 
			
		||||
		if (it1->first != it2->first || *it1->second != *it2->second)
 | 
			
		||||
			// key or value differ
 | 
			
		||||
| 
						 | 
				
			
			@ -612,10 +657,10 @@ bool DynamicConfig::operator==(const DynamicConfig &rhs) const
 | 
			
		|||
 | 
			
		||||
ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool create)
 | 
			
		||||
{
 | 
			
		||||
    t_options_map::iterator it = options.find(opt_key);
 | 
			
		||||
    auto it = options.find(opt_key);
 | 
			
		||||
    if (it != options.end())
 | 
			
		||||
        // Option was found.
 | 
			
		||||
        return it->second;
 | 
			
		||||
        return it->second.get();
 | 
			
		||||
    if (! create)
 | 
			
		||||
        // Option was not found and a new option shall not be created.
 | 
			
		||||
        return nullptr;
 | 
			
		||||
| 
						 | 
				
			
			@ -628,34 +673,8 @@ ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool cre
 | 
			
		|||
//        throw std::runtime_error(std::string("Invalid option name: ") + opt_key);
 | 
			
		||||
        // Let the parent decide what to do if the opt_key is not defined by this->def().
 | 
			
		||||
        return nullptr;
 | 
			
		||||
    ConfigOption *opt = nullptr;
 | 
			
		||||
    if (optdef->default_value) {
 | 
			
		||||
        opt = (optdef->default_value->type() == coEnum) ?
 | 
			
		||||
            // Special case: For a DynamicConfig, convert a templated enum to a generic enum.
 | 
			
		||||
            new ConfigOptionEnumGeneric(optdef->enum_keys_map, optdef->default_value->getInt()) :
 | 
			
		||||
            optdef->default_value->clone();
 | 
			
		||||
    } else {
 | 
			
		||||
        switch (optdef->type) {
 | 
			
		||||
        case coFloat:           opt = new ConfigOptionFloat();          break;
 | 
			
		||||
        case coFloats:          opt = new ConfigOptionFloats();         break;
 | 
			
		||||
        case coInt:             opt = new ConfigOptionInt();            break;
 | 
			
		||||
        case coInts:            opt = new ConfigOptionInts();           break;
 | 
			
		||||
        case coString:          opt = new ConfigOptionString();         break;
 | 
			
		||||
        case coStrings:         opt = new ConfigOptionStrings();        break;
 | 
			
		||||
        case coPercent:         opt = new ConfigOptionPercent();        break;
 | 
			
		||||
        case coPercents:        opt = new ConfigOptionPercents();       break;
 | 
			
		||||
        case coFloatOrPercent:  opt = new ConfigOptionFloatOrPercent(); break;
 | 
			
		||||
        case coPoint:           opt = new ConfigOptionPoint();          break;
 | 
			
		||||
        case coPoints:          opt = new ConfigOptionPoints();         break;
 | 
			
		||||
        case coPoint3:          opt = new ConfigOptionPoint3();         break;
 | 
			
		||||
    //    case coPoint3s:         opt = new ConfigOptionPoint3s();        break;
 | 
			
		||||
        case coBool:            opt = new ConfigOptionBool();           break;
 | 
			
		||||
        case coBools:           opt = new ConfigOptionBools();          break;
 | 
			
		||||
        case coEnum:            opt = new ConfigOptionEnumGeneric(optdef->enum_keys_map); break;
 | 
			
		||||
        default:                throw std::runtime_error(std::string("Unknown option type for option ") + opt_key);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    this->options[opt_key] = opt;
 | 
			
		||||
    ConfigOption *opt = optdef->create_default_option();
 | 
			
		||||
    this->options.emplace_hint(it, opt_key, std::unique_ptr<ConfigOption>(opt));
 | 
			
		||||
    return opt;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -802,3 +821,64 @@ t_config_option_keys StaticConfig::keys() const
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#include <cereal/types/polymorphic.hpp>
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOption)
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle<double>)
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle<int>)
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle<std::string>)
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle<Slic3r::Vec2d>)
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle<Slic3r::Vec3d>)
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle<bool>)
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVectorBase)
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector<double>)
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector<int>)
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector<std::string>)
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector<Slic3r::Vec2d>)
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector<unsigned char>)
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloat)
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloats)
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionInt)
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionInts)
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionString)
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionStrings)
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPercent)
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPercents)
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloatOrPercent)
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPoint)
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPoints)
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPoint3)
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionBool)
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionBools)
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionEnumGeneric)
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigBase)
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::DynamicConfig)
 | 
			
		||||
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle<double>) 
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle<int>) 
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle<std::string>) 
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle<Slic3r::Vec2d>)
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle<Slic3r::Vec3d>)
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle<bool>) 
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionVectorBase) 
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector<double>)
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector<int>)
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector<std::string>)
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector<Slic3r::Vec2d>)
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector<unsigned char>)
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle<double>, Slic3r::ConfigOptionFloat)
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<double>, Slic3r::ConfigOptionFloats)
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle<int>, Slic3r::ConfigOptionInt)
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<int>, Slic3r::ConfigOptionInts)
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle<std::string>, Slic3r::ConfigOptionString)
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<std::string>, Slic3r::ConfigOptionStrings)
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionFloat, Slic3r::ConfigOptionPercent)
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionFloats, Slic3r::ConfigOptionPercents)
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionPercent, Slic3r::ConfigOptionFloatOrPercent)
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle<Slic3r::Vec2d>, Slic3r::ConfigOptionPoint)
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<Slic3r::Vec2d>, Slic3r::ConfigOptionPoints)
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle<Slic3r::Vec3d>, Slic3r::ConfigOptionPoint3)
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle<bool>, Slic3r::ConfigOptionBool)
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<unsigned char>, Slic3r::ConfigOptionBools)
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionInt, Slic3r::ConfigOptionEnumGeneric)
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigBase, Slic3r::DynamicConfig)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,6 +18,9 @@
 | 
			
		|||
#include <boost/format.hpp>
 | 
			
		||||
#include <boost/property_tree/ptree.hpp>
 | 
			
		||||
 | 
			
		||||
#include <cereal/access.hpp>
 | 
			
		||||
#include <cereal/types/base_class.hpp>
 | 
			
		||||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
 | 
			
		||||
// Name of the configuration option.
 | 
			
		||||
| 
						 | 
				
			
			@ -152,6 +155,10 @@ public:
 | 
			
		|||
 | 
			
		||||
    bool operator==(const T &rhs) const { return this->value == rhs; }
 | 
			
		||||
    bool operator!=(const T &rhs) const { return this->value != rhs; }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend class cereal::access;
 | 
			
		||||
	template<class Archive> void serialize(Archive & ar) { ar(this->value); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Value of a vector valued option (bools, ints, floats, strings, points)
 | 
			
		||||
| 
						 | 
				
			
			@ -294,6 +301,10 @@ public:
 | 
			
		|||
 | 
			
		||||
    bool operator==(const std::vector<T> &rhs) const { return this->values == rhs; }
 | 
			
		||||
    bool operator!=(const std::vector<T> &rhs) const { return this->values != rhs; }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend class cereal::access;
 | 
			
		||||
	template<class Archive> void serialize(Archive & ar) { ar(this->values); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ConfigOptionFloat : public ConfigOptionSingle<double>
 | 
			
		||||
| 
						 | 
				
			
			@ -328,6 +339,10 @@ public:
 | 
			
		|||
        this->set(opt);
 | 
			
		||||
        return *this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend class cereal::access;
 | 
			
		||||
	template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionSingle<double>>(this)); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ConfigOptionFloats : public ConfigOptionVector<double>
 | 
			
		||||
| 
						 | 
				
			
			@ -386,6 +401,10 @@ public:
 | 
			
		|||
        this->set(opt);
 | 
			
		||||
        return *this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend class cereal::access;
 | 
			
		||||
	template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionVector<double>>(this)); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ConfigOptionInt : public ConfigOptionSingle<int>
 | 
			
		||||
| 
						 | 
				
			
			@ -422,6 +441,10 @@ public:
 | 
			
		|||
        this->set(opt);
 | 
			
		||||
        return *this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend class cereal::access;
 | 
			
		||||
	template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionSingle<int>>(this)); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ConfigOptionInts : public ConfigOptionVector<int>
 | 
			
		||||
| 
						 | 
				
			
			@ -472,6 +495,10 @@ public:
 | 
			
		|||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend class cereal::access;
 | 
			
		||||
	template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionVector<int>>(this)); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ConfigOptionString : public ConfigOptionSingle<std::string>
 | 
			
		||||
| 
						 | 
				
			
			@ -496,6 +523,10 @@ public:
 | 
			
		|||
        UNUSED(append);
 | 
			
		||||
        return unescape_string_cstyle(str, this->value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend class cereal::access;
 | 
			
		||||
	template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionSingle<std::string>>(this)); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// semicolon-separated strings
 | 
			
		||||
| 
						 | 
				
			
			@ -530,6 +561,10 @@ public:
 | 
			
		|||
            this->values.clear();
 | 
			
		||||
        return unescape_strings_cstyle(str, this->values);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend class cereal::access;
 | 
			
		||||
	template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionVector<std::string>>(this)); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ConfigOptionPercent : public ConfigOptionFloat
 | 
			
		||||
| 
						 | 
				
			
			@ -562,6 +597,10 @@ public:
 | 
			
		|||
        iss >> this->value;
 | 
			
		||||
        return !iss.fail();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend class cereal::access;
 | 
			
		||||
	template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionFloat>(this)); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ConfigOptionPercents : public ConfigOptionFloats
 | 
			
		||||
| 
						 | 
				
			
			@ -616,6 +655,10 @@ public:
 | 
			
		|||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend class cereal::access;
 | 
			
		||||
	template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionFloats>(this)); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ConfigOptionFloatOrPercent : public ConfigOptionPercent
 | 
			
		||||
| 
						 | 
				
			
			@ -665,6 +708,10 @@ public:
 | 
			
		|||
        iss >> this->value;
 | 
			
		||||
        return !iss.fail();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend class cereal::access;
 | 
			
		||||
	template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionPercent>(this), percent); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ConfigOptionPoint : public ConfigOptionSingle<Vec2d>
 | 
			
		||||
| 
						 | 
				
			
			@ -695,6 +742,10 @@ public:
 | 
			
		|||
        return sscanf(str.data(), " %lf , %lf %c", &this->value(0), &this->value(1), &dummy) == 2 ||
 | 
			
		||||
               sscanf(str.data(), " %lf x %lf %c", &this->value(0), &this->value(1), &dummy) == 2;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend class cereal::access;
 | 
			
		||||
	template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionSingle<Vec2d>>(this)); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ConfigOptionPoints : public ConfigOptionVector<Vec2d>
 | 
			
		||||
| 
						 | 
				
			
			@ -754,8 +805,21 @@ public:
 | 
			
		|||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend class cereal::access;
 | 
			
		||||
	template<class Archive> void save(Archive& archive) const {
 | 
			
		||||
		size_t cnt = this->values.size();
 | 
			
		||||
		archive(cnt);
 | 
			
		||||
		archive.saveBinary((const char*)this->values.data(), sizeof(Vec2d) * cnt);
 | 
			
		||||
	}
 | 
			
		||||
	template<class Archive> void load(Archive& archive) {
 | 
			
		||||
		size_t cnt;
 | 
			
		||||
		archive(cnt);
 | 
			
		||||
		this->values.assign(cnt, Vec2d());
 | 
			
		||||
		archive.loadBinary((char*)this->values.data(), sizeof(Vec2d) * cnt);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ConfigOptionPoint3 : public ConfigOptionSingle<Vec3d>
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -787,6 +851,10 @@ public:
 | 
			
		|||
        return sscanf(str.data(), " %lf , %lf , %lf %c", &this->value(0), &this->value(1), &this->value(2), &dummy) == 2 ||
 | 
			
		||||
               sscanf(str.data(), " %lf x %lf x %lf %c", &this->value(0), &this->value(1), &this->value(2), &dummy) == 2;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend class cereal::access;
 | 
			
		||||
	template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionSingle<Vec3d>>(this)); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ConfigOptionBool : public ConfigOptionSingle<bool>
 | 
			
		||||
| 
						 | 
				
			
			@ -813,6 +881,10 @@ public:
 | 
			
		|||
        this->value = (str.compare("1") == 0);
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend class cereal::access;
 | 
			
		||||
	template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionSingle<bool>>(this)); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ConfigOptionBools : public ConfigOptionVector<unsigned char>
 | 
			
		||||
| 
						 | 
				
			
			@ -868,6 +940,10 @@ public:
 | 
			
		|||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend class cereal::access;
 | 
			
		||||
	template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionVector<unsigned char>>(this)); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Map from an enum integer value to an enum name.
 | 
			
		||||
| 
						 | 
				
			
			@ -1006,19 +1082,73 @@ public:
 | 
			
		|||
        this->value = it->second;
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend class cereal::access;
 | 
			
		||||
	template<class Archive> void serialize(Archive& ar) { ar(cereal::base_class<ConfigOptionInt>(this)); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Definition of a configuration value for the purpose of GUI presentation, editing, value mapping and config file handling.
 | 
			
		||||
class ConfigOptionDef
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	// Identifier of this option. It is stored here so that it is accessible through the by_serialization_key_ordinal map.
 | 
			
		||||
	t_config_option_key 				opt_key;
 | 
			
		||||
    // What type? bool, int, string etc.
 | 
			
		||||
    ConfigOptionType                    type            = coNone;
 | 
			
		||||
    // Default value of this option. The default value object is owned by ConfigDef, it is released in its destructor.
 | 
			
		||||
    Slic3r::clonable_ptr<const ConfigOption> default_value;
 | 
			
		||||
    void set_default_value(const ConfigOption* ptr) { this->default_value = Slic3r::clonable_ptr<const ConfigOption>(ptr); }
 | 
			
		||||
    template<typename T>
 | 
			
		||||
    const T* get_default_value() const { return static_cast<const T*>(this->default_value.get()); }
 | 
			
		||||
    void 								set_default_value(const ConfigOption* ptr) { this->default_value = Slic3r::clonable_ptr<const ConfigOption>(ptr); }
 | 
			
		||||
    template<typename T> const T* 		get_default_value() const { return static_cast<const T*>(this->default_value.get()); }
 | 
			
		||||
 | 
			
		||||
    // Create an empty option to be used as a base for deserialization of DynamicConfig.
 | 
			
		||||
    ConfigOption*						create_empty_option() const;
 | 
			
		||||
    // Create a default option to be inserted into a DynamicConfig.
 | 
			
		||||
    ConfigOption*						create_default_option() const;
 | 
			
		||||
 | 
			
		||||
    template<class Archive> ConfigOption* load_option_from_archive(Archive &archive) const {
 | 
			
		||||
	    switch (this->type) {
 | 
			
		||||
	    case coFloat:           { auto opt = new ConfigOptionFloat();  			archive(*opt); return opt; }
 | 
			
		||||
	    case coFloats:          { auto opt = new ConfigOptionFloats(); 			archive(*opt); return opt; }
 | 
			
		||||
	    case coInt:             { auto opt = new ConfigOptionInt();    			archive(*opt); return opt; }
 | 
			
		||||
	    case coInts:            { auto opt = new ConfigOptionInts();   			archive(*opt); return opt; }
 | 
			
		||||
	    case coString:          { auto opt = new ConfigOptionString(); 			archive(*opt); return opt; }
 | 
			
		||||
	    case coStrings:         { auto opt = new ConfigOptionStrings(); 		archive(*opt); return opt; }
 | 
			
		||||
	    case coPercent:         { auto opt = new ConfigOptionPercent(); 		archive(*opt); return opt; }
 | 
			
		||||
	    case coPercents:        { auto opt = new ConfigOptionPercents(); 		archive(*opt); return opt; }
 | 
			
		||||
	    case coFloatOrPercent:  { auto opt = new ConfigOptionFloatOrPercent(); 	archive(*opt); return opt; }
 | 
			
		||||
	    case coPoint:           { auto opt = new ConfigOptionPoint(); 			archive(*opt); return opt; }
 | 
			
		||||
	    case coPoints:          { auto opt = new ConfigOptionPoints(); 			archive(*opt); return opt; }
 | 
			
		||||
	    case coPoint3:          { auto opt = new ConfigOptionPoint3(); 			archive(*opt); return opt; }
 | 
			
		||||
	    case coBool:            { auto opt = new ConfigOptionBool(); 			archive(*opt); return opt; }
 | 
			
		||||
	    case coBools:           { auto opt = new ConfigOptionBools(); 			archive(*opt); return opt; }
 | 
			
		||||
	    case coEnum:            { auto opt = new ConfigOptionEnumGeneric(this->enum_keys_map); archive(*opt); return opt; }
 | 
			
		||||
	    default:                throw std::runtime_error(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key);
 | 
			
		||||
	    }
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    template<class Archive> ConfigOption* save_option_to_archive(Archive &archive, const ConfigOption *opt) const {
 | 
			
		||||
	    switch (this->type) {
 | 
			
		||||
	    case coFloat:           archive(*static_cast<const ConfigOptionFloat*>(opt));  			break;
 | 
			
		||||
	    case coFloats:          archive(*static_cast<const ConfigOptionFloats*>(opt)); 			break;
 | 
			
		||||
	    case coInt:             archive(*static_cast<const ConfigOptionInt*>(opt)); 	 		break;
 | 
			
		||||
	    case coInts:            archive(*static_cast<const ConfigOptionInts*>(opt)); 	 		break;
 | 
			
		||||
	    case coString:          archive(*static_cast<const ConfigOptionString*>(opt)); 			break;
 | 
			
		||||
	    case coStrings:         archive(*static_cast<const ConfigOptionStrings*>(opt)); 		break;
 | 
			
		||||
	    case coPercent:         archive(*static_cast<const ConfigOptionPercent*>(opt)); 		break;
 | 
			
		||||
	    case coPercents:        archive(*static_cast<const ConfigOptionPercents*>(opt)); 		break;
 | 
			
		||||
	    case coFloatOrPercent:  archive(*static_cast<const ConfigOptionFloatOrPercent*>(opt));	break;
 | 
			
		||||
	    case coPoint:           archive(*static_cast<const ConfigOptionPoint*>(opt)); 			break;
 | 
			
		||||
	    case coPoints:          archive(*static_cast<const ConfigOptionPoints*>(opt)); 			break;
 | 
			
		||||
	    case coPoint3:          archive(*static_cast<const ConfigOptionPoint3*>(opt)); 			break;
 | 
			
		||||
	    case coBool:            archive(*static_cast<const ConfigOptionBool*>(opt)); 			break;
 | 
			
		||||
	    case coBools:           archive(*static_cast<const ConfigOptionBools*>(opt)); 			break;
 | 
			
		||||
	    case coEnum:            archive(*static_cast<const ConfigOptionEnumGeneric*>(opt)); 	break;
 | 
			
		||||
	    default:                throw std::runtime_error(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key);
 | 
			
		||||
	    }
 | 
			
		||||
		// Make the compiler happy, shut up the warnings.
 | 
			
		||||
		return nullptr;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    // Usually empty. 
 | 
			
		||||
    // Special values - "i_enum_open", "f_enum_open" to provide combo box for int or float selection,
 | 
			
		||||
| 
						 | 
				
			
			@ -1088,6 +1218,9 @@ public:
 | 
			
		|||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 0 is an invalid key.
 | 
			
		||||
    size_t 								serialization_key_ordinal = 0;
 | 
			
		||||
 | 
			
		||||
    // Returns the alternative CLI arguments for the given option.
 | 
			
		||||
    // If there are no cli arguments defined, use the key and replace underscores with dashes.
 | 
			
		||||
    std::vector<std::string> cli_args(const std::string &key) const;
 | 
			
		||||
| 
						 | 
				
			
			@ -1107,7 +1240,8 @@ typedef std::map<t_config_option_key, ConfigOptionDef> t_optiondef_map;
 | 
			
		|||
class ConfigDef
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    t_optiondef_map         options;
 | 
			
		||||
    t_optiondef_map         					options;
 | 
			
		||||
    std::map<size_t, const ConfigOptionDef*>	by_serialization_key_ordinal;
 | 
			
		||||
 | 
			
		||||
    bool                    has(const t_config_option_key &opt_key) const { return this->options.count(opt_key) > 0; }
 | 
			
		||||
    const ConfigOptionDef*  get(const t_config_option_key &opt_key) const {
 | 
			
		||||
| 
						 | 
				
			
			@ -1128,11 +1262,7 @@ public:
 | 
			
		|||
        std::function<bool(const ConfigOptionDef &)> filter = [](const ConfigOptionDef &){ return true; }) const;
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
    ConfigOptionDef*        add(const t_config_option_key &opt_key, ConfigOptionType type) {
 | 
			
		||||
        ConfigOptionDef* opt = &this->options[opt_key];
 | 
			
		||||
        opt->type = type;
 | 
			
		||||
        return opt;
 | 
			
		||||
    }
 | 
			
		||||
    ConfigOptionDef*        add(const t_config_option_key &opt_key, ConfigOptionType type);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// An abstract configuration store.
 | 
			
		||||
| 
						 | 
				
			
			@ -1201,7 +1331,7 @@ public:
 | 
			
		|||
    bool equals(const ConfigBase &other) const { return this->diff(other).empty(); }
 | 
			
		||||
    t_config_option_keys diff(const ConfigBase &other) const;
 | 
			
		||||
    t_config_option_keys equal(const ConfigBase &other) const;
 | 
			
		||||
    std::string serialize(const t_config_option_key &opt_key) const;
 | 
			
		||||
    std::string opt_serialize(const t_config_option_key &opt_key) const;
 | 
			
		||||
    // Set a configuration value from a string, it will call an overridable handle_legacy() 
 | 
			
		||||
    // to resolve renamed and removed configuration keys.
 | 
			
		||||
    bool set_deserialize(const t_config_option_key &opt_key, const std::string &str, bool append = false);
 | 
			
		||||
| 
						 | 
				
			
			@ -1239,7 +1369,7 @@ public:
 | 
			
		|||
        assert(this->def() == nullptr || this->def() == rhs.def());
 | 
			
		||||
        this->clear();
 | 
			
		||||
        for (const auto &kvp : rhs.options)
 | 
			
		||||
            this->options[kvp.first] = kvp.second->clone();
 | 
			
		||||
            this->options[kvp.first].reset(kvp.second->clone());
 | 
			
		||||
        return *this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1262,15 +1392,13 @@ public:
 | 
			
		|||
        for (const auto &kvp : rhs.options) {
 | 
			
		||||
            auto it = this->options.find(kvp.first);
 | 
			
		||||
            if (it == this->options.end())
 | 
			
		||||
                this->options[kvp.first] = kvp.second->clone();
 | 
			
		||||
                this->options[kvp.first].reset(kvp.second->clone());
 | 
			
		||||
            else {
 | 
			
		||||
                assert(it->second->type() == kvp.second->type());
 | 
			
		||||
                if (it->second->type() == kvp.second->type())
 | 
			
		||||
                    *it->second = *kvp.second;
 | 
			
		||||
                else {
 | 
			
		||||
                    delete it->second;
 | 
			
		||||
                    it->second = kvp.second->clone();
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                    it->second.reset(kvp.second->clone());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return *this;
 | 
			
		||||
| 
						 | 
				
			
			@ -1281,14 +1409,13 @@ public:
 | 
			
		|||
    DynamicConfig& operator+=(DynamicConfig &&rhs) 
 | 
			
		||||
    {
 | 
			
		||||
        assert(this->def() == nullptr || this->def() == rhs.def());
 | 
			
		||||
        for (const auto &kvp : rhs.options) {
 | 
			
		||||
        for (auto &kvp : rhs.options) {
 | 
			
		||||
            auto it = this->options.find(kvp.first);
 | 
			
		||||
            if (it == this->options.end()) {
 | 
			
		||||
                this->options[kvp.first] = kvp.second;
 | 
			
		||||
                this->options.insert(std::make_pair(kvp.first, std::move(kvp.second)));
 | 
			
		||||
            } else {
 | 
			
		||||
                assert(it->second->type() == kvp.second->type());
 | 
			
		||||
                delete it->second;
 | 
			
		||||
                it->second = kvp.second;
 | 
			
		||||
                it->second = std::move(kvp.second);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        rhs.options.clear();
 | 
			
		||||
| 
						 | 
				
			
			@ -1305,8 +1432,6 @@ public:
 | 
			
		|||
 | 
			
		||||
    void clear()
 | 
			
		||||
    { 
 | 
			
		||||
        for (auto &opt : this->options) 
 | 
			
		||||
            delete opt.second; 
 | 
			
		||||
        this->options.clear(); 
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1315,7 +1440,6 @@ public:
 | 
			
		|||
        auto it = this->options.find(opt_key);
 | 
			
		||||
        if (it == this->options.end())
 | 
			
		||||
            return false;
 | 
			
		||||
        delete it->second;
 | 
			
		||||
        this->options.erase(it);
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -1340,11 +1464,10 @@ public:
 | 
			
		|||
    {
 | 
			
		||||
        auto it = this->options.find(opt_key);
 | 
			
		||||
        if (it == this->options.end()) {
 | 
			
		||||
            this->options[opt_key] = opt;
 | 
			
		||||
            this->options[opt_key].reset(opt);
 | 
			
		||||
            return true;
 | 
			
		||||
        } else {
 | 
			
		||||
            delete it->second;
 | 
			
		||||
            it->second = opt;
 | 
			
		||||
            it->second.reset(opt);
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -1374,12 +1497,15 @@ public:
 | 
			
		|||
    void                read_cli(const std::vector<std::string> &tokens, t_config_option_keys* extra, t_config_option_keys* keys = nullptr);
 | 
			
		||||
    bool                read_cli(int argc, char** argv, t_config_option_keys* extra, t_config_option_keys* keys = nullptr);
 | 
			
		||||
 | 
			
		||||
    typedef std::map<t_config_option_key,ConfigOption*> t_options_map;
 | 
			
		||||
    t_options_map::const_iterator cbegin() const { return options.cbegin(); }
 | 
			
		||||
    t_options_map::const_iterator cend()   const { return options.cend(); }
 | 
			
		||||
    std::map<t_config_option_key, std::unique_ptr<ConfigOption>>::const_iterator cbegin() const { return options.cbegin(); }
 | 
			
		||||
    std::map<t_config_option_key, std::unique_ptr<ConfigOption>>::const_iterator cend()   const { return options.cend(); }
 | 
			
		||||
    size_t                        												 size()   const { return options.size(); }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    t_options_map options;
 | 
			
		||||
    std::map<t_config_option_key, std::unique_ptr<ConfigOption>> options;
 | 
			
		||||
 | 
			
		||||
	friend class cereal::access;
 | 
			
		||||
	template<class Archive> void serialize(Archive &ar) { ar(options); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Configuration store with a static definition of configuration values.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2134,7 +2134,7 @@ namespace Slic3r {
 | 
			
		|||
                    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));
 | 
			
		||||
                        pt::ptree& opt_tree = range_tree.add("option", config.opt_serialize(opt_key));
 | 
			
		||||
                        opt_tree.put("<xmlattr>.opt_key", opt_key);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
| 
						 | 
				
			
			@ -2216,7 +2216,7 @@ namespace Slic3r {
 | 
			
		|||
 | 
			
		||||
        for (const std::string &key : config.keys())
 | 
			
		||||
            if (key != "compatible_printers")
 | 
			
		||||
                out += "; " + key + " = " + config.serialize(key) + "\n";
 | 
			
		||||
                out += "; " + key + " = " + config.opt_serialize(key) + "\n";
 | 
			
		||||
 | 
			
		||||
        if (!out.empty())
 | 
			
		||||
        {
 | 
			
		||||
| 
						 | 
				
			
			@ -2250,7 +2250,7 @@ namespace Slic3r {
 | 
			
		|||
                // stores object's config data
 | 
			
		||||
                for (const std::string& key : obj->config.keys())
 | 
			
		||||
                {
 | 
			
		||||
                    stream << "  <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << OBJECT_TYPE << "\" " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << obj->config.serialize(key) << "\"/>\n";
 | 
			
		||||
                    stream << "  <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << OBJECT_TYPE << "\" " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << obj->config.opt_serialize(key) << "\"/>\n";
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                for (const ModelVolume* volume : obj_metadata.second.object->volumes)
 | 
			
		||||
| 
						 | 
				
			
			@ -2280,7 +2280,7 @@ namespace Slic3r {
 | 
			
		|||
                            // stores volume's config data
 | 
			
		||||
                            for (const std::string& key : volume->config.keys())
 | 
			
		||||
                            {
 | 
			
		||||
                                stream << "   <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << volume->config.serialize(key) << "\"/>\n";
 | 
			
		||||
                                stream << "   <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << volume->config.opt_serialize(key) << "\"/>\n";
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            stream << "  </" << VOLUME_TAG << ">\n";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -901,7 +901,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config)
 | 
			
		|||
        std::string str_config = "\n";
 | 
			
		||||
        for (const std::string &key : config->keys())
 | 
			
		||||
            if (key != "compatible_printers")
 | 
			
		||||
                str_config += "; " + key + " = " + config->serialize(key) + "\n";
 | 
			
		||||
                str_config += "; " + key + " = " + config->opt_serialize(key) + "\n";
 | 
			
		||||
        stream << "<metadata type=\"" << SLIC3R_CONFIG_TYPE << "\">" << xml_escape(str_config) << "</metadata>\n";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -913,7 +913,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config)
 | 
			
		|||
        for (const auto &attr : material.second->attributes)
 | 
			
		||||
            stream << "    <metadata type=\"" << attr.first << "\">" << attr.second << "</metadata>\n";
 | 
			
		||||
        for (const std::string &key : material.second->config.keys())
 | 
			
		||||
            stream << "    <metadata type=\"slic3r." << key << "\">" << material.second->config.serialize(key) << "</metadata>\n";
 | 
			
		||||
            stream << "    <metadata type=\"slic3r." << key << "\">" << material.second->config.opt_serialize(key) << "</metadata>\n";
 | 
			
		||||
        stream << "  </material>\n";
 | 
			
		||||
    }
 | 
			
		||||
    std::string instances;
 | 
			
		||||
| 
						 | 
				
			
			@ -921,7 +921,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config)
 | 
			
		|||
        ModelObject *object = model->objects[object_id];
 | 
			
		||||
        stream << "  <object id=\"" << object_id << "\">\n";
 | 
			
		||||
        for (const std::string &key : object->config.keys())
 | 
			
		||||
            stream << "    <metadata type=\"slic3r." << key << "\">" << object->config.serialize(key) << "</metadata>\n";
 | 
			
		||||
            stream << "    <metadata type=\"slic3r." << key << "\">" << object->config.opt_serialize(key) << "</metadata>\n";
 | 
			
		||||
        if (!object->name.empty())
 | 
			
		||||
            stream << "    <metadata type=\"name\">" << xml_escape(object->name) << "</metadata>\n";
 | 
			
		||||
        const std::vector<double> &layer_height_profile = object->layer_height_profile;
 | 
			
		||||
| 
						 | 
				
			
			@ -933,10 +933,8 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config)
 | 
			
		|||
                stream << ";" << layer_height_profile[i];
 | 
			
		||||
                stream << "\n    </metadata>\n";
 | 
			
		||||
        }
 | 
			
		||||
        //FIXME Store the layer height ranges (ModelObject::layer_height_ranges)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        // #ys_FIXME_experiment : Try to export layer config range
 | 
			
		||||
        // Export layer height ranges including the layer range specific config overrides.
 | 
			
		||||
        const t_layer_config_ranges& config_ranges = object->layer_config_ranges;
 | 
			
		||||
        if (!config_ranges.empty())
 | 
			
		||||
        {
 | 
			
		||||
| 
						 | 
				
			
			@ -950,7 +948,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config)
 | 
			
		|||
                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 << "        <metadata type=\"slic3r." << key << "\">" << range.second.opt_serialize(key) << "</metadata>\n";
 | 
			
		||||
 | 
			
		||||
                stream << "      </range>\n";
 | 
			
		||||
                layer_counter++;
 | 
			
		||||
| 
						 | 
				
			
			@ -1005,7 +1003,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config)
 | 
			
		|||
            else
 | 
			
		||||
                stream << "      <volume materialid=\"" << volume->material_id() << "\">\n";
 | 
			
		||||
            for (const std::string &key : volume->config.keys())
 | 
			
		||||
                stream << "        <metadata type=\"slic3r." << key << "\">" << volume->config.serialize(key) << "</metadata>\n";
 | 
			
		||||
                stream << "        <metadata type=\"slic3r." << key << "\">" << volume->config.opt_serialize(key) << "</metadata>\n";
 | 
			
		||||
            if (!volume->name.empty())
 | 
			
		||||
                stream << "        <metadata type=\"name\">" << xml_escape(volume->name) << "</metadata>\n";
 | 
			
		||||
            if (volume->is_modifier())
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1809,7 +1809,7 @@ void GCode::append_full_config(const Print& print, std::string& str)
 | 
			
		|||
        const StaticPrintConfig *cfg = configs[i];
 | 
			
		||||
        for (const std::string &key : cfg->keys())
 | 
			
		||||
            if (key != "compatible_printers")
 | 
			
		||||
                str += "; " + key + " = " + cfg->serialize(key) + "\n";
 | 
			
		||||
                str += "; " + key + " = " + cfg->opt_serialize(key) + "\n";
 | 
			
		||||
    }
 | 
			
		||||
    const DynamicConfig &full_config = print.placeholder_parser().config();
 | 
			
		||||
	for (const char *key : {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,6 +7,9 @@
 | 
			
		|||
#include "Polygon.hpp"
 | 
			
		||||
#include "Polyline.hpp"
 | 
			
		||||
 | 
			
		||||
// Serialization through the Cereal library
 | 
			
		||||
#include <cereal/access.hpp>
 | 
			
		||||
 | 
			
		||||
#include "boost/polygon/voronoi.hpp"
 | 
			
		||||
using boost::polygon::voronoi_builder;
 | 
			
		||||
using boost::polygon::voronoi_diagram;
 | 
			
		||||
| 
						 | 
				
			
			@ -263,6 +266,17 @@ public:
 | 
			
		|||
    // as possible in least squares norm in regard to the 8 corners of bbox.
 | 
			
		||||
    // Bounding box is expected to be centered around zero in all axes.
 | 
			
		||||
    static Transformation volume_to_bed_transformation(const Transformation& instance_transformation, const BoundingBoxf3& bbox);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend class cereal::access;
 | 
			
		||||
	template<class Archive> void serialize(Archive & ar) { ar(m_offset, m_rotation, m_scaling_factor, m_mirror); }
 | 
			
		||||
	explicit Transformation(int) : m_dirty(true) {}
 | 
			
		||||
	template <class Archive> static void load_and_construct(Archive &ar, cereal::construct<Transformation> &construct)
 | 
			
		||||
	{
 | 
			
		||||
		// Calling a private constructor with special "int" parameter to indicate that no construction is necessary.
 | 
			
		||||
		construct(1);
 | 
			
		||||
		ar(construct.ptr()->m_offset, construct.ptr()->m_rotation, construct.ptr()->m_scaling_factor, construct.ptr()->m_mirror);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Rotation when going from the first coordinate system with rotation rot_xyz_from applied
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -128,7 +128,7 @@ void Layer::make_perimeters()
 | 
			
		|||
                && config.external_perimeter_speed == other_config.external_perimeter_speed
 | 
			
		||||
                && config.gap_fill_speed    == other_config.gap_fill_speed
 | 
			
		||||
                && config.overhangs         == other_config.overhangs
 | 
			
		||||
                && config.serialize("perimeter_extrusion_width").compare(other_config.serialize("perimeter_extrusion_width")) == 0
 | 
			
		||||
                && config.opt_serialize("perimeter_extrusion_width") == other_config.opt_serialize("perimeter_extrusion_width")
 | 
			
		||||
                && config.thin_walls        == other_config.thin_walls
 | 
			
		||||
                && config.external_perimeters_first == other_config.external_perimeters_first) {
 | 
			
		||||
                layerms.push_back(other_layerm);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,21 +22,6 @@ namespace Slic3r {
 | 
			
		|||
 | 
			
		||||
unsigned int Model::s_auto_extruder_id = 1;
 | 
			
		||||
 | 
			
		||||
size_t ModelBase::s_last_id = 0;
 | 
			
		||||
 | 
			
		||||
// Unique object / instance ID for the wipe tower.
 | 
			
		||||
ModelID wipe_tower_object_id()
 | 
			
		||||
{
 | 
			
		||||
    static ModelBase mine;
 | 
			
		||||
    return mine.id();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ModelID wipe_tower_instance_id()
 | 
			
		||||
{
 | 
			
		||||
    static ModelBase mine;
 | 
			
		||||
    return mine.id();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Model& Model::assign_copy(const Model &rhs)
 | 
			
		||||
{
 | 
			
		||||
    this->copy_id(rhs);
 | 
			
		||||
| 
						 | 
				
			
			@ -87,6 +72,19 @@ void Model::assign_new_unique_ids_recursive()
 | 
			
		|||
        model_object->assign_new_unique_ids_recursive();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Model::update_links_bottom_up_recursive()
 | 
			
		||||
{
 | 
			
		||||
	for (std::pair<const t_model_material_id, ModelMaterial*> &kvp : this->materials)
 | 
			
		||||
		kvp.second->set_model(this);
 | 
			
		||||
	for (ModelObject *model_object : this->objects) {
 | 
			
		||||
		model_object->set_model(this);
 | 
			
		||||
		for (ModelInstance *model_instance : model_object->instances)
 | 
			
		||||
			model_instance->set_model_object(model_object);
 | 
			
		||||
		for (ModelVolume *model_volume : model_object->volumes)
 | 
			
		||||
			model_volume->set_model_object(model_object);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Model Model::read_from_file(const std::string &input_file, DynamicPrintConfig *config, bool add_default_instances)
 | 
			
		||||
{
 | 
			
		||||
    Model model;
 | 
			
		||||
| 
						 | 
				
			
			@ -221,7 +219,7 @@ bool Model::delete_object(ModelObject* object)
 | 
			
		|||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Model::delete_object(ModelID id)
 | 
			
		||||
bool Model::delete_object(ObjectID id)
 | 
			
		||||
{
 | 
			
		||||
    if (id.id != 0) {
 | 
			
		||||
        size_t idx = 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -622,11 +620,15 @@ ModelObject::~ModelObject()
 | 
			
		|||
// maintains the m_model pointer
 | 
			
		||||
ModelObject& ModelObject::assign_copy(const ModelObject &rhs)
 | 
			
		||||
{
 | 
			
		||||
    this->copy_id(rhs);
 | 
			
		||||
	assert(this->id().invalid() || this->id() == rhs.id());
 | 
			
		||||
	assert(this->config.id().invalid() || this->config.id() == rhs.config.id());
 | 
			
		||||
	this->copy_id(rhs);
 | 
			
		||||
 | 
			
		||||
    this->name                        = rhs.name;
 | 
			
		||||
    this->input_file                  = rhs.input_file;
 | 
			
		||||
    // Copies the config's ID
 | 
			
		||||
    this->config                      = rhs.config;
 | 
			
		||||
    assert(this->config.id() == rhs.config.id());
 | 
			
		||||
    this->sla_support_points          = rhs.sla_support_points;
 | 
			
		||||
    this->sla_points_status           = rhs.sla_points_status;
 | 
			
		||||
    this->layer_config_ranges         = rhs.layer_config_ranges;    // #ys_FIXME_experiment
 | 
			
		||||
| 
						 | 
				
			
			@ -658,11 +660,14 @@ ModelObject& ModelObject::assign_copy(const ModelObject &rhs)
 | 
			
		|||
// maintains the m_model pointer
 | 
			
		||||
ModelObject& ModelObject::assign_copy(ModelObject &&rhs)
 | 
			
		||||
{
 | 
			
		||||
	assert(this->id().invalid());
 | 
			
		||||
    this->copy_id(rhs);
 | 
			
		||||
 | 
			
		||||
    this->name                        = std::move(rhs.name);
 | 
			
		||||
    this->input_file                  = std::move(rhs.input_file);
 | 
			
		||||
    // Moves the config's ID
 | 
			
		||||
    this->config                      = std::move(rhs.config);
 | 
			
		||||
    assert(this->config.id() == rhs.config.id());
 | 
			
		||||
    this->sla_support_points          = std::move(rhs.sla_support_points);
 | 
			
		||||
    this->sla_points_status           = std::move(rhs.sla_points_status);
 | 
			
		||||
    this->layer_config_ranges         = std::move(rhs.layer_config_ranges); // #ys_FIXME_experiment
 | 
			
		||||
| 
						 | 
				
			
			@ -1070,11 +1075,11 @@ void ModelObject::mirror(Axis axis)
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// This method could only be called before the meshes of this ModelVolumes are not shared!
 | 
			
		||||
void ModelObject::scale_mesh(const Vec3d &versor)
 | 
			
		||||
void ModelObject::scale_mesh_after_creation(const Vec3d &versor)
 | 
			
		||||
{
 | 
			
		||||
    for (ModelVolume *v : this->volumes)
 | 
			
		||||
    {
 | 
			
		||||
        v->scale_geometry(versor);
 | 
			
		||||
        v->scale_geometry_after_creation(versor);
 | 
			
		||||
        v->set_offset(versor.cwiseProduct(v->get_offset()));
 | 
			
		||||
    }
 | 
			
		||||
    this->invalidate_bounding_box();
 | 
			
		||||
| 
						 | 
				
			
			@ -1191,13 +1196,19 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
 | 
			
		|||
            if (keep_upper && upper_mesh.facets_count() > 0) {
 | 
			
		||||
                ModelVolume* vol = upper->add_volume(upper_mesh);
 | 
			
		||||
                vol->name	= volume->name;
 | 
			
		||||
                vol->config = volume->config;
 | 
			
		||||
                // Don't copy the config's ID.
 | 
			
		||||
				static_cast<DynamicPrintConfig&>(vol->config) = static_cast<const DynamicPrintConfig&>(volume->config);
 | 
			
		||||
    			assert(vol->config.id().valid());
 | 
			
		||||
	    		assert(vol->config.id() != volume->config.id());
 | 
			
		||||
                vol->set_material(volume->material_id(), *volume->material());
 | 
			
		||||
            }
 | 
			
		||||
            if (keep_lower && lower_mesh.facets_count() > 0) {
 | 
			
		||||
                ModelVolume* vol = lower->add_volume(lower_mesh);
 | 
			
		||||
                vol->name	= volume->name;
 | 
			
		||||
                vol->config = volume->config;
 | 
			
		||||
                // Don't copy the config's ID.
 | 
			
		||||
				static_cast<DynamicPrintConfig&>(vol->config) = static_cast<const DynamicPrintConfig&>(volume->config);
 | 
			
		||||
    			assert(vol->config.id().valid());
 | 
			
		||||
	    		assert(vol->config.id() != volume->config.id());
 | 
			
		||||
                vol->set_material(volume->material_id(), *volume->material());
 | 
			
		||||
 | 
			
		||||
                // Compute the lower part instances' bounding boxes to figure out where to place
 | 
			
		||||
| 
						 | 
				
			
			@ -1272,7 +1283,10 @@ void ModelObject::split(ModelObjectPtrs* new_objects)
 | 
			
		|||
        // XXX: this seems to be the only real usage of m_model, maybe refactor this so that it's not needed?
 | 
			
		||||
        ModelObject* new_object = m_model->add_object();    
 | 
			
		||||
        new_object->name   = this->name;
 | 
			
		||||
        new_object->config = this->config;
 | 
			
		||||
        // Don't copy the config's ID.
 | 
			
		||||
		static_cast<DynamicPrintConfig&>(new_object->config) = static_cast<const DynamicPrintConfig&>(this->config);
 | 
			
		||||
		assert(new_object->config.id().valid());
 | 
			
		||||
		assert(new_object->config.id() != this->config.id());
 | 
			
		||||
        new_object->instances.reserve(this->instances.size());
 | 
			
		||||
        for (const ModelInstance *model_instance : this->instances)
 | 
			
		||||
            new_object->add_instance(*model_instance);
 | 
			
		||||
| 
						 | 
				
			
			@ -1565,9 +1579,9 @@ void ModelVolume::center_geometry_after_creation()
 | 
			
		|||
    if (!shift.isApprox(Vec3d::Zero()))
 | 
			
		||||
    {
 | 
			
		||||
    	if (m_mesh)
 | 
			
		||||
        	m_mesh->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2));
 | 
			
		||||
        	const_cast<TriangleMesh*>(m_mesh.get())->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2));
 | 
			
		||||
        if (m_convex_hull)
 | 
			
		||||
        	m_convex_hull->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2));
 | 
			
		||||
			const_cast<TriangleMesh*>(m_convex_hull.get())->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2));
 | 
			
		||||
        translate(shift);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1720,10 +1734,10 @@ void ModelVolume::mirror(Axis axis)
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// This method could only be called before the meshes of this ModelVolumes are not shared!
 | 
			
		||||
void ModelVolume::scale_geometry(const Vec3d& versor)
 | 
			
		||||
void ModelVolume::scale_geometry_after_creation(const Vec3d& versor)
 | 
			
		||||
{
 | 
			
		||||
    m_mesh->scale(versor);
 | 
			
		||||
    m_convex_hull->scale(versor);
 | 
			
		||||
	const_cast<TriangleMesh*>(m_mesh.get())->scale(versor);
 | 
			
		||||
	const_cast<TriangleMesh*>(m_convex_hull.get())->scale(versor);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ModelVolume::transform_this_mesh(const Transform3d &mesh_trafo, bool fix_left_handed)
 | 
			
		||||
| 
						 | 
				
			
			@ -1867,21 +1881,26 @@ bool model_volume_list_changed(const ModelObject &model_object_old, const ModelO
 | 
			
		|||
// Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique.
 | 
			
		||||
void check_model_ids_validity(const Model &model)
 | 
			
		||||
{
 | 
			
		||||
    std::set<ModelID> ids;
 | 
			
		||||
    auto check = [&ids](ModelID id) { 
 | 
			
		||||
        assert(id.id > 0);
 | 
			
		||||
    std::set<ObjectID> ids;
 | 
			
		||||
    auto check = [&ids](ObjectID id) { 
 | 
			
		||||
        assert(id.valid());
 | 
			
		||||
        assert(ids.find(id) == ids.end());
 | 
			
		||||
        ids.insert(id);
 | 
			
		||||
    };
 | 
			
		||||
    for (const ModelObject *model_object : model.objects) {
 | 
			
		||||
        check(model_object->id());
 | 
			
		||||
        for (const ModelVolume *model_volume : model_object->volumes)
 | 
			
		||||
        check(model_object->config.id());
 | 
			
		||||
        for (const ModelVolume *model_volume : model_object->volumes) {
 | 
			
		||||
            check(model_volume->id());
 | 
			
		||||
	        check(model_volume->config.id());
 | 
			
		||||
        }
 | 
			
		||||
        for (const ModelInstance *model_instance : model_object->instances)
 | 
			
		||||
            check(model_instance->id());
 | 
			
		||||
    }
 | 
			
		||||
    for (const auto mm : model.materials)
 | 
			
		||||
    for (const auto mm : model.materials) {
 | 
			
		||||
        check(mm.second->id());
 | 
			
		||||
        check(mm.second->config.id());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void check_model_ids_equal(const Model &model1, const Model &model2)
 | 
			
		||||
| 
						 | 
				
			
			@ -1892,10 +1911,13 @@ void check_model_ids_equal(const Model &model1, const Model &model2)
 | 
			
		|||
        const ModelObject &model_object1 = *model1.objects[idx_model];
 | 
			
		||||
        const ModelObject &model_object2 = *  model2.objects[idx_model];
 | 
			
		||||
        assert(model_object1.id() == model_object2.id());
 | 
			
		||||
        assert(model_object1.config.id() == model_object2.config.id());
 | 
			
		||||
        assert(model_object1.volumes.size() == model_object2.volumes.size());
 | 
			
		||||
        assert(model_object1.instances.size() == model_object2.instances.size());
 | 
			
		||||
        for (size_t i = 0; i < model_object1.volumes.size(); ++ i)
 | 
			
		||||
        for (size_t i = 0; i < model_object1.volumes.size(); ++ i) {
 | 
			
		||||
            assert(model_object1.volumes[i]->id() == model_object2.volumes[i]->id());
 | 
			
		||||
        	assert(model_object1.volumes[i]->config.id() == model_object2.volumes[i]->config.id());
 | 
			
		||||
        }
 | 
			
		||||
        for (size_t i = 0; i < model_object1.instances.size(); ++ i)
 | 
			
		||||
            assert(model_object1.instances[i]->id() == model_object2.instances[i]->id());
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -1906,9 +1928,22 @@ void check_model_ids_equal(const Model &model1, const Model &model2)
 | 
			
		|||
        for (; it1 != model1.materials.end(); ++ it1, ++ it2) {
 | 
			
		||||
            assert(it1->first == it2->first); // compare keys
 | 
			
		||||
            assert(it1->second->id() == it2->second->id());
 | 
			
		||||
        	assert(it1->second->config.id() == it2->second->config.id());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
#endif /* NDEBUG */
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#if 0
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::ModelObject)
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::ModelVolume)
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::ModelInstance)
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::Model)
 | 
			
		||||
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ObjectBase, Slic3r::ModelObject)
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ObjectBase, Slic3r::ModelVolume)
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ObjectBase, Slic3r::ModelInstance)
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ObjectBase, Slic3r::Model)
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			@ -2,19 +2,20 @@
 | 
			
		|||
#define slic3r_Model_hpp_
 | 
			
		||||
 | 
			
		||||
#include "libslic3r.h"
 | 
			
		||||
#include "PrintConfig.hpp"
 | 
			
		||||
#include "Geometry.hpp"
 | 
			
		||||
#include "Layer.hpp"
 | 
			
		||||
#include "ObjectID.hpp"
 | 
			
		||||
#include "Point.hpp"
 | 
			
		||||
#include "TriangleMesh.hpp"
 | 
			
		||||
#include "PrintConfig.hpp"
 | 
			
		||||
#include "Slicing.hpp"
 | 
			
		||||
#include "SLA/SLACommon.hpp"
 | 
			
		||||
#include "TriangleMesh.hpp"
 | 
			
		||||
 | 
			
		||||
#include <map>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <utility>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include "Geometry.hpp"
 | 
			
		||||
#include <libslic3r/SLA/SLACommon.hpp>
 | 
			
		||||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -26,6 +27,38 @@ class ModelVolume;
 | 
			
		|||
class Print;
 | 
			
		||||
class SLAPrint;
 | 
			
		||||
 | 
			
		||||
namespace UndoRedo {
 | 
			
		||||
	class StackImpl;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ModelConfig : public ObjectBase, public DynamicPrintConfig
 | 
			
		||||
{
 | 
			
		||||
private:
 | 
			
		||||
	friend class cereal::access;
 | 
			
		||||
	friend class UndoRedo::StackImpl;
 | 
			
		||||
	friend class ModelObject;
 | 
			
		||||
	friend class ModelVolume;
 | 
			
		||||
	friend class ModelMaterial;
 | 
			
		||||
 | 
			
		||||
    // Constructors to be only called by derived classes.
 | 
			
		||||
    // Default constructor to assign a unique ID.
 | 
			
		||||
    explicit ModelConfig() {}
 | 
			
		||||
    // Constructor with ignored int parameter to assign an invalid ID, to be replaced
 | 
			
		||||
    // by an existing ID copied from elsewhere.
 | 
			
		||||
    explicit ModelConfig(int) : ObjectBase(-1) {}
 | 
			
		||||
    // Copy constructor copies the ID.
 | 
			
		||||
	explicit ModelConfig(const ModelConfig &cfg) : ObjectBase(-1), DynamicPrintConfig(cfg) { this->copy_id(cfg); }
 | 
			
		||||
    // Move constructor copies the ID.
 | 
			
		||||
	explicit ModelConfig(ModelConfig &&cfg) : ObjectBase(-1), DynamicPrintConfig(std::move(cfg)) { this->copy_id(cfg); }
 | 
			
		||||
 | 
			
		||||
	ModelConfig& operator=(const ModelConfig &rhs) = default;
 | 
			
		||||
    ModelConfig& operator=(ModelConfig &&rhs) = default;
 | 
			
		||||
 | 
			
		||||
	template<class Archive> void serialize(Archive &ar) {
 | 
			
		||||
		ar(cereal::base_class<DynamicPrintConfig>(this));
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
typedef std::string t_model_material_id;
 | 
			
		||||
typedef std::string t_model_material_attribute;
 | 
			
		||||
typedef std::map<t_model_material_attribute, std::string> t_model_material_attributes;
 | 
			
		||||
| 
						 | 
				
			
			@ -35,74 +68,13 @@ typedef std::vector<ModelObject*> ModelObjectPtrs;
 | 
			
		|||
typedef std::vector<ModelVolume*> ModelVolumePtrs;
 | 
			
		||||
typedef std::vector<ModelInstance*> ModelInstancePtrs;
 | 
			
		||||
 | 
			
		||||
// Unique identifier of a Model, ModelObject, ModelVolume, ModelInstance or ModelMaterial.
 | 
			
		||||
// Used to synchronize the front end (UI) with the back end (BackgroundSlicingProcess / Print / PrintObject)
 | 
			
		||||
// Valid IDs are strictly positive (non zero).
 | 
			
		||||
// It is declared as an object, as some compilers (notably msvcc) consider a typedef size_t equivalent to size_t
 | 
			
		||||
// for parameter overload.
 | 
			
		||||
struct ModelID 
 | 
			
		||||
{
 | 
			
		||||
	ModelID(size_t id) : id(id) {}
 | 
			
		||||
 | 
			
		||||
	bool operator==(const ModelID &rhs) const { return this->id == rhs.id; }
 | 
			
		||||
	bool operator!=(const ModelID &rhs) const { return this->id != rhs.id; }
 | 
			
		||||
	bool operator< (const ModelID &rhs) const { return this->id <  rhs.id; }
 | 
			
		||||
	bool operator> (const ModelID &rhs) const { return this->id >  rhs.id; }
 | 
			
		||||
	bool operator<=(const ModelID &rhs) const { return this->id <= rhs.id; }
 | 
			
		||||
	bool operator>=(const ModelID &rhs) const { return this->id >= rhs.id; }
 | 
			
		||||
 | 
			
		||||
    bool valid() const { return id != 0; }
 | 
			
		||||
 | 
			
		||||
	size_t	id;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Unique object / instance ID for the wipe tower.
 | 
			
		||||
extern ModelID wipe_tower_object_id();
 | 
			
		||||
extern ModelID wipe_tower_instance_id();
 | 
			
		||||
 | 
			
		||||
// Base for Model, ModelObject, ModelVolume, ModelInstance or ModelMaterial to provide a unique ID
 | 
			
		||||
// to synchronize the front end (UI) with the back end (BackgroundSlicingProcess / Print / PrintObject).
 | 
			
		||||
// Achtung! The s_last_id counter is not thread safe, so it is expected, that the ModelBase derived instances
 | 
			
		||||
// are only instantiated from the main thread.
 | 
			
		||||
class ModelBase
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    ModelID     id() const { return m_id; }
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
    // Constructors to be only called by derived classes.
 | 
			
		||||
    // Default constructor to assign a unique ID.
 | 
			
		||||
    ModelBase() : m_id(generate_new_id()) {}
 | 
			
		||||
    // Constructor with ignored int parameter to assign an invalid ID, to be replaced
 | 
			
		||||
    // by an existing ID copied from elsewhere.
 | 
			
		||||
    ModelBase(int) : m_id(ModelID(0)) {}
 | 
			
		||||
 | 
			
		||||
    // Use with caution!
 | 
			
		||||
    void        set_new_unique_id() { m_id = generate_new_id(); }
 | 
			
		||||
    void        set_invalid_id()    { m_id = 0; }
 | 
			
		||||
    // Use with caution!
 | 
			
		||||
    void        copy_id(const ModelBase &rhs) { m_id = rhs.id(); }
 | 
			
		||||
 | 
			
		||||
    // Override this method if a ModelBase derived class owns other ModelBase derived instances.
 | 
			
		||||
    void        assign_new_unique_ids_recursive() { this->set_new_unique_id(); }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    ModelID                 m_id;
 | 
			
		||||
 | 
			
		||||
	static inline ModelID   generate_new_id() { return ModelID(++ s_last_id); }
 | 
			
		||||
    static size_t           s_last_id;
 | 
			
		||||
	
 | 
			
		||||
	friend ModelID wipe_tower_object_id();
 | 
			
		||||
	friend ModelID wipe_tower_instance_id();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#define MODELBASE_DERIVED_COPY_MOVE_CLONE(TYPE) \
 | 
			
		||||
#define OBJECTBASE_DERIVED_COPY_MOVE_CLONE(TYPE) \
 | 
			
		||||
    /* Copy a model, copy the IDs. The Print::apply() will call the TYPE::copy() method */ \
 | 
			
		||||
    /* to make a private copy for background processing. */ \
 | 
			
		||||
    static TYPE* new_copy(const TYPE &rhs)  { return new TYPE(rhs); } \
 | 
			
		||||
    static TYPE* new_copy(TYPE &&rhs)       { return new TYPE(std::move(rhs)); } \
 | 
			
		||||
    static TYPE  make_copy(const TYPE &rhs) { return TYPE(rhs); } \
 | 
			
		||||
    static TYPE  make_copy(TYPE &&rhs)      { return TYPE(std::move(rhs)); } \
 | 
			
		||||
    static TYPE* new_copy(const TYPE &rhs)  { auto *ret = new TYPE(rhs); assert(ret->id() == rhs.id()); return ret; } \
 | 
			
		||||
    static TYPE* new_copy(TYPE &&rhs)       { auto *ret = new TYPE(std::move(rhs)); assert(ret->id() == rhs.id()); return ret; } \
 | 
			
		||||
    static TYPE  make_copy(const TYPE &rhs) { TYPE ret(rhs); assert(ret.id() == rhs.id()); return ret; } \
 | 
			
		||||
    static TYPE  make_copy(TYPE &&rhs)      { TYPE ret(std::move(rhs)); assert(ret.id() == rhs.id()); return ret; } \
 | 
			
		||||
    TYPE&        assign_copy(const TYPE &rhs); \
 | 
			
		||||
    TYPE&        assign_copy(TYPE &&rhs); \
 | 
			
		||||
    /* Copy a TYPE, generate new IDs. The front end will use this call. */ \
 | 
			
		||||
| 
						 | 
				
			
			@ -110,52 +82,62 @@ private:
 | 
			
		|||
        /* Default constructor assigning an invalid ID. */ \
 | 
			
		||||
        auto obj = new TYPE(-1); \
 | 
			
		||||
        obj->assign_clone(rhs); \
 | 
			
		||||
        assert(obj->id().valid() && obj->id() != rhs.id()); \
 | 
			
		||||
        return obj; \
 | 
			
		||||
	} \
 | 
			
		||||
    TYPE         make_clone(const TYPE &rhs) { \
 | 
			
		||||
        /* Default constructor assigning an invalid ID. */ \
 | 
			
		||||
        TYPE obj(-1); \
 | 
			
		||||
        obj.assign_clone(rhs); \
 | 
			
		||||
        assert(obj.id().valid() && obj.id() != rhs.id()); \
 | 
			
		||||
        return obj; \
 | 
			
		||||
    } \
 | 
			
		||||
    TYPE&        assign_clone(const TYPE &rhs) { \
 | 
			
		||||
        this->assign_copy(rhs); \
 | 
			
		||||
        assert(this->id().valid() && this->id() == rhs.id()); \
 | 
			
		||||
        this->assign_new_unique_ids_recursive(); \
 | 
			
		||||
        assert(this->id().valid() && this->id() != rhs.id()); \
 | 
			
		||||
		return *this; \
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
#define MODELBASE_DERIVED_PRIVATE_COPY_MOVE(TYPE) \
 | 
			
		||||
private: \
 | 
			
		||||
    /* Private constructor with an unused int parameter will create a TYPE instance with an invalid ID. */ \
 | 
			
		||||
    explicit TYPE(int) : ModelBase(-1) {}; \
 | 
			
		||||
    void assign_new_unique_ids_recursive();
 | 
			
		||||
 | 
			
		||||
// Material, which may be shared across multiple ModelObjects of a single Model.
 | 
			
		||||
class ModelMaterial : public ModelBase
 | 
			
		||||
class ModelMaterial final : public ObjectBase
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    // Attributes are defined by the AMF file format, but they don't seem to be used by Slic3r for any purpose.
 | 
			
		||||
    t_model_material_attributes attributes;
 | 
			
		||||
    // Dynamic configuration storage for the object specific configuration values, overriding the global configuration.
 | 
			
		||||
    DynamicPrintConfig config;
 | 
			
		||||
    ModelConfig config;
 | 
			
		||||
 | 
			
		||||
    Model* get_model() const { return m_model; }
 | 
			
		||||
    void apply(const t_model_material_attributes &attributes)
 | 
			
		||||
        { this->attributes.insert(attributes.begin(), attributes.end()); }
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
    friend class Model;
 | 
			
		||||
	// Constructor, which assigns a new unique ID.
 | 
			
		||||
	ModelMaterial(Model *model) : m_model(model) {}
 | 
			
		||||
	// Copy constructor copies the ID and m_model!
 | 
			
		||||
	ModelMaterial(const ModelMaterial &rhs) = default;
 | 
			
		||||
	void set_model(Model *model) { m_model = model; }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    // Parent, owning this material.
 | 
			
		||||
    Model *m_model;
 | 
			
		||||
    
 | 
			
		||||
	ModelMaterial() = delete;
 | 
			
		||||
 | 
			
		||||
    // To be accessed by the Model.
 | 
			
		||||
    friend class Model;
 | 
			
		||||
	// Constructor, which assigns a new unique ID to the material and to its config.
 | 
			
		||||
	ModelMaterial(Model *model) : m_model(model) { assert(this->id().valid()); }
 | 
			
		||||
	// Copy constructor copies the IDs of the ModelMaterial and its config, and m_model!
 | 
			
		||||
	ModelMaterial(const ModelMaterial &rhs) = default;
 | 
			
		||||
	void set_model(Model *model) { m_model = model; }
 | 
			
		||||
	void set_new_unique_id() { ObjectBase::set_new_unique_id(); this->config.set_new_unique_id(); }
 | 
			
		||||
 | 
			
		||||
	// To be accessed by the serialization and Undo/Redo code.
 | 
			
		||||
	friend class cereal::access;
 | 
			
		||||
	friend class UndoRedo::StackImpl;
 | 
			
		||||
	// Create an object for deserialization, don't allocate IDs for ModelMaterial and its config.
 | 
			
		||||
	ModelMaterial() : ObjectBase(-1), config(-1), m_model(nullptr) { assert(this->id().invalid()); assert(this->config.id().invalid()); }
 | 
			
		||||
	template<class Archive> void serialize(Archive &ar) { 
 | 
			
		||||
		assert(this->id().invalid()); assert(this->config.id().invalid());
 | 
			
		||||
		ar(attributes, config);
 | 
			
		||||
		// assert(this->id().valid()); assert(this->config.id().valid());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Disabled methods.
 | 
			
		||||
	ModelMaterial(ModelMaterial &&rhs) = delete;
 | 
			
		||||
	ModelMaterial& operator=(const ModelMaterial &rhs) = delete;
 | 
			
		||||
    ModelMaterial& operator=(ModelMaterial &&rhs) = delete;
 | 
			
		||||
| 
						 | 
				
			
			@ -165,9 +147,8 @@ private:
 | 
			
		|||
// and possibly having multiple modifier volumes, each modifier volume with its set of parameters and materials.
 | 
			
		||||
// Each ModelObject may be instantiated mutliple times, each instance having different placement on the print bed,
 | 
			
		||||
// different rotation and different uniform scaling.
 | 
			
		||||
class ModelObject : public ModelBase
 | 
			
		||||
class ModelObject final : public ObjectBase
 | 
			
		||||
{
 | 
			
		||||
    friend class Model;
 | 
			
		||||
public:
 | 
			
		||||
    std::string             name;
 | 
			
		||||
    std::string             input_file;    // XXX: consider fs::path
 | 
			
		||||
| 
						 | 
				
			
			@ -178,7 +159,7 @@ public:
 | 
			
		|||
    // ModelVolumes are owned by this ModelObject.
 | 
			
		||||
    ModelVolumePtrs         volumes;
 | 
			
		||||
    // Configuration parameters specific to a single ModelObject, overriding the global Slic3r settings.
 | 
			
		||||
    DynamicPrintConfig      config;
 | 
			
		||||
    ModelConfig      		config;
 | 
			
		||||
    // 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.
 | 
			
		||||
| 
						 | 
				
			
			@ -264,7 +245,7 @@ public:
 | 
			
		|||
    void mirror(Axis axis);
 | 
			
		||||
 | 
			
		||||
    // This method could only be called before the meshes of this ModelVolumes are not shared!
 | 
			
		||||
    void scale_mesh(const Vec3d& versor);
 | 
			
		||||
    void scale_mesh_after_creation(const Vec3d& versor);
 | 
			
		||||
 | 
			
		||||
    size_t materials_count() const;
 | 
			
		||||
    size_t facets_count() const;
 | 
			
		||||
| 
						 | 
				
			
			@ -288,31 +269,53 @@ public:
 | 
			
		|||
 | 
			
		||||
    std::string get_export_filename() const;
 | 
			
		||||
 | 
			
		||||
    // Get full stl statistics for all object's meshes 
 | 
			
		||||
    // Get full stl statistics for all object's meshes
 | 
			
		||||
    stl_stats   get_object_stl_stats() const;
 | 
			
		||||
    // Get count of errors in the mesh( or all object's meshes, if volume index isn't defined) 
 | 
			
		||||
    // Get count of errors in the mesh( or all object's meshes, if volume index isn't defined)
 | 
			
		||||
    int         get_mesh_errors_count(const int vol_idx = -1) const;
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
    friend class Print;
 | 
			
		||||
    friend class SLAPrint;
 | 
			
		||||
    // Called by Print::apply() to set the model pointer after making a copy.
 | 
			
		||||
    void        set_model(Model *model) { m_model = model; }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    ModelObject(Model *model) : m_model(model), origin_translation(Vec3d::Zero()), 
 | 
			
		||||
        m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) {}
 | 
			
		||||
    ~ModelObject();
 | 
			
		||||
    friend class Model;
 | 
			
		||||
    // This constructor assigns new ID to this ModelObject and its config.
 | 
			
		||||
	explicit ModelObject(Model *model) : m_model(model), origin_translation(Vec3d::Zero()),
 | 
			
		||||
        m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false)
 | 
			
		||||
        { assert(this->id().valid()); }
 | 
			
		||||
	explicit ModelObject(int) : ObjectBase(-1), config(-1), m_model(nullptr), origin_translation(Vec3d::Zero()), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false)
 | 
			
		||||
		{ assert(this->id().invalid()); assert(this->config.id().invalid()); }
 | 
			
		||||
	~ModelObject();
 | 
			
		||||
	void assign_new_unique_ids_recursive() override;
 | 
			
		||||
 | 
			
		||||
    /* 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). */
 | 
			
		||||
    ModelObject(const ModelObject &rhs) : ModelBase(-1), m_model(rhs.m_model) { this->assign_copy(rhs); }
 | 
			
		||||
    explicit ModelObject(ModelObject &&rhs) : ModelBase(-1) { this->assign_copy(std::move(rhs)); }
 | 
			
		||||
    ModelObject& operator=(const ModelObject &rhs) { this->assign_copy(rhs); m_model = rhs.m_model; return *this; }
 | 
			
		||||
    ModelObject& operator=(ModelObject &&rhs) { this->assign_copy(std::move(rhs)); m_model = rhs.m_model; return *this; }
 | 
			
		||||
    // 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).
 | 
			
		||||
    ModelObject(const ModelObject &rhs) : ObjectBase(-1), config(-1), m_model(rhs.m_model) { 
 | 
			
		||||
    	assert(this->id().invalid()); assert(this->config.id().invalid()); assert(rhs.id() != rhs.config.id());
 | 
			
		||||
    	this->assign_copy(rhs);
 | 
			
		||||
    	assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
 | 
			
		||||
    	assert(this->id() == rhs.id()); assert(this->config.id() == rhs.config.id());
 | 
			
		||||
    }
 | 
			
		||||
    explicit ModelObject(ModelObject &&rhs) : ObjectBase(-1), config(-1) { 
 | 
			
		||||
    	assert(this->id().invalid()); assert(this->config.id().invalid()); assert(rhs.id() != rhs.config.id());
 | 
			
		||||
    	this->assign_copy(std::move(rhs));
 | 
			
		||||
    	assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
 | 
			
		||||
    	assert(this->id() == rhs.id()); assert(this->config.id() == rhs.config.id());
 | 
			
		||||
    }
 | 
			
		||||
    ModelObject& operator=(const ModelObject &rhs) { 
 | 
			
		||||
    	this->assign_copy(rhs); 
 | 
			
		||||
    	m_model = rhs.m_model;
 | 
			
		||||
    	assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
 | 
			
		||||
    	assert(this->id() == rhs.id()); assert(this->config.id() == rhs.config.id());
 | 
			
		||||
    	return *this;
 | 
			
		||||
    }
 | 
			
		||||
    ModelObject& operator=(ModelObject &&rhs) { 
 | 
			
		||||
    	this->assign_copy(std::move(rhs)); 
 | 
			
		||||
    	m_model = rhs.m_model;
 | 
			
		||||
    	assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
 | 
			
		||||
    	assert(this->id() == rhs.id()); assert(this->config.id() == rhs.config.id());
 | 
			
		||||
    	return *this;
 | 
			
		||||
    }
 | 
			
		||||
	void set_new_unique_id() { ObjectBase::set_new_unique_id(); this->config.set_new_unique_id(); }
 | 
			
		||||
 | 
			
		||||
    MODELBASE_DERIVED_COPY_MOVE_CLONE(ModelObject)
 | 
			
		||||
	MODELBASE_DERIVED_PRIVATE_COPY_MOVE(ModelObject)
 | 
			
		||||
    OBJECTBASE_DERIVED_COPY_MOVE_CLONE(ModelObject)
 | 
			
		||||
 | 
			
		||||
    // Parent object, owning this ModelObject. Set to nullptr here, so the macros above will have it initialized.
 | 
			
		||||
    Model                *m_model = nullptr;
 | 
			
		||||
| 
						 | 
				
			
			@ -323,7 +326,25 @@ private:
 | 
			
		|||
    mutable BoundingBoxf3 m_raw_bounding_box;
 | 
			
		||||
    mutable bool          m_raw_bounding_box_valid;
 | 
			
		||||
    mutable BoundingBoxf3 m_raw_mesh_bounding_box;
 | 
			
		||||
    mutable bool          m_raw_mesh_bounding_box_valid;    
 | 
			
		||||
    mutable bool          m_raw_mesh_bounding_box_valid;
 | 
			
		||||
 | 
			
		||||
    // Called by Print::apply() to set the model pointer after making a copy.
 | 
			
		||||
    friend class Print;
 | 
			
		||||
    friend class SLAPrint;
 | 
			
		||||
    void        set_model(Model *model) { m_model = model; }
 | 
			
		||||
 | 
			
		||||
    // Undo / Redo through the cereal serialization library
 | 
			
		||||
	friend class cereal::access;
 | 
			
		||||
	friend class UndoRedo::StackImpl;
 | 
			
		||||
	// Used for deserialization -> Don't allocate any IDs for the ModelObject or its config.
 | 
			
		||||
	ModelObject() : ObjectBase(-1), config(-1), m_model(nullptr), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) {
 | 
			
		||||
		assert(this->id().invalid()); assert(this->config.id().invalid());
 | 
			
		||||
	}
 | 
			
		||||
	template<class Archive> void serialize(Archive &ar) {
 | 
			
		||||
		ar(cereal::base_class<ObjectBase>(this));
 | 
			
		||||
		ar(name, input_file, instances, volumes, config, layer_config_ranges, layer_height_profile, sla_support_points, sla_points_status, origin_translation,
 | 
			
		||||
			m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Declared outside of ModelVolume, so it could be forward declared.
 | 
			
		||||
| 
						 | 
				
			
			@ -337,20 +358,20 @@ enum class ModelVolumeType : int {
 | 
			
		|||
 | 
			
		||||
// An object STL, or a modifier volume, over which a different set of parameters shall be applied.
 | 
			
		||||
// ModelVolume instances are owned by a ModelObject.
 | 
			
		||||
class ModelVolume : public ModelBase
 | 
			
		||||
class ModelVolume final : public ObjectBase
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    std::string         name;
 | 
			
		||||
    // The triangular model.
 | 
			
		||||
    const TriangleMesh& mesh() const { return *m_mesh.get(); }
 | 
			
		||||
    void                set_mesh(const TriangleMesh &mesh) { m_mesh = std::make_shared<TriangleMesh>(mesh); }
 | 
			
		||||
    void                set_mesh(TriangleMesh &&mesh) { m_mesh = std::make_shared<TriangleMesh>(std::move(mesh)); }
 | 
			
		||||
    void                set_mesh(std::shared_ptr<TriangleMesh> &mesh) { m_mesh = mesh; }
 | 
			
		||||
    void                set_mesh(std::unique_ptr<TriangleMesh> &&mesh) { m_mesh = std::move(mesh); }
 | 
			
		||||
	void				reset_mesh() { m_mesh = std::make_shared<TriangleMesh>(); }
 | 
			
		||||
    void                set_mesh(const TriangleMesh &mesh) { m_mesh = std::make_shared<const TriangleMesh>(mesh); }
 | 
			
		||||
    void                set_mesh(TriangleMesh &&mesh) { m_mesh = std::make_shared<const TriangleMesh>(std::move(mesh)); }
 | 
			
		||||
    void                set_mesh(std::shared_ptr<const TriangleMesh> &mesh) { m_mesh = mesh; }
 | 
			
		||||
    void                set_mesh(std::unique_ptr<const TriangleMesh> &&mesh) { m_mesh = std::move(mesh); }
 | 
			
		||||
	void				reset_mesh() { m_mesh = std::make_shared<const TriangleMesh>(); }
 | 
			
		||||
    // Configuration parameters specific to an object model geometry or a modifier volume, 
 | 
			
		||||
    // overriding the global Slic3r settings and the ModelObject settings.
 | 
			
		||||
    DynamicPrintConfig  config;
 | 
			
		||||
    ModelConfig  		config;
 | 
			
		||||
 | 
			
		||||
    // A parent object owning this modifier volume.
 | 
			
		||||
    ModelObject*        get_object() const { return this->object; };
 | 
			
		||||
| 
						 | 
				
			
			@ -385,7 +406,7 @@ public:
 | 
			
		|||
    void                mirror(Axis axis);
 | 
			
		||||
 | 
			
		||||
    // This method could only be called before the meshes of this ModelVolumes are not shared!
 | 
			
		||||
    void                scale_geometry(const Vec3d& versor);
 | 
			
		||||
    void                scale_geometry_after_creation(const Vec3d& versor);
 | 
			
		||||
 | 
			
		||||
    // Translates the mesh and the convex hull so that the origin of their vertices is in the center of this volume's bounding box.
 | 
			
		||||
    // Attention! This method may only be called just after ModelVolume creation! It must not be called once the TriangleMesh of this ModelVolume is shared!
 | 
			
		||||
| 
						 | 
				
			
			@ -431,66 +452,88 @@ public:
 | 
			
		|||
 | 
			
		||||
    const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); }
 | 
			
		||||
 | 
			
		||||
    using ModelBase::set_new_unique_id;
 | 
			
		||||
	void set_new_unique_id() { ObjectBase::set_new_unique_id(); this->config.set_new_unique_id(); }
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
	friend class Print;
 | 
			
		||||
    friend class SLAPrint;
 | 
			
		||||
    friend class Model;
 | 
			
		||||
	friend class ModelObject;
 | 
			
		||||
 | 
			
		||||
	// Copies IDs of both the ModelVolume and its config.
 | 
			
		||||
	explicit ModelVolume(const ModelVolume &rhs) = default;
 | 
			
		||||
    void     set_model_object(ModelObject *model_object) { object = model_object; }
 | 
			
		||||
	void 	 assign_new_unique_ids_recursive() override { ObjectBase::set_new_unique_id(); config.set_new_unique_id(); }
 | 
			
		||||
    void     transform_this_mesh(const Transform3d& t, bool fix_left_handed);
 | 
			
		||||
    void     transform_this_mesh(const Matrix3d& m, bool fix_left_handed);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    // Parent object owning this ModelVolume.
 | 
			
		||||
    ModelObject*                    object;
 | 
			
		||||
    ModelObject*                    	object;
 | 
			
		||||
    // The triangular model.
 | 
			
		||||
    std::shared_ptr<TriangleMesh>   m_mesh;
 | 
			
		||||
    std::shared_ptr<const TriangleMesh> m_mesh;
 | 
			
		||||
    // Is it an object to be printed, or a modifier volume?
 | 
			
		||||
    ModelVolumeType                 m_type;
 | 
			
		||||
    t_model_material_id             m_material_id;
 | 
			
		||||
    ModelVolumeType                 	m_type;
 | 
			
		||||
    t_model_material_id             	m_material_id;
 | 
			
		||||
    // The convex hull of this model's mesh.
 | 
			
		||||
    std::shared_ptr<TriangleMesh>   m_convex_hull;
 | 
			
		||||
    Geometry::Transformation        m_transformation;
 | 
			
		||||
    std::shared_ptr<const TriangleMesh> m_convex_hull;
 | 
			
		||||
    Geometry::Transformation        	m_transformation;
 | 
			
		||||
 | 
			
		||||
    // flag to optimize the checking if the volume is splittable
 | 
			
		||||
    //     -1   ->   is unknown value (before first cheking)
 | 
			
		||||
    //      0   ->   is not splittable
 | 
			
		||||
    //      1   ->   is splittable
 | 
			
		||||
    mutable int               m_is_splittable{ -1 };
 | 
			
		||||
    mutable int               		m_is_splittable{ -1 };
 | 
			
		||||
 | 
			
		||||
	ModelVolume(ModelObject *object, const TriangleMesh &mesh) : m_mesh(new TriangleMesh(mesh)), m_type(ModelVolumeType::MODEL_PART), object(object)
 | 
			
		||||
    {
 | 
			
		||||
		assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
 | 
			
		||||
        if (mesh.stl.stats.number_of_facets > 1)
 | 
			
		||||
            calculate_convex_hull();
 | 
			
		||||
    }
 | 
			
		||||
    ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) :
 | 
			
		||||
		m_mesh(new TriangleMesh(std::move(mesh))), m_convex_hull(new TriangleMesh(std::move(convex_hull))), m_type(ModelVolumeType::MODEL_PART), object(object) {}
 | 
			
		||||
		m_mesh(new TriangleMesh(std::move(mesh))), m_convex_hull(new TriangleMesh(std::move(convex_hull))), m_type(ModelVolumeType::MODEL_PART), object(object) {
 | 
			
		||||
		assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    // Copying an existing volume, therefore this volume will get a copy of the ID assigned.
 | 
			
		||||
    ModelVolume(ModelObject *object, const ModelVolume &other) :
 | 
			
		||||
        ModelBase(other), // copy the ID
 | 
			
		||||
        ObjectBase(other),
 | 
			
		||||
        name(other.name), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation)
 | 
			
		||||
    {
 | 
			
		||||
		assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
 | 
			
		||||
		assert(this->id() == other.id() && this->config.id() == other.config.id());
 | 
			
		||||
        this->set_material_id(other.material_id());
 | 
			
		||||
    }
 | 
			
		||||
    // Providing a new mesh, therefore this volume will get a new unique ID assigned.
 | 
			
		||||
    ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) :
 | 
			
		||||
        name(other.name), m_mesh(new TriangleMesh(std::move(mesh))), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation)
 | 
			
		||||
    {
 | 
			
		||||
		assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
 | 
			
		||||
		assert(this->id() != other.id() && this->config.id() == other.config.id());
 | 
			
		||||
        this->set_material_id(other.material_id());
 | 
			
		||||
        this->config.set_new_unique_id();
 | 
			
		||||
        if (mesh.stl.stats.number_of_facets > 1)
 | 
			
		||||
            calculate_convex_hull();
 | 
			
		||||
		assert(this->config.id().valid()); assert(this->config.id() != other.config.id()); assert(this->id() != this->config.id());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ModelVolume& operator=(ModelVolume &rhs) = delete;
 | 
			
		||||
 | 
			
		||||
	friend class cereal::access;
 | 
			
		||||
	friend class UndoRedo::StackImpl;
 | 
			
		||||
	// Used for deserialization, therefore no IDs are allocated.
 | 
			
		||||
	ModelVolume() : ObjectBase(-1), config(-1), object(nullptr) {
 | 
			
		||||
		assert(this->id().invalid()); assert(this->config.id().invalid());
 | 
			
		||||
	}
 | 
			
		||||
	template<class Archive> void serialize(Archive &ar) {
 | 
			
		||||
		ar(name, config, m_mesh, m_type, m_material_id, m_convex_hull, m_transformation, m_is_splittable);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// A single instance of a ModelObject.
 | 
			
		||||
// Knows the affine transformation of an object.
 | 
			
		||||
class ModelInstance : public ModelBase
 | 
			
		||||
class ModelInstance final : public ObjectBase
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    enum EPrintVolumeState : unsigned char
 | 
			
		||||
| 
						 | 
				
			
			@ -556,6 +599,7 @@ public:
 | 
			
		|||
protected:
 | 
			
		||||
    friend class Print;
 | 
			
		||||
    friend class SLAPrint;
 | 
			
		||||
    friend class Model;
 | 
			
		||||
    friend class ModelObject;
 | 
			
		||||
 | 
			
		||||
    explicit ModelInstance(const ModelInstance &rhs) = default;
 | 
			
		||||
| 
						 | 
				
			
			@ -566,15 +610,22 @@ private:
 | 
			
		|||
    ModelObject* object;
 | 
			
		||||
 | 
			
		||||
    // Constructor, which assigns a new unique ID.
 | 
			
		||||
    explicit ModelInstance(ModelObject *object) : object(object), print_volume_state(PVS_Inside) {}
 | 
			
		||||
    explicit ModelInstance(ModelObject *object) : object(object), print_volume_state(PVS_Inside) { assert(this->id().valid()); }
 | 
			
		||||
    // Constructor, which assigns a new unique ID.
 | 
			
		||||
    explicit ModelInstance(ModelObject *object, const ModelInstance &other) :
 | 
			
		||||
        m_transformation(other.m_transformation), object(object), print_volume_state(PVS_Inside) {}
 | 
			
		||||
        m_transformation(other.m_transformation), object(object), print_volume_state(PVS_Inside) { assert(this->id().valid() && this->id() != other.id()); }
 | 
			
		||||
 | 
			
		||||
    ModelInstance() = delete;
 | 
			
		||||
    explicit ModelInstance(ModelInstance &&rhs) = delete;
 | 
			
		||||
    ModelInstance& operator=(const ModelInstance &rhs) = delete;
 | 
			
		||||
    ModelInstance& operator=(ModelInstance &&rhs) = delete;
 | 
			
		||||
 | 
			
		||||
	friend class cereal::access;
 | 
			
		||||
	friend class UndoRedo::StackImpl;
 | 
			
		||||
	// Used for deserialization, therefore no IDs are allocated.
 | 
			
		||||
	ModelInstance() : ObjectBase(-1), object(nullptr) { assert(this->id().invalid()); }
 | 
			
		||||
	template<class Archive> void serialize(Archive &ar) {
 | 
			
		||||
		ar(m_transformation, print_volume_state);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// The print bed content.
 | 
			
		||||
| 
						 | 
				
			
			@ -582,7 +633,7 @@ private:
 | 
			
		|||
// and with multiple modifier meshes.
 | 
			
		||||
// A model groups multiple objects, each object having possibly multiple instances,
 | 
			
		||||
// all objects may share mutliple materials.
 | 
			
		||||
class Model : public ModelBase
 | 
			
		||||
class Model final : public ObjectBase
 | 
			
		||||
{
 | 
			
		||||
    static unsigned int s_auto_extruder_id;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -594,17 +645,17 @@ public:
 | 
			
		|||
    ModelObjectPtrs     objects;
 | 
			
		||||
    
 | 
			
		||||
    // Default constructor assigns a new ID to the model.
 | 
			
		||||
    Model() {}
 | 
			
		||||
    Model() { assert(this->id().valid()); }
 | 
			
		||||
    ~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).
 | 
			
		||||
    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; }
 | 
			
		||||
    Model& operator=(Model &&rhs) { this->assign_copy(std::move(rhs)); return *this; }
 | 
			
		||||
    /* 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) : ObjectBase(-1) { assert(this->id().invalid()); this->assign_copy(rhs); assert(this->id().valid()); assert(this->id() == rhs.id()); }
 | 
			
		||||
    explicit Model(Model &&rhs) : ObjectBase(-1) { assert(this->id().invalid()); this->assign_copy(std::move(rhs)); assert(this->id().valid()); assert(this->id() == rhs.id()); }
 | 
			
		||||
    Model& operator=(const Model &rhs) { this->assign_copy(rhs); assert(this->id().valid()); assert(this->id() == rhs.id()); return *this; }
 | 
			
		||||
    Model& operator=(Model &&rhs) { this->assign_copy(std::move(rhs)); assert(this->id().valid()); assert(this->id() == rhs.id()); return *this; }
 | 
			
		||||
 | 
			
		||||
    MODELBASE_DERIVED_COPY_MOVE_CLONE(Model)
 | 
			
		||||
    OBJECTBASE_DERIVED_COPY_MOVE_CLONE(Model)
 | 
			
		||||
 | 
			
		||||
    static Model read_from_file(const std::string &input_file, DynamicPrintConfig *config = nullptr, bool add_default_instances = true);
 | 
			
		||||
    static Model read_from_archive(const std::string &input_file, DynamicPrintConfig *config, bool add_default_instances = true);
 | 
			
		||||
| 
						 | 
				
			
			@ -615,7 +666,7 @@ public:
 | 
			
		|||
    ModelObject* add_object(const char *name, const char *path, TriangleMesh &&mesh);
 | 
			
		||||
    ModelObject* add_object(const ModelObject &other);
 | 
			
		||||
    void         delete_object(size_t idx);
 | 
			
		||||
    bool         delete_object(ModelID id);
 | 
			
		||||
    bool         delete_object(ObjectID id);
 | 
			
		||||
    bool         delete_object(ModelObject* object);
 | 
			
		||||
    void         clear_objects();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -633,24 +684,24 @@ public:
 | 
			
		|||
    BoundingBoxf3 bounding_box() const;
 | 
			
		||||
    // Set the print_volume_state of PrintObject::instances, 
 | 
			
		||||
    // return total number of printable objects.
 | 
			
		||||
    unsigned int update_print_volume_state(const BoundingBoxf3 &print_volume);
 | 
			
		||||
    unsigned int  update_print_volume_state(const BoundingBoxf3 &print_volume);
 | 
			
		||||
	// Returns true if any ModelObject was modified.
 | 
			
		||||
    bool center_instances_around_point(const Vec2d &point);
 | 
			
		||||
    void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); }
 | 
			
		||||
    TriangleMesh mesh() const;
 | 
			
		||||
    bool arrange_objects(coordf_t dist, const BoundingBoxf* bb = NULL);
 | 
			
		||||
    bool 		  center_instances_around_point(const Vec2d &point);
 | 
			
		||||
    void 		  translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); }
 | 
			
		||||
    TriangleMesh  mesh() const;
 | 
			
		||||
    bool 		  arrange_objects(coordf_t dist, const BoundingBoxf* bb = NULL);
 | 
			
		||||
    // Croaks if the duplicated objects do not fit the print bed.
 | 
			
		||||
    void duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL);
 | 
			
		||||
    void duplicate_objects(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL);
 | 
			
		||||
    void duplicate_objects_grid(size_t x, size_t y, coordf_t dist);
 | 
			
		||||
    void 		  duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL);
 | 
			
		||||
    void 	      duplicate_objects(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL);
 | 
			
		||||
    void 		  duplicate_objects_grid(size_t x, size_t y, coordf_t dist);
 | 
			
		||||
 | 
			
		||||
    bool looks_like_multipart_object() const;
 | 
			
		||||
    void convert_multipart_object(unsigned int max_extruders);
 | 
			
		||||
    bool 		  looks_like_multipart_object() const;
 | 
			
		||||
    void 		  convert_multipart_object(unsigned int max_extruders);
 | 
			
		||||
 | 
			
		||||
    // Ensures that the min z of the model is not negative
 | 
			
		||||
    void adjust_min_z();
 | 
			
		||||
    void 		  adjust_min_z();
 | 
			
		||||
 | 
			
		||||
    void print_info() const { for (const ModelObject *o : this->objects) o->print_info(); }
 | 
			
		||||
    void 		  print_info() const { for (const ModelObject *o : this->objects) o->print_info(); }
 | 
			
		||||
 | 
			
		||||
    static unsigned int get_auto_extruder_id(unsigned int max_extruders);
 | 
			
		||||
    static std::string get_auto_extruder_id_as_string(unsigned int max_extruders);
 | 
			
		||||
| 
						 | 
				
			
			@ -662,11 +713,19 @@ public:
 | 
			
		|||
    std::string         propose_export_file_name_and_path(const std::string &new_extension) const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    MODELBASE_DERIVED_PRIVATE_COPY_MOVE(Model)
 | 
			
		||||
	explicit Model(int) : ObjectBase(-1) { assert(this->id().invalid()); };
 | 
			
		||||
	void assign_new_unique_ids_recursive();
 | 
			
		||||
	void update_links_bottom_up_recursive();
 | 
			
		||||
 | 
			
		||||
	friend class cereal::access;
 | 
			
		||||
	friend class UndoRedo::StackImpl;
 | 
			
		||||
	template<class Archive> void serialize(Archive &ar) {
 | 
			
		||||
		ar(materials, objects);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#undef MODELBASE_DERIVED_COPY_MOVE_CLONE
 | 
			
		||||
#undef MODELBASE_DERIVED_PRIVATE_COPY_MOVE
 | 
			
		||||
#undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE
 | 
			
		||||
#undef OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE
 | 
			
		||||
 | 
			
		||||
// Test whether the two models contain the same number of ModelObjects with the same set of IDs
 | 
			
		||||
// ordered in the same order. In that case it is not necessary to kill the background processing.
 | 
			
		||||
| 
						 | 
				
			
			@ -686,6 +745,6 @@ void check_model_ids_validity(const Model &model);
 | 
			
		|||
void check_model_ids_equal(const Model &model1, const Model &model2);
 | 
			
		||||
#endif /* NDEBUG */
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
} // namespace Slic3r
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
#endif /* slic3r_Model_hpp_ */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										22
									
								
								src/libslic3r/ObjectID.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/libslic3r/ObjectID.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,22 @@
 | 
			
		|||
#include "ObjectID.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
 | 
			
		||||
size_t ObjectBase::s_last_id = 0;
 | 
			
		||||
 | 
			
		||||
// Unique object / instance ID for the wipe tower.
 | 
			
		||||
ObjectID wipe_tower_object_id()
 | 
			
		||||
{
 | 
			
		||||
    static ObjectBase mine;
 | 
			
		||||
    return mine.id();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ObjectID wipe_tower_instance_id()
 | 
			
		||||
{
 | 
			
		||||
    static ObjectBase mine;
 | 
			
		||||
    return mine.id();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace Slic3r
 | 
			
		||||
 | 
			
		||||
// CEREAL_REGISTER_TYPE(Slic3r::ObjectBase)
 | 
			
		||||
							
								
								
									
										93
									
								
								src/libslic3r/ObjectID.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								src/libslic3r/ObjectID.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,93 @@
 | 
			
		|||
#ifndef slic3r_ObjectID_hpp_
 | 
			
		||||
#define slic3r_ObjectID_hpp_
 | 
			
		||||
 | 
			
		||||
#include <cereal/access.hpp>
 | 
			
		||||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
 | 
			
		||||
namespace UndoRedo {
 | 
			
		||||
	class StackImpl;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Unique identifier of a mutable object accross the application.
 | 
			
		||||
// Used to synchronize the front end (UI) with the back end (BackgroundSlicingProcess / Print / PrintObject)
 | 
			
		||||
// (for Model, ModelObject, ModelVolume, ModelInstance or ModelMaterial classes)
 | 
			
		||||
// and to serialize / deserialize an object onto the Undo / Redo stack.
 | 
			
		||||
// Valid IDs are strictly positive (non zero).
 | 
			
		||||
// It is declared as an object, as some compilers (notably msvcc) consider a typedef size_t equivalent to size_t
 | 
			
		||||
// for parameter overload.
 | 
			
		||||
class ObjectID
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	ObjectID(size_t id) : id(id) {}
 | 
			
		||||
	// Default constructor constructs an invalid ObjectID.
 | 
			
		||||
	ObjectID() : id(0) {}
 | 
			
		||||
 | 
			
		||||
	bool operator==(const ObjectID &rhs) const { return this->id == rhs.id; }
 | 
			
		||||
	bool operator!=(const ObjectID &rhs) const { return this->id != rhs.id; }
 | 
			
		||||
	bool operator< (const ObjectID &rhs) const { return this->id <  rhs.id; }
 | 
			
		||||
	bool operator> (const ObjectID &rhs) const { return this->id >  rhs.id; }
 | 
			
		||||
	bool operator<=(const ObjectID &rhs) const { return this->id <= rhs.id; }
 | 
			
		||||
	bool operator>=(const ObjectID &rhs) const { return this->id >= rhs.id; }
 | 
			
		||||
 | 
			
		||||
    bool valid() const { return id != 0; }
 | 
			
		||||
    bool invalid() const { return id == 0; }
 | 
			
		||||
 | 
			
		||||
	size_t	id;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend class cereal::access;
 | 
			
		||||
	template<class Archive> void serialize(Archive &ar) { ar(id); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Base for Model, ModelObject, ModelVolume, ModelInstance or ModelMaterial to provide a unique ID
 | 
			
		||||
// to synchronize the front end (UI) with the back end (BackgroundSlicingProcess / Print / PrintObject).
 | 
			
		||||
// Achtung! The s_last_id counter is not thread safe, so it is expected, that the ObjectBase derived instances
 | 
			
		||||
// are only instantiated from the main thread.
 | 
			
		||||
class ObjectBase
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    ObjectID     id() const { return m_id; }
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
    // Constructors to be only called by derived classes.
 | 
			
		||||
    // Default constructor to assign a unique ID.
 | 
			
		||||
    ObjectBase() : m_id(generate_new_id()) {}
 | 
			
		||||
    // Constructor with ignored int parameter to assign an invalid ID, to be replaced
 | 
			
		||||
    // by an existing ID copied from elsewhere.
 | 
			
		||||
    ObjectBase(int) : m_id(ObjectID(0)) {}
 | 
			
		||||
	// The class tree will have virtual tables and type information.
 | 
			
		||||
	virtual ~ObjectBase() {}
 | 
			
		||||
 | 
			
		||||
    // Use with caution!
 | 
			
		||||
    void        set_new_unique_id() { m_id = generate_new_id(); }
 | 
			
		||||
    void        set_invalid_id()    { m_id = 0; }
 | 
			
		||||
    // Use with caution!
 | 
			
		||||
    void        copy_id(const ObjectBase &rhs) { m_id = rhs.id(); }
 | 
			
		||||
 | 
			
		||||
    // Override this method if a ObjectBase derived class owns other ObjectBase derived instances.
 | 
			
		||||
    virtual void assign_new_unique_ids_recursive() { this->set_new_unique_id(); }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    ObjectID                m_id;
 | 
			
		||||
 | 
			
		||||
	static inline ObjectID  generate_new_id() { return ObjectID(++ s_last_id); }
 | 
			
		||||
    static size_t           s_last_id;
 | 
			
		||||
	
 | 
			
		||||
	friend ObjectID wipe_tower_object_id();
 | 
			
		||||
	friend ObjectID wipe_tower_instance_id();
 | 
			
		||||
 | 
			
		||||
	friend class cereal::access;
 | 
			
		||||
	friend class Slic3r::UndoRedo::StackImpl;
 | 
			
		||||
	template<class Archive> void serialize(Archive &ar) { ar(m_id); }
 | 
			
		||||
    ObjectBase(const ObjectID id) : m_id(id) {}
 | 
			
		||||
  	template<class Archive> static void load_and_construct(Archive & ar, cereal::construct<ObjectBase> &construct) { ObjectID id; ar(id); construct(id); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Unique object / instance ID for the wipe tower.
 | 
			
		||||
extern ObjectID wipe_tower_object_id();
 | 
			
		||||
extern ObjectID wipe_tower_instance_id();
 | 
			
		||||
 | 
			
		||||
} // namespace Slic3r
 | 
			
		||||
 | 
			
		||||
#endif /* slic3r_ObjectID_hpp_ */
 | 
			
		||||
| 
						 | 
				
			
			@ -62,8 +62,8 @@ inline Vec2i64 to_2d(const Vec3i64 &pt3) { return Vec2i64(pt3(0), pt3(1)); }
 | 
			
		|||
inline Vec2f   to_2d(const Vec3f   &pt3) { return Vec2f  (pt3(0), pt3(1)); }
 | 
			
		||||
inline Vec2d   to_2d(const Vec3d   &pt3) { return Vec2d  (pt3(0), pt3(1)); }
 | 
			
		||||
 | 
			
		||||
inline Vec3d to_3d(const Vec2d &v, double z) { return Vec3d(v(0), v(1), z); }
 | 
			
		||||
inline Vec3f to_3d(const Vec2f &v, float z) { return Vec3f(v(0), v(1), z); }
 | 
			
		||||
inline Vec3d   to_3d(const Vec2d &v, double z) { return Vec3d(v(0), v(1), z); }
 | 
			
		||||
inline Vec3f   to_3d(const Vec2f &v, float z) { return Vec3f(v(0), v(1), z); }
 | 
			
		||||
inline Vec3i64 to_3d(const Vec2i64 &v, float z) { return Vec3i64(int64_t(v(0)), int64_t(v(1)), int64_t(z)); }
 | 
			
		||||
inline Vec3crd to_3d(const Vec3crd &p, coord_t z) { return Vec3crd(p(0), p(1), z); }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -291,4 +291,21 @@ namespace boost { namespace polygon {
 | 
			
		|||
} }
 | 
			
		||||
// end Boost
 | 
			
		||||
 | 
			
		||||
// Serialization through the Cereal library
 | 
			
		||||
namespace cereal {
 | 
			
		||||
//	template<class Archive> void serialize(Archive& archive, Slic3r::Vec2crd &v) { archive(v.x(), v.y()); }
 | 
			
		||||
//	template<class Archive> void serialize(Archive& archive, Slic3r::Vec3crd &v) { archive(v.x(), v.y(), v.z()); }
 | 
			
		||||
	template<class Archive> void serialize(Archive& archive, Slic3r::Vec2i   &v) { archive(v.x(), v.y()); }
 | 
			
		||||
	template<class Archive> void serialize(Archive& archive, Slic3r::Vec3i   &v) { archive(v.x(), v.y(), v.z()); }
 | 
			
		||||
//	template<class Archive> void serialize(Archive& archive, Slic3r::Vec2i64 &v) { archive(v.x(), v.y()); }
 | 
			
		||||
//	template<class Archive> void serialize(Archive& archive, Slic3r::Vec3i64 &v) { archive(v.x(), v.y(), v.z()); }
 | 
			
		||||
	template<class Archive> void serialize(Archive& archive, Slic3r::Vec2f   &v) { archive(v.x(), v.y()); }
 | 
			
		||||
	template<class Archive> void serialize(Archive& archive, Slic3r::Vec3f   &v) { archive(v.x(), v.y(), v.z()); }
 | 
			
		||||
	template<class Archive> void serialize(Archive& archive, Slic3r::Vec2d   &v) { archive(v.x(), v.y()); }
 | 
			
		||||
	template<class Archive> void serialize(Archive& archive, Slic3r::Vec3d   &v) { archive(v.x(), v.y(), v.z()); }
 | 
			
		||||
 | 
			
		||||
	template<class Archive> void load(Archive& archive, Slic3r::Matrix2f &m) { archive.loadBinary((char*)m.data(), sizeof(float) * 4); }
 | 
			
		||||
	template<class Archive> void save(Archive& archive, Slic3r::Matrix2f &m) { archive.saveBinary((char*)m.data(), sizeof(float) * 4); }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -392,7 +392,7 @@ static inline void model_volume_list_copy_configs(ModelObject &model_object_dst,
 | 
			
		|||
        assert(mv_src.id() == mv_dst.id());
 | 
			
		||||
        // Copy the ModelVolume data.
 | 
			
		||||
        mv_dst.name   = mv_src.name;
 | 
			
		||||
        mv_dst.config = mv_src.config;
 | 
			
		||||
		static_cast<DynamicPrintConfig&>(mv_dst.config) = static_cast<const DynamicPrintConfig&>(mv_src.config);
 | 
			
		||||
        //FIXME what to do with the materials?
 | 
			
		||||
        // mv_dst.m_material_id = mv_src.m_material_id;
 | 
			
		||||
        ++ i_src;
 | 
			
		||||
| 
						 | 
				
			
			@ -587,10 +587,10 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co
 | 
			
		|||
            Moved,
 | 
			
		||||
            Deleted,
 | 
			
		||||
        };
 | 
			
		||||
        ModelObjectStatus(ModelID id, Status status = Unknown) : id(id), status(status) {}
 | 
			
		||||
        ModelID     id;
 | 
			
		||||
        Status      status;
 | 
			
		||||
        LayerRanges layer_ranges;
 | 
			
		||||
        ModelObjectStatus(ObjectID id, Status status = Unknown) : id(id), status(status) {}
 | 
			
		||||
		ObjectID     id;
 | 
			
		||||
        Status       status;
 | 
			
		||||
        LayerRanges  layer_ranges;
 | 
			
		||||
        // Search by id.
 | 
			
		||||
        bool operator<(const ModelObjectStatus &rhs) const { return id < rhs.id; }
 | 
			
		||||
    };
 | 
			
		||||
| 
						 | 
				
			
			@ -695,9 +695,9 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co
 | 
			
		|||
            print_object(print_object),
 | 
			
		||||
            trafo(print_object->trafo()),
 | 
			
		||||
            status(status) {}
 | 
			
		||||
        PrintObjectStatus(ModelID id) : id(id), print_object(nullptr), trafo(Transform3d::Identity()), status(Unknown) {}
 | 
			
		||||
        PrintObjectStatus(ObjectID id) : id(id), print_object(nullptr), trafo(Transform3d::Identity()), status(Unknown) {}
 | 
			
		||||
        // ID of the ModelObject & PrintObject
 | 
			
		||||
        ModelID          id;
 | 
			
		||||
        ObjectID          id;
 | 
			
		||||
        // Pointer to the old PrintObject
 | 
			
		||||
        PrintObject     *print_object;
 | 
			
		||||
        // Trafo generated with model_object->world_matrix(true) 
 | 
			
		||||
| 
						 | 
				
			
			@ -757,7 +757,7 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co
 | 
			
		|||
            // Synchronize Object's config.
 | 
			
		||||
            bool object_config_changed = model_object.config != model_object_new.config;
 | 
			
		||||
			if (object_config_changed)
 | 
			
		||||
                model_object.config = model_object_new.config;
 | 
			
		||||
				static_cast<DynamicPrintConfig&>(model_object.config) = static_cast<const DynamicPrintConfig&>(model_object_new.config);
 | 
			
		||||
            if (! object_diff.empty() || object_config_changed) {
 | 
			
		||||
                PrintObjectConfig new_config = PrintObject::object_config_from_model_object(m_default_object_config, model_object, num_extruders);
 | 
			
		||||
                auto range = print_object_status.equal_range(PrintObjectStatus(model_object.id()));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -246,7 +246,7 @@ public:
 | 
			
		|||
    struct TaskParams {
 | 
			
		||||
		TaskParams() : single_model_object(0), single_model_instance_only(false), to_object_step(-1), to_print_step(-1) {}
 | 
			
		||||
        // If non-empty, limit the processing to this ModelObject.
 | 
			
		||||
        ModelID                 single_model_object;
 | 
			
		||||
        ObjectID                single_model_object;
 | 
			
		||||
		// If set, only process single_model_object. Otherwise process everything, but single_model_object first.
 | 
			
		||||
		bool					single_model_instance_only;
 | 
			
		||||
        // If non-negative, stop processing at the successive object step.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -406,10 +406,13 @@ void PrintConfigDef::init_fff_params()
 | 
			
		|||
    def->set_default_value(new ConfigOptionEnum<InfillPattern>(ipRectilinear));
 | 
			
		||||
 | 
			
		||||
    def = this->add("bottom_fill_pattern", coEnum);
 | 
			
		||||
    *def = *def_top_fill_pattern;
 | 
			
		||||
    def->label = L("Bottom fill pattern");
 | 
			
		||||
    def->category = L("Infill");
 | 
			
		||||
    def->tooltip = L("Fill pattern for bottom infill. This only affects the bottom external visible layer, and not its adjacent solid shells.");
 | 
			
		||||
    def->cli = "bottom-fill-pattern|external-fill-pattern|solid-fill-pattern";
 | 
			
		||||
    def->enum_keys_map = &ConfigOptionEnum<InfillPattern>::get_enum_values();
 | 
			
		||||
    def->enum_values = def_top_fill_pattern->enum_values;
 | 
			
		||||
    def->aliases = def_top_fill_pattern->aliases;
 | 
			
		||||
    def->set_default_value(new ConfigOptionEnum<InfillPattern>(ipRectilinear));
 | 
			
		||||
 | 
			
		||||
    def = this->add("external_perimeter_extrusion_width", coFloatOrPercent);
 | 
			
		||||
| 
						 | 
				
			
			@ -3250,3 +3253,7 @@ void DynamicPrintAndCLIConfig::handle_legacy(t_config_option_key &opt_key, std::
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#include <cereal/types/polymorphic.hpp>
 | 
			
		||||
CEREAL_REGISTER_TYPE(Slic3r::DynamicPrintConfig)
 | 
			
		||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::DynamicConfig, Slic3r::DynamicPrintConfig) 
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1218,6 +1218,8 @@ private:
 | 
			
		|||
            this->options.insert(cli_actions_config_def.options.begin(), cli_actions_config_def.options.end());
 | 
			
		||||
            this->options.insert(cli_transform_config_def.options.begin(), cli_transform_config_def.options.end());
 | 
			
		||||
            this->options.insert(cli_misc_config_def.options.begin(), cli_misc_config_def.options.end());
 | 
			
		||||
            for (const auto &kvp : this->options)
 | 
			
		||||
            	this->by_serialization_key_ordinal[kvp.second.serialization_key_ordinal] = &kvp.second;
 | 
			
		||||
        }
 | 
			
		||||
        // Do not release the default values, they are handled by print_config_def & cli_actions_config_def / cli_transform_config_def / cli_misc_config_def.
 | 
			
		||||
        ~PrintAndCLIConfigDef() { this->options.clear(); }
 | 
			
		||||
| 
						 | 
				
			
			@ -1227,4 +1229,38 @@ private:
 | 
			
		|||
 | 
			
		||||
} // namespace Slic3r
 | 
			
		||||
 | 
			
		||||
// Serialization through the Cereal library
 | 
			
		||||
namespace cereal {
 | 
			
		||||
	// Let cereal know that there are load / save non-member functions declared for DynamicPrintConfig, ignore serialize / load / save from parent class DynamicConfig.
 | 
			
		||||
	template <class Archive> struct specialize<Archive, Slic3r::DynamicPrintConfig, cereal::specialization::non_member_load_save> {};
 | 
			
		||||
 | 
			
		||||
	template<class Archive> void load(Archive& archive, Slic3r::DynamicPrintConfig &config) 
 | 
			
		||||
	{
 | 
			
		||||
		size_t cnt;
 | 
			
		||||
		archive(cnt);
 | 
			
		||||
		config.clear();
 | 
			
		||||
		for (size_t i = 0; i < cnt; ++ i) {
 | 
			
		||||
			size_t serialization_key_ordinal;
 | 
			
		||||
			archive(serialization_key_ordinal);
 | 
			
		||||
			assert(serialization_key_ordinal > 0);
 | 
			
		||||
			auto it = Slic3r::print_config_def.by_serialization_key_ordinal.find(serialization_key_ordinal);
 | 
			
		||||
			assert(it != Slic3r::print_config_def.by_serialization_key_ordinal.end());
 | 
			
		||||
			config.set_key_value(it->second->opt_key, it->second->load_option_from_archive(archive));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	template<class Archive> void save(Archive& archive, const Slic3r::DynamicPrintConfig &config)
 | 
			
		||||
	{
 | 
			
		||||
		size_t cnt = config.size();
 | 
			
		||||
		archive(cnt);
 | 
			
		||||
		for (auto it = config.cbegin(); it != config.cend(); ++it) {
 | 
			
		||||
			const Slic3r::ConfigOptionDef* optdef = Slic3r::print_config_def.get(it->first);
 | 
			
		||||
			assert(optdef != nullptr);
 | 
			
		||||
			assert(optdef->serialization_key_ordinal > 0);
 | 
			
		||||
			archive(optdef->serialization_key_ordinal);
 | 
			
		||||
			optdef->save_option_to_archive(archive, it->second.get());
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -43,6 +43,8 @@ struct SupportPoint {
 | 
			
		|||
 | 
			
		||||
    bool operator==(const SupportPoint& sp) const { return (pos==sp.pos) && head_front_radius==sp.head_front_radius && is_new_island==sp.is_new_island; }
 | 
			
		||||
    bool operator!=(const SupportPoint& sp) const { return !(sp == (*this)); }
 | 
			
		||||
 | 
			
		||||
	template<class Archive> void serialize(Archive &ar) { ar(pos, head_front_radius, is_new_island); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// An index-triangle structure for libIGL functions. Also serves as an
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -211,8 +211,8 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf
 | 
			
		|||
            Moved,
 | 
			
		||||
            Deleted,
 | 
			
		||||
        };
 | 
			
		||||
        ModelObjectStatus(ModelID id, Status status = Unknown) : id(id), status(status) {}
 | 
			
		||||
        ModelID                 id;
 | 
			
		||||
        ModelObjectStatus(ObjectID id, Status status = Unknown) : id(id), status(status) {}
 | 
			
		||||
        ObjectID                id;
 | 
			
		||||
        Status                  status;
 | 
			
		||||
        // Search by id.
 | 
			
		||||
        bool operator<(const ModelObjectStatus &rhs) const { return id < rhs.id; }
 | 
			
		||||
| 
						 | 
				
			
			@ -315,9 +315,9 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf
 | 
			
		|||
            print_object(print_object),
 | 
			
		||||
            trafo(print_object->trafo()),
 | 
			
		||||
            status(status) {}
 | 
			
		||||
        PrintObjectStatus(ModelID id) : id(id), print_object(nullptr), trafo(Transform3d::Identity()), status(Unknown) {}
 | 
			
		||||
        PrintObjectStatus(ObjectID id) : id(id), print_object(nullptr), trafo(Transform3d::Identity()), status(Unknown) {}
 | 
			
		||||
        // ID of the ModelObject & PrintObject
 | 
			
		||||
        ModelID          id;
 | 
			
		||||
        ObjectID         id;
 | 
			
		||||
        // Pointer to the old PrintObject
 | 
			
		||||
        SLAPrintObject  *print_object;
 | 
			
		||||
        // Trafo generated with model_object->world_matrix(true)
 | 
			
		||||
| 
						 | 
				
			
			@ -367,7 +367,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf
 | 
			
		|||
                // Synchronize Object's config.
 | 
			
		||||
                bool object_config_changed = model_object.config != model_object_new.config;
 | 
			
		||||
                if (object_config_changed)
 | 
			
		||||
                    model_object.config = model_object_new.config;
 | 
			
		||||
					static_cast<DynamicPrintConfig&>(model_object.config) = static_cast<const DynamicPrintConfig&>(model_object_new.config);
 | 
			
		||||
                if (! object_diff.empty() || object_config_changed) {
 | 
			
		||||
                    SLAPrintObjectConfig new_config = m_default_object_config;
 | 
			
		||||
                    normalize_and_apply_config(new_config, model_object.config);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -54,10 +54,10 @@ public:
 | 
			
		|||
    bool                        is_left_handed() const { return m_left_handed; }
 | 
			
		||||
 | 
			
		||||
    struct Instance {
 | 
			
		||||
        Instance(ModelID instance_id, const Point &shift, float rotation) : instance_id(instance_id), shift(shift), rotation(rotation) {}
 | 
			
		||||
        Instance(ObjectID instance_id, const Point &shift, float rotation) : instance_id(instance_id), shift(shift), rotation(rotation) {}
 | 
			
		||||
        bool operator==(const Instance &rhs) const { return this->instance_id == rhs.instance_id && this->shift == rhs.shift && this->rotation == rhs.rotation; }
 | 
			
		||||
        // ID of the corresponding ModelInstance.
 | 
			
		||||
        ModelID instance_id;
 | 
			
		||||
        ObjectID instance_id;
 | 
			
		||||
        // Slic3r::Point objects in scaled G-code coordinates
 | 
			
		||||
        Point 	shift;
 | 
			
		||||
        // Rotation along the Z axis, in radians.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -175,4 +175,9 @@ extern int generate_layer_height_texture(
 | 
			
		|||
 | 
			
		||||
}; // namespace Slic3r
 | 
			
		||||
 | 
			
		||||
namespace cereal
 | 
			
		||||
{
 | 
			
		||||
	template<class Archive> void serialize(Archive& archive, Slic3r::t_layer_height_range &lhr) { archive(lhr.first, lhr.second); }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* slic3r_Slicing_hpp_ */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -195,4 +195,24 @@ TriangleMesh make_sphere(double rho, double fa=(2*PI/360));
 | 
			
		|||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Serialization through the Cereal library
 | 
			
		||||
#include <cereal/access.hpp>
 | 
			
		||||
namespace cereal {
 | 
			
		||||
	template <class Archive> struct specialize<Archive, Slic3r::TriangleMesh, cereal::specialization::non_member_load_save> {};
 | 
			
		||||
	template<class Archive> void load(Archive &archive, Slic3r::TriangleMesh &mesh) {
 | 
			
		||||
        stl_file &stl = mesh.stl;
 | 
			
		||||
        stl.stats.type = inmemory;
 | 
			
		||||
		archive(stl.stats.number_of_facets, stl.stats.original_num_facets);
 | 
			
		||||
        stl_allocate(&stl);
 | 
			
		||||
		archive.loadBinary((char*)stl.facet_start.data(), stl.facet_start.size() * 50);
 | 
			
		||||
        stl_get_size(&stl);
 | 
			
		||||
        mesh.repair();
 | 
			
		||||
	}
 | 
			
		||||
	template<class Archive> void save(Archive &archive, const Slic3r::TriangleMesh &mesh) {
 | 
			
		||||
		const stl_file& stl = mesh.stl;
 | 
			
		||||
		archive(stl.stats.number_of_facets, stl.stats.original_num_facets);
 | 
			
		||||
		archive.saveBinary((char*)stl.facet_start.data(), stl.facet_start.size() * 50);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -182,7 +182,7 @@ class ScopeGuard
 | 
			
		|||
public:
 | 
			
		||||
    typedef std::function<void()> Closure;
 | 
			
		||||
private:
 | 
			
		||||
    bool committed;
 | 
			
		||||
//    bool committed;
 | 
			
		||||
    Closure closure;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -100,7 +100,10 @@
 | 
			
		|||
#include <tbb/task_scheduler_init.h>
 | 
			
		||||
 | 
			
		||||
#include <Eigen/Dense>
 | 
			
		||||
#include <Eigen/Geometry> 
 | 
			
		||||
#include <Eigen/Geometry>
 | 
			
		||||
 | 
			
		||||
#include <cereal/access.hpp>
 | 
			
		||||
#include <cereal/types/base_class.hpp>
 | 
			
		||||
 | 
			
		||||
#include "BoundingBox.hpp"
 | 
			
		||||
#include "ClipperUtils.hpp"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -148,6 +148,8 @@ set(SLIC3R_GUI_SOURCES
 | 
			
		|||
    Utils/PresetUpdater.hpp
 | 
			
		||||
    Utils/Time.cpp
 | 
			
		||||
    Utils/Time.hpp
 | 
			
		||||
    Utils/UndoRedo.cpp
 | 
			
		||||
    Utils/UndoRedo.hpp
 | 
			
		||||
    Utils/HexFile.cpp
 | 
			
		||||
    Utils/HexFile.hpp
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -161,7 +163,7 @@ endif ()
 | 
			
		|||
 | 
			
		||||
add_library(libslic3r_gui STATIC ${SLIC3R_GUI_SOURCES})
 | 
			
		||||
 | 
			
		||||
target_link_libraries(libslic3r_gui libslic3r avrdude imgui ${GLEW_LIBRARIES})
 | 
			
		||||
target_link_libraries(libslic3r_gui libslic3r avrdude cereal imgui ${GLEW_LIBRARIES})
 | 
			
		||||
if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
 | 
			
		||||
    add_precompiled_header(libslic3r_gui pchheader.hpp FORCEINCLUDE)
 | 
			
		||||
endif ()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1188,6 +1188,8 @@ wxDEFINE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent);
 | 
			
		|||
wxDEFINE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent);
 | 
			
		||||
wxDEFINE_EVENT(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, wxKeyEvent);
 | 
			
		||||
wxDEFINE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent);
 | 
			
		||||
wxDEFINE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent);
 | 
			
		||||
wxDEFINE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent);
 | 
			
		||||
 | 
			
		||||
GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar)
 | 
			
		||||
    : m_canvas(canvas)
 | 
			
		||||
| 
						 | 
				
			
			@ -1791,7 +1793,7 @@ std::vector<int> GLCanvas3D::load_object(const Model& model, int obj_idx)
 | 
			
		|||
void GLCanvas3D::mirror_selection(Axis axis)
 | 
			
		||||
{
 | 
			
		||||
    m_selection.mirror(axis);
 | 
			
		||||
    do_mirror();
 | 
			
		||||
    do_mirror("Mirror Object");
 | 
			
		||||
    wxGetApp().obj_manipul()->set_dirty();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1812,14 +1814,14 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
 | 
			
		|||
    struct ModelVolumeState {
 | 
			
		||||
        ModelVolumeState(const GLVolume *volume) : 
 | 
			
		||||
			model_volume(nullptr), geometry_id(volume->geometry_id), volume_idx(-1) {}
 | 
			
		||||
		ModelVolumeState(const ModelVolume *model_volume, const ModelID &instance_id, const GLVolume::CompositeID &composite_id) :
 | 
			
		||||
		ModelVolumeState(const ModelVolume *model_volume, const ObjectID &instance_id, const GLVolume::CompositeID &composite_id) :
 | 
			
		||||
			model_volume(model_volume), geometry_id(std::make_pair(model_volume->id().id, instance_id.id)), composite_id(composite_id), volume_idx(-1) {}
 | 
			
		||||
		ModelVolumeState(const ModelID &volume_id, const ModelID &instance_id) :
 | 
			
		||||
		ModelVolumeState(const ObjectID &volume_id, const ObjectID &instance_id) :
 | 
			
		||||
			model_volume(nullptr), geometry_id(std::make_pair(volume_id.id, instance_id.id)), volume_idx(-1) {}
 | 
			
		||||
		bool new_geometry() const { return this->volume_idx == size_t(-1); }
 | 
			
		||||
		const ModelVolume		   *model_volume;
 | 
			
		||||
        // ModelID of ModelVolume + ModelID of ModelInstance
 | 
			
		||||
        // or timestamp of an SLAPrintObjectStep + ModelID of ModelInstance
 | 
			
		||||
        // ObjectID of ModelVolume + ObjectID of ModelInstance
 | 
			
		||||
        // or timestamp of an SLAPrintObjectStep + ObjectID of ModelInstance
 | 
			
		||||
        std::pair<size_t, size_t>   geometry_id;
 | 
			
		||||
        GLVolume::CompositeID       composite_id;
 | 
			
		||||
        // Volume index in the new GLVolume vector.
 | 
			
		||||
| 
						 | 
				
			
			@ -2316,6 +2318,9 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
 | 
			
		|||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ((keyCode == WXK_ESCAPE) && _deactivate_undo_redo_toolbar_items())
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    if (m_gizmos.on_char(evt, *this))
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2348,6 +2353,25 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
 | 
			
		|||
#endif /* __APPLE__ */
 | 
			
		||||
            post_event(SimpleEvent(EVT_GLTOOLBAR_PASTE));
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#ifdef __APPLE__
 | 
			
		||||
        case 'y':
 | 
			
		||||
        case 'Y':
 | 
			
		||||
#else /* __APPLE__ */
 | 
			
		||||
        case WXK_CONTROL_Y:
 | 
			
		||||
#endif /* __APPLE__ */
 | 
			
		||||
            post_event(SimpleEvent(EVT_GLCANVAS_REDO));
 | 
			
		||||
        break;
 | 
			
		||||
#ifdef __APPLE__
 | 
			
		||||
        case 'z':
 | 
			
		||||
        case 'Z':
 | 
			
		||||
#else /* __APPLE__ */
 | 
			
		||||
        case WXK_CONTROL_Z:
 | 
			
		||||
#endif /* __APPLE__ */
 | 
			
		||||
            post_event(SimpleEvent(EVT_GLCANVAS_UNDO));
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
#ifdef __APPLE__
 | 
			
		||||
        case WXK_BACK: // the low cost Apple solutions are not equipped with a Delete key, use Backspace instead.
 | 
			
		||||
#else /* __APPLE__ */
 | 
			
		||||
| 
						 | 
				
			
			@ -2368,7 +2392,6 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
 | 
			
		|||
#endif /* __APPLE__ */
 | 
			
		||||
                  post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE));
 | 
			
		||||
                  break;
 | 
			
		||||
 | 
			
		||||
        case WXK_ESCAPE: { deselect_all(); break; }
 | 
			
		||||
        case '0': { select_view("iso"); break; }
 | 
			
		||||
        case '1': { select_view("top"); break; }
 | 
			
		||||
| 
						 | 
				
			
			@ -2715,12 +2738,17 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
 | 
			
		|||
    }
 | 
			
		||||
    else if (evt.Leaving())
 | 
			
		||||
    {
 | 
			
		||||
        _deactivate_undo_redo_toolbar_items();
 | 
			
		||||
 | 
			
		||||
        // to remove hover on objects when the mouse goes out of this canvas
 | 
			
		||||
        m_mouse.position = Vec2d(-1.0, -1.0);
 | 
			
		||||
        m_dirty = true;
 | 
			
		||||
    }
 | 
			
		||||
    else if (evt.LeftDown() || evt.RightDown())
 | 
			
		||||
    else if (evt.LeftDown() || evt.RightDown() || evt.MiddleDown())
 | 
			
		||||
    {
 | 
			
		||||
        if (_deactivate_undo_redo_toolbar_items())
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // If user pressed left or right button we first check whether this happened
 | 
			
		||||
        // on a volume or not.
 | 
			
		||||
        m_layers_editing.state = LayersEditing::Unknown;
 | 
			
		||||
| 
						 | 
				
			
			@ -2918,9 +2946,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
 | 
			
		|||
        else if ((m_mouse.drag.move_volume_idx != -1) && m_mouse.dragging)
 | 
			
		||||
        {
 | 
			
		||||
            m_regenerate_volumes = false;
 | 
			
		||||
            do_move();
 | 
			
		||||
            do_move("Move Object");
 | 
			
		||||
            wxGetApp().obj_manipul()->set_dirty();
 | 
			
		||||
            // Let the platter know that the dragging finished, so a delayed refresh
 | 
			
		||||
            // Let the plater know that the dragging finished, so a delayed refresh
 | 
			
		||||
            // of the scene with the background processing data should be performed.
 | 
			
		||||
            post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED));
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -3077,11 +3105,14 @@ void GLCanvas3D::set_tooltip(const std::string& tooltip) const
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void GLCanvas3D::do_move()
 | 
			
		||||
void GLCanvas3D::do_move(const std::string& snapshot_type)
 | 
			
		||||
{
 | 
			
		||||
    if (m_model == nullptr)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    if (!snapshot_type.empty())
 | 
			
		||||
        wxGetApp().plater()->take_snapshot(_(L(snapshot_type)));
 | 
			
		||||
 | 
			
		||||
    std::set<std::pair<int, int>> done;  // keeps track of modified instances
 | 
			
		||||
    bool object_moved = false;
 | 
			
		||||
    Vec3d wipe_tower_origin = Vec3d::Zero();
 | 
			
		||||
| 
						 | 
				
			
			@ -3132,13 +3163,18 @@ void GLCanvas3D::do_move()
 | 
			
		|||
 | 
			
		||||
    if (wipe_tower_origin != Vec3d::Zero())
 | 
			
		||||
        post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_MOVED, std::move(wipe_tower_origin)));
 | 
			
		||||
 | 
			
		||||
    m_dirty = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLCanvas3D::do_rotate()
 | 
			
		||||
void GLCanvas3D::do_rotate(const std::string& snapshot_type)
 | 
			
		||||
{
 | 
			
		||||
    if (m_model == nullptr)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    if (!snapshot_type.empty())
 | 
			
		||||
        wxGetApp().plater()->take_snapshot(_(L(snapshot_type)));
 | 
			
		||||
 | 
			
		||||
    std::set<std::pair<int, int>> done;  // keeps track of modified instances
 | 
			
		||||
 | 
			
		||||
    Selection::EMode selection_mode = m_selection.get_mode();
 | 
			
		||||
| 
						 | 
				
			
			@ -3187,13 +3223,18 @@ void GLCanvas3D::do_rotate()
 | 
			
		|||
 | 
			
		||||
    if (!done.empty())
 | 
			
		||||
        post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED));
 | 
			
		||||
 | 
			
		||||
    m_dirty = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLCanvas3D::do_scale()
 | 
			
		||||
void GLCanvas3D::do_scale(const std::string& snapshot_type)
 | 
			
		||||
{
 | 
			
		||||
    if (m_model == nullptr)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    if (!snapshot_type.empty())
 | 
			
		||||
        wxGetApp().plater()->take_snapshot(_(L(snapshot_type)));
 | 
			
		||||
 | 
			
		||||
    std::set<std::pair<int, int>> done;  // keeps track of modified instances
 | 
			
		||||
 | 
			
		||||
    Selection::EMode selection_mode = m_selection.get_mode();
 | 
			
		||||
| 
						 | 
				
			
			@ -3239,18 +3280,27 @@ void GLCanvas3D::do_scale()
 | 
			
		|||
 | 
			
		||||
    if (!done.empty())
 | 
			
		||||
        post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED));
 | 
			
		||||
 | 
			
		||||
    m_dirty = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLCanvas3D::do_flatten()
 | 
			
		||||
void GLCanvas3D::do_flatten(const Vec3d& normal, const std::string& snapshot_type)
 | 
			
		||||
{
 | 
			
		||||
    do_rotate();
 | 
			
		||||
    if (!snapshot_type.empty())
 | 
			
		||||
        wxGetApp().plater()->take_snapshot(_(L(snapshot_type)));
 | 
			
		||||
 | 
			
		||||
    m_selection.flattening_rotate(normal);
 | 
			
		||||
    do_rotate(""); // avoid taking another snapshot
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLCanvas3D::do_mirror()
 | 
			
		||||
void GLCanvas3D::do_mirror(const std::string& snapshot_type)
 | 
			
		||||
{
 | 
			
		||||
    if (m_model == nullptr)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    if (!snapshot_type.empty())
 | 
			
		||||
        wxGetApp().plater()->take_snapshot(_(L(snapshot_type)));
 | 
			
		||||
 | 
			
		||||
    std::set<std::pair<int, int>> done;  // keeps track of modified instances
 | 
			
		||||
 | 
			
		||||
    Selection::EMode selection_mode = m_selection.get_mode();
 | 
			
		||||
| 
						 | 
				
			
			@ -3289,6 +3339,8 @@ void GLCanvas3D::do_mirror()
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
 | 
			
		||||
 | 
			
		||||
    m_dirty = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLCanvas3D::set_camera_zoom(double zoom)
 | 
			
		||||
| 
						 | 
				
			
			@ -3412,6 +3464,40 @@ bool GLCanvas3D::_is_shown_on_screen() const
 | 
			
		|||
    return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Getter for the const char*[]
 | 
			
		||||
static bool string_getter(const bool is_undo, int idx, const char** out_text)
 | 
			
		||||
{
 | 
			
		||||
    return wxGetApp().plater()->undo_redo_string_getter(is_undo, idx, out_text);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x)
 | 
			
		||||
{
 | 
			
		||||
    const wxString& stack_name = _(is_undo ? L("Undo") : L("Redo"));
 | 
			
		||||
    ImGuiWrapper* imgui = wxGetApp().imgui();
 | 
			
		||||
 | 
			
		||||
    const float x = pos_x * (float)get_camera().get_zoom() + 0.5f * (float)get_canvas_size().get_width();
 | 
			
		||||
    imgui->set_next_window_pos(x, m_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f);
 | 
			
		||||
    imgui->set_next_window_bg_alpha(0.5f);
 | 
			
		||||
    imgui->begin(wxString::Format(_(L("%s Stack")), stack_name),
 | 
			
		||||
                 ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
 | 
			
		||||
 | 
			
		||||
    int hovered = m_imgui_undo_redo_hovered_pos;
 | 
			
		||||
    int selected = -1;
 | 
			
		||||
    const float em = static_cast<float>(wxGetApp().em_unit());
 | 
			
		||||
 | 
			
		||||
    if (imgui->undo_redo_list(ImVec2(12 * em, 20 * em), is_undo, &string_getter, hovered, selected))
 | 
			
		||||
        m_imgui_undo_redo_hovered_pos = hovered;
 | 
			
		||||
    else
 | 
			
		||||
        m_imgui_undo_redo_hovered_pos = -1;
 | 
			
		||||
 | 
			
		||||
    if (selected >= 0)
 | 
			
		||||
        is_undo ? wxGetApp().plater()->undo_to(selected) : wxGetApp().plater()->redo_to(selected);
 | 
			
		||||
 | 
			
		||||
    imgui->text(wxString::Format(_(L("%s %d Action")), stack_name, hovered + 1));
 | 
			
		||||
 | 
			
		||||
    imgui->end();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool GLCanvas3D::_init_toolbar()
 | 
			
		||||
{
 | 
			
		||||
    if (!m_toolbar.is_enabled())
 | 
			
		||||
| 
						 | 
				
			
			@ -3444,7 +3530,7 @@ bool GLCanvas3D::_init_toolbar()
 | 
			
		|||
    item.icon_filename = "add.svg";
 | 
			
		||||
    item.tooltip = _utf8(L("Add...")) + " [" + GUI::shortkey_ctrl_prefix() + "I]";
 | 
			
		||||
    item.sprite_id = 0;
 | 
			
		||||
    item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ADD)); };
 | 
			
		||||
    item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ADD)); };
 | 
			
		||||
    if (!m_toolbar.add_item(item))
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3452,8 +3538,8 @@ bool GLCanvas3D::_init_toolbar()
 | 
			
		|||
    item.icon_filename = "remove.svg";
 | 
			
		||||
    item.tooltip = _utf8(L("Delete")) + " [Del]";
 | 
			
		||||
    item.sprite_id = 1;
 | 
			
		||||
    item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE)); };
 | 
			
		||||
    item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_delete(); };
 | 
			
		||||
    item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE)); };
 | 
			
		||||
    item.enabling_callback = []()->bool { return wxGetApp().plater()->can_delete(); };
 | 
			
		||||
    if (!m_toolbar.add_item(item))
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3461,8 +3547,8 @@ bool GLCanvas3D::_init_toolbar()
 | 
			
		|||
    item.icon_filename = "delete_all.svg";
 | 
			
		||||
    item.tooltip = _utf8(L("Delete all")) + " [" + GUI::shortkey_ctrl_prefix() + "Del]";
 | 
			
		||||
    item.sprite_id = 2;
 | 
			
		||||
    item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); };
 | 
			
		||||
    item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_delete_all(); };
 | 
			
		||||
    item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); };
 | 
			
		||||
    item.enabling_callback = []()->bool { return wxGetApp().plater()->can_delete_all(); };
 | 
			
		||||
    if (!m_toolbar.add_item(item))
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3470,8 +3556,8 @@ bool GLCanvas3D::_init_toolbar()
 | 
			
		|||
    item.icon_filename = "arrange.svg";
 | 
			
		||||
    item.tooltip = _utf8(L("Arrange")) + " [A]";
 | 
			
		||||
    item.sprite_id = 3;
 | 
			
		||||
    item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ARRANGE)); };
 | 
			
		||||
    item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_arrange(); };
 | 
			
		||||
    item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ARRANGE)); };
 | 
			
		||||
    item.enabling_callback = []()->bool { return wxGetApp().plater()->can_arrange(); };
 | 
			
		||||
    if (!m_toolbar.add_item(item))
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3482,8 +3568,8 @@ bool GLCanvas3D::_init_toolbar()
 | 
			
		|||
    item.icon_filename = "copy.svg";
 | 
			
		||||
    item.tooltip = _utf8(L("Copy")) + " [" + GUI::shortkey_ctrl_prefix() + "C]";
 | 
			
		||||
    item.sprite_id = 4;
 | 
			
		||||
    item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_COPY)); };
 | 
			
		||||
    item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_copy_to_clipboard(); };
 | 
			
		||||
    item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_COPY)); };
 | 
			
		||||
    item.enabling_callback = []()->bool { return wxGetApp().plater()->can_copy_to_clipboard(); };
 | 
			
		||||
    if (!m_toolbar.add_item(item))
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3491,8 +3577,8 @@ bool GLCanvas3D::_init_toolbar()
 | 
			
		|||
    item.icon_filename = "paste.svg";
 | 
			
		||||
    item.tooltip = _utf8(L("Paste")) + " [" + GUI::shortkey_ctrl_prefix() + "V]";
 | 
			
		||||
    item.sprite_id = 5;
 | 
			
		||||
    item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_PASTE)); };
 | 
			
		||||
    item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_paste_from_clipboard(); };
 | 
			
		||||
    item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_PASTE)); };
 | 
			
		||||
    item.enabling_callback = []()->bool { return wxGetApp().plater()->can_paste_from_clipboard(); };
 | 
			
		||||
    if (!m_toolbar.add_item(item))
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3503,9 +3589,10 @@ bool GLCanvas3D::_init_toolbar()
 | 
			
		|||
    item.icon_filename = "instance_add.svg";
 | 
			
		||||
    item.tooltip = _utf8(L("Add instance")) + " [+]";
 | 
			
		||||
    item.sprite_id = 6;
 | 
			
		||||
    item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_MORE)); };
 | 
			
		||||
    item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_MORE)); };
 | 
			
		||||
    item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; };
 | 
			
		||||
    item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_increase_instances(); };
 | 
			
		||||
    item.enabling_callback = []()->bool { return wxGetApp().plater()->can_increase_instances(); };
 | 
			
		||||
 | 
			
		||||
    if (!m_toolbar.add_item(item))
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3513,9 +3600,9 @@ bool GLCanvas3D::_init_toolbar()
 | 
			
		|||
    item.icon_filename = "instance_remove.svg";
 | 
			
		||||
    item.tooltip = _utf8(L("Remove instance")) + " [-]";
 | 
			
		||||
    item.sprite_id = 7;
 | 
			
		||||
    item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_FEWER)); };
 | 
			
		||||
    item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_FEWER)); };
 | 
			
		||||
    item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; };
 | 
			
		||||
    item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_decrease_instances(); };
 | 
			
		||||
    item.enabling_callback = []()->bool { return wxGetApp().plater()->can_decrease_instances(); };
 | 
			
		||||
    if (!m_toolbar.add_item(item))
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3526,9 +3613,9 @@ bool GLCanvas3D::_init_toolbar()
 | 
			
		|||
    item.icon_filename = "split_objects.svg";
 | 
			
		||||
    item.tooltip = _utf8(L("Split to objects"));
 | 
			
		||||
    item.sprite_id = 8;
 | 
			
		||||
    item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_OBJECTS)); };
 | 
			
		||||
    item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_OBJECTS)); };
 | 
			
		||||
    item.visibility_callback = GLToolbarItem::Default_Visibility_Callback;
 | 
			
		||||
    item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_split_to_objects(); };
 | 
			
		||||
    item.enabling_callback = []()->bool { return wxGetApp().plater()->can_split_to_objects(); };
 | 
			
		||||
    if (!m_toolbar.add_item(item))
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3536,9 +3623,9 @@ bool GLCanvas3D::_init_toolbar()
 | 
			
		|||
    item.icon_filename = "split_parts.svg";
 | 
			
		||||
    item.tooltip = _utf8(L("Split to parts"));
 | 
			
		||||
    item.sprite_id = 9;
 | 
			
		||||
    item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_VOLUMES)); };
 | 
			
		||||
    item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_VOLUMES)); };
 | 
			
		||||
    item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; };
 | 
			
		||||
    item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_split_to_volumes(); };
 | 
			
		||||
    item.enabling_callback = []()->bool { return wxGetApp().plater()->can_split_to_volumes(); };
 | 
			
		||||
    if (!m_toolbar.add_item(item))
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3549,10 +3636,42 @@ bool GLCanvas3D::_init_toolbar()
 | 
			
		|||
    item.icon_filename = "layers_white.svg";
 | 
			
		||||
    item.tooltip = _utf8(L("Layers editing"));
 | 
			
		||||
    item.sprite_id = 10;
 | 
			
		||||
    item.is_toggable = true;
 | 
			
		||||
    item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); };
 | 
			
		||||
    item.left.toggable = true;
 | 
			
		||||
    item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); };
 | 
			
		||||
    item.visibility_callback = [this]()->bool { return m_process->current_printer_technology() == ptFFF; };
 | 
			
		||||
    item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_layers_editing(); };
 | 
			
		||||
    item.enabling_callback = []()->bool { return wxGetApp().plater()->can_layers_editing(); };
 | 
			
		||||
    if (!m_toolbar.add_item(item))
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
    if (!m_toolbar.add_separator())
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
    item.name = "undo";
 | 
			
		||||
#if ENABLE_SVG_ICONS
 | 
			
		||||
    item.icon_filename = "undo_toolbar.svg";
 | 
			
		||||
#endif // ENABLE_SVG_ICONS
 | 
			
		||||
    item.tooltip = _utf8(L("Undo")) + " [" + GUI::shortkey_ctrl_prefix() + "Z]";
 | 
			
		||||
    item.sprite_id = 11;
 | 
			
		||||
    item.left.toggable = false;
 | 
			
		||||
    item.left.action_callback = [this]() { post_event(SimpleEvent(EVT_GLCANVAS_UNDO)); };
 | 
			
		||||
    item.right.toggable = true;
 | 
			
		||||
    item.right.action_callback = [this]() { m_imgui_undo_redo_hovered_pos = -1; };
 | 
			
		||||
    item.right.render_callback = [this](float left, float right, float, float) { if (m_canvas != nullptr) _render_undo_redo_stack(true, 0.5f * (left + right)); };
 | 
			
		||||
    item.visibility_callback = []()->bool { return true; };
 | 
			
		||||
    item.enabling_callback = [this]()->bool { return wxGetApp().plater()->can_undo(); };
 | 
			
		||||
    if (!m_toolbar.add_item(item))
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
    item.name = "redo";
 | 
			
		||||
#if ENABLE_SVG_ICONS
 | 
			
		||||
    item.icon_filename = "redo_toolbar.svg";
 | 
			
		||||
#endif // ENABLE_SVG_ICONS
 | 
			
		||||
    item.tooltip = _utf8(L("Redo")) + " [" + GUI::shortkey_ctrl_prefix() + "Y]";
 | 
			
		||||
    item.sprite_id = 12;
 | 
			
		||||
    item.left.action_callback = [this]() { post_event(SimpleEvent(EVT_GLCANVAS_REDO)); };
 | 
			
		||||
    item.right.action_callback = [this]() { m_imgui_undo_redo_hovered_pos = -1; };
 | 
			
		||||
    item.right.render_callback = [this](float left, float right, float, float) { if (m_canvas != nullptr) _render_undo_redo_stack(false, 0.5f * (left + right)); };
 | 
			
		||||
    item.enabling_callback = [this]()->bool { return wxGetApp().plater()->can_redo(); };
 | 
			
		||||
    if (!m_toolbar.add_item(item))
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -5551,6 +5670,22 @@ void GLCanvas3D::_update_selection_from_hover()
 | 
			
		|||
    m_dirty = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool GLCanvas3D::_deactivate_undo_redo_toolbar_items()
 | 
			
		||||
{
 | 
			
		||||
    if (m_toolbar.is_item_pressed("undo"))
 | 
			
		||||
    {
 | 
			
		||||
        m_toolbar.force_right_action(m_toolbar.get_item_id("undo"), *this);
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    else if (m_toolbar.is_item_pressed("redo"))
 | 
			
		||||
    {
 | 
			
		||||
        m_toolbar.force_right_action(m_toolbar.get_item_id("redo"), *this);
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const Print* GLCanvas3D::fff_print() const
 | 
			
		||||
{
 | 
			
		||||
    return (m_process == nullptr) ? nullptr : m_process->fff_print();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -127,6 +127,8 @@ wxDECLARE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent);
 | 
			
		|||
wxDECLARE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent);
 | 
			
		||||
wxDECLARE_EVENT(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, wxKeyEvent);
 | 
			
		||||
wxDECLARE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent);
 | 
			
		||||
wxDECLARE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent);
 | 
			
		||||
wxDECLARE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent);
 | 
			
		||||
 | 
			
		||||
class GLCanvas3D
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -485,6 +487,8 @@ private:
 | 
			
		|||
    RenderStats m_render_stats;
 | 
			
		||||
#endif // ENABLE_RENDER_STATISTICS
 | 
			
		||||
 | 
			
		||||
    int m_imgui_undo_redo_hovered_pos{ -1 };
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar);
 | 
			
		||||
    ~GLCanvas3D();
 | 
			
		||||
| 
						 | 
				
			
			@ -596,11 +600,12 @@ public:
 | 
			
		|||
 | 
			
		||||
    void set_tooltip(const std::string& tooltip) const;
 | 
			
		||||
 | 
			
		||||
    void do_move();
 | 
			
		||||
    void do_rotate();
 | 
			
		||||
    void do_scale();
 | 
			
		||||
    void do_flatten();
 | 
			
		||||
    void do_mirror();
 | 
			
		||||
    // the following methods add a snapshot to the undo/redo stack, unless the given string is empty
 | 
			
		||||
    void do_move(const std::string& snapshot_type);
 | 
			
		||||
    void do_rotate(const std::string& snapshot_type);
 | 
			
		||||
    void do_scale(const std::string& snapshot_type);
 | 
			
		||||
    void do_flatten(const Vec3d& normal, const std::string& snapshot_type);
 | 
			
		||||
    void do_mirror(const std::string& snapshot_type);
 | 
			
		||||
 | 
			
		||||
    void set_camera_zoom(double zoom);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -673,6 +678,7 @@ private:
 | 
			
		|||
#endif // ENABLE_SHOW_CAMERA_TARGET
 | 
			
		||||
    void _render_sla_slices() const;
 | 
			
		||||
    void _render_selection_sidebar_hints() const;
 | 
			
		||||
    void _render_undo_redo_stack(const bool is_undo, float pos_x);
 | 
			
		||||
 | 
			
		||||
    void _update_volumes_hover_state() const;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -731,6 +737,8 @@ private:
 | 
			
		|||
    // updates the selection from the content of m_hover_volume_idxs
 | 
			
		||||
    void _update_selection_from_hover();
 | 
			
		||||
 | 
			
		||||
    bool _deactivate_undo_redo_toolbar_items();
 | 
			
		||||
 | 
			
		||||
    static std::vector<float> _parse_colors(const std::vector<std::string>& colors);
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -34,18 +34,24 @@ wxDEFINE_EVENT(EVT_GLVIEWTOOLBAR_PREVIEW, SimpleEvent);
 | 
			
		|||
 | 
			
		||||
const GLToolbarItem::ActionCallback GLToolbarItem::Default_Action_Callback = [](){};
 | 
			
		||||
const GLToolbarItem::VisibilityCallback GLToolbarItem::Default_Visibility_Callback = []()->bool { return true; };
 | 
			
		||||
const GLToolbarItem::EnabledStateCallback GLToolbarItem::Default_Enabled_State_Callback = []()->bool { return true; };
 | 
			
		||||
const GLToolbarItem::EnablingCallback GLToolbarItem::Default_Enabling_Callback = []()->bool { return true; };
 | 
			
		||||
const GLToolbarItem::RenderCallback GLToolbarItem::Default_Render_Callback = [](float, float, float, float){};
 | 
			
		||||
 | 
			
		||||
GLToolbarItem::Data::Option::Option()
 | 
			
		||||
    : toggable(false)
 | 
			
		||||
    , action_callback(Default_Action_Callback)
 | 
			
		||||
    , render_callback(nullptr)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
GLToolbarItem::Data::Data()
 | 
			
		||||
    : name("")
 | 
			
		||||
    , icon_filename("")
 | 
			
		||||
    , tooltip("")
 | 
			
		||||
    , sprite_id(-1)
 | 
			
		||||
    , is_toggable(false)
 | 
			
		||||
    , visible(true)
 | 
			
		||||
    , action_callback(Default_Action_Callback)
 | 
			
		||||
    , visibility_callback(Default_Visibility_Callback)
 | 
			
		||||
    , enabled_state_callback(Default_Enabled_State_Callback)
 | 
			
		||||
    , enabling_callback(Default_Enabling_Callback)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -53,6 +59,7 @@ GLToolbarItem::GLToolbarItem(GLToolbarItem::EType type, const GLToolbarItem::Dat
 | 
			
		|||
    : m_type(type)
 | 
			
		||||
    , m_state(Normal)
 | 
			
		||||
    , m_data(data)
 | 
			
		||||
    , m_last_action_type(Undefined)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -68,7 +75,7 @@ bool GLToolbarItem::update_visibility()
 | 
			
		|||
 | 
			
		||||
bool GLToolbarItem::update_enabled_state()
 | 
			
		||||
{
 | 
			
		||||
    bool enabled = m_data.enabled_state_callback();
 | 
			
		||||
    bool enabled = m_data.enabling_callback();
 | 
			
		||||
    bool ret = (is_enabled() != enabled);
 | 
			
		||||
    if (ret)
 | 
			
		||||
        m_state = enabled ? GLToolbarItem::Normal : GLToolbarItem::Disabled;
 | 
			
		||||
| 
						 | 
				
			
			@ -79,6 +86,14 @@ bool GLToolbarItem::update_enabled_state()
 | 
			
		|||
void GLToolbarItem::render(unsigned int tex_id, float left, float right, float bottom, float top, unsigned int tex_width, unsigned int tex_height, unsigned int icon_size) const
 | 
			
		||||
{
 | 
			
		||||
    GLTexture::render_sub_texture(tex_id, left, right, bottom, top, get_uvs(tex_width, tex_height, icon_size));
 | 
			
		||||
 | 
			
		||||
    if (is_pressed())
 | 
			
		||||
    {
 | 
			
		||||
        if ((m_last_action_type == Left) && m_data.left.can_render())
 | 
			
		||||
            m_data.left.render_callback(left, right, bottom, top);
 | 
			
		||||
        else if ((m_last_action_type == Right) && m_data.right.can_render())
 | 
			
		||||
            m_data.right.render_callback(left, right, bottom, top);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
GLTexture::Quad_UVs GLToolbarItem::get_uvs(unsigned int tex_width, unsigned int tex_height, unsigned int icon_size) const
 | 
			
		||||
| 
						 | 
				
			
			@ -136,6 +151,7 @@ GLToolbar::GLToolbar(GLToolbar::EType type, const std::string& name)
 | 
			
		|||
    , m_enabled(false)
 | 
			
		||||
    , m_icons_texture_dirty(true)
 | 
			
		||||
    , m_tooltip("")
 | 
			
		||||
    , m_pressed_toggable_id(-1)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -295,7 +311,7 @@ void GLToolbar::select_item(const std::string& name)
 | 
			
		|||
 | 
			
		||||
bool GLToolbar::is_item_pressed(const std::string& name) const
 | 
			
		||||
{
 | 
			
		||||
    for (GLToolbarItem* item : m_items)
 | 
			
		||||
    for (const GLToolbarItem* item : m_items)
 | 
			
		||||
    {
 | 
			
		||||
        if (item->get_name() == name)
 | 
			
		||||
            return item->is_pressed();
 | 
			
		||||
| 
						 | 
				
			
			@ -306,7 +322,7 @@ bool GLToolbar::is_item_pressed(const std::string& name) const
 | 
			
		|||
 | 
			
		||||
bool GLToolbar::is_item_disabled(const std::string& name) const
 | 
			
		||||
{
 | 
			
		||||
    for (GLToolbarItem* item : m_items)
 | 
			
		||||
    for (const GLToolbarItem* item : m_items)
 | 
			
		||||
    {
 | 
			
		||||
        if (item->get_name() == name)
 | 
			
		||||
            return item->is_disabled();
 | 
			
		||||
| 
						 | 
				
			
			@ -317,7 +333,7 @@ bool GLToolbar::is_item_disabled(const std::string& name) const
 | 
			
		|||
 | 
			
		||||
bool GLToolbar::is_item_visible(const std::string& name) const
 | 
			
		||||
{
 | 
			
		||||
    for (GLToolbarItem* item : m_items)
 | 
			
		||||
    for (const GLToolbarItem* item : m_items)
 | 
			
		||||
    {
 | 
			
		||||
        if (item->get_name() == name)
 | 
			
		||||
            return item->is_visible();
 | 
			
		||||
| 
						 | 
				
			
			@ -326,11 +342,46 @@ bool GLToolbar::is_item_visible(const std::string& name) const
 | 
			
		|||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool GLToolbar::is_any_item_pressed() const
 | 
			
		||||
{
 | 
			
		||||
    for (const GLToolbarItem* item : m_items)
 | 
			
		||||
    {
 | 
			
		||||
        if (item->is_pressed())
 | 
			
		||||
            return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
unsigned int GLToolbar::get_item_id(const std::string& name) const
 | 
			
		||||
{
 | 
			
		||||
    for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i)
 | 
			
		||||
    {
 | 
			
		||||
        if (m_items[i]->get_name() == name)
 | 
			
		||||
            return i;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return -1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLToolbar::force_left_action(unsigned int item_id, GLCanvas3D& parent)
 | 
			
		||||
{
 | 
			
		||||
    do_action(GLToolbarItem::Left, item_id, parent, false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLToolbar::force_right_action(unsigned int item_id, GLCanvas3D& parent)
 | 
			
		||||
{
 | 
			
		||||
    do_action(GLToolbarItem::Right, item_id, parent, false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool GLToolbar::update_items_state()
 | 
			
		||||
{
 | 
			
		||||
    bool ret = false;
 | 
			
		||||
    ret |= update_items_visibility();
 | 
			
		||||
    ret |= update_items_enabled_state();
 | 
			
		||||
    if (!is_any_item_pressed())
 | 
			
		||||
        m_pressed_toggable_id = -1;
 | 
			
		||||
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -392,10 +443,11 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent)
 | 
			
		|||
            m_mouse_capture.left = true;
 | 
			
		||||
            m_mouse_capture.parent = &parent;
 | 
			
		||||
            processed = true;
 | 
			
		||||
            if ((item_id != -2) && !m_items[item_id]->is_separator())
 | 
			
		||||
            if ((item_id != -2) && !m_items[item_id]->is_separator() && ((m_pressed_toggable_id == -1) || (m_items[item_id]->get_last_action_type() == GLToolbarItem::Left)))
 | 
			
		||||
            {
 | 
			
		||||
                // mouse is inside an icon
 | 
			
		||||
                do_action((unsigned int)item_id, parent);
 | 
			
		||||
                do_action(GLToolbarItem::Left, (unsigned int)item_id, parent, true);
 | 
			
		||||
                parent.set_as_dirty();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else if (evt.MiddleDown())
 | 
			
		||||
| 
						 | 
				
			
			@ -407,6 +459,13 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent)
 | 
			
		|||
        {
 | 
			
		||||
            m_mouse_capture.right = true;
 | 
			
		||||
            m_mouse_capture.parent = &parent;
 | 
			
		||||
            processed = true;
 | 
			
		||||
            if ((item_id != -2) && !m_items[item_id]->is_separator() && ((m_pressed_toggable_id == -1) || (m_items[item_id]->get_last_action_type() == GLToolbarItem::Right)))
 | 
			
		||||
            {
 | 
			
		||||
                // mouse is inside an icon
 | 
			
		||||
                do_action(GLToolbarItem::Right, (unsigned int)item_id, parent, true);
 | 
			
		||||
                parent.set_as_dirty();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else if (evt.LeftUp())
 | 
			
		||||
            processed = true;
 | 
			
		||||
| 
						 | 
				
			
			@ -477,38 +536,61 @@ float GLToolbar::get_main_size() const
 | 
			
		|||
    return size * m_layout.scale;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLToolbar::do_action(unsigned int item_id, GLCanvas3D& parent)
 | 
			
		||||
void GLToolbar::do_action(GLToolbarItem::EActionType type, unsigned int item_id, GLCanvas3D& parent, bool check_hover)
 | 
			
		||||
{
 | 
			
		||||
    if (item_id < (unsigned int)m_items.size())
 | 
			
		||||
    if ((m_pressed_toggable_id == -1) || (m_pressed_toggable_id == item_id))
 | 
			
		||||
    {
 | 
			
		||||
        GLToolbarItem* item = m_items[item_id];
 | 
			
		||||
        if ((item != nullptr) && !item->is_separator() && item->is_hovered())
 | 
			
		||||
        if (item_id < (unsigned int)m_items.size())
 | 
			
		||||
        {
 | 
			
		||||
            if (item->is_toggable())
 | 
			
		||||
            GLToolbarItem* item = m_items[item_id];
 | 
			
		||||
            if ((item != nullptr) && !item->is_separator() && (!check_hover || item->is_hovered()))
 | 
			
		||||
            {
 | 
			
		||||
                GLToolbarItem::EState state = item->get_state();
 | 
			
		||||
                if (state == GLToolbarItem::Hover)
 | 
			
		||||
                    item->set_state(GLToolbarItem::HoverPressed);
 | 
			
		||||
                else if (state == GLToolbarItem::HoverPressed)
 | 
			
		||||
                    item->set_state(GLToolbarItem::Hover);
 | 
			
		||||
 | 
			
		||||
                parent.render();
 | 
			
		||||
                item->do_action();
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                if (m_type == Radio)
 | 
			
		||||
                    select_item(item->get_name());
 | 
			
		||||
                else
 | 
			
		||||
                    item->set_state(GLToolbarItem::HoverPressed);
 | 
			
		||||
 | 
			
		||||
                parent.render();
 | 
			
		||||
                item->do_action();
 | 
			
		||||
                if ((m_type == Normal) && (item->get_state() != GLToolbarItem::Disabled))
 | 
			
		||||
                if (((type == GLToolbarItem::Right) && item->is_right_toggable()) ||
 | 
			
		||||
                    ((type == GLToolbarItem::Left) && item->is_left_toggable()))
 | 
			
		||||
                {
 | 
			
		||||
                    // the item may get disabled during the action, if not, set it back to hover state
 | 
			
		||||
                    item->set_state(GLToolbarItem::Hover);
 | 
			
		||||
                    GLToolbarItem::EState state = item->get_state();
 | 
			
		||||
                    if (state == GLToolbarItem::Hover)
 | 
			
		||||
                        item->set_state(GLToolbarItem::HoverPressed);
 | 
			
		||||
                    else if (state == GLToolbarItem::HoverPressed)
 | 
			
		||||
                        item->set_state(GLToolbarItem::Hover);
 | 
			
		||||
                    else if (state == GLToolbarItem::Pressed)
 | 
			
		||||
                        item->set_state(GLToolbarItem::Normal);
 | 
			
		||||
                    else if (state == GLToolbarItem::Normal)
 | 
			
		||||
                        item->set_state(GLToolbarItem::Pressed);
 | 
			
		||||
 | 
			
		||||
                    m_pressed_toggable_id = item->is_pressed() ? item_id : -1;
 | 
			
		||||
                    item->reset_last_action_type();
 | 
			
		||||
 | 
			
		||||
                    parent.render();
 | 
			
		||||
                    switch (type)
 | 
			
		||||
                    {
 | 
			
		||||
                    default:
 | 
			
		||||
                    case GLToolbarItem::Left: { item->do_left_action(); break; }
 | 
			
		||||
                    case GLToolbarItem::Right: { item->do_right_action(); break; }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    if (m_type == Radio)
 | 
			
		||||
                        select_item(item->get_name());
 | 
			
		||||
                    else
 | 
			
		||||
                        item->set_state(item->is_hovered() ? GLToolbarItem::HoverPressed : GLToolbarItem::Pressed);
 | 
			
		||||
 | 
			
		||||
                    item->reset_last_action_type();
 | 
			
		||||
                    parent.render();
 | 
			
		||||
                    switch (type)
 | 
			
		||||
                    {
 | 
			
		||||
                    default:
 | 
			
		||||
                    case GLToolbarItem::Left: { item->do_left_action(); break; }
 | 
			
		||||
                    case GLToolbarItem::Right: { item->do_right_action(); break; }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if ((m_type == Normal) && (item->get_state() != GLToolbarItem::Disabled))
 | 
			
		||||
                    {
 | 
			
		||||
                        // the item may get disabled during the action, if not, set it back to hover state
 | 
			
		||||
                        item->set_state(GLToolbarItem::Hover);
 | 
			
		||||
                        parent.render();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -1212,9 +1294,15 @@ bool GLToolbar::update_items_enabled_state()
 | 
			
		|||
{
 | 
			
		||||
    bool ret = false;
 | 
			
		||||
 | 
			
		||||
    for (GLToolbarItem* item : m_items)
 | 
			
		||||
    for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i)
 | 
			
		||||
    {
 | 
			
		||||
        GLToolbarItem* item = m_items[i];
 | 
			
		||||
        ret |= item->update_enabled_state();
 | 
			
		||||
        if (item->is_enabled() && (m_pressed_toggable_id != -1) && (m_pressed_toggable_id != i))
 | 
			
		||||
        {
 | 
			
		||||
            ret = true;
 | 
			
		||||
            item->set_state(GLToolbarItem::Disabled);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (ret)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,7 +36,8 @@ class GLToolbarItem
 | 
			
		|||
public:
 | 
			
		||||
    typedef std::function<void()> ActionCallback;
 | 
			
		||||
    typedef std::function<bool()> VisibilityCallback;
 | 
			
		||||
    typedef std::function<bool()> EnabledStateCallback;
 | 
			
		||||
    typedef std::function<bool()> EnablingCallback;
 | 
			
		||||
    typedef std::function<void(float, float, float, float)> RenderCallback;
 | 
			
		||||
 | 
			
		||||
    enum EType : unsigned char
 | 
			
		||||
    {
 | 
			
		||||
| 
						 | 
				
			
			@ -45,6 +46,14 @@ public:
 | 
			
		|||
        Num_Types
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    enum EActionType : unsigned char
 | 
			
		||||
    {
 | 
			
		||||
        Undefined,
 | 
			
		||||
        Left,
 | 
			
		||||
        Right,
 | 
			
		||||
        Num_Action_Types
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    enum EState : unsigned char
 | 
			
		||||
    {
 | 
			
		||||
        Normal,
 | 
			
		||||
| 
						 | 
				
			
			@ -57,27 +66,42 @@ public:
 | 
			
		|||
 | 
			
		||||
    struct Data
 | 
			
		||||
    {
 | 
			
		||||
        struct Option
 | 
			
		||||
        {
 | 
			
		||||
            bool toggable;
 | 
			
		||||
            ActionCallback action_callback;
 | 
			
		||||
            RenderCallback render_callback;
 | 
			
		||||
 | 
			
		||||
            Option();
 | 
			
		||||
 | 
			
		||||
            bool can_render() const { return toggable && (render_callback != nullptr); }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        std::string name;
 | 
			
		||||
        std::string icon_filename;
 | 
			
		||||
        std::string tooltip;
 | 
			
		||||
        unsigned int sprite_id;
 | 
			
		||||
        bool is_toggable;
 | 
			
		||||
        // mouse left click
 | 
			
		||||
        Option left;
 | 
			
		||||
        // mouse right click
 | 
			
		||||
        Option right;
 | 
			
		||||
        bool visible;
 | 
			
		||||
        ActionCallback action_callback;
 | 
			
		||||
        VisibilityCallback visibility_callback;
 | 
			
		||||
        EnabledStateCallback enabled_state_callback;
 | 
			
		||||
        EnablingCallback enabling_callback;
 | 
			
		||||
 | 
			
		||||
        Data();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    static const ActionCallback Default_Action_Callback;
 | 
			
		||||
    static const VisibilityCallback Default_Visibility_Callback;
 | 
			
		||||
    static const EnabledStateCallback Default_Enabled_State_Callback;
 | 
			
		||||
    static const EnablingCallback Default_Enabling_Callback;
 | 
			
		||||
    static const RenderCallback Default_Render_Callback;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    EType m_type;
 | 
			
		||||
    EState m_state;
 | 
			
		||||
    Data m_data;
 | 
			
		||||
    EActionType m_last_action_type;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    GLToolbarItem(EType type, const Data& data);
 | 
			
		||||
| 
						 | 
				
			
			@ -89,17 +113,25 @@ public:
 | 
			
		|||
    const std::string& get_icon_filename() const { return m_data.icon_filename; }
 | 
			
		||||
    const std::string& get_tooltip() const { return m_data.tooltip; }
 | 
			
		||||
 | 
			
		||||
    void do_action() { m_data.action_callback(); }
 | 
			
		||||
    void do_left_action() { m_last_action_type = Left; m_data.left.action_callback(); }
 | 
			
		||||
    void do_right_action() { m_last_action_type = Right; m_data.right.action_callback(); }
 | 
			
		||||
 | 
			
		||||
    bool is_enabled() const { return m_state != Disabled; }
 | 
			
		||||
    bool is_disabled() const { return m_state == Disabled; }
 | 
			
		||||
    bool is_hovered() const { return (m_state == Hover) || (m_state == HoverPressed); }
 | 
			
		||||
    bool is_pressed() const { return (m_state == Pressed) || (m_state == HoverPressed); }
 | 
			
		||||
 | 
			
		||||
    bool is_toggable() const { return m_data.is_toggable; }
 | 
			
		||||
    bool is_visible() const { return m_data.visible; }
 | 
			
		||||
    bool is_separator() const { return m_type == Separator; }
 | 
			
		||||
 | 
			
		||||
    bool is_left_toggable() const { return m_data.left.toggable; }
 | 
			
		||||
    bool is_right_toggable() const { return m_data.right.toggable; }
 | 
			
		||||
 | 
			
		||||
    bool has_left_render_callback() const { return m_data.left.render_callback != nullptr; }
 | 
			
		||||
    bool has_right_render_callback() const { return m_data.right.render_callback != nullptr; }
 | 
			
		||||
 | 
			
		||||
    EActionType get_last_action_type() const { return m_last_action_type; }
 | 
			
		||||
    void reset_last_action_type() { m_last_action_type = Undefined; }
 | 
			
		||||
 | 
			
		||||
    // returns true if the state changes
 | 
			
		||||
    bool update_visibility();
 | 
			
		||||
    // returns true if the state changes
 | 
			
		||||
| 
						 | 
				
			
			@ -212,6 +244,7 @@ private:
 | 
			
		|||
 | 
			
		||||
    MouseCapture m_mouse_capture;
 | 
			
		||||
    std::string m_tooltip;
 | 
			
		||||
    unsigned int m_pressed_toggable_id;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    GLToolbar(EType type, const std::string& name);
 | 
			
		||||
| 
						 | 
				
			
			@ -246,6 +279,13 @@ public:
 | 
			
		|||
    bool is_item_disabled(const std::string& name) const;
 | 
			
		||||
    bool is_item_visible(const std::string& name) const;
 | 
			
		||||
 | 
			
		||||
    bool is_any_item_pressed() const;
 | 
			
		||||
 | 
			
		||||
    unsigned int get_item_id(const std::string& name) const;
 | 
			
		||||
 | 
			
		||||
    void force_left_action(unsigned int item_id, GLCanvas3D& parent);
 | 
			
		||||
    void force_right_action(unsigned int item_id, GLCanvas3D& parent);
 | 
			
		||||
 | 
			
		||||
    const std::string& get_tooltip() const { return m_tooltip; }
 | 
			
		||||
 | 
			
		||||
    // returns true if any item changed its state
 | 
			
		||||
| 
						 | 
				
			
			@ -262,7 +302,7 @@ private:
 | 
			
		|||
    float get_height_horizontal() const;
 | 
			
		||||
    float get_height_vertical() const;
 | 
			
		||||
    float get_main_size() const;
 | 
			
		||||
    void do_action(unsigned int item_id, GLCanvas3D& parent);
 | 
			
		||||
    void do_action(GLToolbarItem::EActionType type, unsigned int item_id, GLCanvas3D& parent, bool check_hover);
 | 
			
		||||
    std::string update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent);
 | 
			
		||||
    std::string update_hover_state_horizontal(const Vec2d& mouse_pos, GLCanvas3D& parent);
 | 
			
		||||
    std::string update_hover_state_vertical(const Vec2d& mouse_pos, GLCanvas3D& parent);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,7 +25,7 @@ namespace GUI
 | 
			
		|||
wxDEFINE_EVENT(EVT_OBJ_LIST_OBJECT_SELECT, SimpleEvent);
 | 
			
		||||
 | 
			
		||||
// pt_FFF
 | 
			
		||||
FreqSettingsBundle FREQ_SETTINGS_BUNDLE_FFF =
 | 
			
		||||
SettingsBundle FREQ_SETTINGS_BUNDLE_FFF =
 | 
			
		||||
{
 | 
			
		||||
    { L("Layers and Perimeters"), { "layer_height" , "perimeters", "top_solid_layers", "bottom_solid_layers" } },
 | 
			
		||||
    { L("Infill")               , { "fill_density", "fill_pattern" } },
 | 
			
		||||
| 
						 | 
				
			
			@ -36,7 +36,7 @@ FreqSettingsBundle FREQ_SETTINGS_BUNDLE_FFF =
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
// pt_SLA
 | 
			
		||||
FreqSettingsBundle FREQ_SETTINGS_BUNDLE_SLA =
 | 
			
		||||
SettingsBundle FREQ_SETTINGS_BUNDLE_SLA =
 | 
			
		||||
{
 | 
			
		||||
    { L("Pad and Support")      , { "supports_enable", "pad_enable" } }
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -66,6 +66,14 @@ static int extruders_count()
 | 
			
		|||
    return wxGetApp().extruders_cnt();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void take_snapshot(const wxString& snapshot_name) 
 | 
			
		||||
{
 | 
			
		||||
    wxGetApp().plater()->take_snapshot(snapshot_name);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void suppress_snapshots(){ wxGetApp().plater()->suppress_snapshots(); }
 | 
			
		||||
static void allow_snapshots()   { wxGetApp().plater()->allow_snapshots(); }
 | 
			
		||||
 | 
			
		||||
ObjectList::ObjectList(wxWindow* parent) :
 | 
			
		||||
    wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_MULTIPLE),
 | 
			
		||||
    m_parent(parent)
 | 
			
		||||
| 
						 | 
				
			
			@ -138,20 +146,24 @@ ObjectList::ObjectList(wxWindow* parent) :
 | 
			
		|||
//    Bind(wxEVT_KEY_DOWN, &ObjectList::OnChar, this);
 | 
			
		||||
    {
 | 
			
		||||
        // Accelerators
 | 
			
		||||
        wxAcceleratorEntry entries[6];
 | 
			
		||||
        wxAcceleratorEntry entries[8];
 | 
			
		||||
        entries[0].Set(wxACCEL_CTRL, (int) 'C',    wxID_COPY);
 | 
			
		||||
        entries[1].Set(wxACCEL_CTRL, (int) 'X',    wxID_CUT);
 | 
			
		||||
        entries[2].Set(wxACCEL_CTRL, (int) 'V',    wxID_PASTE);
 | 
			
		||||
        entries[3].Set(wxACCEL_CTRL, (int) 'A',    wxID_SELECTALL);
 | 
			
		||||
        entries[4].Set(wxACCEL_NORMAL, WXK_DELETE, wxID_DELETE);
 | 
			
		||||
        entries[5].Set(wxACCEL_NORMAL, WXK_BACK,   wxID_DELETE);
 | 
			
		||||
        wxAcceleratorTable accel(6, entries);
 | 
			
		||||
        entries[4].Set(wxACCEL_CTRL, (int) 'Z',    wxID_UNDO);
 | 
			
		||||
        entries[5].Set(wxACCEL_CTRL, (int) 'Y',    wxID_REDO);
 | 
			
		||||
        entries[6].Set(wxACCEL_NORMAL, WXK_DELETE, wxID_DELETE);
 | 
			
		||||
        entries[7].Set(wxACCEL_NORMAL, WXK_BACK,   wxID_DELETE);
 | 
			
		||||
        wxAcceleratorTable accel(8, entries);
 | 
			
		||||
        SetAcceleratorTable(accel);
 | 
			
		||||
 | 
			
		||||
        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);
 | 
			
		||||
        this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->undo();  					}, wxID_UNDO);
 | 
			
		||||
        this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->redo();                    	}, wxID_REDO);
 | 
			
		||||
    }
 | 
			
		||||
#else __WXOSX__
 | 
			
		||||
    Bind(wxEVT_CHAR, [this](wxKeyEvent& event) { key_event(event); }); // doesn't work on OSX
 | 
			
		||||
| 
						 | 
				
			
			@ -655,7 +667,6 @@ void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& vol
 | 
			
		|||
    if (volumes.empty())
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    ModelObject& model_object = *(*m_objects)[obj_idx];
 | 
			
		||||
    const auto object_item = m_objects_model->GetItemById(obj_idx);
 | 
			
		||||
 | 
			
		||||
    wxDataViewItemArray items;
 | 
			
		||||
| 
						 | 
				
			
			@ -665,10 +676,7 @@ void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& vol
 | 
			
		|||
        const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(object_item, wxString::FromUTF8(volume->name.c_str()), volume->type(), 
 | 
			
		||||
            volume->get_mesh_errors_count()>0 ,
 | 
			
		||||
            volume->config.has("extruder") ? volume->config.option<ConfigOptionInt>("extruder")->value : 0);
 | 
			
		||||
        auto opt_keys = volume->config.keys();
 | 
			
		||||
        if (!opt_keys.empty() && !((opt_keys.size() == 1) && (opt_keys[0] == "extruder")))
 | 
			
		||||
            select_item(m_objects_model->AddSettingsChild(vol_item));
 | 
			
		||||
 | 
			
		||||
        add_settings_item(vol_item, &volume->config);
 | 
			
		||||
        items.Add(vol_item);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -805,6 +813,16 @@ void ObjectList::paste()
 | 
			
		|||
        wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_PASTE));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectList::undo()
 | 
			
		||||
{
 | 
			
		||||
	wxGetApp().plater()->undo();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectList::redo()
 | 
			
		||||
{
 | 
			
		||||
	wxGetApp().plater()->redo();	
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifndef __WXOSX__
 | 
			
		||||
void ObjectList::key_event(wxKeyEvent& event)
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -823,6 +841,10 @@ void ObjectList::key_event(wxKeyEvent& event)
 | 
			
		|||
        copy();
 | 
			
		||||
    else if (wxGetKeyState(wxKeyCode('V')) && wxGetKeyState(WXK_CONTROL))
 | 
			
		||||
        paste();
 | 
			
		||||
    else if (wxGetKeyState(wxKeyCode('Y')) && wxGetKeyState(WXK_CONTROL))
 | 
			
		||||
        redo();
 | 
			
		||||
    else if (wxGetKeyState(wxKeyCode('Z')) && wxGetKeyState(WXK_CONTROL))
 | 
			
		||||
        undo();
 | 
			
		||||
    else
 | 
			
		||||
        event.Skip();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -908,6 +930,7 @@ void ObjectList::OnDrop(wxDataViewEvent &event)
 | 
			
		|||
 | 
			
		||||
    if (m_dragged_data.type() == itInstance)
 | 
			
		||||
    {
 | 
			
		||||
        take_snapshot(_(L("Instances to Separated Objects")));
 | 
			
		||||
        instances_to_separated_object(m_dragged_data.obj_idx(), m_dragged_data.inst_idxs());
 | 
			
		||||
        m_dragged_data.clear();
 | 
			
		||||
        return;
 | 
			
		||||
| 
						 | 
				
			
			@ -925,6 +948,8 @@ void ObjectList::OnDrop(wxDataViewEvent &event)
 | 
			
		|||
//     if (to_volume_id > from_volume_id) to_volume_id--;
 | 
			
		||||
// #endif // __WXGTK__
 | 
			
		||||
 | 
			
		||||
    take_snapshot(_(L("Remov Volume(s)")));
 | 
			
		||||
 | 
			
		||||
    auto& volumes = (*m_objects)[m_dragged_data.obj_idx()]->volumes;
 | 
			
		||||
    auto delta = to_volume_id < from_volume_id ? -1 : 1;
 | 
			
		||||
    int cnt = 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -963,7 +988,7 @@ std::vector<std::string> ObjectList::get_options(const bool is_part)
 | 
			
		|||
    
 | 
			
		||||
const std::vector<std::string>& ObjectList::get_options_for_bundle(const wxString& bundle_name)
 | 
			
		||||
{
 | 
			
		||||
    const FreqSettingsBundle& bundle = printer_technology() == ptSLA ? 
 | 
			
		||||
    const SettingsBundle& bundle = printer_technology() == ptSLA ? 
 | 
			
		||||
                                       FREQ_SETTINGS_BUNDLE_SLA : FREQ_SETTINGS_BUNDLE_FFF;
 | 
			
		||||
 | 
			
		||||
    for (auto& it : bundle)
 | 
			
		||||
| 
						 | 
				
			
			@ -973,7 +998,7 @@ const std::vector<std::string>& ObjectList::get_options_for_bundle(const wxStrin
 | 
			
		|||
    }
 | 
			
		||||
#if 0
 | 
			
		||||
    // if "Quick menu" is selected
 | 
			
		||||
    FreqSettingsBundle& bundle_quick = printer_technology() == ptSLA ?
 | 
			
		||||
    SettingsBundle& bundle_quick = printer_technology() == ptSLA ?
 | 
			
		||||
                                       m_freq_settings_sla: m_freq_settings_fff;
 | 
			
		||||
 | 
			
		||||
    for (auto& it : bundle_quick)
 | 
			
		||||
| 
						 | 
				
			
			@ -1049,7 +1074,7 @@ void ObjectList::get_settings_choice(const wxString& category_name)
 | 
			
		|||
    if (selection_cnt > 0) 
 | 
			
		||||
    {
 | 
			
		||||
        // Add selected items to the "Quick menu"
 | 
			
		||||
        FreqSettingsBundle& freq_settings = printer_technology() == ptSLA ?
 | 
			
		||||
        SettingsBundle& freq_settings = printer_technology() == ptSLA ?
 | 
			
		||||
                                            m_freq_settings_sla : m_freq_settings_fff;
 | 
			
		||||
        bool changed_existing = false;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1090,6 +1115,8 @@ void ObjectList::get_settings_choice(const wxString& category_name)
 | 
			
		|||
    }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    take_snapshot(wxString::Format(_(L("Add Settings for %s")), is_part ? _(L("Sub-object")) : _(L("Object"))));
 | 
			
		||||
 | 
			
		||||
    std::vector <std::string> selected_options;
 | 
			
		||||
    selected_options.reserve(selection_cnt);
 | 
			
		||||
    for (auto sel : selections)
 | 
			
		||||
| 
						 | 
				
			
			@ -1119,8 +1146,8 @@ void ObjectList::get_settings_choice(const wxString& category_name)
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // Add settings item for object
 | 
			
		||||
    update_settings_item();
 | 
			
		||||
    // Add settings item for object/sub-object and show them 
 | 
			
		||||
    show_settings(add_settings_item(GetSelection(), m_config));    
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectList::get_freq_settings_choice(const wxString& bundle_name)
 | 
			
		||||
| 
						 | 
				
			
			@ -1140,6 +1167,8 @@ void ObjectList::get_freq_settings_choice(const wxString& bundle_name)
 | 
			
		|||
    assert(m_config);
 | 
			
		||||
    auto opt_keys = m_config->keys();
 | 
			
		||||
 | 
			
		||||
    take_snapshot(wxString::Format(_(L("Add Settings Bundle for %s")), m_objects_model->GetItemType(GetSelection()) & itObject ? _(L("Object")) : _(L("Sub-object"))));
 | 
			
		||||
 | 
			
		||||
    const DynamicPrintConfig& from_config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
 | 
			
		||||
    for (auto& opt_key : options)
 | 
			
		||||
    {
 | 
			
		||||
| 
						 | 
				
			
			@ -1154,13 +1183,21 @@ void ObjectList::get_freq_settings_choice(const wxString& bundle_name)
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Add settings item for object
 | 
			
		||||
    update_settings_item();
 | 
			
		||||
    // Add settings item for object/sub-object and show them 
 | 
			
		||||
    show_settings(add_settings_item(GetSelection(), m_config));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectList::update_settings_item()
 | 
			
		||||
void ObjectList::show_settings(const wxDataViewItem settings_item)
 | 
			
		||||
{
 | 
			
		||||
    auto item = GetSelection();
 | 
			
		||||
    if (!settings_item)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    select_item(settings_item);
 | 
			
		||||
    
 | 
			
		||||
    // update object selection on Plater
 | 
			
		||||
    if (!m_prevent_canvas_selection_update)
 | 
			
		||||
        update_selections_on_canvas();
 | 
			
		||||
/*    auto item = GetSelection();
 | 
			
		||||
    if (item) {
 | 
			
		||||
        if (m_objects_model->GetItemType(item) == itInstance)
 | 
			
		||||
            item = m_objects_model->GetTopParent(item);
 | 
			
		||||
| 
						 | 
				
			
			@ -1172,12 +1209,14 @@ void ObjectList::update_settings_item()
 | 
			
		|||
        if (!m_prevent_canvas_selection_update)
 | 
			
		||||
            update_selections_on_canvas();
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
    else { 
 | 
			
		||||
        //# ys_FIXME ??? use case ???
 | 
			
		||||
        auto panel = wxGetApp().sidebar().scrolled_panel();
 | 
			
		||||
        panel->Freeze();
 | 
			
		||||
        wxGetApp().obj_settings()->UpdateAndShow(true);
 | 
			
		||||
        panel->Thaw();
 | 
			
		||||
    }
 | 
			
		||||
    */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
wxMenu* ObjectList::append_submenu_add_generic(wxMenu* menu, const ModelVolumeType type) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1488,7 +1527,7 @@ wxMenu* ObjectList::create_settings_popupmenu(wxMenu *parent_menu)
 | 
			
		|||
void ObjectList::create_freq_settings_popupmenu(wxMenu *menu)
 | 
			
		||||
{
 | 
			
		||||
    // Add default settings bundles
 | 
			
		||||
    const FreqSettingsBundle& bundle = printer_technology() == ptFFF ?
 | 
			
		||||
    const SettingsBundle& bundle = printer_technology() == ptFFF ?
 | 
			
		||||
                                     FREQ_SETTINGS_BUNDLE_FFF : FREQ_SETTINGS_BUNDLE_SLA;
 | 
			
		||||
 | 
			
		||||
    const int extruders_cnt = extruders_count();
 | 
			
		||||
| 
						 | 
				
			
			@ -1503,7 +1542,7 @@ void ObjectList::create_freq_settings_popupmenu(wxMenu *menu)
 | 
			
		|||
    }
 | 
			
		||||
#if 0
 | 
			
		||||
    // Add "Quick" settings bundles
 | 
			
		||||
    const FreqSettingsBundle& bundle_quick = printer_technology() == ptFFF ?
 | 
			
		||||
    const SettingsBundle& bundle_quick = printer_technology() == ptFFF ?
 | 
			
		||||
                                             m_freq_settings_fff : m_freq_settings_sla;
 | 
			
		||||
 | 
			
		||||
    for (auto& it : bundle_quick) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1539,6 +1578,8 @@ void ObjectList::load_subobject(ModelVolumeType type)
 | 
			
		|||
    if (m_objects_model->GetItemType(item)&itInstance)
 | 
			
		||||
        item = m_objects_model->GetItemById(obj_idx);
 | 
			
		||||
 | 
			
		||||
    take_snapshot(_(L("Load Part")));
 | 
			
		||||
 | 
			
		||||
    std::vector<std::pair<wxString, bool>> volumes_info;
 | 
			
		||||
    load_part((*m_objects)[obj_idx], volumes_info, type);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1614,6 +1655,8 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode
 | 
			
		|||
    if (instance_idx == -1)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    take_snapshot(_(L("Add Generic Subobject")));
 | 
			
		||||
 | 
			
		||||
    // Selected object
 | 
			
		||||
    ModelObject  &model_object = *(*m_objects)[obj_idx];
 | 
			
		||||
    // Bounding box of the selected instance in world coordinate system including the translation, without modifiers.
 | 
			
		||||
| 
						 | 
				
			
			@ -1720,6 +1763,8 @@ void ObjectList::del_settings_from_config(const wxDataViewItem& parent_item)
 | 
			
		|||
        is_layer_settings && opt_cnt == 2 && m_config->has("extruder") && m_config->has("layer_height"))
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    take_snapshot(_(L("Delete Settings")));
 | 
			
		||||
 | 
			
		||||
    int extruder = -1;
 | 
			
		||||
    if (m_config->has("extruder"))
 | 
			
		||||
        extruder = m_config->option<ConfigOptionInt>("extruder")->value;
 | 
			
		||||
| 
						 | 
				
			
			@ -1742,6 +1787,8 @@ void ObjectList::del_instances_from_object(const int obj_idx)
 | 
			
		|||
    if (instances.size() <= 1)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    take_snapshot(_(L("Delete All Instances from Object")));
 | 
			
		||||
 | 
			
		||||
    while ( instances.size()> 1)
 | 
			
		||||
        instances.pop_back();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1755,6 +1802,8 @@ void ObjectList::del_layer_from_object(const int obj_idx, const t_layer_height_r
 | 
			
		|||
    const auto del_range = object(obj_idx)->layer_config_ranges.find(layer_range);
 | 
			
		||||
    if (del_range == object(obj_idx)->layer_config_ranges.end())
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    take_snapshot(_(L("Delete Layers Range")));
 | 
			
		||||
        
 | 
			
		||||
    object(obj_idx)->layer_config_ranges.erase(del_range);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1789,6 +1838,8 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con
 | 
			
		|||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        take_snapshot(_(L("Delete Subobject")));
 | 
			
		||||
 | 
			
		||||
        object->delete_volume(idx);
 | 
			
		||||
 | 
			
		||||
        if (object->volumes.size() == 1)
 | 
			
		||||
| 
						 | 
				
			
			@ -1805,6 +1856,8 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con
 | 
			
		|||
            Slic3r::GUI::show_error(nullptr, _(L("You can't delete the last intance from object.")));
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        take_snapshot(_(L("Delete Instance")));
 | 
			
		||||
        object->delete_instance(idx);
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
| 
						 | 
				
			
			@ -1832,6 +1885,8 @@ void ObjectList::split()
 | 
			
		|||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    take_snapshot(_(L("Split to Parts")));
 | 
			
		||||
 | 
			
		||||
    wxBusyCursor wait;
 | 
			
		||||
 | 
			
		||||
    auto model_object = (*m_objects)[obj_idx];
 | 
			
		||||
| 
						 | 
				
			
			@ -1850,11 +1905,7 @@ void ObjectList::split()
 | 
			
		|||
            volume->config.option<ConfigOptionInt>("extruder")->value : 0,
 | 
			
		||||
            false);
 | 
			
		||||
        // add settings to the part, if it has those
 | 
			
		||||
        auto opt_keys = volume->config.keys();
 | 
			
		||||
        if ( !(opt_keys.size() == 1 && opt_keys[0] == "extruder") ) {
 | 
			
		||||
            select_item(m_objects_model->AddSettingsChild(vol_item));
 | 
			
		||||
            Expand(vol_item);
 | 
			
		||||
        }
 | 
			
		||||
        add_settings_item(vol_item, &volume->config);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (parent == item)
 | 
			
		||||
| 
						 | 
				
			
			@ -1879,8 +1930,10 @@ void ObjectList::layers_editing()
 | 
			
		|||
        t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges;
 | 
			
		||||
 | 
			
		||||
        // set some default value
 | 
			
		||||
        if (ranges.empty())
 | 
			
		||||
        if (ranges.empty()) {
 | 
			
		||||
            take_snapshot(_(L("Add Layers")));
 | 
			
		||||
            ranges[{ 0.0f, 2.0f }] = get_default_layer_config(obj_idx);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // create layer root item
 | 
			
		||||
        layers_item = add_layer_root_item(obj_item);
 | 
			
		||||
| 
						 | 
				
			
			@ -1912,6 +1965,7 @@ wxDataViewItem ObjectList::add_layer_root_item(const wxDataViewItem obj_item)
 | 
			
		|||
    for (const auto range : object(obj_idx)->layer_config_ranges)
 | 
			
		||||
        add_layer_item(range.first, layers_item);
 | 
			
		||||
 | 
			
		||||
    Expand(layers_item);
 | 
			
		||||
    return layers_item;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2097,7 +2151,79 @@ void ObjectList::part_selection_changed()
 | 
			
		|||
    panel.Thaw();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectList::add_object_to_list(size_t obj_idx)
 | 
			
		||||
SettingsBundle ObjectList::get_item_settings_bundle(const DynamicPrintConfig* config, const bool is_layers_range_settings)
 | 
			
		||||
{
 | 
			
		||||
    auto opt_keys = config->keys();
 | 
			
		||||
    if (opt_keys.empty())
 | 
			
		||||
        return SettingsBundle();
 | 
			
		||||
 | 
			
		||||
    update_opt_keys(opt_keys); // update options list according to print technology
 | 
			
		||||
 | 
			
		||||
    if (opt_keys.size() == 1 && opt_keys[0] == "extruder" ||
 | 
			
		||||
        is_layers_range_settings && opt_keys.size() == 2)
 | 
			
		||||
        return SettingsBundle();
 | 
			
		||||
 | 
			
		||||
    const int extruders_cnt = wxGetApp().extruders_edited_cnt();
 | 
			
		||||
 | 
			
		||||
    SettingsBundle bundle;
 | 
			
		||||
    for (auto& opt_key : opt_keys)
 | 
			
		||||
    {
 | 
			
		||||
        auto category = config->def()->get(opt_key)->category;
 | 
			
		||||
        if (category.empty() || (category == "Extruders" && extruders_cnt == 1)) 
 | 
			
		||||
            continue;
 | 
			
		||||
 | 
			
		||||
        std::vector< std::string > new_category;
 | 
			
		||||
 | 
			
		||||
        auto& cat_opt = bundle.find(category) == bundle.end() ? new_category : bundle.at(category);
 | 
			
		||||
        cat_opt.push_back(opt_key);
 | 
			
		||||
        if (cat_opt.size() == 1)
 | 
			
		||||
            bundle[category] = cat_opt;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return bundle;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Add new SettingsItem for parent_item if it doesn't exist, or just update a digest according to new config
 | 
			
		||||
wxDataViewItem ObjectList::add_settings_item(wxDataViewItem parent_item, const DynamicPrintConfig* config)
 | 
			
		||||
{
 | 
			
		||||
    wxDataViewItem ret = wxDataViewItem(0);
 | 
			
		||||
 | 
			
		||||
    if (!parent_item)
 | 
			
		||||
        return ret;
 | 
			
		||||
 | 
			
		||||
    const bool is_layers_range_settings = m_objects_model->GetItemType(parent_item) == itLayer;
 | 
			
		||||
    SettingsBundle cat_options = get_item_settings_bundle(config, is_layers_range_settings);
 | 
			
		||||
    if (cat_options.empty())
 | 
			
		||||
        return ret;
 | 
			
		||||
 | 
			
		||||
    std::vector<std::string> categories;
 | 
			
		||||
    categories.reserve(cat_options.size());
 | 
			
		||||
    for (auto& cat : cat_options)
 | 
			
		||||
    {
 | 
			
		||||
        if (cat.second.size() == 1 &&
 | 
			
		||||
            (cat.second[0] == "extruder" || is_layers_range_settings && cat.second[0] == "layer_height"))
 | 
			
		||||
            continue;
 | 
			
		||||
 | 
			
		||||
        categories.push_back(cat.first);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (categories.empty())
 | 
			
		||||
        return ret;
 | 
			
		||||
 | 
			
		||||
    if (m_objects_model->GetItemType(parent_item) & itInstance)
 | 
			
		||||
        parent_item = m_objects_model->GetTopParent(parent_item);
 | 
			
		||||
 | 
			
		||||
    ret = m_objects_model->IsSettingsItem(parent_item) ? parent_item : m_objects_model->GetSettingsItem(parent_item);
 | 
			
		||||
 | 
			
		||||
    if (!ret) ret = m_objects_model->AddSettingsChild(parent_item);
 | 
			
		||||
 | 
			
		||||
    m_objects_model->UpdateSettingsDigest(ret, categories);
 | 
			
		||||
    Expand(parent_item);
 | 
			
		||||
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed)
 | 
			
		||||
{
 | 
			
		||||
    auto model_object = (*m_objects)[obj_idx];
 | 
			
		||||
    const wxString& item_name = from_u8(model_object->name);
 | 
			
		||||
| 
						 | 
				
			
			@ -2116,11 +2242,7 @@ void ObjectList::add_object_to_list(size_t obj_idx)
 | 
			
		|||
                !volume->config.has("extruder") ? 0 :
 | 
			
		||||
                volume->config.option<ConfigOptionInt>("extruder")->value,
 | 
			
		||||
                false);
 | 
			
		||||
            auto opt_keys = volume->config.keys();
 | 
			
		||||
            if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) {
 | 
			
		||||
                select_item(m_objects_model->AddSettingsChild(vol_item));
 | 
			
		||||
                Expand(vol_item);
 | 
			
		||||
            }
 | 
			
		||||
            add_settings_item(vol_item, &volume->config);
 | 
			
		||||
        }
 | 
			
		||||
        Expand(item);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -2130,17 +2252,14 @@ void ObjectList::add_object_to_list(size_t obj_idx)
 | 
			
		|||
        increase_object_instances(obj_idx, model_object->instances.size());
 | 
			
		||||
 | 
			
		||||
    // add settings to the object, if it has those
 | 
			
		||||
    auto opt_keys = model_object->config.keys();
 | 
			
		||||
    if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) {
 | 
			
		||||
        select_item(m_objects_model->AddSettingsChild(item));
 | 
			
		||||
        Expand(item);
 | 
			
		||||
    }
 | 
			
		||||
    add_settings_item(item, &model_object->config);
 | 
			
		||||
 | 
			
		||||
    // Add layers if it has
 | 
			
		||||
    add_layer_root_item(item);
 | 
			
		||||
 | 
			
		||||
#ifndef __WXOSX__ 
 | 
			
		||||
    selection_changed();
 | 
			
		||||
    if (call_selection_changed)
 | 
			
		||||
	    selection_changed();
 | 
			
		||||
#endif //__WXMSW__
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2175,6 +2294,8 @@ void ObjectList::delete_from_model_and_list(const ItemType type, const int obj_i
 | 
			
		|||
    if ( !(type&(itObject|itVolume|itInstance)) )
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    take_snapshot(_(L("Delete Selected Item")));
 | 
			
		||||
 | 
			
		||||
    if (type&itObject) {
 | 
			
		||||
        del_object(obj_idx);
 | 
			
		||||
        delete_object_from_list(obj_idx);
 | 
			
		||||
| 
						 | 
				
			
			@ -2285,6 +2406,9 @@ void ObjectList::remove()
 | 
			
		|||
 | 
			
		||||
    wxDataViewItem  parent = wxDataViewItem(0);
 | 
			
		||||
 | 
			
		||||
    take_snapshot(_(L("Delete Selected")));
 | 
			
		||||
    suppress_snapshots();
 | 
			
		||||
 | 
			
		||||
    for (auto& item : sels)
 | 
			
		||||
    {
 | 
			
		||||
        if (m_objects_model->GetParent(item) == wxDataViewItem(0))
 | 
			
		||||
| 
						 | 
				
			
			@ -2305,6 +2429,8 @@ void ObjectList::remove()
 | 
			
		|||
 | 
			
		||||
    if (parent)
 | 
			
		||||
        select_item(parent);
 | 
			
		||||
 | 
			
		||||
    allow_snapshots();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectList::del_layer_range(const t_layer_height_range& range)
 | 
			
		||||
| 
						 | 
				
			
			@ -2350,6 +2476,8 @@ void ObjectList::add_layer_range_after_current(const t_layer_height_range& curre
 | 
			
		|||
    
 | 
			
		||||
    if (current_range == last_range)
 | 
			
		||||
    {
 | 
			
		||||
        take_snapshot(_(L("Add New Layers 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);
 | 
			
		||||
| 
						 | 
				
			
			@ -2377,22 +2505,28 @@ void ObjectList::add_layer_range_after_current(const t_layer_height_range& curre
 | 
			
		|||
            
 | 
			
		||||
            t_layer_height_range new_range = { midl_layer, next_range.second };
 | 
			
		||||
 | 
			
		||||
            take_snapshot(_(L("Add New Layers Range")));
 | 
			
		||||
            suppress_snapshots();
 | 
			
		||||
 | 
			
		||||
            // create new 2 layers instead of deleted one
 | 
			
		||||
 | 
			
		||||
            // 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);
 | 
			
		||||
            allow_snapshots();
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            take_snapshot(_(L("Add New Layers Range")));
 | 
			
		||||
 | 
			
		||||
            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);
 | 
			
		||||
| 
						 | 
				
			
			@ -2420,9 +2554,7 @@ void ObjectList::add_layer_item(const t_layer_height_range& range,
 | 
			
		|||
                                                            range, 
 | 
			
		||||
                                                            config.opt_int("extruder"),
 | 
			
		||||
                                                            layer_idx);
 | 
			
		||||
 | 
			
		||||
    if (config.keys().size() > 2)
 | 
			
		||||
        select_item(m_objects_model->AddSettingsChild(layer_item));
 | 
			
		||||
    add_settings_item(layer_item, &config);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ObjectList::edit_layer_range(const t_layer_height_range& range, coordf_t layer_height)
 | 
			
		||||
| 
						 | 
				
			
			@ -2452,6 +2584,8 @@ bool ObjectList::edit_layer_range(const t_layer_height_range& range, const t_lay
 | 
			
		|||
    const int obj_idx = get_selected_obj_idx();
 | 
			
		||||
    if (obj_idx < 0) return false;
 | 
			
		||||
 | 
			
		||||
    take_snapshot(_(L("Edit Layers Range")));
 | 
			
		||||
 | 
			
		||||
    const ItemType sel_type = m_objects_model->GetItemType(GetSelection());
 | 
			
		||||
 | 
			
		||||
    t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges;
 | 
			
		||||
| 
						 | 
				
			
			@ -2918,6 +3052,8 @@ void ObjectList::change_part_type()
 | 
			
		|||
	if (new_type == type || new_type == ModelVolumeType::INVALID)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    take_snapshot(_(L("Paste from Clipboard")));
 | 
			
		||||
 | 
			
		||||
    const auto item = GetSelection();
 | 
			
		||||
    volume->set_type(new_type);
 | 
			
		||||
    m_objects_model->SetVolumeType(item, new_type);
 | 
			
		||||
| 
						 | 
				
			
			@ -2933,18 +3069,17 @@ void ObjectList::change_part_type()
 | 
			
		|||
    }
 | 
			
		||||
    else if (!settings_item && 
 | 
			
		||||
              (new_type == ModelVolumeType::MODEL_PART || new_type == ModelVolumeType::PARAMETER_MODIFIER)) {
 | 
			
		||||
        select_item(m_objects_model->AddSettingsChild(item));
 | 
			
		||||
        add_settings_item(item, &volume->config);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectList::last_volume_is_deleted(const int obj_idx)
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    if (obj_idx < 0 || m_objects->empty() ||
 | 
			
		||||
        obj_idx <= m_objects->size() ||
 | 
			
		||||
        (*m_objects)[obj_idx]->volumes.empty())
 | 
			
		||||
    if (obj_idx < 0 || obj_idx >= m_objects->size() || (*m_objects)[obj_idx]->volumes.empty())
 | 
			
		||||
        return;
 | 
			
		||||
    auto volume = (*m_objects)[obj_idx]->volumes[0];
 | 
			
		||||
 | 
			
		||||
    auto volume = (*m_objects)[obj_idx]->volumes.front();
 | 
			
		||||
 | 
			
		||||
    // clear volume's config values
 | 
			
		||||
    volume->config.clear();
 | 
			
		||||
| 
						 | 
				
			
			@ -2966,6 +3101,7 @@ bool ObjectList::has_multi_part_objects()
 | 
			
		|||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* #lm_FIXME_delete_after_testing
 | 
			
		||||
void ObjectList::update_settings_items()
 | 
			
		||||
{
 | 
			
		||||
    m_prevent_canvas_selection_update = true;
 | 
			
		||||
| 
						 | 
				
			
			@ -2991,22 +3127,52 @@ void ObjectList::update_settings_items()
 | 
			
		|||
    SetSelections(sel);
 | 
			
		||||
    m_prevent_canvas_selection_update = false;
 | 
			
		||||
}
 | 
			
		||||
*/
 | 
			
		||||
void ObjectList::update_and_show_object_settings_item()
 | 
			
		||||
{
 | 
			
		||||
    const wxDataViewItem item = GetSelection();
 | 
			
		||||
    if (!item) return;
 | 
			
		||||
 | 
			
		||||
    const wxDataViewItem& obj_item = m_objects_model->IsSettingsItem(item) ? m_objects_model->GetParent(item) : item;
 | 
			
		||||
    select_item(add_settings_item(obj_item, &get_item_config(obj_item)));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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));
 | 
			
		||||
    const wxDataViewItem old_settings_item = m_objects_model->GetSettingsItem(item);
 | 
			
		||||
    const wxDataViewItem new_settings_item = add_settings_item(item, &get_item_config(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);
 | 
			
		||||
    if (!new_settings_item && old_settings_item)
 | 
			
		||||
        m_objects_model->Delete(old_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);
 | 
			
		||||
    // if ols settings item was is selected area
 | 
			
		||||
    if (selections.Index(old_settings_item) != wxNOT_FOUND)
 | 
			
		||||
    {
 | 
			
		||||
        // If settings item was just updated
 | 
			
		||||
        if (old_settings_item == new_settings_item)
 | 
			
		||||
        {
 | 
			
		||||
            Sidebar& panel = wxGetApp().sidebar();
 | 
			
		||||
            panel.Freeze();
 | 
			
		||||
 | 
			
		||||
            // update settings list
 | 
			
		||||
            wxGetApp().obj_settings()->UpdateAndShow(true);
 | 
			
		||||
 | 
			
		||||
            panel.Layout();
 | 
			
		||||
            panel.Thaw();
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        // If settings item was deleted from the list, 
 | 
			
		||||
        // it's need to be deleted from selection array, if it was there
 | 
			
		||||
        {
 | 
			
		||||
            selections.Remove(old_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);
 | 
			
		||||
                select_item(item); // to correct update of the SettingsList and ManipulationPanel sizers
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3360,6 +3526,35 @@ void ObjectList::set_extruder_for_selected_items(const int extruder) const
 | 
			
		|||
    wxGetApp().plater()->update();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectList::update_after_undo_redo()
 | 
			
		||||
{
 | 
			
		||||
    m_prevent_list_events = true;
 | 
			
		||||
    m_prevent_canvas_selection_update = true;
 | 
			
		||||
 | 
			
		||||
    suppress_snapshots();
 | 
			
		||||
 | 
			
		||||
    // Unselect all objects before deleting them, so that no change of selection is emitted during deletion.
 | 
			
		||||
    this->UnselectAll();
 | 
			
		||||
    m_objects_model->DeleteAll();
 | 
			
		||||
 | 
			
		||||
    size_t obj_idx = 0;
 | 
			
		||||
    while (obj_idx < m_objects->size()) {
 | 
			
		||||
        add_object_to_list(obj_idx, false);
 | 
			
		||||
        ++obj_idx;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    allow_snapshots();
 | 
			
		||||
 | 
			
		||||
#ifndef __WXOSX__ 
 | 
			
		||||
    selection_changed();
 | 
			
		||||
#endif /* __WXOSX__ */
 | 
			
		||||
 | 
			
		||||
    update_selections();
 | 
			
		||||
 | 
			
		||||
    m_prevent_canvas_selection_update = false;
 | 
			
		||||
    m_prevent_list_events = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ModelObject* ObjectList::object(const int obj_idx) const
 | 
			
		||||
{
 | 
			
		||||
    if (obj_idx < 0)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,7 +26,7 @@ enum class ModelVolumeType : int;
 | 
			
		|||
// FIXME: broken build on mac os because of this is missing:
 | 
			
		||||
typedef std::vector<std::string>    t_config_option_keys;
 | 
			
		||||
 | 
			
		||||
typedef std::map<std::string, std::vector<std::string>> FreqSettingsBundle;
 | 
			
		||||
typedef std::map<std::string, std::vector<std::string>> SettingsBundle;
 | 
			
		||||
 | 
			
		||||
//				  category ->		vector 			 ( option	;  label )
 | 
			
		||||
typedef std::map< std::string, std::vector< std::pair<std::string, std::string> > > settings_menu_hierarchy;
 | 
			
		||||
| 
						 | 
				
			
			@ -152,8 +152,8 @@ class ObjectList : public wxDataViewCtrl
 | 
			
		|||
    wxDataViewItem m_last_selected_item {nullptr};
 | 
			
		||||
 | 
			
		||||
#if 0
 | 
			
		||||
    FreqSettingsBundle m_freq_settings_fff;
 | 
			
		||||
    FreqSettingsBundle m_freq_settings_sla;
 | 
			
		||||
    SettingsBundle m_freq_settings_fff;
 | 
			
		||||
    SettingsBundle m_freq_settings_sla;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
| 
						 | 
				
			
			@ -204,10 +204,12 @@ public:
 | 
			
		|||
 | 
			
		||||
    void                copy();
 | 
			
		||||
    void                paste();
 | 
			
		||||
    void                undo();
 | 
			
		||||
    void                redo();
 | 
			
		||||
 | 
			
		||||
    void                get_settings_choice(const wxString& category_name);
 | 
			
		||||
    void                get_freq_settings_choice(const wxString& bundle_name);
 | 
			
		||||
    void                update_settings_item();
 | 
			
		||||
    void                show_settings(const wxDataViewItem settings_item);
 | 
			
		||||
 | 
			
		||||
    wxMenu*             append_submenu_add_generic(wxMenu* menu, const ModelVolumeType type);
 | 
			
		||||
    void                append_menu_items_add_volume(wxMenu* menu);
 | 
			
		||||
| 
						 | 
				
			
			@ -245,6 +247,7 @@ public:
 | 
			
		|||
    void                layers_editing();
 | 
			
		||||
 | 
			
		||||
    wxDataViewItem      add_layer_root_item(const wxDataViewItem obj_item);
 | 
			
		||||
    wxDataViewItem      add_settings_item(wxDataViewItem parent_item, const DynamicPrintConfig* config);
 | 
			
		||||
 | 
			
		||||
    DynamicPrintConfig  get_default_layer_config(const int obj_idx);
 | 
			
		||||
    bool                get_volume_by_item(const wxDataViewItem& item, ModelVolume*& volume);
 | 
			
		||||
| 
						 | 
				
			
			@ -256,12 +259,13 @@ public:
 | 
			
		|||
    wxBoxSizer*         get_sizer() {return  m_sizer;}
 | 
			
		||||
    int                 get_selected_obj_idx() const;
 | 
			
		||||
    DynamicPrintConfig& get_item_config(const wxDataViewItem& item) const;
 | 
			
		||||
    SettingsBundle      get_item_settings_bundle(const DynamicPrintConfig* config, const bool is_layers_range_settings);
 | 
			
		||||
 | 
			
		||||
    void                changed_object(const int obj_idx = -1) const;
 | 
			
		||||
    void                part_selection_changed();
 | 
			
		||||
 | 
			
		||||
    // Add object to the list
 | 
			
		||||
    void add_object_to_list(size_t obj_idx);
 | 
			
		||||
    void add_object_to_list(size_t obj_idx, bool call_selection_changed = true);
 | 
			
		||||
    // Delete object from the list
 | 
			
		||||
    void delete_object_from_list();
 | 
			
		||||
    void delete_object_from_list(const size_t obj_idx);
 | 
			
		||||
| 
						 | 
				
			
			@ -315,6 +319,7 @@ public:
 | 
			
		|||
    void last_volume_is_deleted(const int obj_idx);
 | 
			
		||||
    bool has_multi_part_objects();
 | 
			
		||||
    void update_settings_items();
 | 
			
		||||
    void update_and_show_object_settings_item();
 | 
			
		||||
    void update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections);
 | 
			
		||||
    void update_object_list_by_printer_technology();
 | 
			
		||||
    void update_object_menu();
 | 
			
		||||
| 
						 | 
				
			
			@ -333,6 +338,8 @@ public:
 | 
			
		|||
 | 
			
		||||
    void msw_rescale();
 | 
			
		||||
 | 
			
		||||
    void update_after_undo_redo();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
#ifdef __WXOSX__
 | 
			
		||||
//    void OnChar(wxKeyEvent& event);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -242,8 +242,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
 | 
			
		|||
                selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL);
 | 
			
		||||
                selection.synchronize_unselected_volumes();
 | 
			
		||||
                // Copy mirroring values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing.
 | 
			
		||||
                canvas->do_mirror();
 | 
			
		||||
                canvas->set_as_dirty();
 | 
			
		||||
                canvas->do_mirror("Set Mirror");
 | 
			
		||||
                UpdateAndShow(true);
 | 
			
		||||
            });
 | 
			
		||||
        return sizer;
 | 
			
		||||
| 
						 | 
				
			
			@ -324,7 +323,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
 | 
			
		|||
                    selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL);
 | 
			
		||||
                    selection.synchronize_unselected_volumes();
 | 
			
		||||
                    // Copy rotation values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing.
 | 
			
		||||
                    canvas->do_rotate();
 | 
			
		||||
                    canvas->do_rotate("Set Rotation");
 | 
			
		||||
 | 
			
		||||
                    UpdateAndShow(true);
 | 
			
		||||
                });
 | 
			
		||||
| 
						 | 
				
			
			@ -710,7 +709,7 @@ void ObjectManipulation::change_position_value(int axis, double value)
 | 
			
		|||
    Selection& selection = canvas->get_selection();
 | 
			
		||||
    selection.start_dragging();
 | 
			
		||||
    selection.translate(position - m_cache.position, selection.requires_local_axes());
 | 
			
		||||
    canvas->do_move();
 | 
			
		||||
    canvas->do_move("Set Position");
 | 
			
		||||
 | 
			
		||||
    m_cache.position = position;
 | 
			
		||||
	m_cache.position_rounded(axis) = DBL_MAX;
 | 
			
		||||
| 
						 | 
				
			
			@ -741,7 +740,7 @@ void ObjectManipulation::change_rotation_value(int axis, double value)
 | 
			
		|||
	selection.rotate(
 | 
			
		||||
		(M_PI / 180.0) * (transformation_type.absolute() ? rotation : rotation - m_cache.rotation), 
 | 
			
		||||
		transformation_type);
 | 
			
		||||
    canvas->do_rotate();
 | 
			
		||||
    canvas->do_rotate("Set Orientation");
 | 
			
		||||
 | 
			
		||||
    m_cache.rotation = rotation;
 | 
			
		||||
	m_cache.rotation_rounded(axis) = DBL_MAX;
 | 
			
		||||
| 
						 | 
				
			
			@ -806,7 +805,7 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const
 | 
			
		|||
 | 
			
		||||
    selection.start_dragging();
 | 
			
		||||
    selection.scale(scaling_factor * 0.01, transformation_type);
 | 
			
		||||
    wxGetApp().plater()->canvas3D()->do_scale();
 | 
			
		||||
    wxGetApp().plater()->canvas3D()->do_scale("Set Scale");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectManipulation::on_change(t_config_option_key opt_key, const boost::any& value)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -63,20 +63,28 @@ ObjectSettings::ObjectSettings(wxWindow* parent) :
 | 
			
		|||
    m_bmp_delete = ScalableBitmap(parent, "cross");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectSettings::update_settings_list()
 | 
			
		||||
bool ObjectSettings::update_settings_list()
 | 
			
		||||
{
 | 
			
		||||
    m_settings_list_sizer->Clear(true);
 | 
			
		||||
    m_og_settings.resize(0);
 | 
			
		||||
 | 
			
		||||
    auto objects_ctrl   = wxGetApp().obj_list();
 | 
			
		||||
    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_model->IsSettingsItem(item) || !config || objects_ctrl->multiple_selection())
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
    const bool is_layers_range_settings = objects_model->GetItemType(objects_model->GetParent(item)) == itLayer;
 | 
			
		||||
	SettingsBundle cat_options = objects_ctrl->get_item_settings_bundle(config, is_layers_range_settings);
 | 
			
		||||
 | 
			
		||||
    if (!cat_options.empty())
 | 
			
		||||
    {
 | 
			
		||||
	    std::vector<std::string> categories;
 | 
			
		||||
        categories.reserve(cat_options.size());
 | 
			
		||||
 | 
			
		||||
    if (item && !objects_ctrl->multiple_selection() && 
 | 
			
		||||
        config && objects_model->IsSettingsItem(item))
 | 
			
		||||
	{
 | 
			
		||||
        auto extra_column = [config, this](wxWindow* parent, const Line& line)
 | 
			
		||||
		{
 | 
			
		||||
			auto opt_key = (line.get_options())[0].opt_id;  //we assume that we have one option per line
 | 
			
		||||
| 
						 | 
				
			
			@ -96,86 +104,61 @@ void ObjectSettings::update_settings_list()
 | 
			
		|||
			return btn;
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		std::map<std::string, std::vector<std::string>> cat_options;
 | 
			
		||||
		auto opt_keys = config->keys();
 | 
			
		||||
        objects_ctrl->update_opt_keys(opt_keys); // update options list according to print technology
 | 
			
		||||
 | 
			
		||||
        m_og_settings.resize(0);
 | 
			
		||||
        std::vector<std::string> categories;
 | 
			
		||||
        if (!(opt_keys.size() == 1 && opt_keys[0] == "extruder"))// return;
 | 
			
		||||
        for (auto& cat : cat_options)
 | 
			
		||||
        {
 | 
			
		||||
            const int extruders_cnt = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA ? 1 :
 | 
			
		||||
                wxGetApp().preset_bundle->printers.get_edited_preset().config.option<ConfigOptionFloats>("nozzle_diameter")->values.size();
 | 
			
		||||
            if (cat.second.size() == 1 &&
 | 
			
		||||
                (cat.second[0] == "extruder" || is_layers_range_settings && cat.second[0] == "layer_height"))
 | 
			
		||||
                continue;
 | 
			
		||||
 | 
			
		||||
            for (auto& opt_key : opt_keys) {
 | 
			
		||||
                auto category = config->def()->get(opt_key)->category;
 | 
			
		||||
                if (category.empty() ||
 | 
			
		||||
                    (category == "Extruders" && extruders_cnt == 1)) continue;
 | 
			
		||||
            categories.push_back(cat.first);
 | 
			
		||||
 | 
			
		||||
                std::vector< std::string > new_category;
 | 
			
		||||
            auto optgroup = std::make_shared<ConfigOptionsGroup>(m_og->ctrl_parent(), _(cat.first), config, false, extra_column);
 | 
			
		||||
            optgroup->label_width = 15;
 | 
			
		||||
            optgroup->sidetext_width = 5.5;
 | 
			
		||||
 | 
			
		||||
                auto& cat_opt = cat_options.find(category) == cat_options.end() ? new_category : cat_options.at(category);
 | 
			
		||||
                cat_opt.push_back(opt_key);
 | 
			
		||||
                if (cat_opt.size() == 1)
 | 
			
		||||
                    cat_options[category] = cat_opt;
 | 
			
		||||
            }
 | 
			
		||||
            optgroup->m_on_change = [](const t_config_option_key& opt_id, const boost::any& value) {
 | 
			
		||||
                                    wxGetApp().obj_list()->changed_object(); };
 | 
			
		||||
 | 
			
		||||
            for (auto& cat : cat_options) {
 | 
			
		||||
                if (cat.second.size() == 1 && 
 | 
			
		||||
                    (cat.second[0] == "extruder" || is_layers_range_settings && cat.second[0] == "layer_height"))
 | 
			
		||||
            // call back for rescaling of the extracolumn control
 | 
			
		||||
            optgroup->rescale_extra_column_item = [this](wxWindow* win) {
 | 
			
		||||
                auto *ctrl = dynamic_cast<ScalableButton*>(win);
 | 
			
		||||
                if (ctrl == nullptr)
 | 
			
		||||
                    return;
 | 
			
		||||
                ctrl->SetBitmap_(m_bmp_delete);
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            const bool is_extruders_cat = cat.first == "Extruders";
 | 
			
		||||
            for (auto& opt : cat.second)
 | 
			
		||||
            {
 | 
			
		||||
                if (opt == "extruder" || is_layers_range_settings && opt == "layer_height")
 | 
			
		||||
                    continue;
 | 
			
		||||
 | 
			
		||||
                auto optgroup = std::make_shared<ConfigOptionsGroup>(m_og->ctrl_parent(), _(cat.first), config, false, extra_column);
 | 
			
		||||
                optgroup->label_width = 15;
 | 
			
		||||
                optgroup->sidetext_width = 5.5;
 | 
			
		||||
 | 
			
		||||
                optgroup->m_on_change = [](const t_config_option_key& opt_id, const boost::any& value) {
 | 
			
		||||
                                        wxGetApp().obj_list()->changed_object(); };
 | 
			
		||||
 | 
			
		||||
                const bool is_extruders_cat = cat.first == "Extruders";
 | 
			
		||||
                for (auto& opt : cat.second)
 | 
			
		||||
                {
 | 
			
		||||
                    if (opt == "extruder" || is_layers_range_settings && opt == "layer_height")
 | 
			
		||||
                        continue;
 | 
			
		||||
                    Option option = optgroup->get_option(opt);
 | 
			
		||||
                    option.opt.width = 12;
 | 
			
		||||
                    if (is_extruders_cat)
 | 
			
		||||
                        option.opt.max = wxGetApp().extruders_cnt();
 | 
			
		||||
                    optgroup->append_single_option_line(option);
 | 
			
		||||
                }
 | 
			
		||||
                optgroup->reload_config();
 | 
			
		||||
                m_settings_list_sizer->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 0);
 | 
			
		||||
 | 
			
		||||
                // call back for rescaling of the extracolumn control
 | 
			
		||||
                optgroup->rescale_extra_column_item = [this](wxWindow* win) {
 | 
			
		||||
                    auto *ctrl = dynamic_cast<ScalableButton*>(win);
 | 
			
		||||
                    if (ctrl == nullptr)
 | 
			
		||||
                        return;
 | 
			
		||||
                    ctrl->SetBitmap_(m_bmp_delete);
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                m_og_settings.push_back(optgroup);
 | 
			
		||||
 | 
			
		||||
                categories.push_back(cat.first);
 | 
			
		||||
                Option option = optgroup->get_option(opt);
 | 
			
		||||
                option.opt.width = 12;
 | 
			
		||||
                if (is_extruders_cat)
 | 
			
		||||
                    option.opt.max = wxGetApp().extruders_edited_cnt();
 | 
			
		||||
                optgroup->append_single_option_line(option);
 | 
			
		||||
            }
 | 
			
		||||
            optgroup->reload_config();
 | 
			
		||||
 | 
			
		||||
            m_settings_list_sizer->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 0);
 | 
			
		||||
            m_og_settings.push_back(optgroup);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (m_og_settings.empty()) {
 | 
			
		||||
            objects_ctrl->select_item(objects_model->Delete(item));
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            if (!categories.empty())
 | 
			
		||||
                objects_model->UpdateSettingsDigest(item, categories);
 | 
			
		||||
        }
 | 
			
		||||
	}
 | 
			
		||||
        if (!categories.empty())
 | 
			
		||||
            objects_model->UpdateSettingsDigest(item, categories);
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        objects_ctrl->select_item(objects_model->Delete(item));
 | 
			
		||||
        return false;
 | 
			
		||||
    } 
 | 
			
		||||
            
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectSettings::UpdateAndShow(const bool show)
 | 
			
		||||
{
 | 
			
		||||
    if (show)
 | 
			
		||||
        update_settings_list();
 | 
			
		||||
 | 
			
		||||
    OG_Settings::UpdateAndShow(show);
 | 
			
		||||
    OG_Settings::UpdateAndShow(show ? update_settings_list() : false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectSettings::msw_rescale()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -44,7 +44,7 @@ public:
 | 
			
		|||
    ObjectSettings(wxWindow* parent);
 | 
			
		||||
    ~ObjectSettings() {}
 | 
			
		||||
 | 
			
		||||
    void        update_settings_list();
 | 
			
		||||
    bool        update_settings_list();
 | 
			
		||||
    void        UpdateAndShow(const bool show) override;
 | 
			
		||||
    void        msw_rescale();
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -379,7 +379,7 @@ bool GLGizmoSlaSupports::is_point_clipped(const Vec3d& point) const
 | 
			
		|||
bool GLGizmoSlaSupports::is_mesh_update_necessary() const
 | 
			
		||||
{
 | 
			
		||||
    return ((m_state == On) && (m_model_object != nullptr) && !m_model_object->instances.empty())
 | 
			
		||||
        && ((m_model_object->id() != m_current_mesh_model_id) || m_its == nullptr);
 | 
			
		||||
        && ((m_model_object->id() != m_current_mesh_object_id) || m_its == nullptr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLGizmoSlaSupports::update_mesh()
 | 
			
		||||
| 
						 | 
				
			
			@ -389,7 +389,7 @@ void GLGizmoSlaSupports::update_mesh()
 | 
			
		|||
    // This mesh does not account for the possible Z up SLA offset.
 | 
			
		||||
    m_mesh = &m_model_object->volumes.front()->mesh();
 | 
			
		||||
    m_its = &m_mesh->its;
 | 
			
		||||
    m_current_mesh_model_id = m_model_object->id();
 | 
			
		||||
    m_current_mesh_object_id = m_model_object->id();
 | 
			
		||||
    m_editing_mode = false;
 | 
			
		||||
 | 
			
		||||
	m_AABB.deinit();
 | 
			
		||||
| 
						 | 
				
			
			@ -923,10 +923,12 @@ RENDER_AGAIN:
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        if (value_changed) { // Update side panel
 | 
			
		||||
            wxTheApp->CallAfter([]() {
 | 
			
		||||
                wxGetApp().obj_settings()->UpdateAndShow(true);
 | 
			
		||||
                wxGetApp().obj_list()->update_settings_items();
 | 
			
		||||
            });
 | 
			
		||||
/*            wxTheApp->CallAfter([]() {
 | 
			
		||||
 *                wxGetApp().obj_settings()->UpdateAndShow(true);
 | 
			
		||||
 *                wxGetApp().obj_list()->update_settings_items();
 | 
			
		||||
 *            });
 | 
			
		||||
 * #lm_FIXME_delete_after_testing */
 | 
			
		||||
            wxGetApp().obj_list()->update_and_show_object_settings_item();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool generate = m_imgui->button(m_desc.at("auto_generate"));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,7 +26,7 @@ class GLGizmoSlaSupports : public GLGizmoBase
 | 
			
		|||
{
 | 
			
		||||
private:
 | 
			
		||||
    ModelObject* m_model_object = nullptr;
 | 
			
		||||
    ModelID m_current_mesh_model_id = 0;
 | 
			
		||||
    ObjectID m_current_mesh_object_id = 0;
 | 
			
		||||
    int m_active_instance = -1;
 | 
			
		||||
    float m_active_instance_bb_radius; // to cache the bb
 | 
			
		||||
    mutable float m_z_shift = 0.f;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -542,8 +542,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas)
 | 
			
		|||
                if (m_current == Flatten)
 | 
			
		||||
                {
 | 
			
		||||
                    // Rotate the object so the normal points downward:
 | 
			
		||||
                    selection.flattening_rotate(get_flattening_normal());
 | 
			
		||||
                    canvas.do_flatten();
 | 
			
		||||
                    canvas.do_flatten(get_flattening_normal(), "Place on Face");
 | 
			
		||||
                    wxGetApp().obj_manipul()->set_dirty();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -616,17 +615,17 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas)
 | 
			
		|||
            case Move:
 | 
			
		||||
            {
 | 
			
		||||
                canvas.disable_regenerate_volumes();
 | 
			
		||||
                canvas.do_move();
 | 
			
		||||
                canvas.do_move("Gizmo-Move Object");
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            case Scale:
 | 
			
		||||
            {
 | 
			
		||||
                canvas.do_scale();
 | 
			
		||||
                canvas.do_scale("Gizmo-Scale Object");
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            case Rotate:
 | 
			
		||||
            {
 | 
			
		||||
                canvas.do_rotate();
 | 
			
		||||
                canvas.do_rotate("Gizmo-Rotate Object");
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            default:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -233,9 +233,9 @@ ImVec2 ImGuiWrapper::calc_text_size(const wxString &text)
 | 
			
		|||
    return size;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ImGuiWrapper::set_next_window_pos(float x, float y, int flag)
 | 
			
		||||
void ImGuiWrapper::set_next_window_pos(float x, float y, int flag, float pivot_x, float pivot_y)
 | 
			
		||||
{
 | 
			
		||||
    ImGui::SetNextWindowPos(ImVec2(x, y), (ImGuiCond)flag);
 | 
			
		||||
    ImGui::SetNextWindowPos(ImVec2(x, y), (ImGuiCond)flag, ImVec2(pivot_x, pivot_y));
 | 
			
		||||
    ImGui::SetNextWindowSize(ImVec2(0.0, 0.0));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -342,6 +342,32 @@ bool ImGuiWrapper::combo(const wxString& label, const std::vector<std::string>&
 | 
			
		|||
    return res;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ImGuiWrapper::undo_redo_list(const ImVec2& size, const bool is_undo, bool (*items_getter)(const bool , int , const char**), int& hovered, int& selected)
 | 
			
		||||
{
 | 
			
		||||
    bool is_hovered = false;
 | 
			
		||||
    ImGui::ListBoxHeader("", size);
 | 
			
		||||
 | 
			
		||||
    int i=0;
 | 
			
		||||
    const char* item_text;
 | 
			
		||||
    while (items_getter(is_undo, i, &item_text))
 | 
			
		||||
    {
 | 
			
		||||
        ImGui::Selectable(item_text, i < hovered);
 | 
			
		||||
 | 
			
		||||
        if (ImGui::IsItemHovered()) {
 | 
			
		||||
            ImGui::SetTooltip(item_text);
 | 
			
		||||
            hovered = i;
 | 
			
		||||
            is_hovered = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (ImGui::IsItemClicked())
 | 
			
		||||
            selected = i;
 | 
			
		||||
        i++;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ImGui::ListBoxFooter();
 | 
			
		||||
    return is_hovered;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ImGuiWrapper::disabled_begin(bool disabled)
 | 
			
		||||
{
 | 
			
		||||
    wxCHECK_RET(!m_disabled, "ImGUI: Unbalanced disabled_begin() call");
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -51,7 +51,7 @@ public:
 | 
			
		|||
    ImVec2 scaled(float x, float y) const { return ImVec2(x * m_font_size, y * m_font_size); }
 | 
			
		||||
    ImVec2 calc_text_size(const wxString &text);
 | 
			
		||||
 | 
			
		||||
    void set_next_window_pos(float x, float y, int flag);
 | 
			
		||||
    void set_next_window_pos(float x, float y, int flag, float pivot_x = 0.0f, float pivot_y = 0.0f);
 | 
			
		||||
    void set_next_window_bg_alpha(float alpha);
 | 
			
		||||
 | 
			
		||||
    bool begin(const std::string &name, int flags = 0);
 | 
			
		||||
| 
						 | 
				
			
			@ -67,6 +67,7 @@ public:
 | 
			
		|||
    void text(const std::string &label);
 | 
			
		||||
    void text(const wxString &label);
 | 
			
		||||
    bool combo(const wxString& label, const std::vector<std::string>& options, int& selection);   // Use -1 to not mark any option as selected
 | 
			
		||||
    bool undo_redo_list(const ImVec2& size, const bool is_undo, bool (*items_getter)(const bool, int, const char**), int& hovered, int& selected);
 | 
			
		||||
 | 
			
		||||
    void disabled_begin(bool disabled);
 | 
			
		||||
    void disabled_end();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -537,6 +537,14 @@ void MainFrame::init_menubar()
 | 
			
		|||
            _(L("Deletes all objects")), [this](wxCommandEvent&) { m_plater->reset_with_confirm(); },
 | 
			
		||||
            menu_icon("delete_all_menu"), nullptr, [this](){return can_delete_all(); }, this);
 | 
			
		||||
 | 
			
		||||
        editMenu->AppendSeparator();
 | 
			
		||||
        append_menu_item(editMenu, wxID_ANY, _(L("&Undo")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "Z",
 | 
			
		||||
            _(L("Undo")), [this](wxCommandEvent&) { m_plater->undo(); },
 | 
			
		||||
            "undo", nullptr, [this](){return m_plater->can_undo(); }, this);
 | 
			
		||||
        append_menu_item(editMenu, wxID_ANY, _(L("&Redo")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "Y",
 | 
			
		||||
            _(L("Redo")), [this](wxCommandEvent&) { m_plater->redo(); },
 | 
			
		||||
            "redo", nullptr, [this](){return m_plater->can_redo(); }, this);
 | 
			
		||||
 | 
			
		||||
        editMenu->AppendSeparator();
 | 
			
		||||
        append_menu_item(editMenu, wxID_ANY, _(L("&Copy")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "C",
 | 
			
		||||
            _(L("Copy selection to clipboard")), [this](wxCommandEvent&) { m_plater->copy_selection_to_clipboard(); },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -70,6 +70,7 @@
 | 
			
		|||
#include "../Utils/ASCIIFolding.hpp"
 | 
			
		||||
#include "../Utils/PrintHost.hpp"
 | 
			
		||||
#include "../Utils/FixModelByWin10.hpp"
 | 
			
		||||
#include "../Utils/UndoRedo.hpp"
 | 
			
		||||
 | 
			
		||||
#include <wx/glcanvas.h>    // Needs to be last because reasons :-/
 | 
			
		||||
#include "WipeTowerDialog.hpp"
 | 
			
		||||
| 
						 | 
				
			
			@ -1254,6 +1255,8 @@ const std::regex PlaterDropTarget::pattern_drop(".*[.](stl|obj|amf|3mf|prusa)",
 | 
			
		|||
 | 
			
		||||
bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames)
 | 
			
		||||
{
 | 
			
		||||
	plater->take_snapshot(_(L("Load Files")));
 | 
			
		||||
 | 
			
		||||
    std::vector<fs::path> paths;
 | 
			
		||||
 | 
			
		||||
    for (const auto &filename : filenames) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1319,7 +1322,12 @@ struct Plater::priv
 | 
			
		|||
    Slic3r::Model               model;
 | 
			
		||||
    PrinterTechnology           printer_technology = ptFFF;
 | 
			
		||||
    Slic3r::GCodePreviewData    gcode_preview_data;
 | 
			
		||||
 | 
			
		||||
    Slic3r::UndoRedo::Stack 	undo_redo_stack;
 | 
			
		||||
    int                         m_prevent_snapshots = 0;     /* Used for avoid of excess "snapshoting". 
 | 
			
		||||
                                                              * Like for "delete selected" or "set numbers of copies"
 | 
			
		||||
                                                              * we should call tack_snapshot just ones 
 | 
			
		||||
                                                              * instead of calls for each action separately
 | 
			
		||||
                                                              * */
 | 
			
		||||
    // GUI elements
 | 
			
		||||
    wxSizer* panel_sizer{ nullptr };
 | 
			
		||||
    wxPanel* current_panel{ nullptr };
 | 
			
		||||
| 
						 | 
				
			
			@ -1619,6 +1627,23 @@ struct Plater::priv
 | 
			
		|||
    void split_object();
 | 
			
		||||
    void split_volume();
 | 
			
		||||
    void scale_selection_to_fit_print_volume();
 | 
			
		||||
 | 
			
		||||
	void take_snapshot(const std::string& snapshot_name)
 | 
			
		||||
	{
 | 
			
		||||
        if (this->m_prevent_snapshots > 0) 
 | 
			
		||||
            return;
 | 
			
		||||
        assert(this->m_prevent_snapshots >= 0);
 | 
			
		||||
	    this->undo_redo_stack.take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection());
 | 
			
		||||
	}
 | 
			
		||||
	void take_snapshot(const wxString& snapshot_name) { this->take_snapshot(std::string(snapshot_name.ToUTF8().data())); }
 | 
			
		||||
    int  get_active_snapshot_index();
 | 
			
		||||
    void undo();
 | 
			
		||||
    void redo();
 | 
			
		||||
    void undo_to(size_t time_to_load);
 | 
			
		||||
    void redo_to(size_t time_to_load);
 | 
			
		||||
    void suppress_snapshots()   { this->m_prevent_snapshots++; }
 | 
			
		||||
    void allow_snapshots()      { this->m_prevent_snapshots--; }
 | 
			
		||||
 | 
			
		||||
    bool background_processing_enabled() const { return this->get_config("background_processing") == "1"; }
 | 
			
		||||
    void update_print_volume_state();
 | 
			
		||||
    void schedule_background_process();
 | 
			
		||||
| 
						 | 
				
			
			@ -1709,6 +1734,7 @@ private:
 | 
			
		|||
 | 
			
		||||
    void update_fff_scene();
 | 
			
		||||
    void update_sla_scene();
 | 
			
		||||
    void update_after_undo_redo();
 | 
			
		||||
 | 
			
		||||
    // path to project file stored with no extension
 | 
			
		||||
    wxString m_project_filename;
 | 
			
		||||
| 
						 | 
				
			
			@ -1811,6 +1837,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
 | 
			
		|||
    view3D_canvas->Bind(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, &priv::on_3dcanvas_mouse_dragging_finished, this);
 | 
			
		||||
    view3D_canvas->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); });
 | 
			
		||||
    view3D_canvas->Bind(EVT_GLCANVAS_RESETGIZMOS, [this](SimpleEvent&) { reset_all_gizmos(); });
 | 
			
		||||
    view3D_canvas->Bind(EVT_GLCANVAS_UNDO, [this](SimpleEvent&) { this->undo(); });
 | 
			
		||||
    view3D_canvas->Bind(EVT_GLCANVAS_REDO, [this](SimpleEvent&) { this->redo(); });
 | 
			
		||||
 | 
			
		||||
    // 3DScene/Toolbar:
 | 
			
		||||
    view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this);
 | 
			
		||||
| 
						 | 
				
			
			@ -1849,6 +1877,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
 | 
			
		|||
 | 
			
		||||
    // updates camera type from .ini file
 | 
			
		||||
    camera.set_type(get_config("use_perspective_camera"));
 | 
			
		||||
 | 
			
		||||
    // Initialize the Undo / Redo stack with a first snapshot.
 | 
			
		||||
	this->take_snapshot(_(L("New Project")));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Plater::priv::~priv()
 | 
			
		||||
| 
						 | 
				
			
			@ -2219,7 +2250,7 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs &mode
 | 
			
		|||
            // the size of the object is too big -> this could lead to overflow when moving to clipper coordinates,
 | 
			
		||||
            // so scale down the mesh
 | 
			
		||||
			double inv = 1. / max_ratio;
 | 
			
		||||
            object->scale_mesh(Vec3d(inv, inv, inv));
 | 
			
		||||
            object->scale_mesh_after_creation(Vec3d(inv, inv, inv));
 | 
			
		||||
            object->origin_translation = Vec3d::Zero();
 | 
			
		||||
            object->center_around_origin();
 | 
			
		||||
            scaled_down = true;
 | 
			
		||||
| 
						 | 
				
			
			@ -2398,12 +2429,15 @@ void Plater::priv::object_list_changed()
 | 
			
		|||
 | 
			
		||||
void Plater::priv::select_all()
 | 
			
		||||
{
 | 
			
		||||
//    this->take_snapshot(_(L("Select All")));
 | 
			
		||||
 | 
			
		||||
    view3D->select_all();
 | 
			
		||||
    this->sidebar->obj_list()->update_selections();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Plater::priv::deselect_all()
 | 
			
		||||
{
 | 
			
		||||
//    this->take_snapshot(_(L("Deselect All")));
 | 
			
		||||
    view3D->deselect_all();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2425,6 +2459,7 @@ void Plater::priv::remove(size_t obj_idx)
 | 
			
		|||
 | 
			
		||||
void Plater::priv::delete_object_from_model(size_t obj_idx)
 | 
			
		||||
{
 | 
			
		||||
    this->take_snapshot(_(L("Delete Object")));
 | 
			
		||||
    model.delete_object(obj_idx);
 | 
			
		||||
    update();
 | 
			
		||||
    object_list_changed();
 | 
			
		||||
| 
						 | 
				
			
			@ -2432,6 +2467,8 @@ void Plater::priv::delete_object_from_model(size_t obj_idx)
 | 
			
		|||
 | 
			
		||||
void Plater::priv::reset()
 | 
			
		||||
{
 | 
			
		||||
	this->take_snapshot(_(L("Reset Project")));
 | 
			
		||||
 | 
			
		||||
    set_project_filename(wxEmptyString);
 | 
			
		||||
 | 
			
		||||
    // Prevent toolpaths preview from rendering while we modify the Print object
 | 
			
		||||
| 
						 | 
				
			
			@ -2457,17 +2494,20 @@ void Plater::priv::reset()
 | 
			
		|||
 | 
			
		||||
void Plater::priv::mirror(Axis axis)
 | 
			
		||||
{
 | 
			
		||||
    this->take_snapshot(_(L("Mirror")));
 | 
			
		||||
    view3D->mirror_selection(axis);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Plater::priv::arrange()
 | 
			
		||||
{
 | 
			
		||||
    this->take_snapshot(_(L("Arrange")));
 | 
			
		||||
    m_ui_jobs.start(Jobs::Arrange);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// This method will find an optimal orientation for the currently selected item
 | 
			
		||||
// Very similar in nature to the arrange method above...
 | 
			
		||||
void Plater::priv::sla_optimize_rotation() {
 | 
			
		||||
    this->take_snapshot(_(L("Optimize Rotation")));
 | 
			
		||||
    m_ui_jobs.start(Jobs::Rotoptimize);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2623,6 +2663,8 @@ void Plater::priv::split_object()
 | 
			
		|||
        Slic3r::GUI::warning_catcher(q, _(L("The selected object couldn't be split because it contains only one part.")));
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        this->take_snapshot(_(L("Split to Objects")));
 | 
			
		||||
 | 
			
		||||
        unsigned int counter = 1;
 | 
			
		||||
        for (ModelObject* m : new_objects)
 | 
			
		||||
            m->name = current_model_object->name + "_" + std::to_string(counter++);
 | 
			
		||||
| 
						 | 
				
			
			@ -2861,6 +2903,8 @@ void Plater::priv::update_sla_scene()
 | 
			
		|||
 | 
			
		||||
void Plater::priv::reload_from_disk()
 | 
			
		||||
{
 | 
			
		||||
    this->take_snapshot(_(L("Reload from Disk")));
 | 
			
		||||
 | 
			
		||||
    const auto &selection = get_selection();
 | 
			
		||||
    const auto obj_orig_idx = selection.get_object_idx();
 | 
			
		||||
    if (selection.is_wipe_tower() || obj_orig_idx == -1) { return; }
 | 
			
		||||
| 
						 | 
				
			
			@ -2894,6 +2938,9 @@ void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* =
 | 
			
		|||
{
 | 
			
		||||
    if (obj_idx < 0)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    this->take_snapshot(_(L("Fix Throught NetFabb")));
 | 
			
		||||
 | 
			
		||||
    fix_model_by_win10_sdk_gui(*model.objects[obj_idx], vol_idx);
 | 
			
		||||
    this->update();
 | 
			
		||||
    this->object_list_changed();
 | 
			
		||||
| 
						 | 
				
			
			@ -3139,6 +3186,8 @@ void Plater::priv::on_action_layersediting(SimpleEvent&)
 | 
			
		|||
 | 
			
		||||
void Plater::priv::on_object_select(SimpleEvent& evt)
 | 
			
		||||
{
 | 
			
		||||
//    this->take_snapshot(_(L("Object Selection")));
 | 
			
		||||
 | 
			
		||||
    wxGetApp().obj_list()->update_selections();
 | 
			
		||||
    selection_changed();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -3414,8 +3463,7 @@ void Plater::priv::init_view_toolbar()
 | 
			
		|||
    item.icon_filename = "editor.svg";
 | 
			
		||||
    item.tooltip = _utf8(L("3D editor view")) + " [" + GUI::shortkey_ctrl_prefix() + "5]";
 | 
			
		||||
    item.sprite_id = 0;
 | 
			
		||||
    item.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_3D)); };
 | 
			
		||||
    item.is_toggable = false;
 | 
			
		||||
    item.left.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_3D)); };
 | 
			
		||||
    if (!view_toolbar.add_item(item))
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3423,8 +3471,7 @@ void Plater::priv::init_view_toolbar()
 | 
			
		|||
    item.icon_filename = "preview.svg";
 | 
			
		||||
    item.tooltip = _utf8(L("Preview")) + " [" + GUI::shortkey_ctrl_prefix() + "6]";
 | 
			
		||||
    item.sprite_id = 1;
 | 
			
		||||
    item.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_PREVIEW)); };
 | 
			
		||||
    item.is_toggable = false;
 | 
			
		||||
    item.left.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_PREVIEW)); };
 | 
			
		||||
    if (!view_toolbar.add_item(item))
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3554,6 +3601,51 @@ void Plater::priv::show_action_buttons(const bool is_ready_to_slice) const
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int Plater::priv::get_active_snapshot_index()
 | 
			
		||||
{
 | 
			
		||||
    const size_t& active_snapshot_time = this->undo_redo_stack.active_snapshot_time();
 | 
			
		||||
    const std::vector<UndoRedo::Snapshot>& ss_stack = this->undo_redo_stack.snapshots();
 | 
			
		||||
    const auto it = std::lower_bound(ss_stack.begin(), ss_stack.end(), UndoRedo::Snapshot(active_snapshot_time));
 | 
			
		||||
    return it - ss_stack.begin();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Plater::priv::undo()
 | 
			
		||||
{
 | 
			
		||||
	if (this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection()))
 | 
			
		||||
		this->update_after_undo_redo();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Plater::priv::redo()
 | 
			
		||||
{ 
 | 
			
		||||
	if (this->undo_redo_stack.redo(model))
 | 
			
		||||
		this->update_after_undo_redo();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Plater::priv::undo_to(size_t time_to_load)
 | 
			
		||||
{
 | 
			
		||||
	if (this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection(), time_to_load))
 | 
			
		||||
		this->update_after_undo_redo();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Plater::priv::redo_to(size_t time_to_load)
 | 
			
		||||
{ 
 | 
			
		||||
	if (this->undo_redo_stack.redo(model, time_to_load))
 | 
			
		||||
		this->update_after_undo_redo();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Plater::priv::update_after_undo_redo()
 | 
			
		||||
{
 | 
			
		||||
	this->view3D->get_canvas3d()->get_selection().clear();
 | 
			
		||||
	this->update(false); // update volumes from the deserializd model
 | 
			
		||||
	//YS_FIXME update obj_list from the deserialized model (maybe store ObjectIDs into the tree?) (no selections at this point of time)
 | 
			
		||||
    this->view3D->get_canvas3d()->get_selection().set_deserialized(GUI::Selection::EMode(this->undo_redo_stack.selection_deserialized().mode), this->undo_redo_stack.selection_deserialized().volumes_and_instances);
 | 
			
		||||
 | 
			
		||||
    wxGetApp().obj_list()->update_after_undo_redo();
 | 
			
		||||
 | 
			
		||||
	//FIXME what about the state of the manipulators?
 | 
			
		||||
	//FIXME what about the focus? Cursor in the side panel?
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Sidebar::set_btn_label(const ActionButtonType btn_type, const wxString& label) const
 | 
			
		||||
{
 | 
			
		||||
    switch (btn_type)
 | 
			
		||||
| 
						 | 
				
			
			@ -3591,6 +3683,8 @@ void Plater::new_project()
 | 
			
		|||
 | 
			
		||||
void Plater::load_project()
 | 
			
		||||
{
 | 
			
		||||
	this->take_snapshot(_(L("Load Project")));
 | 
			
		||||
 | 
			
		||||
    wxString input_file;
 | 
			
		||||
    wxGetApp().load_project(this, input_file);
 | 
			
		||||
    load_project(input_file);
 | 
			
		||||
| 
						 | 
				
			
			@ -3616,6 +3710,8 @@ void Plater::add_model()
 | 
			
		|||
    if (input_files.empty())
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    this->take_snapshot(_(L("Add object(s)")));
 | 
			
		||||
 | 
			
		||||
    std::vector<fs::path> input_paths;
 | 
			
		||||
    for (const auto &file : input_files) {
 | 
			
		||||
        input_paths.push_back(into_path(file));
 | 
			
		||||
| 
						 | 
				
			
			@ -3673,13 +3769,18 @@ void Plater::delete_object_from_model(size_t obj_idx) { p->delete_object_from_mo
 | 
			
		|||
 | 
			
		||||
void Plater::remove_selected()
 | 
			
		||||
{
 | 
			
		||||
	this->take_snapshot(_(L("Delete Selected Objects")));
 | 
			
		||||
    this->suppress_snapshots();
 | 
			
		||||
    this->p->view3D->delete_selected();
 | 
			
		||||
    this->allow_snapshots();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Plater::increase_instances(size_t num)
 | 
			
		||||
{
 | 
			
		||||
    if (! can_increase_instances()) { return; }
 | 
			
		||||
 | 
			
		||||
	this->take_snapshot(_(L("Increase Instances")));
 | 
			
		||||
 | 
			
		||||
    int obj_idx = p->get_selected_object_idx();
 | 
			
		||||
 | 
			
		||||
    ModelObject* model_object = p->model.objects[obj_idx];
 | 
			
		||||
| 
						 | 
				
			
			@ -3714,6 +3815,8 @@ void Plater::decrease_instances(size_t num)
 | 
			
		|||
{
 | 
			
		||||
    if (! can_decrease_instances()) { return; }
 | 
			
		||||
 | 
			
		||||
	this->take_snapshot(_(L("Decrease Instances")));
 | 
			
		||||
 | 
			
		||||
    int obj_idx = p->get_selected_object_idx();
 | 
			
		||||
 | 
			
		||||
    ModelObject* model_object = p->model.objects[obj_idx];
 | 
			
		||||
| 
						 | 
				
			
			@ -3748,11 +3851,16 @@ void Plater::set_number_of_copies(/*size_t num*/)
 | 
			
		|||
    if (num < 0)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    this->take_snapshot(wxString::Format(_(L("Set numbers of copies to %d")), num));
 | 
			
		||||
    this->suppress_snapshots();
 | 
			
		||||
 | 
			
		||||
    int diff = (int)num - (int)model_object->instances.size();
 | 
			
		||||
    if (diff > 0)
 | 
			
		||||
        increase_instances(diff);
 | 
			
		||||
    else if (diff < 0)
 | 
			
		||||
        decrease_instances(-diff);
 | 
			
		||||
 | 
			
		||||
    this->allow_snapshots();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Plater::is_selection_empty() const
 | 
			
		||||
| 
						 | 
				
			
			@ -3776,6 +3884,8 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_uppe
 | 
			
		|||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this->take_snapshot(_(L("Cut")));
 | 
			
		||||
 | 
			
		||||
    wxBusyCursor wait;
 | 
			
		||||
    const auto new_objects = object->cut(instance_idx, z, keep_upper, keep_lower, rotate_lower);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -4065,6 +4175,45 @@ void Plater::send_gcode()
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Plater::take_snapshot(const std::string &snapshot_name) { p->take_snapshot(snapshot_name); }
 | 
			
		||||
void Plater::take_snapshot(const wxString &snapshot_name) { p->take_snapshot(snapshot_name); }
 | 
			
		||||
void Plater::suppress_snapshots() { p->suppress_snapshots(); }
 | 
			
		||||
void Plater::allow_snapshots() { p->allow_snapshots(); }
 | 
			
		||||
void Plater::undo() { p->undo(); }
 | 
			
		||||
void Plater::redo() { p->redo(); }
 | 
			
		||||
void Plater::undo_to(int selection)
 | 
			
		||||
{
 | 
			
		||||
    if (selection == 0) {
 | 
			
		||||
        p->undo();
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    const int idx = p->get_active_snapshot_index() - selection - 1;
 | 
			
		||||
    p->undo_to(p->undo_redo_stack.snapshots()[idx].timestamp);
 | 
			
		||||
}
 | 
			
		||||
void Plater::redo_to(int selection)
 | 
			
		||||
{
 | 
			
		||||
    if (selection == 0) {
 | 
			
		||||
        p->redo();
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    const int idx = p->get_active_snapshot_index() + selection + 1;
 | 
			
		||||
    p->redo_to(p->undo_redo_stack.snapshots()[idx].timestamp);
 | 
			
		||||
}
 | 
			
		||||
bool Plater::undo_redo_string_getter(const bool is_undo, int idx, const char** out_text)
 | 
			
		||||
{
 | 
			
		||||
    const std::vector<UndoRedo::Snapshot>& ss_stack = p->undo_redo_stack.snapshots();
 | 
			
		||||
    const int idx_in_ss_stack = p->get_active_snapshot_index() + (is_undo ? -(++idx) : idx);
 | 
			
		||||
 | 
			
		||||
    if (0 < idx_in_ss_stack && idx_in_ss_stack < ss_stack.size() - 1) {
 | 
			
		||||
        *out_text = ss_stack[idx_in_ss_stack].name.c_str();
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Plater::on_extruders_change(int num_extruders)
 | 
			
		||||
{
 | 
			
		||||
    auto& choices = sidebar().combos_filament();
 | 
			
		||||
| 
						 | 
				
			
			@ -4290,8 +4439,11 @@ void Plater::copy_selection_to_clipboard()
 | 
			
		|||
 | 
			
		||||
void Plater::paste_from_clipboard()
 | 
			
		||||
{
 | 
			
		||||
    if (can_paste_from_clipboard())
 | 
			
		||||
        p->view3D->get_canvas3d()->get_selection().paste_from_clipboard();
 | 
			
		||||
    if (!can_paste_from_clipboard())
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    this->take_snapshot(_(L("Paste From Clipboard")));
 | 
			
		||||
    p->view3D->get_canvas3d()->get_selection().paste_from_clipboard();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Plater::msw_rescale()
 | 
			
		||||
| 
						 | 
				
			
			@ -4356,6 +4508,16 @@ bool Plater::can_copy_to_clipboard() const
 | 
			
		|||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Plater::can_undo() const
 | 
			
		||||
{
 | 
			
		||||
    return p->undo_redo_stack.has_undo_snapshot();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Plater::can_redo() const
 | 
			
		||||
{
 | 
			
		||||
    return p->undo_redo_stack.has_redo_snapshot();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SuppressBackgroundProcessingUpdate::SuppressBackgroundProcessingUpdate() :
 | 
			
		||||
    m_was_running(wxGetApp().plater()->is_background_process_running())
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -184,6 +184,16 @@ public:
 | 
			
		|||
    void fix_through_netfabb(const int obj_idx, const int vol_idx = -1);
 | 
			
		||||
    void send_gcode();
 | 
			
		||||
 | 
			
		||||
    void take_snapshot(const std::string &snapshot_name);
 | 
			
		||||
    void take_snapshot(const wxString &snapshot_name);
 | 
			
		||||
    void suppress_snapshots();
 | 
			
		||||
    void allow_snapshots();
 | 
			
		||||
    void undo();
 | 
			
		||||
    void redo();
 | 
			
		||||
    void undo_to(int selection);
 | 
			
		||||
    void redo_to(int selection);
 | 
			
		||||
    bool undo_redo_string_getter(const bool is_undo, int idx, const char** out_text);
 | 
			
		||||
 | 
			
		||||
    void on_extruders_change(int extruders_count);
 | 
			
		||||
    void on_config_change(const DynamicPrintConfig &config);
 | 
			
		||||
    // On activating the parent window.
 | 
			
		||||
| 
						 | 
				
			
			@ -218,6 +228,8 @@ public:
 | 
			
		|||
    bool can_layers_editing() const;
 | 
			
		||||
    bool can_paste_from_clipboard() const;
 | 
			
		||||
    bool can_copy_to_clipboard() const;
 | 
			
		||||
    bool can_undo() const;
 | 
			
		||||
    bool can_redo() const;
 | 
			
		||||
 | 
			
		||||
    void msw_rescale();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1366,7 +1366,7 @@ void PresetBundle::export_configbundle(const std::string &path, bool export_syst
 | 
			
		|||
                continue;
 | 
			
		||||
            c << std::endl << "[" << presets->section_name() << ":" << preset.name << "]" << std::endl;
 | 
			
		||||
            for (const std::string &opt_key : preset.config.keys())
 | 
			
		||||
                c << opt_key << " = " << preset.config.serialize(opt_key) << std::endl;
 | 
			
		||||
                c << opt_key << " = " << preset.config.opt_serialize(opt_key) << std::endl;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -312,6 +312,22 @@ void Selection::add_all()
 | 
			
		|||
    this->set_bounding_boxes_dirty();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Selection::set_deserialized(EMode mode, const std::vector<std::pair<size_t, size_t>> &volumes_and_instances)
 | 
			
		||||
{
 | 
			
		||||
    if (! m_valid)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
	m_mode = mode;
 | 
			
		||||
    for (unsigned int i : m_list)
 | 
			
		||||
        (*m_volumes)[i]->selected = false;
 | 
			
		||||
    m_list.clear();
 | 
			
		||||
    for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++ i)
 | 
			
		||||
		if (std::binary_search(volumes_and_instances.begin(), volumes_and_instances.end(), (*m_volumes)[i]->geometry_id))
 | 
			
		||||
			this->do_add_volume(i);
 | 
			
		||||
    update_type();
 | 
			
		||||
    this->set_bounding_boxes_dirty();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Selection::clear()
 | 
			
		||||
{
 | 
			
		||||
    if (!m_valid)
 | 
			
		||||
| 
						 | 
				
			
			@ -790,6 +806,8 @@ void Selection::scale_to_fit_print_volume(const DynamicPrintConfig& config)
 | 
			
		|||
            double s = std::min(sx, std::min(sy, sz));
 | 
			
		||||
            if (s != 1.0)
 | 
			
		||||
            {
 | 
			
		||||
                wxGetApp().plater()->take_snapshot(_(L("Scale To Fit")));
 | 
			
		||||
 | 
			
		||||
                TransformationType type;
 | 
			
		||||
                type.set_world();
 | 
			
		||||
                type.set_relative();
 | 
			
		||||
| 
						 | 
				
			
			@ -798,12 +816,12 @@ void Selection::scale_to_fit_print_volume(const DynamicPrintConfig& config)
 | 
			
		|||
                // apply scale
 | 
			
		||||
                start_dragging();
 | 
			
		||||
                scale(s * Vec3d::Ones(), type);
 | 
			
		||||
                wxGetApp().plater()->canvas3D()->do_scale();
 | 
			
		||||
                wxGetApp().plater()->canvas3D()->do_scale(""); // avoid storing another snapshot
 | 
			
		||||
 | 
			
		||||
                // center selection on print bed
 | 
			
		||||
                start_dragging();
 | 
			
		||||
                translate(print_volume.center() - get_bounding_box().center());
 | 
			
		||||
                wxGetApp().plater()->canvas3D()->do_move();
 | 
			
		||||
                wxGetApp().plater()->canvas3D()->do_move(""); // avoid storing another snapshot
 | 
			
		||||
 | 
			
		||||
                wxGetApp().obj_manipul()->set_dirty();
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -1177,7 +1195,7 @@ void Selection::copy_to_clipboard()
 | 
			
		|||
        ModelObject* dst_object = m_clipboard.add_object();
 | 
			
		||||
        dst_object->name                 = src_object->name;
 | 
			
		||||
        dst_object->input_file           = src_object->input_file;
 | 
			
		||||
        dst_object->config               = src_object->config;
 | 
			
		||||
		static_cast<DynamicPrintConfig&>(dst_object->config) = static_cast<const DynamicPrintConfig&>(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_config_ranges  = src_object->layer_config_ranges;     // #ys_FIXME_experiment
 | 
			
		||||
| 
						 | 
				
			
			@ -2016,6 +2034,10 @@ bool Selection::is_from_fully_selected_instance(unsigned int volume_idx) const
 | 
			
		|||
 | 
			
		||||
void Selection::paste_volumes_from_clipboard()
 | 
			
		||||
{
 | 
			
		||||
#ifdef _DEBUG
 | 
			
		||||
    check_model_ids_validity(*m_model);
 | 
			
		||||
#endif /* _DEBUG */
 | 
			
		||||
 | 
			
		||||
    int dst_obj_idx = get_object_idx();
 | 
			
		||||
    if ((dst_obj_idx < 0) || ((int)m_model->objects.size() <= dst_obj_idx))
 | 
			
		||||
        return;
 | 
			
		||||
| 
						 | 
				
			
			@ -2057,6 +2079,9 @@ void Selection::paste_volumes_from_clipboard()
 | 
			
		|||
            }
 | 
			
		||||
 | 
			
		||||
            volumes.push_back(dst_volume);
 | 
			
		||||
#ifdef _DEBUG
 | 
			
		||||
		    check_model_ids_validity(*m_model);
 | 
			
		||||
#endif /* _DEBUG */
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // keeps relative position of multivolume selections
 | 
			
		||||
| 
						 | 
				
			
			@ -2070,10 +2095,18 @@ void Selection::paste_volumes_from_clipboard()
 | 
			
		|||
 | 
			
		||||
        wxGetApp().obj_list()->paste_volumes_into_list(dst_obj_idx, volumes);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
#ifdef _DEBUG
 | 
			
		||||
    check_model_ids_validity(*m_model);
 | 
			
		||||
#endif /* _DEBUG */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Selection::paste_objects_from_clipboard()
 | 
			
		||||
{
 | 
			
		||||
#ifdef _DEBUG
 | 
			
		||||
    check_model_ids_validity(*m_model);
 | 
			
		||||
#endif /* _DEBUG */
 | 
			
		||||
 | 
			
		||||
    std::vector<size_t> object_idxs;
 | 
			
		||||
    const ModelObjectPtrs& src_objects = m_clipboard.get_objects();
 | 
			
		||||
    for (const ModelObject* src_object : src_objects)
 | 
			
		||||
| 
						 | 
				
			
			@ -2087,9 +2120,16 @@ void Selection::paste_objects_from_clipboard()
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        object_idxs.push_back(m_model->objects.size() - 1);
 | 
			
		||||
#ifdef _DEBUG
 | 
			
		||||
	    check_model_ids_validity(*m_model);
 | 
			
		||||
#endif /* _DEBUG */
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    wxGetApp().obj_list()->paste_objects_into_list(object_idxs);
 | 
			
		||||
 | 
			
		||||
#ifdef _DEBUG
 | 
			
		||||
    check_model_ids_validity(*m_model);
 | 
			
		||||
#endif /* _DEBUG */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace GUI
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -171,7 +171,7 @@ private:
 | 
			
		|||
        Vec3d dragging_center;
 | 
			
		||||
        // Map from indices of ModelObject instances in Model::objects
 | 
			
		||||
        // to a set of indices of ModelVolume instances in ModelObject::instances
 | 
			
		||||
        // Here the index means a position inside the respective std::vector, not ModelID.
 | 
			
		||||
        // Here the index means a position inside the respective std::vector, not ObjectID.
 | 
			
		||||
        ObjectIdxsToInstanceIdxsMap content;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -237,6 +237,9 @@ public:
 | 
			
		|||
 | 
			
		||||
    void add_all();
 | 
			
		||||
 | 
			
		||||
    // To be called after Undo or Redo once the volumes are updated.
 | 
			
		||||
    void set_deserialized(EMode mode, const std::vector<std::pair<size_t, size_t>> &volumes_and_instances);
 | 
			
		||||
 | 
			
		||||
    // Update the selection based on the new instance IDs.
 | 
			
		||||
	void instances_changed(const std::vector<size_t> &instance_ids_selected);
 | 
			
		||||
    // Update the selection based on the map from old indices to new indices after m_volumes changed.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										821
									
								
								src/slic3r/Utils/UndoRedo.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										821
									
								
								src/slic3r/Utils/UndoRedo.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,821 @@
 | 
			
		|||
#include "UndoRedo.hpp"
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <iostream>
 | 
			
		||||
#include <fstream>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <typeinfo> 
 | 
			
		||||
#include <cassert>
 | 
			
		||||
#include <cstddef>
 | 
			
		||||
 | 
			
		||||
#include <cereal/types/polymorphic.hpp>
 | 
			
		||||
#include <cereal/types/map.hpp> 
 | 
			
		||||
#include <cereal/types/string.hpp> 
 | 
			
		||||
#include <cereal/types/utility.hpp> 
 | 
			
		||||
#include <cereal/types/vector.hpp> 
 | 
			
		||||
#include <cereal/archives/binary.hpp>
 | 
			
		||||
#define CEREAL_FUTURE_EXPERIMENTAL
 | 
			
		||||
#include <cereal/archives/adapters.hpp>
 | 
			
		||||
 | 
			
		||||
#include <libslic3r/ObjectID.hpp>
 | 
			
		||||
 | 
			
		||||
#include <boost/foreach.hpp>
 | 
			
		||||
 | 
			
		||||
#ifndef NDEBUG
 | 
			
		||||
// #define SLIC3R_UNDOREDO_DEBUG
 | 
			
		||||
#endif /* NDEBUG */
 | 
			
		||||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
namespace UndoRedo {
 | 
			
		||||
 | 
			
		||||
static std::string topmost_snapsnot_name = "@@@ Topmost @@@";
 | 
			
		||||
 | 
			
		||||
bool Snapshot::is_topmost() const
 | 
			
		||||
{
 | 
			
		||||
	return this->name == topmost_snapsnot_name;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Time interval, start is closed, end is open.
 | 
			
		||||
struct Interval
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	Interval(size_t begin, size_t end) : m_begin(begin), m_end(end) {}
 | 
			
		||||
 | 
			
		||||
	size_t  begin() const { return m_begin; }
 | 
			
		||||
	size_t  end()   const { return m_end; }
 | 
			
		||||
 | 
			
		||||
	bool 	is_valid() const { return m_begin >= 0 && m_begin < m_end; }
 | 
			
		||||
	// This interval comes strictly before the rhs interval.
 | 
			
		||||
	bool 	strictly_before(const Interval &rhs) const { return this->is_valid() && rhs.is_valid() && m_end <= rhs.m_begin; }
 | 
			
		||||
	// This interval comes strictly after the rhs interval.
 | 
			
		||||
	bool 	strictly_after(const Interval &rhs) const { return this->is_valid() && rhs.is_valid() && rhs.m_end <= m_begin; }
 | 
			
		||||
 | 
			
		||||
	bool    operator<(const Interval &rhs) const { return (m_begin < rhs.m_begin) || (m_begin == rhs.m_begin && m_end < rhs.m_end); }
 | 
			
		||||
	bool 	operator==(const Interval &rhs) const { return m_begin == rhs.m_begin && m_end == rhs.m_end; }
 | 
			
		||||
 | 
			
		||||
	void    trim_end(size_t new_end) { m_end = std::min(m_end, new_end); }
 | 
			
		||||
	void 	extend_end(size_t new_end) { assert(new_end >= m_end); m_end = new_end; }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	size_t 	m_begin;
 | 
			
		||||
	size_t 	m_end;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// History of a single object tracked by the Undo / Redo stack. The object may be mutable or immutable.
 | 
			
		||||
class ObjectHistoryBase
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	virtual ~ObjectHistoryBase() {}
 | 
			
		||||
 | 
			
		||||
	// Is the object captured by this history mutable or immutable?
 | 
			
		||||
	virtual bool is_mutable() const = 0;
 | 
			
		||||
	virtual bool is_immutable() const = 0;
 | 
			
		||||
	virtual const void* immutable_object_ptr() const { return nullptr; }
 | 
			
		||||
 | 
			
		||||
	// If the history is empty, the ObjectHistory object could be released.
 | 
			
		||||
	virtual bool empty() = 0;
 | 
			
		||||
 | 
			
		||||
	// Release all data after the given timestamp. For the ImmutableObjectHistory, the shared pointer is NOT released.
 | 
			
		||||
	virtual void release_after_timestamp(size_t timestamp) = 0;
 | 
			
		||||
 | 
			
		||||
#ifdef SLIC3R_UNDOREDO_DEBUG
 | 
			
		||||
	// Human readable debug information.
 | 
			
		||||
	virtual std::string	format() = 0;
 | 
			
		||||
#endif /* SLIC3R_UNDOREDO_DEBUG */
 | 
			
		||||
 | 
			
		||||
#ifndef NDEBUG
 | 
			
		||||
	virtual bool valid() = 0;
 | 
			
		||||
#endif /* NDEBUG */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename T> class ObjectHistory : public ObjectHistoryBase
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	~ObjectHistory() override {}
 | 
			
		||||
 | 
			
		||||
	// If the history is empty, the ObjectHistory object could be released.
 | 
			
		||||
	bool empty() override { return m_history.empty(); }
 | 
			
		||||
 | 
			
		||||
	// Release all data after the given timestamp. The shared pointer is NOT released.
 | 
			
		||||
	void release_after_timestamp(size_t timestamp) override {
 | 
			
		||||
		assert(! m_history.empty());
 | 
			
		||||
		assert(this->valid());
 | 
			
		||||
		// it points to an interval which either starts with timestamp, or follows the timestamp.
 | 
			
		||||
		auto it = std::lower_bound(m_history.begin(), m_history.end(), T(timestamp, timestamp));
 | 
			
		||||
		if (it != m_history.begin()) {
 | 
			
		||||
			auto it_prev = it;
 | 
			
		||||
			-- it_prev;
 | 
			
		||||
			assert(it_prev->begin() < timestamp);
 | 
			
		||||
			// Trim the last interval with timestamp.
 | 
			
		||||
			it_prev->trim_end(timestamp);
 | 
			
		||||
		}
 | 
			
		||||
		m_history.erase(it, m_history.end());
 | 
			
		||||
		assert(this->valid());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
	std::vector<T>	m_history;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Big objects (mainly the triangle meshes) are tracked by Slicer using the shared pointers
 | 
			
		||||
// and they are immutable.
 | 
			
		||||
// The Undo / Redo stack therefore may keep a shared pointer to these immutable objects
 | 
			
		||||
// and as long as the ref counter of these objects is higher than 1 (1 reference is held
 | 
			
		||||
// by the Undo / Redo stack), there is no cost associated to holding the object
 | 
			
		||||
// at the Undo / Redo stack. Once the reference counter drops to 1 (only the Undo / Redo
 | 
			
		||||
// stack holds the reference), the shared pointer may get serialized (and possibly compressed)
 | 
			
		||||
// and the shared pointer may be released.
 | 
			
		||||
// The history of a single immutable object may not be continuous, as an immutable object may
 | 
			
		||||
// be removed from the scene while being kept at the Copy / Paste stack.
 | 
			
		||||
template<typename T>
 | 
			
		||||
class ImmutableObjectHistory : public ObjectHistory<Interval>
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	ImmutableObjectHistory(std::shared_ptr<const T>	shared_object) : m_shared_object(shared_object) {}
 | 
			
		||||
	~ImmutableObjectHistory() override {}
 | 
			
		||||
 | 
			
		||||
	bool is_mutable() const override { return false; }
 | 
			
		||||
	bool is_immutable() const override { return true; }
 | 
			
		||||
	const void* immutable_object_ptr() const { return (const void*)m_shared_object.get(); }
 | 
			
		||||
 | 
			
		||||
	void save(size_t active_snapshot_time, size_t current_time) {
 | 
			
		||||
		assert(m_history.empty() || m_history.back().end() <= active_snapshot_time || 
 | 
			
		||||
			// The snapshot of an immutable object may have already been taken from another mutable object.
 | 
			
		||||
			(m_history.back().begin() <= active_snapshot_time && m_history.back().end() == current_time + 1));
 | 
			
		||||
		if (m_history.empty() || m_history.back().end() < active_snapshot_time)
 | 
			
		||||
			m_history.emplace_back(active_snapshot_time, current_time + 1);
 | 
			
		||||
		else
 | 
			
		||||
			m_history.back().extend_end(current_time + 1);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool has_snapshot(size_t timestamp) {
 | 
			
		||||
		if (m_history.empty())
 | 
			
		||||
			return false;
 | 
			
		||||
		auto it = std::lower_bound(m_history.begin(), m_history.end(), Interval(timestamp, timestamp));
 | 
			
		||||
		if (it == m_history.end() || it->begin() > timestamp) {
 | 
			
		||||
			if (it == m_history.begin())
 | 
			
		||||
				return false;
 | 
			
		||||
			-- it;
 | 
			
		||||
		}
 | 
			
		||||
		return timestamp >= it->begin() && timestamp < it->end();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool 						is_serialized() const { return m_shared_object.get() == nullptr; }
 | 
			
		||||
	const std::string&			serialized_data() const { return m_serialized; }
 | 
			
		||||
	std::shared_ptr<const T>& 	shared_ptr(StackImpl &stack);
 | 
			
		||||
 | 
			
		||||
#ifdef SLIC3R_UNDOREDO_DEBUG
 | 
			
		||||
	std::string 				format() override {
 | 
			
		||||
		std::string out = typeid(T).name();
 | 
			
		||||
		out += this->is_serialized() ? 
 | 
			
		||||
			std::string(" len:") + std::to_string(m_serialized.size()) : 
 | 
			
		||||
			std::string(" shared_ptr:") + ptr_to_string(m_shared_object.get());
 | 
			
		||||
		for (const Interval &interval : m_history)
 | 
			
		||||
			out += std::string(", <") + std::to_string(interval.begin()) + "," + std::to_string(interval.end()) + ")";
 | 
			
		||||
		return out;
 | 
			
		||||
	}
 | 
			
		||||
#endif /* SLIC3R_UNDOREDO_DEBUG */
 | 
			
		||||
 | 
			
		||||
#ifndef NDEBUG
 | 
			
		||||
	bool 						valid() override;
 | 
			
		||||
#endif /* NDEBUG */
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	// Either the source object is held by a shared pointer and the m_serialized field is empty,
 | 
			
		||||
	// or the shared pointer is null and the object is being serialized into m_serialized.
 | 
			
		||||
	std::shared_ptr<const T>	m_shared_object;
 | 
			
		||||
	std::string 				m_serialized;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct MutableHistoryInterval
 | 
			
		||||
{
 | 
			
		||||
private:
 | 
			
		||||
	struct Data
 | 
			
		||||
	{
 | 
			
		||||
		// Reference counter of this data chunk. We may have used shared_ptr, but the shared_ptr is thread safe
 | 
			
		||||
		// with the associated cost of CPU cache invalidation on refcount change.
 | 
			
		||||
		size_t		refcnt;
 | 
			
		||||
		size_t		size;
 | 
			
		||||
		char 		data[1];
 | 
			
		||||
 | 
			
		||||
		bool 		matches(const std::string& rhs) { return this->size == rhs.size() && memcmp(this->data, rhs.data(), this->size) == 0; }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Interval    m_interval;
 | 
			
		||||
	Data	   *m_data;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	MutableHistoryInterval(const Interval &interval, const std::string &input_data) : m_interval(interval), m_data(nullptr) {
 | 
			
		||||
		m_data = (Data*)new char[offsetof(Data, data) + input_data.size()];
 | 
			
		||||
		m_data->refcnt = 1;
 | 
			
		||||
		m_data->size = input_data.size();
 | 
			
		||||
		memcpy(m_data->data, input_data.data(), input_data.size());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	MutableHistoryInterval(const Interval &interval, MutableHistoryInterval &other) : m_interval(interval), m_data(other.m_data) {
 | 
			
		||||
		++ m_data->refcnt;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// as a key for std::lower_bound
 | 
			
		||||
	MutableHistoryInterval(const size_t begin, const size_t end) : m_interval(begin, end), m_data(nullptr) {}
 | 
			
		||||
 | 
			
		||||
	MutableHistoryInterval(MutableHistoryInterval&& rhs) : m_interval(rhs.m_interval), m_data(rhs.m_data) { rhs.m_data = nullptr; }
 | 
			
		||||
	MutableHistoryInterval& operator=(MutableHistoryInterval&& rhs) { m_interval = rhs.m_interval; m_data = rhs.m_data; rhs.m_data = nullptr; return *this; }
 | 
			
		||||
 | 
			
		||||
	~MutableHistoryInterval() {
 | 
			
		||||
		if (m_data != nullptr && -- m_data->refcnt == 0)
 | 
			
		||||
			delete[] (char*)m_data;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const Interval& interval() const { return m_interval; }
 | 
			
		||||
	size_t		begin() const { return m_interval.begin(); }
 | 
			
		||||
	size_t		end()   const { return m_interval.end(); }
 | 
			
		||||
	void 		trim_end(size_t timestamp) { m_interval.trim_end(timestamp); }
 | 
			
		||||
	void 		extend_end(size_t timestamp) { m_interval.extend_end(timestamp); }
 | 
			
		||||
 | 
			
		||||
	bool		operator<(const MutableHistoryInterval& rhs) const { return m_interval < rhs.m_interval; }
 | 
			
		||||
	bool 		operator==(const MutableHistoryInterval& rhs) const { return m_interval == rhs.m_interval; }
 | 
			
		||||
 | 
			
		||||
	const char* data() const { return m_data->data; }
 | 
			
		||||
	size_t  	size() const { return m_data->size; }
 | 
			
		||||
	size_t		refcnt() const { return m_data->refcnt; }
 | 
			
		||||
	bool		matches(const std::string& data) { return m_data->matches(data); }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	MutableHistoryInterval(const MutableHistoryInterval &rhs);
 | 
			
		||||
	MutableHistoryInterval& operator=(const MutableHistoryInterval &rhs);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static inline std::string ptr_to_string(const void* ptr)
 | 
			
		||||
{
 | 
			
		||||
	char buf[64];
 | 
			
		||||
	sprintf(buf, "%p", ptr);
 | 
			
		||||
	return buf;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Smaller objects (Model, ModelObject, ModelInstance, ModelVolume, DynamicPrintConfig)
 | 
			
		||||
// are mutable and there is not tracking of the changes, therefore a snapshot needs to be
 | 
			
		||||
// taken every time and compared to the previous data at the Undo / Redo stack.
 | 
			
		||||
// The serialized data is stored if it is different from the last value on the stack, otherwise
 | 
			
		||||
// the serialized data is discarded.
 | 
			
		||||
// The history of a single mutable object may not be continuous, as an mutable object may
 | 
			
		||||
// be removed from the scene while being kept at the Copy / Paste stack, therefore an object snapshot
 | 
			
		||||
// with the same serialized object data may be shared by multiple history intervals.
 | 
			
		||||
template<typename T>
 | 
			
		||||
class MutableObjectHistory : public ObjectHistory<MutableHistoryInterval>
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	~MutableObjectHistory() override {}
 | 
			
		||||
 | 
			
		||||
	bool is_mutable() const override { return true; }
 | 
			
		||||
	bool is_immutable() const override { return false; }
 | 
			
		||||
 | 
			
		||||
	void save(size_t active_snapshot_time, size_t current_time, const std::string &data) {
 | 
			
		||||
		assert(m_history.empty() || m_history.back().end() <= active_snapshot_time);
 | 
			
		||||
		if (m_history.empty() || m_history.back().end() < active_snapshot_time) {
 | 
			
		||||
			if (! m_history.empty() && m_history.back().matches(data))
 | 
			
		||||
				// Share the previous data by reference counting.
 | 
			
		||||
				m_history.emplace_back(Interval(current_time, current_time + 1), m_history.back());
 | 
			
		||||
			else
 | 
			
		||||
				// Allocate new data.
 | 
			
		||||
				m_history.emplace_back(Interval(current_time, current_time + 1), data);
 | 
			
		||||
		} else {
 | 
			
		||||
			assert(! m_history.empty());
 | 
			
		||||
			assert(m_history.back().end() == active_snapshot_time);
 | 
			
		||||
			if (m_history.back().matches(data))
 | 
			
		||||
				// Just extend the last interval using the old data.
 | 
			
		||||
				m_history.back().extend_end(current_time + 1);
 | 
			
		||||
			else
 | 
			
		||||
				// Allocate new data time continuous with the previous data.
 | 
			
		||||
				m_history.emplace_back(Interval(active_snapshot_time, current_time + 1), data);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	std::string load(size_t timestamp) const {
 | 
			
		||||
		assert(! m_history.empty());
 | 
			
		||||
		auto it = std::lower_bound(m_history.begin(), m_history.end(), MutableHistoryInterval(timestamp, timestamp));
 | 
			
		||||
		if (it == m_history.end() || it->begin() > timestamp) {
 | 
			
		||||
			assert(it != m_history.begin());
 | 
			
		||||
			-- it;
 | 
			
		||||
		}
 | 
			
		||||
		assert(timestamp >= it->begin() && timestamp < it->end());
 | 
			
		||||
		return std::string(it->data(), it->data() + it->size());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
#ifdef SLIC3R_UNDOREDO_DEBUG
 | 
			
		||||
	std::string format() override {
 | 
			
		||||
		std::string out = typeid(T).name();
 | 
			
		||||
		for (const MutableHistoryInterval &interval : m_history)
 | 
			
		||||
			out += std::string(", ptr:") + ptr_to_string(interval.data()) + " len:" + std::to_string(interval.size()) + " <" + std::to_string(interval.begin()) + "," + std::to_string(interval.end()) + ")";
 | 
			
		||||
		return out;
 | 
			
		||||
	}
 | 
			
		||||
#endif /* SLIC3R_UNDOREDO_DEBUG */
 | 
			
		||||
 | 
			
		||||
#ifndef NDEBUG
 | 
			
		||||
	bool valid() override;
 | 
			
		||||
#endif /* NDEBUG */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#ifndef NDEBUG
 | 
			
		||||
template<typename T>
 | 
			
		||||
bool ImmutableObjectHistory<T>::valid()
 | 
			
		||||
{
 | 
			
		||||
	// The immutable object content is captured either by a shared object, or by its serialization, but not both.
 | 
			
		||||
	assert(! m_shared_object == ! m_serialized.empty());
 | 
			
		||||
	// Verify that the history intervals are sorted and do not overlap.
 | 
			
		||||
	if (! m_history.empty())
 | 
			
		||||
		for (size_t i = 1; i < m_history.size(); ++ i)
 | 
			
		||||
			assert(m_history[i - 1].strictly_before(m_history[i]));
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
#endif /* NDEBUG */
 | 
			
		||||
 | 
			
		||||
#ifndef NDEBUG
 | 
			
		||||
template<typename T>
 | 
			
		||||
bool MutableObjectHistory<T>::valid()
 | 
			
		||||
{
 | 
			
		||||
	// Verify that the history intervals are sorted and do not overlap, and that the data reference counters are correct.
 | 
			
		||||
	if (! m_history.empty()) {
 | 
			
		||||
		std::map<const char*, size_t> refcntrs;
 | 
			
		||||
		assert(m_history.front().data() != nullptr);
 | 
			
		||||
		++ refcntrs[m_history.front().data()];
 | 
			
		||||
		for (size_t i = 1; i < m_history.size(); ++ i) {
 | 
			
		||||
			assert(m_history[i - 1].interval().strictly_before(m_history[i].interval()));
 | 
			
		||||
			++ refcntrs[m_history[i].data()];
 | 
			
		||||
		}
 | 
			
		||||
		for (const auto &hi : m_history) {
 | 
			
		||||
			assert(hi.data() != nullptr);
 | 
			
		||||
			assert(refcntrs[hi.data()] == hi.refcnt());
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
#endif /* NDEBUG */
 | 
			
		||||
 | 
			
		||||
class StackImpl
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	// Stack needs to be initialized. An empty stack is not valid, there must be a "New Project" status stored at the beginning.
 | 
			
		||||
	StackImpl() : m_active_snapshot_time(0), m_current_time(0) {}
 | 
			
		||||
 | 
			
		||||
	// The Undo / Redo stack is being initialized with an empty model and an empty selection.
 | 
			
		||||
	// The first snapshot cannot be removed.
 | 
			
		||||
	void initialize(const Slic3r::Model &model, const Slic3r::GUI::Selection &selection);
 | 
			
		||||
 | 
			
		||||
	// Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time.
 | 
			
		||||
	void take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection);
 | 
			
		||||
	void load_snapshot(size_t timestamp, Slic3r::Model &model);
 | 
			
		||||
 | 
			
		||||
	bool has_undo_snapshot() const;
 | 
			
		||||
	bool has_redo_snapshot() const;
 | 
			
		||||
	bool undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, size_t jump_to_time);
 | 
			
		||||
	bool redo(Slic3r::Model &model, size_t jump_to_time);
 | 
			
		||||
 | 
			
		||||
	// Snapshot history (names with timestamps).
 | 
			
		||||
	const std::vector<Snapshot>& 	snapshots() const { return m_snapshots; }
 | 
			
		||||
	// Timestamp of the active snapshot.
 | 
			
		||||
	size_t 							active_snapshot_time() const { return m_active_snapshot_time; }
 | 
			
		||||
 | 
			
		||||
	const Selection& selection_deserialized() const { return m_selection; }
 | 
			
		||||
 | 
			
		||||
//protected:
 | 
			
		||||
	template<typename T, typename T_AS> ObjectID save_mutable_object(const T &object);
 | 
			
		||||
	template<typename T> ObjectID save_immutable_object(std::shared_ptr<const T> &object);
 | 
			
		||||
	template<typename T> T* load_mutable_object(const Slic3r::ObjectID id);
 | 
			
		||||
	template<typename T> std::shared_ptr<const T> load_immutable_object(const Slic3r::ObjectID id);
 | 
			
		||||
	template<typename T, typename T_AS> void load_mutable_object(const Slic3r::ObjectID id, T &target);
 | 
			
		||||
 | 
			
		||||
#ifdef SLIC3R_UNDOREDO_DEBUG
 | 
			
		||||
	std::string format() const {
 | 
			
		||||
		std::string out = "Objects\n";
 | 
			
		||||
		for (const std::pair<const ObjectID, std::unique_ptr<ObjectHistoryBase>> &kvp : m_objects)
 | 
			
		||||
			out += std::string("ObjectID:") + std::to_string(kvp.first.id) + " " + kvp.second->format() + "\n";
 | 
			
		||||
		out += "Snapshots\n";
 | 
			
		||||
		for (const Snapshot &snapshot : m_snapshots) {
 | 
			
		||||
			if (snapshot.timestamp == m_active_snapshot_time)
 | 
			
		||||
				out += ">>> ";
 | 
			
		||||
			out += std::string("Name: \"") + snapshot.name + "\", timestamp: " + std::to_string(snapshot.timestamp) + 
 | 
			
		||||
				", Model ID:" + ((snapshot.model_id == 0) ? "Invalid" : std::to_string(snapshot.model_id)) + "\n";
 | 
			
		||||
		}
 | 
			
		||||
		if (m_active_snapshot_time > m_snapshots.back().timestamp)
 | 
			
		||||
			out += ">>>\n";
 | 
			
		||||
		out += "Current time: " + std::to_string(m_current_time) + "\n";
 | 
			
		||||
		return out;
 | 
			
		||||
	}
 | 
			
		||||
	void print() const {
 | 
			
		||||
		std::cout << "Undo / Redo stack" << std::endl;
 | 
			
		||||
		std::cout << this->format() << std::endl;
 | 
			
		||||
	}
 | 
			
		||||
#endif /* SLIC3R_UNDOREDO_DEBUG */
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#ifndef NDEBUG
 | 
			
		||||
	bool valid() const {
 | 
			
		||||
		assert(! m_snapshots.empty());
 | 
			
		||||
		auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time));
 | 
			
		||||
		assert(it != m_snapshots.begin() && it != m_snapshots.end() && it->timestamp == m_active_snapshot_time);
 | 
			
		||||
		assert(m_active_snapshot_time < m_snapshots.back().timestamp || m_snapshots.back().is_topmost());
 | 
			
		||||
		for (auto it = m_objects.begin(); it != m_objects.end(); ++ it)
 | 
			
		||||
			assert(it->second->valid());
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
#endif /* NDEBUG */
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	template<typename T> ObjectID 	immutable_object_id(const std::shared_ptr<const T> &ptr) { 
 | 
			
		||||
		return this->immutable_object_id_impl((const void*)ptr.get());
 | 
			
		||||
	}
 | 
			
		||||
	ObjectID     					immutable_object_id_impl(const void *ptr) {
 | 
			
		||||
		auto it = m_shared_ptr_to_object_id.find(ptr);
 | 
			
		||||
		if (it == m_shared_ptr_to_object_id.end()) {
 | 
			
		||||
			// Allocate a new temporary ObjectID for this shared pointer.
 | 
			
		||||
			ObjectBase object_with_id;
 | 
			
		||||
			it = m_shared_ptr_to_object_id.insert(it, std::make_pair(ptr, object_with_id.id()));
 | 
			
		||||
		}
 | 
			
		||||
		return it->second;
 | 
			
		||||
	}
 | 
			
		||||
	void 							collect_garbage();
 | 
			
		||||
 | 
			
		||||
	// Each individual object (Model, ModelObject, ModelInstance, ModelVolume, Selection, TriangleMesh)
 | 
			
		||||
	// is stored with its own history, referenced by the ObjectID. Immutable objects do not provide
 | 
			
		||||
	// their own IDs, therefore there are temporary IDs generated for them and stored to m_shared_ptr_to_object_id.
 | 
			
		||||
	std::map<ObjectID, std::unique_ptr<ObjectHistoryBase>> 	m_objects;
 | 
			
		||||
	std::map<const void*, ObjectID>							m_shared_ptr_to_object_id;
 | 
			
		||||
	// Snapshot history (names with timestamps).
 | 
			
		||||
	std::vector<Snapshot>									m_snapshots;
 | 
			
		||||
	// Timestamp of the active snapshot.
 | 
			
		||||
	size_t 													m_active_snapshot_time;
 | 
			
		||||
	// Logical time counter. m_current_time is being incremented with each snapshot taken.
 | 
			
		||||
	size_t 													m_current_time;
 | 
			
		||||
	// Last selection serialized or deserialized.
 | 
			
		||||
	Selection 												m_selection;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
using InputArchive  = cereal::UserDataAdapter<StackImpl, cereal::BinaryInputArchive>;
 | 
			
		||||
using OutputArchive = cereal::UserDataAdapter<StackImpl, cereal::BinaryOutputArchive>;
 | 
			
		||||
 | 
			
		||||
} // namespace UndoRedo
 | 
			
		||||
 | 
			
		||||
class Model;
 | 
			
		||||
class ModelObject;
 | 
			
		||||
class ModelVolume;
 | 
			
		||||
class ModelInstance;
 | 
			
		||||
class ModelMaterial;
 | 
			
		||||
class ModelConfig;
 | 
			
		||||
class DynamicPrintConfig;
 | 
			
		||||
class TriangleMesh;
 | 
			
		||||
 | 
			
		||||
} // namespace Slic3r
 | 
			
		||||
 | 
			
		||||
namespace cereal
 | 
			
		||||
{
 | 
			
		||||
	// Let cereal know that there are load / save non-member functions declared for ModelObject*, ignore serialization of pointers triggering 
 | 
			
		||||
	// static assert, that cereal does not support serialization of raw pointers.
 | 
			
		||||
	template <class Archive> struct specialize<Archive, Slic3r::Model*, cereal::specialization::non_member_load_save> {};
 | 
			
		||||
	template <class Archive> struct specialize<Archive, Slic3r::ModelObject*, cereal::specialization::non_member_load_save> {};
 | 
			
		||||
	template <class Archive> struct specialize<Archive, Slic3r::ModelVolume*, cereal::specialization::non_member_load_save> {};
 | 
			
		||||
	template <class Archive> struct specialize<Archive, Slic3r::ModelInstance*, cereal::specialization::non_member_load_save> {};
 | 
			
		||||
	template <class Archive> struct specialize<Archive, Slic3r::ModelMaterial*, cereal::specialization::non_member_load_save> {};
 | 
			
		||||
	template <class Archive> struct specialize<Archive, Slic3r::ModelConfig, cereal::specialization::non_member_load_save> {};
 | 
			
		||||
	template <class Archive> struct specialize<Archive, std::shared_ptr<Slic3r::TriangleMesh>, cereal::specialization::non_member_load_save> {};
 | 
			
		||||
 | 
			
		||||
	// Store ObjectBase derived class onto the Undo / Redo stack as a separate object,
 | 
			
		||||
	// store just the ObjectID to this stream.
 | 
			
		||||
	template <class T> void save(BinaryOutputArchive& ar, T* const& ptr)
 | 
			
		||||
	{
 | 
			
		||||
		ar(cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar).save_mutable_object<T, T>(*ptr));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Load ObjectBase derived class from the Undo / Redo stack as a separate object
 | 
			
		||||
	// based on the ObjectID loaded from this stream.
 | 
			
		||||
	template <class T> void load(BinaryInputArchive& ar, T*& ptr)
 | 
			
		||||
	{
 | 
			
		||||
		Slic3r::UndoRedo::StackImpl& stack = cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar);
 | 
			
		||||
		size_t id;
 | 
			
		||||
		ar(id);
 | 
			
		||||
		ptr = stack.load_mutable_object<T>(Slic3r::ObjectID(id));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Store ObjectBase derived class onto the Undo / Redo stack as a separate object,
 | 
			
		||||
	// store just the ObjectID to this stream.
 | 
			
		||||
	template <class T> void save(BinaryOutputArchive &ar, const std::unique_ptr<T> &ptr)
 | 
			
		||||
	{
 | 
			
		||||
		ar(cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar).save_mutable_object<T>(*ptr.get()));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Load ObjectBase derived class from the Undo / Redo stack as a separate object
 | 
			
		||||
	// based on the ObjectID loaded from this stream.
 | 
			
		||||
	template <class T> void load(BinaryInputArchive &ar, std::unique_ptr<T> &ptr)
 | 
			
		||||
	{
 | 
			
		||||
		Slic3r::UndoRedo::StackImpl& stack = cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar);
 | 
			
		||||
		size_t id;
 | 
			
		||||
		ar(id);
 | 
			
		||||
		ptr.reset(stack.load_mutable_object<T>(Slic3r::ObjectID(id)));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Store ObjectBase derived class onto the Undo / Redo stack as a separate object,
 | 
			
		||||
	// store just the ObjectID to this stream.
 | 
			
		||||
	void save(BinaryOutputArchive& ar, const Slic3r::ModelConfig &cfg)
 | 
			
		||||
	{
 | 
			
		||||
		ar(cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar).save_mutable_object<Slic3r::ModelConfig, Slic3r::DynamicPrintConfig>(cfg));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Load ObjectBase derived class from the Undo / Redo stack as a separate object
 | 
			
		||||
	// based on the ObjectID loaded from this stream.
 | 
			
		||||
	void load(BinaryInputArchive& ar, Slic3r::ModelConfig &cfg)
 | 
			
		||||
	{
 | 
			
		||||
		Slic3r::UndoRedo::StackImpl& stack = cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar);
 | 
			
		||||
		size_t id;
 | 
			
		||||
		ar(id);
 | 
			
		||||
		stack.load_mutable_object<Slic3r::ModelConfig, Slic3r::DynamicPrintConfig>(Slic3r::ObjectID(id), cfg);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Store ObjectBase derived class onto the Undo / Redo stack as a separate object,
 | 
			
		||||
	// store just the ObjectID to this stream.
 | 
			
		||||
	template <class T> void save(BinaryOutputArchive &ar, const std::shared_ptr<const T> &ptr)
 | 
			
		||||
	{
 | 
			
		||||
		ar(cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar).save_immutable_object<T>(const_cast<std::shared_ptr<const T>&>(ptr)));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Load ObjectBase derived class from the Undo / Redo stack as a separate object
 | 
			
		||||
	// based on the ObjectID loaded from this stream.
 | 
			
		||||
	template <class T> void load(BinaryInputArchive &ar, std::shared_ptr<const T> &ptr)
 | 
			
		||||
	{
 | 
			
		||||
		Slic3r::UndoRedo::StackImpl &stack = cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar);
 | 
			
		||||
		size_t id;
 | 
			
		||||
		ar(id);
 | 
			
		||||
		ptr = stack.load_immutable_object<T>(Slic3r::ObjectID(id));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#include <libslic3r/Model.hpp>
 | 
			
		||||
#include <libslic3r/TriangleMesh.hpp>
 | 
			
		||||
#include <slic3r/GUI/Selection.hpp>
 | 
			
		||||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
namespace UndoRedo {
 | 
			
		||||
 | 
			
		||||
template<typename T> std::shared_ptr<const T>& 	ImmutableObjectHistory<T>::shared_ptr(StackImpl &stack)
 | 
			
		||||
{
 | 
			
		||||
	if (m_shared_object.get() == nullptr && ! this->m_serialized.empty()) {
 | 
			
		||||
		// Deserialize the object.
 | 
			
		||||
		std::istringstream iss(m_serialized);
 | 
			
		||||
		{
 | 
			
		||||
			Slic3r::UndoRedo::InputArchive archive(stack, iss);
 | 
			
		||||
			typedef typename std::remove_const<T>::type Type;
 | 
			
		||||
			std::unique_ptr<Type> mesh(new Type());
 | 
			
		||||
			archive(*mesh.get());
 | 
			
		||||
			m_shared_object = std::move(mesh);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return m_shared_object;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template<typename T, typename T_AS> ObjectID StackImpl::save_mutable_object(const T &object)
 | 
			
		||||
{
 | 
			
		||||
	// First find or allocate a history stack for the ObjectID of this object instance.
 | 
			
		||||
	auto it_object_history = m_objects.find(object.id());
 | 
			
		||||
	if (it_object_history == m_objects.end())
 | 
			
		||||
		it_object_history = m_objects.insert(it_object_history, std::make_pair(object.id(), std::unique_ptr<MutableObjectHistory<T>>(new MutableObjectHistory<T>())));
 | 
			
		||||
	auto *object_history = static_cast<MutableObjectHistory<T>*>(it_object_history->second.get());
 | 
			
		||||
	// Then serialize the object into a string.
 | 
			
		||||
	std::ostringstream oss;
 | 
			
		||||
	{
 | 
			
		||||
		Slic3r::UndoRedo::OutputArchive archive(*this, oss);
 | 
			
		||||
		archive(static_cast<const T_AS&>(object));
 | 
			
		||||
	}
 | 
			
		||||
	object_history->save(m_active_snapshot_time, m_current_time, oss.str());
 | 
			
		||||
	return object.id();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template<typename T> ObjectID StackImpl::save_immutable_object(std::shared_ptr<const T> &object)
 | 
			
		||||
{
 | 
			
		||||
	// First allocate a temporary ObjectID for this pointer.
 | 
			
		||||
	ObjectID object_id = this->immutable_object_id(object);
 | 
			
		||||
	// and find or allocate a history stack for the ObjectID associated to this shared_ptr.
 | 
			
		||||
	auto it_object_history = m_objects.find(object_id);
 | 
			
		||||
	if (it_object_history == m_objects.end())
 | 
			
		||||
		it_object_history = m_objects.emplace_hint(it_object_history, object_id, std::unique_ptr<ImmutableObjectHistory<T>>(new ImmutableObjectHistory<T>(object)));
 | 
			
		||||
	// Then save the interval.
 | 
			
		||||
	static_cast<ImmutableObjectHistory<T>*>(it_object_history->second.get())->save(m_active_snapshot_time, m_current_time);
 | 
			
		||||
	return object_id;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template<typename T> T* StackImpl::load_mutable_object(const Slic3r::ObjectID id)
 | 
			
		||||
{
 | 
			
		||||
	T *target = new T();
 | 
			
		||||
	this->load_mutable_object<T, T>(id, *target);
 | 
			
		||||
	return target;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template<typename T> std::shared_ptr<const T> StackImpl::load_immutable_object(const Slic3r::ObjectID id)
 | 
			
		||||
{
 | 
			
		||||
	// First find a history stack for the ObjectID of this object instance.
 | 
			
		||||
	auto it_object_history = m_objects.find(id);
 | 
			
		||||
	assert(it_object_history != m_objects.end());
 | 
			
		||||
	auto *object_history = static_cast<ImmutableObjectHistory<T>*>(it_object_history->second.get());
 | 
			
		||||
	assert(object_history->has_snapshot(m_active_snapshot_time));
 | 
			
		||||
	return object_history->shared_ptr(*this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template<typename T, typename T_AS> void StackImpl::load_mutable_object(const Slic3r::ObjectID id, T &target)
 | 
			
		||||
{
 | 
			
		||||
	// First find a history stack for the ObjectID of this object instance.
 | 
			
		||||
	auto it_object_history = m_objects.find(id);
 | 
			
		||||
	assert(it_object_history != m_objects.end());
 | 
			
		||||
	auto *object_history = static_cast<const MutableObjectHistory<T>*>(it_object_history->second.get());
 | 
			
		||||
	// Then get the data associated with the object history and m_active_snapshot_time.
 | 
			
		||||
	std::istringstream iss(object_history->load(m_active_snapshot_time));
 | 
			
		||||
	Slic3r::UndoRedo::InputArchive archive(*this, iss);
 | 
			
		||||
	target.m_id = id;
 | 
			
		||||
	archive(static_cast<T_AS&>(target));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time.
 | 
			
		||||
void StackImpl::take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection)
 | 
			
		||||
{
 | 
			
		||||
	// Release old snapshot data.
 | 
			
		||||
	assert(m_active_snapshot_time <= m_current_time);
 | 
			
		||||
	for (auto &kvp : m_objects)
 | 
			
		||||
		kvp.second->release_after_timestamp(m_active_snapshot_time);
 | 
			
		||||
	{
 | 
			
		||||
		auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time));
 | 
			
		||||
		m_snapshots.erase(it, m_snapshots.end());
 | 
			
		||||
	}
 | 
			
		||||
	// Take new snapshots.
 | 
			
		||||
	this->save_mutable_object<Slic3r::Model, Slic3r::Model>(model);
 | 
			
		||||
	m_selection.volumes_and_instances.clear();
 | 
			
		||||
	m_selection.volumes_and_instances.reserve(selection.get_volume_idxs().size());
 | 
			
		||||
	m_selection.mode = selection.get_mode();
 | 
			
		||||
	for (unsigned int volume_idx : selection.get_volume_idxs())
 | 
			
		||||
		m_selection.volumes_and_instances.emplace_back(selection.get_volume(volume_idx)->geometry_id);
 | 
			
		||||
	this->save_mutable_object<Selection, Selection>(m_selection);
 | 
			
		||||
	// Save the snapshot info.
 | 
			
		||||
	m_snapshots.emplace_back(snapshot_name, m_current_time ++, model.id().id);
 | 
			
		||||
	m_active_snapshot_time = m_current_time;
 | 
			
		||||
	// Save snapshot info of the last "current" aka "top most" state, that is only being serialized
 | 
			
		||||
	// if undoing an action. Such a snapshot has an invalid Model ID assigned if it was not taken yet.
 | 
			
		||||
	m_snapshots.emplace_back(topmost_snapsnot_name, m_active_snapshot_time, 0);
 | 
			
		||||
	// Release empty objects from the history.
 | 
			
		||||
	this->collect_garbage();
 | 
			
		||||
	assert(this->valid());
 | 
			
		||||
#ifdef SLIC3R_UNDOREDO_DEBUG
 | 
			
		||||
	std::cout << "After snapshot" << std::endl;
 | 
			
		||||
	this->print();
 | 
			
		||||
#endif /* SLIC3R_UNDOREDO_DEBUG */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model &model)
 | 
			
		||||
{
 | 
			
		||||
	// Find the snapshot by time. It must exist.
 | 
			
		||||
	const auto it_snapshot = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(timestamp));
 | 
			
		||||
	if (it_snapshot == m_snapshots.end() || it_snapshot->timestamp != timestamp)
 | 
			
		||||
		throw std::runtime_error((boost::format("Snapshot with timestamp %1% does not exist") % timestamp).str());
 | 
			
		||||
 | 
			
		||||
	m_active_snapshot_time = timestamp;
 | 
			
		||||
	model.clear_objects();
 | 
			
		||||
	model.clear_materials();
 | 
			
		||||
	this->load_mutable_object<Slic3r::Model, Slic3r::Model>(ObjectID(it_snapshot->model_id), model);
 | 
			
		||||
	model.update_links_bottom_up_recursive();
 | 
			
		||||
	m_selection.volumes_and_instances.clear();
 | 
			
		||||
	this->load_mutable_object<Selection, Selection>(m_selection.id(), m_selection);
 | 
			
		||||
	// Sort the volumes so that we may use binary search.
 | 
			
		||||
	std::sort(m_selection.volumes_and_instances.begin(), m_selection.volumes_and_instances.end());
 | 
			
		||||
	this->m_active_snapshot_time = timestamp;
 | 
			
		||||
	assert(this->valid());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool StackImpl::has_undo_snapshot() const
 | 
			
		||||
{ 
 | 
			
		||||
	assert(this->valid());
 | 
			
		||||
	auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time));
 | 
			
		||||
	return -- it != m_snapshots.begin();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool StackImpl::has_redo_snapshot() const
 | 
			
		||||
{
 | 
			
		||||
	assert(this->valid());
 | 
			
		||||
	auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time));
 | 
			
		||||
	return ++ it != m_snapshots.end();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool StackImpl::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, size_t time_to_load)
 | 
			
		||||
{ 
 | 
			
		||||
	assert(this->valid());
 | 
			
		||||
	if (time_to_load == SIZE_MAX) {
 | 
			
		||||
		auto it_current = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time));
 | 
			
		||||
		if (-- it_current == m_snapshots.begin())
 | 
			
		||||
			return false;
 | 
			
		||||
		time_to_load = it_current->timestamp;
 | 
			
		||||
	}
 | 
			
		||||
	assert(time_to_load < m_active_snapshot_time);
 | 
			
		||||
	assert(std::binary_search(m_snapshots.begin(), m_snapshots.end(), Snapshot(time_to_load)));
 | 
			
		||||
	if (m_active_snapshot_time == m_snapshots.back().timestamp && ! m_snapshots.back().is_topmost_captured()) {
 | 
			
		||||
		// The current state is temporary. The current state needs to be captured to be redoable.
 | 
			
		||||
		this->take_snapshot(topmost_snapsnot_name, model, selection);
 | 
			
		||||
		// The line above entered another topmost_snapshot_name.
 | 
			
		||||
		assert(m_snapshots.back().is_topmost());
 | 
			
		||||
		assert(! m_snapshots.back().is_topmost_captured());
 | 
			
		||||
		// Pop it back, it is not needed as there is now a captured topmost state.
 | 
			
		||||
		m_snapshots.pop_back();
 | 
			
		||||
		// current_time was extended, but it should not cause any harm. Resetting it back may complicate the logic unnecessarily.
 | 
			
		||||
		//-- m_current_time;
 | 
			
		||||
		assert(m_snapshots.back().is_topmost());
 | 
			
		||||
		assert(m_snapshots.back().is_topmost_captured());
 | 
			
		||||
	}
 | 
			
		||||
	this->load_snapshot(time_to_load, model);
 | 
			
		||||
#ifdef SLIC3R_UNDOREDO_DEBUG
 | 
			
		||||
	std::cout << "After undo" << std::endl;
 | 
			
		||||
 	this->print();
 | 
			
		||||
#endif /* SLIC3R_UNDOREDO_DEBUG */
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool StackImpl::redo(Slic3r::Model &model, size_t time_to_load)
 | 
			
		||||
{ 
 | 
			
		||||
	assert(this->valid());
 | 
			
		||||
	if (time_to_load == SIZE_MAX) {
 | 
			
		||||
		auto it_current = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time));
 | 
			
		||||
		if (++ it_current == m_snapshots.end())
 | 
			
		||||
			return false;
 | 
			
		||||
		time_to_load = it_current->timestamp;
 | 
			
		||||
	}
 | 
			
		||||
	assert(time_to_load > m_active_snapshot_time);
 | 
			
		||||
	assert(std::binary_search(m_snapshots.begin(), m_snapshots.end(), Snapshot(time_to_load)));
 | 
			
		||||
	this->load_snapshot(time_to_load, model);
 | 
			
		||||
#ifdef SLIC3R_UNDOREDO_DEBUG
 | 
			
		||||
	std::cout << "After redo" << std::endl;
 | 
			
		||||
 	this->print();
 | 
			
		||||
#endif /* SLIC3R_UNDOREDO_DEBUG */
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void StackImpl::collect_garbage()
 | 
			
		||||
{
 | 
			
		||||
	// Purge objects with empty histories.
 | 
			
		||||
	for (auto it = m_objects.begin(); it != m_objects.end();) {
 | 
			
		||||
		if (it->second->empty()) {
 | 
			
		||||
			if (it->second->immutable_object_ptr() != nullptr)
 | 
			
		||||
				// Release the immutable object from the ptr to ObjectID map.
 | 
			
		||||
				m_shared_ptr_to_object_id.erase(it->second->immutable_object_ptr());
 | 
			
		||||
			it = m_objects.erase(it);
 | 
			
		||||
		} else
 | 
			
		||||
			++ it;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Wrappers of the private implementation.
 | 
			
		||||
Stack::Stack() : pimpl(new StackImpl()) {}
 | 
			
		||||
Stack::~Stack() {}
 | 
			
		||||
void Stack::take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection) { pimpl->take_snapshot(snapshot_name, model, selection); }
 | 
			
		||||
bool Stack::has_undo_snapshot() const { return pimpl->has_undo_snapshot(); }
 | 
			
		||||
bool Stack::has_redo_snapshot() const { return pimpl->has_redo_snapshot(); }
 | 
			
		||||
bool Stack::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, size_t time_to_load) { return pimpl->undo(model, selection, time_to_load); }
 | 
			
		||||
bool Stack::redo(Slic3r::Model &model, size_t time_to_load) { return pimpl->redo(model, time_to_load); }
 | 
			
		||||
const Selection& Stack::selection_deserialized() const { return pimpl->selection_deserialized(); }
 | 
			
		||||
 | 
			
		||||
const std::vector<Snapshot>& Stack::snapshots() const { return pimpl->snapshots(); }
 | 
			
		||||
size_t Stack::active_snapshot_time() const { return pimpl->active_snapshot_time(); }
 | 
			
		||||
 | 
			
		||||
} // namespace UndoRedo
 | 
			
		||||
} // namespace Slic3r
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//FIXME we should have unit tests for testing serialization of basic types as DynamicPrintConfig.
 | 
			
		||||
#if 0
 | 
			
		||||
#include "libslic3r/Config.hpp"
 | 
			
		||||
#include "libslic3r/PrintConfig.hpp"
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
	bool test_dynamic_print_config_serialization() {
 | 
			
		||||
		FullPrintConfig full_print_config;
 | 
			
		||||
		DynamicPrintConfig cfg;
 | 
			
		||||
		cfg.apply(full_print_config, false);
 | 
			
		||||
 | 
			
		||||
		std::string serialized;
 | 
			
		||||
	   	try {
 | 
			
		||||
			std::ostringstream ss;
 | 
			
		||||
			cereal::BinaryOutputArchive oarchive(ss);
 | 
			
		||||
	        oarchive(cfg);
 | 
			
		||||
			serialized = ss.str();
 | 
			
		||||
	    } catch (std::runtime_error e) {
 | 
			
		||||
	        e.what();
 | 
			
		||||
	    }
 | 
			
		||||
 | 
			
		||||
	    DynamicPrintConfig cfg2;
 | 
			
		||||
	   	try {
 | 
			
		||||
			std::stringstream ss(serialized);
 | 
			
		||||
			cereal::BinaryInputArchive iarchive(ss);
 | 
			
		||||
	        iarchive(cfg2);
 | 
			
		||||
	    } catch (std::runtime_error e) {
 | 
			
		||||
	        e.what();
 | 
			
		||||
	    }
 | 
			
		||||
 | 
			
		||||
	    if (cfg == cfg2) {
 | 
			
		||||
	    	printf("Yes!\n");
 | 
			
		||||
			return true;
 | 
			
		||||
	    }
 | 
			
		||||
	    printf("No!\n");
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
} // namespace Slic3r
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										94
									
								
								src/slic3r/Utils/UndoRedo.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								src/slic3r/Utils/UndoRedo.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,94 @@
 | 
			
		|||
#ifndef slic3r_Utils_UndoRedo_hpp_
 | 
			
		||||
#define slic3r_Utils_UndoRedo_hpp_
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <cassert>
 | 
			
		||||
 | 
			
		||||
#include <libslic3r/ObjectID.hpp>
 | 
			
		||||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
 | 
			
		||||
class Model;
 | 
			
		||||
 | 
			
		||||
namespace GUI {
 | 
			
		||||
	class Selection;
 | 
			
		||||
} // namespace GUI
 | 
			
		||||
 | 
			
		||||
namespace UndoRedo {
 | 
			
		||||
 | 
			
		||||
struct Snapshot
 | 
			
		||||
{
 | 
			
		||||
	Snapshot(size_t timestamp) : timestamp(timestamp) {}
 | 
			
		||||
	Snapshot(const std::string &name, size_t timestamp, size_t model_id) : name(name), timestamp(timestamp), model_id(model_id) {}
 | 
			
		||||
	
 | 
			
		||||
	std::string name;
 | 
			
		||||
	size_t 		timestamp;
 | 
			
		||||
	size_t 		model_id;
 | 
			
		||||
 | 
			
		||||
	bool		operator< (const Snapshot &rhs) const { return this->timestamp < rhs.timestamp; }
 | 
			
		||||
	bool		operator==(const Snapshot &rhs) const { return this->timestamp == rhs.timestamp; }
 | 
			
		||||
 | 
			
		||||
	// The topmost snapshot represents the current state when going forward.
 | 
			
		||||
	bool 		is_topmost() const;
 | 
			
		||||
	// The topmost snapshot is not being serialized to the Undo / Redo stack until going back in time, 
 | 
			
		||||
	// when the top most state is being serialized, so we can redo back to the top most state.
 | 
			
		||||
	bool 		is_topmost_captured() const { assert(this->is_topmost()); return model_id > 0; }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Excerpt of Slic3r::GUI::Selection for serialization onto the Undo / Redo stack.
 | 
			
		||||
struct Selection : public Slic3r::ObjectBase {
 | 
			
		||||
	unsigned char							mode;
 | 
			
		||||
	std::vector<std::pair<size_t, size_t>>	volumes_and_instances;
 | 
			
		||||
	template<class Archive> void serialize(Archive &ar) { ar(mode, volumes_and_instances); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class StackImpl;
 | 
			
		||||
 | 
			
		||||
class Stack
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	// Stack needs to be initialized. An empty stack is not valid, there must be a "New Project" status stored at the beginning.
 | 
			
		||||
	// The first "New Project" snapshot shall not be removed.
 | 
			
		||||
	Stack();
 | 
			
		||||
	~Stack();
 | 
			
		||||
 | 
			
		||||
	// Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time.
 | 
			
		||||
	void take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection);
 | 
			
		||||
 | 
			
		||||
	// To be queried to enable / disable the Undo / Redo buttons at the UI.
 | 
			
		||||
	bool has_undo_snapshot() const;
 | 
			
		||||
	bool has_redo_snapshot() const;
 | 
			
		||||
 | 
			
		||||
	// Roll back the time. If time_to_load is SIZE_MAX, the previous snapshot is activated.
 | 
			
		||||
	// Undoing an action may need to take a snapshot of the current application state, so that redo to the current state is possible.
 | 
			
		||||
	bool undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, size_t time_to_load = SIZE_MAX);
 | 
			
		||||
 | 
			
		||||
	// Jump forward in time. If time_to_load is SIZE_MAX, the next snapshot is activated.
 | 
			
		||||
	bool redo(Slic3r::Model &model, size_t time_to_load = SIZE_MAX);
 | 
			
		||||
 | 
			
		||||
	// Snapshot history (names with timestamps).
 | 
			
		||||
	// Each snapshot indicates start of an interval in which this operation is performed.
 | 
			
		||||
	// There is one additional snapshot taken at the very end, which indicates the current unnamed state.
 | 
			
		||||
 | 
			
		||||
	const std::vector<Snapshot>& snapshots() const;
 | 
			
		||||
	// Timestamp of the active snapshot. One of the snapshots of this->snapshots() shall have Snapshot::timestamp equal to this->active_snapshot_time().
 | 
			
		||||
	// The snapshot time indicates start of an operation, which is finished at the time of the following snapshot, therefore
 | 
			
		||||
	// the active snapshot is the successive snapshot. The same logic applies to the time_to_load parameter of undo() and redo() operations.
 | 
			
		||||
	size_t active_snapshot_time() const;
 | 
			
		||||
 | 
			
		||||
	// After load_snapshot() / undo() / redo() the selection is deserialized into a list of ObjectIDs, which needs to be converted
 | 
			
		||||
	// into the list of GLVolume pointers once the 3D scene is updated.
 | 
			
		||||
	const Selection& selection_deserialized() const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend class StackImpl;
 | 
			
		||||
	std::unique_ptr<StackImpl> 	pimpl;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}; // namespace UndoRedo
 | 
			
		||||
}; // namespace Slic3r
 | 
			
		||||
 | 
			
		||||
#endif /* slic3r_Utils_UndoRedo_hpp_ */
 | 
			
		||||
| 
						 | 
				
			
			@ -9,19 +9,19 @@ use Test::More tests => 147;
 | 
			
		|||
foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintConfig) {
 | 
			
		||||
    $config->set('layer_height', 0.3);
 | 
			
		||||
    ok abs($config->get('layer_height') - 0.3) < 1e-4, 'set/get float';
 | 
			
		||||
    is $config->serialize('layer_height'), '0.3', 'serialize float';
 | 
			
		||||
    is $config->opt_serialize('layer_height'), '0.3', 'serialize float';
 | 
			
		||||
    
 | 
			
		||||
    $config->set('perimeters', 2);
 | 
			
		||||
    is $config->get('perimeters'), 2, 'set/get int';
 | 
			
		||||
    is $config->serialize('perimeters'), '2', 'serialize int';
 | 
			
		||||
    is $config->opt_serialize('perimeters'), '2', 'serialize int';
 | 
			
		||||
    
 | 
			
		||||
    $config->set('extrusion_axis', 'A');
 | 
			
		||||
    is $config->get('extrusion_axis'), 'A', 'set/get string';
 | 
			
		||||
    is $config->serialize('extrusion_axis'), 'A', 'serialize string';
 | 
			
		||||
    is $config->opt_serialize('extrusion_axis'), 'A', 'serialize string';
 | 
			
		||||
    
 | 
			
		||||
    $config->set('notes', "foo\nbar");
 | 
			
		||||
    is $config->get('notes'), "foo\nbar", 'set/get string with newline';
 | 
			
		||||
    is $config->serialize('notes'), 'foo\nbar', 'serialize string with newline';
 | 
			
		||||
    is $config->opt_serialize('notes'), 'foo\nbar', 'serialize string with newline';
 | 
			
		||||
    $config->set_deserialize('notes', 'bar\nbaz');
 | 
			
		||||
    is $config->get('notes'), "bar\nbaz", 'deserialize string with newline';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -59,7 +59,7 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo
 | 
			
		|||
        )
 | 
			
		||||
    {    
 | 
			
		||||
        $config->set('filament_notes', $test_data->{values});
 | 
			
		||||
        is $config->serialize('filament_notes'), $test_data->{serialized}, 'serialize multi-string value ' . $test_data->{name};
 | 
			
		||||
        is $config->opt_serialize('filament_notes'), $test_data->{serialized}, 'serialize multi-string value ' . $test_data->{name};
 | 
			
		||||
        $config->set_deserialize('filament_notes', '');
 | 
			
		||||
        is_deeply $config->get('filament_notes'), [], 'deserialize multi-string value - empty ' . $test_data->{name};
 | 
			
		||||
        $config->set_deserialize('filament_notes', $test_data->{serialized});
 | 
			
		||||
| 
						 | 
				
			
			@ -68,12 +68,12 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo
 | 
			
		|||
 | 
			
		||||
    $config->set('first_layer_height', 0.3);
 | 
			
		||||
    ok abs($config->get('first_layer_height') - 0.3) < 1e-4, 'set/get absolute floatOrPercent';
 | 
			
		||||
    is $config->serialize('first_layer_height'), '0.3', 'serialize absolute floatOrPercent';
 | 
			
		||||
    is $config->opt_serialize('first_layer_height'), '0.3', 'serialize absolute floatOrPercent';
 | 
			
		||||
    
 | 
			
		||||
    $config->set('first_layer_height', '50%');
 | 
			
		||||
    $config->get_abs_value('first_layer_height');
 | 
			
		||||
    ok abs($config->get_abs_value('first_layer_height') - 0.15) < 1e-4, 'set/get relative floatOrPercent';
 | 
			
		||||
    is $config->serialize('first_layer_height'), '50%', 'serialize relative floatOrPercent';
 | 
			
		||||
    is $config->opt_serialize('first_layer_height'), '50%', 'serialize relative floatOrPercent';
 | 
			
		||||
    
 | 
			
		||||
    # Uh-oh, we have no point option to test at the moment
 | 
			
		||||
    #ok $config->set('print_center', [50,80]), 'valid point coordinates';
 | 
			
		||||
| 
						 | 
				
			
			@ -86,11 +86,10 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo
 | 
			
		|||
    
 | 
			
		||||
    $config->set('use_relative_e_distances', 1);
 | 
			
		||||
    is $config->get('use_relative_e_distances'), 1, 'set/get bool';
 | 
			
		||||
    is $config->serialize('use_relative_e_distances'), '1', 'serialize bool';
 | 
			
		||||
    
 | 
			
		||||
    is $config->opt_serialize('use_relative_e_distances'), '1', 'serialize bool';
 | 
			
		||||
    $config->set('gcode_flavor', 'teacup');
 | 
			
		||||
    is $config->get('gcode_flavor'), 'teacup', 'set/get enum';
 | 
			
		||||
    is $config->serialize('gcode_flavor'), 'teacup', 'serialize enum';
 | 
			
		||||
    is $config->opt_serialize('gcode_flavor'), 'teacup', 'serialize enum';
 | 
			
		||||
    $config->set_deserialize('gcode_flavor', 'mach3');
 | 
			
		||||
    is $config->get('gcode_flavor'), 'mach3', 'deserialize enum (gcode_flavor)';
 | 
			
		||||
    $config->set_deserialize('gcode_flavor', 'machinekit');
 | 
			
		||||
| 
						 | 
				
			
			@ -106,7 +105,7 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo
 | 
			
		|||
    is_deeply [ map $_->pp, @{$config->get('extruder_offset')} ], [[10,20],[30,45]], 'set/get points';
 | 
			
		||||
    $config->set('extruder_offset', [Slic3r::Pointf->new(10,20),Slic3r::Pointf->new(30,45)]);
 | 
			
		||||
    is_deeply [ map $_->pp, @{$config->get('extruder_offset')} ], [[10,20],[30,45]], 'set/get points';
 | 
			
		||||
    is $config->serialize('extruder_offset'), '10x20,30x45', 'serialize points';
 | 
			
		||||
    is $config->opt_serialize('extruder_offset'), '10x20,30x45', 'serialize points';
 | 
			
		||||
    $config->set_deserialize('extruder_offset', '20x10');
 | 
			
		||||
    is_deeply [ map $_->pp, @{$config->get('extruder_offset')} ], [[20,10]], 'deserialize points';
 | 
			
		||||
    $config->set_deserialize('extruder_offset', '0x0');
 | 
			
		||||
| 
						 | 
				
			
			@ -120,7 +119,7 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo
 | 
			
		|||
    # truncate ->get() to first decimal digit
 | 
			
		||||
    $config->set('nozzle_diameter', [0.2,3]);
 | 
			
		||||
    is_deeply [ map int($_*10)/10, @{$config->get('nozzle_diameter')} ], [0.2,3], 'set/get floats';
 | 
			
		||||
    is $config->serialize('nozzle_diameter'), '0.2,3', 'serialize floats';
 | 
			
		||||
    is $config->opt_serialize('nozzle_diameter'), '0.2,3', 'serialize floats';
 | 
			
		||||
    $config->set_deserialize('nozzle_diameter', '0.1,0.4');
 | 
			
		||||
    is_deeply [ map int($_*10)/10, @{$config->get('nozzle_diameter')} ], [0.1,0.4], 'deserialize floats';
 | 
			
		||||
    $config->set_deserialize('nozzle_diameter', '3');
 | 
			
		||||
| 
						 | 
				
			
			@ -133,7 +132,7 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo
 | 
			
		|||
    
 | 
			
		||||
    $config->set('temperature', [180,210]);
 | 
			
		||||
    is_deeply $config->get('temperature'), [180,210], 'set/get ints';
 | 
			
		||||
    is $config->serialize('temperature'), '180,210', 'serialize ints';
 | 
			
		||||
    is $config->opt_serialize('temperature'), '180,210', 'serialize ints';
 | 
			
		||||
    $config->set_deserialize('temperature', '195,220');
 | 
			
		||||
    is_deeply $config->get('temperature'), [195,220], 'deserialize ints';
 | 
			
		||||
    {
 | 
			
		||||
| 
						 | 
				
			
			@ -147,7 +146,7 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo
 | 
			
		|||
    is $config->get_at('wipe', 0), 1, 'get_at bools';
 | 
			
		||||
    is $config->get_at('wipe', 1), 0, 'get_at bools';
 | 
			
		||||
    is $config->get_at('wipe', 9), 1, 'get_at bools';
 | 
			
		||||
    is $config->serialize('wipe'), '1,0', 'serialize bools';
 | 
			
		||||
    is $config->opt_serialize('wipe'), '1,0', 'serialize bools';
 | 
			
		||||
    $config->set_deserialize('wipe', '0,1,1');
 | 
			
		||||
    is_deeply $config->get('wipe'), [0,1,1], 'deserialize bools';
 | 
			
		||||
    $config->set_deserialize('wipe', '');
 | 
			
		||||
| 
						 | 
				
			
			@ -162,7 +161,7 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo
 | 
			
		|||
    
 | 
			
		||||
    $config->set('post_process', ['foo','bar']);
 | 
			
		||||
    is_deeply $config->get('post_process'), ['foo','bar'], 'set/get strings';
 | 
			
		||||
    is $config->serialize('post_process'), 'foo;bar', 'serialize strings';
 | 
			
		||||
    is $config->opt_serialize('post_process'), 'foo;bar', 'serialize strings';
 | 
			
		||||
    $config->set_deserialize('post_process', 'bar;baz');
 | 
			
		||||
    is_deeply $config->get('post_process'), ['bar','baz'], 'deserialize strings';
 | 
			
		||||
    {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,7 +33,7 @@
 | 
			
		|||
        %code{% RETVAL = ConfigBase__set_deserialize(THIS, opt_key, str); %};
 | 
			
		||||
    void set_ifndef(t_config_option_key opt_key, SV* value, bool deserialize = false)
 | 
			
		||||
        %code{% ConfigBase__set_ifndef(THIS, opt_key, value, deserialize); %};
 | 
			
		||||
    std::string serialize(t_config_option_key opt_key);
 | 
			
		||||
    std::string opt_serialize(t_config_option_key opt_key);
 | 
			
		||||
    double get_abs_value(t_config_option_key opt_key);
 | 
			
		||||
    %name{get_abs_value_over}
 | 
			
		||||
        double get_abs_value(t_config_option_key opt_key, double ratio_over);
 | 
			
		||||
| 
						 | 
				
			
			@ -95,7 +95,7 @@
 | 
			
		|||
        %code{% RETVAL = ConfigBase__set_deserialize(THIS, opt_key, str); %};
 | 
			
		||||
    void set_ifndef(t_config_option_key opt_key, SV* value, bool deserialize = false)
 | 
			
		||||
        %code{% ConfigBase__set_ifndef(THIS, opt_key, value, deserialize); %};
 | 
			
		||||
    std::string serialize(t_config_option_key opt_key);
 | 
			
		||||
    std::string opt_serialize(t_config_option_key opt_key);
 | 
			
		||||
    double get_abs_value(t_config_option_key opt_key);
 | 
			
		||||
    %name{get_abs_value_over}
 | 
			
		||||
        double get_abs_value(t_config_option_key opt_key, double ratio_over);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue