mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-31 12:41:20 -06:00 
			
		
		
		
	Merge branch 'master' into ys_overrides
This commit is contained in:
		
						commit
						8112d80fa2
					
				
					 159 changed files with 10096 additions and 5248 deletions
				
			
		|  | @ -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 */ | ||||
|  | @ -430,6 +430,8 @@ private: | |||
| // floats of the first edge matches all six floats of the second edge.
 | ||||
| void stl_check_facets_exact(stl_file *stl) | ||||
| { | ||||
| 	assert(stl->facet_start.size() == stl->neighbors_start.size()); | ||||
| 
 | ||||
|   	stl->stats.connected_edges         = 0; | ||||
|   	stl->stats.connected_facets_1_edge = 0; | ||||
|   	stl->stats.connected_facets_2_edge = 0; | ||||
|  |  | |||
|  | @ -127,6 +127,10 @@ struct stl_file { | |||
| 		this->stats.reset(); | ||||
| 	} | ||||
| 
 | ||||
| 	size_t memsize() const { | ||||
| 		return sizeof(*this) + sizeof(stl_facet) * facet_start.size() + sizeof(stl_neighbors) * neighbors_start.size(); | ||||
| 	} | ||||
| 
 | ||||
| 	std::vector<stl_facet>     		facet_start; | ||||
| 	std::vector<stl_neighbors> 		neighbors_start; | ||||
| 	// Statistics
 | ||||
|  | @ -139,6 +143,10 @@ struct indexed_triangle_set | |||
| 
 | ||||
| 	void clear() { indices.clear(); vertices.clear(); } | ||||
| 
 | ||||
| 	size_t memsize() const { | ||||
| 		return sizeof(*this) + sizeof(stl_triangle_vertex_indices) * indices.size() + sizeof(stl_vertex) * vertices.size(); | ||||
| 	} | ||||
| 
 | ||||
| 	std::vector<stl_triangle_vertex_indices> 	indices; | ||||
| 	std::vector<stl_vertex>       				vertices; | ||||
| 	//FIXME add normals once we get rid of the stl_file from TriangleMesh completely.
 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -81,9 +81,8 @@ add_library(libslic3r STATIC | |||
|     GCode/SpiralVase.hpp | ||||
|     GCode/ToolOrdering.cpp | ||||
|     GCode/ToolOrdering.hpp | ||||
|     GCode/WipeTower.cpp | ||||
|     GCode/WipeTower.hpp | ||||
|     GCode/WipeTowerPrusaMM.cpp | ||||
|     GCode/WipeTowerPrusaMM.hpp | ||||
|     GCode.cpp | ||||
|     GCode.hpp | ||||
|     GCodeReader.cpp | ||||
|  | @ -114,6 +113,8 @@ add_library(libslic3r STATIC | |||
|     MultiPoint.cpp | ||||
|     MultiPoint.hpp | ||||
|     MutablePriorityQueue.hpp | ||||
|     ObjectID.cpp | ||||
|     ObjectID.hpp | ||||
|     PerimeterGenerator.cpp | ||||
|     PerimeterGenerator.hpp | ||||
|     PlaceholderParser.cpp | ||||
|  | @ -189,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.
 | ||||
|  | @ -90,7 +93,7 @@ enum ConfigOptionMode { | |||
|     comExpert | ||||
| }; | ||||
| 
 | ||||
| enum PrinterTechnology | ||||
| enum PrinterTechnology : unsigned char | ||||
| { | ||||
|     // Fused Filament Fabrication
 | ||||
|     ptFFF, | ||||
|  | @ -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.
 | ||||
|  |  | |||
|  | @ -146,10 +146,10 @@ void EdgeGrid::Grid::create_from_m_contours(coord_t resolution) | |||
| 		    coord_t iy    = p1(1) / m_resolution; | ||||
| 		    coord_t ixb   = p2(0) / m_resolution; | ||||
| 		    coord_t iyb   = p2(1) / m_resolution; | ||||
| 			assert(ix >= 0 && ix < m_cols); | ||||
| 			assert(iy >= 0 && iy < m_rows); | ||||
| 			assert(ixb >= 0 && ixb < m_cols); | ||||
| 			assert(iyb >= 0 && iyb < m_rows); | ||||
| 			assert(ix >= 0 && size_t(ix) < m_cols); | ||||
| 			assert(iy >= 0 && size_t(iy) < m_rows); | ||||
| 			assert(ixb >= 0 && size_t(ixb) < m_cols); | ||||
| 			assert(iyb >= 0 && size_t(iyb) < m_rows); | ||||
| 			// Account for the end points.
 | ||||
| 			++ m_cells[iy*m_cols+ix].end; | ||||
| 			if (ix == ixb && iy == iyb) | ||||
|  | @ -290,10 +290,10 @@ void EdgeGrid::Grid::create_from_m_contours(coord_t resolution) | |||
| 			coord_t iy = p1(1) / m_resolution; | ||||
| 			coord_t ixb = p2(0) / m_resolution; | ||||
| 			coord_t iyb = p2(1) / m_resolution; | ||||
| 			assert(ix >= 0 && ix < m_cols); | ||||
| 			assert(iy >= 0 && iy < m_rows); | ||||
| 			assert(ixb >= 0 && ixb < m_cols); | ||||
| 			assert(iyb >= 0 && iyb < m_rows); | ||||
| 			assert(ix >= 0 && size_t(ix) < m_cols); | ||||
| 			assert(iy >= 0 && size_t(iy) < m_rows); | ||||
| 			assert(ixb >= 0 && size_t(ixb) < m_cols); | ||||
| 			assert(iyb >= 0 && size_t(iyb) < m_rows); | ||||
| 			// Account for the end points.
 | ||||
| 			m_cell_data[m_cells[iy*m_cols + ix].end++] = std::pair<size_t, size_t>(i, j); | ||||
| 			if (ix == ixb && iy == iyb) | ||||
|  | @ -775,11 +775,11 @@ void EdgeGrid::Grid::calculate_sdf() | |||
| 				// For each corner of this cell and its 1 ring neighbours:
 | ||||
| 				for (int corner_y = -1; corner_y < 3; ++ corner_y) { | ||||
| 					coord_t corner_r = r + corner_y; | ||||
| 					if (corner_r < 0 || corner_r >= nrows) | ||||
| 					if (corner_r < 0 || (size_t)corner_r >= nrows) | ||||
| 						continue; | ||||
| 					for (int corner_x = -1; corner_x < 3; ++ corner_x) { | ||||
| 						coord_t corner_c = c + corner_x; | ||||
| 						if (corner_c < 0 || corner_c >= ncols) | ||||
| 						if (corner_c < 0 || (size_t)corner_c >= ncols) | ||||
| 							continue; | ||||
| 						float  &d_min = m_signed_distance_field[corner_r * ncols + corner_c]; | ||||
| 						Slic3r::Point pt(m_bbox.min(0) + corner_c * m_resolution, m_bbox.min(1) + corner_r * m_resolution); | ||||
|  | @ -1137,9 +1137,9 @@ bool EdgeGrid::Grid::signed_distance_edges(const Point &pt, coord_t search_radiu | |||
| 		return false; | ||||
| 	bbox.max(0) /= m_resolution; | ||||
| 	bbox.max(1) /= m_resolution; | ||||
| 	if (bbox.max(0) >= m_cols) | ||||
| 	if ((size_t)bbox.max(0) >= m_cols) | ||||
| 		bbox.max(0) = m_cols - 1; | ||||
| 	if (bbox.max(1) >= m_rows) | ||||
| 	if ((size_t)bbox.max(1) >= m_rows) | ||||
| 		bbox.max(1) = m_rows - 1; | ||||
| 	// Lower boundary, round to grid and test validity.
 | ||||
| 	bbox.min(0) -= search_radius; | ||||
|  |  | |||
|  | @ -78,8 +78,8 @@ protected: | |||
| #endif | ||||
| 	bool cell_inside_or_crossing(int r, int c) const | ||||
| 	{ | ||||
| 		if (r < 0 || r >= m_rows || | ||||
| 			c < 0 || c >= m_cols) | ||||
| 		if (r < 0 || (size_t)r >= m_rows || | ||||
| 			c < 0 || (size_t)c >= m_cols) | ||||
| 			// The cell is outside the domain. Hoping that the contours were correctly oriented, so
 | ||||
| 			// there is a CCW outmost contour so the out of domain cells are outside.
 | ||||
| 			return false; | ||||
|  |  | |||
|  | @ -660,7 +660,7 @@ void gcode_spread_points( | |||
| 	for (ExtrusionPoints::const_iterator it = points.begin(); it != points.end(); ++ it) { | ||||
| 		const V2f  ¢er = it->center; | ||||
| 		const float radius = it->radius; | ||||
| 		const float radius2 = radius * radius; | ||||
| 		//const float radius2 = radius * radius;
 | ||||
| 		const float height_target = it->height; | ||||
| 		B2f bbox(center - V2f(radius, radius), center + V2f(radius, radius)); | ||||
| 		B2i bboxi( | ||||
|  | @ -774,8 +774,8 @@ void gcode_spread_points( | |||
| 			} | ||||
| 		} | ||||
| #endif | ||||
| 		float area_circle_total2 = float(M_PI) * sqr(radius); | ||||
| 		float area_err = fabs(area_circle_total2 - area_circle_total) / area_circle_total2; | ||||
| //		float area_circle_total2 = float(M_PI) * sqr(radius);
 | ||||
| //		float area_err = fabs(area_circle_total2 - area_circle_total) / area_circle_total2;
 | ||||
| //		printf("area_circle_total: %f, %f, %f\n", area_circle_total, area_circle_total2, area_err);
 | ||||
| 		float volume_full = float(M_PI) * sqr(radius) * height_target; | ||||
| //		if (true) {
 | ||||
|  | @ -905,8 +905,8 @@ void ExtrusionSimulator::set_image_size(const Point &image_size) | |||
| 	// printf("Allocating image data, allocated\n");
 | ||||
| 
 | ||||
| 	//FIXME fill the image with red vertical lines.
 | ||||
| 	for (size_t r = 0; r < image_size.y(); ++ r) { | ||||
| 		for (size_t c = 0; c < image_size.x(); c += 2) { | ||||
| 	for (size_t r = 0; r < size_t(image_size.y()); ++ r) { | ||||
| 		for (size_t c = 0; c < size_t(image_size.x()); c += 2) { | ||||
| 			// Color red
 | ||||
| 			pimpl->image_data[r * image_size.x() * 4 + c * 4] = 255; | ||||
| 			// Opacity full
 | ||||
|  | @ -958,7 +958,7 @@ void ExtrusionSimulator::extrude_to_accumulator(const ExtrusionPath &path, const | |||
| 	float scalex  = float(viewport.size().x()) / float(bbox.size().x()); | ||||
| 	float scaley  = float(viewport.size().y()) / float(bbox.size().y()); | ||||
| 	float w = scale_(path.width) * scalex; | ||||
| 	float h = scale_(path.height) * scalex; | ||||
| 	//float h = scale_(path.height) * scalex;
 | ||||
| 	w = scale_(path.mm3_per_mm / path.height) * scalex; | ||||
| 	// printf("scalex: %f, scaley: %f\n", scalex, scaley);
 | ||||
| 	// printf("bbox: %d,%d %d,%d\n", bbox.min.x(), bbox.min.y, bbox.max.x(), bbox.max.y);
 | ||||
|  | @ -993,8 +993,8 @@ void ExtrusionSimulator::evaluate_accumulator(ExtrusionSimulationType simulation | |||
| 		for (int r = 0; r < sz.y(); ++r) { | ||||
| 			for (int c = 0; c < sz.x(); ++c) { | ||||
| 				float p = 0; | ||||
| 				for (int j = 0; j < pimpl->bitmap_oversampled; ++ j) { | ||||
| 					for (int i = 0; i < pimpl->bitmap_oversampled; ++ i) { | ||||
| 				for (unsigned int j = 0; j < pimpl->bitmap_oversampled; ++ j) { | ||||
| 					for (unsigned int i = 0; i < pimpl->bitmap_oversampled; ++ i) { | ||||
| 						if (pimpl->bitmap[r * pimpl->bitmap_oversampled + j][c * pimpl->bitmap_oversampled + i]) | ||||
| 							p += 1.f; | ||||
| 					} | ||||
|  |  | |||
|  | @ -126,7 +126,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) | |||
|         Polygons surfaces_polygons = to_polygons(surfaces); | ||||
|         Polygons collapsed = diff( | ||||
|             surfaces_polygons, | ||||
|             offset2(surfaces_polygons, -distance_between_surfaces/2, +distance_between_surfaces/2), | ||||
|             offset2(surfaces_polygons, (float)-distance_between_surfaces/2, (float)+distance_between_surfaces/2), | ||||
|             true); | ||||
|         Polygons to_subtract; | ||||
|         to_subtract.reserve(collapsed.size() + number_polygons(surfaces)); | ||||
|  | @ -137,7 +137,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) | |||
|         surfaces_append( | ||||
|             surfaces, | ||||
|             intersection_ex( | ||||
|                 offset(collapsed, distance_between_surfaces), | ||||
|                 offset(collapsed, (float)distance_between_surfaces), | ||||
|                 to_subtract, | ||||
|                 true), | ||||
|             stInternalSolid); | ||||
|  | @ -219,14 +219,14 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) | |||
|         f->z = layerm.layer()->print_z; | ||||
|         f->angle = float(Geometry::deg2rad(layerm.region()->config().fill_angle.value)); | ||||
|         // Maximum length of the perimeter segment linking two infill lines.
 | ||||
|         f->link_max_length = scale_(link_max_length); | ||||
|         f->link_max_length = (coord_t)scale_(link_max_length); | ||||
|         // Used by the concentric infill pattern to clip the loops to create extrusion paths.
 | ||||
|         f->loop_clipping = scale_(flow.nozzle_diameter) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER; | ||||
|         f->loop_clipping = coord_t(scale_(flow.nozzle_diameter) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER); | ||||
| //        f->layer_height = h;
 | ||||
| 
 | ||||
|         // apply half spacing using this flow's own spacing and generate infill
 | ||||
|         FillParams params; | ||||
|         params.density = 0.01 * density; | ||||
|         params.density = float(0.01 * density); | ||||
| //        params.dont_adjust = true;
 | ||||
|         params.dont_adjust = false; | ||||
|         Polylines polylines = f->fill_surface(&surface, params); | ||||
|  | @ -240,7 +240,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) | |||
|             // so we can safely ignore the slight variation that might have
 | ||||
|             // been applied to $f->flow_spacing
 | ||||
|         } else { | ||||
|             flow = Flow::new_from_spacing(f->spacing, flow.nozzle_diameter, h, is_bridge || f->use_bridge_flow()); | ||||
|             flow = Flow::new_from_spacing(f->spacing, flow.nozzle_diameter, (float)h, is_bridge || f->use_bridge_flow()); | ||||
|         } | ||||
| 
 | ||||
|         // Save into layer.
 | ||||
|  |  | |||
|  | @ -130,14 +130,14 @@ static inline Point hilbert_n_to_xy(const size_t n) | |||
|         } | ||||
|     } | ||||
|     int state    = (ndigits & 1) ? 4 : 0; | ||||
|     int dirstate = (ndigits & 1) ? 0 : 4; | ||||
| //    int dirstate = (ndigits & 1) ? 0 : 4;
 | ||||
|     coord_t x = 0; | ||||
|     coord_t y = 0; | ||||
|     for (int i = (int)ndigits - 1; i >= 0; -- i) { | ||||
|         int digit = (n >> (i * 2)) & 3; | ||||
|         state += digit; | ||||
|         if (digit != 3) | ||||
|             dirstate = state; // lowest non-3 digit
 | ||||
| //        if (digit != 3)
 | ||||
| //            dirstate = state; // lowest non-3 digit
 | ||||
|         x |= digit_to_x[state] << i; | ||||
|         y |= digit_to_y[state] << i; | ||||
|         state = next_state[state]; | ||||
|  |  | |||
|  | @ -287,7 +287,8 @@ public: | |||
|         assert(aoffset1 < 0); | ||||
|         assert(aoffset2 < 0); | ||||
|         assert(aoffset2 < aoffset1); | ||||
|         bool sticks_removed = remove_sticks(polygons_src); | ||||
| //        bool sticks_removed = 
 | ||||
|         remove_sticks(polygons_src); | ||||
| //        if (sticks_removed) printf("Sticks removed!\n");
 | ||||
|         polygons_outer = offset(polygons_src, aoffset1, | ||||
|             ClipperLib::jtMiter, | ||||
|  | @ -481,7 +482,7 @@ static inline IntersectionTypeOtherVLine intersection_type_on_prev_next_vertical | |||
| { | ||||
|     // This routine will propose a connecting line even if the connecting perimeter segment intersects 
 | ||||
|     // iVertical line multiple times before reaching iIntersectionOther.
 | ||||
|     if (iIntersectionOther == -1) | ||||
|     if (iIntersectionOther == size_t(-1)) | ||||
|         return INTERSECTION_TYPE_OTHER_VLINE_UNDEFINED; | ||||
|     assert(dir_is_next ? (iVerticalLine + 1 < segs.size()) : (iVerticalLine > 0)); | ||||
|     const SegmentedIntersectionLine &il_this      = segs[iVerticalLine]; | ||||
|  | @ -858,8 +859,8 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP | |||
|             if (il > ir) | ||||
|                 // No vertical line intersects this segment.
 | ||||
|                 continue; | ||||
|             assert(il >= 0 && il < segs.size()); | ||||
|             assert(ir >= 0 && ir < segs.size()); | ||||
|             assert(il >= 0 && size_t(il) < segs.size()); | ||||
|             assert(ir >= 0 && size_t(ir) < segs.size()); | ||||
|             for (int i = il; i <= ir; ++ i) { | ||||
|                 coord_t this_x = segs[i].pos; | ||||
| 				assert(this_x == i * line_spacing + x0); | ||||
|  | @ -1159,8 +1160,8 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP | |||
|             int iSegAbove = -1; | ||||
|             int iSegBelow = -1; | ||||
|             { | ||||
|                 SegmentIntersection::SegmentIntersectionType type_crossing = (intrsctn->type == SegmentIntersection::INNER_LOW) ? | ||||
|                     SegmentIntersection::INNER_HIGH : SegmentIntersection::INNER_LOW; | ||||
| //                SegmentIntersection::SegmentIntersectionType type_crossing = (intrsctn->type == SegmentIntersection::INNER_LOW) ?
 | ||||
| //                    SegmentIntersection::INNER_HIGH : SegmentIntersection::INNER_LOW;
 | ||||
|                 // Does the perimeter intersect the current vertical line above intrsctn?
 | ||||
|                 for (size_t i = i_intersection + 1; i + 1 < seg.intersections.size(); ++ i) | ||||
| //                    if (seg.intersections[i].iContour == intrsctn->iContour && seg.intersections[i].type == type_crossing) {
 | ||||
|  |  | |||
|  | @ -849,7 +849,7 @@ static inline IntersectionTypeOtherVLine intersection_type_on_prev_next_vertical | |||
| { | ||||
|     // This routine will propose a connecting line even if the connecting perimeter segment intersects 
 | ||||
|     // iVertical line multiple times before reaching iIntersectionOther.
 | ||||
|     if (iIntersectionOther == -1) | ||||
|     if (iIntersectionOther == size_t(-1)) | ||||
|         return INTERSECTION_TYPE_OTHER_VLINE_UNDEFINED; | ||||
|     assert(dir_is_next ? (iVerticalLine + 1 < segs.size()) : (iVerticalLine > 0)); | ||||
|     const SegmentedIntersectionLine &il_this      = segs[iVerticalLine]; | ||||
|  | @ -1284,8 +1284,8 @@ static bool fill_hatching_segments_legacy( | |||
|             int iSegAbove = -1; | ||||
|             int iSegBelow = -1; | ||||
|             { | ||||
|                 SegmentIntersection::SegmentIntersectionType type_crossing = (intrsctn->type == SegmentIntersection::INNER_LOW) ? | ||||
|                     SegmentIntersection::INNER_HIGH : SegmentIntersection::INNER_LOW; | ||||
| //                SegmentIntersection::SegmentIntersectionType type_crossing = (intrsctn->type == SegmentIntersection::INNER_LOW) ?
 | ||||
| //                    SegmentIntersection::INNER_HIGH : SegmentIntersection::INNER_LOW;
 | ||||
|                 // Does the perimeter intersect the current vertical line above intrsctn?
 | ||||
|                 for (size_t i = i_intersection + 1; i + 1 < seg.intersections.size(); ++ i) | ||||
| //                    if (seg.intersections[i].iContour == intrsctn->iContour && seg.intersections[i].type == type_crossing) {
 | ||||
|  |  | |||
|  | @ -11,10 +11,16 @@ | |||
| #include <boost/algorithm/string/classification.hpp> | ||||
| #include <boost/algorithm/string/split.hpp> | ||||
| #include <boost/algorithm/string/predicate.hpp> | ||||
| #include <boost/algorithm/string/replace.hpp> | ||||
| #include <boost/filesystem/operations.hpp> | ||||
| #include <boost/nowide/fstream.hpp> | ||||
| #include <boost/nowide/cstdio.hpp> | ||||
| 
 | ||||
| #include <boost/property_tree/ptree.hpp> | ||||
| #include <boost/property_tree/xml_parser.hpp> | ||||
| #include <boost/foreach.hpp> | ||||
| namespace pt = boost::property_tree; | ||||
| 
 | ||||
| #include <expat.h> | ||||
| #include <Eigen/Dense> | ||||
| #include "miniz_extension.hpp" | ||||
|  | @ -33,6 +39,7 @@ const std::string RELATIONSHIPS_FILE = "_rels/.rels"; | |||
| const std::string PRINT_CONFIG_FILE = "Metadata/Slic3r_PE.config"; | ||||
| const std::string MODEL_CONFIG_FILE = "Metadata/Slic3r_PE_model.config"; | ||||
| const std::string LAYER_HEIGHTS_PROFILE_FILE = "Metadata/Slic3r_PE_layer_heights_profile.txt"; | ||||
| const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/Prusa_Slicer_layer_config_ranges.xml"; | ||||
| const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt"; | ||||
| 
 | ||||
| const char* MODEL_TAG = "model"; | ||||
|  | @ -331,6 +338,7 @@ namespace Slic3r { | |||
|         typedef std::map<int, ObjectMetadata> IdToMetadataMap; | ||||
|         typedef std::map<int, Geometry> IdToGeometryMap; | ||||
|         typedef std::map<int, std::vector<coordf_t>> IdToLayerHeightsProfileMap; | ||||
|         typedef std::map<int, t_layer_config_ranges> IdToLayerConfigRangesMap; | ||||
|         typedef std::map<int, std::vector<sla::SupportPoint>> IdToSlaSupportPointsMap; | ||||
| 
 | ||||
|         // Version of the 3mf file
 | ||||
|  | @ -347,6 +355,7 @@ namespace Slic3r { | |||
|         CurrentConfig m_curr_config; | ||||
|         IdToMetadataMap m_objects_metadata; | ||||
|         IdToLayerHeightsProfileMap m_layer_heights_profiles; | ||||
|         IdToLayerConfigRangesMap m_layer_config_ranges; | ||||
|         IdToSlaSupportPointsMap m_sla_support_points; | ||||
|         std::string m_curr_metadata_name; | ||||
|         std::string m_curr_characters; | ||||
|  | @ -365,6 +374,7 @@ namespace Slic3r { | |||
|         bool _load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config); | ||||
|         bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); | ||||
|         void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); | ||||
|         void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); | ||||
|         void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); | ||||
| 
 | ||||
|         void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, const std::string& archive_filename); | ||||
|  | @ -476,6 +486,7 @@ namespace Slic3r { | |||
|         m_curr_config.volume_id = -1; | ||||
|         m_objects_metadata.clear(); | ||||
|         m_layer_heights_profiles.clear(); | ||||
|         m_layer_config_ranges.clear(); | ||||
|         m_sla_support_points.clear(); | ||||
|         m_curr_metadata_name.clear(); | ||||
|         m_curr_characters.clear(); | ||||
|  | @ -546,9 +557,14 @@ namespace Slic3r { | |||
| 
 | ||||
|                 if (boost::algorithm::iequals(name, LAYER_HEIGHTS_PROFILE_FILE)) | ||||
|                 { | ||||
|                     // extract slic3r lazer heights profile file
 | ||||
|                     // extract slic3r layer heights profile file
 | ||||
|                     _extract_layer_heights_profile_config_from_archive(archive, stat); | ||||
|                 } | ||||
|                 if (boost::algorithm::iequals(name, LAYER_CONFIG_RANGES_FILE)) | ||||
|                 { | ||||
|                     // extract slic3r layer config ranges file
 | ||||
|                     _extract_layer_config_ranges_from_archive(archive, stat); | ||||
|                 } | ||||
|                 else if (boost::algorithm::iequals(name, SLA_SUPPORT_POINTS_FILE)) | ||||
|                 { | ||||
|                     // extract sla support points file
 | ||||
|  | @ -592,6 +608,11 @@ namespace Slic3r { | |||
|             if (obj_layer_heights_profile != m_layer_heights_profiles.end()) | ||||
|                 model_object->layer_height_profile = obj_layer_heights_profile->second; | ||||
| 
 | ||||
|             // m_layer_config_ranges are indexed by a 1 based model object index.
 | ||||
|             IdToLayerConfigRangesMap::iterator obj_layer_config_ranges = m_layer_config_ranges.find(object.second + 1); | ||||
|             if (obj_layer_config_ranges != m_layer_config_ranges.end()) | ||||
|                 model_object->layer_config_ranges = obj_layer_config_ranges->second; | ||||
| 
 | ||||
|             // m_sla_support_points are indexed by a 1 based model object index.
 | ||||
|             IdToSlaSupportPointsMap::iterator obj_sla_support_points = m_sla_support_points.find(object.second + 1); | ||||
|             if (obj_sla_support_points != m_sla_support_points.end() && !obj_sla_support_points->second.empty()) { | ||||
|  | @ -675,7 +696,7 @@ namespace Slic3r { | |||
|         if (!XML_ParseBuffer(m_xml_parser, (int)stat.m_uncomp_size, 1)) | ||||
|         { | ||||
|             char error_buf[1024]; | ||||
|             ::sprintf(error_buf, "Error (%s) while parsing xml file at line %d", XML_ErrorString(XML_GetErrorCode(m_xml_parser)), XML_GetCurrentLineNumber(m_xml_parser)); | ||||
|             ::sprintf(error_buf, "Error (%s) while parsing xml file at line %d", XML_ErrorString(XML_GetErrorCode(m_xml_parser)), (int)XML_GetCurrentLineNumber(m_xml_parser)); | ||||
|             add_error(error_buf); | ||||
|             return false; | ||||
|         } | ||||
|  | @ -769,6 +790,66 @@ namespace Slic3r { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     void _3MF_Importer::_extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) | ||||
|     { | ||||
|         if (stat.m_uncomp_size > 0) | ||||
|         { | ||||
|             std::string buffer((size_t)stat.m_uncomp_size, 0); | ||||
|             mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); | ||||
|             if (res == 0) { | ||||
|                 add_error("Error while reading layer config ranges data to buffer"); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             std::istringstream iss(buffer); // wrap returned xml to istringstream
 | ||||
|             pt::ptree objects_tree; | ||||
|             pt::read_xml(iss, objects_tree); | ||||
| 
 | ||||
|             for (const auto& object : objects_tree.get_child("objects")) | ||||
|             { | ||||
|                 pt::ptree object_tree = object.second; | ||||
|                 int obj_idx = object_tree.get<int>("<xmlattr>.id", -1); | ||||
|                 if (obj_idx <= 0) { | ||||
|                     add_error("Found invalid object id"); | ||||
|                     continue; | ||||
|                 } | ||||
| 
 | ||||
|                 IdToLayerConfigRangesMap::iterator object_item = m_layer_config_ranges.find(obj_idx); | ||||
|                 if (object_item != m_layer_config_ranges.end()) { | ||||
|                     add_error("Found duplicated layer config range"); | ||||
|                     continue; | ||||
|                 } | ||||
| 
 | ||||
|                 t_layer_config_ranges config_ranges; | ||||
| 
 | ||||
|                 for (const auto& range : object_tree) | ||||
|                 { | ||||
|                     if (range.first != "range") | ||||
|                         continue; | ||||
|                     pt::ptree range_tree = range.second; | ||||
|                     double min_z = range_tree.get<double>("<xmlattr>.min_z"); | ||||
|                     double max_z = range_tree.get<double>("<xmlattr>.max_z"); | ||||
| 
 | ||||
|                     // get Z range information
 | ||||
|                     DynamicPrintConfig& config = config_ranges[{ min_z, max_z }]; | ||||
| 
 | ||||
|                     for (const auto& option : range_tree) | ||||
|                     { | ||||
|                         if (option.first != "option") | ||||
|                             continue; | ||||
|                         std::string opt_key = option.second.get<std::string>("<xmlattr>.opt_key"); | ||||
|                         std::string value = option.second.data(); | ||||
| 
 | ||||
|                         config.set_deserialize(opt_key, value); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 if (!config_ranges.empty()) | ||||
|                     m_layer_config_ranges.insert(IdToLayerConfigRangesMap::value_type(obj_idx, config_ranges)); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     void _3MF_Importer::_extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) | ||||
|     { | ||||
|         if (stat.m_uncomp_size > 0) | ||||
|  | @ -895,7 +976,7 @@ namespace Slic3r { | |||
|         if (!XML_ParseBuffer(m_xml_parser, (int)stat.m_uncomp_size, 1)) | ||||
|         { | ||||
|             char error_buf[1024]; | ||||
|             ::sprintf(error_buf, "Error (%s) while parsing xml file at line %d", XML_ErrorString(XML_GetErrorCode(m_xml_parser)), XML_GetCurrentLineNumber(m_xml_parser)); | ||||
|             ::sprintf(error_buf, "Error (%s) while parsing xml file at line %d", XML_ErrorString(XML_GetErrorCode(m_xml_parser)), (int)XML_GetCurrentLineNumber(m_xml_parser)); | ||||
|             add_error(error_buf); | ||||
|             return false; | ||||
|         } | ||||
|  | @ -1452,7 +1533,7 @@ namespace Slic3r { | |||
|             object->second.metadata.emplace_back(key, value); | ||||
|         else if (type == VOLUME_TYPE) | ||||
|         { | ||||
|             if (m_curr_config.volume_id < object->second.volumes.size()) | ||||
|             if (size_t(m_curr_config.volume_id) < object->second.volumes.size()) | ||||
|                 object->second.volumes[m_curr_config.volume_id].metadata.emplace_back(key, value); | ||||
|         } | ||||
|         else | ||||
|  | @ -1624,6 +1705,7 @@ namespace Slic3r { | |||
|         bool _add_mesh_to_object_stream(std::stringstream& stream, ModelObject& object, VolumeToOffsetsMap& volumes_offsets); | ||||
|         bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items); | ||||
|         bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model); | ||||
|         bool _add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model); | ||||
|         bool _add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model); | ||||
|         bool _add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config); | ||||
|         bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, const IdToObjectDataMap &objects_data); | ||||
|  | @ -1684,6 +1766,16 @@ namespace Slic3r { | |||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         // Adds layer config ranges file ("Metadata/Slic3r_PE_layer_config_ranges.txt").
 | ||||
|         // All layer height profiles of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model.
 | ||||
|         // The index differes from the index of an object ID of an object instance of a 3MF file!
 | ||||
|         if (!_add_layer_config_ranges_file_to_archive(archive, model)) | ||||
|         { | ||||
|             close_zip_writer(&archive); | ||||
|             boost::filesystem::remove(filename); | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         // Adds sla support points file ("Metadata/Slic3r_PE_sla_support_points.txt").
 | ||||
|         // All  sla support points of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model.
 | ||||
|         // The index differes from the index of an object ID of an object instance of a 3MF file!
 | ||||
|  | @ -1895,7 +1987,7 @@ namespace Slic3r { | |||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             vertices_count += its.vertices.size(); | ||||
|             vertices_count += (int)its.vertices.size(); | ||||
| 
 | ||||
|             const Transform3d& matrix = volume->get_matrix(); | ||||
| 
 | ||||
|  | @ -1925,7 +2017,7 @@ namespace Slic3r { | |||
| 
 | ||||
|             // updates triangle offsets
 | ||||
|             volume_it->second.first_triangle_id = triangles_count; | ||||
|             triangles_count += its.indices.size(); | ||||
|             triangles_count += (int)its.indices.size(); | ||||
|             volume_it->second.last_triangle_id = triangles_count - 1; | ||||
| 
 | ||||
|             for (size_t i = 0; i < its.indices.size(); ++ i) | ||||
|  | @ -2013,6 +2105,70 @@ namespace Slic3r { | |||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     bool _3MF_Exporter::_add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model) | ||||
|     { | ||||
|         std::string out = ""; | ||||
|         pt::ptree tree; | ||||
| 
 | ||||
|         unsigned int object_cnt = 0; | ||||
|         for (const ModelObject* object : model.objects) | ||||
|         { | ||||
|             object_cnt++; | ||||
|             const t_layer_config_ranges& ranges = object->layer_config_ranges; | ||||
|             if (!ranges.empty()) | ||||
|             { | ||||
|                 pt::ptree& obj_tree = tree.add("objects.object",""); | ||||
| 
 | ||||
|                 obj_tree.put("<xmlattr>.id", object_cnt); | ||||
| 
 | ||||
|                 // Store the layer config ranges.
 | ||||
|                 for (const auto& range : ranges) | ||||
|                 { | ||||
|                     pt::ptree& range_tree = obj_tree.add("range", ""); | ||||
| 
 | ||||
|                     // store minX and maxZ
 | ||||
|                     range_tree.put("<xmlattr>.min_z", range.first.first); | ||||
|                     range_tree.put("<xmlattr>.max_z", range.first.second); | ||||
| 
 | ||||
|                     // store range configuration
 | ||||
|                     const DynamicPrintConfig& config = range.second; | ||||
|                     for (const std::string& opt_key : config.keys()) | ||||
|                     { | ||||
|                         pt::ptree& opt_tree = range_tree.add("option", config.opt_serialize(opt_key)); | ||||
|                         opt_tree.put("<xmlattr>.opt_key", opt_key); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (!tree.empty()) | ||||
|         { | ||||
|             std::ostringstream oss; | ||||
|             boost::property_tree::write_xml(oss, tree); | ||||
|             out = oss.str(); | ||||
| 
 | ||||
|             // Post processing("beautification") of the output string for a better preview
 | ||||
|             boost::replace_all(out, "><object",      ">\n <object"); | ||||
|             boost::replace_all(out, "><range",       ">\n  <range"); | ||||
|             boost::replace_all(out, "><option",      ">\n   <option"); | ||||
|             boost::replace_all(out, "></range>",     ">\n  </range>"); | ||||
|             boost::replace_all(out, "></object>",    ">\n </object>"); | ||||
|             // OR just 
 | ||||
|             boost::replace_all(out, "><",            ">\n<");  | ||||
|         } | ||||
| 
 | ||||
|         if (!out.empty()) | ||||
|         { | ||||
|             if (!mz_zip_writer_add_mem(&archive, LAYER_CONFIG_RANGES_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) | ||||
|             { | ||||
|                 add_error("Unable to add layer heights profile file to archive"); | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     bool _3MF_Exporter::_add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model) | ||||
|     { | ||||
|         std::string out = ""; | ||||
|  | @ -2060,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()) | ||||
|         { | ||||
|  | @ -2094,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) | ||||
|  | @ -2124,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"; | ||||
|  |  | |||
|  | @ -106,6 +106,9 @@ struct AMFParserContext | |||
|                                         // amf/material/metadata
 | ||||
|         NODE_TYPE_OBJECT,               // amf/object
 | ||||
|                                         // amf/object/metadata
 | ||||
|         NODE_TYPE_LAYER_CONFIG,         // amf/object/layer_config_ranges
 | ||||
|         NODE_TYPE_RANGE,                // amf/object/layer_config_ranges/range
 | ||||
|                                         // amf/object/layer_config_ranges/range/metadata
 | ||||
|         NODE_TYPE_MESH,                 // amf/object/mesh
 | ||||
|         NODE_TYPE_VERTICES,             // amf/object/mesh/vertices
 | ||||
|         NODE_TYPE_VERTEX,               // amf/object/mesh/vertices/vertex
 | ||||
|  | @ -189,7 +192,7 @@ struct AMFParserContext | |||
|     }; | ||||
| 
 | ||||
|     // Version of the amf file
 | ||||
|     unsigned int m_version; | ||||
|     unsigned int             m_version; | ||||
|     // Current Expat XML parser instance.
 | ||||
|     XML_Parser               m_parser; | ||||
|     // Model to receive objects extracted from an AMF file.
 | ||||
|  | @ -260,7 +263,9 @@ void AMFParserContext::startElement(const char *name, const char **atts) | |||
|                 m_value[0] = get_attribute(atts, "type"); | ||||
|                 node_type_new = NODE_TYPE_METADATA; | ||||
|             } | ||||
|         } else if (strcmp(name, "mesh") == 0) { | ||||
|         } else if (strcmp(name, "layer_config_ranges") == 0 && m_path[1] == NODE_TYPE_OBJECT) | ||||
|                 node_type_new = NODE_TYPE_LAYER_CONFIG; | ||||
|         else if (strcmp(name, "mesh") == 0) { | ||||
|             if (m_path[1] == NODE_TYPE_OBJECT) | ||||
|                 node_type_new = NODE_TYPE_MESH; | ||||
|         } else if (strcmp(name, "instance") == 0) { | ||||
|  | @ -317,6 +322,10 @@ void AMFParserContext::startElement(const char *name, const char **atts) | |||
|             else if (strcmp(name, "mirrorz") == 0) | ||||
|                 node_type_new = NODE_TYPE_MIRRORZ; | ||||
|         } | ||||
|         else if (m_path[2] == NODE_TYPE_LAYER_CONFIG && strcmp(name, "range") == 0) { | ||||
|             assert(m_object); | ||||
|             node_type_new = NODE_TYPE_RANGE; | ||||
|         } | ||||
|         break; | ||||
|     case 4: | ||||
|         if (m_path[3] == NODE_TYPE_VERTICES) { | ||||
|  | @ -334,6 +343,10 @@ void AMFParserContext::startElement(const char *name, const char **atts) | |||
|             } else if (strcmp(name, "triangle") == 0) | ||||
|                 node_type_new = NODE_TYPE_TRIANGLE; | ||||
|         } | ||||
|         else if (m_path[3] == NODE_TYPE_RANGE && strcmp(name, "metadata") == 0) { | ||||
|             m_value[0] = get_attribute(atts, "type"); | ||||
|             node_type_new = NODE_TYPE_METADATA; | ||||
|         } | ||||
|         break; | ||||
|     case 5: | ||||
|         if (strcmp(name, "coordinates") == 0) { | ||||
|  | @ -571,8 +584,13 @@ void AMFParserContext::endElement(const char * /* name */) | |||
|                         config = &m_material->config; | ||||
|                     else if (m_path[1] == NODE_TYPE_OBJECT && m_object) | ||||
|                         config = &m_object->config; | ||||
|                 } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume) | ||||
|                 } | ||||
|                 else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume) | ||||
|                     config = &m_volume->config; | ||||
|                 else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_RANGE && m_object && !m_object->layer_config_ranges.empty()) { | ||||
|                     auto it  = --m_object->layer_config_ranges.end(); | ||||
|                     config = &it->second; | ||||
|                 } | ||||
|                 if (config) | ||||
|                     config->set_deserialize(opt_key, m_value[1]); | ||||
|             } else if (m_path.size() == 3 && m_path[1] == NODE_TYPE_OBJECT && m_object && strcmp(opt_key, "layer_height_profile") == 0) { | ||||
|  | @ -598,7 +616,7 @@ void AMFParserContext::endElement(const char * /* name */) | |||
|                     if (end != nullptr) | ||||
| 	                    *end = 0; | ||||
| 
 | ||||
|                     point(coord_idx) = atof(p); | ||||
|                     point(coord_idx) = float(atof(p)); | ||||
|                     if (++coord_idx == 5) { | ||||
|                         m_object->sla_support_points.push_back(sla::SupportPoint(point)); | ||||
|                         coord_idx = 0; | ||||
|  | @ -609,6 +627,16 @@ void AMFParserContext::endElement(const char * /* name */) | |||
|                 } | ||||
|                 m_object->sla_points_status = sla::PointsStatus::UserModified; | ||||
|             } | ||||
|             else if (m_path.size() == 5 && m_path[1] == NODE_TYPE_OBJECT && m_path[3] == NODE_TYPE_RANGE &&  | ||||
|                      m_object && strcmp(opt_key, "layer_height_range") == 0) { | ||||
|                 // Parse object's layer_height_range, a semicolon separated doubles.
 | ||||
|                 char* p = const_cast<char*>(m_value[1].c_str()); | ||||
|                 char* end = strchr(p, ';'); | ||||
|                 *end = 0; | ||||
| 
 | ||||
|                 const t_layer_height_range range = {double(atof(p)), double(atof(end + 1))}; | ||||
|                 m_object->layer_config_ranges[range]; | ||||
|             } | ||||
|             else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume) { | ||||
|                 if (strcmp(opt_key, "modifier") == 0) { | ||||
|                     // Is this volume a modifier volume?
 | ||||
|  | @ -694,8 +722,8 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, Model *model) | |||
|         } | ||||
|         int done = feof(pFile); | ||||
|         if (XML_Parse(parser, buff, len, done) == XML_STATUS_ERROR) { | ||||
|             printf("AMF parser: Parse error at line %ul:\n%s\n", | ||||
|                   XML_GetCurrentLineNumber(parser), | ||||
|             printf("AMF parser: Parse error at line %d:\n%s\n", | ||||
|                   (int)XML_GetCurrentLineNumber(parser), | ||||
|                   XML_ErrorString(XML_GetErrorCode(parser))); | ||||
|             break; | ||||
|         } | ||||
|  | @ -753,7 +781,7 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi | |||
| 
 | ||||
|     if (!XML_ParseBuffer(parser, (int)stat.m_uncomp_size, 1)) | ||||
|     { | ||||
|         printf("Error (%s) while parsing xml file at line %d\n", XML_ErrorString(XML_GetErrorCode(parser)), XML_GetCurrentLineNumber(parser)); | ||||
|         printf("Error (%s) while parsing xml file at line %d\n", XML_ErrorString(XML_GetErrorCode(parser)), (int)XML_GetCurrentLineNumber(parser)); | ||||
|         close_zip_reader(&archive); | ||||
|         return false; | ||||
|     } | ||||
|  | @ -873,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"; | ||||
|     } | ||||
| 
 | ||||
|  | @ -885,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; | ||||
|  | @ -893,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; | ||||
|  | @ -905,7 +933,30 @@ 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)
 | ||||
| 
 | ||||
|         // 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()) | ||||
|         { | ||||
|             // Store the layer config range as a single semicolon separated list.
 | ||||
|             stream << "    <layer_config_ranges>\n"; | ||||
|             size_t layer_counter = 0; | ||||
|             for (auto range : config_ranges) { | ||||
|                 stream << "      <range id=\"" << layer_counter << "\">\n"; | ||||
| 
 | ||||
|                 stream << "        <metadata type=\"slic3r.layer_height_range\">"; | ||||
|                 stream << range.first.first << ";" << range.first.second << "</metadata>\n"; | ||||
| 
 | ||||
|                 for (const std::string& key : range.second.keys()) | ||||
|                     stream << "        <metadata type=\"slic3r." << key << "\">" << range.second.opt_serialize(key) << "</metadata>\n"; | ||||
| 
 | ||||
|                 stream << "      </range>\n"; | ||||
|                 layer_counter++; | ||||
|             } | ||||
| 
 | ||||
|             stream << "    </layer_config_ranges>\n"; | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         const std::vector<sla::SupportPoint>& sla_support_points = object->sla_support_points; | ||||
|         if (!sla_support_points.empty()) { | ||||
|  | @ -941,7 +992,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) | |||
|                 stream << "           </coordinates>\n"; | ||||
|                 stream << "         </vertex>\n"; | ||||
|             } | ||||
|             num_vertices += its.vertices.size(); | ||||
|             num_vertices += (int)its.vertices.size(); | ||||
|         } | ||||
|         stream << "      </vertices>\n"; | ||||
|         for (size_t i_volume = 0; i_volume < object->volumes.size(); ++i_volume) { | ||||
|  | @ -952,14 +1003,14 @@ 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()) | ||||
|                 stream << "        <metadata type=\"slic3r.modifier\">1</metadata>\n"; | ||||
|             stream << "        <metadata type=\"slic3r.volume_type\">" << ModelVolume::type_to_string(volume->type()) << "</metadata>\n"; | ||||
| 			const indexed_triangle_set &its = volume->mesh().its; | ||||
|             for (size_t i = 0; i < (int)its.indices.size(); ++i) { | ||||
|             for (size_t i = 0; i < its.indices.size(); ++i) { | ||||
|                 stream << "        <triangle>\n"; | ||||
|                 for (int j = 0; j < 3; ++j) | ||||
|                 stream << "          <v" << j + 1 << ">" << its.indices[i][j] + vertices_offset << "</v" << j + 1 << ">\n"; | ||||
|  |  | |||
|  | @ -176,8 +176,7 @@ static bool obj_parseline(const char *line, ObjData &data) | |||
| 		EATWS(); | ||||
| 		if (*line == 0) | ||||
| 			return false; | ||||
| 		// number of vertices of this face
 | ||||
| 		int n = 0; | ||||
| 
 | ||||
| 		// current vertex to be parsed
 | ||||
| 		ObjVertex vertex; | ||||
| 		char *endptr = 0; | ||||
|  | @ -266,7 +265,6 @@ static bool obj_parseline(const char *line, ObjData &data) | |||
| 	{ | ||||
| 		// o [object name]
 | ||||
| 		EATWS(); | ||||
| 		const char *name = line; | ||||
| 		while (*line != ' ' && *line != '\t' && *line != 0) | ||||
| 			++ line; | ||||
| 		// copy name to line.
 | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ | |||
| #include "EdgeGrid.hpp" | ||||
| #include "Geometry.hpp" | ||||
| #include "GCode/PrintExtents.hpp" | ||||
| #include "GCode/WipeTowerPrusaMM.hpp" | ||||
| #include "GCode/WipeTower.hpp" | ||||
| #include "Utils.hpp" | ||||
| 
 | ||||
| #include <algorithm> | ||||
|  | @ -162,59 +162,115 @@ std::string Wipe::wipe(GCode &gcodegen, bool toolchange) | |||
|     return gcode; | ||||
| } | ||||
| 
 | ||||
| static inline Point wipe_tower_point_to_object_point(GCode &gcodegen, const WipeTower::xy &wipe_tower_pt) | ||||
| static inline Point wipe_tower_point_to_object_point(GCode &gcodegen, const Vec2f &wipe_tower_pt) | ||||
| { | ||||
|     return Point(scale_(wipe_tower_pt.x - gcodegen.origin()(0)), scale_(wipe_tower_pt.y - gcodegen.origin()(1))); | ||||
|     return Point(scale_(wipe_tower_pt.x() - gcodegen.origin()(0)), scale_(wipe_tower_pt.y() - gcodegen.origin()(1))); | ||||
| } | ||||
| 
 | ||||
| std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const | ||||
| { | ||||
|     if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool) | ||||
|         throw std::invalid_argument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect."); | ||||
| 
 | ||||
|     std::string gcode; | ||||
| 
 | ||||
|     // Toolchangeresult.gcode assumes the wipe tower corner is at the origin
 | ||||
|     // We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position
 | ||||
|     float alpha = m_wipe_tower_rotation/180.f * float(M_PI); | ||||
|     WipeTower::xy start_pos = tcr.start_pos; | ||||
|     WipeTower::xy end_pos = tcr.end_pos; | ||||
|     start_pos.rotate(alpha); | ||||
|     start_pos.translate(m_wipe_tower_pos); | ||||
|     end_pos.rotate(alpha); | ||||
|     end_pos.translate(m_wipe_tower_pos); | ||||
|     std::string tcr_rotated_gcode = rotate_wipe_tower_moves(tcr.gcode, tcr.start_pos, m_wipe_tower_pos, alpha); | ||||
|      | ||||
|     Vec2f start_pos = tcr.start_pos; | ||||
|     Vec2f end_pos = tcr.end_pos; | ||||
|     if (!tcr.priming) { | ||||
|         start_pos = Eigen::Rotation2Df(alpha) * start_pos; | ||||
|         start_pos += m_wipe_tower_pos; | ||||
|         end_pos = Eigen::Rotation2Df(alpha) * end_pos; | ||||
|         end_pos += m_wipe_tower_pos; | ||||
|     } | ||||
| 
 | ||||
|     Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos; | ||||
|     float wipe_tower_rotation = tcr.priming ? 0.f : alpha; | ||||
| 
 | ||||
|     std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation); | ||||
| 
 | ||||
|     // Disable linear advance for the wipe tower operations.
 | ||||
|     gcode += (gcodegen.config().gcode_flavor == gcfRepRap ? std::string("M572 D0 S0\n") : std::string("M900 K0\n")); | ||||
|     // Move over the wipe tower.
 | ||||
|     // Retract for a tool change, using the toolchange retract value and setting the priming extra length.
 | ||||
|     gcode += gcodegen.retract(true); | ||||
|     gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; | ||||
|     gcode += gcodegen.travel_to( | ||||
|         wipe_tower_point_to_object_point(gcodegen, start_pos), | ||||
|         erMixed, | ||||
|         "Travel to a Wipe Tower"); | ||||
|     gcode += gcodegen.unretract(); | ||||
| 
 | ||||
|     // Let the tool change be executed by the wipe tower class.
 | ||||
|     // Inform the G-code writer about the changes done behind its back.
 | ||||
|     gcode += tcr_rotated_gcode; | ||||
|     // Let the m_writer know the current extruder_id, but ignore the generated G-code.
 | ||||
| 	if (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id)) | ||||
|         gcodegen.writer().toolchange(new_extruder_id); | ||||
|     if (!tcr.priming) { | ||||
|         // Move over the wipe tower.
 | ||||
|         // Retract for a tool change, using the toolchange retract value and setting the priming extra length.
 | ||||
|         gcode += gcodegen.retract(true); | ||||
|         gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; | ||||
|         gcode += gcodegen.travel_to( | ||||
|             wipe_tower_point_to_object_point(gcodegen, start_pos), | ||||
|             erMixed, | ||||
|             "Travel to a Wipe Tower"); | ||||
|         gcode += gcodegen.unretract(); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     // Process the end filament gcode.
 | ||||
|     std::string end_filament_gcode_str; | ||||
|     if (gcodegen.writer().extruder() != nullptr) { | ||||
|         // Process the custom end_filament_gcode in case of single_extruder_multi_material.
 | ||||
|         unsigned int        old_extruder_id     = gcodegen.writer().extruder()->id(); | ||||
|         const std::string  &end_filament_gcode  = gcodegen.config().end_filament_gcode.get_at(old_extruder_id); | ||||
|         if (gcodegen.writer().extruder() != nullptr && ! end_filament_gcode.empty()) { | ||||
|             end_filament_gcode_str = gcodegen.placeholder_parser_process("end_filament_gcode", end_filament_gcode, old_extruder_id); | ||||
|             check_add_eol(end_filament_gcode_str); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Process the custom toolchange_gcode. If it is empty, provide a simple Tn command to change the filament.
 | ||||
|     // Otherwise, leave control to the user completely.
 | ||||
|     std::string toolchange_gcode_str; | ||||
|     if (true /*gcodegen.writer().extruder() != nullptr*/) { | ||||
|         const std::string& toolchange_gcode = gcodegen.config().toolchange_gcode.value; | ||||
|         if (!toolchange_gcode.empty()) { | ||||
|             DynamicConfig config; | ||||
|             int previous_extruder_id = gcodegen.writer().extruder() ? (int)gcodegen.writer().extruder()->id() : -1; | ||||
|             config.set_key_value("previous_extruder", new ConfigOptionInt(previous_extruder_id)); | ||||
|             config.set_key_value("next_extruder",     new ConfigOptionInt((int)new_extruder_id)); | ||||
|             config.set_key_value("layer_num",         new ConfigOptionInt(gcodegen.m_layer_index)); | ||||
|             config.set_key_value("layer_z",           new ConfigOptionFloat(tcr.print_z)); | ||||
|             toolchange_gcode_str = gcodegen.placeholder_parser_process("toolchange_gcode", toolchange_gcode, new_extruder_id, &config); | ||||
|             check_add_eol(toolchange_gcode_str); | ||||
|         } | ||||
| 
 | ||||
|         std::string toolchange_command; | ||||
|         if (tcr.priming || (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id))) | ||||
|             toolchange_command = gcodegen.writer().toolchange(new_extruder_id); | ||||
|         if (toolchange_gcode.empty()) | ||||
|             toolchange_gcode_str = toolchange_command; | ||||
|         else { | ||||
|             // We have informed the m_writer about the current extruder_id, we can ignore the generated G-code.
 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     gcodegen.placeholder_parser().set("current_extruder", new_extruder_id); | ||||
| 
 | ||||
|     // Always append the filament start G-code even if the extruder did not switch,
 | ||||
|     // because the wipe tower resets the linear advance and we want it to be re-enabled.
 | ||||
|     // Process the start filament gcode.
 | ||||
|     std::string start_filament_gcode_str; | ||||
|     const std::string &start_filament_gcode = gcodegen.config().start_filament_gcode.get_at(new_extruder_id); | ||||
|     if (! start_filament_gcode.empty()) { | ||||
|         // Process the start_filament_gcode for the active filament only.
 | ||||
|         DynamicConfig config; | ||||
|         config.set_key_value("filament_extruder_id", new ConfigOptionInt(new_extruder_id)); | ||||
|         gcode += gcodegen.placeholder_parser_process("start_filament_gcode", start_filament_gcode, new_extruder_id, &config); | ||||
|         check_add_eol(gcode); | ||||
|         start_filament_gcode_str = gcodegen.placeholder_parser_process("start_filament_gcode", start_filament_gcode, new_extruder_id, &config); | ||||
|         check_add_eol(start_filament_gcode_str); | ||||
|     } | ||||
| 
 | ||||
|     // Insert the end filament, toolchange, and start filament gcode into the generated gcode.
 | ||||
|     DynamicConfig config; | ||||
|     config.set_key_value("end_filament_gcode",   new ConfigOptionString(end_filament_gcode_str)); | ||||
|     config.set_key_value("toolchange_gcode",     new ConfigOptionString(toolchange_gcode_str)); | ||||
|     config.set_key_value("start_filament_gcode", new ConfigOptionString(start_filament_gcode_str)); | ||||
|     std::string tcr_gcode, tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_extruder_id, &config); | ||||
|     unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode); | ||||
|     gcode += tcr_gcode; | ||||
|     check_add_eol(toolchange_gcode_str); | ||||
| 
 | ||||
| 
 | ||||
|     // A phony move to the end position at the wipe tower.
 | ||||
|     gcodegen.writer().travel_to_xy(Vec2d(end_pos.x, end_pos.y)); | ||||
|     gcodegen.writer().travel_to_xy(end_pos.cast<double>()); | ||||
|     gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos)); | ||||
| 
 | ||||
|     // Prepare a future wipe.
 | ||||
|  | @ -224,8 +280,8 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T | |||
|         gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, end_pos)); | ||||
|         // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge.
 | ||||
|         gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen,  | ||||
|             WipeTower::xy((std::abs(m_left - end_pos.x) < std::abs(m_right - end_pos.x)) ? m_right : m_left, | ||||
|             end_pos.y))); | ||||
|             Vec2f((std::abs(m_left - end_pos.x()) < std::abs(m_right - end_pos.x())) ? m_right : m_left, | ||||
|             end_pos.y()))); | ||||
|     } | ||||
| 
 | ||||
|     // Let the planner know we are traveling between objects.
 | ||||
|  | @ -235,17 +291,21 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T | |||
| 
 | ||||
| // This function postprocesses gcode_original, rotates and moves all G1 extrusions and returns resulting gcode
 | ||||
| // Starting position has to be supplied explicitely (otherwise it would fail in case first G1 command only contained one coordinate)
 | ||||
| std::string WipeTowerIntegration::rotate_wipe_tower_moves(const std::string& gcode_original, const WipeTower::xy& start_pos, const WipeTower::xy& translation, float angle) const | ||||
| std::string WipeTowerIntegration::post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const | ||||
| { | ||||
|     std::istringstream gcode_str(gcode_original); | ||||
|     Vec2f extruder_offset = m_extruder_offsets[tcr.initial_tool].cast<float>(); | ||||
| 
 | ||||
|     std::istringstream gcode_str(tcr.gcode); | ||||
|     std::string gcode_out; | ||||
|     std::string line; | ||||
|     WipeTower::xy pos = start_pos; | ||||
|     WipeTower::xy transformed_pos; | ||||
|     WipeTower::xy old_pos(-1000.1f, -1000.1f); | ||||
|     Vec2f pos = tcr.start_pos; | ||||
|     Vec2f transformed_pos = pos; | ||||
|     Vec2f old_pos(-1000.1f, -1000.1f); | ||||
| 
 | ||||
|     while (gcode_str) { | ||||
|         std::getline(gcode_str, line);  // we read the gcode line by line
 | ||||
| 
 | ||||
|         // All G1 commands should be translated and rotated
 | ||||
|         if (line.find("G1 ") == 0) { | ||||
|             std::ostringstream line_out; | ||||
|             std::istringstream line_str(line); | ||||
|  | @ -253,31 +313,48 @@ std::string WipeTowerIntegration::rotate_wipe_tower_moves(const std::string& gco | |||
|             char ch = 0; | ||||
|             while (line_str >> ch) { | ||||
|                 if (ch == 'X') | ||||
|                     line_str >> pos.x; | ||||
|                     line_str >> pos.x(); | ||||
|                 else | ||||
|                     if (ch == 'Y') | ||||
|                         line_str >> pos.y; | ||||
|                         line_str >> pos.y(); | ||||
|                     else | ||||
|                         line_out << ch; | ||||
|             } | ||||
| 
 | ||||
|             transformed_pos = pos; | ||||
|             transformed_pos.rotate(angle); | ||||
|             transformed_pos.translate(translation); | ||||
|             transformed_pos = Eigen::Rotation2Df(angle) * transformed_pos; | ||||
|             transformed_pos += translation; | ||||
| 
 | ||||
|             if (transformed_pos != old_pos) { | ||||
|                 line = line_out.str(); | ||||
|                 char buf[2048] = "G1"; | ||||
|                 if (transformed_pos.x != old_pos.x) | ||||
|                     sprintf(buf + strlen(buf), " X%.3f", transformed_pos.x); | ||||
|                 if (transformed_pos.y != old_pos.y) | ||||
|                     sprintf(buf + strlen(buf), " Y%.3f", transformed_pos.y); | ||||
|                 std::ostringstream oss; | ||||
|                 oss << std::fixed << std::setprecision(3) << "G1 "; | ||||
|                 if (transformed_pos.x() != old_pos.x()) | ||||
|                     oss << " X" << transformed_pos.x() - extruder_offset.x(); | ||||
|                 if (transformed_pos.y() != old_pos.y()) | ||||
|                     oss << " Y" << transformed_pos.y() - extruder_offset.y(); | ||||
| 
 | ||||
|                 line.replace(line.find("G1 "), 3, buf); | ||||
|                 line.replace(line.find("G1 "), 3, oss.str()); | ||||
|                 old_pos = transformed_pos; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         gcode_out += line + "\n"; | ||||
| 
 | ||||
|         // If this was a toolchange command, we should change current extruder offset
 | ||||
|         if (line == "[toolchange_gcode]") { | ||||
|             extruder_offset = m_extruder_offsets[tcr.new_tool].cast<float>(); | ||||
| 
 | ||||
|             // If the extruder offset changed, add an extra move so everything is continuous
 | ||||
|             if (extruder_offset != m_extruder_offsets[tcr.initial_tool].cast<float>()) { | ||||
|                 std::ostringstream oss; | ||||
|                 oss << std::fixed << std::setprecision(3) | ||||
|                     << "G1 X" << transformed_pos.x() - extruder_offset.x() | ||||
|                     << " Y"   << transformed_pos.y() - extruder_offset.y() | ||||
|                     << "\n"; | ||||
|                 gcode_out += oss.str(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     return gcode_out; | ||||
| } | ||||
|  | @ -288,27 +365,36 @@ std::string WipeTowerIntegration::prime(GCode &gcodegen) | |||
|     assert(m_layer_idx == 0); | ||||
|     std::string gcode; | ||||
| 
 | ||||
|     if (&m_priming != nullptr && ! m_priming.extrusions.empty()) { | ||||
|     if (&m_priming != nullptr) { | ||||
|         // Disable linear advance for the wipe tower operations.
 | ||||
|         gcode += (gcodegen.config().gcode_flavor == gcfRepRap ? std::string("M572 D0 S0\n") : std::string("M900 K0\n")); | ||||
|         // Let the tool change be executed by the wipe tower class.
 | ||||
|         // Inform the G-code writer about the changes done behind its back.
 | ||||
|         gcode += m_priming.gcode; | ||||
|         // Let the m_writer know the current extruder_id, but ignore the generated G-code.
 | ||||
|         unsigned int current_extruder_id = m_priming.extrusions.back().tool; | ||||
|         gcodegen.writer().toolchange(current_extruder_id); | ||||
|         gcodegen.placeholder_parser().set("current_extruder", current_extruder_id); | ||||
|             //gcode += (gcodegen.config().gcode_flavor == gcfRepRap ? std::string("M572 D0 S0\n") : std::string("M900 K0\n"));
 | ||||
| 
 | ||||
|         for (const WipeTower::ToolChangeResult& tcr : m_priming) { | ||||
|             if (!tcr.extrusions.empty()) | ||||
|                 gcode += append_tcr(gcodegen, tcr, tcr.new_tool); | ||||
| 
 | ||||
| 
 | ||||
|             // Let the tool change be executed by the wipe tower class.
 | ||||
|             // Inform the G-code writer about the changes done behind its back.
 | ||||
|             //gcode += tcr.gcode;
 | ||||
|             // Let the m_writer know the current extruder_id, but ignore the generated G-code.
 | ||||
|       //      unsigned int current_extruder_id = tcr.extrusions.back().tool;
 | ||||
|       //      gcodegen.writer().toolchange(current_extruder_id);
 | ||||
|       //      gcodegen.placeholder_parser().set("current_extruder", current_extruder_id);
 | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         // A phony move to the end position at the wipe tower.
 | ||||
|         gcodegen.writer().travel_to_xy(Vec2d(m_priming.end_pos.x, m_priming.end_pos.y)); | ||||
|         gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, m_priming.end_pos)); | ||||
|        /* gcodegen.writer().travel_to_xy(Vec2d(m_priming.back().end_pos.x, m_priming.back().end_pos.y));
 | ||||
|         gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, m_priming.back().end_pos)); | ||||
|         // Prepare a future wipe.
 | ||||
|         gcodegen.m_wipe.path.points.clear(); | ||||
|         // Start the wipe at the current position.
 | ||||
|         gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, m_priming.end_pos)); | ||||
|         gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, m_priming.back().end_pos)); | ||||
|         // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge.
 | ||||
|         gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen,  | ||||
|             WipeTower::xy((std::abs(m_left - m_priming.end_pos.x) < std::abs(m_right - m_priming.end_pos.x)) ? m_right : m_left, | ||||
|             m_priming.end_pos.y))); | ||||
|         gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, | ||||
|             WipeTower::xy((std::abs(m_left - m_priming.back().end_pos.x) < std::abs(m_right - m_priming.back().end_pos.x)) ? m_right : m_left, | ||||
|             m_priming.back().end_pos.y)));*/ | ||||
|     } | ||||
|     return gcode; | ||||
| } | ||||
|  | @ -316,10 +402,10 @@ std::string WipeTowerIntegration::prime(GCode &gcodegen) | |||
| std::string WipeTowerIntegration::tool_change(GCode &gcodegen, int extruder_id, bool finish_layer) | ||||
| { | ||||
|     std::string gcode; | ||||
| 	assert(m_layer_idx >= 0 && m_layer_idx <= m_tool_changes.size()); | ||||
| 	assert(m_layer_idx >= 0 && size_t(m_layer_idx) <= m_tool_changes.size()); | ||||
|     if (! m_brim_done || gcodegen.writer().need_toolchange(extruder_id) || finish_layer) { | ||||
| 		if (m_layer_idx < m_tool_changes.size()) { | ||||
| 			assert(m_tool_change_idx < m_tool_changes[m_layer_idx].size()); | ||||
| 		if (m_layer_idx < (int)m_tool_changes.size()) { | ||||
| 			assert(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size()); | ||||
| 			gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id); | ||||
| 		} | ||||
|         m_brim_done = true; | ||||
|  | @ -586,6 +672,9 @@ void GCode::_do_export(Print &print, FILE *file) | |||
|     } | ||||
|     m_analyzer.set_extruder_offsets(extruder_offsets); | ||||
| 
 | ||||
|     // tell analyzer about the gcode flavor
 | ||||
|     m_analyzer.set_gcode_flavor(print.config().gcode_flavor); | ||||
| 
 | ||||
|     // resets analyzer's tracking data
 | ||||
|     m_last_mm3_per_mm = GCodeAnalyzer::Default_mm3_per_mm; | ||||
|     m_last_width = GCodeAnalyzer::Default_Width; | ||||
|  | @ -804,24 +893,16 @@ void GCode::_do_export(Print &print, FILE *file) | |||
| 
 | ||||
|     // Write the custom start G-code
 | ||||
|     _writeln(file, start_gcode); | ||||
|     // Process filament-specific gcode in extruder order.
 | ||||
|     if (print.config().single_extruder_multi_material) { | ||||
|         if (has_wipe_tower) { | ||||
|             // Wipe tower will control the extruder switching, it will call the start_filament_gcode.
 | ||||
|         } else { | ||||
|             // Only initialize the initial extruder.
 | ||||
| 
 | ||||
|     // Process filament-specific gcode.
 | ||||
|    /* if (has_wipe_tower) {
 | ||||
|         // Wipe tower will control the extruder switching, it will call the start_filament_gcode.
 | ||||
|     } else { | ||||
|             DynamicConfig config; | ||||
|             config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(initial_extruder_id))); | ||||
| 			_writeln(file, this->placeholder_parser_process("start_filament_gcode", print.config().start_filament_gcode.values[initial_extruder_id], initial_extruder_id, &config)); | ||||
|         } | ||||
|     } else { | ||||
|         DynamicConfig config; | ||||
|         for (const std::string &start_gcode : print.config().start_filament_gcode.values) { | ||||
|             int extruder_id = (unsigned int)(&start_gcode - &print.config().start_filament_gcode.values.front()); | ||||
|             config.set_key_value("filament_extruder_id", new ConfigOptionInt(extruder_id)); | ||||
|             _writeln(file, this->placeholder_parser_process("start_filament_gcode", start_gcode, extruder_id, &config)); | ||||
|         } | ||||
|             _writeln(file, this->placeholder_parser_process("start_filament_gcode", print.config().start_filament_gcode.values[initial_extruder_id], initial_extruder_id, &config)); | ||||
|     } | ||||
| */ | ||||
|     this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, true); | ||||
|     print.throw_if_canceled(); | ||||
| 
 | ||||
|  | @ -1057,6 +1138,10 @@ void GCode::_do_export(Print &print, FILE *file) | |||
|     print.m_print_statistics.clear(); | ||||
|     print.m_print_statistics.estimated_normal_print_time = m_normal_time_estimator.get_time_dhms(); | ||||
|     print.m_print_statistics.estimated_silent_print_time = m_silent_time_estimator_enabled ? m_silent_time_estimator.get_time_dhms() : "N/A"; | ||||
|     print.m_print_statistics.estimated_normal_color_print_times = m_normal_time_estimator.get_color_times_dhms(); | ||||
|     if (m_silent_time_estimator_enabled) | ||||
|         print.m_print_statistics.estimated_silent_color_print_times = m_silent_time_estimator.get_color_times_dhms(); | ||||
| 
 | ||||
|     std::vector<Extruder> extruders = m_writer.extruders(); | ||||
|     if (! extruders.empty()) { | ||||
|         std::pair<std::string, unsigned int> out_filament_used_mm ("; filament used [mm] = ", 0); | ||||
|  | @ -1253,7 +1338,7 @@ void GCode::_print_first_layer_extruder_temperatures(FILE *file, Print &print, c | |||
|         int temp = print.config().first_layer_temperature.get_at(first_printing_extruder_id); | ||||
|         if (temp_by_gcode >= 0 && temp_by_gcode < 1000) | ||||
|             temp = temp_by_gcode; | ||||
|         m_writer.set_temperature(temp_by_gcode, wait, first_printing_extruder_id); | ||||
|         m_writer.set_temperature(temp, wait, first_printing_extruder_id); | ||||
|     } else { | ||||
|         // Custom G-code does not set the extruder temperature. Do it now.
 | ||||
|         if (print.config().single_extruder_multi_material.value) { | ||||
|  | @ -1344,11 +1429,11 @@ void GCode::process_layer( | |||
|     // Check whether it is possible to apply the spiral vase logic for this layer.
 | ||||
|     // Just a reminder: A spiral vase mode is allowed for a single object, single material print only.
 | ||||
|     if (m_spiral_vase && layers.size() == 1 && support_layer == nullptr) { | ||||
|         bool enable = (layer.id() > 0 || print.config().brim_width.value == 0.) && (layer.id() >= print.config().skirt_height.value && ! print.has_infinite_skirt()); | ||||
|         bool enable = (layer.id() > 0 || print.config().brim_width.value == 0.) && (layer.id() >= (size_t)print.config().skirt_height.value && ! print.has_infinite_skirt()); | ||||
|         if (enable) { | ||||
|             for (const LayerRegion *layer_region : layer.regions()) | ||||
|                 if (layer_region->region()->config().bottom_solid_layers.value > layer.id() || | ||||
|                     layer_region->perimeters.items_count() > 1 || | ||||
|                 if (size_t(layer_region->region()->config().bottom_solid_layers.value) > layer.id() || | ||||
|                     layer_region->perimeters.items_count() > 1u || | ||||
|                     layer_region->fills.items_count() > 0) { | ||||
|                     enable = false; | ||||
|                     break; | ||||
|  | @ -1416,11 +1501,11 @@ void GCode::process_layer( | |||
|     bool extrude_skirt =  | ||||
| 		! print.skirt().entities.empty() && | ||||
|         // Not enough skirt layers printed yet.
 | ||||
|         (m_skirt_done.size() < print.config().skirt_height.value || print.has_infinite_skirt()) && | ||||
|         (m_skirt_done.size() < (size_t)print.config().skirt_height.value || print.has_infinite_skirt()) && | ||||
|         // This print_z has not been extruded yet
 | ||||
| 		(m_skirt_done.empty() ? 0. : m_skirt_done.back()) < print_z - EPSILON && | ||||
|         // and this layer is the 1st layer, or it is an object layer, or it is a raft layer.
 | ||||
|         (first_layer || object_layer != nullptr || support_layer->id() < m_config.raft_layers.value); | ||||
|         (first_layer || object_layer != nullptr || support_layer->id() < (size_t)m_config.raft_layers.value); | ||||
|     std::map<unsigned int, std::pair<size_t, size_t>> skirt_loops_per_extruder; | ||||
|     coordf_t                                          skirt_height = 0.; | ||||
|     if (extrude_skirt) { | ||||
|  | @ -1751,7 +1836,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 : { | ||||
|  | @ -2069,19 +2154,18 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou | |||
| 
 | ||||
|         // Retrieve the last start position for this object.
 | ||||
|         float last_pos_weight = 1.f; | ||||
|         switch (seam_position) { | ||||
|         case spAligned: | ||||
| 
 | ||||
|         if (seam_position == spAligned) { | ||||
|             // Seam is aligned to the seam at the preceding layer.
 | ||||
|             if (m_layer != NULL && m_seam_position.count(m_layer->object()) > 0) { | ||||
|                 last_pos = m_seam_position[m_layer->object()]; | ||||
|                 last_pos_weight = 1.f; | ||||
|             } | ||||
|             break; | ||||
|         case spRear: | ||||
|         } | ||||
|         else if (seam_position == spRear) { | ||||
|             last_pos = m_layer->object()->bounding_box().center(); | ||||
|             last_pos(1) += coord_t(3. * m_layer->object()->bounding_box().radius()); | ||||
|             last_pos_weight = 5.f; | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         // Insert a projection of last_pos into the polygon.
 | ||||
|  | @ -2090,7 +2174,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou | |||
|             Points::iterator it = project_point_to_polygon_and_insert(polygon, last_pos, 0.1 * nozzle_r); | ||||
|             last_pos_proj_idx = it - polygon.points.begin(); | ||||
|         } | ||||
|         Point last_pos_proj = polygon.points[last_pos_proj_idx]; | ||||
| 
 | ||||
|         // Parametrize the polygon by its length.
 | ||||
|         std::vector<float> lengths = polygon_parameter_by_length(polygon); | ||||
| 
 | ||||
|  | @ -2100,7 +2184,6 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou | |||
|         // No penalty for reflex points, slight penalty for convex points, high penalty for flat surfaces.
 | ||||
|         const float penaltyConvexVertex = 1.f; | ||||
|         const float penaltyFlatSurface  = 5.f; | ||||
|         const float penaltySeam         = 1.3f; | ||||
|         const float penaltyOverhangHalf = 10.f; | ||||
|         // Penalty for visible seams.
 | ||||
|         for (size_t i = 0; i < polygon.points.size(); ++ i) { | ||||
|  | @ -2145,10 +2228,14 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou | |||
|                 // Signed distance is positive outside the object, negative inside the object.
 | ||||
|                 // The point is considered at an overhang, if it is more than nozzle radius
 | ||||
|                 // outside of the lower layer contour.
 | ||||
|                 bool found = (*lower_layer_edge_grid)->signed_distance(p, search_r, dist); | ||||
|                 #ifdef NDEBUG // to suppress unused variable warning in release mode
 | ||||
|                     (*lower_layer_edge_grid)->signed_distance(p, search_r, dist); | ||||
|                 #else | ||||
|                     bool found = (*lower_layer_edge_grid)->signed_distance(p, search_r, dist); | ||||
|                 #endif | ||||
|                 // If the approximate Signed Distance Field was initialized over lower_layer_edge_grid,
 | ||||
|                 // then the signed distnace shall always be known.
 | ||||
|                 assert(found); | ||||
|                 assert(found);  | ||||
|                 penalties[i] += extrudate_overlap_penalty(float(nozzle_r), penaltyOverhangHalf, float(dist)); | ||||
|             } | ||||
|         } | ||||
|  | @ -2556,7 +2643,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, | |||
|             gcode += buf; | ||||
|         } | ||||
| 
 | ||||
|         if (m_last_mm3_per_mm != path.mm3_per_mm) | ||||
|         if (last_was_wipe_tower || (m_last_mm3_per_mm != path.mm3_per_mm)) | ||||
|         { | ||||
|             m_last_mm3_per_mm = path.mm3_per_mm; | ||||
| 
 | ||||
|  | @ -2724,7 +2811,17 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z) | |||
|     // if we are running a single-extruder setup, just set the extruder and return nothing
 | ||||
|     if (!m_writer.multiple_extruders) { | ||||
|         m_placeholder_parser.set("current_extruder", extruder_id); | ||||
|         return m_writer.toolchange(extruder_id); | ||||
|          | ||||
|         std::string gcode; | ||||
|         // Append the filament start G-code.
 | ||||
|         const std::string &start_filament_gcode = m_config.start_filament_gcode.get_at(extruder_id); | ||||
|         if (! start_filament_gcode.empty()) { | ||||
|             // Process the start_filament_gcode for the filament.
 | ||||
|             gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id); | ||||
|             check_add_eol(gcode); | ||||
|         } | ||||
|         gcode += m_writer.toolchange(extruder_id); | ||||
|         return gcode; | ||||
|     } | ||||
|      | ||||
|     // prepend retraction on the current extruder
 | ||||
|  | @ -2734,38 +2831,57 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z) | |||
|     m_wipe.reset_path(); | ||||
|      | ||||
|     if (m_writer.extruder() != nullptr) { | ||||
|         // Process the custom end_filament_gcode in case of single_extruder_multi_material.
 | ||||
|         // Process the custom end_filament_gcode. set_extruder() is only called if there is no wipe tower
 | ||||
|         // so it should not be injected twice.
 | ||||
|         unsigned int        old_extruder_id     = m_writer.extruder()->id(); | ||||
|         const std::string  &end_filament_gcode  = m_config.end_filament_gcode.get_at(old_extruder_id); | ||||
|         if (m_config.single_extruder_multi_material && ! end_filament_gcode.empty()) { | ||||
|         if (! end_filament_gcode.empty()) { | ||||
|             gcode += placeholder_parser_process("end_filament_gcode", end_filament_gcode, old_extruder_id); | ||||
|             check_add_eol(gcode); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     m_placeholder_parser.set("current_extruder", extruder_id); | ||||
| 
 | ||||
|     if (m_writer.extruder() != nullptr && ! m_config.toolchange_gcode.value.empty()) { | ||||
|         // Process the custom toolchange_gcode.
 | ||||
|         DynamicConfig config; | ||||
|         config.set_key_value("previous_extruder", new ConfigOptionInt((int)m_writer.extruder()->id())); | ||||
|         config.set_key_value("next_extruder",     new ConfigOptionInt((int)extruder_id)); | ||||
|         config.set_key_value("layer_num",         new ConfigOptionInt(m_layer_index)); | ||||
|         config.set_key_value("layer_z",           new ConfigOptionFloat(print_z)); | ||||
|         gcode += placeholder_parser_process("toolchange_gcode", m_config.toolchange_gcode.value, extruder_id, &config); | ||||
|         check_add_eol(gcode); | ||||
|     } | ||||
|      | ||||
|     // If ooze prevention is enabled, park current extruder in the nearest
 | ||||
|     // standby point and set it to the standby temperature.
 | ||||
|     if (m_ooze_prevention.enable && m_writer.extruder() != nullptr) | ||||
|         gcode += m_ooze_prevention.pre_toolchange(*this); | ||||
|     // Append the toolchange command.
 | ||||
|     gcode += m_writer.toolchange(extruder_id); | ||||
|     // Append the filament start G-code for single_extruder_multi_material.
 | ||||
| 
 | ||||
|     const std::string& toolchange_gcode = m_config.toolchange_gcode.value; | ||||
| 
 | ||||
|     // Process the custom toolchange_gcode. If it is empty, insert just a Tn command.
 | ||||
|     if (!toolchange_gcode.empty()) { | ||||
|         DynamicConfig config; | ||||
|         config.set_key_value("previous_extruder", new ConfigOptionInt((int)(m_writer.extruder() != nullptr ? m_writer.extruder()->id() : -1 ))); | ||||
|         config.set_key_value("next_extruder",     new ConfigOptionInt((int)extruder_id)); | ||||
|         config.set_key_value("layer_num",         new ConfigOptionInt(m_layer_index)); | ||||
|         config.set_key_value("layer_z",           new ConfigOptionFloat(print_z)); | ||||
|         gcode += placeholder_parser_process("toolchange_gcode", toolchange_gcode, extruder_id, &config); | ||||
|         check_add_eol(gcode); | ||||
|     } | ||||
| 
 | ||||
|     // We inform the writer about what is happening, but we may not use the resulting gcode.
 | ||||
|     std::string toolchange_command = m_writer.toolchange(extruder_id); | ||||
|     if (toolchange_gcode.empty()) | ||||
|         gcode += toolchange_command; | ||||
|     else { | ||||
|         // user provided his own toolchange gcode, no need to do anything
 | ||||
|     } | ||||
| 
 | ||||
|     // Set the temperature if the wipe tower didn't (not needed for non-single extruder MM)
 | ||||
|     if (m_config.single_extruder_multi_material && !m_config.wipe_tower) { | ||||
|         int temp = (m_layer_index == 0 ? m_config.first_layer_temperature.get_at(extruder_id) : | ||||
|                                          m_config.temperature.get_at(extruder_id)); | ||||
| 
 | ||||
|         gcode += m_writer.set_temperature(temp, false); | ||||
|     } | ||||
| 
 | ||||
|     m_placeholder_parser.set("current_extruder", extruder_id); | ||||
| 
 | ||||
|     // Append the filament start G-code.
 | ||||
|     const std::string &start_filament_gcode = m_config.start_filament_gcode.get_at(extruder_id); | ||||
|     if (m_config.single_extruder_multi_material && ! start_filament_gcode.empty()) { | ||||
|         // Process the start_filament_gcode for the active filament only.
 | ||||
|     if (! start_filament_gcode.empty()) { | ||||
|         // Process the start_filament_gcode for the new filament.
 | ||||
|         gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id); | ||||
|         check_add_eol(gcode); | ||||
|     } | ||||
|  |  | |||
|  | @ -83,13 +83,14 @@ class WipeTowerIntegration { | |||
| public: | ||||
|     WipeTowerIntegration( | ||||
|         const PrintConfig                                           &print_config, | ||||
|         const WipeTower::ToolChangeResult                           &priming, | ||||
|         const std::vector<WipeTower::ToolChangeResult>              &priming, | ||||
|         const std::vector<std::vector<WipeTower::ToolChangeResult>> &tool_changes, | ||||
|         const WipeTower::ToolChangeResult                           &final_purge) : | ||||
|         m_left(/*float(print_config.wipe_tower_x.value)*/ 0.f), | ||||
|         m_right(float(/*print_config.wipe_tower_x.value +*/ print_config.wipe_tower_width.value)), | ||||
|         m_wipe_tower_pos(float(print_config.wipe_tower_x.value), float(print_config.wipe_tower_y.value)), | ||||
|         m_wipe_tower_rotation(float(print_config.wipe_tower_rotation_angle)), | ||||
|         m_extruder_offsets(print_config.extruder_offset.values), | ||||
|         m_priming(priming), | ||||
|         m_tool_changes(tool_changes), | ||||
|         m_final_purge(final_purge), | ||||
|  | @ -107,16 +108,18 @@ private: | |||
|     WipeTowerIntegration& operator=(const WipeTowerIntegration&); | ||||
|     std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const; | ||||
| 
 | ||||
|     // Postprocesses gcode: rotates and moves all G1 extrusions and returns result
 | ||||
|     std::string rotate_wipe_tower_moves(const std::string& gcode_original, const WipeTower::xy& start_pos, const WipeTower::xy& translation, float angle) const; | ||||
|     // Postprocesses gcode: rotates and moves G1 extrusions and returns result
 | ||||
|     std::string post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const; | ||||
| 
 | ||||
|     // Left / right edges of the wipe tower, for the planning of wipe moves.
 | ||||
|     const float                                                  m_left; | ||||
|     const float                                                  m_right; | ||||
|     const WipeTower::xy                                          m_wipe_tower_pos; | ||||
|     const Vec2f                                                  m_wipe_tower_pos; | ||||
|     const float                                                  m_wipe_tower_rotation; | ||||
|     const std::vector<Vec2d>                                     m_extruder_offsets; | ||||
| 
 | ||||
|     // Reference to cached values at the Printer class.
 | ||||
|     const WipeTower::ToolChangeResult                           &m_priming; | ||||
|     const std::vector<WipeTower::ToolChangeResult>              &m_priming; | ||||
|     const std::vector<std::vector<WipeTower::ToolChangeResult>> &m_tool_changes; | ||||
|     const WipeTower::ToolChangeResult                           &m_final_purge; | ||||
|     // Current layer index.
 | ||||
|  |  | |||
|  | @ -106,6 +106,11 @@ void GCodeAnalyzer::set_extruder_offsets(const GCodeAnalyzer::ExtruderOffsetsMap | |||
|     m_extruder_offsets = extruder_offsets; | ||||
| } | ||||
| 
 | ||||
| void GCodeAnalyzer::set_gcode_flavor(const GCodeFlavor& flavor) | ||||
| { | ||||
|     m_gcode_flavor = flavor; | ||||
| } | ||||
| 
 | ||||
| void GCodeAnalyzer::reset() | ||||
| { | ||||
|     _set_units(Millimeters); | ||||
|  | @ -249,6 +254,14 @@ void GCodeAnalyzer::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLi | |||
|                         _processM83(line); | ||||
|                         break; | ||||
|                     } | ||||
|                 case 108: | ||||
|                 case 135: | ||||
|                     { | ||||
|                         // these are used by MakerWare and Sailfish firmwares
 | ||||
|                         // for tool changing - we can process it in one place
 | ||||
|                         _processM108orM135(line); | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 break; | ||||
|  | @ -426,9 +439,27 @@ void GCodeAnalyzer::_processM600(const GCodeReader::GCodeLine& line) | |||
|     _set_cp_color_id(m_state.cur_cp_color_id); | ||||
| } | ||||
| 
 | ||||
| void GCodeAnalyzer::_processT(const GCodeReader::GCodeLine& line) | ||||
| void GCodeAnalyzer::_processM108orM135(const GCodeReader::GCodeLine& line) | ||||
| { | ||||
|     // These M-codes are used by MakerWare and Sailfish to change active tool.
 | ||||
|     // They have to be processed otherwise toolchanges will be unrecognised
 | ||||
|     // by the analyzer - see https://github.com/prusa3d/PrusaSlicer/issues/2566
 | ||||
| 
 | ||||
|     size_t code = ::atoi(&(line.cmd()[1])); | ||||
|     if ((code == 108 && m_gcode_flavor == gcfSailfish) | ||||
|      || (code == 135 && m_gcode_flavor == gcfMakerWare)) { | ||||
| 
 | ||||
|         std::string cmd = line.raw(); | ||||
|         size_t T_pos = cmd.find("T"); | ||||
|         if (T_pos != std::string::npos) { | ||||
|             cmd = cmd.substr(T_pos); | ||||
|             _processT(cmd); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GCodeAnalyzer::_processT(const std::string& cmd) | ||||
| { | ||||
|     std::string cmd = line.cmd(); | ||||
|     if (cmd.length() > 1) | ||||
|     { | ||||
|         unsigned int id = (unsigned int)::strtol(cmd.substr(1).c_str(), nullptr, 10); | ||||
|  | @ -442,6 +473,11 @@ void GCodeAnalyzer::_processT(const GCodeReader::GCodeLine& line) | |||
|     } | ||||
| } | ||||
| 
 | ||||
| void GCodeAnalyzer::_processT(const GCodeReader::GCodeLine& line) | ||||
| { | ||||
|     _processT(line.cmd()); | ||||
| } | ||||
| 
 | ||||
| bool GCodeAnalyzer::_process_tags(const GCodeReader::GCodeLine& line) | ||||
| { | ||||
|     std::string comment = line.comment(); | ||||
|  |  | |||
|  | @ -106,6 +106,7 @@ private: | |||
|     GCodeReader m_parser; | ||||
|     TypeToMovesMap m_moves_map; | ||||
|     ExtruderOffsetsMap m_extruder_offsets; | ||||
|     GCodeFlavor m_gcode_flavor; | ||||
| 
 | ||||
|     // The output of process_layer()
 | ||||
|     std::string m_process_output; | ||||
|  | @ -115,6 +116,8 @@ public: | |||
| 
 | ||||
|     void set_extruder_offsets(const ExtruderOffsetsMap& extruder_offsets); | ||||
| 
 | ||||
|     void set_gcode_flavor(const GCodeFlavor& flavor); | ||||
| 
 | ||||
|     // Reinitialize the analyzer
 | ||||
|     void reset(); | ||||
| 
 | ||||
|  | @ -164,10 +167,14 @@ private: | |||
|     // Set extruder to relative mode
 | ||||
|     void _processM83(const GCodeReader::GCodeLine& line); | ||||
| 
 | ||||
|     // Set tool (MakerWare and Sailfish flavor)
 | ||||
|     void _processM108orM135(const GCodeReader::GCodeLine& line); | ||||
| 
 | ||||
|     // Set color change
 | ||||
|     void _processM600(const GCodeReader::GCodeLine& line); | ||||
| 
 | ||||
|     // Processes T line (Select Tool)
 | ||||
|     void _processT(const std::string& command); | ||||
|     void _processT(const GCodeReader::GCodeLine& line); | ||||
| 
 | ||||
|     // Processes the tags
 | ||||
|  |  | |||
|  | @ -672,7 +672,7 @@ std::string CoolingBuffer::apply_layer_cooldown( | |||
| #define EXTRUDER_CONFIG(OPT) config.OPT.get_at(m_current_extruder) | ||||
|         int min_fan_speed = EXTRUDER_CONFIG(min_fan_speed); | ||||
|         int fan_speed_new = EXTRUDER_CONFIG(fan_always_on) ? min_fan_speed : 0; | ||||
|         if (layer_id >= EXTRUDER_CONFIG(disable_fan_first_layers)) { | ||||
|         if (layer_id >= (size_t)EXTRUDER_CONFIG(disable_fan_first_layers)) { | ||||
|             int   max_fan_speed             = EXTRUDER_CONFIG(max_fan_speed); | ||||
|             float slowdown_below_layer_time = float(EXTRUDER_CONFIG(slowdown_below_layer_time)); | ||||
|             float fan_below_layer_time      = float(EXTRUDER_CONFIG(fan_below_layer_time)); | ||||
|  |  | |||
|  | @ -404,6 +404,8 @@ std::string GCodePreviewData::get_legend_title() const | |||
|         return L("Tool"); | ||||
|     case Extrusion::ColorPrint: | ||||
|         return L("Color Print"); | ||||
|     case Extrusion::Num_View_Types: | ||||
|         break; // just to supress warning about non-handled value
 | ||||
|     } | ||||
| 
 | ||||
|     return ""; | ||||
|  | @ -466,7 +468,7 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std:: | |||
|         } | ||||
|     case Extrusion::Tool: | ||||
|         { | ||||
|             unsigned int tools_colors_count = tool_colors.size() / 4; | ||||
|             unsigned int tools_colors_count = (unsigned int)tool_colors.size() / 4; | ||||
|             items.reserve(tools_colors_count); | ||||
|             for (unsigned int i = 0; i < tools_colors_count; ++i) | ||||
|             { | ||||
|  | @ -491,20 +493,25 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std:: | |||
|                     items.emplace_back(Slic3r::I18N::translate(L("Default print color")), color); | ||||
|                     break; | ||||
|                 } | ||||
| 
 | ||||
|                 std::string id_str = std::to_string(i + 1) + ": "; | ||||
| 
 | ||||
|                 if (i == 0) { | ||||
|                     items.emplace_back((boost::format(Slic3r::I18N::translate(L("up to %.2f mm"))) % cp_values[0].first).str(), color); | ||||
|                     items.emplace_back(id_str + (boost::format(Slic3r::I18N::translate(L("up to %.2f mm"))) % cp_values[0].first).str(), color); | ||||
|                     break; | ||||
|                 } | ||||
|                 if (i == color_print_cnt) { | ||||
|                     items.emplace_back((boost::format(Slic3r::I18N::translate(L("above %.2f mm"))) % cp_values[i-1].second).str(), color); | ||||
|                     items.emplace_back(id_str + (boost::format(Slic3r::I18N::translate(L("above %.2f mm"))) % cp_values[i - 1].second).str(), color); | ||||
|                     continue; | ||||
|                 } | ||||
| 
 | ||||
| //                 items.emplace_back((boost::format(Slic3r::I18N::translate(L("%.2f - %.2f mm"))) %  cp_values[i-1] % cp_values[i]).str(), color);
 | ||||
|                 items.emplace_back((boost::format(Slic3r::I18N::translate(L("%.2f - %.2f mm"))) %  cp_values[i-1].second % cp_values[i].first).str(), color); | ||||
|                 items.emplace_back(id_str + (boost::format(Slic3r::I18N::translate(L("%.2f - %.2f mm"))) % cp_values[i - 1].second% cp_values[i].first).str(), color); | ||||
|             } | ||||
|             break; | ||||
|         } | ||||
|     case Extrusion::Num_View_Types: | ||||
|         break; // just to supress warning about non-handled value
 | ||||
|     } | ||||
| 
 | ||||
|     return items; | ||||
|  |  | |||
|  | @ -149,8 +149,8 @@ BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_ | |||
|                 const WipeTower::Extrusion &e = tcr.extrusions[i]; | ||||
|                 if (e.width > 0) { | ||||
|                     Vec2d delta = 0.5 * Vec2d(e.width, e.width); | ||||
|                     Vec2d p1 = trafo * Vec2d((&e - 1)->pos.x, (&e - 1)->pos.y); | ||||
|                     Vec2d p2 = trafo * Vec2d(e.pos.x, e.pos.y); | ||||
|                     Vec2d p1 = trafo * (&e - 1)->pos.cast<double>(); | ||||
|                     Vec2d p2 = trafo * e.pos.cast<double>(); | ||||
|                     bbox.merge(p1.cwiseMin(p2) - delta); | ||||
|                     bbox.merge(p1.cwiseMax(p2) + delta); | ||||
|                 } | ||||
|  | @ -165,18 +165,19 @@ BoundingBoxf get_wipe_tower_priming_extrusions_extents(const Print &print) | |||
| { | ||||
|     BoundingBoxf bbox; | ||||
|     if (print.wipe_tower_data().priming != nullptr) { | ||||
|         const WipeTower::ToolChangeResult &tcr = *print.wipe_tower_data().priming; | ||||
|         for (size_t i = 1; i < tcr.extrusions.size(); ++ i) { | ||||
|             const WipeTower::Extrusion &e = tcr.extrusions[i]; | ||||
|             if (e.width > 0) { | ||||
|                 Vec2d  p1((&e - 1)->pos.x, (&e - 1)->pos.y); | ||||
|                 Vec2d  p2(e.pos.x, e.pos.y); | ||||
|                 bbox.merge(p1); | ||||
|                 coordf_t radius = 0.5 * e.width; | ||||
|                 bbox.min(0) = std::min(bbox.min(0), std::min(p1(0), p2(0)) - radius); | ||||
|                 bbox.min(1) = std::min(bbox.min(1), std::min(p1(1), p2(1)) - radius); | ||||
|                 bbox.max(0) = std::max(bbox.max(0), std::max(p1(0), p2(0)) + radius); | ||||
|                 bbox.max(1) = std::max(bbox.max(1), std::max(p1(1), p2(1)) + radius); | ||||
|         for (const WipeTower::ToolChangeResult &tcr : *print.wipe_tower_data().priming) { | ||||
|             for (size_t i = 1; i < tcr.extrusions.size(); ++ i) { | ||||
|                 const WipeTower::Extrusion &e = tcr.extrusions[i]; | ||||
|                 if (e.width > 0) { | ||||
|                     const Vec2d& p1 = (&e - 1)->pos.cast<double>(); | ||||
|                     const Vec2d& p2 = e.pos.cast<double>(); | ||||
|                     bbox.merge(p1); | ||||
|                     coordf_t radius = 0.5 * e.width; | ||||
|                     bbox.min(0) = std::min(bbox.min(0), std::min(p1(0), p2(0)) - radius); | ||||
|                     bbox.min(1) = std::min(bbox.min(1), std::min(p1(1), p2(1)) - radius); | ||||
|                     bbox.max(0) = std::max(bbox.max(0), std::max(p1(0), p2(0)) + radius); | ||||
|                     bbox.max(1) = std::max(bbox.max(1), std::max(p1(1), p2(1)) + radius); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  |  | |||
|  | @ -24,7 +24,7 @@ std::string SpiralVase::process_layer(const std::string &gcode) | |||
|     // Get total XY length for this layer by summing all extrusion moves.
 | ||||
|     float total_layer_length = 0; | ||||
|     float layer_height = 0; | ||||
|     float z; | ||||
|     float z = 0.f; | ||||
|     bool set_z = false; | ||||
|      | ||||
|     { | ||||
|  |  | |||
|  | @ -324,9 +324,8 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_ | |||
| 						m_layer_tools[j].has_wipe_tower = true; | ||||
| 					} else { | ||||
| 						LayerTools <_extra = *m_layer_tools.insert(m_layer_tools.begin() + j, lt_new); | ||||
|                         LayerTools <_prev  = m_layer_tools[j - 1]; | ||||
|                         LayerTools <_next  = m_layer_tools[j + 1]; | ||||
|                         assert(! lt_prev.extruders.empty() && ! lt_next.extruders.empty()); | ||||
|                         assert(! m_layer_tools[j - 1].extruders.empty() && ! lt_next.extruders.empty()); | ||||
|                         // FIXME: Following assert tripped when running combine_infill.t. I decided to comment it out for now.
 | ||||
|                         // If it is a bug, it's likely not critical, because this code is unchanged for a long time. It might
 | ||||
|                         // still be worth looking into it more and decide if it is a bug or an obsolete assert.
 | ||||
|  | @ -495,9 +494,6 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int | |||
|                         if (!is_overriddable(*fill, print.config(), *object, region)) | ||||
|                             continue; | ||||
| 
 | ||||
|                         // What extruder would this normally be printed with?
 | ||||
|                         unsigned int correct_extruder = Print::get_extruder(*fill, region); | ||||
| 
 | ||||
|                         if (volume_to_wipe<=0) | ||||
|                             continue; | ||||
| 
 | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -1,95 +1,30 @@ | |||
| #ifndef slic3r_WipeTower_hpp_ | ||||
| #define slic3r_WipeTower_hpp_ | ||||
| #ifndef WipeTower_ | ||||
| #define WipeTower_ | ||||
| 
 | ||||
| #include <math.h> | ||||
| #include <utility> | ||||
| #include <cmath> | ||||
| #include <string> | ||||
| #include <vector> | ||||
| #include <sstream> | ||||
| #include <utility> | ||||
| #include <algorithm> | ||||
| 
 | ||||
| #include "libslic3r/PrintConfig.hpp" | ||||
| 
 | ||||
| 
 | ||||
| namespace Slic3r | ||||
| { | ||||
| 
 | ||||
| // A pure virtual WipeTower definition.
 | ||||
| class WipeTowerWriter; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| class WipeTower | ||||
| { | ||||
| public: | ||||
| 	// Internal point class, to make the wipe tower independent from other slic3r modules.
 | ||||
| 	// This is important for Prusa Research as we want to build the wipe tower post-processor independently from slic3r.
 | ||||
| 	struct xy | ||||
|     struct Extrusion | ||||
| 	{ | ||||
| 		xy(float x = 0.f, float y = 0.f) : x(x), y(y) {} | ||||
| 		xy(const xy& pos,float xp,float yp) : x(pos.x+xp), y(pos.y+yp) {} | ||||
| 		xy  operator+(const xy &rhs) const { xy out(*this); out.x += rhs.x; out.y += rhs.y; return out; } | ||||
| 		xy  operator-(const xy &rhs) const { xy out(*this); out.x -= rhs.x; out.y -= rhs.y; return out; } | ||||
| 		xy& operator+=(const xy &rhs) { x += rhs.x; y += rhs.y; return *this; } | ||||
| 		xy& operator-=(const xy &rhs) { x -= rhs.x; y -= rhs.y; return *this; } | ||||
| 		bool operator==(const xy &rhs) const { return x == rhs.x && y == rhs.y; } | ||||
| 		bool operator!=(const xy &rhs) const { return x != rhs.x || y != rhs.y; } | ||||
| 		 | ||||
| 		// Rotate the point around center of the wipe tower about given angle (in degrees)
 | ||||
| 		xy rotate(float width, float depth, float angle) const { | ||||
| 			xy out(0,0); | ||||
| 			float temp_x = x - width / 2.f; | ||||
| 			float temp_y = y - depth / 2.f; | ||||
| 			angle *= float(M_PI/180.); | ||||
| 			out.x += temp_x * cos(angle)  -  temp_y * sin(angle) + width / 2.f; | ||||
| 			out.y += temp_x * sin(angle)  +  temp_y * cos(angle) + depth / 2.f; | ||||
| 			return out; | ||||
| 		} | ||||
|          | ||||
|         // Rotate the point around origin about given angle in degrees
 | ||||
|         void rotate(float angle) { | ||||
|             float temp_x = x * cos(angle)  -  y * sin(angle); | ||||
| 			y = x * sin(angle)  +  y * cos(angle); | ||||
|             x = temp_x; | ||||
| 		} | ||||
| 
 | ||||
|         void translate(const xy& vect) { | ||||
|             x += vect.x; | ||||
|             y += vect.y; | ||||
|         } | ||||
| 
 | ||||
| 		float x; | ||||
| 		float y; | ||||
| 	}; | ||||
| 
 | ||||
| 	WipeTower() {} | ||||
| 	virtual ~WipeTower() {} | ||||
| 
 | ||||
| 	// Return the wipe tower position.
 | ||||
| 	virtual const xy& position() const = 0; | ||||
| 
 | ||||
| 	// Return the wipe tower width.
 | ||||
| 	virtual float     width() const = 0; | ||||
| 
 | ||||
| 	// The wipe tower is finished, there should be no more tool changes or wipe tower prints.
 | ||||
| 	virtual bool 	  finished() const = 0; | ||||
| 
 | ||||
| 	// Switch to a next layer.
 | ||||
| 	virtual void 	  set_layer( | ||||
| 		// Print height of this layer.
 | ||||
| 		float  print_z, | ||||
| 		// Layer height, used to calculate extrusion the rate. 
 | ||||
| 		float  layer_height,  | ||||
| 		// Maximum number of tool changes on this layer or the layers below.
 | ||||
| 		size_t max_tool_changes,  | ||||
| 		// Is this the first layer of the print? In that case print the brim first.
 | ||||
| 		bool   is_first_layer, | ||||
| 		// Is this the last layer of the wipe tower?
 | ||||
| 		bool   is_last_layer) = 0; | ||||
| 
 | ||||
| 	enum Purpose { | ||||
| 		PURPOSE_MOVE_TO_TOWER, | ||||
| 		PURPOSE_EXTRUDE, | ||||
| 		PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE, | ||||
| 	}; | ||||
| 
 | ||||
| 	// Extrusion path of the wipe tower, for 3D preview of the generated tool paths.
 | ||||
| 	struct Extrusion | ||||
| 	{ | ||||
| 		Extrusion(const xy &pos, float width, unsigned int tool) : pos(pos), width(width), tool(tool) {} | ||||
| 		Extrusion(const Vec2f &pos, float width, unsigned int tool) : pos(pos), width(width), tool(tool) {} | ||||
| 		// End position of this extrusion.
 | ||||
| 		xy				pos; | ||||
| 		Vec2f				pos; | ||||
| 		// Width of a squished extrusion, corrected for the roundings of the squished extrusions.
 | ||||
| 		// This is left zero if it is a travel move.
 | ||||
| 		float 			width; | ||||
|  | @ -108,10 +43,10 @@ public: | |||
| 		std::vector<Extrusion> 	extrusions; | ||||
| 		// Initial position, at which the wipe tower starts its action.
 | ||||
| 		// At this position the extruder is loaded and there is no Z-hop applied.
 | ||||
| 		xy						start_pos; | ||||
| 		Vec2f						start_pos; | ||||
| 		// Last point, at which the normal G-code generator of Slic3r shall continue.
 | ||||
| 		// At this position the extruder is loaded and there is no Z-hop applied.
 | ||||
| 		xy						end_pos; | ||||
| 		Vec2f						end_pos; | ||||
| 		// Time elapsed over this tool change.
 | ||||
| 		// This is useful not only for the print time estimation, but also for the control of layer cooling.
 | ||||
| 		float  				    elapsed_time; | ||||
|  | @ -119,50 +54,377 @@ public: | |||
|         // Is this a priming extrusion? (If so, the wipe tower rotation & translation will not be applied later)
 | ||||
|         bool                    priming; | ||||
| 
 | ||||
|         // Initial tool
 | ||||
|         int initial_tool; | ||||
| 
 | ||||
|         // New tool
 | ||||
|         int new_tool; | ||||
| 
 | ||||
| 		// Sum the total length of the extrusion.
 | ||||
| 		float total_extrusion_length_in_plane() { | ||||
| 			float e_length = 0.f; | ||||
| 			for (size_t i = 1; i < this->extrusions.size(); ++ i) { | ||||
| 				const Extrusion &e = this->extrusions[i]; | ||||
| 				if (e.width > 0) { | ||||
| 					xy v = e.pos - (&e - 1)->pos; | ||||
| 					e_length += sqrt(v.x*v.x+v.y*v.y); | ||||
| 					Vec2f v = e.pos - (&e - 1)->pos; | ||||
| 					e_length += v.norm(); | ||||
| 				} | ||||
| 			} | ||||
| 			return e_length; | ||||
| 		} | ||||
| 	}; | ||||
| 
 | ||||
| 	// x			-- x coordinates of wipe tower in mm ( left bottom corner )
 | ||||
| 	// y			-- y coordinates of wipe tower in mm ( left bottom corner )
 | ||||
| 	// width		-- width of wipe tower in mm ( default 60 mm - leave as it is )
 | ||||
| 	// wipe_area	-- space available for one toolchange in mm
 | ||||
| 	WipeTower(bool semm, float x, float y, float width, float rotation_angle, float cooling_tube_retraction, | ||||
|               float cooling_tube_length, float parking_pos_retraction, float extra_loading_move,  | ||||
|               float bridging, bool set_extruder_trimpot, GCodeFlavor flavor, | ||||
|               const std::vector<std::vector<float>>& wiping_matrix, unsigned int initial_tool) : | ||||
|         m_semm(semm), | ||||
|         m_wipe_tower_pos(x, y), | ||||
| 		m_wipe_tower_width(width), | ||||
| 		m_wipe_tower_rotation_angle(rotation_angle), | ||||
| 		m_y_shift(0.f), | ||||
| 		m_z_pos(0.f), | ||||
| 		m_is_first_layer(false), | ||||
|         m_gcode_flavor(flavor), | ||||
|         m_bridging(bridging), | ||||
|         m_current_tool(initial_tool), | ||||
|         wipe_volumes(wiping_matrix) | ||||
|         { | ||||
|             // If this is a single extruder MM printer, we will use all the SE-specific config values.
 | ||||
|             // Otherwise, the defaults will be used to turn off the SE stuff.
 | ||||
|             if (m_semm) { | ||||
|                 m_cooling_tube_retraction = cooling_tube_retraction; | ||||
|                 m_cooling_tube_length = cooling_tube_length; | ||||
|                 m_parking_pos_retraction = parking_pos_retraction; | ||||
|                 m_extra_loading_move = extra_loading_move; | ||||
|                 m_set_extruder_trimpot = set_extruder_trimpot; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| 	virtual ~WipeTower() {} | ||||
| 
 | ||||
| 
 | ||||
| 	// Set the extruder properties.
 | ||||
| 	void set_extruder(size_t idx, std::string material, int temp, int first_layer_temp, float loading_speed, float loading_speed_start, | ||||
|                       float unloading_speed, float unloading_speed_start, float delay, int cooling_moves, | ||||
|                       float cooling_initial_speed, float cooling_final_speed, std::string ramming_parameters, float max_volumetric_speed, float nozzle_diameter) | ||||
| 	{ | ||||
|         //while (m_filpar.size() < idx+1)   // makes sure the required element is in the vector
 | ||||
|         m_filpar.push_back(FilamentParameters()); | ||||
| 
 | ||||
|         m_filpar[idx].material = material; | ||||
|         m_filpar[idx].temperature = temp; | ||||
|         m_filpar[idx].first_layer_temperature = first_layer_temp; | ||||
| 
 | ||||
|         // If this is a single extruder MM printer, we will use all the SE-specific config values.
 | ||||
|         // Otherwise, the defaults will be used to turn off the SE stuff.
 | ||||
|         if (m_semm) { | ||||
|             m_filpar[idx].loading_speed           = loading_speed; | ||||
|             m_filpar[idx].loading_speed_start     = loading_speed_start; | ||||
|             m_filpar[idx].unloading_speed         = unloading_speed; | ||||
|             m_filpar[idx].unloading_speed_start   = unloading_speed_start; | ||||
|             m_filpar[idx].delay                   = delay; | ||||
|             m_filpar[idx].cooling_moves           = cooling_moves; | ||||
|             m_filpar[idx].cooling_initial_speed   = cooling_initial_speed; | ||||
|             m_filpar[idx].cooling_final_speed     = cooling_final_speed; | ||||
|         } | ||||
| 
 | ||||
|         if (max_volumetric_speed != 0.f) | ||||
|             m_filpar[idx].max_e_speed = (max_volumetric_speed / Filament_Area); | ||||
|         m_filpar[idx].nozzle_diameter = nozzle_diameter; // to be used in future with (non-single) multiextruder MM
 | ||||
| 
 | ||||
|         m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter
 | ||||
| 
 | ||||
|         if (m_semm) { | ||||
|             std::stringstream stream{ramming_parameters}; | ||||
|             float speed = 0.f; | ||||
|             stream >> m_filpar[idx].ramming_line_width_multiplicator >> m_filpar[idx].ramming_step_multiplicator; | ||||
|             m_filpar[idx].ramming_line_width_multiplicator /= 100; | ||||
|             m_filpar[idx].ramming_step_multiplicator /= 100; | ||||
|             while (stream >> speed) | ||||
|                 m_filpar[idx].ramming_speed.push_back(speed); | ||||
|         } | ||||
| 
 | ||||
|         m_used_filament_length.resize(std::max(m_used_filament_length.size(), idx + 1)); // makes sure that the vector is big enough so we don't have to check later
 | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| 	// Appends into internal structure m_plan containing info about the future wipe tower
 | ||||
| 	// to be used before building begins. The entries must be added ordered in z.
 | ||||
| 	void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, bool brim, float wipe_volume = 0.f); | ||||
| 
 | ||||
| 	// Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result"
 | ||||
| 	void generate(std::vector<std::vector<ToolChangeResult>> &result); | ||||
| 
 | ||||
|     float get_depth() const { return m_wipe_tower_depth; } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 	// Switch to a next layer.
 | ||||
| 	void set_layer( | ||||
| 		// Print height of this layer.
 | ||||
| 		float print_z, | ||||
| 		// Layer height, used to calculate extrusion the rate.
 | ||||
| 		float layer_height, | ||||
| 		// Maximum number of tool changes on this layer or the layers below.
 | ||||
| 		size_t max_tool_changes, | ||||
| 		// Is this the first layer of the print? In that case print the brim first.
 | ||||
| 		bool is_first_layer, | ||||
| 		// Is this the last layer of the waste tower?
 | ||||
| 		bool is_last_layer) | ||||
| 	{ | ||||
| 		m_z_pos 				= print_z; | ||||
| 		m_layer_height			= layer_height; | ||||
| 		m_is_first_layer 		= is_first_layer; | ||||
| 		m_print_brim = is_first_layer; | ||||
| 		m_depth_traversed  = 0.f; | ||||
| 		m_current_shape = (! is_first_layer && m_current_shape == SHAPE_NORMAL) ? SHAPE_REVERSED : SHAPE_NORMAL; | ||||
| 		if (is_first_layer) { | ||||
|             this->m_num_layer_changes 	= 0; | ||||
|             this->m_num_tool_changes 	= 0; | ||||
|         } | ||||
|         else | ||||
|             ++ m_num_layer_changes; | ||||
| 		 | ||||
| 		// Calculate extrusion flow from desired line width, nozzle diameter, filament diameter and layer_height:
 | ||||
| 		m_extrusion_flow = extrusion_flow(layer_height); | ||||
| 
 | ||||
|         // Advance m_layer_info iterator, making sure we got it right
 | ||||
| 		while (!m_plan.empty() && m_layer_info->z < print_z - WT_EPSILON && m_layer_info+1 != m_plan.end()) | ||||
| 			++m_layer_info; | ||||
| 	} | ||||
| 
 | ||||
| 	// Return the wipe tower position.
 | ||||
| 	const Vec2f& 		 position() const { return m_wipe_tower_pos; } | ||||
| 	// Return the wipe tower width.
 | ||||
| 	float     		 width()    const { return m_wipe_tower_width; } | ||||
| 	// The wipe tower is finished, there should be no more tool changes or wipe tower prints.
 | ||||
| 	bool 	  		 finished() const { return m_max_color_changes == 0; } | ||||
| 
 | ||||
| 	// Returns gcode to prime the nozzles at the front edge of the print bed.
 | ||||
| 	virtual ToolChangeResult prime( | ||||
| 	std::vector<ToolChangeResult> prime( | ||||
| 		// print_z of the first layer.
 | ||||
| 		float 						first_layer_height,  | ||||
| 		// Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object.
 | ||||
| 		const std::vector<unsigned int> &tools, | ||||
| 		// If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower.
 | ||||
| 		// If false, the last priming are will be large enough to wipe the last extruder sufficiently.
 | ||||
| 		bool 						last_wipe_inside_wipe_tower) = 0; | ||||
| 		bool 						last_wipe_inside_wipe_tower); | ||||
| 
 | ||||
| 	// Returns gcode for toolchange and the end position.
 | ||||
| 	// if new_tool == -1, just unload the current filament over the wipe tower.
 | ||||
| 	virtual ToolChangeResult tool_change(unsigned int new_tool, bool last_in_layer) = 0; | ||||
| 	// Returns gcode for a toolchange and a final print head position.
 | ||||
| 	// On the first layer, extrude a brim around the future wipe tower first.
 | ||||
| 	ToolChangeResult tool_change(unsigned int new_tool, bool last_in_layer); | ||||
| 
 | ||||
| 	// Close the current wipe tower layer with a perimeter and possibly fill the unfilled space with a zig-zag.
 | ||||
| 	// Fill the unfilled space with a sparse infill.
 | ||||
| 	// Call this method only if layer_finished() is false.
 | ||||
| 	virtual ToolChangeResult finish_layer() = 0; | ||||
| 	ToolChangeResult finish_layer(); | ||||
| 
 | ||||
| 	// Is the current layer finished? A layer is finished if either the wipe tower is finished, or
 | ||||
| 	// the wipe tower has been completely covered by the tool change extrusions,
 | ||||
| 	// or the rest of the tower has been filled by a sparse infill with the finish_layer() method.
 | ||||
| 	virtual bool 		     layer_finished() const = 0; | ||||
| 	// Is the current layer finished?
 | ||||
| 	bool 			 layer_finished() const { | ||||
| 		return ( (m_is_first_layer ? m_wipe_tower_depth - m_perimeter_width : m_layer_info->depth) - WT_EPSILON < m_depth_traversed); | ||||
| 	} | ||||
| 
 | ||||
|     // Returns used filament length per extruder:
 | ||||
|     virtual std::vector<float> get_used_filament() const = 0; | ||||
|     std::vector<float> get_used_filament() const { return m_used_filament_length; } | ||||
|     int get_number_of_toolchanges() const { return m_num_tool_changes; } | ||||
| 
 | ||||
|     // Returns total number of toolchanges:
 | ||||
|     virtual int get_number_of_toolchanges() const = 0; | ||||
|     struct FilamentParameters { | ||||
|         std::string 	    material = "PLA"; | ||||
|         int  			    temperature = 0; | ||||
|         int  			    first_layer_temperature = 0; | ||||
|         float               loading_speed = 0.f; | ||||
|         float               loading_speed_start = 0.f; | ||||
|         float               unloading_speed = 0.f; | ||||
|         float               unloading_speed_start = 0.f; | ||||
|         float               delay = 0.f ; | ||||
|         int                 cooling_moves = 0; | ||||
|         float               cooling_initial_speed = 0.f; | ||||
|         float               cooling_final_speed = 0.f; | ||||
|         float               ramming_line_width_multiplicator = 1.f; | ||||
|         float               ramming_step_multiplicator = 1.f; | ||||
|         float               max_e_speed = std::numeric_limits<float>::max(); | ||||
|         std::vector<float>  ramming_speed; | ||||
|         float               nozzle_diameter; | ||||
|     }; | ||||
| 
 | ||||
| private: | ||||
| 	WipeTower(); | ||||
| 
 | ||||
| 	enum wipe_shape // A fill-in direction
 | ||||
| 	{ | ||||
| 		SHAPE_NORMAL = 1, | ||||
| 		SHAPE_REVERSED = -1 | ||||
| 	}; | ||||
| 
 | ||||
| 
 | ||||
|     const bool  m_peters_wipe_tower   = false; // sparse wipe tower inspired by Peter's post processor - not finished yet
 | ||||
|     const float Filament_Area         = float(M_PI * 1.75f * 1.75f / 4.f); // filament area in mm^2
 | ||||
|     const float Width_To_Nozzle_Ratio = 1.25f; // desired line width (oval) in multiples of nozzle diameter - may not be actually neccessary to adjust
 | ||||
|     const float WT_EPSILON            = 1e-3f; | ||||
| 
 | ||||
| 
 | ||||
| 	bool   m_semm               = true; // Are we using a single extruder multimaterial printer?
 | ||||
|     Vec2f  m_wipe_tower_pos; 			// Left front corner of the wipe tower in mm.
 | ||||
| 	float  m_wipe_tower_width; 			// Width of the wipe tower.
 | ||||
| 	float  m_wipe_tower_depth 	= 0.f; 	// Depth of the wipe tower
 | ||||
| 	float  m_wipe_tower_rotation_angle = 0.f; // Wipe tower rotation angle in degrees (with respect to x axis)
 | ||||
|     float  m_internal_rotation  = 0.f; | ||||
| 	float  m_y_shift			= 0.f;  // y shift passed to writer
 | ||||
| 	float  m_z_pos 				= 0.f;  // Current Z position.
 | ||||
| 	float  m_layer_height 		= 0.f; 	// Current layer height.
 | ||||
| 	size_t m_max_color_changes 	= 0; 	// Maximum number of color changes per layer.
 | ||||
| 	bool   m_is_first_layer 	= false;// Is this the 1st layer of the print? If so, print the brim around the waste tower.
 | ||||
|     int    m_old_temperature    = -1;   // To keep track of what was the last temp that we set (so we don't issue the command when not neccessary)
 | ||||
| 
 | ||||
| 	// G-code generator parameters.
 | ||||
|     float           m_cooling_tube_retraction   = 0.f; | ||||
|     float           m_cooling_tube_length       = 0.f; | ||||
|     float           m_parking_pos_retraction    = 0.f; | ||||
|     float           m_extra_loading_move        = 0.f; | ||||
|     float           m_bridging                  = 0.f; | ||||
|     bool            m_set_extruder_trimpot      = false; | ||||
|     bool            m_adhesion                  = true; | ||||
|     GCodeFlavor     m_gcode_flavor; | ||||
| 
 | ||||
| 	float m_perimeter_width = 0.4f * Width_To_Nozzle_Ratio; // Width of an extrusion line, also a perimeter spacing for 100% infill.
 | ||||
| 	float m_extrusion_flow = 0.038f; //0.029f;// Extrusion flow is derived from m_perimeter_width, layer height and filament diameter.
 | ||||
| 
 | ||||
| 	// Extruder specific parameters.
 | ||||
|     std::vector<FilamentParameters> m_filpar; | ||||
| 
 | ||||
| 
 | ||||
| 	// State of the wipe tower generator.
 | ||||
| 	unsigned int m_num_layer_changes = 0; // Layer change counter for the output statistics.
 | ||||
| 	unsigned int m_num_tool_changes  = 0; // Tool change change counter for the output statistics.
 | ||||
| 	///unsigned int 	m_idx_tool_change_in_layer = 0; // Layer change counter in this layer. Counting up to m_max_color_changes.
 | ||||
| 	bool m_print_brim = true; | ||||
| 	// A fill-in direction (positive Y, negative Y) alternates with each layer.
 | ||||
| 	wipe_shape   	m_current_shape = SHAPE_NORMAL; | ||||
| 	unsigned int 	m_current_tool  = 0; | ||||
|     const std::vector<std::vector<float>> wipe_volumes; | ||||
| 
 | ||||
| 	float           m_depth_traversed = 0.f; // Current y position at the wipe tower.
 | ||||
| 	bool 			m_left_to_right   = true; | ||||
| 	float			m_extra_spacing   = 1.f; | ||||
| 
 | ||||
| 	// Calculates extrusion flow needed to produce required line width for given layer height
 | ||||
| 	float extrusion_flow(float layer_height = -1.f) const	// negative layer_height - return current m_extrusion_flow
 | ||||
| 	{ | ||||
| 		if ( layer_height < 0 ) | ||||
| 			return m_extrusion_flow; | ||||
| 		return layer_height * ( m_perimeter_width - layer_height * (1.f-float(M_PI)/4.f)) / Filament_Area; | ||||
| 	} | ||||
| 
 | ||||
| 	// Calculates length of extrusion line to extrude given volume
 | ||||
| 	float volume_to_length(float volume, float line_width, float layer_height) const { | ||||
| 		return std::max(0.f, volume / (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f)))); | ||||
| 	} | ||||
| 
 | ||||
| 	// Calculates depth for all layers and propagates them downwards
 | ||||
| 	void plan_tower(); | ||||
| 
 | ||||
| 	// Goes through m_plan and recalculates depths and width of the WT to make it exactly square - experimental
 | ||||
| 	void make_wipe_tower_square(); | ||||
| 
 | ||||
|     // Goes through m_plan, calculates border and finish_layer extrusions and subtracts them from last wipe
 | ||||
|     void save_on_last_wipe(); | ||||
| 
 | ||||
| 
 | ||||
| 	struct box_coordinates | ||||
| 	{ | ||||
| 		box_coordinates(float left, float bottom, float width, float height) : | ||||
| 			ld(left        , bottom         ), | ||||
| 			lu(left        , bottom + height), | ||||
| 			rd(left + width, bottom         ), | ||||
| 			ru(left + width, bottom + height) {} | ||||
| 		box_coordinates(const Vec2f &pos, float width, float height) : box_coordinates(pos(0), pos(1), width, height) {} | ||||
| 		void translate(const Vec2f &shift) { | ||||
| 			ld += shift; lu += shift; | ||||
| 			rd += shift; ru += shift; | ||||
| 		} | ||||
| 		void translate(const float dx, const float dy) { translate(Vec2f(dx, dy)); } | ||||
| 		void expand(const float offset) { | ||||
| 			ld += Vec2f(- offset, - offset); | ||||
| 			lu += Vec2f(- offset,   offset); | ||||
| 			rd += Vec2f(  offset, - offset); | ||||
| 			ru += Vec2f(  offset,   offset); | ||||
| 		} | ||||
| 		void expand(const float offset_x, const float offset_y) { | ||||
| 			ld += Vec2f(- offset_x, - offset_y); | ||||
| 			lu += Vec2f(- offset_x,   offset_y); | ||||
| 			rd += Vec2f(  offset_x, - offset_y); | ||||
| 			ru += Vec2f(  offset_x,   offset_y); | ||||
| 		} | ||||
| 		Vec2f ld;  // left down
 | ||||
| 		Vec2f lu;	// left upper 
 | ||||
| 		Vec2f rd;	// right lower
 | ||||
| 		Vec2f ru;  // right upper
 | ||||
| 	}; | ||||
| 
 | ||||
| 
 | ||||
| 	// to store information about tool changes for a given layer
 | ||||
| 	struct WipeTowerInfo{ | ||||
| 		struct ToolChange { | ||||
| 			unsigned int old_tool; | ||||
| 			unsigned int new_tool; | ||||
| 			float required_depth; | ||||
|             float ramming_depth; | ||||
|             float first_wipe_line; | ||||
|             float wipe_volume; | ||||
| 			ToolChange(unsigned int old, unsigned int newtool, float depth=0.f, float ramming_depth=0.f, float fwl=0.f, float wv=0.f) | ||||
|             : old_tool{old}, new_tool{newtool}, required_depth{depth}, ramming_depth{ramming_depth}, first_wipe_line{fwl}, wipe_volume{wv} {} | ||||
| 		}; | ||||
| 		float z;		// z position of the layer
 | ||||
| 		float height;	// layer height
 | ||||
| 		float depth;	// depth of the layer based on all layers above
 | ||||
| 		float extra_spacing; | ||||
| 		float toolchanges_depth() const { float sum = 0.f; for (const auto &a : tool_changes) sum += a.required_depth; return sum; } | ||||
| 
 | ||||
| 		std::vector<ToolChange> tool_changes; | ||||
| 
 | ||||
| 		WipeTowerInfo(float z_par, float layer_height_par) | ||||
| 			: z{z_par}, height{layer_height_par}, depth{0}, extra_spacing{1.f} {} | ||||
| 	}; | ||||
| 
 | ||||
| 	std::vector<WipeTowerInfo> m_plan; 	// Stores information about all layers and toolchanges for the future wipe tower (filled by plan_toolchange(...))
 | ||||
| 	std::vector<WipeTowerInfo>::iterator m_layer_info = m_plan.end(); | ||||
| 
 | ||||
|     // Stores information about used filament length per extruder:
 | ||||
|     std::vector<float> m_used_filament_length; | ||||
| 
 | ||||
| 
 | ||||
| 	// Returns gcode for wipe tower brim
 | ||||
| 	// sideOnly			-- set to false -- experimental, draw brim on sides of wipe tower
 | ||||
| 	// offset			-- set to 0		-- experimental, offset to replace brim in front / rear of wipe tower
 | ||||
| 	ToolChangeResult toolchange_Brim(bool sideOnly = false, float y_offset = 0.f); | ||||
| 
 | ||||
| 	void toolchange_Unload( | ||||
| 		WipeTowerWriter &writer, | ||||
| 		const box_coordinates  &cleaning_box,  | ||||
| 		const std::string&	 	current_material, | ||||
| 		const int 				new_temperature); | ||||
| 
 | ||||
| 	void toolchange_Change( | ||||
| 		WipeTowerWriter &writer, | ||||
| 		const unsigned int		new_tool, | ||||
| 		const std::string& 		new_material); | ||||
| 	 | ||||
| 	void toolchange_Load( | ||||
| 		WipeTowerWriter &writer, | ||||
| 		const box_coordinates  &cleaning_box); | ||||
| 	 | ||||
| 	void toolchange_Wipe( | ||||
| 		WipeTowerWriter &writer, | ||||
| 		const box_coordinates  &cleaning_box, | ||||
| 		float wipe_volume); | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| }; // namespace Slic3r
 | ||||
| 
 | ||||
| #endif /* slic3r_WipeTower_hpp_ */ | ||||
| #endif // WipeTowerPrusaMM_hpp_ 
 | ||||
|  |  | |||
|  | @ -1,388 +0,0 @@ | |||
| #ifndef WipeTowerPrusaMM_hpp_ | ||||
| #define WipeTowerPrusaMM_hpp_ | ||||
| 
 | ||||
| #include <cmath> | ||||
| #include <string> | ||||
| #include <sstream> | ||||
| #include <utility> | ||||
| #include <algorithm> | ||||
| 
 | ||||
| #include "WipeTower.hpp" | ||||
| #include "PrintConfig.hpp" | ||||
| 
 | ||||
| 
 | ||||
| namespace Slic3r | ||||
| { | ||||
| 
 | ||||
| namespace PrusaMultiMaterial { | ||||
| 	class Writer; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| class WipeTowerPrusaMM : public WipeTower | ||||
| { | ||||
| public: | ||||
| 	enum material_type | ||||
| 	{ | ||||
| 		INVALID = -1, | ||||
| 		PLA   = 0,		// E:210C	B:55C
 | ||||
| 		ABS   = 1,		// E:255C	B:100C
 | ||||
| 		PET   = 2,		// E:240C	B:90C
 | ||||
| 		HIPS  = 3,		// E:220C	B:100C
 | ||||
| 		FLEX  = 4,		// E:245C	B:80C
 | ||||
| 		SCAFF = 5,		// E:215C	B:55C
 | ||||
| 		EDGE  = 6,		// E:240C	B:80C
 | ||||
| 		NGEN  = 7,		// E:230C	B:80C
 | ||||
| 		PVA   = 8,	    // E:210C	B:80C
 | ||||
| 		PC    = 9 | ||||
| 	}; | ||||
| 
 | ||||
| 	// Parse material name into material_type.
 | ||||
| 	static material_type parse_material(const char *name); | ||||
| 	static std::string   to_string(material_type material); | ||||
| 
 | ||||
| 	// x			-- x coordinates of wipe tower in mm ( left bottom corner )
 | ||||
| 	// y			-- y coordinates of wipe tower in mm ( left bottom corner )
 | ||||
| 	// width		-- width of wipe tower in mm ( default 60 mm - leave as it is )
 | ||||
| 	// wipe_area	-- space available for one toolchange in mm
 | ||||
| 	WipeTowerPrusaMM(float x, float y, float width, float rotation_angle, float cooling_tube_retraction, | ||||
|                      float cooling_tube_length, float parking_pos_retraction, float extra_loading_move,  | ||||
|                      float bridging, bool set_extruder_trimpot, GCodeFlavor flavor, | ||||
|                      const std::vector<std::vector<float>>& wiping_matrix, unsigned int initial_tool) : | ||||
|     m_wipe_tower_pos(x, y), | ||||
| 		m_wipe_tower_width(width), | ||||
| 		m_wipe_tower_rotation_angle(rotation_angle), | ||||
| 		m_y_shift(0.f), | ||||
| 		m_z_pos(0.f), | ||||
| 		m_is_first_layer(false), | ||||
|         m_cooling_tube_retraction(cooling_tube_retraction), | ||||
|         m_cooling_tube_length(cooling_tube_length), | ||||
|         m_parking_pos_retraction(parking_pos_retraction), | ||||
|         m_extra_loading_move(extra_loading_move), | ||||
| 		m_bridging(bridging), | ||||
| 		m_set_extruder_trimpot(set_extruder_trimpot), | ||||
|         m_gcode_flavor(flavor), | ||||
|         m_current_tool(initial_tool), | ||||
|         wipe_volumes(wiping_matrix) | ||||
|         {} | ||||
| 
 | ||||
| 	virtual ~WipeTowerPrusaMM() {} | ||||
| 
 | ||||
| 
 | ||||
| 	// Set the extruder properties.
 | ||||
| 	void set_extruder(size_t idx, material_type material, int temp, int first_layer_temp, float loading_speed, float loading_speed_start, | ||||
|                       float unloading_speed, float unloading_speed_start, float delay, int cooling_moves, | ||||
|                       float cooling_initial_speed, float cooling_final_speed, std::string ramming_parameters, float nozzle_diameter) | ||||
| 	{ | ||||
|         //while (m_filpar.size() < idx+1)   // makes sure the required element is in the vector
 | ||||
|         m_filpar.push_back(FilamentParameters()); | ||||
| 
 | ||||
|         m_filpar[idx].material = material; | ||||
|         if (material == FLEX || material == SCAFF || material == PVA) { | ||||
|     		// MMU2 lowers the print speed using the speed override (M220) for printing of soluble PVA/BVOH and flex materials.
 | ||||
|     		// Therefore it does not make sense to use the new M220 B and M220 R (backup / restore).
 | ||||
|         	m_retain_speed_override = false; | ||||
|         } | ||||
|         m_filpar[idx].temperature = temp; | ||||
|         m_filpar[idx].first_layer_temperature = first_layer_temp; | ||||
|         m_filpar[idx].loading_speed = loading_speed; | ||||
|         m_filpar[idx].loading_speed_start = loading_speed_start; | ||||
|         m_filpar[idx].unloading_speed = unloading_speed; | ||||
|         m_filpar[idx].unloading_speed_start = unloading_speed_start; | ||||
|         m_filpar[idx].delay = delay; | ||||
|         m_filpar[idx].cooling_moves = cooling_moves; | ||||
|         m_filpar[idx].cooling_initial_speed = cooling_initial_speed; | ||||
|         m_filpar[idx].cooling_final_speed = cooling_final_speed; | ||||
|         m_filpar[idx].nozzle_diameter = nozzle_diameter; // to be used in future with (non-single) multiextruder MM
 | ||||
| 
 | ||||
|         m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter
 | ||||
| 
 | ||||
|         std::stringstream stream{ramming_parameters}; | ||||
|         float speed = 0.f; | ||||
|         stream >> m_filpar[idx].ramming_line_width_multiplicator >> m_filpar[idx].ramming_step_multiplicator; | ||||
|         m_filpar[idx].ramming_line_width_multiplicator /= 100; | ||||
|         m_filpar[idx].ramming_step_multiplicator /= 100; | ||||
|         while (stream >> speed) | ||||
|             m_filpar[idx].ramming_speed.push_back(speed); | ||||
| 
 | ||||
|         m_used_filament_length.resize(std::max(m_used_filament_length.size(), idx + 1)); // makes sure that the vector is big enough so we don't have to check later
 | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| 	// Appends into internal structure m_plan containing info about the future wipe tower
 | ||||
| 	// to be used before building begins. The entries must be added ordered in z.
 | ||||
| 	void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, bool brim, float wipe_volume = 0.f); | ||||
| 
 | ||||
| 	// Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result"
 | ||||
| 	void generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &result); | ||||
| 
 | ||||
|     float get_depth() const { return m_wipe_tower_depth; } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 	// Switch to a next layer.
 | ||||
| 	virtual void set_layer( | ||||
| 		// Print height of this layer.
 | ||||
| 		float print_z, | ||||
| 		// Layer height, used to calculate extrusion the rate.
 | ||||
| 		float layer_height, | ||||
| 		// Maximum number of tool changes on this layer or the layers below.
 | ||||
| 		size_t max_tool_changes, | ||||
| 		// Is this the first layer of the print? In that case print the brim first.
 | ||||
| 		bool is_first_layer, | ||||
| 		// Is this the last layer of the waste tower?
 | ||||
| 		bool is_last_layer) | ||||
| 	{ | ||||
| 		m_z_pos 				= print_z; | ||||
| 		m_layer_height			= layer_height; | ||||
| 		m_is_first_layer 		= is_first_layer; | ||||
| 		m_print_brim = is_first_layer; | ||||
| 		m_depth_traversed  = 0.f; | ||||
| 		m_current_shape = (! is_first_layer && m_current_shape == SHAPE_NORMAL) ? SHAPE_REVERSED : SHAPE_NORMAL; | ||||
| 		if (is_first_layer) { | ||||
|             this->m_num_layer_changes 	= 0; | ||||
|             this->m_num_tool_changes 	= 0; | ||||
|         } | ||||
|         else | ||||
|             ++ m_num_layer_changes; | ||||
| 		 | ||||
| 		// Calculate extrusion flow from desired line width, nozzle diameter, filament diameter and layer_height:
 | ||||
| 		m_extrusion_flow = extrusion_flow(layer_height); | ||||
| 
 | ||||
|         // Advance m_layer_info iterator, making sure we got it right
 | ||||
| 		while (!m_plan.empty() && m_layer_info->z < print_z - WT_EPSILON && m_layer_info+1 != m_plan.end()) | ||||
| 			++m_layer_info; | ||||
| 	} | ||||
| 
 | ||||
| 	// Return the wipe tower position.
 | ||||
| 	virtual const xy& 		 position() const { return m_wipe_tower_pos; } | ||||
| 	// Return the wipe tower width.
 | ||||
| 	virtual float     		 width()    const { return m_wipe_tower_width; } | ||||
| 	// The wipe tower is finished, there should be no more tool changes or wipe tower prints.
 | ||||
| 	virtual bool 	  		 finished() const { return m_max_color_changes == 0; } | ||||
| 
 | ||||
| 	// Returns gcode to prime the nozzles at the front edge of the print bed.
 | ||||
| 	virtual ToolChangeResult prime( | ||||
| 		// print_z of the first layer.
 | ||||
| 		float 						first_layer_height,  | ||||
| 		// Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object.
 | ||||
| 		const std::vector<unsigned int> &tools, | ||||
| 		// If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower.
 | ||||
| 		// If false, the last priming are will be large enough to wipe the last extruder sufficiently.
 | ||||
| 		bool 						last_wipe_inside_wipe_tower); | ||||
| 
 | ||||
| 	// Returns gcode for a toolchange and a final print head position.
 | ||||
| 	// On the first layer, extrude a brim around the future wipe tower first.
 | ||||
| 	virtual ToolChangeResult tool_change(unsigned int new_tool, bool last_in_layer); | ||||
| 
 | ||||
| 	// Fill the unfilled space with a sparse infill.
 | ||||
| 	// Call this method only if layer_finished() is false.
 | ||||
| 	virtual ToolChangeResult finish_layer(); | ||||
| 
 | ||||
| 	// Is the current layer finished?
 | ||||
| 	virtual bool 			 layer_finished() const { | ||||
| 		return ( (m_is_first_layer ? m_wipe_tower_depth - m_perimeter_width : m_layer_info->depth) - WT_EPSILON < m_depth_traversed); | ||||
| 	} | ||||
| 
 | ||||
|     virtual std::vector<float> get_used_filament() const override { return m_used_filament_length; } | ||||
|     virtual int get_number_of_toolchanges() const override { return m_num_tool_changes; } | ||||
| 
 | ||||
| 
 | ||||
| private: | ||||
| 	WipeTowerPrusaMM(); | ||||
| 
 | ||||
| 	enum wipe_shape // A fill-in direction
 | ||||
| 	{ | ||||
| 		SHAPE_NORMAL = 1, | ||||
| 		SHAPE_REVERSED = -1 | ||||
| 	}; | ||||
| 
 | ||||
| 
 | ||||
|     const bool  m_peters_wipe_tower   = false; // sparse wipe tower inspired by Peter's post processor - not finished yet
 | ||||
|     const float Filament_Area         = float(M_PI * 1.75f * 1.75f / 4.f); // filament area in mm^2
 | ||||
|     const float Width_To_Nozzle_Ratio = 1.25f; // desired line width (oval) in multiples of nozzle diameter - may not be actually neccessary to adjust
 | ||||
|     const float WT_EPSILON            = 1e-3f; | ||||
| 
 | ||||
| 
 | ||||
| 	xy 	   m_wipe_tower_pos; 			// Left front corner of the wipe tower in mm.
 | ||||
| 	float  m_wipe_tower_width; 			// Width of the wipe tower.
 | ||||
| 	float  m_wipe_tower_depth 	= 0.f; 	// Depth of the wipe tower
 | ||||
| 	float  m_wipe_tower_rotation_angle = 0.f; // Wipe tower rotation angle in degrees (with respect to x axis)
 | ||||
|     float  m_internal_rotation  = 0.f; | ||||
| 	float  m_y_shift			= 0.f;  // y shift passed to writer
 | ||||
| 	float  m_z_pos 				= 0.f;  // Current Z position.
 | ||||
| 	float  m_layer_height 		= 0.f; 	// Current layer height.
 | ||||
| 	size_t m_max_color_changes 	= 0; 	// Maximum number of color changes per layer.
 | ||||
| 	bool   m_is_first_layer 	= false;// Is this the 1st layer of the print? If so, print the brim around the waste tower.
 | ||||
|     int    m_old_temperature    = -1;   // To keep track of what was the last temp that we set (so we don't issue the command when not neccessary)
 | ||||
| 
 | ||||
| 	// G-code generator parameters.
 | ||||
|     float           m_cooling_tube_retraction   = 0.f; | ||||
|     float           m_cooling_tube_length       = 0.f; | ||||
|     float           m_parking_pos_retraction    = 0.f; | ||||
|     float           m_extra_loading_move        = 0.f; | ||||
|     float           m_bridging                  = 0.f; | ||||
|     bool            m_set_extruder_trimpot      = false; | ||||
|     bool 			m_retain_speed_override		= true; | ||||
|     bool            m_adhesion                  = true; | ||||
|     GCodeFlavor     m_gcode_flavor; | ||||
| 
 | ||||
| 	float m_perimeter_width = 0.4f * Width_To_Nozzle_Ratio; // Width of an extrusion line, also a perimeter spacing for 100% infill.
 | ||||
| 	float m_extrusion_flow = 0.038f; //0.029f;// Extrusion flow is derived from m_perimeter_width, layer height and filament diameter.
 | ||||
| 
 | ||||
| 
 | ||||
|     struct FilamentParameters { | ||||
|         material_type 	    material = PLA; | ||||
|         int  			    temperature = 0; | ||||
|         int  			    first_layer_temperature = 0; | ||||
|         float               loading_speed = 0.f; | ||||
|         float               loading_speed_start = 0.f; | ||||
|         float               unloading_speed = 0.f; | ||||
|         float               unloading_speed_start = 0.f; | ||||
|         float               delay = 0.f ; | ||||
|         int                 cooling_moves = 0; | ||||
|         float               cooling_initial_speed = 0.f; | ||||
|         float               cooling_final_speed = 0.f; | ||||
|         float               ramming_line_width_multiplicator = 0.f; | ||||
|         float               ramming_step_multiplicator = 0.f; | ||||
|         std::vector<float>  ramming_speed; | ||||
|         float               nozzle_diameter; | ||||
|     }; | ||||
| 
 | ||||
| 	// Extruder specific parameters.
 | ||||
|     std::vector<FilamentParameters> m_filpar; | ||||
| 
 | ||||
| 
 | ||||
| 	// State of the wipe tower generator.
 | ||||
| 	unsigned int m_num_layer_changes = 0; // Layer change counter for the output statistics.
 | ||||
| 	unsigned int m_num_tool_changes  = 0; // Tool change change counter for the output statistics.
 | ||||
| 	///unsigned int 	m_idx_tool_change_in_layer = 0; // Layer change counter in this layer. Counting up to m_max_color_changes.
 | ||||
| 	bool m_print_brim = true; | ||||
| 	// A fill-in direction (positive Y, negative Y) alternates with each layer.
 | ||||
| 	wipe_shape   	m_current_shape = SHAPE_NORMAL; | ||||
| 	unsigned int 	m_current_tool  = 0; | ||||
|     const std::vector<std::vector<float>> wipe_volumes; | ||||
| 
 | ||||
| 	float           m_depth_traversed = 0.f; // Current y position at the wipe tower.
 | ||||
| 	bool 			m_left_to_right   = true; | ||||
| 	float			m_extra_spacing   = 1.f; | ||||
| 
 | ||||
| 	// Calculates extrusion flow needed to produce required line width for given layer height
 | ||||
| 	float extrusion_flow(float layer_height = -1.f) const	// negative layer_height - return current m_extrusion_flow
 | ||||
| 	{ | ||||
| 		if ( layer_height < 0 ) | ||||
| 			return m_extrusion_flow; | ||||
| 		return layer_height * ( m_perimeter_width - layer_height * (1.f-float(M_PI)/4.f)) / Filament_Area; | ||||
| 	} | ||||
| 
 | ||||
| 	// Calculates length of extrusion line to extrude given volume
 | ||||
| 	float volume_to_length(float volume, float line_width, float layer_height) const { | ||||
| 		return std::max(0.f, volume / (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f)))); | ||||
| 	} | ||||
| 
 | ||||
| 	// Calculates depth for all layers and propagates them downwards
 | ||||
| 	void plan_tower(); | ||||
| 
 | ||||
| 	// Goes through m_plan and recalculates depths and width of the WT to make it exactly square - experimental
 | ||||
| 	void make_wipe_tower_square(); | ||||
| 
 | ||||
|     // Goes through m_plan, calculates border and finish_layer extrusions and subtracts them from last wipe
 | ||||
|     void save_on_last_wipe(); | ||||
| 
 | ||||
| 
 | ||||
| 	struct box_coordinates | ||||
| 	{ | ||||
| 		box_coordinates(float left, float bottom, float width, float height) : | ||||
| 			ld(left        , bottom         ), | ||||
| 			lu(left        , bottom + height), | ||||
| 			rd(left + width, bottom         ), | ||||
| 			ru(left + width, bottom + height) {} | ||||
| 		box_coordinates(const xy &pos, float width, float height) : box_coordinates(pos.x, pos.y, width, height) {} | ||||
| 		void translate(const xy &shift) { | ||||
| 			ld += shift; lu += shift; | ||||
| 			rd += shift; ru += shift; | ||||
| 		} | ||||
| 		void translate(const float dx, const float dy) { translate(xy(dx, dy)); } | ||||
| 		void expand(const float offset) { | ||||
| 			ld += xy(- offset, - offset); | ||||
| 			lu += xy(- offset,   offset); | ||||
| 			rd += xy(  offset, - offset); | ||||
| 			ru += xy(  offset,   offset); | ||||
| 		} | ||||
| 		void expand(const float offset_x, const float offset_y) { | ||||
| 			ld += xy(- offset_x, - offset_y); | ||||
| 			lu += xy(- offset_x,   offset_y); | ||||
| 			rd += xy(  offset_x, - offset_y); | ||||
| 			ru += xy(  offset_x,   offset_y); | ||||
| 		} | ||||
| 		xy ld;  // left down
 | ||||
| 		xy lu;	// left upper 
 | ||||
| 		xy rd;	// right lower
 | ||||
| 		xy ru;  // right upper
 | ||||
| 	}; | ||||
| 
 | ||||
| 
 | ||||
| 	// to store information about tool changes for a given layer
 | ||||
| 	struct WipeTowerInfo{ | ||||
| 		struct ToolChange { | ||||
| 			unsigned int old_tool; | ||||
| 			unsigned int new_tool; | ||||
| 			float required_depth; | ||||
|             float ramming_depth; | ||||
|             float first_wipe_line; | ||||
|             float wipe_volume; | ||||
| 			ToolChange(unsigned int old, unsigned int newtool, float depth=0.f, float ramming_depth=0.f, float fwl=0.f, float wv=0.f) | ||||
|             : old_tool{old}, new_tool{newtool}, required_depth{depth}, ramming_depth{ramming_depth}, first_wipe_line{fwl}, wipe_volume{wv} {} | ||||
| 		}; | ||||
| 		float z;		// z position of the layer
 | ||||
| 		float height;	// layer height
 | ||||
| 		float depth;	// depth of the layer based on all layers above
 | ||||
| 		float extra_spacing; | ||||
| 		float toolchanges_depth() const { float sum = 0.f; for (const auto &a : tool_changes) sum += a.required_depth; return sum; } | ||||
| 
 | ||||
| 		std::vector<ToolChange> tool_changes; | ||||
| 
 | ||||
| 		WipeTowerInfo(float z_par, float layer_height_par) | ||||
| 			: z{z_par}, height{layer_height_par}, depth{0}, extra_spacing{1.f} {} | ||||
| 	}; | ||||
| 
 | ||||
| 	std::vector<WipeTowerInfo> m_plan; 	// Stores information about all layers and toolchanges for the future wipe tower (filled by plan_toolchange(...))
 | ||||
| 	std::vector<WipeTowerInfo>::iterator m_layer_info = m_plan.end(); | ||||
| 
 | ||||
|     // Stores information about used filament length per extruder:
 | ||||
|     std::vector<float> m_used_filament_length; | ||||
| 
 | ||||
| 
 | ||||
| 	// Returns gcode for wipe tower brim
 | ||||
| 	// sideOnly			-- set to false -- experimental, draw brim on sides of wipe tower
 | ||||
| 	// offset			-- set to 0		-- experimental, offset to replace brim in front / rear of wipe tower
 | ||||
| 	ToolChangeResult toolchange_Brim(bool sideOnly = false, float y_offset = 0.f); | ||||
| 
 | ||||
| 	void toolchange_Unload( | ||||
| 		PrusaMultiMaterial::Writer &writer, | ||||
| 		const box_coordinates  &cleaning_box,  | ||||
| 		const material_type	 	current_material, | ||||
| 		const int 				new_temperature); | ||||
| 
 | ||||
| 	void toolchange_Change( | ||||
| 		PrusaMultiMaterial::Writer &writer, | ||||
| 		const unsigned int		new_tool, | ||||
| 		material_type 			new_material); | ||||
| 	 | ||||
| 	void toolchange_Load( | ||||
| 		PrusaMultiMaterial::Writer &writer, | ||||
| 		const box_coordinates  &cleaning_box); | ||||
| 	 | ||||
| 	void toolchange_Wipe( | ||||
| 		PrusaMultiMaterial::Writer &writer, | ||||
| 		const box_coordinates  &cleaning_box, | ||||
| 		float wipe_volume); | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| }; // namespace Slic3r
 | ||||
| 
 | ||||
| #endif /* WipeTowerPrusaMM_hpp_ */ | ||||
|  | @ -174,7 +174,7 @@ namespace Slic3r { | |||
|     const std::string GCodeTimeEstimator::Silent_Last_M73_Output_Placeholder_Tag = "; SILENT_LAST_M73_OUTPUT_PLACEHOLDER"; | ||||
| 
 | ||||
|     GCodeTimeEstimator::GCodeTimeEstimator(EMode mode) | ||||
|         : _mode(mode) | ||||
|         : m_mode(mode) | ||||
|     { | ||||
|         reset(); | ||||
|         set_default(); | ||||
|  | @ -183,7 +183,7 @@ namespace Slic3r { | |||
|     void GCodeTimeEstimator::add_gcode_line(const std::string& gcode_line) | ||||
|     { | ||||
|         PROFILE_FUNC(); | ||||
|         _parser.parse_line(gcode_line,  | ||||
|         m_parser.parse_line(gcode_line,  | ||||
|             [this](GCodeReader &reader, const GCodeReader::GCodeLine &line) | ||||
|         { this->_process_gcode_line(reader, line); }); | ||||
|     } | ||||
|  | @ -196,7 +196,7 @@ namespace Slic3r { | |||
|         { this->_process_gcode_line(reader, line); }; | ||||
|         for (; *ptr != 0;) { | ||||
|             gline.reset(); | ||||
|             ptr = _parser.parse_line(ptr, gline, action); | ||||
|             ptr = m_parser.parse_line(ptr, gline, action); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -206,10 +206,13 @@ namespace Slic3r { | |||
|         if (start_from_beginning) | ||||
|         { | ||||
|             _reset_time(); | ||||
|             _last_st_synchronized_block_id = -1; | ||||
|             m_last_st_synchronized_block_id = -1; | ||||
|         } | ||||
|         _calculate_time(); | ||||
| 
 | ||||
|         if (m_needs_color_times && (m_color_time_cache != 0.0f)) | ||||
|             m_color_times.push_back(m_color_time_cache); | ||||
| 
 | ||||
| #if ENABLE_MOVE_STATS | ||||
|         _log_moves_stats(); | ||||
| #endif // ENABLE_MOVE_STATS
 | ||||
|  | @ -219,12 +222,15 @@ namespace Slic3r { | |||
|     { | ||||
|         reset(); | ||||
| 
 | ||||
|         _parser.parse_buffer(gcode, | ||||
|         m_parser.parse_buffer(gcode, | ||||
|             [this](GCodeReader &reader, const GCodeReader::GCodeLine &line) | ||||
|         { this->_process_gcode_line(reader, line); }); | ||||
| 
 | ||||
|         _calculate_time(); | ||||
| 
 | ||||
|         if (m_needs_color_times && (m_color_time_cache != 0.0f)) | ||||
|             m_color_times.push_back(m_color_time_cache); | ||||
| 
 | ||||
| #if ENABLE_MOVE_STATS | ||||
|         _log_moves_stats(); | ||||
| #endif // ENABLE_MOVE_STATS
 | ||||
|  | @ -234,9 +240,12 @@ namespace Slic3r { | |||
|     { | ||||
|         reset(); | ||||
| 
 | ||||
|         _parser.parse_file(file, boost::bind(&GCodeTimeEstimator::_process_gcode_line, this, _1, _2)); | ||||
|         m_parser.parse_file(file, boost::bind(&GCodeTimeEstimator::_process_gcode_line, this, _1, _2)); | ||||
|         _calculate_time(); | ||||
| 
 | ||||
|         if (m_needs_color_times && (m_color_time_cache != 0.0f)) | ||||
|             m_color_times.push_back(m_color_time_cache); | ||||
| 
 | ||||
| #if ENABLE_MOVE_STATS | ||||
|         _log_moves_stats(); | ||||
| #endif // ENABLE_MOVE_STATS
 | ||||
|  | @ -249,9 +258,12 @@ namespace Slic3r { | |||
|         auto action = [this](GCodeReader &reader, const GCodeReader::GCodeLine &line) | ||||
|         { this->_process_gcode_line(reader, line); }; | ||||
|         for (const std::string& line : gcode_lines) | ||||
|             _parser.parse_line(line, action); | ||||
|             m_parser.parse_line(line, action); | ||||
|         _calculate_time(); | ||||
| 
 | ||||
|         if (m_needs_color_times && (m_color_time_cache != 0.0f)) | ||||
|             m_color_times.push_back(m_color_time_cache); | ||||
| 
 | ||||
| #if ENABLE_MOVE_STATS | ||||
|         _log_moves_stats(); | ||||
| #endif // ENABLE_MOVE_STATS
 | ||||
|  | @ -270,7 +282,7 @@ namespace Slic3r { | |||
|             throw std::runtime_error(std::string("Remaining times export failed.\nCannot open file for writing.\n")); | ||||
| 
 | ||||
|         std::string time_mask; | ||||
|         switch (_mode) | ||||
|         switch (m_mode) | ||||
|         { | ||||
|         default: | ||||
|         case Normal: | ||||
|  | @ -291,7 +303,7 @@ namespace Slic3r { | |||
|         // buffer line to export only when greater than 64K to reduce writing calls
 | ||||
|         std::string export_line; | ||||
|         char time_line[64]; | ||||
| 		G1LineIdToBlockIdMap::const_iterator it_line_id = _g1_line_ids.begin(); | ||||
| 		G1LineIdToBlockIdMap::const_iterator it_line_id = m_g1_line_ids.begin(); | ||||
| 		while (std::getline(in, gcode_line)) | ||||
|         { | ||||
|             if (!in.good()) | ||||
|  | @ -301,15 +313,15 @@ namespace Slic3r { | |||
|             } | ||||
| 
 | ||||
|             // replaces placeholders for initial line M73 with the real lines
 | ||||
|             if (((_mode == Normal) && (gcode_line == Normal_First_M73_Output_Placeholder_Tag)) || | ||||
|                 ((_mode == Silent) && (gcode_line == Silent_First_M73_Output_Placeholder_Tag))) | ||||
|             if (((m_mode == Normal) && (gcode_line == Normal_First_M73_Output_Placeholder_Tag)) || | ||||
|                 ((m_mode == Silent) && (gcode_line == Silent_First_M73_Output_Placeholder_Tag))) | ||||
|             { | ||||
|                 sprintf(time_line, time_mask.c_str(), "0", _get_time_minutes(_time).c_str()); | ||||
|                 sprintf(time_line, time_mask.c_str(), "0", _get_time_minutes(m_time).c_str()); | ||||
|                 gcode_line = time_line; | ||||
|             } | ||||
|             // replaces placeholders for final line M73 with the real lines
 | ||||
|             else if (((_mode == Normal) && (gcode_line == Normal_Last_M73_Output_Placeholder_Tag)) || | ||||
|                      ((_mode == Silent) && (gcode_line == Silent_Last_M73_Output_Placeholder_Tag))) | ||||
|             else if (((m_mode == Normal) && (gcode_line == Normal_Last_M73_Output_Placeholder_Tag)) || | ||||
|                      ((m_mode == Silent) && (gcode_line == Silent_Last_M73_Output_Placeholder_Tag))) | ||||
|             { | ||||
|                 sprintf(time_line, time_mask.c_str(), "100", "0"); | ||||
|                 gcode_line = time_line; | ||||
|  | @ -319,27 +331,27 @@ namespace Slic3r { | |||
| 
 | ||||
| 
 | ||||
|             // add remaining time lines where needed
 | ||||
|             _parser.parse_line(gcode_line, | ||||
|             m_parser.parse_line(gcode_line, | ||||
|                 [this, &it_line_id, &g1_lines_count, &last_recorded_time, &time_line, &gcode_line, time_mask, interval](GCodeReader& reader, const GCodeReader::GCodeLine& line) | ||||
|             { | ||||
|                 if (line.cmd_is("G1")) | ||||
|                 { | ||||
|                     ++g1_lines_count; | ||||
| 
 | ||||
| 					assert(it_line_id == _g1_line_ids.end() || it_line_id->first >= g1_lines_count); | ||||
| 					assert(it_line_id == m_g1_line_ids.end() || it_line_id->first >= g1_lines_count); | ||||
| 
 | ||||
| 					const Block *block = nullptr; | ||||
| 					if (it_line_id != _g1_line_ids.end() && it_line_id->first == g1_lines_count) { | ||||
| 						if (line.has_e() && it_line_id->second < (unsigned int)_blocks.size()) | ||||
| 							block = &_blocks[it_line_id->second]; | ||||
| 					if (it_line_id != m_g1_line_ids.end() && it_line_id->first == g1_lines_count) { | ||||
| 						if (line.has_e() && it_line_id->second < (unsigned int)m_blocks.size()) | ||||
| 							block = &m_blocks[it_line_id->second]; | ||||
| 						++it_line_id; | ||||
| 					} | ||||
| 
 | ||||
| 					if (block != nullptr && block->elapsed_time != -1.0f) { | ||||
|                         float block_remaining_time = _time - block->elapsed_time; | ||||
|                         float block_remaining_time = m_time - block->elapsed_time; | ||||
|                         if (std::abs(last_recorded_time - block_remaining_time) > interval) | ||||
|                         { | ||||
|                             sprintf(time_line, time_mask.c_str(), std::to_string((int)(100.0f * block->elapsed_time / _time)).c_str(), _get_time_minutes(block_remaining_time).c_str()); | ||||
|                             sprintf(time_line, time_mask.c_str(), std::to_string((int)(100.0f * block->elapsed_time / m_time)).c_str(), _get_time_minutes(block_remaining_time).c_str()); | ||||
|                             gcode_line += time_line; | ||||
| 
 | ||||
|                             last_recorded_time = block_remaining_time; | ||||
|  | @ -387,240 +399,240 @@ namespace Slic3r { | |||
| 
 | ||||
|     void GCodeTimeEstimator::set_axis_position(EAxis axis, float position) | ||||
|     { | ||||
|         _state.axis[axis].position = position; | ||||
|         m_state.axis[axis].position = position; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_axis_max_feedrate(EAxis axis, float feedrate_mm_sec) | ||||
|     { | ||||
|         _state.axis[axis].max_feedrate = feedrate_mm_sec; | ||||
|         m_state.axis[axis].max_feedrate = feedrate_mm_sec; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_axis_max_acceleration(EAxis axis, float acceleration) | ||||
|     { | ||||
|         _state.axis[axis].max_acceleration = acceleration; | ||||
|         m_state.axis[axis].max_acceleration = acceleration; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_axis_max_jerk(EAxis axis, float jerk) | ||||
|     { | ||||
|         _state.axis[axis].max_jerk = jerk; | ||||
|         m_state.axis[axis].max_jerk = jerk; | ||||
|     } | ||||
| 
 | ||||
|     float GCodeTimeEstimator::get_axis_position(EAxis axis) const | ||||
|     { | ||||
|         return _state.axis[axis].position; | ||||
|         return m_state.axis[axis].position; | ||||
|     } | ||||
| 
 | ||||
|     float GCodeTimeEstimator::get_axis_max_feedrate(EAxis axis) const | ||||
|     { | ||||
|         return _state.axis[axis].max_feedrate; | ||||
|         return m_state.axis[axis].max_feedrate; | ||||
|     } | ||||
| 
 | ||||
|     float GCodeTimeEstimator::get_axis_max_acceleration(EAxis axis) const | ||||
|     { | ||||
|         return _state.axis[axis].max_acceleration; | ||||
|         return m_state.axis[axis].max_acceleration; | ||||
|     } | ||||
| 
 | ||||
|     float GCodeTimeEstimator::get_axis_max_jerk(EAxis axis) const | ||||
|     { | ||||
|         return _state.axis[axis].max_jerk; | ||||
|         return m_state.axis[axis].max_jerk; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_feedrate(float feedrate_mm_sec) | ||||
|     { | ||||
|         _state.feedrate = feedrate_mm_sec; | ||||
|         m_state.feedrate = feedrate_mm_sec; | ||||
|     } | ||||
| 
 | ||||
|     float GCodeTimeEstimator::get_feedrate() const | ||||
|     { | ||||
|         return _state.feedrate; | ||||
|         return m_state.feedrate; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_acceleration(float acceleration_mm_sec2) | ||||
|     { | ||||
|         _state.acceleration = (_state.max_acceleration == 0) ?  | ||||
|         m_state.acceleration = (m_state.max_acceleration == 0) ?  | ||||
|             acceleration_mm_sec2 :  | ||||
|             // Clamp the acceleration with the maximum.
 | ||||
|             std::min(_state.max_acceleration, acceleration_mm_sec2); | ||||
|             std::min(m_state.max_acceleration, acceleration_mm_sec2); | ||||
|     } | ||||
| 
 | ||||
|     float GCodeTimeEstimator::get_acceleration() const | ||||
|     { | ||||
|         return _state.acceleration; | ||||
|         return m_state.acceleration; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_max_acceleration(float acceleration_mm_sec2) | ||||
|     { | ||||
|         _state.max_acceleration = acceleration_mm_sec2; | ||||
|         m_state.max_acceleration = acceleration_mm_sec2; | ||||
|         if (acceleration_mm_sec2 > 0) | ||||
|             _state.acceleration = acceleration_mm_sec2; | ||||
|             m_state.acceleration = acceleration_mm_sec2; | ||||
|     } | ||||
| 
 | ||||
|     float GCodeTimeEstimator::get_max_acceleration() const | ||||
|     { | ||||
|         return _state.max_acceleration; | ||||
|         return m_state.max_acceleration; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_retract_acceleration(float acceleration_mm_sec2) | ||||
|     { | ||||
|         _state.retract_acceleration = acceleration_mm_sec2; | ||||
|         m_state.retract_acceleration = acceleration_mm_sec2; | ||||
|     } | ||||
| 
 | ||||
|     float GCodeTimeEstimator::get_retract_acceleration() const | ||||
|     { | ||||
|         return _state.retract_acceleration; | ||||
|         return m_state.retract_acceleration; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_minimum_feedrate(float feedrate_mm_sec) | ||||
|     { | ||||
|         _state.minimum_feedrate = feedrate_mm_sec; | ||||
|         m_state.minimum_feedrate = feedrate_mm_sec; | ||||
|     } | ||||
| 
 | ||||
|     float GCodeTimeEstimator::get_minimum_feedrate() const | ||||
|     { | ||||
|         return _state.minimum_feedrate; | ||||
|         return m_state.minimum_feedrate; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_minimum_travel_feedrate(float feedrate_mm_sec) | ||||
|     { | ||||
|         _state.minimum_travel_feedrate = feedrate_mm_sec; | ||||
|         m_state.minimum_travel_feedrate = feedrate_mm_sec; | ||||
|     } | ||||
| 
 | ||||
|     float GCodeTimeEstimator::get_minimum_travel_feedrate() const | ||||
|     { | ||||
|         return _state.minimum_travel_feedrate; | ||||
|         return m_state.minimum_travel_feedrate; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_filament_load_times(const std::vector<double> &filament_load_times) | ||||
|     { | ||||
|         _state.filament_load_times.clear(); | ||||
|         m_state.filament_load_times.clear(); | ||||
|         for (double t : filament_load_times) | ||||
|             _state.filament_load_times.push_back((float)t); | ||||
|             m_state.filament_load_times.push_back((float)t); | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_filament_unload_times(const std::vector<double> &filament_unload_times) | ||||
|     { | ||||
|         _state.filament_unload_times.clear(); | ||||
|         m_state.filament_unload_times.clear(); | ||||
|         for (double t : filament_unload_times) | ||||
|             _state.filament_unload_times.push_back((float)t); | ||||
|             m_state.filament_unload_times.push_back((float)t); | ||||
|     } | ||||
| 
 | ||||
|     float GCodeTimeEstimator::get_filament_load_time(unsigned int id_extruder) | ||||
|     { | ||||
|         return | ||||
|             (_state.filament_load_times.empty() || id_extruder == _state.extruder_id_unloaded) ?  | ||||
|             (m_state.filament_load_times.empty() || id_extruder == m_state.extruder_id_unloaded) ?  | ||||
|                 0 : | ||||
|                 (_state.filament_load_times.size() <= id_extruder) ? | ||||
|                     _state.filament_load_times.front() :  | ||||
|                     _state.filament_load_times[id_extruder]; | ||||
|                 (m_state.filament_load_times.size() <= id_extruder) ? | ||||
|                     m_state.filament_load_times.front() :  | ||||
|                     m_state.filament_load_times[id_extruder]; | ||||
|     } | ||||
| 
 | ||||
|     float GCodeTimeEstimator::get_filament_unload_time(unsigned int id_extruder) | ||||
|     { | ||||
|         return | ||||
|             (_state.filament_unload_times.empty() || id_extruder == _state.extruder_id_unloaded) ?  | ||||
|             (m_state.filament_unload_times.empty() || id_extruder == m_state.extruder_id_unloaded) ?  | ||||
|                 0 : | ||||
|                 (_state.filament_unload_times.size() <= id_extruder) ? | ||||
|                     _state.filament_unload_times.front() :  | ||||
|                     _state.filament_unload_times[id_extruder]; | ||||
|                 (m_state.filament_unload_times.size() <= id_extruder) ? | ||||
|                     m_state.filament_unload_times.front() :  | ||||
|                     m_state.filament_unload_times[id_extruder]; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_extrude_factor_override_percentage(float percentage) | ||||
|     { | ||||
|         _state.extrude_factor_override_percentage = percentage; | ||||
|         m_state.extrude_factor_override_percentage = percentage; | ||||
|     } | ||||
| 
 | ||||
|     float GCodeTimeEstimator::get_extrude_factor_override_percentage() const | ||||
|     { | ||||
|         return _state.extrude_factor_override_percentage; | ||||
|         return m_state.extrude_factor_override_percentage; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_dialect(GCodeFlavor dialect) | ||||
|     { | ||||
|         _state.dialect = dialect; | ||||
|         m_state.dialect = dialect; | ||||
|     } | ||||
| 
 | ||||
|     GCodeFlavor GCodeTimeEstimator::get_dialect() const | ||||
|     { | ||||
|         PROFILE_FUNC(); | ||||
|         return _state.dialect; | ||||
|         return m_state.dialect; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_units(GCodeTimeEstimator::EUnits units) | ||||
|     { | ||||
|         _state.units = units; | ||||
|         m_state.units = units; | ||||
|     } | ||||
| 
 | ||||
|     GCodeTimeEstimator::EUnits GCodeTimeEstimator::get_units() const | ||||
|     { | ||||
|         return _state.units; | ||||
|         return m_state.units; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_global_positioning_type(GCodeTimeEstimator::EPositioningType type) | ||||
|     { | ||||
|         _state.global_positioning_type = type; | ||||
|         m_state.global_positioning_type = type; | ||||
|     } | ||||
| 
 | ||||
|     GCodeTimeEstimator::EPositioningType GCodeTimeEstimator::get_global_positioning_type() const | ||||
|     { | ||||
|         return _state.global_positioning_type; | ||||
|         return m_state.global_positioning_type; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_e_local_positioning_type(GCodeTimeEstimator::EPositioningType type) | ||||
|     { | ||||
|         _state.e_local_positioning_type = type; | ||||
|         m_state.e_local_positioning_type = type; | ||||
|     } | ||||
| 
 | ||||
|     GCodeTimeEstimator::EPositioningType GCodeTimeEstimator::get_e_local_positioning_type() const | ||||
|     { | ||||
|         return _state.e_local_positioning_type; | ||||
|         return m_state.e_local_positioning_type; | ||||
|     } | ||||
| 
 | ||||
|     int GCodeTimeEstimator::get_g1_line_id() const | ||||
|     { | ||||
|         return _state.g1_line_id; | ||||
|         return m_state.g1_line_id; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::increment_g1_line_id() | ||||
|     { | ||||
|         ++_state.g1_line_id; | ||||
|         ++m_state.g1_line_id; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::reset_g1_line_id() | ||||
|     { | ||||
|         _state.g1_line_id = 0; | ||||
|         m_state.g1_line_id = 0; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_extruder_id(unsigned int id) | ||||
|     { | ||||
|         _state.extruder_id = id; | ||||
|         m_state.extruder_id = id; | ||||
|     } | ||||
| 
 | ||||
|     unsigned int GCodeTimeEstimator::get_extruder_id() const | ||||
|     { | ||||
|         return _state.extruder_id; | ||||
|         return m_state.extruder_id; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::reset_extruder_id() | ||||
|     { | ||||
|         // Set the initial extruder ID to unknown. For the multi-material setup it means
 | ||||
|         // that all the filaments are parked in the MMU and no filament is loaded yet.
 | ||||
|         _state.extruder_id = _state.extruder_id_unloaded; | ||||
|         m_state.extruder_id = m_state.extruder_id_unloaded; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::add_additional_time(float timeSec) | ||||
|     { | ||||
|         PROFILE_FUNC(); | ||||
|         _state.additional_time += timeSec; | ||||
|         m_state.additional_time += timeSec; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_additional_time(float timeSec) | ||||
|     { | ||||
|         _state.additional_time = timeSec; | ||||
|         m_state.additional_time = timeSec; | ||||
|     } | ||||
| 
 | ||||
|     float GCodeTimeEstimator::get_additional_time() const | ||||
|     { | ||||
|         return _state.additional_time; | ||||
|         return m_state.additional_time; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_default() | ||||
|  | @ -648,8 +660,8 @@ namespace Slic3r { | |||
|             set_axis_max_jerk(axis, DEFAULT_AXIS_MAX_JERK[a]); | ||||
|         } | ||||
| 
 | ||||
|         _state.filament_load_times.clear(); | ||||
|         _state.filament_unload_times.clear(); | ||||
|         m_state.filament_load_times.clear(); | ||||
|         m_state.filament_unload_times.clear(); | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::reset() | ||||
|  | @ -664,7 +676,7 @@ namespace Slic3r { | |||
| 
 | ||||
|     float GCodeTimeEstimator::get_time() const | ||||
|     { | ||||
|         return _time; | ||||
|         return m_time; | ||||
|     } | ||||
| 
 | ||||
|     std::string GCodeTimeEstimator::get_time_dhms() const | ||||
|  | @ -677,19 +689,44 @@ namespace Slic3r { | |||
|         return _get_time_minutes(get_time()); | ||||
|     } | ||||
| 
 | ||||
|     std::vector<float> GCodeTimeEstimator::get_color_times() const | ||||
|     { | ||||
|         return m_color_times; | ||||
|     } | ||||
| 
 | ||||
|     std::vector<std::string> GCodeTimeEstimator::get_color_times_dhms() const | ||||
|     { | ||||
|         std::vector<std::string> ret; | ||||
|         for (float t : m_color_times) | ||||
|         { | ||||
|             ret.push_back(_get_time_dhms(t)); | ||||
|         } | ||||
|         return ret; | ||||
|     } | ||||
| 
 | ||||
|     std::vector<std::string> GCodeTimeEstimator::get_color_times_minutes() const | ||||
|     { | ||||
|         std::vector<std::string> ret; | ||||
|         for (float t : m_color_times) | ||||
|         { | ||||
|             ret.push_back(_get_time_minutes(t)); | ||||
|         } | ||||
|         return ret; | ||||
|     } | ||||
| 
 | ||||
|     // Return an estimate of the memory consumed by the time estimator.
 | ||||
| 	size_t GCodeTimeEstimator::memory_used() const | ||||
|     { | ||||
|         size_t out = sizeof(*this); | ||||
| 		out += SLIC3R_STDVEC_MEMSIZE(this->_blocks, Block); | ||||
| 		out += SLIC3R_STDVEC_MEMSIZE(this->_g1_line_ids, G1LineIdToBlockId); | ||||
| 		out += SLIC3R_STDVEC_MEMSIZE(this->m_blocks, Block); | ||||
| 		out += SLIC3R_STDVEC_MEMSIZE(this->m_g1_line_ids, G1LineIdToBlockId); | ||||
|         return out; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::_reset() | ||||
|     { | ||||
|         _curr.reset(); | ||||
|         _prev.reset(); | ||||
|         m_curr.reset(); | ||||
|         m_prev.reset(); | ||||
| 
 | ||||
|         set_axis_position(X, 0.0f); | ||||
|         set_axis_position(Y, 0.0f); | ||||
|  | @ -701,19 +738,23 @@ namespace Slic3r { | |||
| 
 | ||||
|         reset_extruder_id(); | ||||
|         reset_g1_line_id(); | ||||
|         _g1_line_ids.clear(); | ||||
|         m_g1_line_ids.clear(); | ||||
| 
 | ||||
|         _last_st_synchronized_block_id = -1; | ||||
|         m_last_st_synchronized_block_id = -1; | ||||
| 
 | ||||
|         m_needs_color_times = false; | ||||
|         m_color_times.clear(); | ||||
|         m_color_time_cache = 0.0f; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::_reset_time() | ||||
|     { | ||||
|         _time = 0.0f; | ||||
|         m_time = 0.0f; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::_reset_blocks() | ||||
|     { | ||||
|         _blocks.clear(); | ||||
|         m_blocks.clear(); | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::_calculate_time() | ||||
|  | @ -723,35 +764,32 @@ namespace Slic3r { | |||
|         _reverse_pass(); | ||||
|         _recalculate_trapezoids(); | ||||
| 
 | ||||
|         _time += get_additional_time(); | ||||
|         m_time += get_additional_time(); | ||||
|         m_color_time_cache += get_additional_time(); | ||||
| 
 | ||||
|         for (int i = _last_st_synchronized_block_id + 1; i < (int)_blocks.size(); ++i) | ||||
|         for (int i = m_last_st_synchronized_block_id + 1; i < (int)m_blocks.size(); ++i) | ||||
|         { | ||||
|             Block& block = _blocks[i]; | ||||
| 
 | ||||
| #if ENABLE_MOVE_STATS | ||||
|             Block& block = m_blocks[i]; | ||||
|             float block_time = 0.0f; | ||||
|             block_time += block.acceleration_time(); | ||||
|             block_time += block.cruise_time(); | ||||
|             block_time += block.deceleration_time(); | ||||
|             _time += block_time; | ||||
|             block.elapsed_time = _time; | ||||
|             m_time += block_time; | ||||
|             block.elapsed_time = m_time; | ||||
| 
 | ||||
| #if ENABLE_MOVE_STATS | ||||
|             MovesStatsMap::iterator it = _moves_stats.find(block.move_type); | ||||
|             if (it == _moves_stats.end()) | ||||
|                 it = _moves_stats.insert(MovesStatsMap::value_type(block.move_type, MoveStats())).first; | ||||
| 
 | ||||
|             it->second.count += 1; | ||||
|             it->second.time += block_time; | ||||
| #else | ||||
|             _time += block.acceleration_time(); | ||||
|             _time += block.cruise_time(); | ||||
|             _time += block.deceleration_time(); | ||||
|             block.elapsed_time = _time; | ||||
| #endif // ENABLE_MOVE_STATS
 | ||||
| 
 | ||||
|             m_color_time_cache += block_time; | ||||
|         } | ||||
| 
 | ||||
|         _last_st_synchronized_block_id = (int)_blocks.size() - 1; | ||||
|         m_last_st_synchronized_block_id = (int)m_blocks.size() - 1; | ||||
|         // The additional time has been consumed (added to the total time), reset it to zero.
 | ||||
|         set_additional_time(0.); | ||||
|     } | ||||
|  | @ -866,6 +904,11 @@ namespace Slic3r { | |||
|                             _processM566(line); | ||||
|                             break; | ||||
|                         } | ||||
|                     case 600: // Set color change
 | ||||
|                         { | ||||
|                             _processM600(line); | ||||
|                             break; | ||||
|                         } | ||||
|                     case 702: // MK3 MMU2: Process the final filament unload.
 | ||||
|                         { | ||||
|                             _processM702(line); | ||||
|  | @ -934,7 +977,7 @@ namespace Slic3r { | |||
|             return; | ||||
| 
 | ||||
|         // calculates block feedrate
 | ||||
|         _curr.feedrate = std::max(get_feedrate(), block.is_travel_move() ? get_minimum_travel_feedrate() : get_minimum_feedrate()); | ||||
|         m_curr.feedrate = std::max(get_feedrate(), block.is_travel_move() ? get_minimum_travel_feedrate() : get_minimum_feedrate()); | ||||
| 
 | ||||
|         float distance = block.move_length(); | ||||
|         float invDistance = 1.0f / distance; | ||||
|  | @ -942,23 +985,23 @@ namespace Slic3r { | |||
|         float min_feedrate_factor = 1.0f; | ||||
|         for (unsigned char a = X; a < Num_Axis; ++a) | ||||
|         { | ||||
|             _curr.axis_feedrate[a] = _curr.feedrate * block.delta_pos[a] * invDistance; | ||||
|             m_curr.axis_feedrate[a] = m_curr.feedrate * block.delta_pos[a] * invDistance; | ||||
|             if (a == E) | ||||
|                 _curr.axis_feedrate[a] *= get_extrude_factor_override_percentage(); | ||||
|                 m_curr.axis_feedrate[a] *= get_extrude_factor_override_percentage(); | ||||
| 
 | ||||
|             _curr.abs_axis_feedrate[a] = std::abs(_curr.axis_feedrate[a]); | ||||
|             if (_curr.abs_axis_feedrate[a] > 0.0f) | ||||
|                 min_feedrate_factor = std::min(min_feedrate_factor, get_axis_max_feedrate((EAxis)a) / _curr.abs_axis_feedrate[a]); | ||||
|             m_curr.abs_axis_feedrate[a] = std::abs(m_curr.axis_feedrate[a]); | ||||
|             if (m_curr.abs_axis_feedrate[a] > 0.0f) | ||||
|                 min_feedrate_factor = std::min(min_feedrate_factor, get_axis_max_feedrate((EAxis)a) / m_curr.abs_axis_feedrate[a]); | ||||
|         } | ||||
|          | ||||
|         block.feedrate.cruise = min_feedrate_factor * _curr.feedrate; | ||||
|         block.feedrate.cruise = min_feedrate_factor * m_curr.feedrate; | ||||
| 
 | ||||
|         if (min_feedrate_factor < 1.0f) | ||||
|         { | ||||
|             for (unsigned char a = X; a < Num_Axis; ++a) | ||||
|             { | ||||
|                 _curr.axis_feedrate[a] *= min_feedrate_factor; | ||||
|                 _curr.abs_axis_feedrate[a] *= min_feedrate_factor; | ||||
|                 m_curr.axis_feedrate[a] *= min_feedrate_factor; | ||||
|                 m_curr.abs_axis_feedrate[a] *= min_feedrate_factor; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|  | @ -975,25 +1018,25 @@ namespace Slic3r { | |||
|         block.acceleration = acceleration; | ||||
| 
 | ||||
|         // calculates block exit feedrate
 | ||||
|         _curr.safe_feedrate = block.feedrate.cruise; | ||||
|         m_curr.safe_feedrate = block.feedrate.cruise; | ||||
| 
 | ||||
|         for (unsigned char a = X; a < Num_Axis; ++a) | ||||
|         { | ||||
|             float axis_max_jerk = get_axis_max_jerk((EAxis)a); | ||||
|             if (_curr.abs_axis_feedrate[a] > axis_max_jerk) | ||||
|                 _curr.safe_feedrate = std::min(_curr.safe_feedrate, axis_max_jerk); | ||||
|             if (m_curr.abs_axis_feedrate[a] > axis_max_jerk) | ||||
|                 m_curr.safe_feedrate = std::min(m_curr.safe_feedrate, axis_max_jerk); | ||||
|         } | ||||
| 
 | ||||
|         block.feedrate.exit = _curr.safe_feedrate; | ||||
|         block.feedrate.exit = m_curr.safe_feedrate; | ||||
| 
 | ||||
|         // calculates block entry feedrate
 | ||||
|         float vmax_junction = _curr.safe_feedrate; | ||||
|         if (!_blocks.empty() && (_prev.feedrate > PREVIOUS_FEEDRATE_THRESHOLD)) | ||||
|         float vmax_junction = m_curr.safe_feedrate; | ||||
|         if (!m_blocks.empty() && (m_prev.feedrate > PREVIOUS_FEEDRATE_THRESHOLD)) | ||||
|         { | ||||
|             bool prev_speed_larger = _prev.feedrate > block.feedrate.cruise; | ||||
|             float smaller_speed_factor = prev_speed_larger ? (block.feedrate.cruise / _prev.feedrate) : (_prev.feedrate / block.feedrate.cruise); | ||||
|             bool prev_speed_larger = m_prev.feedrate > block.feedrate.cruise; | ||||
|             float smaller_speed_factor = prev_speed_larger ? (block.feedrate.cruise / m_prev.feedrate) : (m_prev.feedrate / block.feedrate.cruise); | ||||
|             // Pick the smaller of the nominal speeds. Higher speed shall not be achieved at the junction during coasting.
 | ||||
|             vmax_junction = prev_speed_larger ? block.feedrate.cruise : _prev.feedrate; | ||||
|             vmax_junction = prev_speed_larger ? block.feedrate.cruise : m_prev.feedrate; | ||||
| 
 | ||||
|             float v_factor = 1.0f; | ||||
|             bool limited = false; | ||||
|  | @ -1001,8 +1044,8 @@ namespace Slic3r { | |||
|             for (unsigned char a = X; a < Num_Axis; ++a) | ||||
|             { | ||||
|                 // Limit an axis. We have to differentiate coasting from the reversal of an axis movement, or a full stop.
 | ||||
|                 float v_exit = _prev.axis_feedrate[a]; | ||||
|                 float v_entry = _curr.axis_feedrate[a]; | ||||
|                 float v_exit = m_prev.axis_feedrate[a]; | ||||
|                 float v_entry = m_curr.axis_feedrate[a]; | ||||
| 
 | ||||
|                 if (prev_speed_larger) | ||||
|                     v_exit *= smaller_speed_factor; | ||||
|  | @ -1044,23 +1087,23 @@ namespace Slic3r { | |||
|             float vmax_junction_threshold = vmax_junction * 0.99f; | ||||
| 
 | ||||
|             // Not coasting. The machine will stop and start the movements anyway, better to start the segment from start.
 | ||||
|             if ((_prev.safe_feedrate > vmax_junction_threshold) && (_curr.safe_feedrate > vmax_junction_threshold)) | ||||
|                 vmax_junction = _curr.safe_feedrate; | ||||
|             if ((m_prev.safe_feedrate > vmax_junction_threshold) && (m_curr.safe_feedrate > vmax_junction_threshold)) | ||||
|                 vmax_junction = m_curr.safe_feedrate; | ||||
|         } | ||||
| 
 | ||||
|         float v_allowable = Block::max_allowable_speed(-acceleration, _curr.safe_feedrate, distance); | ||||
|         float v_allowable = Block::max_allowable_speed(-acceleration, m_curr.safe_feedrate, distance); | ||||
|         block.feedrate.entry = std::min(vmax_junction, v_allowable); | ||||
| 
 | ||||
|         block.max_entry_speed = vmax_junction; | ||||
|         block.flags.nominal_length = (block.feedrate.cruise <= v_allowable); | ||||
|         block.flags.recalculate = true; | ||||
|         block.safe_feedrate = _curr.safe_feedrate; | ||||
|         block.safe_feedrate = m_curr.safe_feedrate; | ||||
| 
 | ||||
|         // calculates block trapezoid
 | ||||
|         block.calculate_trapezoid(); | ||||
| 
 | ||||
|         // updates previous
 | ||||
|         _prev = _curr; | ||||
|         m_prev = m_curr; | ||||
| 
 | ||||
|         // updates axis positions
 | ||||
|         for (unsigned char a = X; a < Num_Axis; ++a) | ||||
|  | @ -1091,8 +1134,8 @@ namespace Slic3r { | |||
| #endif // ENABLE_MOVE_STATS
 | ||||
| 
 | ||||
|         // adds block to blocks list
 | ||||
|         _blocks.emplace_back(block); | ||||
|         _g1_line_ids.emplace_back(G1LineIdToBlockIdMap::value_type(get_g1_line_id(), (unsigned int)_blocks.size() - 1)); | ||||
|         m_blocks.emplace_back(block); | ||||
|         m_g1_line_ids.emplace_back(G1LineIdToBlockIdMap::value_type(get_g1_line_id(), (unsigned int)m_blocks.size() - 1)); | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::_processG4(const GCodeReader::GCodeLine& line) | ||||
|  | @ -1336,6 +1379,18 @@ namespace Slic3r { | |||
|             set_axis_max_jerk(E, line.e() * MMMIN_TO_MMSEC); | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::_processM600(const GCodeReader::GCodeLine& line) | ||||
|     { | ||||
|         PROFILE_FUNC(); | ||||
|         m_needs_color_times = true; | ||||
|         _calculate_time(); | ||||
|         if (m_color_time_cache != 0.0f) | ||||
|         { | ||||
|             m_color_times.push_back(m_color_time_cache); | ||||
|             m_color_time_cache = 0.0f; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::_processM702(const GCodeReader::GCodeLine& line) | ||||
|     { | ||||
|         PROFILE_FUNC(); | ||||
|  | @ -1376,11 +1431,11 @@ namespace Slic3r { | |||
|     void GCodeTimeEstimator::_forward_pass() | ||||
|     { | ||||
|         PROFILE_FUNC(); | ||||
|         if (_blocks.size() > 1) | ||||
|         if (m_blocks.size() > 1) | ||||
|         { | ||||
|             for (int i = _last_st_synchronized_block_id + 1; i < (int)_blocks.size() - 1; ++i) | ||||
|             for (int i = m_last_st_synchronized_block_id + 1; i < (int)m_blocks.size() - 1; ++i) | ||||
|             { | ||||
|                 _planner_forward_pass_kernel(_blocks[i], _blocks[i + 1]); | ||||
|                 _planner_forward_pass_kernel(m_blocks[i], m_blocks[i + 1]); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | @ -1388,11 +1443,11 @@ namespace Slic3r { | |||
|     void GCodeTimeEstimator::_reverse_pass() | ||||
|     { | ||||
|         PROFILE_FUNC(); | ||||
|         if (_blocks.size() > 1) | ||||
|         if (m_blocks.size() > 1) | ||||
|         { | ||||
|             for (int i = (int)_blocks.size() - 1; i >= _last_st_synchronized_block_id + 2; --i) | ||||
|             for (int i = (int)m_blocks.size() - 1; i >= m_last_st_synchronized_block_id + 2; --i) | ||||
|             { | ||||
|                 _planner_reverse_pass_kernel(_blocks[i - 1], _blocks[i]); | ||||
|                 _planner_reverse_pass_kernel(m_blocks[i - 1], m_blocks[i]); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | @ -1444,9 +1499,9 @@ namespace Slic3r { | |||
|         Block* curr = nullptr; | ||||
|         Block* next = nullptr; | ||||
| 
 | ||||
|         for (int i = _last_st_synchronized_block_id + 1; i < (int)_blocks.size(); ++i) | ||||
|         for (int i = m_last_st_synchronized_block_id + 1; i < (int)m_blocks.size(); ++i) | ||||
|         { | ||||
|             Block& b = _blocks[i]; | ||||
|             Block& b = m_blocks[i]; | ||||
| 
 | ||||
|             curr = next; | ||||
|             next = &b; | ||||
|  | @ -1517,7 +1572,7 @@ namespace Slic3r { | |||
|         { | ||||
|             std::cout << MOVE_TYPE_STR[move.first]; | ||||
|             std::cout << ": count " << move.second.count << " (" << 100.0f * (float)move.second.count / moves_count << "%)"; | ||||
|             std::cout << " - time: " << move.second.time << "s (" << 100.0f * move.second.time / _time << "%)"; | ||||
|             std::cout << " - time: " << move.second.time << "s (" << 100.0f * move.second.time / m_time << "%)"; | ||||
|             std::cout << std::endl; | ||||
|         } | ||||
|         std::cout << std::endl; | ||||
|  |  | |||
|  | @ -215,17 +215,22 @@ namespace Slic3r { | |||
|         typedef std::vector<G1LineIdToBlockId> G1LineIdToBlockIdMap; | ||||
| 
 | ||||
|     private: | ||||
|         EMode _mode; | ||||
|         GCodeReader _parser; | ||||
|         State _state; | ||||
|         Feedrates _curr; | ||||
|         Feedrates _prev; | ||||
|         BlocksList _blocks; | ||||
|         EMode m_mode; | ||||
|         GCodeReader m_parser; | ||||
|         State m_state; | ||||
|         Feedrates m_curr; | ||||
|         Feedrates m_prev; | ||||
|         BlocksList m_blocks; | ||||
|         // Map between g1 line id and blocks id, used to speed up export of remaining times
 | ||||
|         G1LineIdToBlockIdMap _g1_line_ids; | ||||
|         G1LineIdToBlockIdMap m_g1_line_ids; | ||||
|         // Index of the last block already st_synchronized
 | ||||
|         int _last_st_synchronized_block_id; | ||||
|         float _time; // s
 | ||||
|         int m_last_st_synchronized_block_id; | ||||
|         float m_time; // s
 | ||||
| 
 | ||||
|         // data to calculate color print times
 | ||||
|         bool m_needs_color_times; | ||||
|         std::vector<float> m_color_times; | ||||
|         float m_color_time_cache; | ||||
| 
 | ||||
| #if ENABLE_MOVE_STATS | ||||
|         MovesStatsMap _moves_stats; | ||||
|  | @ -341,6 +346,15 @@ namespace Slic3r { | |||
|         // Returns the estimated time, in minutes (integer)
 | ||||
|         std::string get_time_minutes() const; | ||||
| 
 | ||||
|        // Returns the estimated time, in seconds, for each color
 | ||||
|         std::vector<float> get_color_times() const; | ||||
| 
 | ||||
|         // Returns the estimated time, in format DDd HHh MMm SSs, for each color
 | ||||
|         std::vector<std::string> get_color_times_dhms() const; | ||||
| 
 | ||||
|         // Returns the estimated time, in minutes (integer), for each color
 | ||||
|         std::vector<std::string> get_color_times_minutes() const; | ||||
| 
 | ||||
|         // Return an estimate of the memory consumed by the time estimator.
 | ||||
|         size_t memory_used() const; | ||||
| 
 | ||||
|  | @ -409,6 +423,9 @@ namespace Slic3r { | |||
|         // Set allowable instantaneous speed change
 | ||||
|         void _processM566(const GCodeReader::GCodeLine& line); | ||||
| 
 | ||||
|         // Set color change
 | ||||
|         void _processM600(const GCodeReader::GCodeLine& line); | ||||
| 
 | ||||
|         // Unload the current filament into the MK3 MMU2 unit at the end of print.
 | ||||
|         void _processM702(const GCodeReader::GCodeLine& line); | ||||
| 
 | ||||
|  |  | |||
|  | @ -912,7 +912,7 @@ MedialAxis::build(ThickPolylines* polylines) | |||
|     } | ||||
|     */ | ||||
|      | ||||
|     typedef const VD::vertex_type vert_t; | ||||
|     //typedef const VD::vertex_type vert_t;
 | ||||
|     typedef const VD::edge_type   edge_t; | ||||
|      | ||||
|     // collect valid edges (i.e. prune those not belonging to MAT)
 | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
|  | @ -201,7 +201,7 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer) | |||
|         size_t n_groups = 0;  | ||||
|         for (size_t i = 0; i < bridges.size(); ++ i) { | ||||
|             // A grup id for this bridge.
 | ||||
|             size_t group_id = (bridge_group[i] == -1) ? (n_groups ++) : bridge_group[i]; | ||||
|             size_t group_id = (bridge_group[i] == size_t(-1)) ? (n_groups ++) : bridge_group[i]; | ||||
|             bridge_group[i] = group_id; | ||||
|             // For all possibly overlaping bridges:
 | ||||
|             for (size_t j = i + 1; j < bridges.size(); ++ j) { | ||||
|  | @ -210,7 +210,7 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer) | |||
|                 if (intersection(bridges_grown[i], bridges_grown[j], false).empty()) | ||||
|                     continue; | ||||
|                 // The two bridge regions intersect. Give them the same group id.
 | ||||
|                 if (bridge_group[j] != -1) { | ||||
|                 if (bridge_group[j] != size_t(-1)) { | ||||
|                     // The j'th bridge has been merged with some other bridge before.
 | ||||
|                     size_t group_id_new = bridge_group[j]; | ||||
|                     for (size_t k = 0; k < j; ++ k) | ||||
|  |  | |||
|  | @ -22,7 +22,6 @@ Linef3 transform(const Linef3& line, const Transform3d& t) | |||
| bool Line::intersection_infinite(const Line &other, Point* point) const | ||||
| { | ||||
|     Vec2d a1 = this->a.cast<double>(); | ||||
|     Vec2d a2 = other.a.cast<double>(); | ||||
|     Vec2d v12 = (other.a - this->a).cast<double>(); | ||||
|     Vec2d v1 = (this->b - this->a).cast<double>(); | ||||
|     Vec2d v2 = (other.b - other.a).cast<double>(); | ||||
|  |  | |||
|  | @ -1,11 +1,13 @@ | |||
| #ifndef MTUTILS_HPP | ||||
| #define MTUTILS_HPP | ||||
| 
 | ||||
| #include <atomic>     // for std::atomic_flag and memory orders
 | ||||
| #include <mutex>      // for std::lock_guard
 | ||||
| #include <functional> // for std::function
 | ||||
| #include <utility>    // for std::forward
 | ||||
| #include <atomic>       // for std::atomic_flag and memory orders
 | ||||
| #include <mutex>        // for std::lock_guard
 | ||||
| #include <functional>   // for std::function
 | ||||
| #include <utility>      // for std::forward
 | ||||
| #include <vector> | ||||
| #include <algorithm> | ||||
| #include <cmath> | ||||
| 
 | ||||
| #include "libslic3r.h" | ||||
| #include "Point.hpp" | ||||
|  | @ -242,6 +244,58 @@ template<class C> bool all_of(const C &container) | |||
|                        }); | ||||
| } | ||||
| 
 | ||||
| template<class T> struct remove_cvref | ||||
| { | ||||
|     using type = | ||||
|         typename std::remove_cv<typename std::remove_reference<T>::type>::type; | ||||
| }; | ||||
| 
 | ||||
| template<class T> using remove_cvref_t = typename remove_cvref<T>::type; | ||||
| 
 | ||||
| template<template<class> class C, class T> | ||||
| class Container : public C<remove_cvref_t<T>> | ||||
| { | ||||
| public: | ||||
|     explicit Container(size_t count, T &&initval) | ||||
|         : C<remove_cvref_t<T>>(count, initval) | ||||
|     {} | ||||
| }; | ||||
| 
 | ||||
| template<class T> using DefaultContainer = std::vector<T>; | ||||
| 
 | ||||
| /// Exactly like Matlab https://www.mathworks.com/help/matlab/ref/linspace.html
 | ||||
| template<class T, class I, template<class> class C = DefaultContainer> | ||||
| inline C<remove_cvref_t<T>> linspace(const T &start, const T &stop, const I &n) | ||||
| { | ||||
|     Container<C, T> vals(n, T()); | ||||
| 
 | ||||
|     T      stride = (stop - start) / n; | ||||
|     size_t i      = 0; | ||||
|     std::generate(vals.begin(), vals.end(), [&i, start, stride] { | ||||
|         return start + i++ * stride; | ||||
|     }); | ||||
| 
 | ||||
|     return vals; | ||||
| } | ||||
| 
 | ||||
| /// A set of equidistant values starting from 'start' (inclusive), ending
 | ||||
| /// in the closest multiple of 'stride' less than or equal to 'end' and
 | ||||
| /// leaving 'stride' space between each value. 
 | ||||
| /// Very similar to Matlab [start:stride:end] notation.
 | ||||
| template<class T, template<class> class C = DefaultContainer> | ||||
| inline C<remove_cvref_t<T>> grid(const T &start, const T &stop, const T &stride) | ||||
| { | ||||
|     Container<C, T> vals(size_t(std::ceil((stop - start) / stride)), T()); | ||||
|      | ||||
|     int i = 0; | ||||
|     std::generate(vals.begin(), vals.end(), [&i, start, stride] { | ||||
|         return start + i++ * stride;  | ||||
|     }); | ||||
|       | ||||
|     return vals; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| // A shorter C++14 style form of the enable_if metafunction
 | ||||
| template<bool B, class T> | ||||
| using enable_if_t = typename std::enable_if<B, T>::type; | ||||
|  | @ -304,7 +358,7 @@ inline SLIC3R_CONSTEXPR ScaledCoordOnly<Tout> scaled(const Tin &v) SLIC3R_NOEXCE | |||
| template<class Tout = coord_t, class Tin, int N, class = FloatingOnly<Tin>> | ||||
| inline EigenVec<ArithmeticOnly<Tout>, N> scaled(const EigenVec<Tin, N> &v) | ||||
| { | ||||
|     return v.template cast<Tout>() / SCALING_FACTOR; | ||||
|     return (v / SCALING_FACTOR).template cast<Tout>(); | ||||
| } | ||||
| 
 | ||||
| // Conversion from arithmetic scaled type to floating point unscaled
 | ||||
|  |  | |||
|  | @ -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,14 +620,18 @@ 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_height_ranges         = rhs.layer_height_ranges; | ||||
|     this->layer_config_ranges         = rhs.layer_config_ranges;    // #ys_FIXME_experiment
 | ||||
|     this->layer_height_profile        = rhs.layer_height_profile; | ||||
|     this->origin_translation          = rhs.origin_translation; | ||||
|     m_bounding_box                    = rhs.m_bounding_box; | ||||
|  | @ -658,14 +660,17 @@ 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_height_ranges         = std::move(rhs.layer_height_ranges); | ||||
|     this->layer_config_ranges         = std::move(rhs.layer_config_ranges); // #ys_FIXME_experiment
 | ||||
|     this->layer_height_profile        = std::move(rhs.layer_height_profile); | ||||
|     this->origin_translation          = std::move(rhs.origin_translation); | ||||
|     m_bounding_box                    = std::move(rhs.m_bounding_box); | ||||
|  | @ -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) | ||||
|  | @ -1845,7 +1859,7 @@ bool model_volume_list_changed(const ModelObject &model_object_old, const ModelO | |||
|         if (!mv_old.get_matrix().isApprox(mv_new.get_matrix())) | ||||
|             return true; | ||||
| 
 | ||||
|         ++i_old; | ||||
|         ++ i_old; | ||||
|         ++ i_new; | ||||
|     } | ||||
|     for (; i_old < model_object_old.volumes.size(); ++ i_old) { | ||||
|  | @ -1863,25 +1877,55 @@ bool model_volume_list_changed(const ModelObject &model_object_old, const ModelO | |||
|     return false; | ||||
| } | ||||
| 
 | ||||
| extern bool model_has_multi_part_objects(const Model &model) | ||||
| { | ||||
|     for (const ModelObject *model_object : model.objects) | ||||
|     	if (model_object->volumes.size() != 1 || ! model_object->volumes.front()->is_model_part()) | ||||
|     		return true; | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| extern bool model_has_advanced_features(const Model &model) | ||||
| { | ||||
| 	auto config_is_advanced = [](const DynamicPrintConfig &config) { | ||||
|         return ! (config.empty() || (config.size() == 1 && config.cbegin()->first == "extruder")); | ||||
| 	}; | ||||
|     for (const ModelObject *model_object : model.objects) { | ||||
|         // Is there more than one instance or advanced config data?
 | ||||
|         if (model_object->instances.size() > 1 || config_is_advanced(model_object->config)) | ||||
|         	return true; | ||||
|         // Is there any modifier or advanced config data?
 | ||||
|         for (const ModelVolume* model_volume : model_object->volumes) | ||||
|             if (! model_volume->is_model_part() || config_is_advanced(model_volume->config)) | ||||
|             	return true; | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| #ifndef NDEBUG | ||||
| // 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 +1936,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 +1953,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,29 @@ | |||
| #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 cereal { | ||||
| 	class BinaryInputArchive; | ||||
| 	class BinaryOutputArchive; | ||||
| 	template <class T> void load_optional(BinaryInputArchive &ar, std::shared_ptr<const T> &ptr); | ||||
| 	template <class T> void save_optional(BinaryOutputArchive &ar, const std::shared_ptr<const T> &ptr); | ||||
| 	template <class T> void load_by_value(BinaryInputArchive &ar, T &obj); | ||||
| 	template <class T> void save_by_value(BinaryOutputArchive &ar, const T &obj); | ||||
| } | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
|  | @ -23,9 +33,57 @@ class ModelInstance; | |||
| class ModelMaterial; | ||||
| class ModelObject; | ||||
| class ModelVolume; | ||||
| class ModelWipeTower; | ||||
| 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)); | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| namespace Internal { | ||||
| 	template<typename T> | ||||
| 	class StaticSerializationWrapper | ||||
| 	{ | ||||
| 	public: | ||||
| 		StaticSerializationWrapper(T &wrap) : wrapped(wrap) {} | ||||
| 	private: | ||||
| 		friend class cereal::access; | ||||
| 		friend class UndoRedo::StackImpl; | ||||
| 		template<class Archive> void load(Archive &ar) { cereal::load_by_value(ar, wrapped); } | ||||
| 		template<class Archive> void save(Archive &ar) const { cereal::save_by_value(ar, wrapped); } | ||||
| 		T&	wrapped; | ||||
| 	}; | ||||
| } | ||||
| 
 | ||||
| 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 +93,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 +107,63 @@ 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()); | ||||
| 		Internal::StaticSerializationWrapper<ModelConfig> config_wrapper(config); | ||||
| 		ar(attributes, config_wrapper); | ||||
| 		// 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 +173,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,9 +185,9 @@ public: | |||
|     // ModelVolumes are owned by this ModelObject.
 | ||||
|     ModelVolumePtrs         volumes; | ||||
|     // Configuration parameters specific to a single ModelObject, overriding the global Slic3r settings.
 | ||||
|     DynamicPrintConfig      config; | ||||
|     // Variation of a layer thickness for spans of Z coordinates.
 | ||||
|     t_layer_height_ranges   layer_height_ranges; | ||||
|     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.
 | ||||
|     // The pairs of <z, layer_height> are packed into a 1D array.
 | ||||
|     std::vector<coordf_t>   layer_height_profile; | ||||
|  | @ -264,7 +271,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 +295,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 +352,26 @@ 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)); | ||||
| 		Internal::StaticSerializationWrapper<ModelConfig> config_wrapper(config); | ||||
| 		ar(name, input_file, instances, volumes, config_wrapper, 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 +385,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 +433,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 +479,105 @@ 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 load(Archive &ar) { | ||||
| 		bool has_convex_hull; | ||||
| 		ar(name, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull); | ||||
| 		cereal::load_by_value(ar, config); | ||||
| 		assert(m_mesh); | ||||
| 		if (has_convex_hull) { | ||||
| 			cereal::load_optional(ar, m_convex_hull); | ||||
| 			if (! m_convex_hull && ! m_mesh->empty()) | ||||
| 				// The convex hull was released from the Undo / Redo stack to conserve memory. Recalculate it.
 | ||||
| 				this->calculate_convex_hull(); | ||||
| 		} else | ||||
| 			m_convex_hull.reset(); | ||||
| 	} | ||||
| 	template<class Archive> void save(Archive &ar) const { | ||||
| 		bool has_convex_hull = m_convex_hull.get() != nullptr; | ||||
| 		ar(name, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull); | ||||
| 		cereal::save_by_value(ar, config); | ||||
| 		if (has_convex_hull) | ||||
| 			cereal::save_optional(ar, m_convex_hull); | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| // 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 +643,7 @@ public: | |||
| protected: | ||||
|     friend class Print; | ||||
|     friend class SLAPrint; | ||||
|     friend class Model; | ||||
|     friend class ModelObject; | ||||
| 
 | ||||
|     explicit ModelInstance(const ModelInstance &rhs) = default; | ||||
|  | @ -566,15 +654,51 @@ 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); | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| class ModelWipeTower final : public ObjectBase | ||||
| { | ||||
| public: | ||||
| 	Vec2d		position; | ||||
| 	double 		rotation; | ||||
| 
 | ||||
| private: | ||||
| 	friend class cereal::access; | ||||
| 	friend class UndoRedo::StackImpl; | ||||
| 	friend class Model; | ||||
| 
 | ||||
|     // Constructors to be only called by derived classes.
 | ||||
|     // Default constructor to assign a unique ID.
 | ||||
|     explicit ModelWipeTower() {} | ||||
|     // Constructor with ignored int parameter to assign an invalid ID, to be replaced
 | ||||
|     // by an existing ID copied from elsewhere.
 | ||||
|     explicit ModelWipeTower(int) : ObjectBase(-1) {} | ||||
|     // Copy constructor copies the ID.
 | ||||
| 	explicit ModelWipeTower(const ModelWipeTower &cfg) = default; | ||||
| 
 | ||||
| 	// Disabled methods.
 | ||||
| 	ModelWipeTower(ModelWipeTower &&rhs) = delete; | ||||
| 	ModelWipeTower& operator=(const ModelWipeTower &rhs) = delete; | ||||
|     ModelWipeTower& operator=(ModelWipeTower &&rhs) = delete; | ||||
| 
 | ||||
|     // For serialization / deserialization of ModelWipeTower composed into another class into the Undo / Redo stack as a separate object.
 | ||||
|     template<typename Archive> void serialize(Archive &ar) { ar(position, rotation); } | ||||
| }; | ||||
| 
 | ||||
| // The print bed content.
 | ||||
|  | @ -582,7 +706,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; | ||||
| 
 | ||||
|  | @ -592,19 +716,21 @@ public: | |||
|     ModelMaterialMap    materials; | ||||
|     // Objects are owned by a model. Each model may have multiple instances, each instance having its own transformation (shift, scale, rotation).
 | ||||
|     ModelObjectPtrs     objects; | ||||
|     // Wipe tower object.
 | ||||
|     ModelWipeTower	    wipe_tower; | ||||
|      | ||||
|     // 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; } | ||||
|     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 +741,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 +759,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 +788,20 @@ 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) { | ||||
| 		Internal::StaticSerializationWrapper<ModelWipeTower> wipe_tower_wrapper(wipe_tower); | ||||
| 		ar(materials, objects, wipe_tower_wrapper); | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| #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.
 | ||||
|  | @ -680,12 +815,24 @@ extern bool model_object_list_extended(const Model &model_old, const Model &mode | |||
| // than the old ModelObject.
 | ||||
| extern bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const ModelVolumeType type); | ||||
| 
 | ||||
| // If the model has multi-part objects, then it is currently not supported by the SLA mode.
 | ||||
| // Either the model cannot be loaded, or a SLA printer has to be activated.
 | ||||
| extern bool model_has_multi_part_objects(const Model &model); | ||||
| // If the model has advanced features, then it cannot be processed in simple mode.
 | ||||
| extern bool model_has_advanced_features(const Model &model); | ||||
| 
 | ||||
| #ifndef NDEBUG | ||||
| // Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique.
 | ||||
| void check_model_ids_validity(const Model &model); | ||||
| void check_model_ids_equal(const Model &model1, const Model &model2); | ||||
| #endif /* NDEBUG */ | ||||
| 
 | ||||
| } // namespace Slic3r
 | ||||
| 
 | ||||
| namespace cereal | ||||
| { | ||||
| 	template <class Archive> struct specialize<Archive, Slic3r::ModelVolume, cereal::specialization::member_load_save> {}; | ||||
| 	template <class Archive> struct specialize<Archive, Slic3r::ModelConfig, cereal::specialization::member_serialize> {}; | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
| #endif /* slic3r_Model_hpp_ */ | ||||
|  |  | |||
|  | @ -332,7 +332,7 @@ Polyline MotionPlannerGraph::shortest_path(size_t node_start, size_t node_end) c | |||
|         queue.pop(); | ||||
|         map_node_to_queue_id[u] = size_t(-1); | ||||
|         // Stop searching if we reached our destination.
 | ||||
|         if (u == node_end) | ||||
|         if (size_t(u) == node_end) | ||||
|             break; | ||||
|         // Visit each edge starting at node u.
 | ||||
|         for (const Neighbor& neighbor : m_adjacency_list[u]) | ||||
|  |  | |||
							
								
								
									
										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_ */ | ||||
|  | @ -175,7 +175,7 @@ void PerimeterGenerator::process() | |||
|                     const PerimeterGeneratorLoop &loop = contours_d[i]; | ||||
|                     // find the contour loop that contains it
 | ||||
|                     for (int t = d - 1; t >= 0; -- t) { | ||||
|                         for (int j = 0; j < contours[t].size(); ++ j) { | ||||
|                         for (size_t j = 0; j < contours[t].size(); ++ j) { | ||||
|                             PerimeterGeneratorLoop &candidate_parent = contours[t][j]; | ||||
|                             if (candidate_parent.polygon.contains(loop.polygon.first_point())) { | ||||
|                                 candidate_parent.children.push_back(loop); | ||||
|  | @ -397,7 +397,7 @@ static inline ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyli | |||
|                 pp.push_back(line.b); | ||||
|                 width.push_back(line.b_width); | ||||
|                  | ||||
|                 assert(pp.size() == segments + 1); | ||||
|                 assert(pp.size() == segments + 1u); | ||||
|                 assert(width.size() == segments*2); | ||||
|             } | ||||
|              | ||||
|  |  | |||
|  | @ -521,7 +521,6 @@ namespace client | |||
|         static void regex_op(expr &lhs, boost::iterator_range<Iterator> &rhs, char op) | ||||
|         { | ||||
|             const std::string *subject  = nullptr; | ||||
|             const std::string *mask     = nullptr; | ||||
|             if (lhs.type == TYPE_STRING) { | ||||
|                 // One type is string, the other could be converted to string.
 | ||||
|                 subject = &lhs.s(); | ||||
|  | @ -563,7 +562,6 @@ namespace client | |||
| 
 | ||||
|         static void ternary_op(expr &lhs, expr &rhs1, expr &rhs2) | ||||
|         { | ||||
|             bool value = false; | ||||
|             if (lhs.type != TYPE_BOOL) | ||||
|                 lhs.throw_exception("Not a boolean expression"); | ||||
|             if (lhs.b()) | ||||
|  | @ -975,7 +973,7 @@ namespace client | |||
|             // depending on the context->just_boolean_expression flag. This way a single static expression parser
 | ||||
|             // could serve both purposes.
 | ||||
|             start = eps[px::bind(&MyContext::evaluate_full_macro, _r1, _a)] > | ||||
|                 (       eps(_a==true) > text_block(_r1) [_val=_1] | ||||
|                 (       (eps(_a==true) > text_block(_r1) [_val=_1]) | ||||
|                     |   conditional_expression(_r1) [ px::bind(&expr<Iterator>::evaluate_boolean_to_string, _1, _val) ] | ||||
| 				) > eoi; | ||||
|             start.name("start"); | ||||
|  | @ -1245,7 +1243,7 @@ static std::string process_macro(const std::string &templ, client::MyContext &co | |||
|     std::string::const_iterator end  = templ.end(); | ||||
|     // Accumulator for the processed template.
 | ||||
|     std::string                 output; | ||||
|     bool res = phrase_parse(iter, end, macro_processor_instance(&context), space, output); | ||||
|     phrase_parse(iter, end, macro_processor_instance(&context), space, output); | ||||
| 	if (!context.error_message.empty()) { | ||||
|         if (context.error_message.back() != '\n' && context.error_message.back() != '\r') | ||||
|             context.error_message += '\n'; | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -61,7 +61,7 @@ Polylines PolylineCollection::_chained_path_from( | |||
|     while (! endpoints.empty()) { | ||||
|         // find nearest point
 | ||||
|         int endpoint_index = nearest_point_index<double>(endpoints, start_near, no_reverse); | ||||
|         assert(endpoint_index >= 0 && endpoint_index < endpoints.size() * 2); | ||||
|         assert(endpoint_index >= 0 && size_t(endpoint_index) < endpoints.size() * 2); | ||||
|         if (move_from_src) { | ||||
|             retval.push_back(std::move(src[endpoints[endpoint_index/2].idx])); | ||||
|         } else { | ||||
|  |  | |||
|  | @ -7,18 +7,20 @@ | |||
| #include "I18N.hpp" | ||||
| #include "SupportMaterial.hpp" | ||||
| #include "GCode.hpp" | ||||
| #include "GCode/WipeTowerPrusaMM.hpp" | ||||
| #include "GCode/WipeTower.hpp" | ||||
| #include "Utils.hpp" | ||||
| 
 | ||||
| //#include "PrintExport.hpp"
 | ||||
| 
 | ||||
| #include <float.h> | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <limits> | ||||
| #include <unordered_set> | ||||
| #include <boost/filesystem/path.hpp> | ||||
| #include <boost/log/trivial.hpp> | ||||
| 
 | ||||
| //! macro used to mark string used at localization, 
 | ||||
| //! macro used to mark string used at localization,
 | ||||
| //! return same string
 | ||||
| #define L(s) Slic3r::I18N::translate(s) | ||||
| 
 | ||||
|  | @ -41,36 +43,6 @@ void Print::clear() | |||
|     m_model.clear_objects(); | ||||
| } | ||||
| 
 | ||||
| // Only used by the Perl test cases.
 | ||||
| void Print::reload_object(size_t /* idx */) | ||||
| { | ||||
| 	ModelObjectPtrs model_objects; | ||||
| 	{ | ||||
| 		tbb::mutex::scoped_lock lock(this->state_mutex()); | ||||
|         // The following call should stop background processing if it is running.
 | ||||
|         this->invalidate_all_steps(); | ||||
| 		/* TODO: this method should check whether the per-object config and per-material configs
 | ||||
| 			have changed in such a way that regions need to be rearranged or we can just apply | ||||
| 			the diff and invalidate something.  Same logic as apply() | ||||
| 			For now we just re-add all objects since we haven't implemented this incremental logic yet. | ||||
| 			This should also check whether object volumes (parts) have changed. */ | ||||
| 		// collect all current model objects
 | ||||
| 		model_objects.reserve(m_objects.size()); | ||||
| 		for (PrintObject *object : m_objects) | ||||
| 			model_objects.push_back(object->model_object()); | ||||
| 		// remove our print objects
 | ||||
| 		for (PrintObject *object : m_objects) | ||||
| 			delete object; | ||||
| 		m_objects.clear(); | ||||
| 		for (PrintRegion *region : m_regions) | ||||
| 			delete region; | ||||
| 		m_regions.clear(); | ||||
| 	} | ||||
| 	// re-add model objects
 | ||||
|     for (ModelObject *mo : model_objects) | ||||
|         this->add_model_object(mo); | ||||
| } | ||||
| 
 | ||||
| PrintRegion* Print::add_region() | ||||
| { | ||||
|     m_regions.emplace_back(new PrintRegion(this)); | ||||
|  | @ -121,7 +93,6 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option | |||
|         "filament_density", | ||||
|         "filament_notes", | ||||
|         "filament_cost", | ||||
|         "filament_max_volumetric_speed", | ||||
|         "first_layer_acceleration", | ||||
|         "first_layer_bed_temperature", | ||||
|         "first_layer_speed", | ||||
|  | @ -216,6 +187,7 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option | |||
|             || opt_key == "filament_cooling_initial_speed" | ||||
|             || opt_key == "filament_cooling_final_speed" | ||||
|             || opt_key == "filament_ramming_parameters" | ||||
|             || opt_key == "filament_max_volumetric_speed" | ||||
|             || opt_key == "gcode_flavor" | ||||
|             || opt_key == "high_current_on_filament_swap" | ||||
|             || opt_key == "infill_first" | ||||
|  | @ -335,7 +307,7 @@ unsigned int Print::num_object_instances() const | |||
| { | ||||
| 	unsigned int instances = 0; | ||||
|     for (const PrintObject *print_object : m_objects) | ||||
|         instances += print_object->copies().size(); | ||||
|         instances += (unsigned int)print_object->copies().size(); | ||||
|     return instances; | ||||
| } | ||||
| 
 | ||||
|  | @ -358,198 +330,6 @@ double Print::max_allowed_layer_height() const | |||
|     return nozzle_diameter_max; | ||||
| } | ||||
| 
 | ||||
| // Caller is responsible for supplying models whose objects don't collide
 | ||||
| // and have explicit instance positions.
 | ||||
| void Print::add_model_object(ModelObject* model_object, int idx) | ||||
| { | ||||
| 	tbb::mutex::scoped_lock lock(this->state_mutex()); | ||||
|     // Add a copy of this ModelObject to this Print.
 | ||||
|     m_model.objects.emplace_back(ModelObject::new_copy(*model_object)); | ||||
|     m_model.objects.back()->set_model(&m_model); | ||||
|     // Initialize a new print object and store it at the given position.
 | ||||
|     PrintObject *object = new PrintObject(this, model_object, true); | ||||
|     if (idx != -1) { | ||||
|         delete m_objects[idx]; | ||||
|         m_objects[idx] = object; | ||||
|     } else | ||||
|         m_objects.emplace_back(object); | ||||
|     // Invalidate all print steps.
 | ||||
|     this->invalidate_all_steps(); | ||||
| 
 | ||||
|     // Set the transformation matrix without translation from the first instance.
 | ||||
|     if (! model_object->instances.empty()) { | ||||
|         // Trafo and bounding box, both in world coordinate system.
 | ||||
|         Transform3d   trafo = model_object->instances.front()->get_matrix(); | ||||
|         BoundingBoxf3 bbox  = model_object->instance_bounding_box(0); | ||||
|         // Now shift the object up to align it with the print bed.
 | ||||
|         trafo.data()[14] -= bbox.min(2); | ||||
| 		// and reset the XY translation.
 | ||||
| 		trafo.data()[12] = 0; | ||||
| 		trafo.data()[13] = 0; | ||||
| 		object->set_trafo(trafo); | ||||
|     } | ||||
| 
 | ||||
|     size_t volume_id = 0; | ||||
|     for (const ModelVolume *volume : model_object->volumes) { | ||||
|         if (! volume->is_model_part() && ! volume->is_modifier()) | ||||
|             continue; | ||||
|         // Get the config applied to this volume.
 | ||||
|         PrintRegionConfig config = PrintObject::region_config_from_model_volume(m_default_region_config, *volume, 99999); | ||||
|         // Find an existing print region with the same config.
 | ||||
|         size_t region_id = size_t(-1); | ||||
|         for (size_t i = 0; i < m_regions.size(); ++ i) | ||||
|             if (config.equals(m_regions[i]->config())) { | ||||
|                 region_id = i; | ||||
|                 break; | ||||
|             } | ||||
|         // If no region exists with the same config, create a new one.
 | ||||
|         if (region_id == size_t(-1)) { | ||||
|             region_id = m_regions.size(); | ||||
|             this->add_region(config); | ||||
|         } | ||||
|         // Assign volume to a region.
 | ||||
|         object->add_region_volume(region_id, volume_id); | ||||
|         ++ volume_id; | ||||
|     } | ||||
| 
 | ||||
|     // Apply config to print object.
 | ||||
|     object->config_apply(this->default_object_config()); | ||||
|     { | ||||
|         //normalize_and_apply_config(object->config(), model_object->config);
 | ||||
|         DynamicPrintConfig src_normalized(model_object->config); | ||||
|         src_normalized.normalize(); | ||||
|         object->config_apply(src_normalized, true); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // This function is only called through the Perl-C++ binding from the unit tests, should be
 | ||||
| // removed when unit tests are rewritten to C++.
 | ||||
| bool Print::apply_config_perl_tests_only(DynamicPrintConfig config) | ||||
| { | ||||
| 	tbb::mutex::scoped_lock lock(this->state_mutex()); | ||||
| 
 | ||||
| 
 | ||||
|     // Perl unit tests were failing in case the preset was not normalized (e.g. https://github.com/prusa3d/PrusaSlicer/issues/2288 was caused
 | ||||
|     // by too short max_layer_height vector. Calling the necessary function Preset::normalize(...) is not currently possible because there is no
 | ||||
|     // access to preset. This should be solved when the unit tests are rewritten to C++. For now we just copy-pasted code from Preset.cpp
 | ||||
|     // to make sure the unit tests pass (functions set_num_extruders and nozzle_options()).
 | ||||
|     auto *nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(config.option("nozzle_diameter", true)); | ||||
|     assert(nozzle_diameter != nullptr); | ||||
|     const auto &defaults = FullPrintConfig::defaults(); | ||||
|     for (const std::string &key : { "nozzle_diameter", "min_layer_height", "max_layer_height", "extruder_offset", | ||||
|                                     "retract_length", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_speed", "deretract_speed", | ||||
|                                     "retract_before_wipe", "retract_restart_extra", "retract_before_travel", "wipe", | ||||
|                                     "retract_layer_change", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour" }) | ||||
|     { | ||||
|         auto *opt = config.option(key, true); | ||||
|         assert(opt != nullptr); | ||||
|         assert(opt->is_vector()); | ||||
|         unsigned int num_extruders = (unsigned int)nozzle_diameter->values.size(); | ||||
|         static_cast<ConfigOptionVectorBase*>(opt)->resize(num_extruders, defaults.option(key)); | ||||
|     } | ||||
| 
 | ||||
|     // we get a copy of the config object so we can modify it safely
 | ||||
|     config.normalize(); | ||||
|      | ||||
|     // apply variables to placeholder parser
 | ||||
| 	this->placeholder_parser().apply_config(config); | ||||
|      | ||||
|     // handle changes to print config
 | ||||
|     t_config_option_keys print_diff = m_config.diff(config); | ||||
|     m_config.apply_only(config, print_diff, true); | ||||
|     bool invalidated = this->invalidate_state_by_config_options(print_diff); | ||||
|      | ||||
|     // handle changes to object config defaults
 | ||||
|     m_default_object_config.apply(config, true); | ||||
|     for (PrintObject *object : m_objects) { | ||||
|         // we don't assume that config contains a full ObjectConfig,
 | ||||
|         // so we base it on the current print-wise default
 | ||||
|         PrintObjectConfig new_config = this->default_object_config(); | ||||
|         // we override the new config with object-specific options
 | ||||
|         normalize_and_apply_config(new_config, object->model_object()->config); | ||||
|         // check whether the new config is different from the current one
 | ||||
|         t_config_option_keys diff = object->config().diff(new_config); | ||||
|         object->config_apply_only(new_config, diff, true); | ||||
|         invalidated |= object->invalidate_state_by_config_options(diff); | ||||
|     } | ||||
|      | ||||
|     // handle changes to regions config defaults
 | ||||
|     m_default_region_config.apply(config, true); | ||||
|      | ||||
|     // All regions now have distinct settings.
 | ||||
|     // Check whether applying the new region config defaults we'd get different regions.
 | ||||
|     bool rearrange_regions = false; | ||||
|     { | ||||
|         // Collect the already visited region configs into other_region_configs,
 | ||||
|         // so one may check for duplicates.
 | ||||
|         std::vector<PrintRegionConfig> other_region_configs; | ||||
|         for (size_t region_id = 0; region_id < m_regions.size(); ++ region_id) { | ||||
|             PrintRegion       ®ion = *m_regions[region_id]; | ||||
|             PrintRegionConfig  this_region_config; | ||||
|             bool               this_region_config_set = false; | ||||
|             for (PrintObject *object : m_objects) { | ||||
|                 if (region_id < object->region_volumes.size()) { | ||||
|                     for (int volume_id : object->region_volumes[region_id]) { | ||||
|                         const ModelVolume &volume = *object->model_object()->volumes[volume_id]; | ||||
|                         if (this_region_config_set) { | ||||
|                             // If the new config for this volume differs from the other
 | ||||
|                             // volume configs currently associated to this region, it means
 | ||||
|                             // the region subdivision does not make sense anymore.
 | ||||
|                             if (! this_region_config.equals(PrintObject::region_config_from_model_volume(m_default_region_config, volume, 99999))) { | ||||
|                                 rearrange_regions = true; | ||||
|                                 goto exit_for_rearrange_regions; | ||||
|                             } | ||||
|                         } else { | ||||
|                             this_region_config = PrintObject::region_config_from_model_volume(m_default_region_config, volume, 99999); | ||||
|                             this_region_config_set = true; | ||||
|                         } | ||||
|                         for (const PrintRegionConfig &cfg : other_region_configs) { | ||||
|                             // If the new config for this volume equals any of the other
 | ||||
|                             // volume configs that are not currently associated to this
 | ||||
|                             // region, it means the region subdivision does not make
 | ||||
|                             // sense anymore.
 | ||||
|                             if (cfg.equals(this_region_config)) { | ||||
|                                 rearrange_regions = true; | ||||
|                                 goto exit_for_rearrange_regions; | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             if (this_region_config_set) { | ||||
|                 t_config_option_keys diff = region.config().diff(this_region_config); | ||||
|                 if (! diff.empty()) { | ||||
|                     region.config_apply_only(this_region_config, diff, false); | ||||
|                     for (PrintObject *object : m_objects) | ||||
|                         if (region_id < object->region_volumes.size() && ! object->region_volumes[region_id].empty()) | ||||
|                             invalidated |= object->invalidate_state_by_config_options(diff); | ||||
|                 } | ||||
|                 other_region_configs.emplace_back(std::move(this_region_config)); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| exit_for_rearrange_regions: | ||||
|      | ||||
|     if (rearrange_regions) { | ||||
|         // The current subdivision of regions does not make sense anymore.
 | ||||
|         // We need to remove all objects and re-add them.
 | ||||
|         ModelObjectPtrs model_objects; | ||||
|         model_objects.reserve(m_objects.size()); | ||||
|         for (PrintObject *object : m_objects) | ||||
|             model_objects.push_back(object->model_object()); | ||||
|         this->clear(); | ||||
|         for (ModelObject *mo : model_objects) | ||||
|             this->add_model_object(mo); | ||||
|         invalidated = true; | ||||
|     } | ||||
| 
 | ||||
|     for (PrintObject *object : m_objects) | ||||
|         object->update_slicing_parameters(); | ||||
| 
 | ||||
|     return invalidated; | ||||
| } | ||||
| 
 | ||||
| // Add or remove support modifier ModelVolumes from model_object_dst to match the ModelVolumes of model_object_new
 | ||||
| // in the exact order and with the same IDs.
 | ||||
| // It is expected, that the model_object_dst already contains the non-support volumes of model_object_new in the correct order.
 | ||||
|  | @ -612,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; | ||||
|  | @ -620,6 +400,20 @@ static inline void model_volume_list_copy_configs(ModelObject &model_object_dst, | |||
|     } | ||||
| } | ||||
| 
 | ||||
| static inline void layer_height_ranges_copy_configs(t_layer_config_ranges &lr_dst, const t_layer_config_ranges &lr_src) | ||||
| { | ||||
|     assert(lr_dst.size() == lr_src.size()); | ||||
|     auto it_src = lr_src.cbegin(); | ||||
|     for (auto &kvp_dst : lr_dst) { | ||||
|         const auto &kvp_src = *it_src ++; | ||||
|         assert(std::abs(kvp_dst.first.first  - kvp_src.first.first ) <= EPSILON); | ||||
|         assert(std::abs(kvp_dst.first.second - kvp_src.first.second) <= EPSILON); | ||||
|         // Layer heights are allowed do differ in case the layer height table is being overriden by the smooth profile.
 | ||||
|         // assert(std::abs(kvp_dst.second.option("layer_height")->getFloat() - kvp_src.second.option("layer_height")->getFloat()) <= EPSILON);
 | ||||
|         kvp_dst.second = kvp_src.second; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static inline bool transform3d_lower(const Transform3d &lhs, const Transform3d &rhs)  | ||||
| { | ||||
|     typedef Transform3d::Scalar T; | ||||
|  | @ -674,6 +468,23 @@ static std::vector<PrintInstances> print_objects_from_model_object(const ModelOb | |||
|     return std::vector<PrintInstances>(trafos.begin(), trafos.end()); | ||||
| } | ||||
| 
 | ||||
| // Compare just the layer ranges and their layer heights, not the associated configs.
 | ||||
| // Ignore the layer heights if check_layer_heights is false.
 | ||||
| bool layer_height_ranges_equal(const t_layer_config_ranges &lr1, const t_layer_config_ranges &lr2, bool check_layer_height) | ||||
| { | ||||
|     if (lr1.size() != lr2.size()) | ||||
|         return false; | ||||
|     auto it2 = lr2.begin(); | ||||
|     for (const auto &kvp1 : lr1) { | ||||
|         const auto &kvp2 = *it2 ++; | ||||
|         if (std::abs(kvp1.first.first  - kvp2.first.first ) > EPSILON || | ||||
|             std::abs(kvp1.first.second - kvp2.first.second) > EPSILON || | ||||
|             (check_layer_height && std::abs(kvp1.second.option("layer_height")->getFloat() - kvp2.second.option("layer_height")->getFloat()) > EPSILON)) | ||||
|             return false; | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &config_in) | ||||
| { | ||||
| #ifdef _DEBUG | ||||
|  | @ -724,6 +535,50 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co | |||
|     // Handle changes to regions config defaults
 | ||||
|     m_default_region_config.apply_only(config, region_diff, true); | ||||
|      | ||||
|     class LayerRanges | ||||
|     { | ||||
|     public: | ||||
|         LayerRanges() {} | ||||
|         // Convert input config ranges into continuous non-overlapping sorted vector of intervals and their configs.
 | ||||
|         void assign(const t_layer_config_ranges &in) { | ||||
|             m_ranges.clear(); | ||||
|             m_ranges.reserve(in.size()); | ||||
|             // Input ranges are sorted lexicographically. First range trims the other ranges.
 | ||||
|             coordf_t last_z = 0; | ||||
|             for (const std::pair<const t_layer_height_range, DynamicPrintConfig> &range : in) { | ||||
| //            for (auto &range : in) {
 | ||||
| 			if (range.first.second > last_z) { | ||||
|                     coordf_t min_z = std::max(range.first.first, 0.); | ||||
|                     if (min_z > last_z + EPSILON) { | ||||
|                         m_ranges.emplace_back(t_layer_height_range(last_z, min_z), nullptr); | ||||
|                         last_z = min_z; | ||||
|                     } | ||||
|                     if (range.first.second > last_z + EPSILON) { | ||||
| 						const DynamicPrintConfig* cfg = &range.second; | ||||
|                         m_ranges.emplace_back(t_layer_height_range(last_z, range.first.second), cfg); | ||||
|                         last_z = range.first.second; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             if (m_ranges.empty()) | ||||
|                 m_ranges.emplace_back(t_layer_height_range(0, DBL_MAX), nullptr); | ||||
|             else if (m_ranges.back().second == nullptr) | ||||
|                 m_ranges.back().first.second = DBL_MAX; | ||||
|             else | ||||
|                 m_ranges.emplace_back(t_layer_height_range(m_ranges.back().first.second, DBL_MAX), nullptr); | ||||
|         } | ||||
|         const DynamicPrintConfig* config(const t_layer_height_range &range) const { | ||||
|             auto it = std::lower_bound(m_ranges.begin(), m_ranges.end(), std::make_pair< t_layer_height_range, const DynamicPrintConfig*>(t_layer_height_range(range.first - EPSILON, range.second - EPSILON), nullptr)); | ||||
|             assert(it != m_ranges.end()); | ||||
|             assert(it == m_ranges.end() || std::abs(it->first.first  - range.first ) < EPSILON); | ||||
|             assert(it == m_ranges.end() || std::abs(it->first.second - range.second) < EPSILON); | ||||
|             return (it == m_ranges.end()) ? nullptr : it->second; | ||||
|         } | ||||
|         std::vector<std::pair<t_layer_height_range, const DynamicPrintConfig*>>::const_iterator begin() const { return m_ranges.cbegin(); } | ||||
|         std::vector<std::pair<t_layer_height_range, const DynamicPrintConfig*>>::const_iterator end() const { return m_ranges.cend(); } | ||||
|     private: | ||||
|         std::vector<std::pair<t_layer_height_range, const DynamicPrintConfig*>> m_ranges; | ||||
|     }; | ||||
|     struct ModelObjectStatus { | ||||
|         enum Status { | ||||
|             Unknown, | ||||
|  | @ -732,9 +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; | ||||
|         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; } | ||||
|     }; | ||||
|  | @ -839,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) 
 | ||||
|  | @ -861,21 +717,23 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co | |||
|         auto it_status = model_object_status.find(ModelObjectStatus(model_object.id())); | ||||
|         assert(it_status != model_object_status.end()); | ||||
|         assert(it_status->status != ModelObjectStatus::Deleted); | ||||
| 		const ModelObject& model_object_new = *model.objects[idx_model_object]; | ||||
| 		const_cast<ModelObjectStatus&>(*it_status).layer_ranges.assign(model_object_new.layer_config_ranges); | ||||
|         if (it_status->status == ModelObjectStatus::New) | ||||
|             // PrintObject instances will be added in the next loop.
 | ||||
|             continue; | ||||
|         // Update the ModelObject instance, possibly invalidate the linked PrintObjects.
 | ||||
|         assert(it_status->status == ModelObjectStatus::Old || it_status->status == ModelObjectStatus::Moved); | ||||
|         const ModelObject &model_object_new = *model.objects[idx_model_object]; | ||||
|         // Check whether a model part volume was added or removed, their transformations or order changed.
 | ||||
|         // Only volume IDs, volume types and their order are checked, configuration and other parameters are NOT checked.
 | ||||
|         bool model_parts_differ         = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::MODEL_PART); | ||||
|         bool modifiers_differ           = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::PARAMETER_MODIFIER); | ||||
|         bool support_blockers_differ    = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_BLOCKER); | ||||
|         bool support_enforcers_differ   = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_ENFORCER); | ||||
|         if (model_parts_differ || modifiers_differ ||  | ||||
|             model_object.origin_translation         != model_object_new.origin_translation   || | ||||
|             model_object.layer_height_ranges        != model_object_new.layer_height_ranges  ||  | ||||
|             model_object.layer_height_profile       != model_object_new.layer_height_profile) { | ||||
|             model_object.layer_height_profile       != model_object_new.layer_height_profile || | ||||
|             ! layer_height_ranges_equal(model_object.layer_config_ranges, model_object_new.layer_config_ranges, model_object_new.layer_height_profile.empty())) { | ||||
|             // The very first step (the slicing step) is invalidated. One may freely remove all associated PrintObjects.
 | ||||
|             auto range = print_object_status.equal_range(PrintObjectStatus(model_object.id())); | ||||
|             for (auto it = range.first; it != range.second; ++ it) { | ||||
|  | @ -899,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())); | ||||
|  | @ -915,7 +773,8 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co | |||
|             //FIXME What to do with m_material_id?
 | ||||
| 			model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::MODEL_PART); | ||||
| 			model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::PARAMETER_MODIFIER); | ||||
|             // Copy the ModelObject name, input_file and instances. The instances will compared against PrintObject instances in the next step.
 | ||||
|             layer_height_ranges_copy_configs(model_object.layer_config_ranges /* dst */, model_object_new.layer_config_ranges /* src */); | ||||
|             // Copy the ModelObject name, input_file and instances. The instances will be compared against PrintObject instances in the next step.
 | ||||
|             model_object.name       = model_object_new.name; | ||||
|             model_object.input_file = model_object_new.input_file; | ||||
|             model_object.clear_instances(); | ||||
|  | @ -1027,19 +886,27 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co | |||
|         PrintRegionConfig  this_region_config; | ||||
|         bool               this_region_config_set = false; | ||||
|         for (PrintObject *print_object : m_objects) { | ||||
|             const LayerRanges *layer_ranges; | ||||
|             { | ||||
|                 auto it_status = model_object_status.find(ModelObjectStatus(print_object->model_object()->id())); | ||||
|                 assert(it_status != model_object_status.end()); | ||||
|                 assert(it_status->status != ModelObjectStatus::Deleted); | ||||
|                 layer_ranges = &it_status->layer_ranges; | ||||
|             } | ||||
|             if (region_id < print_object->region_volumes.size()) { | ||||
|                 for (int volume_id : print_object->region_volumes[region_id]) { | ||||
|                     const ModelVolume &volume = *print_object->model_object()->volumes[volume_id]; | ||||
|                 for (const std::pair<t_layer_height_range, int> &volume_and_range : print_object->region_volumes[region_id]) { | ||||
|                     const ModelVolume        &volume             = *print_object->model_object()->volumes[volume_and_range.second]; | ||||
|                     const DynamicPrintConfig *layer_range_config = layer_ranges->config(volume_and_range.first); | ||||
|                     if (this_region_config_set) { | ||||
|                         // If the new config for this volume differs from the other
 | ||||
|                         // volume configs currently associated to this region, it means
 | ||||
|                         // the region subdivision does not make sense anymore.
 | ||||
|                         if (! this_region_config.equals(PrintObject::region_config_from_model_volume(m_default_region_config, volume, num_extruders))) | ||||
|                         if (! this_region_config.equals(PrintObject::region_config_from_model_volume(m_default_region_config, layer_range_config, volume, num_extruders))) | ||||
|                             // Regions were split. Reset this print_object.
 | ||||
|                             goto print_object_end; | ||||
|                     } else { | ||||
|                         this_region_config = PrintObject::region_config_from_model_volume(m_default_region_config, volume, num_extruders); | ||||
| 						for (size_t i = 0; i < region_id; ++i) { | ||||
|                         this_region_config = PrintObject::region_config_from_model_volume(m_default_region_config, layer_range_config, volume, num_extruders); | ||||
| 						for (size_t i = 0; i < region_id; ++ i) { | ||||
| 							const PrintRegion ®ion_other = *m_regions[i]; | ||||
| 							if (region_other.m_refcnt != 0 && region_other.config().equals(this_region_config)) | ||||
| 								// Regions were merged. Reset this print_object.
 | ||||
|  | @ -1054,7 +921,7 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co | |||
|             update_apply_status(print_object->invalidate_all_steps()); | ||||
|             // Decrease the references to regions from this volume.
 | ||||
|             int ireg = 0; | ||||
|             for (const std::vector<int> &volumes : print_object->region_volumes) { | ||||
|             for (const std::vector<std::pair<t_layer_height_range, int>> &volumes : print_object->region_volumes) { | ||||
|                 if (! volumes.empty()) | ||||
|                     -- m_regions[ireg]->m_refcnt; | ||||
|                 ++ ireg; | ||||
|  | @ -1076,52 +943,65 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co | |||
|     for (size_t idx_print_object = 0; idx_print_object < m_objects.size(); ++ idx_print_object) { | ||||
|         PrintObject        &print_object0 = *m_objects[idx_print_object]; | ||||
|         const ModelObject  &model_object  = *print_object0.model_object(); | ||||
|         std::vector<int>    map_volume_to_region(model_object.volumes.size(), -1); | ||||
|         const LayerRanges *layer_ranges; | ||||
|         { | ||||
|             auto it_status = model_object_status.find(ModelObjectStatus(model_object.id())); | ||||
|             assert(it_status != model_object_status.end()); | ||||
|             assert(it_status->status != ModelObjectStatus::Deleted); | ||||
|             layer_ranges = &it_status->layer_ranges; | ||||
|         } | ||||
|         std::vector<int>   regions_in_object; | ||||
|         regions_in_object.reserve(64); | ||||
|         for (size_t i = idx_print_object; i < m_objects.size() && m_objects[i]->model_object() == &model_object; ++ i) { | ||||
|             PrintObject &print_object = *m_objects[i]; | ||||
| 			bool         fresh = print_object.region_volumes.empty(); | ||||
|             unsigned int volume_id = 0; | ||||
|             unsigned int idx_region_in_object = 0; | ||||
|             for (const ModelVolume *volume : model_object.volumes) { | ||||
|                 if (! volume->is_model_part() && ! volume->is_modifier()) { | ||||
| 					++ volume_id; | ||||
| 					continue; | ||||
| 				} | ||||
|                 int region_id = -1; | ||||
|                 if (&print_object == &print_object0) { | ||||
|                     // Get the config applied to this volume.
 | ||||
|                     PrintRegionConfig config = PrintObject::region_config_from_model_volume(m_default_region_config, *volume, num_extruders); | ||||
|                     // Find an existing print region with the same config.
 | ||||
| 					int idx_empty_slot = -1; | ||||
| 					for (int i = 0; i < (int)m_regions.size(); ++ i) { | ||||
| 						if (m_regions[i]->m_refcnt == 0) { | ||||
|                             if (idx_empty_slot == -1) | ||||
|                                 idx_empty_slot = i; | ||||
|                         } else if (config.equals(m_regions[i]->config())) { | ||||
|                             region_id = i; | ||||
|                             break; | ||||
|                 // Filter the layer ranges, so they do not overlap and they contain at least a single layer.
 | ||||
|                 // Now insert a volume with a layer range to its own region.
 | ||||
|                 for (auto it_range = layer_ranges->begin(); it_range != layer_ranges->end(); ++ it_range) { | ||||
|                     int region_id = -1; | ||||
|                     if (&print_object == &print_object0) { | ||||
|                         // Get the config applied to this volume.
 | ||||
|                         PrintRegionConfig config = PrintObject::region_config_from_model_volume(m_default_region_config, it_range->second, *volume, num_extruders); | ||||
|                         // Find an existing print region with the same config.
 | ||||
|     					int idx_empty_slot = -1; | ||||
|     					for (int i = 0; i < (int)m_regions.size(); ++ i) { | ||||
|     						if (m_regions[i]->m_refcnt == 0) { | ||||
|                                 if (idx_empty_slot == -1) | ||||
|                                     idx_empty_slot = i; | ||||
|                             } else if (config.equals(m_regions[i]->config())) { | ||||
|                                 region_id = i; | ||||
|                                 break; | ||||
|                             } | ||||
|     					} | ||||
|                         // If no region exists with the same config, create a new one.
 | ||||
|     					if (region_id == -1) { | ||||
|     						if (idx_empty_slot == -1) { | ||||
|     							region_id = (int)m_regions.size(); | ||||
|     							this->add_region(config); | ||||
|     						} else { | ||||
|     							region_id = idx_empty_slot; | ||||
|                                 m_regions[region_id]->set_config(std::move(config)); | ||||
|     						} | ||||
|                         } | ||||
| 					} | ||||
|                     // If no region exists with the same config, create a new one.
 | ||||
| 					if (region_id == -1) { | ||||
| 						if (idx_empty_slot == -1) { | ||||
| 							region_id = (int)m_regions.size(); | ||||
| 							this->add_region(config); | ||||
| 						} else { | ||||
| 							region_id = idx_empty_slot; | ||||
|                             m_regions[region_id]->set_config(std::move(config)); | ||||
| 						} | ||||
|                     } | ||||
|                     map_volume_to_region[volume_id] = region_id; | ||||
|                 } else | ||||
|                     region_id = map_volume_to_region[volume_id]; | ||||
|                 // Assign volume to a region.
 | ||||
| 				if (fresh) { | ||||
| 					if (region_id >= print_object.region_volumes.size() || print_object.region_volumes[region_id].empty()) | ||||
| 						++ m_regions[region_id]->m_refcnt; | ||||
| 					print_object.add_region_volume(region_id, volume_id); | ||||
| 				} | ||||
|                 ++ volume_id; | ||||
|             } | ||||
|                         regions_in_object.emplace_back(region_id); | ||||
|                     } else | ||||
|                         region_id = regions_in_object[idx_region_in_object ++]; | ||||
|                     // Assign volume to a region.
 | ||||
|     				if (fresh) { | ||||
|     					if ((size_t)region_id >= print_object.region_volumes.size() || print_object.region_volumes[region_id].empty()) | ||||
|     						++ m_regions[region_id]->m_refcnt; | ||||
|     					print_object.add_region_volume(region_id, volume_id, it_range->first); | ||||
|     				} | ||||
|                 } | ||||
| 				++ volume_id; | ||||
| 			} | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -1175,9 +1055,9 @@ std::string Print::validate() const | |||
|                 Polygon        convex_hull0    = offset( | ||||
|                     print_object->model_object()->convex_hull_2d( | ||||
|                         Geometry::assemble_transform(Vec3d::Zero(), rotation, model_instance0->get_scaling_factor(), model_instance0->get_mirror())), | ||||
|                     scale_(m_config.extruder_clearance_radius.value) / 2., jtRound, scale_(0.1)).front(); | ||||
|                     float(scale_(0.5 * m_config.extruder_clearance_radius.value)), jtRound, float(scale_(0.1))).front(); | ||||
|                 // Now we check that no instance of convex_hull intersects any of the previously checked object instances.
 | ||||
|                 for (const Point © : print_object->m_copies) { | ||||
|                 for (const Point © : print_object->copies()) { | ||||
|                     Polygon convex_hull = convex_hull0; | ||||
|                     convex_hull.translate(copy); | ||||
|                     if (! intersection(convex_hulls_other, convex_hull).empty()) | ||||
|  | @ -1211,23 +1091,21 @@ std::string Print::validate() const | |||
|             return L("The Spiral Vase option can only be used when printing single material objects."); | ||||
|     } | ||||
| 
 | ||||
|     if (m_config.single_extruder_multi_material) { | ||||
|         for (size_t i=1; i<m_config.nozzle_diameter.values.size(); ++i) | ||||
|             if (m_config.nozzle_diameter.values[i] != m_config.nozzle_diameter.values[i-1]) | ||||
|                 return L("All extruders must have the same diameter for single extruder multimaterial printer."); | ||||
|     } | ||||
| 
 | ||||
|     if (this->has_wipe_tower() && ! m_objects.empty()) { | ||||
|         if (m_config.gcode_flavor != gcfRepRap && m_config.gcode_flavor != gcfRepetier && m_config.gcode_flavor != gcfMarlin) | ||||
|             return L("The Wipe Tower is currently only supported for the Marlin, RepRap/Sprinter and Repetier G-code flavors."); | ||||
|         if (! m_config.use_relative_e_distances) | ||||
|             return L("The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1)."); | ||||
|          | ||||
|         for (size_t i=1; i<m_config.nozzle_diameter.values.size(); ++i) | ||||
|             if (m_config.nozzle_diameter.values[i] != m_config.nozzle_diameter.values[i-1]) | ||||
|                 return L("All extruders must have the same diameter for the Wipe Tower."); | ||||
| 
 | ||||
|         if (m_objects.size() > 1) { | ||||
|             bool                                has_custom_layering = false; | ||||
|             std::vector<std::vector<coordf_t>>  layer_height_profiles; | ||||
|             for (const PrintObject *object : m_objects) { | ||||
|                 has_custom_layering = ! object->model_object()->layer_height_ranges.empty() || ! object->model_object()->layer_height_profile.empty(); | ||||
|                 has_custom_layering = ! object->model_object()->layer_config_ranges.empty() || ! object->model_object()->layer_height_profile.empty();      // #ys_FIXME_experiment
 | ||||
|                 if (has_custom_layering) { | ||||
|                     layer_height_profiles.assign(m_objects.size(), std::vector<coordf_t>()); | ||||
|                     break; | ||||
|  | @ -1259,11 +1137,10 @@ std::string Print::validate() const | |||
|             if (has_custom_layering) { | ||||
|                 const std::vector<coordf_t> &layer_height_profile_tallest = layer_height_profiles[tallest_object_idx]; | ||||
|                 for (size_t idx_object = 0; idx_object < m_objects.size(); ++ idx_object) { | ||||
|                     const PrintObject           *object               = m_objects[idx_object]; | ||||
|                     const std::vector<coordf_t> &layer_height_profile = layer_height_profiles[idx_object]; | ||||
|                     bool                         failed               = false; | ||||
|                     if (layer_height_profile_tallest.size() >= layer_height_profile.size()) { | ||||
|                         int i = 0; | ||||
|                         size_t i = 0; | ||||
|                         while (i < layer_height_profile.size() && i < layer_height_profile_tallest.size()) { | ||||
|                             if (std::abs(layer_height_profile_tallest[i] - layer_height_profile[i])) { | ||||
|                                 failed = true; | ||||
|  | @ -1435,9 +1312,9 @@ Flow Print::brim_flow() const | |||
|        generation as well. */ | ||||
|     return Flow::new_from_config_width( | ||||
|         frPerimeter, | ||||
|         width,  | ||||
|         m_config.nozzle_diameter.get_at(m_regions.front()->config().perimeter_extruder-1), | ||||
|         this->skirt_first_layer_height(), | ||||
| 		width, | ||||
|         (float)m_config.nozzle_diameter.get_at(m_regions.front()->config().perimeter_extruder-1), | ||||
| 		(float)this->skirt_first_layer_height(), | ||||
|         0 | ||||
|     ); | ||||
| } | ||||
|  | @ -1457,9 +1334,9 @@ Flow Print::skirt_flow() const | |||
|        generation as well. */ | ||||
|     return Flow::new_from_config_width( | ||||
|         frPerimeter, | ||||
|         width,  | ||||
|         m_config.nozzle_diameter.get_at(m_objects.front()->config().support_material_extruder-1), | ||||
|         this->skirt_first_layer_height(), | ||||
| 		width, | ||||
| 		(float)m_config.nozzle_diameter.get_at(m_objects.front()->config().support_material_extruder-1), | ||||
| 		(float)this->skirt_first_layer_height(), | ||||
|         0 | ||||
|     ); | ||||
| } | ||||
|  | @ -1628,26 +1505,26 @@ void Print::_make_skirt() | |||
|     } | ||||
| 
 | ||||
|     // Number of skirt loops per skirt layer.
 | ||||
|     int n_skirts = m_config.skirts.value; | ||||
|     size_t n_skirts = m_config.skirts.value; | ||||
|     if (this->has_infinite_skirt() && n_skirts == 0) | ||||
|         n_skirts = 1; | ||||
| 
 | ||||
|     // Initial offset of the brim inner edge from the object (possible with a support & raft).
 | ||||
|     // The skirt will touch the brim if the brim is extruded.
 | ||||
|     Flow brim_flow = this->brim_flow(); | ||||
|     Flow   brim_flow = this->brim_flow(); | ||||
|     double actual_brim_width = brim_flow.spacing() * floor(m_config.brim_width.value / brim_flow.spacing()); | ||||
|     coord_t distance = scale_(std::max(m_config.skirt_distance.value, actual_brim_width) - spacing/2.); | ||||
|     auto   distance = float(scale_(std::max(m_config.skirt_distance.value, actual_brim_width) - spacing/2.)); | ||||
|     // Draw outlines from outside to inside.
 | ||||
|     // Loop while we have less skirts than required or any extruder hasn't reached the min length if any.
 | ||||
|     std::vector<coordf_t> extruded_length(extruders.size(), 0.); | ||||
|     for (int i = n_skirts, extruder_idx = 0; i > 0; -- i) { | ||||
|     for (size_t i = n_skirts, extruder_idx = 0; i > 0; -- i) { | ||||
|         this->throw_if_canceled(); | ||||
|         // Offset the skirt outside.
 | ||||
|         distance += coord_t(scale_(spacing)); | ||||
|         distance += float(scale_(spacing)); | ||||
|         // Generate the skirt centerline.
 | ||||
|         Polygon loop; | ||||
|         { | ||||
|             Polygons loops = offset(convex_hull, distance, ClipperLib::jtRound, scale_(0.1)); | ||||
|             Polygons loops = offset(convex_hull, distance, ClipperLib::jtRound, float(scale_(0.1))); | ||||
|             Geometry::simplify_polygons(loops, scale_(0.05), &loops); | ||||
| 			if (loops.empty()) | ||||
| 				break; | ||||
|  | @ -1658,9 +1535,9 @@ void Print::_make_skirt() | |||
|         eloop.paths.emplace_back(ExtrusionPath( | ||||
|             ExtrusionPath( | ||||
|                 erSkirt, | ||||
|                 mm3_per_mm,         // this will be overridden at G-code export time
 | ||||
|                 (float)mm3_per_mm,         // this will be overridden at G-code export time
 | ||||
|                 flow.width, | ||||
|                 first_layer_height  // this will be overridden at G-code export time
 | ||||
| 				(float)first_layer_height  // this will be overridden at G-code export time
 | ||||
|             ))); | ||||
|         eloop.paths.back().polyline = loop.split_at_first_point(); | ||||
|         m_skirt.append(eloop); | ||||
|  | @ -1730,7 +1607,6 @@ void Print::_make_brim() | |||
| bool Print::has_wipe_tower() const | ||||
| { | ||||
|     return  | ||||
|         m_config.single_extruder_multi_material.value &&  | ||||
|         ! m_config.spiral_vase.value && | ||||
|         m_config.wipe_tower.value &&  | ||||
|         m_config.nozzle_diameter.values.size() > 1; | ||||
|  | @ -1786,7 +1662,7 @@ void Print::_make_wipe_tower() | |||
|                 // Insert the new support layer.
 | ||||
|                 double height    = lt.print_z - m_wipe_tower_data.tool_ordering.layer_tools()[i-1].print_z; | ||||
|                 //FIXME the support layer ID is set to -1, as Vojtech hopes it is not being used anyway.
 | ||||
|                 it_layer = m_objects.front()->insert_support_layer(it_layer, size_t(-1), height, lt.print_z, lt.print_z - 0.5 * height); | ||||
|                 it_layer = m_objects.front()->insert_support_layer(it_layer, -1, height, lt.print_z, lt.print_z - 0.5 * height); | ||||
|                 ++ it_layer; | ||||
|             } | ||||
|         } | ||||
|  | @ -1794,12 +1670,13 @@ void Print::_make_wipe_tower() | |||
|     this->throw_if_canceled(); | ||||
| 
 | ||||
|     // Initialize the wipe tower.
 | ||||
|     WipeTowerPrusaMM wipe_tower( | ||||
|     WipeTower wipe_tower( | ||||
|         m_config.single_extruder_multi_material.value, | ||||
|         float(m_config.wipe_tower_x.value),     float(m_config.wipe_tower_y.value),  | ||||
|         float(m_config.wipe_tower_width.value), | ||||
|         float(m_config.wipe_tower_rotation_angle.value), float(m_config.cooling_tube_retraction.value), | ||||
|         float(m_config.cooling_tube_length.value), float(m_config.parking_pos_retraction.value), | ||||
|         float(m_config.extra_loading_move.value), float(m_config.wipe_tower_bridging),  | ||||
|         float(m_config.extra_loading_move.value), float(m_config.wipe_tower_bridging), | ||||
|         m_config.high_current_on_filament_swap.value, m_config.gcode_flavor, wipe_volumes, | ||||
|         m_wipe_tower_data.tool_ordering.first_extruder()); | ||||
| 
 | ||||
|  | @ -1808,24 +1685,26 @@ void Print::_make_wipe_tower() | |||
| 
 | ||||
|     // Set the extruder & material properties at the wipe tower object.
 | ||||
|     for (size_t i = 0; i < number_of_extruders; ++ i) | ||||
| 
 | ||||
|         wipe_tower.set_extruder( | ||||
|             i,  | ||||
|             WipeTowerPrusaMM::parse_material(m_config.filament_type.get_at(i).c_str()), | ||||
|             m_config.filament_type.get_at(i), | ||||
|             m_config.temperature.get_at(i), | ||||
|             m_config.first_layer_temperature.get_at(i), | ||||
|             m_config.filament_loading_speed.get_at(i), | ||||
|             m_config.filament_loading_speed_start.get_at(i), | ||||
|             m_config.filament_unloading_speed.get_at(i), | ||||
|             m_config.filament_unloading_speed_start.get_at(i), | ||||
|             m_config.filament_toolchange_delay.get_at(i), | ||||
| 			(float)m_config.filament_loading_speed.get_at(i), | ||||
| 			(float)m_config.filament_loading_speed_start.get_at(i), | ||||
| 			(float)m_config.filament_unloading_speed.get_at(i), | ||||
| 			(float)m_config.filament_unloading_speed_start.get_at(i), | ||||
| 			(float)m_config.filament_toolchange_delay.get_at(i), | ||||
|             m_config.filament_cooling_moves.get_at(i), | ||||
|             m_config.filament_cooling_initial_speed.get_at(i), | ||||
|             m_config.filament_cooling_final_speed.get_at(i), | ||||
| 			(float)m_config.filament_cooling_initial_speed.get_at(i), | ||||
| 			(float)m_config.filament_cooling_final_speed.get_at(i), | ||||
|             m_config.filament_ramming_parameters.get_at(i), | ||||
|             m_config.filament_max_volumetric_speed.get_at(i), | ||||
|             m_config.nozzle_diameter.get_at(i)); | ||||
| 
 | ||||
|     m_wipe_tower_data.priming = Slic3r::make_unique<WipeTower::ToolChangeResult>( | ||||
|         wipe_tower.prime(this->skirt_first_layer_height(), m_wipe_tower_data.tool_ordering.all_extruders(), false)); | ||||
|     m_wipe_tower_data.priming = Slic3r::make_unique<std::vector<WipeTower::ToolChangeResult>>( | ||||
|         wipe_tower.prime((float)this->skirt_first_layer_height(), m_wipe_tower_data.tool_ordering.all_extruders(), false)); | ||||
| 
 | ||||
|     // Lets go through the wipe tower layers and determine pairs of extruder changes for each
 | ||||
|     // to pass to wipe_tower (so that it can use it for planning the layout of the tower)
 | ||||
|  | @ -1834,21 +1713,21 @@ void Print::_make_wipe_tower() | |||
|         for (auto &layer_tools : m_wipe_tower_data.tool_ordering.layer_tools()) { // for all layers
 | ||||
|             if (!layer_tools.has_wipe_tower) continue; | ||||
|             bool first_layer = &layer_tools == &m_wipe_tower_data.tool_ordering.front(); | ||||
|             wipe_tower.plan_toolchange(layer_tools.print_z, layer_tools.wipe_tower_layer_height, current_extruder_id, current_extruder_id,false); | ||||
|             wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_extruder_id, current_extruder_id, false); | ||||
|             for (const auto extruder_id : layer_tools.extruders) { | ||||
|                 if ((first_layer && extruder_id == m_wipe_tower_data.tool_ordering.all_extruders().back()) || extruder_id != current_extruder_id) { | ||||
|                     float volume_to_wipe = wipe_volumes[current_extruder_id][extruder_id];             // total volume to wipe after this toolchange
 | ||||
|                     // Not all of that can be used for infill purging:
 | ||||
|                     volume_to_wipe -= m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); | ||||
|                     volume_to_wipe -= (float)m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); | ||||
| 
 | ||||
|                     // try to assign some infills/objects for the wiping:
 | ||||
|                     volume_to_wipe = layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, current_extruder_id, extruder_id, volume_to_wipe); | ||||
| 
 | ||||
|                     // add back the minimal amount toforce on the wipe tower:
 | ||||
|                     volume_to_wipe += m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); | ||||
|                     volume_to_wipe += (float)m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); | ||||
| 
 | ||||
|                     // request a toolchange at the wipe tower with at least volume_to_wipe purging amount
 | ||||
|                     wipe_tower.plan_toolchange(layer_tools.print_z, layer_tools.wipe_tower_layer_height, current_extruder_id, extruder_id, | ||||
|                     wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_extruder_id, extruder_id, | ||||
|                                                first_layer && extruder_id == m_wipe_tower_data.tool_ordering.all_extruders().back(), volume_to_wipe); | ||||
|                     current_extruder_id = extruder_id; | ||||
|                 } | ||||
|  |  | |||
|  | @ -80,8 +80,8 @@ private: // Prevents erroneous use by other classes. | |||
|     typedef PrintObjectBaseWithState<Print, PrintObjectStep, posCount> Inherited; | ||||
| 
 | ||||
| public: | ||||
|     // vector of (vectors of volume ids), indexed by region_id
 | ||||
|     std::vector<std::vector<int>> region_volumes; | ||||
|     // vector of (layer height ranges and vectors of volume ids), indexed by region_id
 | ||||
|     std::vector<std::vector<std::pair<t_layer_height_range, int>>> region_volumes; | ||||
| 
 | ||||
|     // this is set to true when LayerRegion->slices is split in top/internal/bottom
 | ||||
|     // so that next call to make_perimeters() performs a union() before computing loops
 | ||||
|  | @ -99,10 +99,10 @@ public: | |||
|     BoundingBox bounding_box() const { return BoundingBox(Point(0,0), to_2d(this->size)); } | ||||
| 
 | ||||
|     // adds region_id, too, if necessary
 | ||||
|     void add_region_volume(unsigned int region_id, int volume_id) { | ||||
|     void add_region_volume(unsigned int region_id, int volume_id, const t_layer_height_range &layer_range) { | ||||
|         if (region_id >= region_volumes.size()) | ||||
| 			region_volumes.resize(region_id + 1); | ||||
|         region_volumes[region_id].emplace_back(volume_id); | ||||
|         region_volumes[region_id].emplace_back(layer_range, volume_id); | ||||
|     } | ||||
|     // This is the *total* layer count (including support layers)
 | ||||
|     // this value is not supposed to be compared with Layer::id
 | ||||
|  | @ -120,7 +120,7 @@ public: | |||
|     void clear_support_layers(); | ||||
|     SupportLayer* get_support_layer(int idx) { return m_support_layers[idx]; } | ||||
|     SupportLayer* add_support_layer(int id, coordf_t height, coordf_t print_z); | ||||
|     SupportLayerPtrs::const_iterator insert_support_layer(SupportLayerPtrs::const_iterator pos, int id, coordf_t height, coordf_t print_z, coordf_t slice_z); | ||||
|     SupportLayerPtrs::const_iterator insert_support_layer(SupportLayerPtrs::const_iterator pos, size_t id, coordf_t height, coordf_t print_z, coordf_t slice_z); | ||||
|     void delete_support_layer(int idx); | ||||
|      | ||||
|     // Initialize the layer_height_profile from the model_object's layer_height_profile, from model_object's layer height table, or from slicing parameters.
 | ||||
|  | @ -141,8 +141,9 @@ public: | |||
|     void slice(); | ||||
| 
 | ||||
|     // Helpers to slice support enforcer / blocker meshes by the support generator.
 | ||||
|     std::vector<ExPolygons>     slice_support_enforcers() const; | ||||
|     std::vector<ExPolygons>     slice_support_blockers() const; | ||||
|     std::vector<ExPolygons>     slice_support_volumes(const ModelVolumeType &model_volume_type) const; | ||||
|     std::vector<ExPolygons>     slice_support_blockers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_BLOCKER); } | ||||
|     std::vector<ExPolygons>     slice_support_enforcers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_ENFORCER); } | ||||
| 
 | ||||
| protected: | ||||
|     // to be called from Print only.
 | ||||
|  | @ -165,7 +166,7 @@ protected: | |||
|     void                    update_slicing_parameters(); | ||||
| 
 | ||||
|     static PrintObjectConfig object_config_from_model_object(const PrintObjectConfig &default_object_config, const ModelObject &object, size_t num_extruders); | ||||
|     static PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &default_region_config, const ModelVolume &volume, size_t num_extruders); | ||||
|     static PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &default_region_config, const DynamicPrintConfig *layer_range_config, const ModelVolume &volume, size_t num_extruders); | ||||
| 
 | ||||
| private: | ||||
|     void make_perimeters(); | ||||
|  | @ -201,9 +202,11 @@ private: | |||
|     LayerPtrs                               m_layers; | ||||
|     SupportLayerPtrs                        m_support_layers; | ||||
| 
 | ||||
|     std::vector<ExPolygons> _slice_region(size_t region_id, const std::vector<float> &z, bool modifier); | ||||
|     std::vector<ExPolygons> _slice_volumes(const std::vector<float> &z, const std::vector<const ModelVolume*> &volumes) const; | ||||
|     std::vector<ExPolygons> _slice_volume(const std::vector<float> &z, const ModelVolume &volume) const; | ||||
|     std::vector<ExPolygons> slice_region(size_t region_id, const std::vector<float> &z) const; | ||||
|     std::vector<ExPolygons> slice_modifiers(size_t region_id, const std::vector<float> &z) const; | ||||
|     std::vector<ExPolygons> slice_volumes(const std::vector<float> &z, const std::vector<const ModelVolume*> &volumes) const; | ||||
|     std::vector<ExPolygons> slice_volume(const std::vector<float> &z, const ModelVolume &volume) const; | ||||
|     std::vector<ExPolygons> slice_volume(const std::vector<float> &z, const std::vector<t_layer_height_range> &ranges, const ModelVolume &volume) const; | ||||
| }; | ||||
| 
 | ||||
| struct WipeTowerData | ||||
|  | @ -213,7 +216,7 @@ struct WipeTowerData | |||
|     // Cache it here, so it does not need to be recalculated during the G-code generation.
 | ||||
|     ToolOrdering                                          tool_ordering; | ||||
|     // Cache of tool changes per print layer.
 | ||||
|     std::unique_ptr<WipeTower::ToolChangeResult>          priming; | ||||
|     std::unique_ptr<std::vector<WipeTower::ToolChangeResult>> priming; | ||||
|     std::vector<std::vector<WipeTower::ToolChangeResult>> tool_changes; | ||||
|     std::unique_ptr<WipeTower::ToolChangeResult>          final_purge; | ||||
|     std::vector<float>                                    used_filament; | ||||
|  | @ -238,6 +241,8 @@ struct PrintStatistics | |||
|     PrintStatistics() { clear(); } | ||||
|     std::string                     estimated_normal_print_time; | ||||
|     std::string                     estimated_silent_print_time; | ||||
|     std::vector<std::string>        estimated_normal_color_print_times; | ||||
|     std::vector<std::string>        estimated_silent_color_print_times; | ||||
|     double                          total_used_filament; | ||||
|     double                          total_extruded_volume; | ||||
|     double                          total_cost; | ||||
|  | @ -256,6 +261,8 @@ struct PrintStatistics | |||
|     void clear() { | ||||
|         estimated_normal_print_time.clear(); | ||||
|         estimated_silent_print_time.clear(); | ||||
|         estimated_normal_color_print_times.clear(); | ||||
|         estimated_silent_color_print_times.clear(); | ||||
|         total_used_filament    = 0.; | ||||
|         total_extruded_volume  = 0.; | ||||
|         total_cost             = 0.; | ||||
|  | @ -291,11 +298,6 @@ public: | |||
| 
 | ||||
|     ApplyStatus         apply(const Model &model, const DynamicPrintConfig &config) override; | ||||
| 
 | ||||
|     // The following three methods are used by the Perl tests only. Get rid of them!
 | ||||
|     void                reload_object(size_t idx); | ||||
|     void                add_model_object(ModelObject* model_object, int idx = -1); | ||||
|     bool                apply_config_perl_tests_only(DynamicPrintConfig config); | ||||
| 
 | ||||
|     void                process() override; | ||||
|     // Exports G-code into a file name based on the path_template, returns the file path of the generated G-code file.
 | ||||
|     // If preview_data is not null, the preview_data is filled in for the G-code visualization (not used by the command line Slic3r).
 | ||||
|  |  | |||
|  | @ -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.
 | ||||
|  |  | |||
|  | @ -36,7 +36,6 @@ PrintConfigDef::PrintConfigDef() | |||
| 
 | ||||
| void PrintConfigDef::init_common_params() | ||||
| { | ||||
|     t_optiondef_map &Options = this->options; | ||||
|     ConfigOptionDef* def; | ||||
| 
 | ||||
|     def = this->add("printer_technology", coEnum); | ||||
|  | @ -102,7 +101,6 @@ void PrintConfigDef::init_common_params() | |||
| 
 | ||||
| void PrintConfigDef::init_fff_params() | ||||
| { | ||||
|     t_optiondef_map &Options = this->options; | ||||
|     ConfigOptionDef* def; | ||||
| 
 | ||||
|     // Maximum extruder temperature, bumped to 1500 to support printing of glass.
 | ||||
|  | @ -368,7 +366,8 @@ void PrintConfigDef::init_fff_params() | |||
| 
 | ||||
|     def = this->add("end_filament_gcode", coStrings); | ||||
|     def->label = L("End G-code"); | ||||
|     def->tooltip = L("This end procedure is inserted at the end of the output file, before the printer end gcode. " | ||||
|     def->tooltip = L("This end procedure is inserted at the end of the output file, before the printer end gcode (and " | ||||
|                    "before any toolchange from this filament in case of multimaterial printers). " | ||||
|                    "Note that you can use placeholder variables for all Slic3r settings. " | ||||
|                    "If you have multiple extruders, the gcode is processed in extruder order."); | ||||
|     def->multiline = true; | ||||
|  | @ -406,10 +405,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); | ||||
|  | @ -1085,16 +1087,16 @@ void PrintConfigDef::init_fff_params() | |||
| 			// Add the machine feedrate limits for XYZE axes. (M203)
 | ||||
| 			def = this->add("machine_max_feedrate_" + axis.name, coFloats); | ||||
| 			def->full_label = (boost::format("Maximum feedrate %1%") % axis_upper).str(); | ||||
| 			L("Maximum feedrate X"); | ||||
| 			L("Maximum feedrate Y"); | ||||
| 			L("Maximum feedrate Z"); | ||||
| 			L("Maximum feedrate E"); | ||||
| 			(void)L("Maximum feedrate X"); | ||||
| 			(void)L("Maximum feedrate Y"); | ||||
| 			(void)L("Maximum feedrate Z"); | ||||
| 			(void)L("Maximum feedrate E"); | ||||
| 			def->category = L("Machine limits"); | ||||
| 			def->tooltip  = (boost::format("Maximum feedrate of the %1% axis") % axis_upper).str(); | ||||
| 			L("Maximum feedrate of the X axis"); | ||||
| 			L("Maximum feedrate of the Y axis"); | ||||
| 			L("Maximum feedrate of the Z axis"); | ||||
| 			L("Maximum feedrate of the E axis"); | ||||
| 			(void)L("Maximum feedrate of the X axis"); | ||||
| 			(void)L("Maximum feedrate of the Y axis"); | ||||
| 			(void)L("Maximum feedrate of the Z axis"); | ||||
| 			(void)L("Maximum feedrate of the E axis"); | ||||
| 			def->sidetext = L("mm/s"); | ||||
| 			def->min = 0; | ||||
| 			def->width = machine_limits_opt_width; | ||||
|  | @ -1103,16 +1105,16 @@ void PrintConfigDef::init_fff_params() | |||
| 			// Add the machine acceleration limits for XYZE axes (M201)
 | ||||
| 			def = this->add("machine_max_acceleration_" + axis.name, coFloats); | ||||
| 			def->full_label = (boost::format("Maximum acceleration %1%") % axis_upper).str(); | ||||
| 			L("Maximum acceleration X"); | ||||
| 			L("Maximum acceleration Y"); | ||||
| 			L("Maximum acceleration Z"); | ||||
| 			L("Maximum acceleration E"); | ||||
| 			(void)L("Maximum acceleration X"); | ||||
| 			(void)L("Maximum acceleration Y"); | ||||
| 			(void)L("Maximum acceleration Z"); | ||||
| 			(void)L("Maximum acceleration E"); | ||||
| 			def->category = L("Machine limits"); | ||||
| 			def->tooltip  = (boost::format("Maximum acceleration of the %1% axis") % axis_upper).str(); | ||||
| 			L("Maximum acceleration of the X axis"); | ||||
| 			L("Maximum acceleration of the Y axis"); | ||||
| 			L("Maximum acceleration of the Z axis"); | ||||
| 			L("Maximum acceleration of the E axis"); | ||||
| 			(void)L("Maximum acceleration of the X axis"); | ||||
| 			(void)L("Maximum acceleration of the Y axis"); | ||||
| 			(void)L("Maximum acceleration of the Z axis"); | ||||
| 			(void)L("Maximum acceleration of the E axis"); | ||||
| 			def->sidetext = L("mm/s²"); | ||||
| 			def->min = 0; | ||||
| 			def->width = machine_limits_opt_width; | ||||
|  | @ -1121,16 +1123,16 @@ void PrintConfigDef::init_fff_params() | |||
| 			// Add the machine jerk limits for XYZE axes (M205)
 | ||||
| 			def = this->add("machine_max_jerk_" + axis.name, coFloats); | ||||
| 			def->full_label = (boost::format("Maximum jerk %1%") % axis_upper).str(); | ||||
| 			L("Maximum jerk X"); | ||||
| 			L("Maximum jerk Y"); | ||||
| 			L("Maximum jerk Z"); | ||||
| 			L("Maximum jerk E"); | ||||
| 			(void)L("Maximum jerk X"); | ||||
| 			(void)L("Maximum jerk Y"); | ||||
| 			(void)L("Maximum jerk Z"); | ||||
| 			(void)L("Maximum jerk E"); | ||||
| 			def->category = L("Machine limits"); | ||||
| 			def->tooltip  = (boost::format("Maximum jerk of the %1% axis") % axis_upper).str(); | ||||
| 			L("Maximum jerk of the X axis"); | ||||
| 			L("Maximum jerk of the Y axis"); | ||||
| 			L("Maximum jerk of the Z axis"); | ||||
| 			L("Maximum jerk of the E axis"); | ||||
| 			(void)L("Maximum jerk of the X axis"); | ||||
| 			(void)L("Maximum jerk of the Y axis"); | ||||
| 			(void)L("Maximum jerk of the Z axis"); | ||||
| 			(void)L("Maximum jerk of the E axis"); | ||||
| 			def->sidetext = L("mm/s"); | ||||
| 			def->min = 0; | ||||
| 			def->width = machine_limits_opt_width; | ||||
|  | @ -1784,7 +1786,8 @@ void PrintConfigDef::init_fff_params() | |||
| 
 | ||||
|     def = this->add("start_filament_gcode", coStrings); | ||||
|     def->label = L("Start G-code"); | ||||
|     def->tooltip = L("This start procedure is inserted at the beginning, after any printer start gcode. " | ||||
|     def->tooltip = L("This start procedure is inserted at the beginning, after any printer start gcode (and " | ||||
|                    "after any toolchange to this filament in case of multi-material printers). " | ||||
|                    "This is used to override settings for a specific filament. If Slic3r detects " | ||||
|                    "M104, M109, M140 or M190 in your custom codes, such commands will " | ||||
|                    "not be prepended automatically so you're free to customize the order " | ||||
|  | @ -2039,9 +2042,10 @@ void PrintConfigDef::init_fff_params() | |||
|      | ||||
|     def = this->add("toolchange_gcode", coString); | ||||
|     def->label = L("Tool change G-code"); | ||||
|     def->tooltip = L("This custom code is inserted right before every extruder change. " | ||||
|                    "Note that you can use placeholder variables for all Slic3r settings as well " | ||||
|                    "as [previous_extruder] and [next_extruder]."); | ||||
|     def->tooltip = L("This custom code is inserted at every extruder change. If you don't leave this empty, you are " | ||||
|                      "expected to take care of the toolchange yourself - PrusaSlicer will not output any other G-code to " | ||||
|                      "change the filament. You can use placeholder variables for all Slic3r settings as well as [previous_extruder] " | ||||
|                      "and [next_extruder], so e.g. the standard toolchange command can be scripted as T[next_extruder]."); | ||||
|     def->multiline = true; | ||||
|     def->full_width = true; | ||||
|     def->height = 5; | ||||
|  | @ -2228,7 +2232,6 @@ void PrintConfigDef::init_fff_params() | |||
| 
 | ||||
| void PrintConfigDef::init_sla_params() | ||||
| { | ||||
|     t_optiondef_map &Options = this->options;     | ||||
|     ConfigOptionDef* def; | ||||
| 
 | ||||
|     // SLA Printer settings
 | ||||
|  | @ -2504,6 +2507,19 @@ void PrintConfigDef::init_sla_params() | |||
|     def->min = 0; | ||||
|     def->mode = comAdvanced; | ||||
|     def->set_default_value(new ConfigOptionFloat(1.0)); | ||||
|      | ||||
|     def = this->add("support_base_safety_distance", coFloat); | ||||
|     def->label = L("Support base safety distance"); | ||||
|     def->category = L("Supports"); | ||||
|     def->tooltip  = L( | ||||
|         "The minimum distance of the pillar base from the model in mm. " | ||||
|         "Makes sense in zero elevation mode where a gap according " | ||||
|         "to this parameter is inserted between the model and the pad."); | ||||
|     def->sidetext = L("mm"); | ||||
|     def->min = 0; | ||||
|     def->max = 10; | ||||
|     def->mode = comExpert; | ||||
|     def->set_default_value(new ConfigOptionFloat(1)); | ||||
| 
 | ||||
|     def = this->add("support_critical_angle", coFloat); | ||||
|     def->label = L("Critical angle"); | ||||
|  | @ -2537,7 +2553,9 @@ void PrintConfigDef::init_sla_params() | |||
|     def = this->add("support_object_elevation", coFloat); | ||||
|     def->label = L("Object elevation"); | ||||
|     def->category = L("Supports"); | ||||
|     def->tooltip = L("How much the supports should lift up the supported object."); | ||||
|     def->tooltip = L("How much the supports should lift up the supported object. " | ||||
|                      "If this value is zero, the bottom of the model geometry " | ||||
|                      "will be considered as part of the pad."); | ||||
|     def->sidetext = L("mm"); | ||||
|     def->min = 0; | ||||
|     def->max = 150; // This is the max height of print on SL1
 | ||||
|  | @ -2623,6 +2641,47 @@ void PrintConfigDef::init_sla_params() | |||
|     def->max = 90; | ||||
|     def->mode = comAdvanced; | ||||
|     def->set_default_value(new ConfigOptionFloat(45.0)); | ||||
|      | ||||
|     def = this->add("pad_object_gap", coFloat); | ||||
|     def->label = L("Pad object gap"); | ||||
|     def->category = L("Pad"); | ||||
|     def->tooltip  = L("The gap between the object bottom and the generated " | ||||
|                       "pad in zero elevation mode."); | ||||
|     def->sidetext = L("mm"); | ||||
|     def->min = 0; | ||||
|     def->max = 10; | ||||
|     def->mode = comExpert; | ||||
|     def->set_default_value(new ConfigOptionFloat(1)); | ||||
|      | ||||
|     def = this->add("pad_object_connector_stride", coFloat); | ||||
|     def->label = L("Pad object connector stride"); | ||||
|     def->category = L("Pad"); | ||||
|     def->tooltip = L("Distance between two connector sticks between " | ||||
|                      "the object pad and the generated pad."); | ||||
|     def->sidetext = L("mm"); | ||||
|     def->min = 0; | ||||
|     def->mode = comExpert; | ||||
|     def->set_default_value(new ConfigOptionFloat(10)); | ||||
|      | ||||
|     def = this->add("pad_object_connector_width", coFloat); | ||||
|     def->label = L("Pad object connector width"); | ||||
|     def->category = L("Pad"); | ||||
|     def->tooltip  = L("The width of the connectors sticks which connect the " | ||||
|                       "object pad and the generated pad."); | ||||
|     def->sidetext = L("mm"); | ||||
|     def->min = 0; | ||||
|     def->mode = comExpert; | ||||
|     def->set_default_value(new ConfigOptionFloat(0.5)); | ||||
|      | ||||
|     def = this->add("pad_object_connector_penetration", coFloat); | ||||
|     def->label = L("Pad object connector penetration"); | ||||
|     def->category = L("Pad"); | ||||
|     def->tooltip  = L( | ||||
|         "How much should the tiny connectors penetrate into the model body."); | ||||
|     def->sidetext = L("mm"); | ||||
|     def->min = 0; | ||||
|     def->mode = comExpert; | ||||
|     def->set_default_value(new ConfigOptionFloat(0.3)); | ||||
| } | ||||
| 
 | ||||
| void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value) | ||||
|  | @ -3194,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)  | ||||
|  |  | |||
|  | @ -983,6 +983,9 @@ public: | |||
| 
 | ||||
|     // The height of the pillar base cone in mm.
 | ||||
|     ConfigOptionFloat support_base_height /*= 1.0*/; | ||||
|      | ||||
|     // The minimum distance of the pillar base from the model in mm.
 | ||||
|     ConfigOptionFloat support_base_safety_distance; /*= 1.0*/; | ||||
| 
 | ||||
|     // The default angle for connecting support sticks and junctions.
 | ||||
|     ConfigOptionFloat support_critical_angle /*= 45*/; | ||||
|  | @ -996,7 +999,7 @@ public: | |||
|     // The elevation in Z direction upwards. This is the space between the pad
 | ||||
|     // and the model object's bounding box bottom. Units in mm.
 | ||||
|     ConfigOptionFloat support_object_elevation /*= 5.0*/; | ||||
| 
 | ||||
|      | ||||
|     /////// Following options influence automatic support points placement:
 | ||||
|     ConfigOptionInt support_points_density_relative; | ||||
|     ConfigOptionFloat support_points_minimal_distance; | ||||
|  | @ -1021,6 +1024,26 @@ public: | |||
| 
 | ||||
|     // The slope of the pad wall...
 | ||||
|     ConfigOptionFloat pad_wall_slope; | ||||
|      | ||||
|     // /////////////////////////////////////////////////////////////////////////
 | ||||
|     // Zero elevation mode parameters:
 | ||||
|     //    - The object pad will be derived from the the model geometry.
 | ||||
|     //    - There will be a gap between the object pad and the generated pad
 | ||||
|     //      according to the support_base_safety_distance parameter.
 | ||||
|     //    - The two pads will be connected with tiny connector sticks
 | ||||
|     // /////////////////////////////////////////////////////////////////////////
 | ||||
|      | ||||
|     // This is the gap between the object bottom and the generated pad
 | ||||
|     ConfigOptionFloat pad_object_gap; | ||||
|      | ||||
|     // How far to place the connector sticks on the object pad perimeter
 | ||||
|     ConfigOptionFloat pad_object_connector_stride; | ||||
|      | ||||
|     // The width of the connectors sticks
 | ||||
|     ConfigOptionFloat pad_object_connector_width; | ||||
|      | ||||
|     // How much should the tiny connectors penetrate into the model body
 | ||||
|     ConfigOptionFloat pad_object_connector_penetration; | ||||
| 
 | ||||
| protected: | ||||
|     void initialize(StaticCacheBase &cache, const char *base_ptr) | ||||
|  | @ -1038,6 +1061,7 @@ protected: | |||
|         OPT_PTR(support_pillar_widening_factor); | ||||
|         OPT_PTR(support_base_diameter); | ||||
|         OPT_PTR(support_base_height); | ||||
|         OPT_PTR(support_base_safety_distance); | ||||
|         OPT_PTR(support_critical_angle); | ||||
|         OPT_PTR(support_max_bridge_length); | ||||
|         OPT_PTR(support_max_pillar_link_distance); | ||||
|  | @ -1050,6 +1074,10 @@ protected: | |||
|         OPT_PTR(pad_max_merge_distance); | ||||
|         OPT_PTR(pad_edge_radius); | ||||
|         OPT_PTR(pad_wall_slope); | ||||
|         OPT_PTR(pad_object_gap); | ||||
|         OPT_PTR(pad_object_connector_stride); | ||||
|         OPT_PTR(pad_object_connector_width); | ||||
|         OPT_PTR(pad_object_connector_penetration); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
|  | @ -1190,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(); } | ||||
|  | @ -1199,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 | ||||
|  |  | |||
|  | @ -18,7 +18,7 @@ | |||
| 
 | ||||
| #include <Shiny/Shiny.h> | ||||
| 
 | ||||
| //! macro used to mark string used at localization, 
 | ||||
| //! macro used to mark string used at localization,
 | ||||
| //! return same string
 | ||||
| #define L(s) Slic3r::I18N::translate(s) | ||||
| 
 | ||||
|  | @ -49,7 +49,7 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, bool add_insta | |||
|     { | ||||
|         // Translate meshes so that our toolpath generation algorithms work with smaller
 | ||||
|         // XY coordinates; this translation is an optimization and not strictly required.
 | ||||
|         // A cloned mesh will be aligned to 0 before slicing in _slice_region() since we
 | ||||
|         // A cloned mesh will be aligned to 0 before slicing in slice_region() since we
 | ||||
|         // don't assume it's already aligned and we don't alter the original position in model.
 | ||||
|         // We store the XY translation so that we can place copies correctly in the output G-code
 | ||||
|         // (copies are expressed in G-code coordinates and this translation is not publicly exposed).
 | ||||
|  | @ -430,7 +430,7 @@ SupportLayer* PrintObject::add_support_layer(int id, coordf_t height, coordf_t p | |||
|     return m_support_layers.back(); | ||||
| } | ||||
| 
 | ||||
| SupportLayerPtrs::const_iterator PrintObject::insert_support_layer(SupportLayerPtrs::const_iterator pos, int id, coordf_t height, coordf_t print_z, coordf_t slice_z) | ||||
| SupportLayerPtrs::const_iterator PrintObject::insert_support_layer(SupportLayerPtrs::const_iterator pos, size_t id, coordf_t height, coordf_t print_z, coordf_t slice_z) | ||||
| { | ||||
|     return m_support_layers.insert(pos, new SupportLayer(id, this, height, print_z, slice_z)); | ||||
| } | ||||
|  | @ -590,7 +590,12 @@ bool PrintObject::invalidate_step(PrintObjectStep step) | |||
| 
 | ||||
| bool PrintObject::invalidate_all_steps() | ||||
| { | ||||
|     return Inherited::invalidate_all_steps() | m_print->invalidate_all_steps(); | ||||
| 	// First call the "invalidate" functions, which may cancel background processing.
 | ||||
|     bool result = Inherited::invalidate_all_steps() | m_print->invalidate_all_steps(); | ||||
| 	// Then reset some of the depending values.
 | ||||
| 	this->m_slicing_params.valid = false; | ||||
| 	this->region_volumes.clear(); | ||||
| 	return result; | ||||
| } | ||||
| 
 | ||||
| bool PrintObject::has_support_material() const | ||||
|  | @ -620,7 +625,7 @@ void PrintObject::detect_surfaces_type() | |||
|     // should be visible.
 | ||||
|     bool interface_shells = m_config.interface_shells.value; | ||||
| 
 | ||||
|     for (int idx_region = 0; idx_region < this->region_volumes.size(); ++ idx_region) { | ||||
|     for (size_t idx_region = 0; idx_region < this->region_volumes.size(); ++ idx_region) { | ||||
|         BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << idx_region << " in parallel - start"; | ||||
| #ifdef SLIC3R_DEBUG_SLICE_PROCESSING | ||||
|         for (Layer *layer : m_layers) | ||||
|  | @ -806,8 +811,6 @@ void PrintObject::process_external_surfaces() | |||
|     BOOST_LOG_TRIVIAL(info) << "Processing external surfaces..." << log_memory_info(); | ||||
| 
 | ||||
| 	for (size_t region_id = 0; region_id < this->region_volumes.size(); ++region_id) { | ||||
|         const PrintRegion ®ion = *m_print->regions()[region_id]; | ||||
|          | ||||
|         BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces for region " << region_id << " in parallel - start"; | ||||
|         tbb::parallel_for( | ||||
|             tbb::blocked_range<size_t>(0, m_layers.size()), | ||||
|  | @ -1028,7 +1031,6 @@ void PrintObject::discover_vertical_shells() | |||
|                         bool hole_first = true; | ||||
|                         for (int n = (int)idx_layer - n_extra_bottom_layers; n <= (int)idx_layer + n_extra_top_layers; ++ n) | ||||
|                             if (n >= 0 && n < (int)m_layers.size()) { | ||||
|                                 Layer &neighbor_layer = *m_layers[n]; | ||||
|                                 const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[n]; | ||||
|                                 if (hole_first) { | ||||
|                                     hole_first = false; | ||||
|  | @ -1354,10 +1356,12 @@ PrintObjectConfig PrintObject::object_config_from_model_object(const PrintObject | |||
|     return config; | ||||
| } | ||||
| 
 | ||||
| PrintRegionConfig PrintObject::region_config_from_model_volume(const PrintRegionConfig &default_region_config, const ModelVolume &volume, size_t num_extruders) | ||||
| PrintRegionConfig PrintObject::region_config_from_model_volume(const PrintRegionConfig &default_region_config, const DynamicPrintConfig *layer_range_config, const ModelVolume &volume, size_t num_extruders) | ||||
| { | ||||
|     PrintRegionConfig config = default_region_config; | ||||
|     normalize_and_apply_config(config, volume.get_object()->config); | ||||
|     if (layer_range_config != nullptr) | ||||
|     	normalize_and_apply_config(config, *layer_range_config); | ||||
|     normalize_and_apply_config(config, volume.config); | ||||
|     if (! volume.material_id().empty()) | ||||
|         normalize_and_apply_config(config, volume.material()->config); | ||||
|  | @ -1375,28 +1379,37 @@ void PrintObject::update_slicing_parameters() | |||
|             this->print()->config(), m_config, unscale<double>(this->size(2)), this->object_extruders()); | ||||
| } | ||||
| 
 | ||||
| SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full_config, const ModelObject &model_object, float object_max_z) | ||||
| SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig& full_config, const ModelObject& model_object, float object_max_z) | ||||
| { | ||||
|     PrintConfig         print_config; | ||||
|     PrintObjectConfig   object_config; | ||||
|     PrintRegionConfig   default_region_config; | ||||
|     print_config .apply(full_config, true); | ||||
|     object_config.apply(full_config, true); | ||||
|     default_region_config.apply(full_config, true); | ||||
|     size_t              num_extruders = print_config.nozzle_diameter.size(); | ||||
|     object_config = object_config_from_model_object(object_config, model_object, num_extruders); | ||||
| 	PrintConfig         print_config; | ||||
| 	PrintObjectConfig   object_config; | ||||
| 	PrintRegionConfig   default_region_config; | ||||
| 	print_config.apply(full_config, true); | ||||
| 	object_config.apply(full_config, true); | ||||
| 	default_region_config.apply(full_config, true); | ||||
| 	size_t              num_extruders = print_config.nozzle_diameter.size(); | ||||
| 	object_config = object_config_from_model_object(object_config, model_object, num_extruders); | ||||
| 
 | ||||
|     std::vector<unsigned int> object_extruders; | ||||
|     for (const ModelVolume *model_volume : model_object.volumes) | ||||
|         if (model_volume->is_model_part()) | ||||
|             PrintRegion::collect_object_printing_extruders( | ||||
|                 print_config, | ||||
|                 region_config_from_model_volume(default_region_config, *model_volume, num_extruders), | ||||
|                 object_extruders); | ||||
| 	std::vector<unsigned int> object_extruders; | ||||
| 	for (const ModelVolume* model_volume : model_object.volumes) | ||||
| 		if (model_volume->is_model_part()) { | ||||
| 			PrintRegion::collect_object_printing_extruders( | ||||
| 				print_config, | ||||
| 				region_config_from_model_volume(default_region_config, nullptr, *model_volume, num_extruders), | ||||
| 				object_extruders); | ||||
| 			for (const std::pair<const t_layer_height_range, DynamicPrintConfig> &range_and_config : model_object.layer_config_ranges) | ||||
| 				if (range_and_config.second.has("perimeter_extruder") || | ||||
| 					range_and_config.second.has("infill_extruder") || | ||||
| 					range_and_config.second.has("solid_infill_extruder")) | ||||
| 					PrintRegion::collect_object_printing_extruders( | ||||
| 						print_config, | ||||
| 						region_config_from_model_volume(default_region_config, &range_and_config.second, *model_volume, num_extruders), | ||||
| 						object_extruders); | ||||
| 		} | ||||
|     sort_remove_duplicates(object_extruders); | ||||
| 
 | ||||
|     if (object_max_z <= 0.f) | ||||
|         object_max_z = model_object.raw_bounding_box().size().z(); | ||||
|         object_max_z = (float)model_object.raw_bounding_box().size().z(); | ||||
| 	return SlicingParameters::create_from_config(print_config, object_config, object_max_z, object_extruders); | ||||
| } | ||||
| 
 | ||||
|  | @ -1430,12 +1443,12 @@ bool PrintObject::update_layer_height_profile(const ModelObject &model_object, c | |||
|         layer_height_profile.clear(); | ||||
| 
 | ||||
|     if (layer_height_profile.empty()) { | ||||
|         if (0) | ||||
|     	if (0) | ||||
| //        if (this->layer_height_profile.empty())
 | ||||
|             layer_height_profile = layer_height_profile_adaptive(slicing_parameters, model_object.layer_height_ranges, model_object.volumes); | ||||
|         	layer_height_profile = layer_height_profile_adaptive(slicing_parameters, model_object.layer_config_ranges, model_object.volumes); | ||||
|         else | ||||
|             layer_height_profile = layer_height_profile_from_ranges(slicing_parameters, model_object.layer_height_ranges); | ||||
|         updated = true; | ||||
|         	layer_height_profile = layer_height_profile_from_ranges(slicing_parameters, model_object.layer_config_ranges);     // #ys_FIXME_experiment
 | ||||
|        	updated = true; | ||||
|     } | ||||
|     return updated; | ||||
| } | ||||
|  | @ -1489,22 +1502,28 @@ void PrintObject::_slice(const std::vector<coordf_t> &layer_height_profile) | |||
|     } | ||||
| 
 | ||||
|     // Count model parts and modifier meshes, check whether the model parts are of the same region.
 | ||||
|     int              single_volume_region = -2; // not set yet
 | ||||
|     int              all_volumes_single_region = -2; // not set yet
 | ||||
|     bool 			 has_z_ranges  = false; | ||||
| 	size_t           num_volumes   = 0; | ||||
|     size_t           num_modifiers = 0; | ||||
|     std::vector<int> map_volume_to_region(this->model_object()->volumes.size()); | ||||
|     for (int region_id = 0; region_id < (int)this->region_volumes.size(); ++ region_id) { | ||||
|         for (int volume_id : this->region_volumes[region_id]) { | ||||
| 		int last_volume_id = -1; | ||||
|         for (const std::pair<t_layer_height_range, int> &volume_and_range : this->region_volumes[region_id]) { | ||||
| 			const int		   volume_id    = volume_and_range.second; | ||||
| 			const ModelVolume *model_volume = this->model_object()->volumes[volume_id]; | ||||
|             if (model_volume->is_model_part()) { | ||||
|                 map_volume_to_region[volume_id] = region_id; | ||||
|                 if (single_volume_region == -2) | ||||
|                     // first model volume met
 | ||||
|                     single_volume_region = region_id; | ||||
|                 else if (single_volume_region != region_id) | ||||
|                     // multiple volumes met and they are not equal
 | ||||
|                     single_volume_region = -1; | ||||
| 				++ num_volumes; | ||||
| 				if (last_volume_id == volume_id) { | ||||
| 					has_z_ranges = true; | ||||
| 				} else { | ||||
| 					last_volume_id = volume_id; | ||||
| 					if (all_volumes_single_region == -2) | ||||
| 						// first model volume met
 | ||||
| 						all_volumes_single_region = region_id; | ||||
| 					else if (all_volumes_single_region != region_id) | ||||
| 						// multiple volumes met and they are not equal
 | ||||
| 						all_volumes_single_region = -1; | ||||
| 					++ num_volumes; | ||||
| 				} | ||||
|             } else if (model_volume->is_modifier()) | ||||
|                 ++ num_modifiers; | ||||
|         } | ||||
|  | @ -1514,13 +1533,13 @@ void PrintObject::_slice(const std::vector<coordf_t> &layer_height_profile) | |||
|     // Slice all non-modifier volumes.
 | ||||
|     bool clipped  = false; | ||||
|     bool upscaled = false; | ||||
|     if (! m_config.clip_multipart_objects.value || single_volume_region >= 0) { | ||||
|     if (! has_z_ranges && (! m_config.clip_multipart_objects.value || all_volumes_single_region >= 0)) { | ||||
|         // Cheap path: Slice regions without mutual clipping.
 | ||||
|         // The cheap path is possible if no clipping is allowed or if slicing volumes of just a single region.
 | ||||
|         for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { | ||||
|             BOOST_LOG_TRIVIAL(debug) << "Slicing objects - region " << region_id; | ||||
|             // slicing in parallel
 | ||||
|             std::vector<ExPolygons> expolygons_by_layer = this->_slice_region(region_id, slice_zs, false); | ||||
|             std::vector<ExPolygons> expolygons_by_layer = this->slice_region(region_id, slice_zs); | ||||
|             m_print->throw_if_canceled(); | ||||
|             BOOST_LOG_TRIVIAL(debug) << "Slicing objects - append slices " << region_id << " start"; | ||||
|             for (size_t layer_id = 0; layer_id < expolygons_by_layer.size(); ++ layer_id) | ||||
|  | @ -1541,15 +1560,29 @@ void PrintObject::_slice(const std::vector<coordf_t> &layer_height_profile) | |||
|         }; | ||||
|         std::vector<SlicedVolume> sliced_volumes; | ||||
|         sliced_volumes.reserve(num_volumes); | ||||
| 		for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) | ||||
| 			for (int volume_id : this->region_volumes[region_id]) { | ||||
| 		for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { | ||||
| 			const std::vector<std::pair<t_layer_height_range, int>> &volumes_and_ranges = this->region_volumes[region_id]; | ||||
| 			for (size_t i = 0; i < volumes_and_ranges.size(); ) { | ||||
| 				int 			   volume_id    = volumes_and_ranges[i].second; | ||||
| 				const ModelVolume *model_volume = this->model_object()->volumes[volume_id]; | ||||
| 				if (model_volume->is_model_part()) { | ||||
| 					BOOST_LOG_TRIVIAL(debug) << "Slicing objects - volume " << volume_id; | ||||
| 					// Find the ranges of this volume. Ranges in volumes_and_ranges must not overlap for a single volume.
 | ||||
| 					std::vector<t_layer_height_range> ranges; | ||||
| 					ranges.emplace_back(volumes_and_ranges[i].first); | ||||
| 					size_t j = i + 1; | ||||
| 					for (; j < volumes_and_ranges.size() && volume_id == volumes_and_ranges[j].second; ++ j) | ||||
| 						if (! ranges.empty() && std::abs(ranges.back().second - volumes_and_ranges[j].first.first) < EPSILON) | ||||
| 							ranges.back().second = volumes_and_ranges[j].first.second; | ||||
| 						else | ||||
| 							ranges.emplace_back(volumes_and_ranges[j].first); | ||||
|                     // slicing in parallel
 | ||||
| 					sliced_volumes.emplace_back(volume_id, map_volume_to_region[volume_id], this->_slice_volume(slice_zs, *model_volume)); | ||||
| 				} | ||||
| 					sliced_volumes.emplace_back(volume_id, (int)region_id, this->slice_volume(slice_zs, ranges, *model_volume)); | ||||
| 					i = j; | ||||
| 				} else | ||||
| 					++ i; | ||||
| 			} | ||||
| 		} | ||||
|         // Second clip the volumes in the order they are presented at the user interface.
 | ||||
|         BOOST_LOG_TRIVIAL(debug) << "Slicing objects - parallel clipping - start"; | ||||
|         tbb::parallel_for( | ||||
|  | @ -1603,7 +1636,7 @@ void PrintObject::_slice(const std::vector<coordf_t> &layer_height_profile) | |||
|         for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { | ||||
|             BOOST_LOG_TRIVIAL(debug) << "Slicing modifier volumes - region " << region_id; | ||||
|             // slicing in parallel
 | ||||
|             std::vector<ExPolygons> expolygons_by_layer = this->_slice_region(region_id, slice_zs, true); | ||||
|             std::vector<ExPolygons> expolygons_by_layer = this->slice_modifiers(region_id, slice_zs); | ||||
|             m_print->throw_if_canceled(); | ||||
|             if (expolygons_by_layer.empty()) | ||||
|                 continue; | ||||
|  | @ -1619,7 +1652,7 @@ void PrintObject::_slice(const std::vector<coordf_t> &layer_height_profile) | |||
|                             Layer       *layer = m_layers[layer_id]; | ||||
|                             LayerRegion *layerm = layer->m_regions[region_id]; | ||||
|                             LayerRegion *other_layerm = layer->m_regions[other_region_id]; | ||||
|                             if (layerm == nullptr || other_layerm == nullptr) | ||||
|                             if (layerm == nullptr || other_layerm == nullptr || other_layerm->slices.empty() || expolygons_by_layer[layer_id].empty()) | ||||
|                                 continue; | ||||
|                             Polygons other_slices = to_polygons(other_layerm->slices); | ||||
|                             ExPolygons my_parts = intersection_ex(other_slices, to_polygons(expolygons_by_layer[layer_id])); | ||||
|  | @ -1752,46 +1785,127 @@ end: | |||
|     BOOST_LOG_TRIVIAL(debug) << "Slicing objects - make_slices in parallel - end"; | ||||
| } | ||||
| 
 | ||||
| std::vector<ExPolygons> PrintObject::_slice_region(size_t region_id, const std::vector<float> &z, bool modifier) | ||||
| // To be used only if there are no layer span specific configurations applied, which would lead to z ranges being generated for this region.
 | ||||
| std::vector<ExPolygons> PrintObject::slice_region(size_t region_id, const std::vector<float> &z) const | ||||
| { | ||||
|     std::vector<const ModelVolume*> volumes; | ||||
| 	std::vector<const ModelVolume*> volumes; | ||||
|     if (region_id < this->region_volumes.size()) { | ||||
|         for (int volume_id : this->region_volumes[region_id]) { | ||||
|             const ModelVolume *volume = this->model_object()->volumes[volume_id]; | ||||
|             if (modifier ? volume->is_modifier() : volume->is_model_part()) | ||||
|                 volumes.emplace_back(volume); | ||||
|         } | ||||
| 		for (const std::pair<t_layer_height_range, int> &volume_and_range : this->region_volumes[region_id]) { | ||||
| 			const ModelVolume *volume = this->model_object()->volumes[volume_and_range.second]; | ||||
| 			if (volume->is_model_part()) | ||||
| 				volumes.emplace_back(volume); | ||||
| 		} | ||||
|     } | ||||
|     return this->_slice_volumes(z, volumes); | ||||
| 	return this->slice_volumes(z, volumes); | ||||
| } | ||||
| 
 | ||||
| std::vector<ExPolygons> PrintObject::slice_support_enforcers() const | ||||
| // Z ranges are not applicable to modifier meshes, therefore a sinle volume will be found in volume_and_range at most once.
 | ||||
| std::vector<ExPolygons> PrintObject::slice_modifiers(size_t region_id, const std::vector<float> &slice_zs) const | ||||
| { | ||||
| 	std::vector<ExPolygons> out; | ||||
|     if (region_id < this->region_volumes.size()) | ||||
|     { | ||||
| 		std::vector<std::vector<t_layer_height_range>> volume_ranges; | ||||
| 		const std::vector<std::pair<t_layer_height_range, int>> &volumes_and_ranges = this->region_volumes[region_id]; | ||||
| 		volume_ranges.reserve(volumes_and_ranges.size()); | ||||
| 		for (size_t i = 0; i < volumes_and_ranges.size(); ) { | ||||
| 			int 			   volume_id    = volumes_and_ranges[i].second; | ||||
| 			const ModelVolume *model_volume = this->model_object()->volumes[volume_id]; | ||||
| 			if (model_volume->is_modifier()) { | ||||
| 				std::vector<t_layer_height_range> ranges; | ||||
| 				ranges.emplace_back(volumes_and_ranges[i].first); | ||||
| 				size_t j = i + 1; | ||||
| 				for (; j < volumes_and_ranges.size() && volume_id == volumes_and_ranges[j].second; ++ j) { | ||||
| 					if (! ranges.empty() && std::abs(ranges.back().second - volumes_and_ranges[j].first.first) < EPSILON) | ||||
| 						ranges.back().second = volumes_and_ranges[j].first.second; | ||||
| 					else | ||||
| 						ranges.emplace_back(volumes_and_ranges[j].first); | ||||
| 				} | ||||
| 				volume_ranges.emplace_back(std::move(ranges)); | ||||
| 				i = j; | ||||
| 			} else | ||||
| 				++ i; | ||||
| 		} | ||||
| 
 | ||||
| 		if (! volume_ranges.empty())  | ||||
| 		{ | ||||
| 			bool equal_ranges = true; | ||||
| 			for (size_t i = 1; i < volume_ranges.size(); ++ i) { | ||||
| 				assert(! volume_ranges[i].empty()); | ||||
| 				if (volume_ranges.front() != volume_ranges[i]) { | ||||
| 					equal_ranges = false; | ||||
| 					break; | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			if (equal_ranges && volume_ranges.front().size() == 1 && volume_ranges.front().front() == t_layer_height_range(0, DBL_MAX)) { | ||||
| 				// No modifier in this region was split to layer spans.
 | ||||
| 				std::vector<const ModelVolume*> volumes; | ||||
| 				for (const std::pair<t_layer_height_range, int> &volume_and_range : this->region_volumes[region_id]) { | ||||
| 					const ModelVolume *volume = this->model_object()->volumes[volume_and_range.second]; | ||||
| 					if (volume->is_modifier()) | ||||
| 						volumes.emplace_back(volume); | ||||
| 				} | ||||
| 				out = this->slice_volumes(slice_zs, volumes); | ||||
| 			} else { | ||||
| 				// Some modifier in this region was split to layer spans.
 | ||||
| 				std::vector<char> merge; | ||||
| 				for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { | ||||
| 					const std::vector<std::pair<t_layer_height_range, int>> &volumes_and_ranges = this->region_volumes[region_id]; | ||||
| 					for (size_t i = 0; i < volumes_and_ranges.size(); ) { | ||||
| 						int 			   volume_id    = volumes_and_ranges[i].second; | ||||
| 						const ModelVolume *model_volume = this->model_object()->volumes[volume_id]; | ||||
| 						if (model_volume->is_modifier()) { | ||||
| 							BOOST_LOG_TRIVIAL(debug) << "Slicing modifiers - volume " << volume_id; | ||||
| 							// Find the ranges of this volume. Ranges in volumes_and_ranges must not overlap for a single volume.
 | ||||
| 							std::vector<t_layer_height_range> ranges; | ||||
| 							ranges.emplace_back(volumes_and_ranges[i].first); | ||||
| 							size_t j = i + 1; | ||||
| 							for (; j < volumes_and_ranges.size() && volume_id == volumes_and_ranges[j].second; ++ j) | ||||
| 								ranges.emplace_back(volumes_and_ranges[j].first); | ||||
| 			                // slicing in parallel
 | ||||
| 			                std::vector<ExPolygons> this_slices = this->slice_volume(slice_zs, ranges, *model_volume); | ||||
| 			                if (out.empty()) { | ||||
| 			                	out = std::move(this_slices); | ||||
| 			                	merge.assign(out.size(), false); | ||||
| 			                } else { | ||||
| 			                	for (size_t i = 0; i < out.size(); ++ i) | ||||
| 			                		if (! this_slices[i].empty()) | ||||
| 			                			if (! out[i].empty()) { | ||||
| 			                				append(out[i], this_slices[i]); | ||||
| 			                				merge[i] = true; | ||||
| 			                			} else | ||||
| 			                				out[i] = std::move(this_slices[i]); | ||||
| 			                } | ||||
| 							i = j; | ||||
| 						} else | ||||
| 							++ i; | ||||
| 					} | ||||
| 				} | ||||
| 				for (size_t i = 0; i < merge.size(); ++ i) | ||||
| 					if (merge[i]) | ||||
| 						out[i] = union_ex(out[i]); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return out; | ||||
| } | ||||
| 
 | ||||
| std::vector<ExPolygons> PrintObject::slice_support_volumes(const ModelVolumeType &model_volume_type) const | ||||
| { | ||||
|     std::vector<const ModelVolume*> volumes; | ||||
|     for (const ModelVolume *volume : this->model_object()->volumes) | ||||
|         if (volume->is_support_enforcer()) | ||||
|         if (volume->type() == model_volume_type) | ||||
|             volumes.emplace_back(volume); | ||||
|     std::vector<float> zs; | ||||
|     zs.reserve(this->layers().size()); | ||||
|     for (const Layer *l : this->layers()) | ||||
|         zs.emplace_back((float)l->slice_z); | ||||
|     return this->_slice_volumes(zs, volumes); | ||||
|     return this->slice_volumes(zs, volumes); | ||||
| } | ||||
| 
 | ||||
| std::vector<ExPolygons> PrintObject::slice_support_blockers() const | ||||
| { | ||||
|     std::vector<const ModelVolume*> volumes; | ||||
|     for (const ModelVolume *volume : this->model_object()->volumes) | ||||
|         if (volume->is_support_blocker()) | ||||
|             volumes.emplace_back(volume); | ||||
|     std::vector<float> zs; | ||||
|     zs.reserve(this->layers().size()); | ||||
|     for (const Layer *l : this->layers()) | ||||
|         zs.emplace_back((float)l->slice_z); | ||||
|     return this->_slice_volumes(zs, volumes); | ||||
| } | ||||
| 
 | ||||
| std::vector<ExPolygons> PrintObject::_slice_volumes(const std::vector<float> &z, const std::vector<const ModelVolume*> &volumes) const | ||||
| std::vector<ExPolygons> PrintObject::slice_volumes(const std::vector<float> &z, const std::vector<const ModelVolume*> &volumes) const | ||||
| { | ||||
|     std::vector<ExPolygons> layers; | ||||
|     if (! volumes.empty()) { | ||||
|  | @ -1828,34 +1942,71 @@ std::vector<ExPolygons> PrintObject::_slice_volumes(const std::vector<float> &z, | |||
|     return layers; | ||||
| } | ||||
| 
 | ||||
| std::vector<ExPolygons> PrintObject::_slice_volume(const std::vector<float> &z, const ModelVolume &volume) const | ||||
| std::vector<ExPolygons> PrintObject::slice_volume(const std::vector<float> &z, const ModelVolume &volume) const | ||||
| { | ||||
|     std::vector<ExPolygons> layers; | ||||
|     // Compose mesh.
 | ||||
|     //FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them.
 | ||||
|     TriangleMesh mesh(volume.mesh()); | ||||
|     mesh.transform(volume.get_matrix(), true); | ||||
| 	if (mesh.repaired) { | ||||
| 		//FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it.
 | ||||
| 		stl_check_facets_exact(&mesh.stl); | ||||
|     if (! z.empty()) { | ||||
| 	    // Compose mesh.
 | ||||
| 	    //FIXME better to split the mesh into separate shells, perform slicing over each shell separately and then to use a Boolean operation to merge them.
 | ||||
| 	    TriangleMesh mesh(volume.mesh()); | ||||
| 	    mesh.transform(volume.get_matrix(), true); | ||||
| 		if (mesh.repaired) { | ||||
| 			//FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it.
 | ||||
| 			stl_check_facets_exact(&mesh.stl); | ||||
| 		} | ||||
| 	    if (mesh.stl.stats.number_of_facets > 0) { | ||||
| 	        mesh.transform(m_trafo, true); | ||||
| 	        // apply XY shift
 | ||||
| 	        mesh.translate(- unscale<float>(m_copies_shift(0)), - unscale<float>(m_copies_shift(1)), 0); | ||||
| 	        // perform actual slicing
 | ||||
| 	        TriangleMeshSlicer mslicer; | ||||
| 	        const Print *print = this->print(); | ||||
| 	        auto callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();}); | ||||
| 	        // TriangleMeshSlicer needs the shared vertices.
 | ||||
| 	        mesh.require_shared_vertices(); | ||||
| 	        mslicer.init(&mesh, callback); | ||||
| 	        mslicer.slice(z, float(m_config.slice_closing_radius.value), &layers, callback); | ||||
| 	        m_print->throw_if_canceled(); | ||||
| 	    } | ||||
| 	} | ||||
|     if (mesh.stl.stats.number_of_facets > 0) { | ||||
|         mesh.transform(m_trafo, true); | ||||
|         // apply XY shift
 | ||||
|         mesh.translate(- unscale<float>(m_copies_shift(0)), - unscale<float>(m_copies_shift(1)), 0); | ||||
|         // perform actual slicing
 | ||||
|         TriangleMeshSlicer mslicer; | ||||
|         const Print *print = this->print(); | ||||
|         auto callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();}); | ||||
|         // TriangleMeshSlicer needs the shared vertices.
 | ||||
|         mesh.require_shared_vertices(); | ||||
|         mslicer.init(&mesh, callback); | ||||
|         mslicer.slice(z, float(m_config.slice_closing_radius.value), &layers, callback); | ||||
|         m_print->throw_if_canceled(); | ||||
|     } | ||||
|     return layers; | ||||
| } | ||||
| 
 | ||||
| // Filter the zs not inside the ranges. The ranges are closed at the botton and open at the top, they are sorted lexicographically and non overlapping.
 | ||||
| std::vector<ExPolygons> PrintObject::slice_volume(const std::vector<float> &z, const std::vector<t_layer_height_range> &ranges, const ModelVolume &volume) const | ||||
| { | ||||
| 	std::vector<ExPolygons> out; | ||||
| 	if (! z.empty() && ! ranges.empty()) { | ||||
| 		if (ranges.size() == 1 && z.front() >= ranges.front().first && z.back() < ranges.front().second) { | ||||
| 			// All layers fit into a single range.
 | ||||
| 			out = this->slice_volume(z, volume); | ||||
| 		} else { | ||||
| 			std::vector<float> 					   z_filtered; | ||||
| 			std::vector<std::pair<size_t, size_t>> n_filtered; | ||||
| 			z_filtered.reserve(z.size()); | ||||
| 			n_filtered.reserve(2 * ranges.size()); | ||||
| 			size_t i = 0; | ||||
| 			for (const t_layer_height_range &range : ranges) { | ||||
| 				for (; i < z.size() && z[i] < range.first; ++ i) ; | ||||
| 				size_t first = i; | ||||
| 				for (; i < z.size() && z[i] < range.second; ++ i) | ||||
| 					z_filtered.emplace_back(z[i]); | ||||
| 				if (i > first) | ||||
| 					n_filtered.emplace_back(std::make_pair(first, i)); | ||||
| 			} | ||||
| 			if (! n_filtered.empty()) { | ||||
| 				std::vector<ExPolygons> layers = this->slice_volume(z_filtered, volume); | ||||
| 				out.assign(z.size(), ExPolygons()); | ||||
| 				i = 0; | ||||
| 				for (const std::pair<size_t, size_t> &span : n_filtered) | ||||
| 					for (size_t j = span.first; j < span.second; ++ j) | ||||
| 						out[j] = std::move(layers[i ++]); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return out; | ||||
| } | ||||
| 
 | ||||
| std::string PrintObject::_fix_slicing_errors() | ||||
| { | ||||
|     // Collect layers with slicing errors.
 | ||||
|  | @ -2119,7 +2270,7 @@ void PrintObject::clip_fill_surfaces() | |||
|             //Should the pw not be half of the current value?
 | ||||
|             float pw = FLT_MAX; | ||||
|             for (const LayerRegion *layerm : layer->m_regions) | ||||
|                 pw = std::min<float>(pw, layerm->flow(frPerimeter).scaled_width()); | ||||
|                 pw = std::min(pw, (float)layerm->flow(frPerimeter).scaled_width()); | ||||
|             // Append such thick perimeters to the areas that need support
 | ||||
|             polygons_append(overhangs, offset2(perimeters, -pw, +pw)); | ||||
|         } | ||||
|  | @ -2154,7 +2305,7 @@ void PrintObject::discover_horizontal_shells() | |||
|     BOOST_LOG_TRIVIAL(trace) << "discover_horizontal_shells()"; | ||||
|      | ||||
|     for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { | ||||
|         for (int i = 0; i < int(m_layers.size()); ++ i) { | ||||
|         for (size_t i = 0; i < m_layers.size(); ++ i) { | ||||
|             m_print->throw_if_canceled(); | ||||
|             LayerRegion             *layerm = m_layers[i]->regions()[region_id]; | ||||
|             const PrintRegionConfig ®ion_config = layerm->region()->config(); | ||||
|  | @ -2171,7 +2322,7 @@ void PrintObject::discover_horizontal_shells() | |||
|             if (region_config.ensure_vertical_shell_thickness.value) | ||||
|                 continue; | ||||
|              | ||||
|             for (int idx_surface_type = 0; idx_surface_type < 3; ++ idx_surface_type) { | ||||
|             for (size_t idx_surface_type = 0; idx_surface_type < 3; ++ idx_surface_type) { | ||||
|                 m_print->throw_if_canceled(); | ||||
|                 SurfaceType type = (idx_surface_type == 0) ? stTop : (idx_surface_type == 1) ? stBottom : stBottomBridge; | ||||
|                 // Find slices of current type for current layer.
 | ||||
|  | @ -2200,7 +2351,7 @@ void PrintObject::discover_horizontal_shells() | |||
| //                Slic3r::debugf "Layer %d has %s surfaces\n", $i, ($type == S_TYPE_TOP) ? 'top' : 'bottom';
 | ||||
|                  | ||||
|                 size_t solid_layers = (type == stTop) ? region_config.top_solid_layers.value : region_config.bottom_solid_layers.value;                 | ||||
|                 for (int n = (type == stTop) ? i-1 : i+1; std::abs(n - i) < solid_layers; (type == stTop) ? -- n : ++ n) { | ||||
|                 for (int n = (type == stTop) ? i-1 : i+1; std::abs(n - (int)i) < solid_layers; (type == stTop) ? -- n : ++ n) { | ||||
|                     if (n < 0 || n >= int(m_layers.size())) | ||||
|                         continue; | ||||
| //                    Slic3r::debugf "  looking for neighbors on layer %d...\n", $n;                  
 | ||||
|  | @ -2345,7 +2496,7 @@ void PrintObject::combine_infill() | |||
|     // Work on each region separately.
 | ||||
|     for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { | ||||
|         const PrintRegion *region = this->print()->regions()[region_id]; | ||||
|         const int every = region->config().infill_every_layers.value; | ||||
|         const size_t every = region->config().infill_every_layers.value; | ||||
|         if (every < 2 || region->config().fill_density == 0.) | ||||
|             continue; | ||||
|         // Limit the number of combined layers to the maximum height allowed by this regions' nozzle.
 | ||||
|  |  | |||
|  | @ -59,8 +59,6 @@ SLAAutoSupports::SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3 | |||
| void SLAAutoSupports::project_onto_mesh(std::vector<sla::SupportPoint>& points) const | ||||
| { | ||||
|     // The function  makes sure that all the points are really exactly placed on the mesh.
 | ||||
|     igl::Hit hit_up{0, 0, 0.f, 0.f, 0.f}; | ||||
|     igl::Hit hit_down{0, 0, 0.f, 0.f, 0.f}; | ||||
| 
 | ||||
|     // Use a reasonable granularity to account for the worker thread synchronization cost.
 | ||||
|     tbb::parallel_for(tbb::blocked_range<size_t>(0, points.size(), 64), | ||||
|  | @ -140,7 +138,6 @@ static std::vector<SLAAutoSupports::MyLayer> make_layers( | |||
| 			SLAAutoSupports::MyLayer &layer_above = layers[layer_id]; | ||||
| 			SLAAutoSupports::MyLayer &layer_below = layers[layer_id - 1]; | ||||
|             //FIXME WTF?
 | ||||
|             const float height = (layer_id>2 ? heights[layer_id-3] : heights[0]-(heights[1]-heights[0])); | ||||
|             const float layer_height = (layer_id!=0 ? heights[layer_id]-heights[layer_id-1] : heights[0]); | ||||
|             const float safe_angle = 5.f * (float(M_PI)/180.f); // smaller number - less supports
 | ||||
|             const float between_layers_offset =  float(scale_(layer_height / std::tan(safe_angle))); | ||||
|  | @ -212,7 +209,7 @@ void SLAAutoSupports::process(const std::vector<ExPolygons>& slices, const std:: | |||
|         for (Structure &top : layer_top->islands) | ||||
| 			for (Structure::Link &bottom_link : top.islands_below) { | ||||
|                 Structure &bottom = *bottom_link.island; | ||||
|                 float centroids_dist = (bottom.centroid - top.centroid).norm(); | ||||
|                 //float centroids_dist = (bottom.centroid - top.centroid).norm();
 | ||||
|                 // Penalization resulting from centroid offset:
 | ||||
| //                  bottom.supports_force *= std::min(1.f, 1.f - std::min(1.f, (1600.f * layer_height) * centroids_dist * centroids_dist / bottom.area));
 | ||||
|                 float &support_force = support_force_bottom[&bottom - layer_bottom->islands.data()]; | ||||
|  | @ -239,7 +236,7 @@ void SLAAutoSupports::process(const std::vector<ExPolygons>& slices, const std:: | |||
| //            s.supports_force_inherited /= std::max(1.f, (layer_height / 0.3f) * e_area / s.area);
 | ||||
|             s.supports_force_inherited /= std::max(1.f, 0.17f * (s.overhangs_area) / s.area); | ||||
| 
 | ||||
|             float force_deficit = s.support_force_deficit(m_config.tear_pressure()); | ||||
|             //float force_deficit = s.support_force_deficit(m_config.tear_pressure());
 | ||||
|             if (s.islands_below.empty()) { // completely new island - needs support no doubt
 | ||||
|                 uniformly_cover({ *s.polygon }, s, point_grid, true); | ||||
|             } else if (! s.dangling_areas.empty()) { | ||||
|  | @ -380,7 +377,7 @@ static inline std::vector<Vec2f> poisson_disk_from_samples(const std::vector<Vec | |||
|     { | ||||
|         typename Cells::iterator last_cell_id_it; | ||||
|         Vec2i           last_cell_id(-1, -1); | ||||
|         for (int i = 0; i < raw_samples_sorted.size(); ++ i) { | ||||
|         for (size_t i = 0; i < raw_samples_sorted.size(); ++ i) { | ||||
|             const RawSample &sample = raw_samples_sorted[i]; | ||||
|             if (sample.cell_id == last_cell_id) { | ||||
|                 // This sample is in the same cell as the previous, so just increase the count.  Cells are
 | ||||
|  | @ -389,7 +386,7 @@ static inline std::vector<Vec2f> poisson_disk_from_samples(const std::vector<Vec | |||
|             } else { | ||||
|                 // This is a new cell.
 | ||||
|                 PoissonDiskGridEntry data; | ||||
|                 data.first_sample_idx = i; | ||||
|                 data.first_sample_idx = int(i); | ||||
|                 data.sample_cnt       = 1; | ||||
|                 auto result     = cells.insert({sample.cell_id, data}); | ||||
|                 last_cell_id    = sample.cell_id; | ||||
|  |  | |||
|  | @ -8,9 +8,9 @@ | |||
| #include "MTUtils.hpp" | ||||
| 
 | ||||
| // For debugging:
 | ||||
| //#include <fstream>
 | ||||
| //#include <libnest2d/tools/benchmark.h>
 | ||||
| //#include "SVG.hpp"
 | ||||
| // #include <fstream>
 | ||||
| // #include <libnest2d/tools/benchmark.h>
 | ||||
| // #include "SVG.hpp"
 | ||||
| 
 | ||||
| namespace Slic3r { namespace sla { | ||||
| 
 | ||||
|  | @ -184,9 +184,10 @@ Contour3D walls(const Polygon& lower, const Polygon& upper, | |||
| } | ||||
| 
 | ||||
| /// Offsetting with clipper and smoothing the edges into a curvature.
 | ||||
| void offset(ExPolygon& sh, coord_t distance) { | ||||
| void offset(ExPolygon& sh, coord_t distance, bool edgerounding = true) { | ||||
|     using ClipperLib::ClipperOffset; | ||||
|     using ClipperLib::jtRound; | ||||
|     using ClipperLib::jtMiter; | ||||
|     using ClipperLib::etClosedPolygon; | ||||
|     using ClipperLib::Paths; | ||||
|     using ClipperLib::Path; | ||||
|  | @ -203,11 +204,13 @@ void offset(ExPolygon& sh, coord_t distance) { | |||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     auto jointype = edgerounding? jtRound : jtMiter; | ||||
|      | ||||
|     ClipperOffset offs; | ||||
|     offs.ArcTolerance = scaled<double>(0.01); | ||||
|     Paths result; | ||||
|     offs.AddPath(ctour, jtRound, etClosedPolygon); | ||||
|     offs.AddPaths(holes, jtRound, etClosedPolygon); | ||||
|     offs.AddPath(ctour, jointype, etClosedPolygon); | ||||
|     offs.AddPaths(holes, jointype, etClosedPolygon); | ||||
|     offs.Execute(result, static_cast<double>(distance)); | ||||
| 
 | ||||
|     // Offsetting reverts the orientation and also removes the last vertex
 | ||||
|  | @ -237,6 +240,50 @@ void offset(ExPolygon& sh, coord_t distance) { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| void offset(Polygon &sh, coord_t distance, bool edgerounding = true) | ||||
| { | ||||
|     using ClipperLib::ClipperOffset; | ||||
|     using ClipperLib::jtRound; | ||||
|     using ClipperLib::jtMiter; | ||||
|     using ClipperLib::etClosedPolygon; | ||||
|     using ClipperLib::Paths; | ||||
|     using ClipperLib::Path; | ||||
| 
 | ||||
|     auto &&ctour = Slic3rMultiPoint_to_ClipperPath(sh); | ||||
| 
 | ||||
|     // If the input is not at least a triangle, we can not do this algorithm
 | ||||
|     if (ctour.size() < 3) { | ||||
|         BOOST_LOG_TRIVIAL(error) << "Invalid geometry for offsetting!"; | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     ClipperOffset offs; | ||||
|     offs.ArcTolerance = 0.01 * scaled(1.); | ||||
|     Paths result; | ||||
|     offs.AddPath(ctour, edgerounding ? jtRound : jtMiter, etClosedPolygon); | ||||
|     offs.Execute(result, static_cast<double>(distance)); | ||||
| 
 | ||||
|     // Offsetting reverts the orientation and also removes the last vertex
 | ||||
|     // so boost will not have a closed polygon.
 | ||||
| 
 | ||||
|     bool found_the_contour = false; | ||||
|     for (auto &r : result) { | ||||
|         if (ClipperLib::Orientation(r)) { | ||||
|             // We don't like if the offsetting generates more than one contour
 | ||||
|             // but throwing would be an overkill. Instead, we should warn the
 | ||||
|             // caller about the inability to create correct geometries
 | ||||
|             if (!found_the_contour) { | ||||
|                 auto rr = ClipperPath_to_Slic3rPolygon(r); | ||||
|                 sh.points.swap(rr.points); | ||||
|                 found_the_contour = true; | ||||
|             } else { | ||||
|                 BOOST_LOG_TRIVIAL(warning) | ||||
|                     << "Warning: offsetting result is invalid!"; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Unification of polygons (with clipper) preserving holes as well.
 | ||||
| ExPolygons unify(const ExPolygons& shapes) { | ||||
|     using ClipperLib::ptSubject; | ||||
|  | @ -307,6 +354,116 @@ ExPolygons unify(const ExPolygons& shapes) { | |||
|     return retv; | ||||
| } | ||||
| 
 | ||||
| Polygons unify(const Polygons& shapes) { | ||||
|     using ClipperLib::ptSubject; | ||||
|      | ||||
|     bool closed = true; | ||||
|     bool valid = true; | ||||
| 
 | ||||
|     ClipperLib::Clipper clipper; | ||||
| 
 | ||||
|     for(auto& path : shapes) { | ||||
|         auto clipperpath = Slic3rMultiPoint_to_ClipperPath(path); | ||||
| 
 | ||||
|         if(!clipperpath.empty()) | ||||
|             valid &= clipper.AddPath(clipperpath, ptSubject, closed); | ||||
|     } | ||||
| 
 | ||||
|     if(!valid) BOOST_LOG_TRIVIAL(warning) << "Unification of invalid shapes!"; | ||||
| 
 | ||||
|     ClipperLib::Paths result; | ||||
|     clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNonZero); | ||||
| 
 | ||||
|     Polygons ret; | ||||
|     for (ClipperLib::Path &p : result) { | ||||
|         Polygon pp = ClipperPath_to_Slic3rPolygon(p); | ||||
|         if (!pp.is_clockwise()) ret.emplace_back(std::move(pp)); | ||||
|     } | ||||
|      | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| // Function to cut tiny connector cavities for a given polygon. The input poly
 | ||||
| // will be offsetted by "padding" and small rectangle shaped cavities will be
 | ||||
| // inserted along the perimeter in every "stride" distance. The stick rectangles
 | ||||
| // will have a with about "stick_width". The input dimensions are in world 
 | ||||
| // measure, not the scaled clipper units.
 | ||||
| void breakstick_holes(ExPolygon& poly, | ||||
|                       double padding, | ||||
|                       double stride, | ||||
|                       double stick_width, | ||||
|                       double penetration) | ||||
| {    | ||||
|     // SVG svg("bridgestick_plate.svg");
 | ||||
|     // svg.draw(poly);
 | ||||
| 
 | ||||
|     auto transf = [stick_width, penetration, padding, stride](Points &pts) { | ||||
|         // The connector stick will be a small rectangle with dimensions
 | ||||
|         // stick_width x (penetration + padding) to have some penetration
 | ||||
|         // into the input polygon.
 | ||||
| 
 | ||||
|         Points out; | ||||
|         out.reserve(2 * pts.size()); // output polygon points
 | ||||
| 
 | ||||
|         // stick bottom and right edge dimensions
 | ||||
|         double sbottom = scaled(stick_width); | ||||
|         double sright  = scaled(penetration + padding); | ||||
| 
 | ||||
|         // scaled stride distance
 | ||||
|         double sstride = scaled(stride); | ||||
|         double t       = 0; | ||||
| 
 | ||||
|         // process pairs of vertices as an edge, start with the last and
 | ||||
|         // first point
 | ||||
|         for (size_t i = pts.size() - 1, j = 0; j < pts.size(); i = j, ++j) { | ||||
|             // Get vertices and the direction vectors
 | ||||
|             const Point &a = pts[i], &b = pts[j]; | ||||
|             Vec2d        dir = b.cast<double>() - a.cast<double>(); | ||||
|             double       nrm = dir.norm(); | ||||
|             dir /= nrm; | ||||
|             Vec2d dirp(-dir(Y), dir(X)); | ||||
| 
 | ||||
|             // Insert start point
 | ||||
|             out.emplace_back(a); | ||||
| 
 | ||||
|             // dodge the start point, do not make sticks on the joins
 | ||||
|             while (t < sbottom) t += sbottom; | ||||
|             double tend = nrm - sbottom; | ||||
| 
 | ||||
|             while (t < tend) { // insert the stick on the polygon perimeter
 | ||||
| 
 | ||||
|                 // calculate the stick rectangle vertices and insert them
 | ||||
|                 // into the output.
 | ||||
|                 Point p1 = a + (t * dir).cast<coord_t>(); | ||||
|                 Point p2 = p1 + (sright * dirp).cast<coord_t>(); | ||||
|                 Point p3 = p2 + (sbottom * dir).cast<coord_t>(); | ||||
|                 Point p4 = p3 + (sright * -dirp).cast<coord_t>(); | ||||
|                 out.insert(out.end(), {p1, p2, p3, p4}); | ||||
| 
 | ||||
|                 // continue along the perimeter
 | ||||
|                 t += sstride; | ||||
|             } | ||||
| 
 | ||||
|             t = t - nrm; | ||||
| 
 | ||||
|             // Insert edge endpoint
 | ||||
|             out.emplace_back(b); | ||||
|         } | ||||
|          | ||||
|         // move the new points
 | ||||
|         out.shrink_to_fit(); | ||||
|         pts.swap(out); | ||||
|     }; | ||||
|      | ||||
|     if(stride > 0.0 && stick_width > 0.0 && padding > 0.0) { | ||||
|         transf(poly.contour.points); | ||||
|         for (auto &h : poly.holes) transf(h.points); | ||||
|     } | ||||
|      | ||||
|     // svg.draw(poly);
 | ||||
|     // svg.Close();
 | ||||
| } | ||||
| 
 | ||||
| /// This method will create a rounded edge around a flat polygon in 3d space.
 | ||||
| /// 'base_plate' parameter is the target plate.
 | ||||
| /// 'radius' is the radius of the edges.
 | ||||
|  | @ -426,41 +583,38 @@ inline Point centroid(Points& pp) { | |||
|     return c; | ||||
| } | ||||
| 
 | ||||
| inline Point centroid(const ExPolygon& poly) { | ||||
|     return poly.contour.centroid(); | ||||
| inline Point centroid(const Polygon& poly) { | ||||
|     return poly.centroid(); | ||||
| } | ||||
| 
 | ||||
| /// A fake concave hull that is constructed by connecting separate shapes
 | ||||
| /// with explicit bridges. Bridges are generated from each shape's centroid
 | ||||
| /// to the center of the "scene" which is the centroid calculated from the shape
 | ||||
| /// centroids (a star is created...)
 | ||||
| ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50, | ||||
|                         ThrowOnCancel throw_on_cancel = [](){}) | ||||
| Polygons concave_hull(const Polygons& polys, double max_dist_mm = 50, | ||||
|                       ThrowOnCancel throw_on_cancel = [](){}) | ||||
| { | ||||
|     namespace bgi = boost::geometry::index; | ||||
|     using SpatElement = std::pair<BoundingBox, unsigned>; | ||||
|     using SpatElement = std::pair<Point, unsigned>; | ||||
|     using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; | ||||
| 
 | ||||
|     if(polys.empty()) return ExPolygons(); | ||||
|     if(polys.empty()) return Polygons(); | ||||
|      | ||||
|     const double max_dist = scaled(max_dist_mm); | ||||
| 
 | ||||
|     ExPolygons punion = unify(polys);   // could be redundant
 | ||||
|     Polygons punion = unify(polys);   // could be redundant
 | ||||
| 
 | ||||
|     if(punion.size() == 1) return punion; | ||||
| 
 | ||||
|     // We get the centroids of all the islands in the 2D slice
 | ||||
|     Points centroids; centroids.reserve(punion.size()); | ||||
|     std::transform(punion.begin(), punion.end(), std::back_inserter(centroids), | ||||
|                    [](const ExPolygon& poly) { return centroid(poly); }); | ||||
| 
 | ||||
| 
 | ||||
|     SpatIndex boxindex; unsigned idx = 0; | ||||
|     std::for_each(punion.begin(), punion.end(), | ||||
|                   [&boxindex, &idx](const ExPolygon& expo) { | ||||
|         BoundingBox bb(expo); | ||||
|         boxindex.insert(std::make_pair(bb, idx++)); | ||||
|     }); | ||||
| 
 | ||||
|                    [](const Polygon& poly) { return centroid(poly); }); | ||||
| 
 | ||||
|     SpatIndex ctrindex; | ||||
|     unsigned  idx = 0; | ||||
|     for(const Point &ct : centroids) ctrindex.insert(std::make_pair(ct, idx++)); | ||||
|      | ||||
|     // Centroid of the centroids of islands. This is where the additional
 | ||||
|     // connector sticks are routed.
 | ||||
|     Point cc = centroid(centroids); | ||||
|  | @ -470,25 +624,32 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50, | |||
|     idx = 0; | ||||
|     std::transform(centroids.begin(), centroids.end(), | ||||
|                    std::back_inserter(punion), | ||||
|                    [&punion, &boxindex, cc, max_dist_mm, &idx, throw_on_cancel] | ||||
|                    [¢roids, &ctrindex, cc, max_dist, &idx, throw_on_cancel] | ||||
|                    (const Point& c) | ||||
|     { | ||||
|         throw_on_cancel(); | ||||
|         double dx = x(c) - x(cc), dy = y(c) - y(cc); | ||||
|         double l = std::sqrt(dx * dx + dy * dy); | ||||
|         double nx = dx / l, ny = dy / l; | ||||
|         double max_dist = scaled<double>(max_dist_mm); | ||||
| 
 | ||||
|         ExPolygon& expo = punion[idx++]; | ||||
|         BoundingBox querybb(expo); | ||||
| 
 | ||||
|         querybb.offset(max_dist); | ||||
|          | ||||
|         Point& ct = centroids[idx]; | ||||
|          | ||||
|         std::vector<SpatElement> result; | ||||
|         boxindex.query(bgi::intersects(querybb), std::back_inserter(result)); | ||||
|         if(result.size() <= 1) return ExPolygon(); | ||||
|         ctrindex.query(bgi::nearest(ct, 2), std::back_inserter(result)); | ||||
| 
 | ||||
|         ExPolygon r; | ||||
|         auto& ctour = r.contour.points; | ||||
|         double dist = max_dist; | ||||
|         for (const SpatElement &el : result) | ||||
|             if (el.second != idx) { | ||||
|                 dist = Line(el.first, ct).length(); | ||||
|                 break; | ||||
|             } | ||||
|          | ||||
|         idx++; | ||||
|          | ||||
|         if (dist >= max_dist) return Polygon(); | ||||
|          | ||||
|         Polygon r; | ||||
|         auto& ctour = r.points; | ||||
| 
 | ||||
|         ctour.reserve(3); | ||||
|         ctour.emplace_back(cc); | ||||
|  | @ -507,24 +668,20 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50, | |||
|     return punion; | ||||
| } | ||||
| 
 | ||||
| void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h, | ||||
|                 float layerh, ThrowOnCancel thrfn) | ||||
| void base_plate(const TriangleMesh &      mesh, | ||||
|                 ExPolygons &              output, | ||||
|                 const std::vector<float> &heights, | ||||
|                 ThrowOnCancel             thrfn) | ||||
| { | ||||
|     TriangleMesh m = mesh; | ||||
|     m.require_shared_vertices(); // TriangleMeshSlicer needs this
 | ||||
|     TriangleMeshSlicer slicer(&m); | ||||
| 
 | ||||
|     auto bb = mesh.bounding_box(); | ||||
|     float gnd = float(bb.min(Z)); | ||||
|     std::vector<float> heights = {float(bb.min(Z))}; | ||||
|     for(float hi = gnd + layerh; hi <= gnd + h; hi += layerh) | ||||
|         heights.emplace_back(hi); | ||||
| 
 | ||||
|     std::vector<ExPolygons> out; out.reserve(size_t(std::ceil(h/layerh))); | ||||
|     if (mesh.empty()) return; | ||||
|     //    m.require_shared_vertices(); // TriangleMeshSlicer needs this    
 | ||||
|     TriangleMeshSlicer slicer(&mesh); | ||||
|      | ||||
|     std::vector<ExPolygons> out; out.reserve(heights.size()); | ||||
|     slicer.slice(heights, 0.f, &out, thrfn); | ||||
| 
 | ||||
|      | ||||
|     size_t count = 0; for(auto& o : out) count += o.size(); | ||||
| 
 | ||||
|      | ||||
|     // Now we have to unify all slice layers which can be an expensive operation
 | ||||
|     // so we will try to simplify the polygons
 | ||||
|     ExPolygons tmp; tmp.reserve(count); | ||||
|  | @ -533,16 +690,33 @@ void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h, | |||
|             auto&& exss = e.simplify(scaled<double>(0.1)); | ||||
|             for(ExPolygon& ep : exss) tmp.emplace_back(std::move(ep)); | ||||
|         } | ||||
| 
 | ||||
|      | ||||
|     ExPolygons utmp = unify(tmp); | ||||
| 
 | ||||
|      | ||||
|     for(auto& o : utmp) { | ||||
|         auto&& smp = o.simplify(scaled<double>(0.1)); | ||||
|         output.insert(output.end(), smp.begin(), smp.end()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| Contour3D create_base_pool(const ExPolygons &ground_layer,  | ||||
| void base_plate(const TriangleMesh &mesh, | ||||
|                 ExPolygons &        output, | ||||
|                 float               h, | ||||
|                 float               layerh, | ||||
|                 ThrowOnCancel       thrfn) | ||||
| { | ||||
|     auto bb = mesh.bounding_box(); | ||||
|     float gnd = float(bb.min(Z)); | ||||
|     std::vector<float> heights = {float(bb.min(Z))}; | ||||
|      | ||||
|     for(float hi = gnd + layerh; hi <= gnd + h; hi += layerh) | ||||
|         heights.emplace_back(hi); | ||||
|      | ||||
|     base_plate(mesh, output, heights, thrfn); | ||||
| } | ||||
| 
 | ||||
| Contour3D create_base_pool(const Polygons &ground_layer,  | ||||
|                            const ExPolygons &obj_self_pad = {}, | ||||
|                            const PoolConfig& cfg = PoolConfig())  | ||||
| { | ||||
|     // for debugging:
 | ||||
|  | @ -557,7 +731,7 @@ Contour3D create_base_pool(const ExPolygons &ground_layer, | |||
|     // serve as the bottom plate of the pad. We will offset this concave hull
 | ||||
|     // and then offset back the result with clipper with rounding edges ON. This
 | ||||
|     // trick will create a nice rounded pad shape.
 | ||||
|     ExPolygons concavehs = concave_hull(ground_layer, mergedist, cfg.throw_on_cancel); | ||||
|     Polygons concavehs = concave_hull(ground_layer, mergedist, cfg.throw_on_cancel); | ||||
| 
 | ||||
|     const double thickness      = cfg.min_wall_thickness_mm; | ||||
|     const double wingheight     = cfg.min_wall_height_mm; | ||||
|  | @ -577,42 +751,37 @@ Contour3D create_base_pool(const ExPolygons &ground_layer, | |||
| 
 | ||||
|     Contour3D pool; | ||||
| 
 | ||||
|     for(ExPolygon& concaveh : concavehs) { | ||||
|         if(concaveh.contour.points.empty()) return pool; | ||||
| 
 | ||||
|         // Get rid of any holes in the concave hull output.
 | ||||
|         concaveh.holes.clear(); | ||||
|     for(Polygon& concaveh : concavehs) { | ||||
|         if(concaveh.points.empty()) return pool; | ||||
| 
 | ||||
|         // Here lies the trick that does the smoothing only with clipper offset
 | ||||
|         // calls. The offset is configured to round edges. Inner edges will
 | ||||
|         // be rounded because we offset twice: ones to get the outer (top) plate
 | ||||
|         // and again to get the inner (bottom) plate
 | ||||
|         auto outer_base = concaveh; | ||||
|         outer_base.holes.clear(); | ||||
|         offset(outer_base, s_safety_dist + s_wingdist + s_thickness); | ||||
| 
 | ||||
|         ExPolygon bottom_poly = outer_base; | ||||
|         bottom_poly.holes.clear(); | ||||
|         ExPolygon bottom_poly; bottom_poly.contour = outer_base; | ||||
|         offset(bottom_poly, -s_bottom_offs); | ||||
| 
 | ||||
|         // Punching a hole in the top plate for the cavity
 | ||||
|         ExPolygon top_poly; | ||||
|         ExPolygon middle_base; | ||||
|         ExPolygon inner_base; | ||||
|         top_poly.contour = outer_base.contour; | ||||
|         top_poly.contour = outer_base; | ||||
| 
 | ||||
|         if(wingheight > 0) { | ||||
|             inner_base = outer_base; | ||||
|             inner_base.contour = outer_base; | ||||
|             offset(inner_base, -(s_thickness + s_wingdist + s_eradius)); | ||||
| 
 | ||||
|             middle_base = outer_base; | ||||
|             middle_base.contour = outer_base; | ||||
|             offset(middle_base, -s_thickness); | ||||
|             top_poly.holes.emplace_back(middle_base.contour); | ||||
|             auto& tph = top_poly.holes.back().points; | ||||
|             std::reverse(tph.begin(), tph.end()); | ||||
|         } | ||||
| 
 | ||||
|         ExPolygon ob = outer_base; double wh = 0; | ||||
|         ExPolygon ob; ob.contour = outer_base; double wh = 0; | ||||
| 
 | ||||
|         // now we will calculate the angle or portion of the circle from
 | ||||
|         // pi/2 that will connect perfectly with the bottom plate.
 | ||||
|  | @ -659,6 +828,7 @@ Contour3D create_base_pool(const ExPolygons &ground_layer, | |||
|         if(wingheight > 0) { | ||||
|             // Generate the smoothed edge geometry
 | ||||
|             wh = 0; | ||||
|             ob = middle_base; | ||||
|             if(s_eradius) pool.merge(round_edges(middle_base, | ||||
|                                    r, | ||||
|                                    phi - 90, // from tangent lines
 | ||||
|  | @ -673,11 +843,59 @@ Contour3D create_base_pool(const ExPolygons &ground_layer, | |||
|                              wh, -wingdist, thrcl)); | ||||
|         } | ||||
| 
 | ||||
|         // Now we need to triangulate the top and bottom plates as well as the
 | ||||
|         // cavity bottom plate which is the same as the bottom plate but it is
 | ||||
|         // elevated by the thickness.
 | ||||
|         if (cfg.embed_object) { | ||||
|             ExPolygons bttms = diff_ex(to_polygons(bottom_poly), | ||||
|                                        to_polygons(obj_self_pad)); | ||||
|              | ||||
|             assert(!bttms.empty()); | ||||
|              | ||||
|             std::sort(bttms.begin(), bttms.end(), | ||||
|                       [](const ExPolygon& e1, const ExPolygon& e2) { | ||||
|                           return e1.contour.area() > e2.contour.area(); | ||||
|                       }); | ||||
|              | ||||
|             if(wingheight > 0) inner_base.holes = bttms.front().holes; | ||||
|             else top_poly.holes = bttms.front().holes; | ||||
| 
 | ||||
|             auto straight_walls = | ||||
|                 [&pool](const Polygon &cntr, coord_t z_low, coord_t z_high) { | ||||
|                      | ||||
|                 auto lines = cntr.lines(); | ||||
|                  | ||||
|                 for (auto &l : lines) { | ||||
|                     auto s = coord_t(pool.points.size()); | ||||
|                     auto& pts = pool.points; | ||||
|                     pts.emplace_back(unscale(l.a.x(), l.a.y(), z_low)); | ||||
|                     pts.emplace_back(unscale(l.b.x(), l.b.y(), z_low)); | ||||
|                     pts.emplace_back(unscale(l.a.x(), l.a.y(), z_high)); | ||||
|                     pts.emplace_back(unscale(l.b.x(), l.b.y(), z_high)); | ||||
|                      | ||||
|                     pool.indices.emplace_back(s, s + 1, s + 3); | ||||
|                     pool.indices.emplace_back(s, s + 3, s + 2); | ||||
|                 } | ||||
|             }; | ||||
|              | ||||
|             coord_t z_lo = -scaled(fullheight), z_hi = -scaled(wingheight); | ||||
|             for (ExPolygon &ep : bttms) { | ||||
|                 pool.merge(triangulate_expolygon_3d(ep, -fullheight, true)); | ||||
|                 for (auto &h : ep.holes) straight_walls(h, z_lo, z_hi); | ||||
|             } | ||||
|              | ||||
|             // Skip the outer contour, triangulate the holes
 | ||||
|             for (auto it = std::next(bttms.begin()); it != bttms.end(); ++it) { | ||||
|                 pool.merge(triangulate_expolygon_3d(*it, -wingheight)); | ||||
|                 straight_walls(it->contour, z_lo, z_hi); | ||||
|             } | ||||
|              | ||||
|         } else { | ||||
|             // Now we need to triangulate the top and bottom plates as well as
 | ||||
|             // the cavity bottom plate which is the same as the bottom plate
 | ||||
|             // but it is elevated by the thickness.
 | ||||
|              | ||||
|             pool.merge(triangulate_expolygon_3d(bottom_poly, -fullheight, true)); | ||||
|         } | ||||
|          | ||||
|         pool.merge(triangulate_expolygon_3d(top_poly)); | ||||
|         pool.merge(triangulate_expolygon_3d(bottom_poly, -fullheight, true)); | ||||
| 
 | ||||
|         if(wingheight > 0) | ||||
|             pool.merge(triangulate_expolygon_3d(inner_base, -wingheight)); | ||||
|  | @ -687,8 +905,8 @@ Contour3D create_base_pool(const ExPolygons &ground_layer, | |||
|     return pool; | ||||
| } | ||||
| 
 | ||||
| void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out, | ||||
|                       const PoolConfig& cfg) | ||||
| void create_base_pool(const Polygons &ground_layer, TriangleMesh& out, | ||||
|                       const ExPolygons &holes, const PoolConfig& cfg) | ||||
| { | ||||
|      | ||||
| 
 | ||||
|  | @ -698,7 +916,7 @@ void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out, | |||
|     // std::fstream fout("pad_debug.obj", std::fstream::out);
 | ||||
|     // if(fout.good()) pool.to_obj(fout);
 | ||||
| 
 | ||||
|     out.merge(mesh(create_base_pool(ground_layer, cfg))); | ||||
|     out.merge(mesh(create_base_pool(ground_layer, holes, cfg))); | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -8,7 +8,9 @@ | |||
| namespace Slic3r { | ||||
| 
 | ||||
| class ExPolygon; | ||||
| class Polygon; | ||||
| using ExPolygons = std::vector<ExPolygon>; | ||||
| using Polygons = std::vector<Polygon>; | ||||
| 
 | ||||
| class TriangleMesh; | ||||
| 
 | ||||
|  | @ -19,16 +21,40 @@ using ThrowOnCancel = std::function<void(void)>; | |||
| /// Calculate the polygon representing the silhouette from the specified height
 | ||||
| void base_plate(const TriangleMesh& mesh,       // input mesh
 | ||||
|                 ExPolygons& output,             // Output will be merged with
 | ||||
|                 float zlevel = 0.1f,            // Plate creation level
 | ||||
|                 float samplingheight = 0.1f,    // The height range to sample
 | ||||
|                 float layerheight = 0.05f,      // The sampling height
 | ||||
|                 ThrowOnCancel thrfn = [](){});  // Will be called frequently
 | ||||
| 
 | ||||
| void base_plate(const TriangleMesh& mesh,       // input mesh
 | ||||
|                 ExPolygons& output,             // Output will be merged with
 | ||||
|                 const std::vector<float>&,      // Exact Z levels to sample
 | ||||
|                 ThrowOnCancel thrfn = [](){});  // Will be called frequently
 | ||||
| 
 | ||||
| // Function to cut tiny connector cavities for a given polygon. The input poly
 | ||||
| // will be offsetted by "padding" and small rectangle shaped cavities will be
 | ||||
| // inserted along the perimeter in every "stride" distance. The stick rectangles
 | ||||
| // will have a with about "stick_width". The input dimensions are in world 
 | ||||
| // measure, not the scaled clipper units.
 | ||||
| void breakstick_holes(ExPolygon &poly, | ||||
|                       double     padding, | ||||
|                       double     stride, | ||||
|                       double     stick_width, | ||||
|                       double     penetration = 0.0); | ||||
| 
 | ||||
| struct PoolConfig { | ||||
|     double min_wall_thickness_mm = 2; | ||||
|     double min_wall_height_mm = 5; | ||||
|     double max_merge_distance_mm = 50; | ||||
|     double edge_radius_mm = 1; | ||||
|     double wall_slope = std::atan(1.0);          // Universal constant for Pi/4
 | ||||
|     struct EmbedObject { | ||||
|         double object_gap_mm = 0.5; | ||||
|         double stick_stride_mm = 10; | ||||
|         double stick_width_mm = 0.3; | ||||
|         double stick_penetration_mm = 0.1; | ||||
|         bool enabled = false; | ||||
|         operator bool() const { return enabled; } | ||||
|     } embed_object; | ||||
| 
 | ||||
|     ThrowOnCancel throw_on_cancel = [](){}; | ||||
| 
 | ||||
|  | @ -42,15 +68,12 @@ struct PoolConfig { | |||
| }; | ||||
| 
 | ||||
| /// Calculate the pool for the mesh for SLA printing
 | ||||
| void create_base_pool(const ExPolygons& base_plate, | ||||
| void create_base_pool(const Polygons& base_plate, | ||||
|                       TriangleMesh& output_mesh, | ||||
|                       const ExPolygons& holes, | ||||
|                       const PoolConfig& = PoolConfig()); | ||||
| 
 | ||||
| /// TODO: Currently the base plate of the pool will have half the height of the
 | ||||
| /// whole pool. So the carved out space has also half the height. This is not
 | ||||
| /// a particularly elegant solution, the thickness should be exactly
 | ||||
| /// min_wall_thickness and it should be corrected in the future. This method
 | ||||
| /// will return the correct value for further processing.
 | ||||
| /// Returns the elevation needed for compensating the pad.
 | ||||
| inline double get_pad_elevation(const PoolConfig& cfg) { | ||||
|     return cfg.min_wall_thickness_mm; | ||||
| } | ||||
|  |  | |||
|  | @ -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
 | ||||
|  | @ -60,7 +62,7 @@ class EigenMesh3D { | |||
| 
 | ||||
|     Eigen::MatrixXd m_V; | ||||
|     Eigen::MatrixXi m_F; | ||||
|     double m_ground_level = 0; | ||||
|     double m_ground_level = 0, m_gnd_offset = 0; | ||||
| 
 | ||||
|     std::unique_ptr<AABBImpl> m_aabb; | ||||
| public: | ||||
|  | @ -71,7 +73,9 @@ public: | |||
| 
 | ||||
|     ~EigenMesh3D(); | ||||
| 
 | ||||
|     inline double ground_level() const { return m_ground_level; } | ||||
|     inline double ground_level() const { return m_ground_level + m_gnd_offset; } | ||||
|     inline void ground_level_offset(double o) { m_gnd_offset = o; } | ||||
|     inline double ground_level_offset() const { return m_gnd_offset; } | ||||
| 
 | ||||
|     inline const Eigen::MatrixXd& V() const { return m_V; } | ||||
|     inline const Eigen::MatrixXi& F() const { return m_F; } | ||||
|  | @ -149,6 +153,12 @@ public: | |||
| #endif /* SLIC3R_SLA_NEEDS_WINDTREE */ | ||||
| 
 | ||||
|     double squared_distance(const Vec3d& p, int& i, Vec3d& c) const; | ||||
|     inline double squared_distance(const Vec3d &p) const | ||||
|     { | ||||
|         int   i; | ||||
|         Vec3d c; | ||||
|         return squared_distance(p, i, c); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,13 +7,15 @@ | |||
| 
 | ||||
| #include <Eigen/Geometry> | ||||
| 
 | ||||
| #include <libslic3r/BoundingBox.hpp> | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace sla { | ||||
| 
 | ||||
| typedef Eigen::Matrix<double,   3, 1, Eigen::DontAlign> Vec3d; | ||||
| using SpatElement = std::pair<Vec3d, unsigned>; | ||||
| using PointIndexEl = std::pair<Vec3d, unsigned>; | ||||
| 
 | ||||
| class SpatIndex { | ||||
| class PointIndex { | ||||
|     class Impl; | ||||
| 
 | ||||
|     // We use Pimpl because it takes a long time to compile boost headers which
 | ||||
|  | @ -21,30 +23,67 @@ class SpatIndex { | |||
|     std::unique_ptr<Impl> m_impl; | ||||
| public: | ||||
| 
 | ||||
|     SpatIndex(); | ||||
|     ~SpatIndex(); | ||||
|     PointIndex(); | ||||
|     ~PointIndex(); | ||||
| 
 | ||||
|     SpatIndex(const SpatIndex&); | ||||
|     SpatIndex(SpatIndex&&); | ||||
|     SpatIndex& operator=(const SpatIndex&); | ||||
|     SpatIndex& operator=(SpatIndex&&); | ||||
|     PointIndex(const PointIndex&); | ||||
|     PointIndex(PointIndex&&); | ||||
|     PointIndex& operator=(const PointIndex&); | ||||
|     PointIndex& operator=(PointIndex&&); | ||||
| 
 | ||||
|     void insert(const SpatElement&); | ||||
|     bool remove(const SpatElement&); | ||||
|     void insert(const PointIndexEl&); | ||||
|     bool remove(const PointIndexEl&); | ||||
| 
 | ||||
|     inline void insert(const Vec3d& v, unsigned idx) | ||||
|     { | ||||
|         insert(std::make_pair(v, unsigned(idx))); | ||||
|     } | ||||
| 
 | ||||
|     std::vector<SpatElement> query(std::function<bool(const SpatElement&)>); | ||||
|     std::vector<SpatElement> nearest(const Vec3d&, unsigned k); | ||||
|     std::vector<PointIndexEl> query(std::function<bool(const PointIndexEl&)>); | ||||
|     std::vector<PointIndexEl> nearest(const Vec3d&, unsigned k); | ||||
| 
 | ||||
|     // For testing
 | ||||
|     size_t size() const; | ||||
|     bool empty() const { return size() == 0; } | ||||
| 
 | ||||
|     void foreach(std::function<void(const SpatElement& el)> fn); | ||||
|     void foreach(std::function<void(const PointIndexEl& el)> fn); | ||||
| }; | ||||
| 
 | ||||
| using BoxIndexEl = std::pair<Slic3r::BoundingBox, unsigned>; | ||||
| 
 | ||||
| class BoxIndex { | ||||
|     class Impl; | ||||
|      | ||||
|     // We use Pimpl because it takes a long time to compile boost headers which
 | ||||
|     // is the engine of this class. We include it only in the cpp file.
 | ||||
|     std::unique_ptr<Impl> m_impl; | ||||
| public: | ||||
|      | ||||
|     BoxIndex(); | ||||
|     ~BoxIndex(); | ||||
|      | ||||
|     BoxIndex(const BoxIndex&); | ||||
|     BoxIndex(BoxIndex&&); | ||||
|     BoxIndex& operator=(const BoxIndex&); | ||||
|     BoxIndex& operator=(BoxIndex&&); | ||||
|      | ||||
|     void insert(const BoxIndexEl&); | ||||
|     inline void insert(const BoundingBox& bb, unsigned idx) | ||||
|     { | ||||
|         insert(std::make_pair(bb, unsigned(idx))); | ||||
|     } | ||||
|      | ||||
|     bool remove(const BoxIndexEl&); | ||||
| 
 | ||||
|     enum QueryType { qtIntersects, qtWithin }; | ||||
| 
 | ||||
|     std::vector<BoxIndexEl> query(const BoundingBox&, QueryType qt); | ||||
|      | ||||
|     // For testing
 | ||||
|     size_t size() const; | ||||
|     bool empty() const { return size() == 0; } | ||||
|      | ||||
|     void foreach(std::function<void(const BoxIndexEl& el)> fn); | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -9,10 +9,12 @@ | |||
| #include "SLASpatIndex.hpp" | ||||
| #include "SLABasePool.hpp" | ||||
| 
 | ||||
| #include <libslic3r/MTUtils.hpp> | ||||
| #include <libslic3r/ClipperUtils.hpp> | ||||
| #include <libslic3r/Model.hpp> | ||||
| 
 | ||||
| #include <libnest2d/optimizers/nlopt/genetic.hpp> | ||||
| #include <libnest2d/optimizers/nlopt/subplex.hpp> | ||||
| #include <boost/log/trivial.hpp> | ||||
| #include <tbb/parallel_for.h> | ||||
| #include <libslic3r/I18N.hpp> | ||||
|  | @ -413,7 +415,7 @@ struct Pillar { | |||
|         assert(steps > 0); | ||||
| 
 | ||||
|         height = jp(Z) - endp(Z); | ||||
|         if(height > 0) { // Endpoint is below the starting point
 | ||||
|         if(height > EPSILON) { // Endpoint is below the starting point
 | ||||
| 
 | ||||
|             // We just create a bridge geometry with the pillar parameters and
 | ||||
|             // move the data.
 | ||||
|  | @ -528,6 +530,7 @@ struct CompactBridge { | |||
|                   const Vec3d& ep, | ||||
|                   const Vec3d& n, | ||||
|                   double r, | ||||
|                   bool endball = true, | ||||
|                   size_t steps = 45) | ||||
|     { | ||||
|         Vec3d startp = sp + r * n; | ||||
|  | @ -541,12 +544,14 @@ struct CompactBridge { | |||
|         double fa = 2*PI/steps; | ||||
|         auto upperball = sphere(r, Portion{PI / 2 - fa, PI}, fa); | ||||
|         for(auto& p : upperball.points) p += startp; | ||||
| 
 | ||||
|         auto lowerball = sphere(r, Portion{0, PI/2 + 2*fa}, fa); | ||||
|         for(auto& p : lowerball.points) p += endp; | ||||
| 
 | ||||
|          | ||||
|         if(endball) { | ||||
|             auto lowerball = sphere(r, Portion{0, PI/2 + 2*fa}, fa); | ||||
|             for(auto& p : lowerball.points) p += endp; | ||||
|             mesh.merge(lowerball); | ||||
|         } | ||||
|          | ||||
|         mesh.merge(upperball); | ||||
|         mesh.merge(lowerball); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
|  | @ -556,28 +561,111 @@ struct Pad { | |||
|     PoolConfig cfg; | ||||
|     double zlevel = 0; | ||||
| 
 | ||||
|     Pad() {} | ||||
|     Pad() = default; | ||||
| 
 | ||||
|     Pad(const TriangleMesh& object_support_mesh, | ||||
|         const ExPolygons& baseplate, | ||||
|     Pad(const TriangleMesh& support_mesh, | ||||
|         const ExPolygons& modelbase, | ||||
|         double ground_level, | ||||
|         const PoolConfig& pcfg) : | ||||
|         cfg(pcfg), | ||||
|         zlevel(ground_level + | ||||
|                (sla::get_pad_fullheight(pcfg) - sla::get_pad_elevation(pcfg)) ) | ||||
|         zlevel(ground_level +  | ||||
|                sla::get_pad_fullheight(pcfg) - | ||||
|                sla::get_pad_elevation(pcfg)) | ||||
|     { | ||||
|         ExPolygons basep; | ||||
|         cfg.throw_on_cancel(); | ||||
|         Polygons basep; | ||||
|         auto &thr = cfg.throw_on_cancel; | ||||
|          | ||||
|         thr(); | ||||
|          | ||||
|         // Get a sample for the pad from the support mesh
 | ||||
|         { | ||||
|             ExPolygons platetmp; | ||||
| 
 | ||||
|         // The 0.1f is the layer height with which the mesh is sampled and then
 | ||||
|         // the layers are unified into one vector of polygons.
 | ||||
|         base_plate(object_support_mesh, basep, | ||||
|                    float(cfg.min_wall_height_mm + cfg.min_wall_thickness_mm), | ||||
|                    0.1f, pcfg.throw_on_cancel); | ||||
|             float zstart = float(zlevel); | ||||
|             float zend   = zstart + float(get_pad_fullheight(pcfg) + EPSILON); | ||||
| 
 | ||||
|         for(auto& bp : baseplate) basep.emplace_back(bp); | ||||
|             base_plate(support_mesh, platetmp, grid(zstart, zend, 0.1f), thr); | ||||
| 
 | ||||
|             // We don't need no... holes control...
 | ||||
|             for (const ExPolygon &bp : platetmp) | ||||
|                 basep.emplace_back(std::move(bp.contour)); | ||||
|         } | ||||
|          | ||||
|         if(pcfg.embed_object) { | ||||
|              | ||||
|             // If the zero elevation mode is ON, we need to process the model
 | ||||
|             // base silhouette. Create the offsetted version and punch the
 | ||||
|             // breaksticks across its perimeter.
 | ||||
|              | ||||
|             ExPolygons modelbase_offs = modelbase; | ||||
|              | ||||
|             if (pcfg.embed_object.object_gap_mm > 0.0) | ||||
|                 modelbase_offs | ||||
|                     = offset_ex(modelbase_offs, | ||||
|                                 float(scaled(pcfg.embed_object.object_gap_mm))); | ||||
|              | ||||
|             // Create a spatial index of the support silhouette polygons.
 | ||||
|             // This will be used to check for intersections with the model
 | ||||
|             // silhouette polygons. If there is no intersection, then a certain
 | ||||
|             // part of the pad is redundant as it does not host any supports.
 | ||||
|             BoxIndex bindex; | ||||
|             { | ||||
|                 unsigned idx = 0; | ||||
|                 for(auto &bp : basep) { | ||||
|                     auto bb = bp.bounding_box(); | ||||
|                     bb.offset(float(scaled(pcfg.min_wall_thickness_mm))); | ||||
|                     bindex.insert(bb, idx++); | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|             // Punching the breaksticks across the offsetted polygon perimeters
 | ||||
|             ExPolygons pad_stickholes; pad_stickholes.reserve(modelbase.size()); | ||||
|             for(auto& poly : modelbase_offs) { | ||||
|                  | ||||
|                 std::vector<BoxIndexEl> qres = | ||||
|                     bindex.query(poly.contour.bounding_box(), | ||||
|                                  BoxIndex::qtIntersects); | ||||
|                      | ||||
|                 if (!qres.empty()) { | ||||
|                      | ||||
|                     // The model silhouette polygon 'poly' HAS an intersection
 | ||||
|                     // with the support silhouettes. Include this polygon
 | ||||
|                     // in the pad holes with the breaksticks and merge the
 | ||||
|                     // original (offsetted) version with the rest of the pad
 | ||||
|                     // base plate.
 | ||||
|                      | ||||
|                     basep.emplace_back(poly.contour); | ||||
|                      | ||||
|                     // The holes of 'poly' will become positive parts of the
 | ||||
|                     // pad, so they has to be checked for intersections as well
 | ||||
|                     // and erased if there is no intersection with the supports
 | ||||
|                     auto it = poly.holes.begin(); | ||||
|                     while(it != poly.holes.end()) { | ||||
|                         if (bindex.query(it->bounding_box(), | ||||
|                                          BoxIndex::qtIntersects).empty()) | ||||
|                             it = poly.holes.erase(it); | ||||
|                         else | ||||
|                             ++it; | ||||
|                     } | ||||
|                      | ||||
|                     // Punch the breaksticks
 | ||||
|                     sla::breakstick_holes( | ||||
|                         poly, | ||||
|                         pcfg.embed_object.object_gap_mm,   // padding
 | ||||
|                         pcfg.embed_object.stick_stride_mm, | ||||
|                         pcfg.embed_object.stick_width_mm, | ||||
|                         pcfg.embed_object.stick_penetration_mm); | ||||
|                      | ||||
|                     pad_stickholes.emplace_back(poly); | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|             create_base_pool(basep, tmesh, pad_stickholes, cfg); | ||||
|         } else { | ||||
|             for (const ExPolygon &bp : modelbase) basep.emplace_back(bp.contour); | ||||
|             create_base_pool(basep, tmesh, {}, cfg); | ||||
|         } | ||||
| 
 | ||||
|         create_base_pool(basep, tmesh, cfg); | ||||
|         tmesh.translate(0, 0, float(zlevel)); | ||||
|     } | ||||
| 
 | ||||
|  | @ -603,7 +691,7 @@ inline Vec2d to_vec2(const Vec3d& v3) { | |||
|     return {v3(X), v3(Y)}; | ||||
| } | ||||
| 
 | ||||
| bool operator==(const SpatElement& e1, const SpatElement& e2) { | ||||
| bool operator==(const PointIndexEl& e1, const PointIndexEl& e2) { | ||||
|     return e1.second == e2.second; | ||||
| } | ||||
| 
 | ||||
|  | @ -620,7 +708,7 @@ ClusteredPoints cluster(const PointSet& points, | |||
| ClusteredPoints cluster( | ||||
|         const std::vector<unsigned>& indices, | ||||
|         std::function<Vec3d(unsigned)> pointfn, | ||||
|         std::function<bool(const SpatElement&, const SpatElement&)> predicate, | ||||
|         std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate, | ||||
|         unsigned max_points); | ||||
| 
 | ||||
| // This class will hold the support tree meshes with some additional bookkeeping
 | ||||
|  | @ -763,9 +851,9 @@ public: | |||
|     } | ||||
| 
 | ||||
|     const Pad& create_pad(const TriangleMesh& object_supports, | ||||
|                           const ExPolygons& baseplate, | ||||
|                           const ExPolygons& modelbase, | ||||
|                           const PoolConfig& cfg) { | ||||
|         m_pad = Pad(object_supports, baseplate, ground_level, cfg); | ||||
|         m_pad = Pad(object_supports, modelbase, ground_level, cfg); | ||||
|         return m_pad; | ||||
|     } | ||||
| 
 | ||||
|  | @ -808,7 +896,6 @@ public: | |||
|             merged.merge(bs.mesh); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         if(m_ctl.stopcondition()) { | ||||
|             // In case of failure we have to return an empty mesh
 | ||||
|             meshcache = TriangleMesh(); | ||||
|  | @ -819,7 +906,7 @@ public: | |||
| 
 | ||||
|         // The mesh will be passed by const-pointer to TriangleMeshSlicer,
 | ||||
|         // which will need this.
 | ||||
|         meshcache.require_shared_vertices(); | ||||
|         if (!meshcache.empty()) meshcache.require_shared_vertices(); | ||||
| 
 | ||||
|         // TODO: Is this necessary?
 | ||||
|         //meshcache.repair();
 | ||||
|  | @ -947,7 +1034,7 @@ class SLASupportTree::Algorithm { | |||
|     ThrowOnCancel m_thr; | ||||
| 
 | ||||
|     // A spatial index to easily find strong pillars to connect to.
 | ||||
|     SpatIndex m_pillar_index; | ||||
|     PointIndex m_pillar_index; | ||||
| 
 | ||||
|     inline double ray_mesh_intersect(const Vec3d& s, | ||||
|                                      const Vec3d& dir) | ||||
|  | @ -1149,7 +1236,7 @@ class SLASupportTree::Algorithm { | |||
|             auto hr = m.query_ray_hit(p + sd*dir, dir); | ||||
| 
 | ||||
|             if(ins_check && hr.is_inside()) { | ||||
|                 if(hr.distance() > r + sd) hits[i] = HitResult(0.0); | ||||
|                 if(hr.distance() > 2 * r + sd) hits[i] = HitResult(0.0); | ||||
|                 else { | ||||
|                     // re-cast the ray from the outside of the object
 | ||||
|                     auto hr2 = | ||||
|  | @ -1264,9 +1351,12 @@ class SLASupportTree::Algorithm { | |||
| 
 | ||||
|     // For connecting a head to a nearby pillar.
 | ||||
|     bool connect_to_nearpillar(const Head& head, long nearpillar_id) { | ||||
| 
 | ||||
|         auto nearpillar = [this, nearpillar_id]() { return m_result.pillar(nearpillar_id); }; | ||||
|         if(nearpillar().bridges > m_cfg.max_bridges_on_pillar) return false; | ||||
|          | ||||
|         auto nearpillar = [this, nearpillar_id]() { | ||||
|             return m_result.pillar(nearpillar_id); | ||||
|         }; | ||||
|          | ||||
|         if (nearpillar().bridges > m_cfg.max_bridges_on_pillar) return false; | ||||
| 
 | ||||
|         Vec3d headjp = head.junction_point(); | ||||
|         Vec3d nearjp_u = nearpillar().startpoint(); | ||||
|  | @ -1337,7 +1427,7 @@ class SLASupportTree::Algorithm { | |||
|     } | ||||
| 
 | ||||
|     bool search_pillar_and_connect(const Head& head) { | ||||
|         SpatIndex spindex = m_pillar_index; | ||||
|         PointIndex spindex = m_pillar_index; | ||||
| 
 | ||||
|         long nearest_id = -1; | ||||
| 
 | ||||
|  | @ -1369,6 +1459,120 @@ class SLASupportTree::Algorithm { | |||
| 
 | ||||
|         return nearest_id >= 0; | ||||
|     } | ||||
|      | ||||
|     // This is a proxy function for pillar creation which will mind the gap
 | ||||
|     // between the pad and the model bottom in zero elevation mode.
 | ||||
|     void create_ground_pillar(const Vec3d &jp, | ||||
|                               const Vec3d &sourcedir, | ||||
|                               double       radius, | ||||
|                               int          head_id = -1) | ||||
|     { | ||||
|         // People were killed for this number (seriously)
 | ||||
|         static const double SQR2 = std::sqrt(2.0); | ||||
|         static const Vec3d  DOWN = {0.0, 0.0, -1.0}; | ||||
| 
 | ||||
|         double gndlvl       = m_result.ground_level; | ||||
|         Vec3d  endp         = {jp(X), jp(Y), gndlvl}; | ||||
|         double sd           = m_cfg.pillar_base_safety_distance_mm; | ||||
|         int    pillar_id    = -1; | ||||
|         double min_dist     = sd + m_cfg.base_radius_mm + EPSILON; | ||||
|         double dist         = 0; | ||||
|         bool   can_add_base = true; | ||||
|         bool   normal_mode  = true; | ||||
| 
 | ||||
|         if (m_cfg.object_elevation_mm < EPSILON | ||||
|             && (dist = std::sqrt(m_mesh.squared_distance(endp))) < min_dist) { | ||||
|             // Get the distance from the mesh. This can be later optimized
 | ||||
|             // to get the distance in 2D plane because we are dealing with
 | ||||
|             // the ground level only.
 | ||||
| 
 | ||||
|             normal_mode     = false; | ||||
|             double mv       = min_dist - dist; | ||||
|             double azimuth  = std::atan2(sourcedir(Y), sourcedir(X)); | ||||
|             double sinpolar = std::sin(PI - m_cfg.bridge_slope); | ||||
|             double cospolar = std::cos(PI - m_cfg.bridge_slope); | ||||
|             double cosazm   = std::cos(azimuth); | ||||
|             double sinazm   = std::sin(azimuth); | ||||
| 
 | ||||
|             auto dir = Vec3d(cosazm * sinpolar, sinazm * sinpolar, cospolar) | ||||
|                            .normalized(); | ||||
| 
 | ||||
|             using namespace libnest2d::opt; | ||||
|             StopCriteria scr; | ||||
|             scr.stop_score = min_dist; | ||||
|             SubplexOptimizer solver(scr); | ||||
| 
 | ||||
|             auto result = solver.optimize_max( | ||||
|                 [this, dir, jp, gndlvl](double mv) { | ||||
|                     Vec3d endp = jp + SQR2 * mv * dir; | ||||
|                     endp(Z)    = gndlvl; | ||||
|                     return std::sqrt(m_mesh.squared_distance(endp)); | ||||
|                 }, | ||||
|                 initvals(mv), bound(0.0, 2 * min_dist)); | ||||
| 
 | ||||
|             mv           = std::get<0>(result.optimum); | ||||
|             endp         = jp + SQR2 * mv * dir; | ||||
|             Vec3d pgnd   = {endp(X), endp(Y), gndlvl}; | ||||
|             can_add_base = result.score > min_dist; | ||||
|              | ||||
|             double gnd_offs = m_mesh.ground_level_offset(); | ||||
|             auto abort_in_shame = | ||||
|                 [gnd_offs, &normal_mode, &can_add_base, &endp, jp, gndlvl]() | ||||
|             { | ||||
|                 normal_mode  = true; | ||||
|                 can_add_base = false;   // Nothing left to do, hope for the best
 | ||||
|                 endp         = {jp(X), jp(Y), gndlvl - gnd_offs }; | ||||
|             }; | ||||
| 
 | ||||
|             // We have to check if the bridge is feasible.
 | ||||
|             if (bridge_mesh_intersect(jp, dir, radius) < (endp - jp).norm()) | ||||
|                 abort_in_shame(); | ||||
|             else { | ||||
|                 // If the new endpoint is below ground, do not make a pillar
 | ||||
|                 if (endp(Z) < gndlvl) | ||||
|                     endp = endp - SQR2 * (gndlvl - endp(Z)) * dir; // back off
 | ||||
|                 else { | ||||
|                      | ||||
|                     auto hit = bridge_mesh_intersect(endp, DOWN, radius); | ||||
|                     if (!std::isinf(hit.distance())) abort_in_shame(); | ||||
| 
 | ||||
|                     Pillar &plr = m_result.add_pillar(endp, pgnd, radius); | ||||
| 
 | ||||
|                     if (can_add_base) | ||||
|                         plr.add_base(m_cfg.base_height_mm, | ||||
|                                      m_cfg.base_radius_mm); | ||||
| 
 | ||||
|                     pillar_id = plr.id; | ||||
|                 } | ||||
| 
 | ||||
|                 m_result.add_bridge(jp, endp, radius); | ||||
|                 m_result.add_junction(endp, radius); | ||||
| 
 | ||||
|                 // Add a degenerated pillar and the bridge.
 | ||||
|                 // The degenerate pillar will have zero length and it will
 | ||||
|                 // prevent from queries of head_pillar() to have non-existing
 | ||||
|                 // pillar when the head should have one.
 | ||||
|                 if (head_id >= 0) | ||||
|                     m_result.add_pillar(unsigned(head_id), jp, radius); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         if (normal_mode) { | ||||
|             Pillar &plr = head_id >= 0 | ||||
|                               ? m_result.add_pillar(unsigned(head_id), | ||||
|                                                     endp, | ||||
|                                                     radius) | ||||
|                               : m_result.add_pillar(jp, endp, radius); | ||||
| 
 | ||||
|             if (can_add_base) | ||||
|                 plr.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); | ||||
| 
 | ||||
|             pillar_id = plr.id; | ||||
|         }  | ||||
|              | ||||
|         if(pillar_id >= 0) // Save the pillar endpoint in the spatial index
 | ||||
|             m_pillar_index.insert(endp, pillar_id); | ||||
|     } | ||||
| 
 | ||||
| public: | ||||
| 
 | ||||
|  | @ -1447,9 +1651,9 @@ public: | |||
|             // (Quaternion::FromTwoVectors) and apply the rotation to the
 | ||||
|             // arrow head.
 | ||||
| 
 | ||||
|             double z = n(2); | ||||
|             double r = 1.0;     // for normalized vector
 | ||||
|             double polar = std::acos(z / r); | ||||
|             double z       = n(2); | ||||
|             double r       = 1.0; // for normalized vector
 | ||||
|             double polar   = std::acos(z / r); | ||||
|             double azimuth = std::atan2(n(1), n(0)); | ||||
| 
 | ||||
|             // skip if the tilt is not sane
 | ||||
|  | @ -1473,14 +1677,14 @@ public: | |||
|                                 std::cos(polar)).normalized(); | ||||
| 
 | ||||
|                 // check available distance
 | ||||
|                 double t = pinhead_mesh_intersect( | ||||
|                                   hp, // touching point
 | ||||
|                                   nn, // normal
 | ||||
|                                   pin_r, | ||||
|                                   m_cfg.head_back_radius_mm, | ||||
|                                   w); | ||||
|                 EigenMesh3D::hit_result t | ||||
|                     = pinhead_mesh_intersect(hp, // touching point
 | ||||
|                                              nn, // normal
 | ||||
|                                              pin_r, | ||||
|                                              m_cfg.head_back_radius_mm, | ||||
|                                              w); | ||||
| 
 | ||||
|                 if(t <= w) { | ||||
|                 if(t.distance() <= w) { | ||||
| 
 | ||||
|                     // Let's try to optimize this angle, there might be a
 | ||||
|                     // viable normal that doesn't collide with the model
 | ||||
|  | @ -1523,12 +1727,17 @@ public: | |||
|                 // save the verified and corrected normal
 | ||||
|                 m_support_nmls.row(fidx) = nn; | ||||
| 
 | ||||
|                 if(t > w) { | ||||
|                     // mark the point for needing a head.
 | ||||
|                     m_iheads.emplace_back(fidx); | ||||
|                 } else if( polar >= 3*PI/4 ) { | ||||
|                     // Headless supports do not tilt like the headed ones so
 | ||||
|                     // the normal should point almost to the ground.
 | ||||
|                 if (t.distance() > w) { | ||||
|                     // Check distance from ground, we might have zero elevation.
 | ||||
|                     if (hp(Z) + w * nn(Z) < m_result.ground_level) { | ||||
|                         m_iheadless.emplace_back(fidx); | ||||
|                     } else { | ||||
|                         // mark the point for needing a head.
 | ||||
|                         m_iheads.emplace_back(fidx); | ||||
|                     } | ||||
|                 } else if (polar >= 3 * PI / 4) { | ||||
|                     // Headless supports do not tilt like the headed ones
 | ||||
|                     // so the normal should point almost to the ground.
 | ||||
|                     m_iheadless.emplace_back(fidx); | ||||
|                 } | ||||
|             } | ||||
|  | @ -1594,16 +1803,22 @@ public: | |||
|         // from each other in the XY plane to not cross their pillar bases
 | ||||
|         // These clusters of support points will join in one pillar,
 | ||||
|         // possibly in their centroid support point.
 | ||||
|          | ||||
|         auto pointfn = [this](unsigned i) { | ||||
|             return m_result.head(i).junction_point(); | ||||
|         }; | ||||
|         auto predicate = [this](const SpatElement& e1, const SpatElement& e2) { | ||||
| 
 | ||||
|         auto predicate = [this](const PointIndexEl &e1, | ||||
|                                 const PointIndexEl &e2) { | ||||
|             double d2d = distance(to_2d(e1.first), to_2d(e2.first)); | ||||
|             double d3d = distance(e1.first, e2.first); | ||||
|             return d2d < 2 * m_cfg.base_radius_mm && | ||||
|                    d3d < m_cfg.max_bridge_length_mm; | ||||
|             return d2d < 2 * m_cfg.base_radius_mm | ||||
|                    && d3d < m_cfg.max_bridge_length_mm; | ||||
|         }; | ||||
|         m_pillar_clusters = cluster(ground_head_indices, pointfn, predicate, | ||||
| 
 | ||||
|         m_pillar_clusters = cluster(ground_head_indices, | ||||
|                                     pointfn, | ||||
|                                     predicate, | ||||
|                                     m_cfg.max_bridges_on_pillar); | ||||
|     } | ||||
| 
 | ||||
|  | @ -1615,7 +1830,7 @@ public: | |||
|     void routing_to_ground() | ||||
|     { | ||||
|         const double pradius = m_cfg.head_back_radius_mm; | ||||
|         const double gndlvl = m_result.ground_level; | ||||
|         // const double gndlvl = m_result.ground_level;
 | ||||
| 
 | ||||
|         ClusterEl cl_centroids; | ||||
|         cl_centroids.reserve(m_pillar_clusters.size()); | ||||
|  | @ -1648,13 +1863,8 @@ public: | |||
| 
 | ||||
|             Head& h = m_result.head(hid); | ||||
|             h.transform(); | ||||
|             Vec3d p = h.junction_point(); p(Z) = gndlvl; | ||||
|             auto& plr = m_result.add_pillar(hid, p, h.r_back_mm) | ||||
|                                 .add_base(m_cfg.base_height_mm, | ||||
|                                           m_cfg.base_radius_mm); | ||||
| 
 | ||||
|             // Save the pillar endpoint and the pillar id in the spatial index
 | ||||
|             m_pillar_index.insert(plr.endpoint(), unsigned(plr.id)); | ||||
|             create_ground_pillar(h.junction_point(), h.dir, h.r_back_mm, h.id); | ||||
|         } | ||||
| 
 | ||||
|         // now we will go through the clusters ones again and connect the
 | ||||
|  | @ -1681,15 +1891,12 @@ public: | |||
|                    !search_pillar_and_connect(sidehead)) | ||||
|                 { | ||||
|                     Vec3d pstart = sidehead.junction_point(); | ||||
|                     Vec3d pend = Vec3d{pstart(X), pstart(Y), gndlvl}; | ||||
|                     //Vec3d pend = Vec3d{pstart(X), pstart(Y), gndlvl};
 | ||||
|                     // Could not find a pillar, create one
 | ||||
|                     auto& pillar = m_result.add_pillar(unsigned(sidehead.id), | ||||
|                                                        pend, pradius) | ||||
|                                            .add_base(m_cfg.base_height_mm, | ||||
|                                                      m_cfg.base_radius_mm); | ||||
| 
 | ||||
|                     // connects to ground, eligible for bridging
 | ||||
|                     m_pillar_index.insert(pend, unsigned(pillar.id)); | ||||
|                     create_ground_pillar(pstart, | ||||
|                                          sidehead.dir, | ||||
|                                          pradius, | ||||
|                                          sidehead.id); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | @ -1718,12 +1925,7 @@ public: | |||
|             m_result.add_bridge(hjp, endp, head.r_back_mm); | ||||
|             m_result.add_junction(endp, head.r_back_mm); | ||||
| 
 | ||||
|             auto groundp = endp; | ||||
|             groundp(Z) = m_result.ground_level; | ||||
|             auto& newpillar = m_result.add_pillar(endp, groundp, head.r_back_mm) | ||||
|                                       .add_base(m_cfg.base_height_mm, | ||||
|                                                 m_cfg.base_radius_mm); | ||||
|             m_pillar_index.insert(groundp, unsigned(newpillar.id)); | ||||
|             this->create_ground_pillar(endp, dir, head.r_back_mm); | ||||
|         }; | ||||
| 
 | ||||
|         std::vector<unsigned> modelpillars; | ||||
|  | @ -1883,6 +2085,28 @@ public: | |||
|             m_pillar_index.insert(pillar.endpoint(), pillid); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     // Helper function for interconnect_pillars where pairs of already connected
 | ||||
|     // pillars should be checked for not to be processed again. This can be done
 | ||||
|     // in O(log) or even constant time with a set or an unordered set of hash
 | ||||
|     // values uniquely representing a pair of integers. The order of numbers
 | ||||
|     // within the pair should not matter, it has the same unique hash.
 | ||||
|     template<class I> static I pairhash(I a, I b) | ||||
|     { | ||||
|         using std::ceil; using std::log2; using std::max; using std::min; | ||||
|          | ||||
|         static_assert(std::is_integral<I>::value, | ||||
|                       "This function works only for integral types."); | ||||
| 
 | ||||
|         I g = min(a, b), l = max(a, b); | ||||
|          | ||||
|         auto bits_g = g ? int(ceil(log2(g))) : 0; | ||||
| 
 | ||||
|         // Assume the hash will fit into the output variable
 | ||||
|         assert((l ? (ceil(log2(l))) : 0) + bits_g < int(sizeof(I) * CHAR_BIT)); | ||||
|          | ||||
|         return (l << bits_g) + g; | ||||
|     } | ||||
| 
 | ||||
|     void interconnect_pillars() { | ||||
|         // Now comes the algorithm that connects pillars with each other.
 | ||||
|  | @ -1900,45 +2124,51 @@ public: | |||
|         double min_height_ratio = 0.5; | ||||
| 
 | ||||
|         std::set<unsigned long> pairs; | ||||
| 
 | ||||
|          | ||||
|         // A function to connect one pillar with its neighbors. THe number of
 | ||||
|         // neighbors is given in the configuration. This function if called
 | ||||
|         // for every pillar in the pillar index. A pair of pillar will not
 | ||||
|         // be connected multiple times this is ensured by the 'pairs' set which
 | ||||
|         // remembers the processed pillar pairs
 | ||||
|         auto cascadefn = | ||||
|                 [this, d, &pairs, min_height_ratio, H1] (const SpatElement& el) | ||||
|                 [this, d, &pairs, min_height_ratio, H1] (const PointIndexEl& el) | ||||
|         { | ||||
|             Vec3d qp = el.first; | ||||
| 
 | ||||
|             const Pillar& pillar = m_result.pillar(el.second); | ||||
|             Vec3d qp = el.first;    // endpoint of the pillar
 | ||||
| 
 | ||||
|             const Pillar& pillar = m_result.pillar(el.second); // actual pillar
 | ||||
|              | ||||
|             // Get the max number of neighbors a pillar should connect to
 | ||||
|             unsigned neighbors = m_cfg.pillar_cascade_neighbors; | ||||
| 
 | ||||
|             // connections are enough for one pillar
 | ||||
|             // connections are already enough for the pillar
 | ||||
|             if(pillar.links >= neighbors) return; | ||||
| 
 | ||||
|             // Query all remaining points within reach
 | ||||
|             auto qres = m_pillar_index.query([qp, d](const SpatElement& e){ | ||||
|             auto qres = m_pillar_index.query([qp, d](const PointIndexEl& e){ | ||||
|                 return distance(e.first, qp) < d; | ||||
|             }); | ||||
| 
 | ||||
|             // sort the result by distance (have to check if this is needed)
 | ||||
|             std::sort(qres.begin(), qres.end(), | ||||
|                       [qp](const SpatElement& e1, const SpatElement& e2){ | ||||
|                       [qp](const PointIndexEl& e1, const PointIndexEl& e2){ | ||||
|                 return distance(e1.first, qp) < distance(e2.first, qp); | ||||
|             }); | ||||
| 
 | ||||
|             for(auto& re : qres) { | ||||
|             for(auto& re : qres) { // process the queried neighbors
 | ||||
| 
 | ||||
|                 if(re.second == el.second) continue; | ||||
|                 if(re.second == el.second) continue; // Skip self
 | ||||
| 
 | ||||
|                 auto a = el.second, b = re.second; | ||||
| 
 | ||||
|                 // I hope that the area of a square is never equal to its
 | ||||
|                 // circumference
 | ||||
|                 auto hashval = 2 * (a + b) + a * b; | ||||
| 
 | ||||
|                 // Get unique hash for the given pair (order doesn't matter)
 | ||||
|                 auto hashval = pairhash(a, b); | ||||
|                  | ||||
|                 // Search for the pair amongst the remembered pairs
 | ||||
|                 if(pairs.find(hashval) != pairs.end()) continue; | ||||
| 
 | ||||
|                 const Pillar& neighborpillar = m_result.pillars()[re.second]; | ||||
| 
 | ||||
|                 // this neighbor is occupied
 | ||||
|                 // this neighbor is occupied, skip
 | ||||
|                 if(neighborpillar.links >= neighbors) continue; | ||||
| 
 | ||||
|                 if(interconnect(pillar, neighborpillar)) { | ||||
|  | @ -1960,47 +2190,79 @@ public: | |||
|                 if(pillar.links >= neighbors) break; | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|          | ||||
|         // Run the cascade for the pillars in the index
 | ||||
|         m_pillar_index.foreach(cascadefn); | ||||
| 
 | ||||
|         | ||||
|         // We would be done here if we could allow some pillars to not be
 | ||||
|         // connected with any neighbors. But this might leave the support tree
 | ||||
|         // unprintable.
 | ||||
|         //
 | ||||
|         // The current solution is to insert additional pillars next to these
 | ||||
|         // lonely pillars. One or even two additional pillar might get inserted
 | ||||
|         // depending on the length of the lonely pillar.
 | ||||
|          | ||||
|         size_t pillarcount = m_result.pillars().size(); | ||||
| 
 | ||||
|          | ||||
|         // Again, go through all pillars, this time in the whole support tree
 | ||||
|         // not just the index.
 | ||||
|         for(size_t pid = 0; pid < pillarcount; pid++) { | ||||
|             auto pillar = [this, pid]() { return m_result.pillar(pid); }; | ||||
| 
 | ||||
|             | ||||
|             // Decide how many additional pillars will be needed:
 | ||||
|              | ||||
|             unsigned needpillars = 0; | ||||
|             if(pillar().bridges > m_cfg.max_bridges_on_pillar) needpillars = 3; | ||||
|             else if(pillar().links < 2 && pillar().height > H2) { | ||||
|             if (pillar().bridges > m_cfg.max_bridges_on_pillar) | ||||
|                 needpillars = 3; | ||||
|             else if (pillar().links < 2 && pillar().height > H2) { | ||||
|                 // Not enough neighbors to support this pillar
 | ||||
|                 needpillars = 2 - pillar().links; | ||||
|             } | ||||
|             else if(pillar().links < 1 && pillar().height > H1) { | ||||
|             } else if (pillar().links < 1 && pillar().height > H1) { | ||||
|                 // No neighbors could be found and the pillar is too long.
 | ||||
|                 needpillars = 1; | ||||
|             } | ||||
| 
 | ||||
|             // Search for new pillar locations
 | ||||
|             bool found = false; | ||||
|             double alpha = 0; // goes to 2Pi
 | ||||
|             double r = 2 * m_cfg.base_radius_mm; | ||||
|             Vec3d pillarsp = pillar().startpoint(); | ||||
|             // Search for new pillar locations:
 | ||||
| 
 | ||||
|             bool   found    = false; | ||||
|             double alpha    = 0; // goes to 2Pi
 | ||||
|             double r        = 2 * m_cfg.base_radius_mm; | ||||
|             Vec3d  pillarsp = pillar().startpoint(); | ||||
| 
 | ||||
|             // temp value for starting point detection
 | ||||
|             Vec3d sp(pillarsp(X), pillarsp(Y), pillarsp(Z) - r); | ||||
|             std::vector<bool> tv(needpillars, false); | ||||
|             std::vector<Vec3d> spts(needpillars); | ||||
| 
 | ||||
|             // A vector of bool for placement feasbility
 | ||||
|             std::vector<bool>  canplace(needpillars, false); | ||||
|             std::vector<Vec3d> spts(needpillars); // vector of starting points
 | ||||
| 
 | ||||
|             double gnd      = m_result.ground_level; | ||||
|             double min_dist = m_cfg.pillar_base_safety_distance_mm + | ||||
|                               m_cfg.base_radius_mm + EPSILON; | ||||
|              | ||||
|             while(!found && alpha < 2*PI) { | ||||
| 
 | ||||
|                 for(unsigned n = 0; n < needpillars; n++) { | ||||
|                     double a = alpha + n * PI/3; | ||||
|                     Vec3d s = sp; | ||||
|                 for (unsigned n = 0; | ||||
|                      n < needpillars && (!n || canplace[n - 1]); | ||||
|                      n++) | ||||
|                 { | ||||
|                     double a = alpha + n * PI / 3; | ||||
|                     Vec3d  s = sp; | ||||
|                     s(X) += std::cos(a) * r; | ||||
|                     s(Y) += std::sin(a) * r; | ||||
|                     spts[n] = s; | ||||
|                      | ||||
|                     // Check the path vertically down                    
 | ||||
|                     auto hr = bridge_mesh_intersect(s, {0, 0, -1}, pillar().r); | ||||
|                     tv[n] = std::isinf(hr.distance()); | ||||
|                     Vec3d gndsp{s(X), s(Y), gnd}; | ||||
|                      | ||||
|                     // If the path is clear, check for pillar base collisions
 | ||||
|                     canplace[n] = std::isinf(hr.distance()) && | ||||
|                                   std::sqrt(m_mesh.squared_distance(gndsp)) > | ||||
|                                       min_dist; | ||||
|                 } | ||||
| 
 | ||||
|                 found = std::all_of(tv.begin(), tv.end(), [](bool v){return v;}); | ||||
|                 found = std::all_of(canplace.begin(), canplace.end(), | ||||
|                                     [](bool v) { return v; }); | ||||
| 
 | ||||
|                 // 20 angles will be tried...
 | ||||
|                 alpha += 0.1 * PI; | ||||
|  | @ -2010,7 +2272,7 @@ public: | |||
|             newpills.reserve(needpillars); | ||||
| 
 | ||||
|             if(found) for(unsigned n = 0; n < needpillars; n++) { | ||||
|                 Vec3d s = spts[n]; double gnd = m_result.ground_level; | ||||
|                 Vec3d s = spts[n];  | ||||
|                 Pillar p(s, Vec3d(s(X), s(Y), gnd), pillar().r); | ||||
|                 p.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); | ||||
| 
 | ||||
|  | @ -2075,9 +2337,13 @@ public: | |||
|             // This is only for checking
 | ||||
|             double idist = bridge_mesh_intersect(sph, dir, R, true); | ||||
|             double dist = ray_mesh_intersect(sj, dir); | ||||
|             if (std::isinf(dist)) | ||||
|                 dist = sph(Z) - m_mesh.ground_level() | ||||
|                        + m_mesh.ground_level_offset(); | ||||
| 
 | ||||
|             if(std::isinf(idist) || std::isnan(idist) || idist < 2*R || | ||||
|                std::isinf(dist)  || std::isnan(dist)   || dist < 2*R) { | ||||
|             if(std::isnan(idist) || idist < 2*R || | ||||
|                std::isnan(dist)  || dist  < 2*R) | ||||
|             { | ||||
|                 BOOST_LOG_TRIVIAL(warning) << "Can not find route for headless" | ||||
|                                            << " support stick at: " | ||||
|                                            << sj.transpose(); | ||||
|  | @ -2085,7 +2351,7 @@ public: | |||
|             } | ||||
| 
 | ||||
|             Vec3d ej = sj + (dist + HWIDTH_MM)* dir; | ||||
|             m_result.add_compact_bridge(sp, ej, n, R); | ||||
|             m_result.add_compact_bridge(sp, ej, n, R, !std::isinf(dist)); | ||||
|         } | ||||
|     } | ||||
| }; | ||||
|  | @ -2214,7 +2480,9 @@ bool SLASupportTree::generate(const std::vector<SupportPoint> &support_points, | |||
|     return pc == ABORT; | ||||
| } | ||||
| 
 | ||||
| SLASupportTree::SLASupportTree(): m_impl(new Impl()) {} | ||||
| SLASupportTree::SLASupportTree(double gnd_lvl): m_impl(new Impl()) { | ||||
|     m_impl->ground_level = gnd_lvl; | ||||
| } | ||||
| 
 | ||||
| const TriangleMesh &SLASupportTree::merged_mesh() const | ||||
| { | ||||
|  | @ -2226,7 +2494,7 @@ void SLASupportTree::merged_mesh_with_pad(TriangleMesh &outmesh) const { | |||
|     outmesh.merge(get_pad()); | ||||
| } | ||||
| 
 | ||||
| SlicedSupports SLASupportTree::slice(float layerh, float init_layerh) const | ||||
| std::vector<ExPolygons> SLASupportTree::slice(float layerh, float init_layerh) const | ||||
| { | ||||
|     if(init_layerh < 0) init_layerh = layerh; | ||||
|     auto& stree = get(); | ||||
|  | @ -2245,36 +2513,31 @@ SlicedSupports SLASupportTree::slice(float layerh, float init_layerh) const | |||
| 
 | ||||
|     TriangleMesh fullmesh = m_impl->merged_mesh(); | ||||
|     fullmesh.merge(get_pad()); | ||||
|     fullmesh.require_shared_vertices(); // TriangleMeshSlicer needs this
 | ||||
|     if (!fullmesh.empty()) fullmesh.require_shared_vertices(); | ||||
|     TriangleMeshSlicer slicer(&fullmesh); | ||||
|     SlicedSupports ret; | ||||
|     std::vector<ExPolygons> ret; | ||||
|     slicer.slice(heights, 0.f, &ret, get().ctl().cancelfn); | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| SlicedSupports SLASupportTree::slice(const std::vector<float> &heights, | ||||
| std::vector<ExPolygons> SLASupportTree::slice(const std::vector<float> &heights, | ||||
|                                      float cr) const | ||||
| { | ||||
|     TriangleMesh fullmesh = m_impl->merged_mesh(); | ||||
|     fullmesh.merge(get_pad()); | ||||
|     fullmesh.require_shared_vertices(); // TriangleMeshSlicer needs this
 | ||||
|     if (!fullmesh.empty()) fullmesh.require_shared_vertices(); | ||||
|     TriangleMeshSlicer slicer(&fullmesh); | ||||
|     SlicedSupports ret; | ||||
|     std::vector<ExPolygons> ret; | ||||
|     slicer.slice(heights, cr, &ret, get().ctl().cancelfn); | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| const TriangleMesh &SLASupportTree::add_pad(const SliceLayer& baseplate, | ||||
| const TriangleMesh &SLASupportTree::add_pad(const ExPolygons& modelbase, | ||||
|                                             const PoolConfig& pcfg) const | ||||
| { | ||||
| //    PoolConfig pcfg;
 | ||||
| //    pcfg.min_wall_thickness_mm = min_wall_thickness_mm;
 | ||||
| //    pcfg.min_wall_height_mm    = min_wall_height_mm;
 | ||||
| //    pcfg.max_merge_distance_mm = max_merge_distance_mm;
 | ||||
| //    pcfg.edge_radius_mm        = edge_radius_mm;
 | ||||
|     return m_impl->create_pad(merged_mesh(), baseplate, pcfg).tmesh; | ||||
|     return m_impl->create_pad(merged_mesh(), modelbase, pcfg).tmesh; | ||||
| } | ||||
| 
 | ||||
| const TriangleMesh &SLASupportTree::get_pad() const | ||||
|  |  | |||
|  | @ -24,10 +24,11 @@ class TriangleMesh; | |||
| class Model; | ||||
| class ModelInstance; | ||||
| class ModelObject; | ||||
| class Polygon; | ||||
| class ExPolygon; | ||||
| 
 | ||||
| using SliceLayer = std::vector<ExPolygon>; | ||||
| using SlicedSupports = std::vector<SliceLayer>; | ||||
| using Polygons = std::vector<Polygon>; | ||||
| using ExPolygons = std::vector<ExPolygon>; | ||||
| 
 | ||||
| namespace sla { | ||||
| 
 | ||||
|  | @ -80,6 +81,10 @@ struct SupportConfig { | |||
|     // The elevation in Z direction upwards. This is the space between the pad
 | ||||
|     // and the model object's bounding box bottom.
 | ||||
|     double object_elevation_mm = 10; | ||||
|      | ||||
|     // The shortest distance between a pillar base perimeter from the model
 | ||||
|     // body. This is only useful when elevation is set to zero.
 | ||||
|     double pillar_base_safety_distance_mm = 0.5; | ||||
| 
 | ||||
|     // /////////////////////////////////////////////////////////////////////////
 | ||||
|     // Compile time configuration values (candidates for runtime)
 | ||||
|  | @ -160,7 +165,7 @@ class SLASupportTree { | |||
| 
 | ||||
| public: | ||||
| 
 | ||||
|     SLASupportTree(); | ||||
|     SLASupportTree(double ground_level = 0.0); | ||||
| 
 | ||||
|     SLASupportTree(const std::vector<SupportPoint>& pts, | ||||
|                    const EigenMesh3D& em, | ||||
|  | @ -179,12 +184,17 @@ public: | |||
|     void merged_mesh_with_pad(TriangleMesh&) const; | ||||
| 
 | ||||
|     /// Get the sliced 2d layers of the support geometry.
 | ||||
|     SlicedSupports slice(float layerh, float init_layerh = -1.0) const; | ||||
|     std::vector<ExPolygons> slice(float layerh, float init_layerh = -1.0) const; | ||||
| 
 | ||||
|     SlicedSupports slice(const std::vector<float>&, float closing_radius) const; | ||||
|     std::vector<ExPolygons> slice(const std::vector<float> &, | ||||
|                                   float closing_radius) const; | ||||
| 
 | ||||
|     /// Adding the "pad" (base pool) under the supports
 | ||||
|     const TriangleMesh& add_pad(const SliceLayer& baseplate, | ||||
|     /// modelbase will be used according to the embed_object flag in PoolConfig.
 | ||||
|     /// If set, the plate will interpreted as the model's intrinsic pad. 
 | ||||
|     /// Otherwise, the modelbase will be unified with the base plate calculated
 | ||||
|     /// from the supports.
 | ||||
|     const TriangleMesh& add_pad(const ExPolygons& modelbase, | ||||
|                                 const PoolConfig& pcfg) const; | ||||
| 
 | ||||
|     /// Get the pad geometry
 | ||||
|  |  | |||
|  | @ -12,10 +12,18 @@ | |||
| #include "SLABoostAdapter.hpp" | ||||
| #include "boost/geometry/index/rtree.hpp" | ||||
| 
 | ||||
| #ifdef _MSC_VER | ||||
| #pragma warning(push) | ||||
| #pragma warning(disable: 4244) | ||||
| #pragma warning(disable: 4267) | ||||
| #endif | ||||
| #include <igl/ray_mesh_intersect.h> | ||||
| #include <igl/point_mesh_squared_distance.h> | ||||
| #include <igl/remove_duplicate_vertices.h> | ||||
| #include <igl/signed_distance.h> | ||||
| #ifdef _MSC_VER | ||||
| #pragma warning(pop) | ||||
| #endif | ||||
| 
 | ||||
| #include <tbb/parallel_for.h> | ||||
| 
 | ||||
|  | @ -29,69 +37,137 @@ namespace sla { | |||
| using igl::PI; | ||||
| 
 | ||||
| /* **************************************************************************
 | ||||
|  * SpatIndex implementation | ||||
|  * PointIndex implementation | ||||
|  * ************************************************************************** */ | ||||
| 
 | ||||
| class SpatIndex::Impl { | ||||
| class PointIndex::Impl { | ||||
| public: | ||||
|     using BoostIndex = boost::geometry::index::rtree< SpatElement, | ||||
|     using BoostIndex = boost::geometry::index::rtree< PointIndexEl, | ||||
|                        boost::geometry::index::rstar<16, 4> /* ? */ >; | ||||
| 
 | ||||
|     BoostIndex m_store; | ||||
| }; | ||||
| 
 | ||||
| SpatIndex::SpatIndex(): m_impl(new Impl()) {} | ||||
| SpatIndex::~SpatIndex() {} | ||||
| PointIndex::PointIndex(): m_impl(new Impl()) {} | ||||
| PointIndex::~PointIndex() {} | ||||
| 
 | ||||
| SpatIndex::SpatIndex(const SpatIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} | ||||
| SpatIndex::SpatIndex(SpatIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} | ||||
| PointIndex::PointIndex(const PointIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} | ||||
| PointIndex::PointIndex(PointIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} | ||||
| 
 | ||||
| SpatIndex& SpatIndex::operator=(const SpatIndex &cpy) | ||||
| PointIndex& PointIndex::operator=(const PointIndex &cpy) | ||||
| { | ||||
|     m_impl.reset(new Impl(*cpy.m_impl)); | ||||
|     return *this; | ||||
| } | ||||
| 
 | ||||
| SpatIndex& SpatIndex::operator=(SpatIndex &&cpy) | ||||
| PointIndex& PointIndex::operator=(PointIndex &&cpy) | ||||
| { | ||||
|     m_impl.swap(cpy.m_impl); | ||||
|     return *this; | ||||
| } | ||||
| 
 | ||||
| void SpatIndex::insert(const SpatElement &el) | ||||
| void PointIndex::insert(const PointIndexEl &el) | ||||
| { | ||||
|     m_impl->m_store.insert(el); | ||||
| } | ||||
| 
 | ||||
| bool SpatIndex::remove(const SpatElement& el) | ||||
| bool PointIndex::remove(const PointIndexEl& el) | ||||
| { | ||||
|     return m_impl->m_store.remove(el) == 1; | ||||
| } | ||||
| 
 | ||||
| std::vector<SpatElement> | ||||
| SpatIndex::query(std::function<bool(const SpatElement &)> fn) | ||||
| std::vector<PointIndexEl> | ||||
| PointIndex::query(std::function<bool(const PointIndexEl &)> fn) | ||||
| { | ||||
|     namespace bgi = boost::geometry::index; | ||||
| 
 | ||||
|     std::vector<SpatElement> ret; | ||||
|     std::vector<PointIndexEl> ret; | ||||
|     m_impl->m_store.query(bgi::satisfies(fn), std::back_inserter(ret)); | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| std::vector<SpatElement> SpatIndex::nearest(const Vec3d &el, unsigned k = 1) | ||||
| std::vector<PointIndexEl> PointIndex::nearest(const Vec3d &el, unsigned k = 1) | ||||
| { | ||||
|     namespace bgi = boost::geometry::index; | ||||
|     std::vector<SpatElement> ret; ret.reserve(k); | ||||
|     std::vector<PointIndexEl> ret; ret.reserve(k); | ||||
|     m_impl->m_store.query(bgi::nearest(el, k), std::back_inserter(ret)); | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| size_t SpatIndex::size() const | ||||
| size_t PointIndex::size() const | ||||
| { | ||||
|     return m_impl->m_store.size(); | ||||
| } | ||||
| 
 | ||||
| void SpatIndex::foreach(std::function<void (const SpatElement &)> fn) | ||||
| void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn) | ||||
| { | ||||
|     for(auto& el : m_impl->m_store) fn(el); | ||||
| } | ||||
| 
 | ||||
| /* **************************************************************************
 | ||||
|  * BoxIndex implementation | ||||
|  * ************************************************************************** */ | ||||
| 
 | ||||
| class BoxIndex::Impl { | ||||
| public: | ||||
|     using BoostIndex = boost::geometry::index:: | ||||
|         rtree<BoxIndexEl, boost::geometry::index::rstar<16, 4> /* ? */>; | ||||
| 
 | ||||
|     BoostIndex m_store; | ||||
| }; | ||||
| 
 | ||||
| BoxIndex::BoxIndex(): m_impl(new Impl()) {} | ||||
| BoxIndex::~BoxIndex() {} | ||||
| 
 | ||||
| BoxIndex::BoxIndex(const BoxIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} | ||||
| BoxIndex::BoxIndex(BoxIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} | ||||
| 
 | ||||
| BoxIndex& BoxIndex::operator=(const BoxIndex &cpy) | ||||
| { | ||||
|     m_impl.reset(new Impl(*cpy.m_impl)); | ||||
|     return *this; | ||||
| } | ||||
| 
 | ||||
| BoxIndex& BoxIndex::operator=(BoxIndex &&cpy) | ||||
| { | ||||
|     m_impl.swap(cpy.m_impl); | ||||
|     return *this; | ||||
| } | ||||
| 
 | ||||
| void BoxIndex::insert(const BoxIndexEl &el) | ||||
| { | ||||
|     m_impl->m_store.insert(el); | ||||
| } | ||||
| 
 | ||||
| bool BoxIndex::remove(const BoxIndexEl& el) | ||||
| { | ||||
|     return m_impl->m_store.remove(el) == 1; | ||||
| } | ||||
| 
 | ||||
| std::vector<BoxIndexEl> BoxIndex::query(const BoundingBox &qrbb, | ||||
|                                         BoxIndex::QueryType qt) | ||||
| { | ||||
|     namespace bgi = boost::geometry::index; | ||||
|      | ||||
|     std::vector<BoxIndexEl> ret; ret.reserve(m_impl->m_store.size()); | ||||
|      | ||||
|     switch (qt) { | ||||
|     case qtIntersects: | ||||
|         m_impl->m_store.query(bgi::intersects(qrbb), std::back_inserter(ret)); | ||||
|         break; | ||||
|     case qtWithin: | ||||
|         m_impl->m_store.query(bgi::within(qrbb), std::back_inserter(ret)); | ||||
|     } | ||||
|      | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| size_t BoxIndex::size() const | ||||
| { | ||||
|     return m_impl->m_store.size(); | ||||
| } | ||||
| 
 | ||||
| void BoxIndex::foreach(std::function<void (const BoxIndexEl &)> fn) | ||||
| { | ||||
|     for(auto& el : m_impl->m_store) fn(el); | ||||
| } | ||||
|  | @ -343,12 +419,14 @@ PointSet normals(const PointSet& points, | |||
|     return ret; | ||||
| } | ||||
| namespace bgi = boost::geometry::index; | ||||
| using Index3D = bgi::rtree< SpatElement, bgi::rstar<16, 4> /* ? */ >; | ||||
| using Index3D = bgi::rtree< PointIndexEl, bgi::rstar<16, 4> /* ? */ >; | ||||
| 
 | ||||
| ClusteredPoints cluster(Index3D& sindex, unsigned max_points, | ||||
|                         std::function<std::vector<SpatElement>(const Index3D&, const SpatElement&)> qfn) | ||||
| ClusteredPoints cluster(Index3D &sindex, | ||||
|                         unsigned max_points, | ||||
|                         std::function<std::vector<PointIndexEl>( | ||||
|                             const Index3D &, const PointIndexEl &)> qfn) | ||||
| { | ||||
|     using Elems = std::vector<SpatElement>; | ||||
|     using Elems = std::vector<PointIndexEl>; | ||||
| 
 | ||||
|     // Recursive function for visiting all the points in a given distance to
 | ||||
|     // each other
 | ||||
|  | @ -356,8 +434,8 @@ ClusteredPoints cluster(Index3D& sindex, unsigned max_points, | |||
|     [&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster) | ||||
|     { | ||||
|         for(auto& p : pts) { | ||||
|             std::vector<SpatElement> tmp = qfn(sindex, p); | ||||
|             auto cmp = [](const SpatElement& e1, const SpatElement& e2){ | ||||
|             std::vector<PointIndexEl> tmp = qfn(sindex, p); | ||||
|             auto cmp = [](const PointIndexEl& e1, const PointIndexEl& e2){ | ||||
|                 return e1.second < e2.second; | ||||
|             }; | ||||
| 
 | ||||
|  | @ -401,12 +479,12 @@ ClusteredPoints cluster(Index3D& sindex, unsigned max_points, | |||
| } | ||||
| 
 | ||||
| namespace { | ||||
| std::vector<SpatElement> distance_queryfn(const Index3D& sindex, | ||||
|                                           const SpatElement& p, | ||||
| std::vector<PointIndexEl> distance_queryfn(const Index3D& sindex, | ||||
|                                           const PointIndexEl& p, | ||||
|                                           double dist, | ||||
|                                           unsigned max_points) | ||||
| { | ||||
|     std::vector<SpatElement> tmp; tmp.reserve(max_points); | ||||
|     std::vector<PointIndexEl> tmp; tmp.reserve(max_points); | ||||
|     sindex.query( | ||||
|         bgi::nearest(p.first, max_points), | ||||
|         std::back_inserter(tmp) | ||||
|  | @ -433,7 +511,7 @@ ClusteredPoints cluster( | |||
|     for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx)); | ||||
| 
 | ||||
|     return cluster(sindex, max_points, | ||||
|                    [dist, max_points](const Index3D& sidx, const SpatElement& p) | ||||
|                    [dist, max_points](const Index3D& sidx, const PointIndexEl& p) | ||||
|     { | ||||
|         return distance_queryfn(sidx, p, dist, max_points); | ||||
|     }); | ||||
|  | @ -443,7 +521,7 @@ ClusteredPoints cluster( | |||
| ClusteredPoints cluster( | ||||
|         const std::vector<unsigned>& indices, | ||||
|         std::function<Vec3d(unsigned)> pointfn, | ||||
|         std::function<bool(const SpatElement&, const SpatElement&)> predicate, | ||||
|         std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate, | ||||
|         unsigned max_points) | ||||
| { | ||||
|     // A spatial index for querying the nearest points
 | ||||
|  | @ -453,10 +531,10 @@ ClusteredPoints cluster( | |||
|     for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx)); | ||||
| 
 | ||||
|     return cluster(sindex, max_points, | ||||
|         [max_points, predicate](const Index3D& sidx, const SpatElement& p) | ||||
|         [max_points, predicate](const Index3D& sidx, const PointIndexEl& p) | ||||
|     { | ||||
|         std::vector<SpatElement> tmp; tmp.reserve(max_points); | ||||
|         sidx.query(bgi::satisfies([p, predicate](const SpatElement& e){ | ||||
|         std::vector<PointIndexEl> tmp; tmp.reserve(max_points); | ||||
|         sidx.query(bgi::satisfies([p, predicate](const PointIndexEl& e){ | ||||
|             return predicate(p, e); | ||||
|         }), std::back_inserter(tmp)); | ||||
|         return tmp; | ||||
|  | @ -473,7 +551,7 @@ ClusteredPoints cluster(const PointSet& pts, double dist, unsigned max_points) | |||
|         sindex.insert(std::make_pair(Vec3d(pts.row(i)), unsigned(i))); | ||||
| 
 | ||||
|     return cluster(sindex, max_points, | ||||
|                    [dist, max_points](const Index3D& sidx, const SpatElement& p) | ||||
|                    [dist, max_points](const Index3D& sidx, const PointIndexEl& p) | ||||
|     { | ||||
|         return distance_queryfn(sidx, p, dist, max_points); | ||||
|     }); | ||||
|  |  | |||
|  | @ -31,11 +31,10 @@ using SupportTreePtr = std::unique_ptr<sla::SLASupportTree>; | |||
| class SLAPrintObject::SupportData | ||||
| { | ||||
| public: | ||||
|     sla::EigenMesh3D emesh;             // index-triangle representation
 | ||||
|     std::vector<sla::SupportPoint> | ||||
|                    support_points;      // all the support points (manual/auto)
 | ||||
|     SupportTreePtr support_tree_ptr;    // the supports
 | ||||
|     SlicedSupports support_slices;      // sliced supports
 | ||||
|     sla::EigenMesh3D emesh;              // index-triangle representation
 | ||||
|     std::vector<sla::SupportPoint> support_points;     // all the support points (manual/auto)
 | ||||
|     SupportTreePtr                 support_tree_ptr;   // the supports
 | ||||
|     std::vector<ExPolygons>        support_slices;     // sliced supports
 | ||||
| 
 | ||||
|     inline SupportData(const TriangleMesh &trmesh) : emesh(trmesh) {} | ||||
| }; | ||||
|  | @ -212,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; } | ||||
|  | @ -316,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)
 | ||||
|  | @ -368,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); | ||||
|  | @ -441,12 +440,10 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf | |||
|         update_apply_status(this->invalidate_all_steps()); | ||||
|         m_objects = print_objects_new; | ||||
|         // Delete the PrintObjects marked as Unknown or Deleted.
 | ||||
|         bool deleted_objects = false; | ||||
|         for (auto &pos : print_object_status) | ||||
|             if (pos.status == PrintObjectStatus::Unknown || pos.status == PrintObjectStatus::Deleted) { | ||||
|                 update_apply_status(pos.print_object->invalidate_all_steps()); | ||||
|                 delete pos.print_object; | ||||
|                 deleted_objects = true; | ||||
|             } | ||||
|         if (new_objects) | ||||
|             update_apply_status(false); | ||||
|  | @ -473,7 +470,7 @@ void SLAPrint::set_task(const TaskParams ¶ms) | |||
| 
 | ||||
|     int n_object_steps = int(params.to_object_step) + 1; | ||||
|     if (n_object_steps == 0) | ||||
|         n_object_steps = (int)slaposCount; | ||||
|         n_object_steps = int(slaposCount); | ||||
| 
 | ||||
|     if (params.single_model_object.valid()) { | ||||
|         // Find the print object to be processed with priority.
 | ||||
|  | @ -488,7 +485,7 @@ void SLAPrint::set_task(const TaskParams ¶ms) | |||
|         // Find out whether the priority print object is being currently processed.
 | ||||
|         bool running = false; | ||||
|         for (int istep = 0; istep < n_object_steps; ++ istep) { | ||||
|             if (! print_object->m_stepmask[istep]) | ||||
|             if (! print_object->m_stepmask[size_t(istep)]) | ||||
|                 // Step was skipped, cancel.
 | ||||
|                 break; | ||||
|             if (print_object->is_step_started_unguarded(SLAPrintObjectStep(istep))) { | ||||
|  | @ -504,7 +501,7 @@ void SLAPrint::set_task(const TaskParams ¶ms) | |||
|         if (params.single_model_instance_only) { | ||||
|             // Suppress all the steps of other instances.
 | ||||
|             for (SLAPrintObject *po : m_objects) | ||||
|                 for (int istep = 0; istep < (int)slaposCount; ++ istep) | ||||
|                 for (size_t istep = 0; istep < slaposCount; ++ istep) | ||||
|                     po->m_stepmask[istep] = false; | ||||
|         } else if (! running) { | ||||
|             // Swap the print objects, so that the selected print_object is first in the row.
 | ||||
|  | @ -514,15 +511,15 @@ void SLAPrint::set_task(const TaskParams ¶ms) | |||
|         } | ||||
|         // and set the steps for the current object.
 | ||||
|         for (int istep = 0; istep < n_object_steps; ++ istep) | ||||
|             print_object->m_stepmask[istep] = true; | ||||
|         for (int istep = n_object_steps; istep < (int)slaposCount; ++ istep) | ||||
|             print_object->m_stepmask[istep] = false; | ||||
|             print_object->m_stepmask[size_t(istep)] = true; | ||||
|         for (int istep = n_object_steps; istep < int(slaposCount); ++ istep) | ||||
|             print_object->m_stepmask[size_t(istep)] = false; | ||||
|     } else { | ||||
|         // Slicing all objects.
 | ||||
|         bool running = false; | ||||
|         for (SLAPrintObject *print_object : m_objects) | ||||
|             for (int istep = 0; istep < n_object_steps; ++ istep) { | ||||
|                 if (! print_object->m_stepmask[istep]) { | ||||
|                 if (! print_object->m_stepmask[size_t(istep)]) { | ||||
|                     // Step may have been skipped. Restart.
 | ||||
|                     goto loop_end; | ||||
|                 } | ||||
|  | @ -538,8 +535,8 @@ void SLAPrint::set_task(const TaskParams ¶ms) | |||
|             this->call_cancel_callback(); | ||||
|         for (SLAPrintObject *po : m_objects) { | ||||
|             for (int istep = 0; istep < n_object_steps; ++ istep) | ||||
|                 po->m_stepmask[istep] = true; | ||||
|             for (int istep = n_object_steps; istep < (int)slaposCount; ++ istep) | ||||
|                 po->m_stepmask[size_t(istep)] = true; | ||||
|             for (auto istep = size_t(n_object_steps); istep < slaposCount; ++ istep) | ||||
|                 po->m_stepmask[istep] = false; | ||||
|         } | ||||
|     } | ||||
|  | @ -557,9 +554,9 @@ void SLAPrint::set_task(const TaskParams ¶ms) | |||
| void SLAPrint::finalize() | ||||
| { | ||||
|     for (SLAPrintObject *po : m_objects) | ||||
|         for (int istep = 0; istep < (int)slaposCount; ++ istep) | ||||
|         for (size_t istep = 0; istep < slaposCount; ++ istep) | ||||
|             po->m_stepmask[istep] = true; | ||||
|     for (int istep = 0; istep < (int)slapsCount; ++ istep) | ||||
|     for (size_t istep = 0; istep < slapsCount; ++ istep) | ||||
|         m_stepmask[istep] = true; | ||||
| } | ||||
| 
 | ||||
|  | @ -597,21 +594,48 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { | |||
|     scfg.pillar_widening_factor = c.support_pillar_widening_factor.getFloat(); | ||||
|     scfg.base_radius_mm = 0.5*c.support_base_diameter.getFloat(); | ||||
|     scfg.base_height_mm = c.support_base_height.getFloat(); | ||||
| 
 | ||||
|     scfg.pillar_base_safety_distance_mm = | ||||
|         c.support_base_safety_distance.getFloat() < EPSILON ? | ||||
|         scfg.safety_distance_mm : c.support_base_safety_distance.getFloat(); | ||||
|      | ||||
|     return scfg; | ||||
| } | ||||
| 
 | ||||
| sla::PoolConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c) { | ||||
|     sla::PoolConfig::EmbedObject ret; | ||||
|      | ||||
|     ret.enabled = c.support_object_elevation.getFloat() <= EPSILON && | ||||
|                   c.pad_enable.getBool() && c.supports_enable.getBool(); | ||||
|      | ||||
|     if(ret.enabled) { | ||||
|         ret.object_gap_mm        = c.pad_object_gap.getFloat(); | ||||
|         ret.stick_width_mm       = c.pad_object_connector_width.getFloat(); | ||||
|         ret.stick_stride_mm      = c.pad_object_connector_stride.getFloat(); | ||||
|         ret.stick_penetration_mm = c.pad_object_connector_penetration | ||||
|                                     .getFloat(); | ||||
|     } | ||||
|      | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| sla::PoolConfig make_pool_config(const SLAPrintObjectConfig& c) { | ||||
|     sla::PoolConfig pcfg; | ||||
| 
 | ||||
|     pcfg.min_wall_thickness_mm = c.pad_wall_thickness.getFloat(); | ||||
|     pcfg.wall_slope = c.pad_wall_slope.getFloat(); | ||||
|     pcfg.edge_radius_mm = c.pad_edge_radius.getFloat(); | ||||
|     pcfg.wall_slope = c.pad_wall_slope.getFloat() * PI / 180.0; | ||||
|      | ||||
|     // We do not support radius for now
 | ||||
|     pcfg.edge_radius_mm = 0.0; //c.pad_edge_radius.getFloat();
 | ||||
|      | ||||
|     pcfg.max_merge_distance_mm = c.pad_max_merge_distance.getFloat(); | ||||
|     pcfg.min_wall_height_mm = c.pad_wall_height.getFloat(); | ||||
| 
 | ||||
|     // set builtin pad implicitly ON
 | ||||
|     pcfg.embed_object = builtin_pad_cfg(c); | ||||
|      | ||||
|     return pcfg; | ||||
| } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| std::string SLAPrint::validate() const | ||||
|  | @ -634,9 +658,21 @@ std::string SLAPrint::validate() const | |||
|                 cfg.head_width_mm + | ||||
|                 2 * cfg.head_back_radius_mm - | ||||
|                 cfg.head_penetration_mm; | ||||
|          | ||||
|         double elv = cfg.object_elevation_mm; | ||||
| 
 | ||||
|         if(supports_en && pinhead_width > cfg.object_elevation_mm) | ||||
|         if(supports_en && elv > EPSILON && elv < pinhead_width ) | ||||
|             return L("Elevation is too low for object."); | ||||
|          | ||||
|         sla::PoolConfig::EmbedObject builtinpad = builtin_pad_cfg(po->config()); | ||||
|         if(supports_en && builtinpad.enabled && | ||||
|            cfg.pillar_base_safety_distance_mm < builtinpad.object_gap_mm) { | ||||
|             return L( | ||||
|                 "The endings of the support pillars will be deployed on the " | ||||
|                 "gap between the object and the pad. 'Support base safety " | ||||
|                 "distance' has to be greater than the 'Pad object gap' " | ||||
|                 "parameter to avoid this."); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return ""; | ||||
|  | @ -751,18 +787,27 @@ void SLAPrint::process() | |||
|              | ||||
|             mit->set_model_slice_idx(po, id); ++mit; | ||||
|         } | ||||
| 
 | ||||
|         if(po.m_config.supports_enable.getBool() || | ||||
|            po.m_config.pad_enable.getBool()) | ||||
|         { | ||||
|             po.m_supportdata.reset( | ||||
|                 new SLAPrintObject::SupportData(po.transformed_mesh()) ); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     // In this step we check the slices, identify island and cover them with
 | ||||
|     // support points. Then we sprinkle the rest of the mesh.
 | ||||
|     auto support_points = [this, ostepd](SLAPrintObject& po) { | ||||
|         const ModelObject& mo = *po.m_model_object; | ||||
|         po.m_supportdata.reset( | ||||
|                     new SLAPrintObject::SupportData(po.transformed_mesh()) ); | ||||
| 
 | ||||
|         // If supports are disabled, we can skip the model scan.
 | ||||
|         if(!po.m_config.supports_enable.getBool()) return; | ||||
| 
 | ||||
|         if (!po.m_supportdata) | ||||
|             po.m_supportdata.reset( | ||||
|                 new SLAPrintObject::SupportData(po.transformed_mesh())); | ||||
| 
 | ||||
|         const ModelObject& mo = *po.m_model_object; | ||||
| 
 | ||||
|         BOOST_LOG_TRIVIAL(debug) << "Support point count " | ||||
|                                  << mo.sla_support_points.size(); | ||||
| 
 | ||||
|  | @ -771,7 +816,7 @@ void SLAPrint::process() | |||
|         // into the backend cache.
 | ||||
|         if (mo.sla_points_status != sla::PointsStatus::UserModified) { | ||||
| 
 | ||||
|             // Hypotetical use of the slice index:
 | ||||
|             // Hypothetical use of the slice index:
 | ||||
|             // auto bb = po.transformed_mesh().bounding_box();
 | ||||
|             // auto range = po.get_slice_records(bb.min(Z));
 | ||||
|             // std::vector<float> heights; heights.reserve(range.size());
 | ||||
|  | @ -820,23 +865,59 @@ void SLAPrint::process() | |||
|             BOOST_LOG_TRIVIAL(debug) << "Automatic support points: " | ||||
|                                      << po.m_supportdata->support_points.size(); | ||||
| 
 | ||||
|             // Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass the update status to GLGizmoSlaSupports
 | ||||
|             m_report_status(*this, -1, L("Generating support points"), SlicingStatus::RELOAD_SLA_SUPPORT_POINTS); | ||||
|             // Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass
 | ||||
|             // the update status to GLGizmoSlaSupports
 | ||||
|             m_report_status(*this, | ||||
|                             -1, | ||||
|                             L("Generating support points"), | ||||
|                             SlicingStatus::RELOAD_SLA_SUPPORT_POINTS); | ||||
|         } | ||||
|         else { | ||||
|             // There are either some points on the front-end, or the user removed them on purpose. No calculation will be done.
 | ||||
|             // There are either some points on the front-end, or the user
 | ||||
|             // removed them on purpose. No calculation will be done.
 | ||||
|             po.m_supportdata->support_points = po.transformed_support_points(); | ||||
|         } | ||||
|          | ||||
|         // If the zero elevation mode is engaged, we have to filter out all the
 | ||||
|         // points that are on the bottom of the object
 | ||||
|         if (po.config().support_object_elevation.getFloat() <= EPSILON) { | ||||
|             double gnd       = po.m_supportdata->emesh.ground_level(); | ||||
|             auto & pts       = po.m_supportdata->support_points; | ||||
|             double tolerance = po.config().pad_enable.getBool() | ||||
|                                    ? po.m_config.pad_wall_thickness.getFloat() | ||||
|                                    : po.m_config.support_base_height.getFloat(); | ||||
| 
 | ||||
|             // get iterator to the reorganized vector end
 | ||||
|             auto endit = std::remove_if( | ||||
|                 pts.begin(), | ||||
|                 pts.end(), | ||||
|                 [tolerance, gnd](const sla::SupportPoint &sp) { | ||||
|                     double diff = std::abs(gnd - double(sp.pos(Z))); | ||||
|                     return diff <= tolerance; | ||||
|                 }); | ||||
|              | ||||
|             // erase all elements after the new end
 | ||||
|             pts.erase(endit, pts.end()); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     // In this step we create the supports
 | ||||
|     auto support_tree = [this, ostepd](SLAPrintObject& po) | ||||
|     { | ||||
|         if(!po.m_supportdata) return; | ||||
|          | ||||
|         sla::PoolConfig pcfg = make_pool_config(po.m_config); | ||||
| 
 | ||||
|         if (pcfg.embed_object) | ||||
|             po.m_supportdata->emesh.ground_level_offset( | ||||
|                 pcfg.min_wall_thickness_mm); | ||||
| 
 | ||||
|         if(!po.m_config.supports_enable.getBool()) { | ||||
|              | ||||
|             // Generate empty support tree. It can still host a pad
 | ||||
|             po.m_supportdata->support_tree_ptr.reset(new SLASupportTree()); | ||||
|             po.m_supportdata->support_tree_ptr.reset( | ||||
|                     new SLASupportTree(po.m_supportdata->emesh.ground_level())); | ||||
|              | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|  | @ -858,7 +939,7 @@ void SLAPrint::process() | |||
| 
 | ||||
|         ctl.stopcondition = [this](){ return canceled(); }; | ||||
|         ctl.cancelfn = [this]() { throw_if_canceled(); }; | ||||
| 
 | ||||
|          | ||||
|         po.m_supportdata->support_tree_ptr.reset( | ||||
|                     new SLASupportTree(po.m_supportdata->support_points, | ||||
|                                        po.m_supportdata->emesh, scfg, ctl)); | ||||
|  | @ -888,40 +969,33 @@ void SLAPrint::process() | |||
|         // and before the supports had been sliced. (or the slicing has to be
 | ||||
|         // repeated)
 | ||||
| 
 | ||||
|         if(!po.m_supportdata || !po.m_supportdata->support_tree_ptr) { | ||||
|             BOOST_LOG_TRIVIAL(error) << "Uninitialized support data at " | ||||
|                                      << "pad creation."; | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if(po.m_config.pad_enable.getBool()) | ||||
|         { | ||||
|             double wt = po.m_config.pad_wall_thickness.getFloat(); | ||||
|             double h =  po.m_config.pad_wall_height.getFloat(); | ||||
|             double md = po.m_config.pad_max_merge_distance.getFloat(); | ||||
|             // Radius is disabled for now...
 | ||||
|             double er = 0; // po.m_config.pad_edge_radius.getFloat();
 | ||||
|             double tilt = po.m_config.pad_wall_slope.getFloat()  * PI / 180.0; | ||||
|             double lh = po.m_config.layer_height.getFloat(); | ||||
|             double elevation = po.m_config.support_object_elevation.getFloat(); | ||||
|             if(!po.m_config.supports_enable.getBool()) elevation = 0; | ||||
|             sla::PoolConfig pcfg(wt, h, md, er, tilt); | ||||
|             // Get the distilled pad configuration from the config
 | ||||
|             sla::PoolConfig pcfg = make_pool_config(po.m_config); | ||||
| 
 | ||||
|             ExPolygons bp; | ||||
|             double pad_h = sla::get_pad_fullheight(pcfg); | ||||
|             auto&& trmesh = po.transformed_mesh(); | ||||
|             ExPolygons bp; // This will store the base plate of the pad.
 | ||||
|             double   pad_h             = sla::get_pad_fullheight(pcfg); | ||||
|             const TriangleMesh &trmesh = po.transformed_mesh(); | ||||
| 
 | ||||
|             // This call can get pretty time consuming
 | ||||
|             auto thrfn = [this](){ throw_if_canceled(); }; | ||||
| 
 | ||||
|             if(elevation < pad_h) { | ||||
|                 // we have to count with the model geometry for the base plate
 | ||||
|                 sla::base_plate(trmesh, bp, float(pad_h), float(lh), thrfn); | ||||
|             if (!po.m_config.supports_enable.getBool() || pcfg.embed_object) { | ||||
|                 // No support (thus no elevation) or zero elevation mode
 | ||||
|                 // we sometimes call it "builtin pad" is enabled so we will
 | ||||
|                 // get a sample from the bottom of the mesh and use it for pad
 | ||||
|                 // creation.
 | ||||
|                 sla::base_plate(trmesh, | ||||
|                                 bp, | ||||
|                                 float(pad_h), | ||||
|                                 float(po.m_config.layer_height.getFloat()), | ||||
|                                 thrfn); | ||||
|             } | ||||
| 
 | ||||
|             pcfg.throw_on_cancel = thrfn; | ||||
|             po.m_supportdata->support_tree_ptr->add_pad(bp, pcfg); | ||||
|         } else { | ||||
|         } else if(po.m_supportdata && po.m_supportdata->support_tree_ptr) { | ||||
|             po.m_supportdata->support_tree_ptr->remove_pad(); | ||||
|         } | ||||
| 
 | ||||
|  | @ -938,6 +1012,11 @@ void SLAPrint::process() | |||
| 
 | ||||
|         if(sd) sd->support_slices.clear(); | ||||
| 
 | ||||
|         // Don't bother if no supports and no pad is present.
 | ||||
|         if (!po.m_config.supports_enable.getBool() && | ||||
|             !po.m_config.pad_enable.getBool()) | ||||
|             return; | ||||
| 
 | ||||
|         if(sd && sd->support_tree_ptr) { | ||||
| 
 | ||||
|             std::vector<float> heights; heights.reserve(po.m_slice_index.size()); | ||||
|  | @ -964,7 +1043,8 @@ void SLAPrint::process() | |||
|             po.m_slice_index[i].set_support_slice_idx(po, i); | ||||
|         } | ||||
| 
 | ||||
|         // Using RELOAD_SLA_PREVIEW to tell the Plater to pass the update status to the 3D preview to load the SLA slices.
 | ||||
|         // Using RELOAD_SLA_PREVIEW to tell the Plater to pass the update
 | ||||
|         // status to the 3D preview to load the SLA slices.
 | ||||
|         m_report_status(*this, -2, "", SlicingStatus::RELOAD_SLA_PREVIEW); | ||||
|     }; | ||||
| 
 | ||||
|  | @ -1536,14 +1616,17 @@ bool SLAPrint::is_step_done(SLAPrintObjectStep step) const | |||
|     return true; | ||||
| } | ||||
| 
 | ||||
| SLAPrintObject::SLAPrintObject(SLAPrint *print, ModelObject *model_object): | ||||
|     Inherited(print, model_object), | ||||
|     m_stepmask(slaposCount, true), | ||||
|     m_transformed_rmesh( [this](TriangleMesh& obj){ | ||||
|             obj = m_model_object->raw_mesh(); obj.transform(m_trafo); obj.require_shared_vertices(); | ||||
|         }) | ||||
| { | ||||
| } | ||||
| SLAPrintObject::SLAPrintObject(SLAPrint *print, ModelObject *model_object) | ||||
|     : Inherited(print, model_object) | ||||
|     , m_stepmask(slaposCount, true) | ||||
|     , m_transformed_rmesh([this](TriangleMesh &obj) { | ||||
|         obj = m_model_object->raw_mesh(); | ||||
|         if (!obj.empty()) { | ||||
|             obj.transform(m_trafo); | ||||
|             obj.require_shared_vertices(); | ||||
|         } | ||||
|     }) | ||||
| {} | ||||
| 
 | ||||
| SLAPrintObject::~SLAPrintObject() {} | ||||
| 
 | ||||
|  | @ -1582,13 +1665,19 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf | |||
|             || opt_key == "support_critical_angle" | ||||
|             || opt_key == "support_max_bridge_length" | ||||
|             || opt_key == "support_max_pillar_link_distance" | ||||
|             || opt_key == "support_base_safety_distance" | ||||
|             ) { | ||||
|             steps.emplace_back(slaposSupportTree); | ||||
|         } else if ( | ||||
|                opt_key == "pad_wall_height" | ||||
|             || opt_key == "pad_max_merge_distance" | ||||
|             || opt_key == "pad_wall_slope" | ||||
|             || opt_key == "pad_edge_radius") { | ||||
|             || opt_key == "pad_edge_radius" | ||||
|             || opt_key == "pad_object_gap" | ||||
|             || opt_key == "pad_object_connector_stride" | ||||
|             || opt_key == "pad_object_connector_width" | ||||
|             || opt_key == "pad_object_connector_penetration" | ||||
|             ) { | ||||
|             steps.emplace_back(slaposBasePool); | ||||
|         } else { | ||||
|             // All keys should be covered.
 | ||||
|  | @ -1629,17 +1718,16 @@ bool SLAPrintObject::invalidate_all_steps() | |||
| } | ||||
| 
 | ||||
| double SLAPrintObject::get_elevation() const { | ||||
|     bool se = m_config.supports_enable.getBool(); | ||||
|     double ret = se? m_config.support_object_elevation.getFloat() : 0; | ||||
|     bool   en  = m_config.supports_enable.getBool(); | ||||
|     double ret = en ? m_config.support_object_elevation.getFloat() : 0.; | ||||
| 
 | ||||
|     // if the pad is enabled, then half of the pad height is its base plate
 | ||||
|     if(m_config.pad_enable.getBool()) { | ||||
|         // Normally the elevation for the pad itself would be the thickness of
 | ||||
|         // its walls but currently it is half of its thickness. Whatever it
 | ||||
|         // will be in the future, we provide the config to the get_pad_elevation
 | ||||
|         // method and we will have the correct value
 | ||||
|         sla::PoolConfig pcfg = make_pool_config(m_config); | ||||
|         ret += sla::get_pad_elevation(pcfg); | ||||
|         if(!pcfg.embed_object) ret += sla::get_pad_elevation(pcfg); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
|  | @ -1647,14 +1735,14 @@ double SLAPrintObject::get_elevation() const { | |||
| 
 | ||||
| double SLAPrintObject::get_current_elevation() const | ||||
| { | ||||
|     bool se = m_config.supports_enable.getBool(); | ||||
|     bool has_supports = is_step_done(slaposSupportTree); | ||||
|     bool has_pad = is_step_done(slaposBasePool); | ||||
| 
 | ||||
|     if(!has_supports && !has_pad) | ||||
|         return 0; | ||||
|     else if(has_supports && !has_pad) | ||||
|         return se ? m_config.support_object_elevation.getFloat() : 0; | ||||
|     else if(has_supports && !has_pad) { | ||||
|         return m_config.support_object_elevation.getFloat(); | ||||
|     } | ||||
| 
 | ||||
|     return get_elevation(); | ||||
| } | ||||
|  | @ -1682,13 +1770,14 @@ namespace { // dummy empty static containers for return values in some methods | |||
| const std::vector<ExPolygons> EMPTY_SLICES; | ||||
| const TriangleMesh EMPTY_MESH; | ||||
| const ExPolygons EMPTY_SLICE; | ||||
| const std::vector<sla::SupportPoint> EMPTY_SUPPORT_POINTS; | ||||
| } | ||||
| 
 | ||||
| const SliceRecord SliceRecord::EMPTY(0, std::nanf(""), 0.f); | ||||
| 
 | ||||
| const std::vector<sla::SupportPoint>& SLAPrintObject::get_support_points() const | ||||
| { | ||||
|     return m_supportdata->support_points; | ||||
|     return m_supportdata? m_supportdata->support_points : EMPTY_SUPPORT_POINTS; | ||||
| } | ||||
| 
 | ||||
| const std::vector<ExPolygons> &SLAPrintObject::get_support_slices() const | ||||
|  |  | |||
|  | @ -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.
 | ||||
|  |  | |||
|  | @ -153,24 +153,33 @@ SlicingParameters SlicingParameters::create_from_config( | |||
|     return params; | ||||
| } | ||||
| 
 | ||||
| // Convert layer_height_ranges to layer_height_profile. Both are referenced to z=0, meaning the raft layers are not accounted for
 | ||||
| std::vector<std::pair<t_layer_height_range, coordf_t>> layer_height_ranges(const t_layer_config_ranges &config_ranges) | ||||
| { | ||||
| 	std::vector<std::pair<t_layer_height_range, coordf_t>> out; | ||||
| 	out.reserve(config_ranges.size()); | ||||
| 	for (const auto &kvp : config_ranges) | ||||
| 		out.emplace_back(kvp.first, kvp.second.option("layer_height")->getFloat()); | ||||
| 	return out; | ||||
| } | ||||
| 
 | ||||
| // Convert layer_config_ranges to layer_height_profile. Both are referenced to z=0, meaning the raft layers are not accounted for
 | ||||
| // in the height profile and the printed object may be lifted by the raft thickness at the time of the G-code generation.
 | ||||
| std::vector<coordf_t> layer_height_profile_from_ranges( | ||||
| 	const SlicingParameters 	&slicing_params, | ||||
| 	const t_layer_height_ranges &layer_height_ranges) | ||||
| 	const t_layer_config_ranges &layer_config_ranges)                           // #ys_FIXME_experiment
 | ||||
| { | ||||
|     // 1) If there are any height ranges, trim one by the other to make them non-overlapping. Insert the 1st layer if fixed.
 | ||||
|     std::vector<std::pair<t_layer_height_range,coordf_t>> ranges_non_overlapping; | ||||
|     ranges_non_overlapping.reserve(layer_height_ranges.size() * 4); | ||||
|     ranges_non_overlapping.reserve(layer_config_ranges.size() * 4);             // #ys_FIXME_experiment
 | ||||
|     if (slicing_params.first_object_layer_height_fixed()) | ||||
|         ranges_non_overlapping.push_back(std::pair<t_layer_height_range,coordf_t>( | ||||
|             t_layer_height_range(0., slicing_params.first_object_layer_height),  | ||||
|             slicing_params.first_object_layer_height)); | ||||
|     // The height ranges are sorted lexicographically by low / high layer boundaries.
 | ||||
|     for (t_layer_height_ranges::const_iterator it_range = layer_height_ranges.begin(); it_range != layer_height_ranges.end(); ++ it_range) { | ||||
|     for (t_layer_config_ranges::const_iterator it_range = layer_config_ranges.begin(); it_range != layer_config_ranges.end(); ++ it_range) { | ||||
|         coordf_t lo = it_range->first.first; | ||||
|         coordf_t hi = std::min(it_range->first.second, slicing_params.object_print_z_height()); | ||||
|         coordf_t height = it_range->second; | ||||
|         coordf_t height = it_range->second.option("layer_height")->getFloat(); | ||||
|         if (! ranges_non_overlapping.empty()) | ||||
|             // Trim current low with the last high.
 | ||||
|             lo = std::max(lo, ranges_non_overlapping.back().first.second); | ||||
|  | @ -187,7 +196,6 @@ std::vector<coordf_t> layer_height_profile_from_ranges( | |||
|         coordf_t hi = it_range->first.second; | ||||
|         coordf_t height = it_range->second; | ||||
|         coordf_t last_z      = layer_height_profile.empty() ? 0. : layer_height_profile[layer_height_profile.size() - 2]; | ||||
|         coordf_t last_height = layer_height_profile.empty() ? 0. : layer_height_profile[layer_height_profile.size() - 1]; | ||||
|         if (lo > last_z + EPSILON) { | ||||
|             // Insert a step of normal layer height.
 | ||||
|             layer_height_profile.push_back(last_z); | ||||
|  | @ -203,7 +211,6 @@ std::vector<coordf_t> layer_height_profile_from_ranges( | |||
|     } | ||||
| 
 | ||||
|     coordf_t last_z      = layer_height_profile.empty() ? 0. : layer_height_profile[layer_height_profile.size() - 2]; | ||||
|     coordf_t last_height = layer_height_profile.empty() ? 0. : layer_height_profile[layer_height_profile.size() - 1]; | ||||
|     if (last_z < slicing_params.object_print_z_height()) { | ||||
|         // Insert a step of normal layer height up to the object top.
 | ||||
|         layer_height_profile.push_back(last_z); | ||||
|  | @ -219,7 +226,7 @@ std::vector<coordf_t> layer_height_profile_from_ranges( | |||
| // Fill layer_height_profile by heights ensuring a prescribed maximum cusp height.
 | ||||
| std::vector<coordf_t> layer_height_profile_adaptive( | ||||
|     const SlicingParameters     &slicing_params, | ||||
|     const t_layer_height_ranges &layer_height_ranges, | ||||
|     const t_layer_config_ranges & /* layer_config_ranges */, | ||||
|     const ModelVolumePtrs		&volumes) | ||||
| { | ||||
|     // 1) Initialize the SlicingAdaptive class with the object meshes.
 | ||||
|  | @ -245,7 +252,6 @@ std::vector<coordf_t> layer_height_profile_adaptive( | |||
|     } | ||||
|     coordf_t slice_z = slicing_params.first_object_layer_height; | ||||
|     coordf_t height  = slicing_params.first_object_layer_height; | ||||
|     coordf_t cusp_height = 0.; | ||||
|     int current_facet = 0; | ||||
|     while ((slice_z - height) <= slicing_params.object_print_z_height()) { | ||||
|         height = 999; | ||||
|  | @ -401,7 +407,6 @@ void adjust_layer_height_profile( | |||
|         } | ||||
|         // Adjust height by layer_thickness_delta.
 | ||||
|         coordf_t weight = std::abs(zz - z) < 0.5 * band_width ? (0.5 + 0.5 * cos(2. * M_PI * (zz - z) / band_width)) : 0.; | ||||
|         coordf_t height_new = height; | ||||
|         switch (action) { | ||||
|             case LAYER_HEIGHT_EDIT_ACTION_INCREASE: | ||||
|             case LAYER_HEIGHT_EDIT_ACTION_DECREASE: | ||||
|  |  | |||
|  | @ -11,6 +11,8 @@ | |||
| 
 | ||||
| #include "libslic3r.h" | ||||
| #include "Utils.hpp" | ||||
| #include "PrintConfig.hpp" | ||||
| 
 | ||||
| namespace Slic3r | ||||
| { | ||||
| 
 | ||||
|  | @ -128,15 +130,17 @@ inline bool equal_layering(const SlicingParameters &sp1, const SlicingParameters | |||
| } | ||||
| 
 | ||||
| typedef std::pair<coordf_t,coordf_t> t_layer_height_range; | ||||
| typedef std::map<t_layer_height_range,coordf_t> t_layer_height_ranges; | ||||
| typedef std::map<t_layer_height_range, DynamicPrintConfig> t_layer_config_ranges; | ||||
| 
 | ||||
| extern std::vector<std::pair<t_layer_height_range, coordf_t>> layer_height_ranges(const t_layer_config_ranges &config_ranges); | ||||
| 
 | ||||
| extern std::vector<coordf_t> layer_height_profile_from_ranges( | ||||
|     const SlicingParameters     &slicing_params, | ||||
|     const t_layer_height_ranges &layer_height_ranges); | ||||
|     const t_layer_config_ranges &layer_config_ranges); | ||||
| 
 | ||||
| extern std::vector<coordf_t> layer_height_profile_adaptive( | ||||
|     const SlicingParameters     &slicing_params, | ||||
|     const t_layer_height_ranges &layer_height_ranges, | ||||
|     const t_layer_config_ranges &layer_config_ranges, | ||||
|     const ModelVolumePtrs       &volumes); | ||||
| 
 | ||||
| 
 | ||||
|  | @ -171,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_ */ | ||||
|  |  | |||
|  | @ -361,17 +361,17 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) | |||
|     std::sort(layers_sorted.begin(), layers_sorted.end(), MyLayersPtrCompare()); | ||||
|     int layer_id = 0; | ||||
|     assert(object.support_layers().empty()); | ||||
|     for (int i = 0; i < int(layers_sorted.size());) { | ||||
|     for (size_t i = 0; i < layers_sorted.size();) { | ||||
|         // Find the last layer with roughly the same print_z, find the minimum layer height of all.
 | ||||
|         // Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should.
 | ||||
|         int j = i + 1; | ||||
|         size_t j = i + 1; | ||||
|         coordf_t zmax = layers_sorted[i]->print_z + EPSILON; | ||||
|         for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) ; | ||||
|         // Assign an average print_z to the set of layers with nearly equal print_z.
 | ||||
|         coordf_t zavg = 0.5 * (layers_sorted[i]->print_z + layers_sorted[j - 1]->print_z); | ||||
|         coordf_t height_min = layers_sorted[i]->height; | ||||
|         bool     empty = true; | ||||
|         for (int u = i; u < j; ++u) { | ||||
|         for (size_t u = i; u < j; ++u) { | ||||
|             MyLayer &layer = *layers_sorted[u]; | ||||
|             if (! layer.polygons.empty()) | ||||
|                 empty = false; | ||||
|  | @ -829,7 +829,7 @@ namespace SupportMaterialInternal { | |||
|         assert(expansion_scaled >= 0.f); | ||||
|         for (const ExtrusionPath &ep : loop.paths) | ||||
|             if (ep.role() == erOverhangPerimeter && ! ep.polyline.empty()) { | ||||
|                 float exp = 0.5f * scale_(ep.width) + expansion_scaled; | ||||
|                 float exp = 0.5f * (float)scale_(ep.width) + expansion_scaled; | ||||
|                 if (ep.is_closed()) { | ||||
|                     if (ep.size() >= 3) { | ||||
|                         // This is a complete loop.
 | ||||
|  | @ -1042,7 +1042,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ | |||
|                         float fw = float(layerm->flow(frExternalPerimeter).scaled_width()); | ||||
|                         no_interface_offset = (no_interface_offset == 0.f) ? fw : std::min(no_interface_offset, fw); | ||||
|                         float lower_layer_offset =  | ||||
|                             (layer_id < m_object_config->support_material_enforce_layers.value) ?  | ||||
|                             (layer_id < (size_t)m_object_config->support_material_enforce_layers.value) ?  | ||||
|                                 // Enforce a full possible support, ignore the overhang angle.
 | ||||
|                                 0.f : | ||||
|                             (threshold_rad > 0. ?  | ||||
|  | @ -1352,7 +1352,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ | |||
| 		{ | ||||
| 			// Find the span of layers, which are to be printed at the first layer height.
 | ||||
| 			int j = 0; | ||||
| 			for (; j < contact_out.size() && contact_out[j]->print_z < m_slicing_params.first_print_layer_height + this->m_support_layer_height_min - EPSILON; ++ j); | ||||
| 			for (; j < (int)contact_out.size() && contact_out[j]->print_z < m_slicing_params.first_print_layer_height + this->m_support_layer_height_min - EPSILON; ++ j); | ||||
| 			if (j > 0) { | ||||
| 				// Merge the contact_out layers (0) to (j - 1) into the contact_out[0].
 | ||||
| 				MyLayer &dst = *contact_out.front(); | ||||
|  | @ -1377,7 +1377,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ | |||
|             // Find the span of layers closer than m_support_layer_height_min.
 | ||||
|             int j = i + 1; | ||||
|             coordf_t zmax = contact_out[i]->print_z + m_support_layer_height_min + EPSILON; | ||||
|             for (; j < contact_out.size() && contact_out[j]->print_z < zmax; ++ j) ; | ||||
|             for (; j < (int)contact_out.size() && contact_out[j]->print_z < zmax; ++ j) ; | ||||
|             if (i + 1 < j) { | ||||
|                 // Merge the contact_out layers (i + 1) to (j - 1) into the contact_out[i].
 | ||||
|                 MyLayer &dst = *contact_out[i]; | ||||
|  | @ -1395,7 +1395,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ | |||
|                 contact_out[k] = contact_out[i]; | ||||
|             i = j; | ||||
|         } | ||||
|         if (k < contact_out.size()) | ||||
|         if (k < (int)contact_out.size()) | ||||
|             contact_out.erase(contact_out.begin() + k, contact_out.end()); | ||||
|     } | ||||
| 
 | ||||
|  | @ -2214,7 +2214,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf | |||
|         // Expand the bases of the support columns in the 1st layer.
 | ||||
|         columns_base->polygons = diff( | ||||
|             offset(columns_base->polygons, inflate_factor_1st_layer), | ||||
|             offset(m_object->layers().front()->slices.expolygons, scale_(m_gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS)); | ||||
|             offset(m_object->layers().front()->slices.expolygons, (float)scale_(m_gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS)); | ||||
|         if (contacts != nullptr) | ||||
|             columns_base->polygons = diff(columns_base->polygons, interface_polygons); | ||||
|     } | ||||
|  | @ -2566,11 +2566,11 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const | |||
|     { | ||||
|         // make more loops
 | ||||
|         Polygons loop_polygons = loops0; | ||||
|         for (size_t i = 1; i < n_contact_loops; ++ i) | ||||
|         for (int i = 1; i < n_contact_loops; ++ i) | ||||
|             polygons_append(loop_polygons,  | ||||
|                 offset2( | ||||
|                     loops0,  | ||||
|                     - int(i) * flow.scaled_spacing() - 0.5f * flow.scaled_spacing(),  | ||||
|                     - i * flow.scaled_spacing() - 0.5f * flow.scaled_spacing(),  | ||||
|                     0.5f * flow.scaled_spacing())); | ||||
|         // Clip such loops to the side oriented towards the object.
 | ||||
|         // Collect split points, so they will be recognized after the clipping.
 | ||||
|  | @ -3226,7 +3226,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( | |||
|                     // TODO: use brim ordering algorithm
 | ||||
|                     Polygons to_infill_polygons = to_polygons(to_infill); | ||||
|                     // TODO: use offset2_ex()
 | ||||
|                     to_infill = offset_ex(to_infill, - 0.4 * float(flow.scaled_spacing())); | ||||
|                     to_infill = offset_ex(to_infill, - 0.4f * float(flow.scaled_spacing())); | ||||
|                     extrusion_entities_append_paths( | ||||
|                         base_layer.extrusions,  | ||||
|                         to_polylines(std::move(to_infill_polygons)), | ||||
|  |  | |||
|  | @ -41,13 +41,4 @@ | |||
| #define ENABLE_TEXTURES_FROM_SVG (1 && ENABLE_1_42_0_ALPHA7) | ||||
| 
 | ||||
| 
 | ||||
| //====================
 | ||||
| // 1.42.0.alpha8 techs
 | ||||
| //====================
 | ||||
| #define ENABLE_1_42_0_ALPHA8 1 | ||||
| 
 | ||||
| // Toolbars and Gizmos use icons imported from svg files
 | ||||
| #define ENABLE_SVG_ICONS (1 && ENABLE_1_42_0_ALPHA8 && ENABLE_TEXTURES_FROM_SVG) | ||||
| 
 | ||||
| 
 | ||||
| #endif // _technologies_h_
 | ||||
|  |  | |||
|  | @ -607,6 +607,40 @@ void TriangleMesh::require_shared_vertices() | |||
|     BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - end"; | ||||
| } | ||||
| 
 | ||||
| size_t TriangleMesh::memsize() const | ||||
| { | ||||
| 	size_t memsize = 8 + this->stl.memsize() + this->its.memsize(); | ||||
| 	return memsize; | ||||
| } | ||||
| 
 | ||||
| // Release optional data from the mesh if the object is on the Undo / Redo stack only. Returns the amount of memory released.
 | ||||
| size_t TriangleMesh::release_optional() | ||||
| { | ||||
| 	size_t memsize_released = sizeof(stl_neighbors) * this->stl.neighbors_start.size() + this->its.memsize(); | ||||
| 	// The indexed triangle set may be recalculated using the stl_generate_shared_vertices() function.
 | ||||
| 	this->its.clear(); | ||||
| 	// The neighbors structure may be recalculated using the stl_check_facets_exact() function.
 | ||||
| 	this->stl.neighbors_start.clear(); | ||||
| 	return memsize_released; | ||||
| } | ||||
| 
 | ||||
| // Restore optional data possibly released by release_optional().
 | ||||
| void TriangleMesh::restore_optional() | ||||
| { | ||||
| 	if (! this->stl.facet_start.empty()) { | ||||
| 		// Save the old stats before calling stl_check_faces_exact, as it may modify the statistics.
 | ||||
| 		stl_stats stats = this->stl.stats; | ||||
| 		if (this->stl.neighbors_start.empty()) { | ||||
| 			stl_reallocate(&this->stl); | ||||
| 			stl_check_facets_exact(&this->stl); | ||||
| 		} | ||||
| 		if (this->its.vertices.empty()) | ||||
| 			stl_generate_shared_vertices(&this->stl, this->its); | ||||
| 		// Restore the old statistics.
 | ||||
| 		this->stl.stats = stats; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callback_type throw_on_cancel) | ||||
| { | ||||
|     mesh = _mesh; | ||||
|  |  | |||
|  | @ -67,6 +67,12 @@ public: | |||
|     size_t facets_count() const { return this->stl.stats.number_of_facets; } | ||||
|     bool   empty() const { return this->facets_count() == 0; } | ||||
|     bool is_splittable() const; | ||||
|     // Estimate of the memory occupied by this structure, important for keeping an eye on the Undo / Redo stack allocation.
 | ||||
|     size_t memsize() const; | ||||
|     // Release optional data from the mesh if the object is on the Undo / Redo stack only. Returns the amount of memory released.
 | ||||
|     size_t release_optional(); | ||||
| 	// Restore optional data possibly released by release_optional().
 | ||||
| 	void restore_optional(); | ||||
| 
 | ||||
|     stl_file stl; | ||||
|     indexed_triangle_set its; | ||||
|  | @ -195,4 +201,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 | ||||
|  |  | |||
|  | @ -21,6 +21,8 @@ extern std::string format_memsize_MB(size_t n); | |||
| // The string is non-empty only if the loglevel >= info (3).
 | ||||
| extern std::string log_memory_info(); | ||||
| extern void disable_multi_threading(); | ||||
| // Returns the size of physical memory (RAM) in bytes.
 | ||||
| extern size_t total_physical_memory(); | ||||
| 
 | ||||
| // Set a path with GUI resource files.
 | ||||
| void set_var_dir(const std::string &path); | ||||
|  | @ -182,7 +184,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" | ||||
|  |  | |||
|  | @ -7,10 +7,15 @@ | |||
| #include <stdio.h> | ||||
| 
 | ||||
| #ifdef WIN32 | ||||
| #include <windows.h> | ||||
| #include <psapi.h> | ||||
| 	#include <windows.h> | ||||
| 	#include <psapi.h> | ||||
| #else | ||||
| #include <unistd.h> | ||||
| 	#include <unistd.h> | ||||
| 	#include <sys/types.h> | ||||
| 	#include <sys/param.h> | ||||
| 	#ifdef BSD | ||||
| 		#include <sys/sysctl.h> | ||||
| 	#endif | ||||
| #endif | ||||
| 
 | ||||
| #include <boost/log/core.hpp> | ||||
|  | @ -414,13 +419,13 @@ std::string format_memsize_MB(size_t n) | |||
|         scale *= 1000; | ||||
|     } | ||||
|     char buf[8]; | ||||
|     sprintf(buf, "%d", n); | ||||
|     sprintf(buf, "%d", (int)n); | ||||
|     out = buf; | ||||
|     while (scale != 1) { | ||||
|         scale /= 1000; | ||||
|         n = n2 / scale; | ||||
|         n2 = n2  % scale; | ||||
|         sprintf(buf, ",%03d", n); | ||||
|         sprintf(buf, ",%03d", (int)n); | ||||
|         out += buf; | ||||
|     } | ||||
|     return out + "MB"; | ||||
|  | @ -467,4 +472,75 @@ std::string log_memory_info() | |||
| } | ||||
| #endif | ||||
| 
 | ||||
| // Returns the size of physical memory (RAM) in bytes.
 | ||||
| // http://nadeausoftware.com/articles/2012/09/c_c_tip_how_get_physical_memory_size_system
 | ||||
| size_t total_physical_memory() | ||||
| { | ||||
| #if defined(_WIN32) && (defined(__CYGWIN__) || defined(__CYGWIN32__)) | ||||
| 	// Cygwin under Windows. ------------------------------------
 | ||||
| 	// New 64-bit MEMORYSTATUSEX isn't available.  Use old 32.bit
 | ||||
| 	MEMORYSTATUS status; | ||||
| 	status.dwLength = sizeof(status); | ||||
| 	GlobalMemoryStatus( &status ); | ||||
| 	return (size_t)status.dwTotalPhys; | ||||
| #elif defined(_WIN32) | ||||
| 	// Windows. -------------------------------------------------
 | ||||
| 	// Use new 64-bit MEMORYSTATUSEX, not old 32-bit MEMORYSTATUS
 | ||||
| 	MEMORYSTATUSEX status; | ||||
| 	status.dwLength = sizeof(status); | ||||
| 	GlobalMemoryStatusEx( &status ); | ||||
| 	return (size_t)status.ullTotalPhys; | ||||
| #elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__)) | ||||
| 	// UNIX variants. -------------------------------------------
 | ||||
| 	// Prefer sysctl() over sysconf() except sysctl() HW_REALMEM and HW_PHYSMEM
 | ||||
| 
 | ||||
| #if defined(CTL_HW) && (defined(HW_MEMSIZE) || defined(HW_PHYSMEM64)) | ||||
| 	int mib[2]; | ||||
| 	mib[0] = CTL_HW; | ||||
| #if defined(HW_MEMSIZE) | ||||
| 	mib[1] = HW_MEMSIZE;            // OSX. ---------------------
 | ||||
| #elif defined(HW_PHYSMEM64) | ||||
| 	mib[1] = HW_PHYSMEM64;          // NetBSD, OpenBSD. ---------
 | ||||
| #endif | ||||
| 	int64_t size = 0;               // 64-bit
 | ||||
| 	size_t len = sizeof( size ); | ||||
| 	if ( sysctl( mib, 2, &size, &len, NULL, 0 ) == 0 ) | ||||
| 		return (size_t)size; | ||||
| 	return 0L;			// Failed?
 | ||||
| 
 | ||||
| #elif defined(_SC_AIX_REALMEM) | ||||
| 	// AIX. -----------------------------------------------------
 | ||||
| 	return (size_t)sysconf( _SC_AIX_REALMEM ) * (size_t)1024L; | ||||
| 
 | ||||
| #elif defined(_SC_PHYS_PAGES) && defined(_SC_PAGESIZE) | ||||
| 	// FreeBSD, Linux, OpenBSD, and Solaris. --------------------
 | ||||
| 	return (size_t)sysconf( _SC_PHYS_PAGES ) * | ||||
| 		(size_t)sysconf( _SC_PAGESIZE ); | ||||
| 
 | ||||
| #elif defined(_SC_PHYS_PAGES) && defined(_SC_PAGE_SIZE) | ||||
| 	// Legacy. --------------------------------------------------
 | ||||
| 	return (size_t)sysconf( _SC_PHYS_PAGES ) * | ||||
| 		(size_t)sysconf( _SC_PAGE_SIZE ); | ||||
| 
 | ||||
| #elif defined(CTL_HW) && (defined(HW_PHYSMEM) || defined(HW_REALMEM)) | ||||
| 	// DragonFly BSD, FreeBSD, NetBSD, OpenBSD, and OSX. --------
 | ||||
| 	int mib[2]; | ||||
| 	mib[0] = CTL_HW; | ||||
| #if defined(HW_REALMEM) | ||||
| 	mib[1] = HW_REALMEM;		// FreeBSD. -----------------
 | ||||
| #elif defined(HW_PYSMEM) | ||||
| 	mib[1] = HW_PHYSMEM;		// Others. ------------------
 | ||||
| #endif | ||||
| 	unsigned int size = 0;		// 32-bit
 | ||||
| 	size_t len = sizeof( size ); | ||||
| 	if ( sysctl( mib, 2, &size, &len, NULL, 0 ) == 0 ) | ||||
| 		return (size_t)size; | ||||
| 	return 0L;			// Failed?
 | ||||
| #endif // sysctl and sysconf variants
 | ||||
| 
 | ||||
| #else | ||||
| 	return 0L;			// Unknown OS.
 | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| }; // namespace Slic3r
 | ||||
|  |  | |||
|  | @ -81,6 +81,8 @@ set(SLIC3R_GUI_SOURCES | |||
|     GUI/GUI_ObjectManipulation.hpp | ||||
|     GUI/GUI_ObjectSettings.cpp | ||||
|     GUI/GUI_ObjectSettings.hpp | ||||
|     GUI/GUI_ObjectLayers.cpp | ||||
|     GUI/GUI_ObjectLayers.hpp | ||||
|     GUI/LambdaObjectDialog.cpp | ||||
|     GUI/LambdaObjectDialog.hpp | ||||
|     GUI/Tab.cpp | ||||
|  | @ -146,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 | ||||
| ) | ||||
|  | @ -159,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 () | ||||
|  |  | |||
|  | @ -274,8 +274,8 @@ void Bed3D::Axes::render_axis(double length) const | |||
| 
 | ||||
| Bed3D::Bed3D() | ||||
|     : m_type(Custom) | ||||
|     , m_requires_canvas_update(false) | ||||
| #if ENABLE_TEXTURES_FROM_SVG | ||||
|     , m_requires_canvas_update(false) | ||||
|     , m_vbo_id(0) | ||||
| #endif // ENABLE_TEXTURES_FROM_SVG
 | ||||
|     , m_scale_factor(1.0f) | ||||
|  | @ -330,12 +330,11 @@ Point Bed3D::point_projection(const Point& point) const | |||
| } | ||||
| 
 | ||||
| #if ENABLE_TEXTURES_FROM_SVG | ||||
| void Bed3D::render(GLCanvas3D* canvas, float theta, bool useVBOs, float scale_factor) const | ||||
| void Bed3D::render(GLCanvas3D* canvas, float theta, float scale_factor) const | ||||
| { | ||||
|     m_scale_factor = scale_factor; | ||||
| 
 | ||||
|     EType type = useVBOs ? m_type : Custom; | ||||
|     switch (type) | ||||
|     switch (m_type) | ||||
|     { | ||||
|     case MK2: | ||||
|     { | ||||
|  | @ -361,7 +360,7 @@ void Bed3D::render(GLCanvas3D* canvas, float theta, bool useVBOs, float scale_fa | |||
|     } | ||||
| } | ||||
| #else | ||||
| void Bed3D::render(GLCanvas3D* canvas, float theta, bool useVBOs, float scale_factor) const | ||||
| void Bed3D::render(float theta, float scale_factor) const | ||||
| { | ||||
|     m_scale_factor = scale_factor; | ||||
| 
 | ||||
|  | @ -372,17 +371,17 @@ void Bed3D::render(GLCanvas3D* canvas, float theta, bool useVBOs, float scale_fa | |||
|     { | ||||
|     case MK2: | ||||
|     { | ||||
|         render_prusa(canvas, "mk2", theta, useVBOs); | ||||
|         render_prusa("mk2", theta); | ||||
|         break; | ||||
|     } | ||||
|     case MK3: | ||||
|     { | ||||
|         render_prusa(canvas, "mk3", theta, useVBOs); | ||||
|         render_prusa("mk3", theta); | ||||
|         break; | ||||
|     } | ||||
|     case SL1: | ||||
|     { | ||||
|         render_prusa(canvas, "sl1", theta, useVBOs); | ||||
|         render_prusa("sl1", theta); | ||||
|         break; | ||||
|     } | ||||
|     default: | ||||
|  | @ -546,7 +545,7 @@ void Bed3D::render_prusa(GLCanvas3D* canvas, const std::string &key, bool bottom | |||
|     if (!bottom) | ||||
|     { | ||||
|         filename = model_path + "_bed.stl"; | ||||
|         if ((m_model.get_filename() != filename) && m_model.init_from_file(filename, true)) { | ||||
|         if ((m_model.get_filename() != filename) && m_model.init_from_file(filename)) { | ||||
|             Vec3d offset = m_bounding_box.center() - Vec3d(0.0, 0.0, 0.5 * m_model.get_bounding_box().size()(2)); | ||||
|             if (key == "mk2") | ||||
|                 // hardcoded value to match the stl model
 | ||||
|  | @ -628,12 +627,12 @@ void Bed3D::render_prusa_shader(bool transparent) const | |||
|         if (position_id != -1) | ||||
|         { | ||||
|             glsafe(::glEnableVertexAttribArray(position_id)); | ||||
|             glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)m_triangles.get_position_offset())); | ||||
|             glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)m_triangles.get_position_offset())); | ||||
|         } | ||||
|         if (tex_coords_id != -1) | ||||
|         { | ||||
|             glsafe(::glEnableVertexAttribArray(tex_coords_id)); | ||||
|             glsafe(::glVertexAttribPointer(tex_coords_id, 2, GL_FLOAT, GL_FALSE, stride, (GLvoid*)m_triangles.get_tex_coords_offset())); | ||||
|             glsafe(::glVertexAttribPointer(tex_coords_id, 2, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)m_triangles.get_tex_coords_offset())); | ||||
|         } | ||||
| 
 | ||||
|         glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)m_triangles.get_vertices_count())); | ||||
|  | @ -651,7 +650,7 @@ void Bed3D::render_prusa_shader(bool transparent) const | |||
|     } | ||||
| } | ||||
| #else | ||||
| void Bed3D::render_prusa(const std::string &key, float theta, bool useVBOs) const | ||||
| void Bed3D::render_prusa(const std::string& key, float theta) const | ||||
| { | ||||
|     std::string tex_path = resources_dir() + "/icons/bed/" + key; | ||||
| 
 | ||||
|  | @ -677,7 +676,7 @@ void Bed3D::render_prusa(const std::string &key, float theta, bool useVBOs) cons | |||
|     std::string filename = tex_path + "_top.png"; | ||||
|     if ((m_top_texture.get_id() == 0) || (m_top_texture.get_source() != filename)) | ||||
|     { | ||||
|         if (!m_top_texture.load_from_file(filename, true)) | ||||
|         if (!m_top_texture.load_from_file(filename, true, true)) | ||||
|         { | ||||
|             render_custom(); | ||||
|             return; | ||||
|  | @ -694,7 +693,7 @@ void Bed3D::render_prusa(const std::string &key, float theta, bool useVBOs) cons | |||
|     filename = tex_path + "_bottom.png"; | ||||
|     if ((m_bottom_texture.get_id() == 0) || (m_bottom_texture.get_source() != filename)) | ||||
|     { | ||||
|         if (!m_bottom_texture.load_from_file(filename, true)) | ||||
|         if (!m_bottom_texture.load_from_file(filename, true, true)) | ||||
|         { | ||||
|             render_custom(); | ||||
|             return; | ||||
|  | @ -711,7 +710,7 @@ void Bed3D::render_prusa(const std::string &key, float theta, bool useVBOs) cons | |||
|     if (theta <= 90.0f) | ||||
|     { | ||||
|         filename = model_path + "_bed.stl"; | ||||
|         if ((m_model.get_filename() != filename) && m_model.init_from_file(filename, useVBOs)) { | ||||
|         if ((m_model.get_filename() != filename) && m_model.init_from_file(filename)) { | ||||
|             Vec3d offset = m_bounding_box.center() - Vec3d(0.0, 0.0, 0.5 * m_model.get_bounding_box().size()(2)); | ||||
|             if (key == "mk2") | ||||
|                 // hardcoded value to match the stl model
 | ||||
|  |  | |||
|  | @ -44,8 +44,8 @@ public: | |||
|     const float* get_vertices_data() const; | ||||
|     unsigned int get_vertices_data_size() const { return (unsigned int)m_vertices.size() * get_vertex_data_size(); } | ||||
|     unsigned int get_vertex_data_size() const { return (unsigned int)(5 * sizeof(float)); } | ||||
|     unsigned int get_position_offset() const { return 0; } | ||||
|     unsigned int get_tex_coords_offset() const { return (unsigned int)(3 * sizeof(float)); } | ||||
|     size_t get_position_offset() const { return 0; } | ||||
|     size_t get_tex_coords_offset() const { return (size_t)(3 * sizeof(float)); } | ||||
|     unsigned int get_vertices_count() const { return (unsigned int)m_vertices.size(); } | ||||
| #else | ||||
|     const float* get_vertices() const { return m_vertices.data(); } | ||||
|  | @ -128,7 +128,11 @@ public: | |||
|     bool contains(const Point& point) const; | ||||
|     Point point_projection(const Point& point) const; | ||||
| 
 | ||||
|     void render(GLCanvas3D* canvas, float theta, bool useVBOs, float scale_factor) const; | ||||
| #if ENABLE_TEXTURES_FROM_SVG | ||||
|     void render(GLCanvas3D* canvas, float theta, float scale_factor) const; | ||||
| #else | ||||
|     void render(float theta, float scale_factor) const; | ||||
| #endif // ENABLE_TEXTURES_FROM_SVG
 | ||||
|     void render_axes() const; | ||||
| 
 | ||||
| private: | ||||
|  | @ -140,7 +144,7 @@ private: | |||
|     void render_prusa(GLCanvas3D* canvas, const std::string& key, bool bottom) const; | ||||
|     void render_prusa_shader(bool transparent) const; | ||||
| #else | ||||
|     void render_prusa(const std::string &key, float theta, bool useVBOs) const; | ||||
|     void render_prusa(const std::string& key, float theta) const; | ||||
| #endif // ENABLE_TEXTURES_FROM_SVG
 | ||||
|     void render_custom() const; | ||||
| #if ENABLE_TEXTURES_FROM_SVG | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -56,7 +56,7 @@ public: | |||
|         vertices_and_normals_interleaved_VBO_id(0), | ||||
|         triangle_indices_VBO_id(0), | ||||
|         quad_indices_VBO_id(0) | ||||
|         { this->setup_sizes(); } | ||||
|         {} | ||||
|     GLIndexedVertexArray(const GLIndexedVertexArray &rhs) : | ||||
|         vertices_and_normals_interleaved(rhs.vertices_and_normals_interleaved), | ||||
|         triangle_indices(rhs.triangle_indices), | ||||
|  | @ -64,7 +64,7 @@ public: | |||
|         vertices_and_normals_interleaved_VBO_id(0), | ||||
|         triangle_indices_VBO_id(0), | ||||
|         quad_indices_VBO_id(0) | ||||
|         { this->setup_sizes(); } | ||||
|         {} | ||||
|     GLIndexedVertexArray(GLIndexedVertexArray &&rhs) : | ||||
|         vertices_and_normals_interleaved(std::move(rhs.vertices_and_normals_interleaved)), | ||||
|         triangle_indices(std::move(rhs.triangle_indices)), | ||||
|  | @ -72,7 +72,9 @@ public: | |||
|         vertices_and_normals_interleaved_VBO_id(0), | ||||
|         triangle_indices_VBO_id(0), | ||||
|         quad_indices_VBO_id(0) | ||||
|         { this->setup_sizes(); } | ||||
|         {} | ||||
| 
 | ||||
|     ~GLIndexedVertexArray() { release_geometry(); } | ||||
| 
 | ||||
|     GLIndexedVertexArray& operator=(const GLIndexedVertexArray &rhs) | ||||
|     { | ||||
|  | @ -82,7 +84,10 @@ public: | |||
|         this->vertices_and_normals_interleaved = rhs.vertices_and_normals_interleaved; | ||||
|         this->triangle_indices                 = rhs.triangle_indices; | ||||
|         this->quad_indices                     = rhs.quad_indices; | ||||
|         this->setup_sizes(); | ||||
|         this->m_bounding_box                   = rhs.m_bounding_box; | ||||
|         vertices_and_normals_interleaved_size  = rhs.vertices_and_normals_interleaved_size; | ||||
|         triangle_indices_size                  = rhs.triangle_indices_size; | ||||
|         quad_indices_size                      = rhs.quad_indices_size; | ||||
|         return *this; | ||||
|     } | ||||
| 
 | ||||
|  | @ -94,30 +99,32 @@ public: | |||
|         this->vertices_and_normals_interleaved = std::move(rhs.vertices_and_normals_interleaved); | ||||
|         this->triangle_indices                 = std::move(rhs.triangle_indices); | ||||
|         this->quad_indices                     = std::move(rhs.quad_indices); | ||||
|         this->setup_sizes(); | ||||
|         this->m_bounding_box                   = std::move(rhs.m_bounding_box); | ||||
|         vertices_and_normals_interleaved_size  = rhs.vertices_and_normals_interleaved_size; | ||||
|         triangle_indices_size                  = rhs.triangle_indices_size; | ||||
|         quad_indices_size                      = rhs.quad_indices_size; | ||||
|         return *this; | ||||
|     } | ||||
| 
 | ||||
|     // Vertices and their normals, interleaved to be used by void glInterleavedArrays(GL_N3F_V3F, 0, x)
 | ||||
|     std::vector<float> vertices_and_normals_interleaved; | ||||
|     std::vector<int>   triangle_indices; | ||||
|     std::vector<int>   quad_indices; | ||||
|     mutable std::vector<float> vertices_and_normals_interleaved; | ||||
|     mutable std::vector<int>   triangle_indices; | ||||
|     mutable std::vector<int>   quad_indices; | ||||
| 
 | ||||
|     // When the geometry data is loaded into the graphics card as Vertex Buffer Objects,
 | ||||
|     // the above mentioned std::vectors are cleared and the following variables keep their original length.
 | ||||
|     size_t             vertices_and_normals_interleaved_size; | ||||
|     size_t             triangle_indices_size; | ||||
|     size_t             quad_indices_size; | ||||
|     size_t vertices_and_normals_interleaved_size{ 0 }; | ||||
|     size_t triangle_indices_size{ 0 }; | ||||
|     size_t quad_indices_size{ 0 }; | ||||
| 
 | ||||
|     // IDs of the Vertex Array Objects, into which the geometry has been loaded.
 | ||||
|     // Zero if the VBOs are not used.
 | ||||
|     unsigned int       vertices_and_normals_interleaved_VBO_id; | ||||
|     unsigned int       triangle_indices_VBO_id; | ||||
|     unsigned int       quad_indices_VBO_id; | ||||
|     // Zero if the VBOs are not sent to GPU yet.
 | ||||
|     mutable unsigned int       vertices_and_normals_interleaved_VBO_id{ 0 }; | ||||
|     mutable unsigned int       triangle_indices_VBO_id{ 0 }; | ||||
|     mutable unsigned int       quad_indices_VBO_id{ 0 }; | ||||
| 
 | ||||
|     void load_mesh_flat_shading(const TriangleMesh &mesh); | ||||
|     void load_mesh_full_shading(const TriangleMesh &mesh); | ||||
| 	void load_mesh(const TriangleMesh &mesh, bool use_VBOs) { use_VBOs ? this->load_mesh_full_shading(mesh) : this->load_mesh_flat_shading(mesh); } | ||||
|     void load_mesh(const TriangleMesh& mesh) { this->load_mesh_full_shading(mesh); } | ||||
| 
 | ||||
|     inline bool has_VBOs() const { return vertices_and_normals_interleaved_VBO_id != 0; } | ||||
| 
 | ||||
|  | @ -128,6 +135,10 @@ public: | |||
|     } | ||||
| 
 | ||||
|     inline void push_geometry(float x, float y, float z, float nx, float ny, float nz) { | ||||
|         assert(this->vertices_and_normals_interleaved_VBO_id == 0); | ||||
|         if (this->vertices_and_normals_interleaved_VBO_id != 0) | ||||
|             return; | ||||
| 
 | ||||
|         if (this->vertices_and_normals_interleaved.size() + 6 > this->vertices_and_normals_interleaved.capacity()) | ||||
|             this->vertices_and_normals_interleaved.reserve(next_highest_power_of_2(this->vertices_and_normals_interleaved.size() + 6)); | ||||
|         this->vertices_and_normals_interleaved.push_back(nx); | ||||
|  | @ -136,6 +147,9 @@ public: | |||
|         this->vertices_and_normals_interleaved.push_back(x); | ||||
|         this->vertices_and_normals_interleaved.push_back(y); | ||||
|         this->vertices_and_normals_interleaved.push_back(z); | ||||
| 
 | ||||
|         this->vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size(); | ||||
|         m_bounding_box.merge(Vec3f(x, y, z).cast<double>()); | ||||
|     }; | ||||
| 
 | ||||
|     inline void push_geometry(double x, double y, double z, double nx, double ny, double nz) { | ||||
|  | @ -147,80 +161,66 @@ public: | |||
|     } | ||||
| 
 | ||||
|     inline void push_triangle(int idx1, int idx2, int idx3) { | ||||
|         assert(this->vertices_and_normals_interleaved_VBO_id == 0); | ||||
|         if (this->vertices_and_normals_interleaved_VBO_id != 0) | ||||
|             return; | ||||
| 
 | ||||
|         if (this->triangle_indices.size() + 3 > this->vertices_and_normals_interleaved.capacity()) | ||||
|             this->triangle_indices.reserve(next_highest_power_of_2(this->triangle_indices.size() + 3)); | ||||
|         this->triangle_indices.push_back(idx1); | ||||
|         this->triangle_indices.push_back(idx2); | ||||
|         this->triangle_indices.push_back(idx3); | ||||
|         this->triangle_indices_size = this->triangle_indices.size(); | ||||
|     }; | ||||
| 
 | ||||
|     inline void push_quad(int idx1, int idx2, int idx3, int idx4) { | ||||
|         assert(this->vertices_and_normals_interleaved_VBO_id == 0); | ||||
|         if (this->vertices_and_normals_interleaved_VBO_id != 0) | ||||
|             return; | ||||
| 
 | ||||
|         if (this->quad_indices.size() + 4 > this->vertices_and_normals_interleaved.capacity()) | ||||
|             this->quad_indices.reserve(next_highest_power_of_2(this->quad_indices.size() + 4)); | ||||
|         this->quad_indices.push_back(idx1); | ||||
|         this->quad_indices.push_back(idx2); | ||||
|         this->quad_indices.push_back(idx3); | ||||
|         this->quad_indices.push_back(idx4); | ||||
|         this->quad_indices_size = this->quad_indices.size(); | ||||
|     }; | ||||
| 
 | ||||
|     // Finalize the initialization of the geometry & indices,
 | ||||
|     // upload the geometry and indices to OpenGL VBO objects
 | ||||
|     // and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs.
 | ||||
|     void finalize_geometry(bool use_VBOs); | ||||
|     void finalize_geometry() const; | ||||
|     // Release the geometry data, release OpenGL VBOs.
 | ||||
|     void release_geometry(); | ||||
|     // Render either using an immediate mode, or the VBOs.
 | ||||
| 
 | ||||
|     void render() const; | ||||
|     void render(const std::pair<size_t, size_t> &tverts_range, const std::pair<size_t, size_t> &qverts_range) const; | ||||
|     void render(const std::pair<size_t, size_t>& tverts_range, const std::pair<size_t, size_t>& qverts_range) const; | ||||
| 
 | ||||
|     // Is there any geometry data stored?
 | ||||
|     bool empty() const { return vertices_and_normals_interleaved_size == 0; } | ||||
| 
 | ||||
|     // Is this object indexed, or is it just a set of triangles?
 | ||||
|     bool indexed() const { return ! this->empty() && this->triangle_indices_size + this->quad_indices_size > 0; } | ||||
| 
 | ||||
|     void clear() { | ||||
|         this->vertices_and_normals_interleaved.clear(); | ||||
|         this->triangle_indices.clear(); | ||||
|         this->quad_indices.clear(); | ||||
|         this->setup_sizes(); | ||||
|         this->m_bounding_box.reset(); | ||||
|         vertices_and_normals_interleaved_size = 0; | ||||
|         triangle_indices_size = 0; | ||||
|         quad_indices_size = 0; | ||||
|     } | ||||
| 
 | ||||
|     // Shrink the internal storage to tighly fit the data stored.
 | ||||
|     void shrink_to_fit() {  | ||||
|         if (! this->has_VBOs()) | ||||
|             this->setup_sizes(); | ||||
|     void shrink_to_fit() const { | ||||
|         this->vertices_and_normals_interleaved.shrink_to_fit(); | ||||
|         this->triangle_indices.shrink_to_fit(); | ||||
|         this->quad_indices.shrink_to_fit(); | ||||
|     } | ||||
| 
 | ||||
|     BoundingBoxf3 bounding_box() const { | ||||
|         BoundingBoxf3 bbox; | ||||
|         if (! this->vertices_and_normals_interleaved.empty()) { | ||||
|             bbox.defined = true; | ||||
|             bbox.min(0) = bbox.max(0) = this->vertices_and_normals_interleaved[3]; | ||||
|             bbox.min(1) = bbox.max(1) = this->vertices_and_normals_interleaved[4]; | ||||
|             bbox.min(2) = bbox.max(2) = this->vertices_and_normals_interleaved[5]; | ||||
|             for (size_t i = 9; i < this->vertices_and_normals_interleaved.size(); i += 6) { | ||||
|                 const float *verts = this->vertices_and_normals_interleaved.data() + i; | ||||
|                 bbox.min(0) = std::min<coordf_t>(bbox.min(0), verts[0]); | ||||
|                 bbox.min(1) = std::min<coordf_t>(bbox.min(1), verts[1]); | ||||
|                 bbox.min(2) = std::min<coordf_t>(bbox.min(2), verts[2]); | ||||
|                 bbox.max(0) = std::max<coordf_t>(bbox.max(0), verts[0]); | ||||
|                 bbox.max(1) = std::max<coordf_t>(bbox.max(1), verts[1]); | ||||
|                 bbox.max(2) = std::max<coordf_t>(bbox.max(2), verts[2]); | ||||
|             } | ||||
|         } | ||||
|         return bbox; | ||||
|     } | ||||
|     const BoundingBoxf3& bounding_box() const { return m_bounding_box; } | ||||
| 
 | ||||
| private: | ||||
|     inline void setup_sizes() { | ||||
|         vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size(); | ||||
|         triangle_indices_size                 = this->triangle_indices.size(); | ||||
|         quad_indices_size                     = this->quad_indices.size(); | ||||
|     } | ||||
|     BoundingBoxf3 m_bounding_box; | ||||
| }; | ||||
| 
 | ||||
| class GLVolume { | ||||
|  | @ -263,8 +263,6 @@ private: | |||
|     mutable bool          m_transformed_convex_hull_bounding_box_dirty; | ||||
| 
 | ||||
| public: | ||||
|     // Bounding box of this volume, in unscaled coordinates.
 | ||||
|     BoundingBoxf3       bounding_box; | ||||
|     // Color of the triangles / quads held by this volume.
 | ||||
|     float               color[4]; | ||||
|     // Color used to render this volume.
 | ||||
|  | @ -329,6 +327,9 @@ public: | |||
|     // Offset into qverts & tverts, or offsets into indices stored into an OpenGL name_index_buffer.
 | ||||
|     std::vector<size_t>         offsets; | ||||
| 
 | ||||
|     // Bounding box of this volume, in unscaled coordinates.
 | ||||
|     const BoundingBoxf3& bounding_box() const { return this->indexed_vertex_array.bounding_box(); } | ||||
| 
 | ||||
|     void set_render_color(float r, float g, float b, float a); | ||||
|     void set_render_color(const float* rgba, unsigned int size); | ||||
|     // Sets render color in dependence of current state
 | ||||
|  | @ -409,16 +410,17 @@ public: | |||
|     BoundingBoxf3        transformed_convex_hull_bounding_box(const Transform3d &trafo) const; | ||||
|     // caching variant
 | ||||
|     const BoundingBoxf3& transformed_convex_hull_bounding_box() const; | ||||
|     // convex hull
 | ||||
|     const TriangleMesh*  convex_hull() const { return m_convex_hull.get(); } | ||||
| 
 | ||||
|     bool                empty() const { return this->indexed_vertex_array.empty(); } | ||||
|     bool                indexed() const { return this->indexed_vertex_array.indexed(); } | ||||
| 
 | ||||
|     void                set_range(coordf_t low, coordf_t high); | ||||
|     void                render() const; | ||||
|     void                render_VBOs(int color_id, int detection_id, int worldmatrix_id) const; | ||||
|     void                render_legacy() const; | ||||
| 
 | ||||
|     void                finalize_geometry(bool use_VBOs) { this->indexed_vertex_array.finalize_geometry(use_VBOs); } | ||||
|     void                render() const; | ||||
|     void                render(int color_id, int detection_id, int worldmatrix_id) const; | ||||
| 
 | ||||
|     void                finalize_geometry() { this->indexed_vertex_array.finalize_geometry(); } | ||||
|     void                release_geometry() { this->indexed_vertex_array.release_geometry(); } | ||||
| 
 | ||||
|     void                set_bounding_boxes_as_dirty() { m_transformed_bounding_box_dirty = true; m_transformed_convex_hull_bounding_box_dirty = true; } | ||||
|  | @ -459,42 +461,38 @@ public: | |||
|     ~GLVolumeCollection() { clear(); }; | ||||
| 
 | ||||
|     std::vector<int> load_object( | ||||
|         const ModelObject       *model_object, | ||||
|         const ModelObject* model_object, | ||||
|         int                      obj_idx, | ||||
|         const std::vector<int>  &instance_idxs, | ||||
|         const std::string       &color_by, | ||||
|         bool                     use_VBOs); | ||||
|         const std::vector<int>& instance_idxs, | ||||
|         const std::string& color_by); | ||||
| 
 | ||||
|     int load_object_volume( | ||||
|         const ModelObject       *model_object, | ||||
|         const ModelObject* model_object, | ||||
|         int                      obj_idx, | ||||
|         int                      volume_idx, | ||||
|         int                      instance_idx, | ||||
|         const std::string       &color_by, | ||||
|         bool                     use_VBOs); | ||||
|         const std::string& color_by); | ||||
| 
 | ||||
|     // Load SLA auxiliary GLVolumes (for support trees or pad).
 | ||||
|     void load_object_auxiliary( | ||||
|         const SLAPrintObject           *print_object, | ||||
|         const SLAPrintObject* print_object, | ||||
|         int                             obj_idx, | ||||
|         // pairs of <instance_idx, print_instance_idx>
 | ||||
|         const std::vector<std::pair<size_t, size_t>> &instances, | ||||
|         const std::vector<std::pair<size_t, size_t>>& instances, | ||||
|         SLAPrintObjectStep              milestone, | ||||
|         // Timestamp of the last change of the milestone
 | ||||
|         size_t                          timestamp, | ||||
|         bool                            use_VBOs); | ||||
|         size_t                          timestamp); | ||||
| 
 | ||||
|     int load_wipe_tower_preview( | ||||
|         int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs, bool size_unknown, float brim_width); | ||||
|         int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width); | ||||
| 
 | ||||
|     // Render the volumes by OpenGL.
 | ||||
|     void render_VBOs(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function<bool(const GLVolume&)> filter_func = std::function<bool(const GLVolume&)>()) const; | ||||
|     void render_legacy(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function<bool(const GLVolume&)> filter_func = std::function<bool(const GLVolume&)>()) const; | ||||
|     void render(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function<bool(const GLVolume&)> filter_func = std::function<bool(const GLVolume&)>()) const; | ||||
| 
 | ||||
|     // Finalize the initialization of the geometry & indices,
 | ||||
|     // upload the geometry and indices to OpenGL VBO objects
 | ||||
|     // and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs.
 | ||||
|     void finalize_geometry(bool use_VBOs) { for (auto *v : volumes) v->finalize_geometry(use_VBOs); } | ||||
|     void finalize_geometry() { for (auto* v : volumes) v->finalize_geometry(); } | ||||
|     // Release the geometry data assigned to the volumes.
 | ||||
|     // If OpenGL VBOs were allocated, an OpenGL context has to be active to release them.
 | ||||
|     void release_geometry() { for (auto *v : volumes) v->release_geometry(); } | ||||
|  | @ -533,17 +531,16 @@ class GLModel | |||
| { | ||||
| protected: | ||||
|     GLVolume m_volume; | ||||
|     bool m_useVBOs; | ||||
|     std::string m_filename; | ||||
| 
 | ||||
| public: | ||||
|     GLModel(); | ||||
|     virtual ~GLModel(); | ||||
| 
 | ||||
|     bool init(bool useVBOs) { return on_init(useVBOs); } | ||||
|     bool init_from_file(const std::string& filename, bool useVBOs) { return on_init_from_file(filename, useVBOs); } | ||||
|     bool init() { return on_init(); } | ||||
|     bool init_from_file(const std::string& filename) { return on_init_from_file(filename); } | ||||
| 
 | ||||
|     void center_around(const Vec3d& center) { m_volume.set_volume_offset(center - m_volume.bounding_box.center()); } | ||||
|     void center_around(const Vec3d& center) { m_volume.set_volume_offset(center - m_volume.bounding_box().center()); } | ||||
|     void set_color(const float* color, unsigned int size); | ||||
| 
 | ||||
|     const Vec3d& get_offset() const; | ||||
|  | @ -554,7 +551,7 @@ public: | |||
|     void set_scale(const Vec3d& scale); | ||||
| 
 | ||||
|     const std::string& get_filename() const { return m_filename; } | ||||
|     const BoundingBoxf3& get_bounding_box() const { return m_volume.bounding_box; } | ||||
|     const BoundingBoxf3& get_bounding_box() const { return m_volume.bounding_box(); } | ||||
|     const BoundingBoxf3& get_transformed_bounding_box() const { return m_volume.transformed_bounding_box(); } | ||||
| 
 | ||||
|     void reset(); | ||||
|  | @ -562,18 +559,14 @@ public: | |||
|     void render() const;  | ||||
| 
 | ||||
| protected: | ||||
|     virtual bool on_init(bool useVBOs) { return false; } | ||||
|     virtual bool on_init_from_file(const std::string& filename, bool useVBOs) { return false; } | ||||
| 
 | ||||
| private: | ||||
|     void render_VBOs() const; | ||||
|     void render_legacy() const; | ||||
|     virtual bool on_init() { return false; } | ||||
|     virtual bool on_init_from_file(const std::string& filename) { return false; } | ||||
| }; | ||||
| 
 | ||||
| class GLArrow : public GLModel | ||||
| { | ||||
| protected: | ||||
|     virtual bool on_init(bool useVBOs); | ||||
|     virtual bool on_init(); | ||||
| }; | ||||
| 
 | ||||
| class GLCurvedArrow : public GLModel | ||||
|  | @ -584,13 +577,13 @@ public: | |||
|     explicit GLCurvedArrow(unsigned int resolution); | ||||
| 
 | ||||
| protected: | ||||
|     virtual bool on_init(bool useVBOs); | ||||
|     virtual bool on_init(); | ||||
| }; | ||||
| 
 | ||||
| class GLBed : public GLModel | ||||
| { | ||||
| protected: | ||||
|     virtual bool on_init_from_file(const std::string& filename, bool useVBOs); | ||||
|     virtual bool on_init_from_file(const std::string& filename); | ||||
| }; | ||||
| 
 | ||||
| class _3DScene | ||||
|  |  | |||
|  | @ -229,6 +229,33 @@ std::string AppConfig::get_last_dir() const | |||
|     return std::string(); | ||||
| } | ||||
| 
 | ||||
| std::vector<std::string> AppConfig::get_recent_projects() const | ||||
| { | ||||
|     std::vector<std::string> ret; | ||||
|     const auto it = m_storage.find("recent_projects"); | ||||
|     if (it != m_storage.end()) | ||||
|     { | ||||
|         for (const std::map<std::string, std::string>::value_type& item : it->second) | ||||
|         { | ||||
|             ret.push_back(item.second); | ||||
|         } | ||||
|     } | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| void AppConfig::set_recent_projects(const std::vector<std::string>& recent_projects) | ||||
| { | ||||
|     auto it = m_storage.find("recent_projects"); | ||||
|     if (it == m_storage.end()) | ||||
|         it = m_storage.insert(std::map<std::string, std::map<std::string, std::string>>::value_type("recent_projects", std::map<std::string, std::string>())).first; | ||||
| 
 | ||||
|     it->second.clear(); | ||||
|     for (unsigned int i = 0; i < (unsigned int)recent_projects.size(); ++i) | ||||
|     { | ||||
|         it->second[std::to_string(i + 1)] = recent_projects[i]; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void AppConfig::update_config_dir(const std::string &dir) | ||||
| { | ||||
|     this->set("recent", "config_directory", dir); | ||||
|  |  | |||
|  | @ -122,6 +122,9 @@ public: | |||
| 	// Does the config file exist?
 | ||||
| 	static bool 		exists(); | ||||
| 
 | ||||
|     std::vector<std::string> get_recent_projects() const; | ||||
|     void set_recent_projects(const std::vector<std::string>& recent_projects); | ||||
| 
 | ||||
| private: | ||||
| 	// Map of section, name -> value
 | ||||
| 	std::map<std::string, std::map<std::string, std::string>> 	m_storage; | ||||
|  |  | |||
|  | @ -53,15 +53,12 @@ void BedShapeDialog::on_dpi_changed(const wxRect &suggested_rect) | |||
| 
 | ||||
| void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) | ||||
| { | ||||
| //  on_change(nullptr);
 | ||||
| 
 | ||||
| 	auto box = new wxStaticBox(this, wxID_ANY, _(L("Shape"))); | ||||
| 	auto sbsizer = new wxStaticBoxSizer(box, wxVERTICAL); | ||||
|     auto sbsizer = new wxStaticBoxSizer(wxVERTICAL, this, _(L("Shape"))); | ||||
| 
 | ||||
| 	// shape options
 | ||||
|     m_shape_options_book = new wxChoicebook(this, wxID_ANY, wxDefaultPosition,  | ||||
|                            wxSize(25*wxGetApp().em_unit(), -1), wxCHB_TOP); | ||||
| 	sbsizer->Add(m_shape_options_book); | ||||
|     sbsizer->Add(m_shape_options_book); | ||||
| 
 | ||||
| 	auto optgroup = init_shape_options_page(_(L("Rectangular"))); | ||||
| 		ConfigOptionDef def; | ||||
|  | @ -92,13 +89,15 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) | |||
| 		Line line{ "", "" }; | ||||
| 		line.full_width = 1; | ||||
| 		line.widget = [this](wxWindow* parent) { | ||||
| 			auto btn = new wxButton(parent, wxID_ANY, _(L("Load shape from STL...")), wxDefaultPosition, wxDefaultSize); | ||||
| 			 | ||||
| 			auto sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
| 			sizer->Add(btn); | ||||
|             auto shape_btn = new wxButton(parent, wxID_ANY, _(L("Load shape from STL..."))); | ||||
|             wxSizer* shape_sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|             shape_sizer->Add(shape_btn, 1, wxEXPAND); | ||||
| 
 | ||||
| 			btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) | ||||
| 			{ | ||||
|             wxSizer* sizer = new wxBoxSizer(wxVERTICAL); | ||||
|             sizer->Add(shape_sizer, 1, wxEXPAND); | ||||
| 
 | ||||
|             shape_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) | ||||
|             { | ||||
| 				load_stl(); | ||||
| 			})); | ||||
| 
 | ||||
|  | @ -106,8 +105,8 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) | |||
| 		}; | ||||
| 		optgroup->append_line(line); | ||||
| 
 | ||||
| 	Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent e) | ||||
| 	{ | ||||
|     Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent& e) | ||||
|     { | ||||
| 		update_shape(); | ||||
| 	})); | ||||
| 
 | ||||
|  | @ -117,8 +116,8 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) | |||
| 
 | ||||
| 	// main sizer
 | ||||
| 	auto top_sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
| 	top_sizer->Add(sbsizer, 0, wxEXPAND | wxLeft | wxTOP | wxBOTTOM, 10); | ||||
| 	if (m_canvas) | ||||
|     top_sizer->Add(sbsizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 10); | ||||
|     if (m_canvas) | ||||
| 		top_sizer->Add(m_canvas, 1, wxEXPAND | wxALL, 10) ; | ||||
| 
 | ||||
| 	SetSizerAndFit(top_sizer); | ||||
|  | @ -135,8 +134,7 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) | |||
| // Create a panel for a rectangular / circular / custom bed shape.
 | ||||
| ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(const wxString& title) | ||||
| { | ||||
| 
 | ||||
| 	auto panel = new wxPanel(m_shape_options_book); | ||||
|     auto panel = new wxPanel(m_shape_options_book); | ||||
| 	ConfigOptionsGroupShp optgroup; | ||||
| 	optgroup = std::make_shared<ConfigOptionsGroup>(panel, _(L("Settings"))); | ||||
| 
 | ||||
|  |  | |||
|  | @ -113,11 +113,19 @@ wxString Field::get_tooltip_text(const wxString& default_string) | |||
| 	wxString tooltip_text(""); | ||||
| 	wxString tooltip = _(m_opt.tooltip); | ||||
|     edit_tooltip(tooltip); | ||||
| 
 | ||||
|     std::string opt_id = m_opt_id; | ||||
|     auto hash_pos = opt_id.find("#"); | ||||
|     if (hash_pos != std::string::npos) { | ||||
|         opt_id.replace(hash_pos, 1,"["); | ||||
|         opt_id += "]"; | ||||
|     } | ||||
| 
 | ||||
| 	if (tooltip.length() > 0) | ||||
|         tooltip_text = tooltip + "\n" + _(L("default value")) + "\t: " + | ||||
|         (boost::iends_with(m_opt_id, "_gcode") ? "\n" : "") + default_string + | ||||
|         (boost::iends_with(m_opt_id, "_gcode") ? "" : "\n") +  | ||||
|         _(L("parameter name")) + "\t: " + m_opt_id; | ||||
|         (boost::iends_with(opt_id, "_gcode") ? "\n" : "") + default_string + | ||||
|         (boost::iends_with(opt_id, "_gcode") ? "" : "\n") +  | ||||
|         _(L("parameter name")) + "\t: " + opt_id; | ||||
| 
 | ||||
| 	return tooltip_text; | ||||
| } | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -12,6 +12,7 @@ | |||
| #include "Camera.hpp" | ||||
| #include "Selection.hpp" | ||||
| #include "Gizmos/GLGizmosManager.hpp" | ||||
| #include "GUI_ObjectLayers.hpp" | ||||
| 
 | ||||
| #include <float.h> | ||||
| 
 | ||||
|  | @ -126,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 | ||||
| { | ||||
|  | @ -435,7 +438,8 @@ private: | |||
|     Shader m_shader; | ||||
|     Mouse m_mouse; | ||||
|     mutable GLGizmosManager m_gizmos; | ||||
|     mutable GLToolbar m_toolbar; | ||||
|     mutable GLToolbar m_main_toolbar; | ||||
|     mutable GLToolbar m_undoredo_toolbar; | ||||
|     ClippingPlane m_clipping_planes[2]; | ||||
|     mutable ClippingPlane m_camera_clipping_plane; | ||||
|     bool m_use_clipping_planes; | ||||
|  | @ -452,7 +456,6 @@ private: | |||
|     // Screen is only refreshed from the OnIdle handler if it is dirty.
 | ||||
|     bool m_dirty; | ||||
|     bool m_initialized; | ||||
|     bool m_use_VBOs; | ||||
|     bool m_apply_zoom_to_volumes_filter; | ||||
|     mutable std::vector<int> m_hover_volume_idxs; | ||||
|     bool m_warning_texture_enabled; | ||||
|  | @ -485,6 +488,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(); | ||||
|  | @ -494,7 +499,7 @@ public: | |||
|     wxGLCanvas* get_wxglcanvas() { return m_canvas; } | ||||
| 	const wxGLCanvas* get_wxglcanvas() const { return m_canvas; } | ||||
| 
 | ||||
|     bool init(bool useVBOs); | ||||
|     bool init(); | ||||
|     void post_event(wxEvent &&event); | ||||
| 
 | ||||
|     void set_as_dirty(); | ||||
|  | @ -513,6 +518,9 @@ public: | |||
|     const Selection& get_selection() const { return m_selection; } | ||||
|     Selection& get_selection() { return m_selection; } | ||||
| 
 | ||||
|     const GLGizmosManager& get_gizmos_manager() const { return m_gizmos; } | ||||
|     GLGizmosManager& get_gizmos_manager() { return m_gizmos; } | ||||
| 
 | ||||
|     void bed_shape_changed(); | ||||
| 
 | ||||
|     void set_clipping_plane(unsigned int id, const ClippingPlane& plane) | ||||
|  | @ -544,7 +552,8 @@ public: | |||
|     void enable_moving(bool enable); | ||||
|     void enable_gizmos(bool enable); | ||||
|     void enable_selection(bool enable); | ||||
|     void enable_toolbar(bool enable); | ||||
|     void enable_main_toolbar(bool enable); | ||||
|     void enable_undoredo_toolbar(bool enable); | ||||
|     void enable_dynamic_background(bool enable); | ||||
|     void allow_multisample(bool allow); | ||||
| 
 | ||||
|  | @ -596,11 +605,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); | ||||
| 
 | ||||
|  | @ -608,6 +618,7 @@ public: | |||
|     void reset_all_gizmos() { m_gizmos.reset_all_states(); } | ||||
| 
 | ||||
|     void handle_sidebar_focus_event(const std::string& opt_key, bool focus_on); | ||||
|     void handle_layers_data_focus_event(const t_layer_height_range range, const EditorType type); | ||||
| 
 | ||||
|     void update_ui_from_settings(); | ||||
| 
 | ||||
|  | @ -635,10 +646,18 @@ public: | |||
|     void start_keeping_dirty() { m_keep_dirty = true; } | ||||
|     void stop_keeping_dirty() { m_keep_dirty = false; } | ||||
| 
 | ||||
|     unsigned int get_main_toolbar_item_id(const std::string& name) const { return m_main_toolbar.get_item_id(name); } | ||||
|     void force_main_toolbar_left_action(unsigned int item_id) { m_main_toolbar.force_left_action(item_id, *this); } | ||||
|     void force_main_toolbar_right_action(unsigned int item_id) { m_main_toolbar.force_right_action(item_id, *this); } | ||||
|     void get_undoredo_toolbar_additional_tooltip(unsigned int item_id, std::string& text) { return m_undoredo_toolbar.get_additional_tooltip(item_id, text); } | ||||
|     void set_undoredo_toolbar_additional_tooltip(unsigned int item_id, const std::string& text) { m_undoredo_toolbar.set_additional_tooltip(item_id, text); } | ||||
| 
 | ||||
| private: | ||||
|     bool _is_shown_on_screen() const; | ||||
| 
 | ||||
|     bool _init_toolbar(); | ||||
|     bool _init_toolbars(); | ||||
|     bool _init_main_toolbar(); | ||||
|     bool _init_undoredo_toolbar(); | ||||
| 
 | ||||
|     bool _set_current(); | ||||
|     void _resize(unsigned int w, unsigned int h); | ||||
|  | @ -665,13 +684,15 @@ private: | |||
|     void _render_volumes_for_picking() const; | ||||
|     void _render_current_gizmo() const; | ||||
|     void _render_gizmos_overlay() const; | ||||
|     void _render_toolbar() const; | ||||
|     void _render_main_toolbar() const; | ||||
|     void _render_undoredo_toolbar() const; | ||||
|     void _render_view_toolbar() const; | ||||
| #if ENABLE_SHOW_CAMERA_TARGET | ||||
|     void _render_camera_target() const; | ||||
| #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; | ||||
| 
 | ||||
|  | @ -711,8 +732,6 @@ private: | |||
|     void _load_gcode_unretractions(const GCodePreviewData& preview_data); | ||||
|     // generates objects and wipe tower geometry
 | ||||
|     void _load_fff_shells(); | ||||
|     // generates objects geometry for sla
 | ||||
|     void _load_sla_shells(); | ||||
|     // sets gcode geometry visibility according to user selection
 | ||||
|     void _update_gcode_volumes_visibility(const GCodePreviewData& preview_data); | ||||
|     void _update_toolpath_volumes_outside_state(); | ||||
|  | @ -727,13 +746,11 @@ private: | |||
| 
 | ||||
|     bool _is_any_volume_outside() const; | ||||
| 
 | ||||
| #if !ENABLE_SVG_ICONS | ||||
|     void _resize_toolbars() const; | ||||
| #endif // !ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     // 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: | ||||
|  |  | |||
|  | @ -192,7 +192,6 @@ GLCanvas3DManager::GLInfo GLCanvas3DManager::s_gl_info; | |||
| GLCanvas3DManager::GLCanvas3DManager() | ||||
|     : m_context(nullptr) | ||||
|     , m_gl_initialized(false) | ||||
|     , m_use_VBOs(false) | ||||
| { | ||||
| } | ||||
| 
 | ||||
|  | @ -266,8 +265,6 @@ void GLCanvas3DManager::init_gl() | |||
|     if (!m_gl_initialized) | ||||
|     { | ||||
|         glewInit(); | ||||
|         const AppConfig* config = GUI::get_app_config(); | ||||
|         m_use_VBOs = s_gl_info.is_version_greater_or_equal_to(2, 0); | ||||
|         m_gl_initialized = true; | ||||
|         if (GLEW_EXT_texture_compression_s3tc) | ||||
|             s_compressed_textures_supported = true; | ||||
|  | @ -323,7 +320,7 @@ bool GLCanvas3DManager::init(GLCanvas3D& canvas) | |||
|     if (!m_gl_initialized) | ||||
|         init_gl(); | ||||
| 
 | ||||
|     return canvas.init(m_use_VBOs); | ||||
|     return canvas.init(); | ||||
| } | ||||
| 
 | ||||
| void GLCanvas3DManager::detect_multisample(int* attribList) | ||||
|  |  | |||
|  | @ -75,7 +75,6 @@ private: | |||
|     wxGLContext* m_context; | ||||
|     static GLInfo s_gl_info; | ||||
|     bool m_gl_initialized; | ||||
|     bool m_use_VBOs; | ||||
|     static EMultisampleState s_multisample; | ||||
|     static bool s_compressed_textures_supported; | ||||
| 
 | ||||
|  |  | |||
|  | @ -34,20 +34,25 @@ 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("") | ||||
| #if ENABLE_SVG_ICONS | ||||
|     , icon_filename("") | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     , tooltip("") | ||||
|     , additional_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) | ||||
| { | ||||
| } | ||||
| 
 | ||||
|  | @ -55,6 +60,7 @@ GLToolbarItem::GLToolbarItem(GLToolbarItem::EType type, const GLToolbarItem::Dat | |||
|     : m_type(type) | ||||
|     , m_state(Normal) | ||||
|     , m_data(data) | ||||
|     , m_last_action_type(Undefined) | ||||
| { | ||||
| } | ||||
| 
 | ||||
|  | @ -70,7 +76,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; | ||||
|  | @ -81,6 +87,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 | ||||
|  | @ -105,14 +119,6 @@ GLTexture::Quad_UVs GLToolbarItem::get_uvs(unsigned int tex_width, unsigned int | |||
|     return uvs; | ||||
| } | ||||
| 
 | ||||
| #if !ENABLE_SVG_ICONS | ||||
| ItemsIconsTexture::Metadata::Metadata() | ||||
|     : filename("") | ||||
|     , icon_size(0) | ||||
| { | ||||
| } | ||||
| #endif // !ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
| BackgroundTexture::Metadata::Metadata() | ||||
|     : filename("") | ||||
|     , left(0) | ||||
|  | @ -122,44 +128,32 @@ BackgroundTexture::Metadata::Metadata() | |||
| { | ||||
| } | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
| const float GLToolbar::Default_Icons_Size = 40.0f; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
| GLToolbar::Layout::Layout() | ||||
|     : type(Horizontal) | ||||
|     , orientation(Center) | ||||
|     , horizontal_orientation(HO_Center) | ||||
|     , vertical_orientation(VO_Center) | ||||
|     , top(0.0f) | ||||
|     , left(0.0f) | ||||
|     , border(0.0f) | ||||
|     , separator_size(0.0f) | ||||
|     , gap_size(0.0f) | ||||
| #if ENABLE_SVG_ICONS | ||||
|     , icons_size(Default_Icons_Size) | ||||
|     , scale(1.0f) | ||||
| #else | ||||
|     , icons_scale(1.0f) | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     , width(0.0f) | ||||
|     , height(0.0f) | ||||
|     , dirty(true) | ||||
| { | ||||
| } | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
| GLToolbar::GLToolbar(GLToolbar::EType type, const std::string& name) | ||||
| #else | ||||
| GLToolbar::GLToolbar(GLToolbar::EType type) | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     : m_type(type) | ||||
| #if ENABLE_SVG_ICONS | ||||
|     , m_name(name) | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     , m_enabled(false) | ||||
| #if ENABLE_SVG_ICONS | ||||
|     , m_icons_texture_dirty(true) | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     , m_tooltip("") | ||||
|     , m_pressed_toggable_id(-1) | ||||
| { | ||||
| } | ||||
| 
 | ||||
|  | @ -171,27 +165,13 @@ GLToolbar::~GLToolbar() | |||
|     } | ||||
| } | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
| bool GLToolbar::init(const BackgroundTexture::Metadata& background_texture) | ||||
| #else | ||||
| bool GLToolbar::init(const ItemsIconsTexture::Metadata& icons_texture, const BackgroundTexture::Metadata& background_texture) | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| { | ||||
| #if ENABLE_SVG_ICONS | ||||
|     if (m_background_texture.texture.get_id() != 0) | ||||
|         return true; | ||||
| 
 | ||||
|     std::string path = resources_dir() + "/icons/"; | ||||
|     bool res = false; | ||||
| #else | ||||
|     if (m_icons_texture.texture.get_id() != 0) | ||||
|         return true; | ||||
| 
 | ||||
|     std::string path = resources_dir() + "/icons/"; | ||||
|     bool res = !icons_texture.filename.empty() && m_icons_texture.texture.load_from_file(path + icons_texture.filename, false); | ||||
|     if (res) | ||||
|         m_icons_texture.metadata = icons_texture; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     if (!background_texture.filename.empty()) | ||||
|         res = m_background_texture.texture.load_from_file(path + background_texture.filename, false, true); | ||||
|  | @ -213,16 +193,6 @@ void GLToolbar::set_layout_type(GLToolbar::Layout::EType type) | |||
|     m_layout.dirty = true; | ||||
| } | ||||
| 
 | ||||
| GLToolbar::Layout::EOrientation GLToolbar::get_layout_orientation() const | ||||
| { | ||||
|     return m_layout.orientation; | ||||
| } | ||||
| 
 | ||||
| void GLToolbar::set_layout_orientation(GLToolbar::Layout::EOrientation orientation) | ||||
| { | ||||
|     m_layout.orientation = orientation; | ||||
| } | ||||
| 
 | ||||
| void GLToolbar::set_position(float top, float left) | ||||
| { | ||||
|     m_layout.top = top; | ||||
|  | @ -247,7 +217,6 @@ void GLToolbar::set_gap_size(float size) | |||
|     m_layout.dirty = true; | ||||
| } | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
| void GLToolbar::set_icons_size(float size) | ||||
| { | ||||
|     if (m_layout.icons_size != size) | ||||
|  | @ -267,13 +236,6 @@ void GLToolbar::set_scale(float scale) | |||
|         m_icons_texture_dirty = true; | ||||
|     } | ||||
| } | ||||
| #else | ||||
| void GLToolbar::set_icons_scale(float scale) | ||||
| { | ||||
|     m_layout.icons_scale = scale; | ||||
|     m_layout.dirty = true; | ||||
| } | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
| bool GLToolbar::is_enabled() const | ||||
| { | ||||
|  | @ -341,7 +303,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(); | ||||
|  | @ -352,7 +314,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(); | ||||
|  | @ -363,7 +325,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(); | ||||
|  | @ -372,11 +334,71 @@ 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); | ||||
| } | ||||
| 
 | ||||
| void GLToolbar::get_additional_tooltip(unsigned int item_id, std::string& text) | ||||
| { | ||||
|     if (item_id < (unsigned int)m_items.size()) | ||||
|     { | ||||
|         GLToolbarItem* item = m_items[item_id]; | ||||
|         if (item != nullptr) | ||||
|         { | ||||
|             text = item->get_additional_tooltip(); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     text = L(""); | ||||
| } | ||||
| 
 | ||||
| void GLToolbar::set_additional_tooltip(unsigned int item_id, const std::string& text) | ||||
| { | ||||
|     if (item_id < (unsigned int)m_items.size()) | ||||
|     { | ||||
|         GLToolbarItem* item = m_items[item_id]; | ||||
|         if (item != nullptr) | ||||
|             item->set_additional_tooltip(text); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 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; | ||||
| } | ||||
| 
 | ||||
|  | @ -385,10 +407,8 @@ void GLToolbar::render(const GLCanvas3D& parent) const | |||
|     if (!m_enabled || m_items.empty()) | ||||
|         return; | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     if (m_icons_texture_dirty) | ||||
|         generate_icons_texture(); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     switch (m_layout.type) | ||||
|     { | ||||
|  | @ -400,6 +420,9 @@ void GLToolbar::render(const GLCanvas3D& parent) const | |||
| 
 | ||||
| bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) | ||||
| { | ||||
|     if (!m_enabled) | ||||
|         return false; | ||||
| 
 | ||||
|     Vec2d mouse_pos((double)evt.GetX(), (double)evt.GetY()); | ||||
|     bool processed = false; | ||||
| 
 | ||||
|  | @ -430,7 +453,7 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) | |||
|     if (item_id == -1) | ||||
|     { | ||||
|         // mouse is outside the toolbar
 | ||||
|         m_tooltip = ""; | ||||
|         m_tooltip = L(""); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|  | @ -440,10 +463,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()) | ||||
|  | @ -455,6 +479,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; | ||||
|  | @ -492,20 +523,12 @@ float GLToolbar::get_width_horizontal() const | |||
| 
 | ||||
| float GLToolbar::get_width_vertical() const | ||||
| { | ||||
| #if ENABLE_SVG_ICONS | ||||
|     return (2.0f * m_layout.border + m_layout.icons_size) * m_layout.scale; | ||||
| #else | ||||
|     return 2.0f * m_layout.border * m_layout.icons_scale + m_icons_texture.metadata.icon_size * m_layout.icons_scale; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| } | ||||
| 
 | ||||
| float GLToolbar::get_height_horizontal() const | ||||
| { | ||||
| #if ENABLE_SVG_ICONS | ||||
|     return (2.0f * m_layout.border + m_layout.icons_size) * m_layout.scale; | ||||
| #else | ||||
|     return 2.0f * m_layout.border * m_layout.icons_scale + m_icons_texture.metadata.icon_size * m_layout.icons_scale; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| } | ||||
| 
 | ||||
| float GLToolbar::get_height_vertical() const | ||||
|  | @ -515,7 +538,6 @@ float GLToolbar::get_height_vertical() const | |||
| 
 | ||||
| float GLToolbar::get_main_size() const | ||||
| { | ||||
| #if ENABLE_SVG_ICONS | ||||
|     float size = 2.0f * m_layout.border; | ||||
|     for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i) | ||||
|     { | ||||
|  | @ -531,59 +553,64 @@ float GLToolbar::get_main_size() const | |||
|     if (m_items.size() > 1) | ||||
|         size += ((float)m_items.size() - 1.0f) * m_layout.gap_size; | ||||
| 
 | ||||
|     size *= m_layout.scale; | ||||
| #else | ||||
|     float size = 2.0f * m_layout.border * m_layout.icons_scale; | ||||
|     for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i) | ||||
|     { | ||||
|         if (!m_items[i]->is_visible()) | ||||
|             continue; | ||||
| 
 | ||||
|         if (m_items[i]->is_separator()) | ||||
|             size += m_layout.separator_size * m_layout.icons_scale; | ||||
|         else | ||||
|             size += (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale; | ||||
|     } | ||||
| 
 | ||||
|     if (m_items.size() > 1) | ||||
|         size += ((float)m_items.size() - 1.0f) * m_layout.gap_size * m_layout.icons_scale; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     return size; | ||||
|     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(); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | @ -593,7 +620,7 @@ void GLToolbar::do_action(unsigned int item_id, GLCanvas3D& parent) | |||
| std::string GLToolbar::update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent) | ||||
| { | ||||
|     if (!m_enabled) | ||||
|         return ""; | ||||
|         return L(""); | ||||
| 
 | ||||
|     switch (m_layout.type) | ||||
|     { | ||||
|  | @ -609,20 +636,12 @@ std::string GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLC | |||
| 
 | ||||
|     float zoom = (float)parent.get_camera().get_zoom(); | ||||
|     float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     float factor = m_layout.scale * inv_zoom; | ||||
| #else | ||||
|     float factor = m_layout.icons_scale * inv_zoom; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     Size cnv_size = parent.get_canvas_size(); | ||||
|     Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     float scaled_icons_size = m_layout.icons_size * factor; | ||||
| #else | ||||
|     float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     float scaled_separator_size = m_layout.separator_size * factor; | ||||
|     float scaled_gap_size = m_layout.gap_size * factor; | ||||
|     float scaled_border = m_layout.border * factor; | ||||
|  | @ -650,7 +669,15 @@ std::string GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLC | |||
|             GLToolbarItem::EState state = item->get_state(); | ||||
|             bool inside = (left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top); | ||||
|             if (inside) | ||||
|             { | ||||
|                 tooltip = item->get_tooltip(); | ||||
|                 if (!item->is_pressed()) | ||||
|                 { | ||||
|                     const std::string& additional_tooltip = item->get_additional_tooltip(); | ||||
|                     if (!additional_tooltip.empty()) | ||||
|                         tooltip += L("\n") + additional_tooltip; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             switch (state) | ||||
|             { | ||||
|  | @ -714,20 +741,12 @@ std::string GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCan | |||
| 
 | ||||
|     float zoom = (float)parent.get_camera().get_zoom(); | ||||
|     float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     float factor = m_layout.scale * inv_zoom; | ||||
| #else | ||||
|     float factor = m_layout.icons_scale * inv_zoom; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     Size cnv_size = parent.get_canvas_size(); | ||||
|     Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     float scaled_icons_size = m_layout.icons_size * factor; | ||||
| #else | ||||
|     float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     float scaled_separator_size = m_layout.separator_size * factor; | ||||
|     float scaled_gap_size = m_layout.gap_size * factor; | ||||
|     float scaled_border = m_layout.border * factor; | ||||
|  | @ -754,7 +773,15 @@ std::string GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCan | |||
|             GLToolbarItem::EState state = item->get_state(); | ||||
|             bool inside = (left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top); | ||||
|             if (inside) | ||||
|             { | ||||
|                 tooltip = item->get_tooltip(); | ||||
|                 if (!item->is_pressed()) | ||||
|                 { | ||||
|                     const std::string& additional_tooltip = item->get_additional_tooltip(); | ||||
|                     if (!additional_tooltip.empty()) | ||||
|                         tooltip += L("\n") + additional_tooltip; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             switch (state) | ||||
|             { | ||||
|  | @ -831,20 +858,12 @@ int GLToolbar::contains_mouse_horizontal(const Vec2d& mouse_pos, const GLCanvas3 | |||
| 
 | ||||
|     float zoom = (float)parent.get_camera().get_zoom(); | ||||
|     float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     float factor = m_layout.scale * inv_zoom; | ||||
| #else | ||||
|     float factor = m_layout.icons_scale * inv_zoom; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     Size cnv_size = parent.get_canvas_size(); | ||||
|     Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     float scaled_icons_size = m_layout.icons_size * factor; | ||||
| #else | ||||
|     float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     float scaled_separator_size = m_layout.separator_size * factor; | ||||
|     float scaled_gap_size = m_layout.gap_size * factor; | ||||
|     float scaled_border = m_layout.border * factor; | ||||
|  | @ -914,20 +933,12 @@ int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& | |||
| 
 | ||||
|     float zoom = (float)parent.get_camera().get_zoom(); | ||||
|     float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     float factor = m_layout.scale * inv_zoom; | ||||
| #else | ||||
|     float factor = m_layout.icons_scale * inv_zoom; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     Size cnv_size = parent.get_canvas_size(); | ||||
|     Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     float scaled_icons_size = m_layout.icons_size * factor; | ||||
| #else | ||||
|     float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     float scaled_separator_size = m_layout.separator_size * factor; | ||||
|     float scaled_gap_size = m_layout.gap_size * factor; | ||||
|     float scaled_border = m_layout.border * factor; | ||||
|  | @ -991,36 +1002,95 @@ int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& | |||
|     return -1; | ||||
| } | ||||
| 
 | ||||
| void GLToolbar::render_background(float left, float top, float right, float bottom, float border) const | ||||
| { | ||||
|     unsigned int tex_id = m_background_texture.texture.get_id(); | ||||
|     float tex_width = (float)m_background_texture.texture.get_width(); | ||||
|     float tex_height = (float)m_background_texture.texture.get_height(); | ||||
|     if ((tex_id != 0) && (tex_width > 0) && (tex_height > 0)) | ||||
|     { | ||||
|         float inv_tex_width = (tex_width != 0.0f) ? 1.0f / tex_width : 0.0f; | ||||
|         float inv_tex_height = (tex_height != 0.0f) ? 1.0f / tex_height : 0.0f; | ||||
| 
 | ||||
|         float internal_left = left + border; | ||||
|         float internal_right = right - border; | ||||
|         float internal_top = top - border; | ||||
|         float internal_bottom = bottom + border; | ||||
| 
 | ||||
|         float left_uv = 0.0f; | ||||
|         float right_uv = 1.0f; | ||||
|         float top_uv = 1.0f; | ||||
|         float bottom_uv = 0.0f; | ||||
| 
 | ||||
|         float internal_left_uv = (float)m_background_texture.metadata.left * inv_tex_width; | ||||
|         float internal_right_uv = 1.0f - (float)m_background_texture.metadata.right * inv_tex_width; | ||||
|         float internal_top_uv = 1.0f - (float)m_background_texture.metadata.top * inv_tex_height; | ||||
|         float internal_bottom_uv = (float)m_background_texture.metadata.bottom * inv_tex_height; | ||||
| 
 | ||||
|         // top-left corner
 | ||||
|         if ((m_layout.horizontal_orientation == Layout::HO_Left) || (m_layout.vertical_orientation == Layout::VO_Top)) | ||||
|             GLTexture::render_sub_texture(tex_id, left, internal_left, internal_top, top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); | ||||
|         else | ||||
|             GLTexture::render_sub_texture(tex_id, left, internal_left, internal_top, top, { { left_uv, internal_top_uv }, { internal_left_uv, internal_top_uv }, { internal_left_uv, top_uv }, { left_uv, top_uv } }); | ||||
| 
 | ||||
|         // top edge
 | ||||
|         if (m_layout.vertical_orientation == Layout::VO_Top) | ||||
|             GLTexture::render_sub_texture(tex_id, internal_left, internal_right, internal_top, top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); | ||||
|         else | ||||
|             GLTexture::render_sub_texture(tex_id, internal_left, internal_right, internal_top, top, { { internal_left_uv, internal_top_uv }, { internal_right_uv, internal_top_uv }, { internal_right_uv, top_uv }, { internal_left_uv, top_uv } }); | ||||
| 
 | ||||
|         // top-right corner
 | ||||
|         if ((m_layout.horizontal_orientation == Layout::HO_Right) || (m_layout.vertical_orientation == Layout::VO_Top)) | ||||
|             GLTexture::render_sub_texture(tex_id, internal_right, right, internal_top, top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); | ||||
|         else | ||||
|             GLTexture::render_sub_texture(tex_id, internal_right, right, internal_top, top, { { internal_right_uv, internal_top_uv }, { right_uv, internal_top_uv }, { right_uv, top_uv }, { internal_right_uv, top_uv } }); | ||||
| 
 | ||||
|         // center-left edge
 | ||||
|         if (m_layout.horizontal_orientation == Layout::HO_Left) | ||||
|             GLTexture::render_sub_texture(tex_id, left, internal_left, internal_bottom, internal_top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); | ||||
|         else | ||||
|             GLTexture::render_sub_texture(tex_id, left, internal_left, internal_bottom, internal_top, { { left_uv, internal_bottom_uv }, { internal_left_uv, internal_bottom_uv }, { internal_left_uv, internal_top_uv }, { left_uv, internal_top_uv } }); | ||||
| 
 | ||||
|         // center
 | ||||
|         GLTexture::render_sub_texture(tex_id, internal_left, internal_right, internal_bottom, internal_top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); | ||||
| 
 | ||||
|         // center-right edge
 | ||||
|         if (m_layout.horizontal_orientation == Layout::HO_Right) | ||||
|             GLTexture::render_sub_texture(tex_id, internal_right, right, internal_bottom, internal_top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); | ||||
|         else | ||||
|             GLTexture::render_sub_texture(tex_id, internal_right, right, internal_bottom, internal_top, { { internal_right_uv, internal_bottom_uv }, { right_uv, internal_bottom_uv }, { right_uv, internal_top_uv }, { internal_right_uv, internal_top_uv } }); | ||||
| 
 | ||||
|         // bottom-left corner
 | ||||
|         if ((m_layout.horizontal_orientation == Layout::HO_Left) || (m_layout.vertical_orientation == Layout::VO_Bottom)) | ||||
|             GLTexture::render_sub_texture(tex_id, left, internal_left, bottom, internal_bottom, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); | ||||
|         else | ||||
|             GLTexture::render_sub_texture(tex_id, left, internal_left, bottom, internal_bottom, { { left_uv, bottom_uv }, { internal_left_uv, bottom_uv }, { internal_left_uv, internal_bottom_uv }, { left_uv, internal_bottom_uv } }); | ||||
| 
 | ||||
|         // bottom edge
 | ||||
|         if (m_layout.vertical_orientation == Layout::VO_Bottom) | ||||
|             GLTexture::render_sub_texture(tex_id, internal_left, internal_right, bottom, internal_bottom, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); | ||||
|         else | ||||
|             GLTexture::render_sub_texture(tex_id, internal_left, internal_right, bottom, internal_bottom, { { internal_left_uv, bottom_uv }, { internal_right_uv, bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_left_uv, internal_bottom_uv } }); | ||||
| 
 | ||||
|         // bottom-right corner
 | ||||
|         if ((m_layout.horizontal_orientation == Layout::HO_Right) || (m_layout.vertical_orientation == Layout::VO_Bottom)) | ||||
|             GLTexture::render_sub_texture(tex_id, internal_right, right, bottom, internal_bottom, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); | ||||
|         else | ||||
|             GLTexture::render_sub_texture(tex_id, internal_right, right, bottom, internal_bottom, { { internal_right_uv, bottom_uv }, { right_uv, bottom_uv }, { right_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv } }); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GLToolbar::render_horizontal(const GLCanvas3D& parent) const | ||||
| { | ||||
| #if ENABLE_SVG_ICONS | ||||
|     unsigned int tex_id = m_icons_texture.get_id(); | ||||
|     int tex_width = m_icons_texture.get_width(); | ||||
|     int tex_height = m_icons_texture.get_height(); | ||||
| #else | ||||
|     unsigned int tex_id = m_icons_texture.texture.get_id(); | ||||
|     int tex_width = m_icons_texture.texture.get_width(); | ||||
|     int tex_height = m_icons_texture.texture.get_height(); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
| #if !ENABLE_SVG_ICONS | ||||
|     if ((tex_id == 0) || (tex_width <= 0) || (tex_height <= 0)) | ||||
|         return; | ||||
| #endif // !ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     float zoom = (float)parent.get_camera().get_zoom(); | ||||
|     float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     float factor = inv_zoom * m_layout.scale; | ||||
| #else | ||||
|     float factor = inv_zoom * m_layout.icons_scale; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     float scaled_icons_size = m_layout.icons_size * factor; | ||||
| #else | ||||
|     float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     float scaled_separator_size = m_layout.separator_size * factor; | ||||
|     float scaled_gap_size = m_layout.gap_size * factor; | ||||
|     float scaled_border = m_layout.border * factor; | ||||
|  | @ -1035,96 +1105,13 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent) const | |||
|     float right = left + scaled_width; | ||||
|     float bottom = top - scaled_height; | ||||
| 
 | ||||
|     // renders background
 | ||||
|     unsigned int bg_tex_id = m_background_texture.texture.get_id(); | ||||
|     float bg_tex_width = (float)m_background_texture.texture.get_width(); | ||||
|     float bg_tex_height = (float)m_background_texture.texture.get_height(); | ||||
|     if ((bg_tex_id != 0) && (bg_tex_width > 0) && (bg_tex_height > 0)) | ||||
|     { | ||||
|         float inv_bg_tex_width = (bg_tex_width != 0.0f) ? 1.0f / bg_tex_width : 0.0f; | ||||
|         float inv_bg_tex_height = (bg_tex_height != 0.0f) ? 1.0f / bg_tex_height : 0.0f; | ||||
| 
 | ||||
|         float bg_uv_left = 0.0f; | ||||
|         float bg_uv_right = 1.0f; | ||||
|         float bg_uv_top = 1.0f; | ||||
|         float bg_uv_bottom = 0.0f; | ||||
| 
 | ||||
|         float bg_left = left; | ||||
|         float bg_right = right; | ||||
|         float bg_top = top; | ||||
|         float bg_bottom = bottom; | ||||
|         float bg_width = right - left; | ||||
|         float bg_height = top - bottom; | ||||
|         float bg_min_size = std::min(bg_width, bg_height); | ||||
| 
 | ||||
|         float bg_uv_i_left = (float)m_background_texture.metadata.left * inv_bg_tex_width; | ||||
|         float bg_uv_i_right = 1.0f - (float)m_background_texture.metadata.right * inv_bg_tex_width; | ||||
|         float bg_uv_i_top = 1.0f - (float)m_background_texture.metadata.top * inv_bg_tex_height; | ||||
|         float bg_uv_i_bottom = (float)m_background_texture.metadata.bottom * inv_bg_tex_height; | ||||
| 
 | ||||
|         float bg_i_left = bg_left + scaled_border; | ||||
|         float bg_i_right = bg_right - scaled_border; | ||||
|         float bg_i_top = bg_top - scaled_border; | ||||
|         float bg_i_bottom = bg_bottom + scaled_border; | ||||
| 
 | ||||
|         switch (m_layout.orientation) | ||||
|         { | ||||
|         case Layout::Top: | ||||
|         { | ||||
|             bg_uv_top = bg_uv_i_top; | ||||
|             bg_i_top = bg_top; | ||||
|             break; | ||||
|         } | ||||
|         case Layout::Bottom: | ||||
|         { | ||||
|             bg_uv_bottom = bg_uv_i_bottom; | ||||
|             bg_i_bottom = bg_bottom; | ||||
|             break; | ||||
|         } | ||||
|         case Layout::Center: | ||||
|         { | ||||
|             break; | ||||
|         } | ||||
|         }; | ||||
| 
 | ||||
|         if ((m_layout.border > 0) && (bg_uv_top != bg_uv_i_top)) | ||||
|         { | ||||
|             if (bg_uv_left != bg_uv_i_left) | ||||
|                 GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_i_top, bg_top, { { bg_uv_left, bg_uv_i_top }, { bg_uv_i_left, bg_uv_i_top }, { bg_uv_i_left, bg_uv_top }, { bg_uv_left, bg_uv_top } }); | ||||
| 
 | ||||
|             GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_i_top, bg_top, { { bg_uv_i_left, bg_uv_i_top }, { bg_uv_i_right, bg_uv_i_top }, { bg_uv_i_right, bg_uv_top }, { bg_uv_i_left, bg_uv_top } }); | ||||
| 
 | ||||
|             if (bg_uv_right != bg_uv_i_right) | ||||
|                 GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_i_top, bg_top, { { bg_uv_i_right, bg_uv_i_top }, { bg_uv_right, bg_uv_i_top }, { bg_uv_right, bg_uv_top }, { bg_uv_i_right, bg_uv_top } }); | ||||
|         } | ||||
| 
 | ||||
|         if ((m_layout.border > 0) && (bg_uv_left != bg_uv_i_left)) | ||||
|             GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_i_bottom, bg_i_top, { { bg_uv_left, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_top }, { bg_uv_left, bg_uv_i_top } }); | ||||
| 
 | ||||
|         GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_i_bottom, bg_i_top, { { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_top }, { bg_uv_i_left, bg_uv_i_top } }); | ||||
| 
 | ||||
|         if ((m_layout.border > 0) && (bg_uv_right != bg_uv_i_right)) | ||||
|             GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_i_bottom, bg_i_top, { { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_right, bg_uv_i_bottom }, { bg_uv_right, bg_uv_i_top }, { bg_uv_i_right, bg_uv_i_top } }); | ||||
| 
 | ||||
|         if ((m_layout.border > 0) && (bg_uv_bottom != bg_uv_i_bottom)) | ||||
|         { | ||||
|             if (bg_uv_left != bg_uv_i_left) | ||||
|                 GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_bottom, bg_i_bottom, { { bg_uv_left, bg_uv_bottom }, { bg_uv_i_left, bg_uv_bottom }, { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_left, bg_uv_i_bottom } }); | ||||
| 
 | ||||
|             GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_bottom, bg_i_bottom, { { bg_uv_i_left, bg_uv_bottom }, { bg_uv_i_right, bg_uv_bottom }, { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_bottom } }); | ||||
| 
 | ||||
|             if (bg_uv_right != bg_uv_i_right) | ||||
|                 GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_bottom, bg_i_bottom, { { bg_uv_i_right, bg_uv_bottom }, { bg_uv_right, bg_uv_bottom }, { bg_uv_right, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_bottom } }); | ||||
|         } | ||||
|     } | ||||
|     render_background(left, top, right, bottom, scaled_border); | ||||
| 
 | ||||
|     left += scaled_border; | ||||
|     top -= scaled_border; | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     if ((tex_id == 0) || (tex_width <= 0) || (tex_height <= 0)) | ||||
|         return; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     // renders icons
 | ||||
|     for (const GLToolbarItem* item : m_items) | ||||
|  | @ -1136,11 +1123,7 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent) const | |||
|             left += separator_stride; | ||||
|         else | ||||
|         { | ||||
| #if ENABLE_SVG_ICONS | ||||
|             item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_width, (unsigned int)tex_height, (unsigned int)(m_layout.icons_size * m_layout.scale)); | ||||
| #else | ||||
|             item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_width, (unsigned int)tex_height, m_icons_texture.metadata.icon_size); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|             left += icon_stride; | ||||
|         } | ||||
|     } | ||||
|  | @ -1148,34 +1131,15 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent) const | |||
| 
 | ||||
| void GLToolbar::render_vertical(const GLCanvas3D& parent) const | ||||
| { | ||||
| #if ENABLE_SVG_ICONS | ||||
|     unsigned int tex_id = m_icons_texture.get_id(); | ||||
|     int tex_width = m_icons_texture.get_width(); | ||||
|     int tex_height = m_icons_texture.get_height(); | ||||
| #else | ||||
|     unsigned int tex_id = m_icons_texture.texture.get_id(); | ||||
|     int tex_width = m_icons_texture.texture.get_width(); | ||||
|     int tex_height = m_icons_texture.texture.get_height(); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
| #if !ENABLE_SVG_ICONS | ||||
|     if ((tex_id == 0) || (tex_width <= 0) || (tex_height <= 0)) | ||||
|         return; | ||||
| #endif // !ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     float zoom = (float)parent.get_camera().get_zoom(); | ||||
|     float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     float factor = inv_zoom * m_layout.scale; | ||||
| #else | ||||
|     float factor = inv_zoom * m_layout.icons_scale; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     float scaled_icons_size = m_layout.icons_size * factor; | ||||
| #else | ||||
|     float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale * factor; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     float scaled_separator_size = m_layout.separator_size * factor; | ||||
|     float scaled_gap_size = m_layout.gap_size * factor; | ||||
|     float scaled_border = m_layout.border * factor; | ||||
|  | @ -1190,96 +1154,13 @@ void GLToolbar::render_vertical(const GLCanvas3D& parent) const | |||
|     float right = left + scaled_width; | ||||
|     float bottom = top - scaled_height; | ||||
| 
 | ||||
|     // renders background
 | ||||
|     unsigned int bg_tex_id = m_background_texture.texture.get_id(); | ||||
|     float bg_tex_width = (float)m_background_texture.texture.get_width(); | ||||
|     float bg_tex_height = (float)m_background_texture.texture.get_height(); | ||||
|     if ((bg_tex_id != 0) && (bg_tex_width > 0) && (bg_tex_height > 0)) | ||||
|     { | ||||
|         float inv_bg_tex_width = (bg_tex_width != 0.0f) ? 1.0f / bg_tex_width : 0.0f; | ||||
|         float inv_bg_tex_height = (bg_tex_height != 0.0f) ? 1.0f / bg_tex_height : 0.0f; | ||||
| 
 | ||||
|         float bg_uv_left = 0.0f; | ||||
|         float bg_uv_right = 1.0f; | ||||
|         float bg_uv_top = 1.0f; | ||||
|         float bg_uv_bottom = 0.0f; | ||||
| 
 | ||||
|         float bg_left = left; | ||||
|         float bg_right = right; | ||||
|         float bg_top = top; | ||||
|         float bg_bottom = bottom; | ||||
|         float bg_width = right - left; | ||||
|         float bg_height = top - bottom; | ||||
|         float bg_min_size = std::min(bg_width, bg_height); | ||||
| 
 | ||||
|         float bg_uv_i_left = (float)m_background_texture.metadata.left * inv_bg_tex_width; | ||||
|         float bg_uv_i_right = 1.0f - (float)m_background_texture.metadata.right * inv_bg_tex_width; | ||||
|         float bg_uv_i_top = 1.0f - (float)m_background_texture.metadata.top * inv_bg_tex_height; | ||||
|         float bg_uv_i_bottom = (float)m_background_texture.metadata.bottom * inv_bg_tex_height; | ||||
| 
 | ||||
|         float bg_i_left = bg_left + scaled_border; | ||||
|         float bg_i_right = bg_right - scaled_border; | ||||
|         float bg_i_top = bg_top - scaled_border; | ||||
|         float bg_i_bottom = bg_bottom + scaled_border; | ||||
| 
 | ||||
|         switch (m_layout.orientation) | ||||
|         { | ||||
|         case Layout::Left: | ||||
|         { | ||||
|             bg_uv_left = bg_uv_i_left; | ||||
|             bg_i_left = bg_left; | ||||
|             break; | ||||
|         } | ||||
|         case Layout::Right: | ||||
|         { | ||||
|             bg_uv_right = bg_uv_i_right; | ||||
|             bg_i_right = bg_right; | ||||
|             break; | ||||
|         } | ||||
|         case Layout::Center: | ||||
|         { | ||||
|             break; | ||||
|         } | ||||
|         }; | ||||
| 
 | ||||
|         if ((m_layout.border > 0) && (bg_uv_top != bg_uv_i_top)) | ||||
|         { | ||||
|             if (bg_uv_left != bg_uv_i_left) | ||||
|                 GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_i_top, bg_top, { { bg_uv_left, bg_uv_i_top }, { bg_uv_i_left, bg_uv_i_top }, { bg_uv_i_left, bg_uv_top }, { bg_uv_left, bg_uv_top } }); | ||||
| 
 | ||||
|             GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_i_top, bg_top, { { bg_uv_i_left, bg_uv_i_top }, { bg_uv_i_right, bg_uv_i_top }, { bg_uv_i_right, bg_uv_top }, { bg_uv_i_left, bg_uv_top } }); | ||||
| 
 | ||||
|             if (bg_uv_right != bg_uv_i_right) | ||||
|                 GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_i_top, bg_top, { { bg_uv_i_right, bg_uv_i_top }, { bg_uv_right, bg_uv_i_top }, { bg_uv_right, bg_uv_top }, { bg_uv_i_right, bg_uv_top } }); | ||||
|         } | ||||
| 
 | ||||
|         if ((m_layout.border > 0) && (bg_uv_left != bg_uv_i_left)) | ||||
|             GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_i_bottom, bg_i_top, { { bg_uv_left, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_top }, { bg_uv_left, bg_uv_i_top } }); | ||||
| 
 | ||||
|         GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_i_bottom, bg_i_top, { { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_top }, { bg_uv_i_left, bg_uv_i_top } }); | ||||
| 
 | ||||
|         if ((m_layout.border > 0) && (bg_uv_right != bg_uv_i_right)) | ||||
|             GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_i_bottom, bg_i_top, { { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_right, bg_uv_i_bottom }, { bg_uv_right, bg_uv_i_top }, { bg_uv_i_right, bg_uv_i_top } }); | ||||
| 
 | ||||
|         if ((m_layout.border > 0) && (bg_uv_bottom != bg_uv_i_bottom)) | ||||
|         { | ||||
|             if (bg_uv_left != bg_uv_i_left) | ||||
|                 GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_bottom, bg_i_bottom, { { bg_uv_left, bg_uv_bottom }, { bg_uv_i_left, bg_uv_bottom }, { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_left, bg_uv_i_bottom } }); | ||||
| 
 | ||||
|             GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_bottom, bg_i_bottom, { { bg_uv_i_left, bg_uv_bottom }, { bg_uv_i_right, bg_uv_bottom }, { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_bottom } }); | ||||
| 
 | ||||
|             if (bg_uv_right != bg_uv_i_right) | ||||
|                 GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_bottom, bg_i_bottom, { { bg_uv_i_right, bg_uv_bottom }, { bg_uv_right, bg_uv_bottom }, { bg_uv_right, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_bottom } }); | ||||
|         } | ||||
|     } | ||||
|     render_background(left, top, right, bottom, scaled_border); | ||||
| 
 | ||||
|     left += scaled_border; | ||||
|     top -= scaled_border; | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     if ((tex_id == 0) || (tex_width <= 0) || (tex_height <= 0)) | ||||
|         return; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     // renders icons
 | ||||
|     for (const GLToolbarItem* item : m_items) | ||||
|  | @ -1291,17 +1172,12 @@ void GLToolbar::render_vertical(const GLCanvas3D& parent) const | |||
|             top -= separator_stride; | ||||
|         else | ||||
|         { | ||||
| #if ENABLE_SVG_ICONS | ||||
|             item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_width, (unsigned int)tex_height, (unsigned int)(m_layout.icons_size * m_layout.scale)); | ||||
| #else | ||||
|             item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_width, (unsigned int)tex_height, m_icons_texture.metadata.icon_size); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|             top -= icon_stride; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
| bool GLToolbar::generate_icons_texture() const | ||||
| { | ||||
|     std::string path = resources_dir() + "/icons/"; | ||||
|  | @ -1337,7 +1213,6 @@ bool GLToolbar::generate_icons_texture() const | |||
| 
 | ||||
|     return res; | ||||
| } | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
| bool GLToolbar::update_items_visibility() | ||||
| { | ||||
|  | @ -1371,9 +1246,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,29 +66,43 @@ 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; | ||||
| #if ENABLE_SVG_ICONS | ||||
|         std::string icon_filename; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|         std::string tooltip; | ||||
|         std::string additional_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); | ||||
|  | @ -88,22 +111,30 @@ public: | |||
|     void set_state(EState state) { m_state = state; } | ||||
| 
 | ||||
|     const std::string& get_name() const { return m_data.name; } | ||||
| #if ENABLE_SVG_ICONS | ||||
|     const std::string& get_icon_filename() const { return m_data.icon_filename; } | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     const std::string& get_tooltip() const { return m_data.tooltip; } | ||||
|     const std::string& get_additional_tooltip() const { return m_data.additional_tooltip; } | ||||
|     void set_additional_tooltip(const std::string& text) { m_data.additional_tooltip = text; } | ||||
| 
 | ||||
|     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
 | ||||
|  | @ -118,27 +149,6 @@ private: | |||
|     friend class GLToolbar; | ||||
| }; | ||||
| 
 | ||||
| #if !ENABLE_SVG_ICONS | ||||
| // items icon textures are assumed to be square and all with the same size in pixels, no internal check is done
 | ||||
| // icons are layed-out into the texture starting from the top-left corner in the same order as enum GLToolbarItem::EState
 | ||||
| // from left to right
 | ||||
| struct ItemsIconsTexture | ||||
| { | ||||
|     struct Metadata | ||||
|     { | ||||
|         // path of the file containing the icons' texture
 | ||||
|         std::string filename; | ||||
|         // size of the square icons, in pixels
 | ||||
|         unsigned int icon_size; | ||||
| 
 | ||||
|         Metadata(); | ||||
|     }; | ||||
| 
 | ||||
|     GLTexture texture; | ||||
|     Metadata metadata; | ||||
| }; | ||||
| #endif // !ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
| struct BackgroundTexture | ||||
| { | ||||
|     struct Metadata | ||||
|  | @ -164,9 +174,7 @@ struct BackgroundTexture | |||
| class GLToolbar | ||||
| { | ||||
| public: | ||||
| #if ENABLE_SVG_ICONS | ||||
|     static const float Default_Icons_Size; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     enum EType : unsigned char | ||||
|     { | ||||
|  | @ -184,29 +192,32 @@ public: | |||
|             Num_Types | ||||
|         }; | ||||
| 
 | ||||
|         enum EOrientation : unsigned int | ||||
|         enum EHorizontalOrientation : unsigned char | ||||
|         { | ||||
|             Top, | ||||
|             Bottom, | ||||
|             Left, | ||||
|             Right, | ||||
|             Center, | ||||
|             Num_Locations | ||||
|             HO_Left, | ||||
|             HO_Center, | ||||
|             HO_Right, | ||||
|             Num_Horizontal_Orientations | ||||
|         }; | ||||
| 
 | ||||
|         enum EVerticalOrientation : unsigned char | ||||
|         { | ||||
|             VO_Top, | ||||
|             VO_Center, | ||||
|             VO_Bottom, | ||||
|             Num_Vertical_Orientations | ||||
|         }; | ||||
| 
 | ||||
|         EType type; | ||||
|         EOrientation orientation; | ||||
|         EHorizontalOrientation horizontal_orientation; | ||||
|         EVerticalOrientation vertical_orientation; | ||||
|         float top; | ||||
|         float left; | ||||
|         float border; | ||||
|         float separator_size; | ||||
|         float gap_size; | ||||
| #if ENABLE_SVG_ICONS | ||||
|         float icons_size; | ||||
|         float scale; | ||||
| #else | ||||
|         float icons_scale; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|         float width; | ||||
|         float height; | ||||
|  | @ -219,16 +230,10 @@ private: | |||
|     typedef std::vector<GLToolbarItem*> ItemsList; | ||||
| 
 | ||||
|     EType m_type; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     std::string m_name; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     bool m_enabled; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     mutable GLTexture m_icons_texture; | ||||
|     mutable bool m_icons_texture_dirty; | ||||
| #else | ||||
|     ItemsIconsTexture m_icons_texture; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     BackgroundTexture m_background_texture; | ||||
|     mutable Layout m_layout; | ||||
| 
 | ||||
|  | @ -249,36 +254,27 @@ private: | |||
| 
 | ||||
|     MouseCapture m_mouse_capture; | ||||
|     std::string m_tooltip; | ||||
|     unsigned int m_pressed_toggable_id; | ||||
| 
 | ||||
| public: | ||||
| #if ENABLE_SVG_ICONS | ||||
|     GLToolbar(EType type, const std::string& name); | ||||
| #else | ||||
|     explicit GLToolbar(EType type); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     ~GLToolbar(); | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     bool init(const BackgroundTexture::Metadata& background_texture); | ||||
| #else | ||||
|     bool init(const ItemsIconsTexture::Metadata& icons_texture, const BackgroundTexture::Metadata& background_texture); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     Layout::EType get_layout_type() const; | ||||
|     void set_layout_type(Layout::EType type); | ||||
|     Layout::EOrientation get_layout_orientation() const; | ||||
|     void set_layout_orientation(Layout::EOrientation orientation); | ||||
|     Layout::EHorizontalOrientation get_horizontal_orientation() const { return m_layout.horizontal_orientation; } | ||||
|     void set_horizontal_orientation(Layout::EHorizontalOrientation orientation) { m_layout.horizontal_orientation = orientation; } | ||||
|     Layout::EVerticalOrientation get_vertical_orientation() const { return m_layout.vertical_orientation; } | ||||
|     void set_vertical_orientation(Layout::EVerticalOrientation orientation) { m_layout.vertical_orientation = orientation; } | ||||
| 
 | ||||
|     void set_position(float top, float left); | ||||
|     void set_border(float border); | ||||
|     void set_separator_size(float size); | ||||
|     void set_gap_size(float size); | ||||
| #if ENABLE_SVG_ICONS | ||||
|     void set_icons_size(float size); | ||||
|     void set_scale(float scale); | ||||
| #else | ||||
|     void set_icons_scale(float scale); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     bool is_enabled() const; | ||||
|     void set_enabled(bool enable); | ||||
|  | @ -295,8 +291,17 @@ 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; } | ||||
| 
 | ||||
|     void get_additional_tooltip(unsigned int item_id, std::string& text); | ||||
|     void set_additional_tooltip(unsigned int item_id, const std::string& text); | ||||
| 
 | ||||
|     // returns true if any item changed its state
 | ||||
|     bool update_items_state(); | ||||
|  | @ -312,7 +317,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); | ||||
|  | @ -321,12 +326,11 @@ private: | |||
|     int contains_mouse_horizontal(const Vec2d& mouse_pos, const GLCanvas3D& parent) const; | ||||
|     int contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& parent) const; | ||||
| 
 | ||||
|     void render_background(float left, float top, float right, float bottom, float border) const; | ||||
|     void render_horizontal(const GLCanvas3D& parent) const; | ||||
|     void render_vertical(const GLCanvas3D& parent) const; | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     bool generate_icons_texture() const; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     // returns true if any item changed its state
 | ||||
|     bool update_items_visibility(); | ||||
|  |  | |||
|  | @ -135,8 +135,7 @@ void config_wizard(int reason) | |||
| 
 | ||||
| 	wxGetApp().load_current_presets(); | ||||
| 
 | ||||
|     if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA && | ||||
|         wxGetApp().obj_list()->has_multi_part_objects()) | ||||
|     if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA && model_has_multi_part_objects(wxGetApp().model())) | ||||
|     { | ||||
|         show_info(nullptr, | ||||
|             _(L("It's impossible to print multi-part object(s) with SLA technology.")) + "\n\n" + | ||||
|  |  | |||
|  | @ -534,7 +534,7 @@ void GUI_App::persist_window_geometry(wxTopLevelWindow *window, bool default_max | |||
|     }); | ||||
| } | ||||
| 
 | ||||
| void GUI_App::load_project(wxWindow *parent, wxString& input_file) | ||||
| void GUI_App::load_project(wxWindow *parent, wxString& input_file) const | ||||
| { | ||||
|     input_file.Clear(); | ||||
|     wxFileDialog dialog(parent ? parent : GetTopWindow(), | ||||
|  | @ -546,7 +546,7 @@ void GUI_App::load_project(wxWindow *parent, wxString& input_file) | |||
|         input_file = dialog.GetPath(); | ||||
| } | ||||
| 
 | ||||
| void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) | ||||
| void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) const | ||||
| { | ||||
|     input_files.Clear(); | ||||
|     wxFileDialog dialog(parent ? parent : GetTopWindow(), | ||||
|  | @ -854,7 +854,7 @@ void GUI_App::add_config_menu(wxMenuBar *menu) | |||
| 
 | ||||
| // This is called when closing the application, when loading a config file or when starting the config wizard
 | ||||
| // to notify the user whether he is aware that some preset changes will be lost.
 | ||||
| bool GUI_App::check_unsaved_changes() | ||||
| bool GUI_App::check_unsaved_changes(const wxString &header) | ||||
| { | ||||
|     wxString dirty; | ||||
|     PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); | ||||
|  | @ -868,8 +868,12 @@ bool GUI_App::check_unsaved_changes() | |||
|         // No changes, the application may close or reload presets.
 | ||||
|         return true; | ||||
|     // Ask the user.
 | ||||
|     wxString message; | ||||
|     if (! header.empty()) | ||||
|     	message = header + "\n\n"; | ||||
|     message += _(L("The presets on the following tabs were modified")) + ": " + dirty + "\n\n" + _(L("Discard changes and continue anyway?")); | ||||
|     wxMessageDialog dialog(mainframe, | ||||
|         _(L("The presets on the following tabs were modified")) + ": " + dirty + "\n\n" + _(L("Discard changes and continue anyway?")), | ||||
|         message, | ||||
|         wxString(SLIC3R_APP_NAME) + " - " + _(L("Unsaved Presets")), | ||||
|         wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT); | ||||
|     return dialog.ShowModal() == wxID_YES; | ||||
|  | @ -934,14 +938,19 @@ ObjectList* GUI_App::obj_list() | |||
|     return sidebar().obj_list(); | ||||
| } | ||||
| 
 | ||||
| ObjectLayers* GUI_App::obj_layers() | ||||
| { | ||||
|     return sidebar().obj_layers(); | ||||
| } | ||||
| 
 | ||||
| Plater* GUI_App::plater() | ||||
| { | ||||
|     return plater_; | ||||
| } | ||||
| 
 | ||||
| ModelObjectPtrs* GUI_App::model_objects() | ||||
| Model& GUI_App::model() | ||||
| { | ||||
|     return &plater_->model().objects; | ||||
|     return plater_->model(); | ||||
| } | ||||
| 
 | ||||
| wxNotebook* GUI_App::tab_panel() const | ||||
|  |  | |||
|  | @ -121,8 +121,8 @@ public: | |||
|     void            recreate_GUI(); | ||||
|     void            system_info(); | ||||
|     void            keyboard_shortcuts(); | ||||
|     void            load_project(wxWindow *parent, wxString& input_file); | ||||
|     void            import_model(wxWindow *parent, wxArrayString& input_files); | ||||
|     void            load_project(wxWindow *parent, wxString& input_file) const; | ||||
|     void            import_model(wxWindow *parent, wxArrayString& input_files) const; | ||||
|     static bool     catch_error(std::function<void()> cb, const std::string& err); | ||||
| 
 | ||||
|     void            persist_window_geometry(wxTopLevelWindow *window, bool default_maximized = false); | ||||
|  | @ -139,7 +139,7 @@ public: | |||
|     void            update_mode(); | ||||
| 
 | ||||
|     void            add_config_menu(wxMenuBar *menu); | ||||
|     bool            check_unsaved_changes(); | ||||
|     bool            check_unsaved_changes(const wxString &header = wxString()); | ||||
|     bool            checked_tab(Tab* tab); | ||||
|     void            load_current_presets(); | ||||
| 
 | ||||
|  | @ -156,8 +156,9 @@ public: | |||
|     ObjectManipulation* obj_manipul(); | ||||
|     ObjectSettings*     obj_settings(); | ||||
|     ObjectList*         obj_list(); | ||||
|     ObjectLayers*       obj_layers(); | ||||
|     Plater*             plater(); | ||||
|     std::vector<ModelObject*> *model_objects(); | ||||
|     Model&      		model(); | ||||
| 
 | ||||
|     AppConfig*      app_config{ nullptr }; | ||||
|     PresetBundle*   preset_bundle{ nullptr }; | ||||
|  |  | |||
							
								
								
									
										341
									
								
								src/slic3r/GUI/GUI_ObjectLayers.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										341
									
								
								src/slic3r/GUI/GUI_ObjectLayers.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,341 @@ | |||
| #include "GUI_ObjectLayers.hpp" | ||||
| #include "GUI_ObjectList.hpp" | ||||
| 
 | ||||
| #include "OptionsGroup.hpp" | ||||
| #include "PresetBundle.hpp" | ||||
| #include "libslic3r/Model.hpp" | ||||
| #include "GLCanvas3D.hpp" | ||||
| 
 | ||||
| #include <boost/algorithm/string.hpp> | ||||
| 
 | ||||
| #include "I18N.hpp" | ||||
| 
 | ||||
| #include <wx/wupdlock.h> | ||||
| 
 | ||||
| namespace Slic3r | ||||
| { | ||||
| namespace GUI | ||||
| { | ||||
| 
 | ||||
| ObjectLayers::ObjectLayers(wxWindow* parent) : | ||||
|     OG_Settings(parent, true) | ||||
| { | ||||
|     m_grid_sizer = new wxFlexGridSizer(3, 5, 5); // "Min Z", "Max Z", "Layer height" & buttons sizer
 | ||||
|     m_grid_sizer->SetFlexibleDirection(wxHORIZONTAL); | ||||
| 
 | ||||
|     // Legend for object layers
 | ||||
|     for (const std::string col : { "Min Z", "Max Z", "Layer height" }) { | ||||
|         auto temp = new wxStaticText(m_parent, wxID_ANY, _(L(col)), wxDefaultPosition, /*size*/wxDefaultSize, wxST_ELLIPSIZE_MIDDLE); | ||||
|         temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); | ||||
|         temp->SetBackgroundStyle(wxBG_STYLE_PAINT); | ||||
|         temp->SetFont(wxGetApp().bold_font()); | ||||
| 
 | ||||
|         m_grid_sizer->Add(temp); | ||||
|     } | ||||
| 
 | ||||
|     m_og->sizer->Clear(true); | ||||
|     m_og->sizer->Add(m_grid_sizer, 0, wxEXPAND | wxALL, wxOSX ? 0 : 5); | ||||
| 
 | ||||
|     m_bmp_delete    = ScalableBitmap(parent, "remove_copies"/*"cross"*/); | ||||
|     m_bmp_add       = ScalableBitmap(parent, "add_copies"); | ||||
| } | ||||
| 
 | ||||
| void ObjectLayers::select_editor(LayerRangeEditor* editor, const bool is_last_edited_range) | ||||
| { | ||||
|     if (is_last_edited_range && m_selection_type == editor->type()) { | ||||
|     /* Workaround! Under OSX we should use CallAfter() for SetFocus() after LayerEditors "reorganizations", 
 | ||||
|      * because of selected control's strange behavior:  | ||||
|      * cursor is set to the control, but blue border - doesn't. | ||||
|      * And as a result we couldn't edit this control. | ||||
|      * */ | ||||
| #ifdef __WXOSX__ | ||||
|         wxTheApp->CallAfter([editor]() { | ||||
| #endif | ||||
|         editor->SetFocus(); | ||||
|         editor->SetInsertionPointEnd(); | ||||
| #ifdef __WXOSX__ | ||||
|         }); | ||||
| #endif | ||||
|     }     | ||||
| } | ||||
| 
 | ||||
| wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range)  | ||||
| { | ||||
|     const bool is_last_edited_range = range == m_selectable_range; | ||||
| 
 | ||||
|     auto set_focus_data = [range, this](const EditorType type) | ||||
|     { | ||||
|         m_selectable_range = range; | ||||
|         m_selection_type = type; | ||||
|     }; | ||||
| 
 | ||||
|     auto update_focus_data = [range, this](const t_layer_height_range& new_range, EditorType type, bool enter_pressed) | ||||
|     { | ||||
|         // change selectable range for new one, if enter was pressed or if same range was selected
 | ||||
|         if (enter_pressed || m_selectable_range == range) | ||||
|             m_selectable_range = new_range; | ||||
|         if (enter_pressed) | ||||
|             m_selection_type = type; | ||||
|     }; | ||||
| 
 | ||||
|     // Add control for the "Min Z"
 | ||||
| 
 | ||||
|     auto editor = new LayerRangeEditor(this, double_to_string(range.first), etMinZ, | ||||
|                                        set_focus_data, [range, update_focus_data, this](coordf_t min_z, bool enter_pressed) | ||||
|     { | ||||
|         if (fabs(min_z - range.first) < EPSILON) { | ||||
|             m_selection_type = etUndef; | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         // data for next focusing
 | ||||
|         coordf_t max_z = min_z < range.second ? range.second : min_z + 0.5; | ||||
|         const t_layer_height_range& new_range = { min_z, max_z }; | ||||
|         update_focus_data(new_range, etMinZ, enter_pressed); | ||||
| 
 | ||||
|         return wxGetApp().obj_list()->edit_layer_range(range, new_range); | ||||
|     }); | ||||
| 
 | ||||
|     select_editor(editor, is_last_edited_range); | ||||
|     m_grid_sizer->Add(editor); | ||||
| 
 | ||||
|     // Add control for the "Max Z"
 | ||||
| 
 | ||||
|     editor = new LayerRangeEditor(this, double_to_string(range.second), etMaxZ, | ||||
|                                   set_focus_data, [range, update_focus_data, this](coordf_t max_z, bool enter_pressed) | ||||
|     { | ||||
|         if (fabs(max_z - range.second) < EPSILON || range.first > max_z) { | ||||
|             m_selection_type = etUndef; | ||||
|             return false;       // LayersList would not be updated/recreated
 | ||||
|         } | ||||
| 
 | ||||
|         // data for next focusing
 | ||||
|         const t_layer_height_range& new_range = { range.first, max_z }; | ||||
|         update_focus_data(new_range, etMaxZ, enter_pressed); | ||||
| 
 | ||||
|         return wxGetApp().obj_list()->edit_layer_range(range, new_range); | ||||
|     }); | ||||
| 
 | ||||
|     select_editor(editor, is_last_edited_range); | ||||
|     m_grid_sizer->Add(editor); | ||||
| 
 | ||||
|     // Add control for the "Layer height"
 | ||||
| 
 | ||||
|     editor = new LayerRangeEditor(this, | ||||
|                                 double_to_string(m_object->layer_config_ranges[range].option("layer_height")->getFloat()), | ||||
|                              etLayerHeight, set_focus_data, [range, this](coordf_t layer_height, bool) | ||||
|     { | ||||
|         return wxGetApp().obj_list()->edit_layer_range(range, layer_height); | ||||
|     }); | ||||
| 
 | ||||
|     select_editor(editor, is_last_edited_range); | ||||
| 
 | ||||
|     auto sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|     sizer->Add(editor); | ||||
|     m_grid_sizer->Add(sizer); | ||||
| 
 | ||||
|     return sizer; | ||||
| } | ||||
| 
 | ||||
| void ObjectLayers::create_layers_list() | ||||
| { | ||||
|     for (const auto layer : m_object->layer_config_ranges) | ||||
|     { | ||||
|         const t_layer_height_range& range = layer.first; | ||||
|         auto sizer = create_layer(range); | ||||
| 
 | ||||
|         auto del_btn = new ScalableButton(m_parent, wxID_ANY, m_bmp_delete); | ||||
|         del_btn->SetToolTip(_(L("Remove layer"))); | ||||
| 
 | ||||
|         sizer->Add(del_btn, 0, wxRIGHT | wxLEFT, em_unit(m_parent)); | ||||
| 
 | ||||
|         del_btn->Bind(wxEVT_BUTTON, [this, range](wxEvent &event) { | ||||
|             wxGetApp().obj_list()->del_layer_range(range); | ||||
|         }); | ||||
| 
 | ||||
|         auto add_btn = new ScalableButton(m_parent, wxID_ANY, m_bmp_add); | ||||
|         add_btn->SetToolTip(_(L("Add layer"))); | ||||
| 
 | ||||
|         sizer->Add(add_btn, 0, wxRIGHT, em_unit(m_parent)); | ||||
| 
 | ||||
|         add_btn->Bind(wxEVT_BUTTON, [this, range](wxEvent &event) { | ||||
|             wxGetApp().obj_list()->add_layer_range_after_current(range); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void ObjectLayers::update_layers_list() | ||||
| { | ||||
|     ObjectList* objects_ctrl   = wxGetApp().obj_list(); | ||||
|     if (objects_ctrl->multiple_selection()) return; | ||||
| 
 | ||||
|     const auto item = objects_ctrl->GetSelection(); | ||||
|     if (!item) return; | ||||
| 
 | ||||
|     const int obj_idx = objects_ctrl->get_selected_obj_idx(); | ||||
|     if (obj_idx < 0) return; | ||||
| 
 | ||||
|     const ItemType type = objects_ctrl->GetModel()->GetItemType(item); | ||||
|     if (!(type & (itLayerRoot | itLayer))) return; | ||||
| 
 | ||||
|     m_object = objects_ctrl->object(obj_idx); | ||||
|     if (!m_object || m_object->layer_config_ranges.empty()) return; | ||||
| 
 | ||||
|     // Delete all controls from options group except of the legends
 | ||||
| 
 | ||||
|     const int cols = m_grid_sizer->GetEffectiveColsCount(); | ||||
|     const int rows = m_grid_sizer->GetEffectiveRowsCount(); | ||||
|     for (int idx = cols*rows-1; idx >= cols; idx--) { | ||||
|         wxSizerItem* t = m_grid_sizer->GetItem(idx); | ||||
|         if (t->IsSizer()) | ||||
|             t->GetSizer()->Clear(true); | ||||
|         else | ||||
|             t->DeleteWindows(); | ||||
|         m_grid_sizer->Remove(idx); | ||||
|     } | ||||
| 
 | ||||
|     // Add new control according to the selected item  
 | ||||
| 
 | ||||
|     if (type & itLayerRoot) | ||||
|         create_layers_list(); | ||||
|     else | ||||
|         create_layer(objects_ctrl->GetModel()->GetLayerRangeByItem(item)); | ||||
| 
 | ||||
|     m_parent->Layout(); | ||||
| } | ||||
| 
 | ||||
| void ObjectLayers::update_scene_from_editor_selection() const | ||||
| { | ||||
|     // needed to show the visual hints in 3D scene
 | ||||
|     wxGetApp().plater()->canvas3D()->handle_layers_data_focus_event(m_selectable_range, m_selection_type); | ||||
| } | ||||
| 
 | ||||
| void ObjectLayers::UpdateAndShow(const bool show) | ||||
| { | ||||
|     if (show) | ||||
|         update_layers_list(); | ||||
| 
 | ||||
|     OG_Settings::UpdateAndShow(show); | ||||
| } | ||||
| 
 | ||||
| void ObjectLayers::msw_rescale() | ||||
| { | ||||
|     m_bmp_delete.msw_rescale(); | ||||
|     m_bmp_add.msw_rescale(); | ||||
| } | ||||
| 
 | ||||
| void ObjectLayers::reset_selection() | ||||
| { | ||||
|     m_selectable_range = { 0.0, 0.0 }; | ||||
|     m_selection_type = etLayerHeight; | ||||
| } | ||||
| 
 | ||||
| LayerRangeEditor::LayerRangeEditor( ObjectLayers* parent, | ||||
|                                     const wxString& value, | ||||
|                                     EditorType type, | ||||
|                                     std::function<void(EditorType)> set_focus_data_fn, | ||||
|                                     std::function<bool(coordf_t, bool enter_pressed)>   edit_fn | ||||
|                                     ) : | ||||
|     m_valid_value(value), | ||||
|     m_type(type), | ||||
|     m_set_focus_data(set_focus_data_fn), | ||||
|     wxTextCtrl(parent->m_parent, wxID_ANY, value, wxDefaultPosition,  | ||||
|                wxSize(8 * em_unit(parent->m_parent), wxDefaultCoord), wxTE_PROCESS_ENTER) | ||||
| { | ||||
|     this->SetFont(wxGetApp().normal_font()); | ||||
|      | ||||
|     this->Bind(wxEVT_TEXT_ENTER, [this, edit_fn](wxEvent&) | ||||
|     { | ||||
|         m_enter_pressed     = true; | ||||
|         // If LayersList wasn't updated/recreated, we can call wxEVT_KILL_FOCUS.Skip()
 | ||||
|         if (m_type&etLayerHeight) { | ||||
|             if (!edit_fn(get_value(), true)) | ||||
|                 SetValue(m_valid_value); | ||||
|             else | ||||
|                 m_valid_value = double_to_string(get_value()); | ||||
|             m_call_kill_focus = true; | ||||
|         } | ||||
|         else if (!edit_fn(get_value(), true)) { | ||||
|             SetValue(m_valid_value); | ||||
|             m_call_kill_focus = true; | ||||
|         } | ||||
|     }, this->GetId()); | ||||
| 
 | ||||
|     this->Bind(wxEVT_KILL_FOCUS, [this, edit_fn](wxFocusEvent& e) | ||||
|     { | ||||
|         if (!m_enter_pressed) { | ||||
| #ifndef __WXGTK__ | ||||
|             /* Update data for next editor selection.
 | ||||
|              * But under GTK it lucks like there is no information about selected control at e.GetWindow(), | ||||
|              * so we'll take it from wxEVT_LEFT_DOWN event | ||||
|              * */ | ||||
|             LayerRangeEditor* new_editor = dynamic_cast<LayerRangeEditor*>(e.GetWindow()); | ||||
|             if (new_editor) | ||||
|                 new_editor->set_focus_data(); | ||||
| #endif // not __WXGTK__
 | ||||
|             // If LayersList wasn't updated/recreated, we should call e.Skip()
 | ||||
|             if (m_type & etLayerHeight) { | ||||
|                 if (!edit_fn(get_value(), false)) | ||||
|                     SetValue(m_valid_value); | ||||
|                 else | ||||
|                     m_valid_value = double_to_string(get_value()); | ||||
|                 e.Skip(); | ||||
|             } | ||||
|             else if (!edit_fn(get_value(), false)) { | ||||
|                 SetValue(m_valid_value); | ||||
|                 e.Skip(); | ||||
|             }  | ||||
|         } | ||||
|         else if (m_call_kill_focus) { | ||||
|             m_call_kill_focus = false; | ||||
|             e.Skip(); | ||||
|         } | ||||
|     }, this->GetId()); | ||||
| 
 | ||||
|     this->Bind(wxEVT_SET_FOCUS, [this, parent](wxFocusEvent& e) | ||||
|     { | ||||
|         set_focus_data(); | ||||
|         parent->update_scene_from_editor_selection(); | ||||
|         e.Skip(); | ||||
|     }, this->GetId()); | ||||
| 
 | ||||
| #ifdef __WXGTK__ // Workaround! To take information about selectable range
 | ||||
|     this->Bind(wxEVT_LEFT_DOWN, [this](wxEvent& e) | ||||
|     { | ||||
|         set_focus_data(); | ||||
|         e.Skip(); | ||||
|     }, this->GetId()); | ||||
| #endif //__WXGTK__
 | ||||
| 
 | ||||
|     this->Bind(wxEVT_CHAR, ([this](wxKeyEvent& event) | ||||
|     { | ||||
|         // select all text using Ctrl+A
 | ||||
|         if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL)) | ||||
|             this->SetSelection(-1, -1); //select all
 | ||||
|         event.Skip(); | ||||
|     })); | ||||
| } | ||||
| 
 | ||||
| coordf_t LayerRangeEditor::get_value() | ||||
| { | ||||
|     wxString str = GetValue(); | ||||
| 
 | ||||
|     coordf_t layer_height; | ||||
|     // Replace the first occurence of comma in decimal number.
 | ||||
|     str.Replace(",", ".", false); | ||||
|     if (str == ".") | ||||
|         layer_height = 0.0; | ||||
|     else | ||||
|     { | ||||
|         if (!str.ToCDouble(&layer_height) || layer_height < 0.0f) | ||||
|         { | ||||
|             show_error(m_parent, _(L("Invalid numeric input."))); | ||||
|             SetValue(double_to_string(layer_height)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return layer_height; | ||||
| } | ||||
| 
 | ||||
| } //namespace GUI
 | ||||
| } //namespace Slic3r 
 | ||||
							
								
								
									
										88
									
								
								src/slic3r/GUI/GUI_ObjectLayers.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								src/slic3r/GUI/GUI_ObjectLayers.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,88 @@ | |||
| #ifndef slic3r_GUI_ObjectLayers_hpp_ | ||||
| #define slic3r_GUI_ObjectLayers_hpp_ | ||||
| 
 | ||||
| #include "GUI_ObjectSettings.hpp" | ||||
| #include "wxExtensions.hpp" | ||||
| 
 | ||||
| #ifdef __WXOSX__ | ||||
| #include "../libslic3r/PrintConfig.hpp" | ||||
| #endif | ||||
| 
 | ||||
| class wxBoxSizer; | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| class ModelObject; | ||||
| 
 | ||||
| namespace GUI { | ||||
| class ConfigOptionsGroup; | ||||
| 
 | ||||
| typedef double                          coordf_t; | ||||
| typedef std::pair<coordf_t, coordf_t>   t_layer_height_range; | ||||
| 
 | ||||
| class ObjectLayers; | ||||
| 
 | ||||
| enum EditorType | ||||
| { | ||||
|     etUndef         = 0, | ||||
|     etMinZ          = 1, | ||||
|     etMaxZ          = 2, | ||||
|     etLayerHeight   = 4, | ||||
| }; | ||||
| 
 | ||||
| class LayerRangeEditor : public wxTextCtrl | ||||
| { | ||||
|     bool                m_enter_pressed     { false }; | ||||
|     bool                m_call_kill_focus   { false }; | ||||
|     wxString            m_valid_value; | ||||
|     EditorType          m_type; | ||||
| 
 | ||||
|     std::function<void(EditorType)> m_set_focus_data; | ||||
| 
 | ||||
| public: | ||||
|     LayerRangeEditor(   ObjectLayers* parent, | ||||
|                         const wxString& value = wxEmptyString, | ||||
|                         EditorType type = etUndef, | ||||
|                         std::function<void(EditorType)>     set_focus_data_fn   = [](EditorType)      {;}, | ||||
|                         std::function<bool(coordf_t, bool)> edit_fn             = [](coordf_t, bool) {return false; } | ||||
|                         ); | ||||
|     ~LayerRangeEditor() {} | ||||
| 
 | ||||
|     EditorType          type() const {return m_type;} | ||||
|     void                set_focus_data() const { m_set_focus_data(m_type);} | ||||
| 
 | ||||
| private: | ||||
|     coordf_t            get_value(); | ||||
| }; | ||||
| 
 | ||||
| class ObjectLayers : public OG_Settings | ||||
| { | ||||
|     ScalableBitmap  m_bmp_delete; | ||||
|     ScalableBitmap  m_bmp_add; | ||||
|     ModelObject*    m_object {nullptr}; | ||||
| 
 | ||||
|     wxFlexGridSizer*       m_grid_sizer; | ||||
|     t_layer_height_range   m_selectable_range; | ||||
|     EditorType             m_selection_type {etUndef}; | ||||
| 
 | ||||
| public: | ||||
|     ObjectLayers(wxWindow* parent); | ||||
|     ~ObjectLayers() {} | ||||
| 
 | ||||
|     void        select_editor(LayerRangeEditor* editor, const bool is_last_edited_range); | ||||
|     wxSizer*    create_layer(const t_layer_height_range& range);    // without_buttons
 | ||||
|     void        create_layers_list(); | ||||
|     void        update_layers_list(); | ||||
| 
 | ||||
|     void        update_scene_from_editor_selection() const; | ||||
| 
 | ||||
|     void        UpdateAndShow(const bool show) override; | ||||
|     void        msw_rescale(); | ||||
|     void        reset_selection(); | ||||
|     void        set_selectable_range(const t_layer_height_range& range) { m_selectable_range = range; } | ||||
| 
 | ||||
|     friend class LayerRangeEditor; | ||||
| }; | ||||
| 
 | ||||
| }} | ||||
| 
 | ||||
| #endif // slic3r_GUI_ObjectLayers_hpp_
 | ||||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -26,13 +26,17 @@ 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; | ||||
| 
 | ||||
| typedef std::vector<ModelVolume*> ModelVolumePtrs; | ||||
| 
 | ||||
| typedef double                                              coordf_t; | ||||
| typedef std::pair<coordf_t, coordf_t>                       t_layer_height_range; | ||||
| typedef std::map<t_layer_height_range, DynamicPrintConfig>  t_layer_config_ranges; | ||||
| 
 | ||||
| namespace GUI { | ||||
| 
 | ||||
| wxDECLARE_EVENT(EVT_OBJ_LIST_OBJECT_SELECT, SimpleEvent); | ||||
|  | @ -64,9 +68,10 @@ class ObjectList : public wxDataViewCtrl | |||
| { | ||||
|     enum SELECTION_MODE | ||||
|     { | ||||
|         smUndef, | ||||
|         smVolume, | ||||
|         smInstance | ||||
|         smUndef     = 0, | ||||
|         smVolume    = 1, | ||||
|         smInstance  = 2, | ||||
|         smLayer     = 4 | ||||
|     } m_selection_mode {smUndef}; | ||||
| 
 | ||||
|     struct dragged_item_data | ||||
|  | @ -119,12 +124,17 @@ class ObjectList : public wxDataViewCtrl | |||
|     MenuWithSeparators  m_menu_part; | ||||
|     MenuWithSeparators  m_menu_sla_object; | ||||
|     MenuWithSeparators  m_menu_instance; | ||||
|     wxMenuItem* m_menu_item_split { nullptr }; | ||||
|     wxMenuItem* m_menu_item_split_part { nullptr }; | ||||
|     MenuWithSeparators  m_menu_layer; | ||||
|     wxMenuItem* m_menu_item_settings { nullptr }; | ||||
|     wxMenuItem* m_menu_item_split_instances { nullptr }; | ||||
| 
 | ||||
|     std::vector<wxBitmap*> m_bmp_vector; | ||||
|     ObjectDataViewModel         *m_objects_model{ nullptr }; | ||||
|     DynamicPrintConfig          *m_config {nullptr}; | ||||
|     std::vector<ModelObject*>   *m_objects{ nullptr }; | ||||
| 
 | ||||
|     std::vector<wxBitmap*>      m_bmp_vector; | ||||
| 
 | ||||
|     t_layer_config_ranges       m_layer_config_ranges_cache; | ||||
| 
 | ||||
|     int			m_selected_object_id = -1; | ||||
|     bool		m_prevent_list_events = false;		// We use this flag to avoid circular event handling Select() 
 | ||||
|  | @ -142,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: | ||||
|  | @ -153,11 +163,11 @@ public: | |||
| 
 | ||||
|     std::map<std::string, wxBitmap> CATEGORY_ICON; | ||||
| 
 | ||||
|     ObjectDataViewModel	*m_objects_model{ nullptr }; | ||||
|     DynamicPrintConfig          *m_config {nullptr}; | ||||
| 
 | ||||
|     std::vector<ModelObject*>   *m_objects{ nullptr }; | ||||
|     ObjectDataViewModel*        GetModel() const    { return m_objects_model; } | ||||
|     DynamicPrintConfig*         config() const      { return m_config; } | ||||
|     std::vector<ModelObject*>*  objects() const     { return m_objects; } | ||||
| 
 | ||||
|     ModelObject*                object(const int obj_idx) const ; | ||||
| 
 | ||||
|     void                create_objects_ctrl(); | ||||
|     void                create_popup_menus(); | ||||
|  | @ -192,13 +202,19 @@ public: | |||
|     void                key_event(wxKeyEvent& event); | ||||
| #endif /* __WXOSX__ */ | ||||
| 
 | ||||
|     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); | ||||
|     wxMenuItem*         append_menu_item_split(wxMenu* menu); | ||||
|     wxMenuItem*         append_menu_item_layers_editing(wxMenu* menu); | ||||
|     wxMenuItem*         append_menu_item_settings(wxMenu* menu); | ||||
|     wxMenuItem*         append_menu_item_change_type(wxMenu* menu); | ||||
|     wxMenuItem*         append_menu_item_instance_to_object(wxMenu* menu, wxWindow* parent); | ||||
|  | @ -222,10 +238,18 @@ public: | |||
| 	void                load_generic_subobject(const std::string& type_name, const ModelVolumeType type); | ||||
|     void                del_object(const int obj_idx); | ||||
|     void                del_subobject_item(wxDataViewItem& item); | ||||
|     void                del_settings_from_config(); | ||||
|     void                del_settings_from_config(const wxDataViewItem& parent_item); | ||||
|     void                del_instances_from_object(const int obj_idx); | ||||
|     void                del_layer_from_object(const int obj_idx, const t_layer_height_range& layer_range); | ||||
|     void                del_layers_from_object(const int obj_idx); | ||||
|     bool                del_subobject_from_object(const int obj_idx, const int idx, const int type); | ||||
|     void                split(); | ||||
|     void                layers_editing(); | ||||
| 
 | ||||
|     wxDataViewItem      add_layer_root_item(const wxDataViewItem obj_item); | ||||
|     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); | ||||
|     bool                is_splittable(); | ||||
|     bool                selected_instances_of_same_object(); | ||||
|  | @ -235,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); | ||||
|  | @ -265,6 +290,14 @@ public: | |||
| 
 | ||||
|     // Remove objects/sub-object from the list
 | ||||
|     void remove(); | ||||
|     void del_layer_range(const t_layer_height_range& range); | ||||
|     void add_layer_range_after_current(const t_layer_height_range& current_range); | ||||
|     void add_layer_item (const t_layer_height_range& range,  | ||||
|                          const wxDataViewItem layers_item,  | ||||
|                          const int layer_idx = -1); | ||||
|     bool edit_layer_range(const t_layer_height_range& range, coordf_t layer_height); | ||||
|     bool edit_layer_range(const t_layer_height_range& range,  | ||||
|                           const t_layer_height_range& new_range); | ||||
| 
 | ||||
|     void init_objects(); | ||||
|     bool multiple_selection() const ; | ||||
|  | @ -284,8 +317,10 @@ public: | |||
|     void change_part_type(); | ||||
| 
 | ||||
|     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(); | ||||
| 
 | ||||
|     void instances_to_separated_object(const int obj_idx, const std::set<int>& inst_idx); | ||||
|  | @ -295,11 +330,15 @@ public: | |||
|     void fix_through_netfabb(); | ||||
|     void update_item_error_icon(const int obj_idx, int vol_idx) const ; | ||||
| 
 | ||||
|     void fill_layer_config_ranges_cache(); | ||||
|     void paste_layers_into_list(); | ||||
|     void paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& volumes); | ||||
|     void paste_objects_into_list(const std::vector<size_t>& object_idxs); | ||||
| 
 | ||||
|     void msw_rescale(); | ||||
| 
 | ||||
|     void update_after_undo_redo(); | ||||
| 
 | ||||
| private: | ||||
| #ifdef __WXOSX__ | ||||
| //    void OnChar(wxKeyEvent& event);
 | ||||
|  |  | |||
|  | @ -17,6 +17,28 @@ namespace Slic3r | |||
| namespace GUI | ||||
| { | ||||
| 
 | ||||
| 
 | ||||
| // Helper function to be used by drop to bed button. Returns lowest point of this
 | ||||
| // volume in world coordinate system.
 | ||||
| static double get_volume_min_z(const GLVolume* volume) | ||||
| { | ||||
|     const Transform3f& world_matrix = volume->world_matrix().cast<float>(); | ||||
| 
 | ||||
|     // need to get the ModelVolume pointer
 | ||||
|     const ModelObject* mo = wxGetApp().model().objects[volume->composite_id.object_id]; | ||||
|     const ModelVolume* mv = mo->volumes[volume->composite_id.volume_id]; | ||||
|     const TriangleMesh& hull = mv->get_convex_hull(); | ||||
| 
 | ||||
|     float min_z = std::numeric_limits<float>::max(); | ||||
|     for (const stl_facet& facet : hull.stl.facet_start) { | ||||
|         for (int i = 0; i < 3; ++ i) | ||||
|             min_z = std::min(min_z, Vec3f::UnitZ().dot(world_matrix * facet.vertex[i])); | ||||
|     } | ||||
|     return min_z; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| static wxBitmapComboBox* create_word_local_combo(wxWindow *parent) | ||||
| { | ||||
|     wxSize size(15 * wxGetApp().em_unit(), -1); | ||||
|  | @ -177,25 +199,24 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : | |||
|     m_mirror_bitmap_off = ScalableBitmap(parent, "mirroring_off.png"); | ||||
|     m_mirror_bitmap_hidden = ScalableBitmap(parent, "mirroring_transparent.png"); | ||||
| 
 | ||||
| 	for (const std::string axis : { "x", "y", "z" }) { | ||||
|         const std::string label = boost::algorithm::to_upper_copy(axis); | ||||
|         def.set_default_value(new ConfigOptionString{ "   " + label }); | ||||
|         Option option = Option(def, axis + "_axis_legend"); | ||||
| 
 | ||||
|         unsigned int axis_idx = (axis[0] - 'x'); // 0, 1 or 2
 | ||||
|     static const char axes[] = { 'X', 'Y', 'Z' }; | ||||
|     for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++) { | ||||
|         const char label = axes[axis_idx]; | ||||
|         def.set_default_value(new ConfigOptionString{ std::string("   ") + label }); | ||||
|         Option option(def, std::string() + label + "_axis_legend"); | ||||
| 
 | ||||
|         // We will add a button to toggle mirroring to each axis:
 | ||||
|         auto mirror_button = [=](wxWindow* parent) { | ||||
|         auto mirror_button = [this, mirror_btn_width, axis_idx, label](wxWindow* parent) { | ||||
|             wxSize btn_size(em_unit(parent) * mirror_btn_width, em_unit(parent) * mirror_btn_width); | ||||
|             auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_off.png", wxEmptyString, btn_size, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW); | ||||
|             btn->SetToolTip(wxString::Format(_(L("Toggle %s axis mirroring")), label)); | ||||
|             btn->SetToolTip(wxString::Format(_(L("Toggle %c axis mirroring")), (int)label)); | ||||
| 
 | ||||
|             m_mirror_buttons[axis_idx].first = btn; | ||||
|             m_mirror_buttons[axis_idx].second = mbShown; | ||||
|             auto sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|             sizer->Add(btn); | ||||
| 
 | ||||
|             btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { | ||||
|             btn->Bind(wxEVT_BUTTON, [this, axis_idx](wxCommandEvent &e) { | ||||
|                 Axis axis = (Axis)(axis_idx + X); | ||||
|                 if (m_mirror_buttons[axis_idx].second == mbHidden) | ||||
|                     return; | ||||
|  | @ -220,11 +241,11 @@ 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(L("Set Mirror")); | ||||
|                 UpdateAndShow(true); | ||||
|             }); | ||||
|         return sizer; | ||||
| 
 | ||||
|             return sizer; | ||||
|         }; | ||||
| 
 | ||||
|         option.side_widget = mirror_button; | ||||
|  | @ -258,13 +279,13 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : | |||
|                 return btn; | ||||
|             }; | ||||
|             // Add reset scale button
 | ||||
|             auto reset_scale_button = [=](wxWindow* parent) { | ||||
|             auto reset_scale_button = [this](wxWindow* parent) { | ||||
|                 auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo")); | ||||
|                 btn->SetToolTip(_(L("Reset scale"))); | ||||
|                 m_reset_scale_button = btn; | ||||
|                 auto sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|                 sizer->Add(btn, wxBU_EXACTFIT); | ||||
|                 btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { | ||||
|                 btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { | ||||
|                     change_scale_value(0, 100.); | ||||
|                     change_scale_value(1, 100.); | ||||
|                     change_scale_value(2, 100.); | ||||
|  | @ -275,13 +296,13 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : | |||
|         } | ||||
|         else if (option_name == "Rotation") { | ||||
|             // Add reset rotation button
 | ||||
|             auto reset_rotation_button = [=](wxWindow* parent) { | ||||
|             auto reset_rotation_button = [this](wxWindow* parent) { | ||||
|                 auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo")); | ||||
|                 btn->SetToolTip(_(L("Reset rotation"))); | ||||
|                 m_reset_rotation_button = btn; | ||||
|                 auto sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|                 sizer->Add(btn, wxBU_EXACTFIT); | ||||
|                 btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { | ||||
|                 btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { | ||||
|                     GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); | ||||
|                     Selection& selection = canvas->get_selection(); | ||||
| 
 | ||||
|  | @ -302,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(L("Set Rotation")); | ||||
| 
 | ||||
|                     UpdateAndShow(true); | ||||
|                 }); | ||||
|  | @ -310,6 +331,34 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : | |||
|             }; | ||||
|             line.append_widget(reset_rotation_button); | ||||
|         } | ||||
|         else if (option_name == "Position") { | ||||
|             // Add drop to bed button
 | ||||
|             auto drop_to_bed_button = [=](wxWindow* parent) { | ||||
|                 auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "drop_to_bed.png")); | ||||
|                 btn->SetToolTip(_(L("Drop to bed"))); | ||||
|                 m_drop_to_bed_button = btn; | ||||
|                 auto sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|                 sizer->Add(btn, wxBU_EXACTFIT); | ||||
|                 btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { | ||||
|                     // ???
 | ||||
|                     GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); | ||||
|                     Selection& selection = canvas->get_selection(); | ||||
| 
 | ||||
|                     if (selection.is_single_volume() || selection.is_single_modifier()) { | ||||
|                         const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); | ||||
| 
 | ||||
|                         const Geometry::Transformation& instance_trafo = volume->get_instance_transformation(); | ||||
|                         Vec3d diff = m_cache.position - instance_trafo.get_matrix(true).inverse() * Vec3d(0., 0., get_volume_min_z(volume)); | ||||
| 
 | ||||
|                         change_position_value(0, diff.x()); | ||||
|                         change_position_value(1, diff.y()); | ||||
|                         change_position_value(2, diff.z()); | ||||
|                     } | ||||
|                 }); | ||||
|             return sizer; | ||||
|             }; | ||||
|             line.append_widget(drop_to_bed_button); | ||||
|         } | ||||
|         // Add empty bmp (Its size have to be equal to PrusaLockButton) in front of "Size" option to label alignment
 | ||||
|         else if (option_name == "Size") { | ||||
|             line.near_label_widget = [this](wxWindow* parent) { | ||||
|  | @ -417,7 +466,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) | |||
| 			m_new_scale    = m_new_size.cwiseProduct(selection.get_unscaled_instance_bounding_box().size().cwiseInverse()) * 100.; | ||||
| 		} else { | ||||
| 			m_new_rotation = volume->get_instance_rotation() * (180. / M_PI); | ||||
| 			m_new_size     = volume->get_instance_transformation().get_scaling_factor().cwiseProduct((*wxGetApp().model_objects())[volume->object_idx()]->raw_mesh_bounding_box().size()); | ||||
| 			m_new_size     = volume->get_instance_transformation().get_scaling_factor().cwiseProduct(wxGetApp().model().objects[volume->object_idx()]->raw_mesh_bounding_box().size()); | ||||
| 			m_new_scale    = volume->get_instance_scaling_factor() * 100.; | ||||
| 		} | ||||
| 
 | ||||
|  | @ -441,7 +490,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) | |||
|         m_new_position = volume->get_volume_offset(); | ||||
|         m_new_rotation = volume->get_volume_rotation() * (180. / M_PI); | ||||
|         m_new_scale    = volume->get_volume_scaling_factor() * 100.; | ||||
|         m_new_size = volume->get_volume_transformation().get_scaling_factor().cwiseProduct(volume->bounding_box.size()); | ||||
|         m_new_size = volume->get_volume_transformation().get_scaling_factor().cwiseProduct(volume->bounding_box().size()); | ||||
|         m_new_enabled = true; | ||||
|     } | ||||
|     else if (obj_list->multiple_selection() || obj_list->is_selected(itInstanceRoot)) | ||||
|  | @ -536,11 +585,13 @@ void ObjectManipulation::update_reset_buttons_visibility() | |||
| 
 | ||||
|     bool show_rotation = false; | ||||
|     bool show_scale = false; | ||||
|     bool show_drop_to_bed = false; | ||||
| 
 | ||||
|     if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { | ||||
|         const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); | ||||
|         Vec3d rotation; | ||||
|         Vec3d scale; | ||||
|         double min_z = 0.; | ||||
| 
 | ||||
|         if (selection.is_single_full_instance()) { | ||||
|             rotation = volume->get_instance_rotation(); | ||||
|  | @ -549,14 +600,17 @@ void ObjectManipulation::update_reset_buttons_visibility() | |||
|         else { | ||||
|             rotation = volume->get_volume_rotation(); | ||||
|             scale = volume->get_volume_scaling_factor(); | ||||
|             min_z = get_volume_min_z(volume); | ||||
|         } | ||||
|         show_rotation = !rotation.isApprox(Vec3d::Zero()); | ||||
|         show_scale = !scale.isApprox(Vec3d::Ones()); | ||||
|         show_drop_to_bed = (std::abs(min_z) > EPSILON); | ||||
|     } | ||||
| 
 | ||||
|     wxGetApp().CallAfter([this, show_rotation, show_scale]{ | ||||
|     wxGetApp().CallAfter([this, show_rotation, show_scale, show_drop_to_bed]{ | ||||
|         m_reset_rotation_button->Show(show_rotation); | ||||
|         m_reset_scale_button->Show(show_scale); | ||||
|         m_drop_to_bed_button->Show(show_drop_to_bed); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
|  | @ -655,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(L("Set Position")); | ||||
| 
 | ||||
|     m_cache.position = position; | ||||
| 	m_cache.position_rounded(axis) = DBL_MAX; | ||||
|  | @ -686,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(L("Set Orientation")); | ||||
| 
 | ||||
|     m_cache.rotation = rotation; | ||||
| 	m_cache.rotation_rounded(axis) = DBL_MAX; | ||||
|  | @ -721,11 +775,11 @@ void ObjectManipulation::change_size_value(int axis, double value) | |||
| 
 | ||||
|     Vec3d ref_size = m_cache.size; | ||||
| 	if (selection.is_single_volume() || selection.is_single_modifier()) | ||||
| 		ref_size = selection.get_volume(*selection.get_volume_idxs().begin())->bounding_box.size(); | ||||
| 	else if (selection.is_single_full_instance()) | ||||
|         ref_size = selection.get_volume(*selection.get_volume_idxs().begin())->bounding_box().size(); | ||||
|     else if (selection.is_single_full_instance()) | ||||
| 		ref_size = m_world_coordinates ?  | ||||
|             selection.get_unscaled_instance_bounding_box().size() : | ||||
|             (*wxGetApp().model_objects())[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size(); | ||||
|             wxGetApp().model().objects[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size(); | ||||
| 
 | ||||
|     this->do_scale(axis, 100. * Vec3d(size(0) / ref_size(0), size(1) / ref_size(1), size(2) / ref_size(2))); | ||||
| 
 | ||||
|  | @ -751,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(L("Set Scale")); | ||||
| } | ||||
| 
 | ||||
| void ObjectManipulation::on_change(t_config_option_key opt_key, const boost::any& value) | ||||
|  | @ -848,7 +902,7 @@ void ObjectManipulation::set_uniform_scaling(const bool new_value) | |||
|                 return; | ||||
|             } | ||||
|             // Bake the rotation into the meshes of the object.
 | ||||
|             (*wxGetApp().model_objects())[volume->composite_id.object_id]->bake_xy_rotation_into_meshes(volume->composite_id.instance_id); | ||||
|             wxGetApp().model().objects[volume->composite_id.object_id]->bake_xy_rotation_into_meshes(volume->composite_id.instance_id); | ||||
|             // Update the 3D scene, selections etc.
 | ||||
|             wxGetApp().plater()->update(); | ||||
|             // Recalculate cached values at this panel, refresh the screen.
 | ||||
|  | @ -869,6 +923,7 @@ void ObjectManipulation::msw_rescale() | |||
|     m_mirror_bitmap_hidden.msw_rescale(); | ||||
|     m_reset_scale_button->msw_rescale(); | ||||
|     m_reset_rotation_button->msw_rescale(); | ||||
|     m_drop_to_bed_button->msw_rescale(); | ||||
| 
 | ||||
|     get_og()->msw_rescale(); | ||||
| } | ||||
|  |  | |||
|  | @ -56,6 +56,7 @@ class ObjectManipulation : public OG_Settings | |||
|     // Non-owning pointers to the reset buttons, so we can hide and show them.
 | ||||
|     ScalableButton* m_reset_scale_button = nullptr; | ||||
|     ScalableButton* m_reset_rotation_button = nullptr; | ||||
|     ScalableButton* m_drop_to_bed_button = nullptr; | ||||
| 
 | ||||
|     // Mirroring buttons and their current state
 | ||||
|     enum MirrorButtonState { | ||||
|  |  | |||
|  | @ -63,18 +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()->m_objects_model; | ||||
|     auto config         = wxGetApp().obj_list()->m_config; | ||||
|     auto objects_model  = wxGetApp().obj_list()->GetModel(); | ||||
|     auto config         = wxGetApp().obj_list()->config(); | ||||
| 
 | ||||
|     const auto item = objects_ctrl->GetSelection(); | ||||
|     if (item && !objects_ctrl->multiple_selection() &&  | ||||
|         config && objects_model->IsSettingsItem(item)) | ||||
| 	{ | ||||
|      | ||||
|     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()); | ||||
| 
 | ||||
|         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
 | ||||
|  | @ -94,85 +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") | ||||
|             // 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_extriders_cat = cat.first == "Extruders"; | ||||
|                 for (auto& opt : cat.second) | ||||
|                 { | ||||
|                     if (opt == "extruder") | ||||
|                         continue; | ||||
|                     Option option = optgroup->get_option(opt); | ||||
|                     option.opt.width = 12; | ||||
|                     if (is_extriders_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(); | ||||
| }; | ||||
|  |  | |||
|  | @ -63,7 +63,8 @@ bool View3D::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_ | |||
|     m_canvas->set_config(config); | ||||
|     m_canvas->enable_gizmos(true); | ||||
|     m_canvas->enable_selection(true); | ||||
|     m_canvas->enable_toolbar(true); | ||||
|     m_canvas->enable_main_toolbar(true); | ||||
|     m_canvas->enable_undoredo_toolbar(true); | ||||
| 
 | ||||
|     wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); | ||||
|     main_sizer->Add(m_canvas_widget, 1, wxALL | wxEXPAND, 0); | ||||
|  |  | |||
|  | @ -132,18 +132,13 @@ void GLGizmoBase::Grabber::render_face(float half_size) const | |||
|     glsafe(::glEnd()); | ||||
| } | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
| 
 | ||||
| GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) | ||||
| #else | ||||
| GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, unsigned int sprite_id) | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     : m_parent(parent) | ||||
|     , m_group_id(-1) | ||||
|     , m_state(Off) | ||||
|     , m_shortcut_key(0) | ||||
| #if ENABLE_SVG_ICONS | ||||
|     , m_icon_filename(icon_filename) | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     , m_sprite_id(sprite_id) | ||||
|     , m_hover_id(-1) | ||||
|     , m_dragging(false) | ||||
|  | @ -185,7 +180,7 @@ void GLGizmoBase::disable_grabber(unsigned int id) | |||
|     on_disable_grabber(id); | ||||
| } | ||||
| 
 | ||||
| void GLGizmoBase::start_dragging(const Selection& selection) | ||||
| void GLGizmoBase::start_dragging() | ||||
| { | ||||
|     m_dragging = true; | ||||
| 
 | ||||
|  | @ -194,7 +189,7 @@ void GLGizmoBase::start_dragging(const Selection& selection) | |||
|         m_grabbers[i].dragging = (m_hover_id == i); | ||||
|     } | ||||
| 
 | ||||
|     on_start_dragging(selection); | ||||
|     on_start_dragging(); | ||||
| } | ||||
| 
 | ||||
| void GLGizmoBase::stop_dragging() | ||||
|  | @ -209,10 +204,10 @@ void GLGizmoBase::stop_dragging() | |||
|     on_stop_dragging(); | ||||
| } | ||||
| 
 | ||||
| void GLGizmoBase::update(const UpdateData& data, const Selection& selection) | ||||
| void GLGizmoBase::update(const UpdateData& data) | ||||
| { | ||||
|     if (m_hover_id != -1) | ||||
|         on_update(data, selection); | ||||
|         on_update(data); | ||||
| } | ||||
| 
 | ||||
| std::array<float, 3> GLGizmoBase::picking_color_component(unsigned int id) const | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ | |||
| #include "slic3r/GUI/I18N.hpp" | ||||
| #include "slic3r/GUI/Selection.hpp" | ||||
| 
 | ||||
| #include <cereal/archives/binary.hpp> | ||||
| 
 | ||||
| class wxWindow; | ||||
| class GLUquadric; | ||||
|  | @ -75,10 +76,10 @@ public: | |||
| 
 | ||||
|     struct UpdateData | ||||
|     { | ||||
|         const Linef3 mouse_ray; | ||||
|         const Point* mouse_pos; | ||||
|         const Linef3& mouse_ray; | ||||
|         const Point& mouse_pos; | ||||
| 
 | ||||
|         UpdateData(const Linef3& mouse_ray, const Point* mouse_pos = nullptr) | ||||
|         UpdateData(const Linef3& mouse_ray, const Point& mouse_pos) | ||||
|             : mouse_ray(mouse_ray), mouse_pos(mouse_pos) | ||||
|         {} | ||||
|     }; | ||||
|  | @ -89,9 +90,7 @@ protected: | |||
|     int m_group_id; | ||||
|     EState m_state; | ||||
|     int m_shortcut_key; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     std::string m_icon_filename; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     unsigned int m_sprite_id; | ||||
|     int m_hover_id; | ||||
|     bool m_dragging; | ||||
|  | @ -102,15 +101,14 @@ protected: | |||
|     ImGuiWrapper* m_imgui; | ||||
| 
 | ||||
| public: | ||||
| #if ENABLE_SVG_ICONS | ||||
|     GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); | ||||
| #else | ||||
|     GLGizmoBase(GLCanvas3D& parent, unsigned int sprite_id); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     virtual ~GLGizmoBase() {} | ||||
| 
 | ||||
|     bool init() { return on_init(); } | ||||
| 
 | ||||
|     void load(cereal::BinaryInputArchive& ar) { m_state = On; on_load(ar); } | ||||
|     void save(cereal::BinaryOutputArchive& ar) const { on_save(ar); } | ||||
| 
 | ||||
|     std::string get_name() const { return on_get_name(); } | ||||
| 
 | ||||
|     int get_group_id() const { return m_group_id; } | ||||
|  | @ -122,11 +120,9 @@ public: | |||
|     int get_shortcut_key() const { return m_shortcut_key; } | ||||
|     void set_shortcut_key(int key) { m_shortcut_key = key; } | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     const std::string& get_icon_filename() const { return m_icon_filename; } | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     bool is_activable(const Selection& selection) const { return on_is_activable(selection); } | ||||
|     bool is_activable() const { return on_is_activable(); } | ||||
|     bool is_selectable() const { return on_is_selectable(); } | ||||
| 
 | ||||
|     unsigned int get_sprite_id() const { return m_sprite_id; } | ||||
|  | @ -139,32 +135,34 @@ public: | |||
|     void enable_grabber(unsigned int id); | ||||
|     void disable_grabber(unsigned int id); | ||||
| 
 | ||||
|     void start_dragging(const Selection& selection); | ||||
|     void start_dragging(); | ||||
|     void stop_dragging(); | ||||
| 
 | ||||
|     bool is_dragging() const { return m_dragging; } | ||||
| 
 | ||||
|     void update(const UpdateData& data, const Selection& selection); | ||||
|     void update(const UpdateData& data); | ||||
| 
 | ||||
|     void render(const Selection& selection) const { on_render(selection); } | ||||
|     void render_for_picking(const Selection& selection) const { on_render_for_picking(selection); } | ||||
|     void render_input_window(float x, float y, float bottom_limit, const Selection& selection) { on_render_input_window(x, y, bottom_limit, selection); } | ||||
|     void render() const { on_render(); } | ||||
|     void render_for_picking() const { on_render_for_picking(); } | ||||
|     void render_input_window(float x, float y, float bottom_limit) { on_render_input_window(x, y, bottom_limit); } | ||||
| 
 | ||||
| protected: | ||||
|     virtual bool on_init() = 0; | ||||
|     virtual void on_load(cereal::BinaryInputArchive& ar) {} | ||||
|     virtual void on_save(cereal::BinaryOutputArchive& ar) const {} | ||||
|     virtual std::string on_get_name() const = 0; | ||||
|     virtual void on_set_state() {} | ||||
|     virtual void on_set_hover_id() {} | ||||
|     virtual bool on_is_activable(const Selection& selection) const { return true; } | ||||
|     virtual bool on_is_activable() const { return true; } | ||||
|     virtual bool on_is_selectable() const { return true; } | ||||
|     virtual void on_enable_grabber(unsigned int id) {} | ||||
|     virtual void on_disable_grabber(unsigned int id) {} | ||||
|     virtual void on_start_dragging(const Selection& selection) {} | ||||
|     virtual void on_start_dragging() {} | ||||
|     virtual void on_stop_dragging() {} | ||||
|     virtual void on_update(const UpdateData& data, const Selection& selection) = 0; | ||||
|     virtual void on_render(const Selection& selection) const = 0; | ||||
|     virtual void on_render_for_picking(const Selection& selection) const = 0; | ||||
|     virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) {} | ||||
|     virtual void on_update(const UpdateData& data) {} | ||||
|     virtual void on_render() const = 0; | ||||
|     virtual void on_render_for_picking() const = 0; | ||||
|     virtual void on_render_input_window(float x, float y, float bottom_limit) {} | ||||
| 
 | ||||
|     // Returns the picking color for the given id, based on the BASE_ID constant
 | ||||
|     // No check is made for clashing with other picking color (i.e. GLVolumes)
 | ||||
|  |  | |||
Some files were not shown because too many files have changed in this diff Show more
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 bubnikv
						bubnikv