mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-31 04:31:15 -06:00 
			
		
		
		
	Merge remote-tracking branch 'origin/master' into ys_hdpi
This commit is contained in:
		
						commit
						e2b8c3e33c
					
				
					 49 changed files with 1198 additions and 760 deletions
				
			
		|  | @ -717,6 +717,7 @@ void GCode::_do_export(Print &print, FILE *file) | |||
|     // Prepare the helper object for replacing placeholders in custom G-code and output filename.
 | ||||
|     m_placeholder_parser = print.placeholder_parser(); | ||||
|     m_placeholder_parser.update_timestamp(); | ||||
|     print.update_object_placeholders(m_placeholder_parser.config_writable()); | ||||
| 
 | ||||
|     // Get optimal tool ordering to minimize tool switches of a multi-exruder print.
 | ||||
|     // For a print by objects, find the 1st printing object.
 | ||||
|  | @ -1622,6 +1623,8 @@ void GCode::process_layer( | |||
| 
 | ||||
|                 unsigned int copy_id = 0; | ||||
|                 for (const Point © : copies) { | ||||
|                     if (this->config().gcode_label_objects) | ||||
|                         gcode += std::string("; printing object ") + print_object->model_object()->name + " id:" + std::to_string(layer_id) + " copy " + std::to_string(copy_id) + "\n"; | ||||
|                     // When starting a new object, use the external motion planner for the first travel move.
 | ||||
|                     std::pair<const PrintObject*, Point> this_object_copy(print_object, copy); | ||||
|                     if (m_last_obj_copy != this_object_copy) | ||||
|  | @ -1646,7 +1649,9 @@ void GCode::process_layer( | |||
|                             gcode += this->extrude_infill(print,by_region_specific); | ||||
|                         } | ||||
|                     } | ||||
|                     ++copy_id; | ||||
|                     if (this->config().gcode_label_objects) | ||||
| 						gcode += std::string("; stop printing object ") + print_object->model_object()->name + " id:" + std::to_string(layer_id) + " copy " + std::to_string(copy_id) + "\n"; | ||||
|                     ++ copy_id; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  |  | |||
|  | @ -83,7 +83,7 @@ struct CoolingLine | |||
| struct PerExtruderAdjustments  | ||||
| { | ||||
|     // Calculate the total elapsed time per this extruder, adjusted for the slowdown.
 | ||||
|     float elapsed_time_total() { | ||||
|     float elapsed_time_total() const { | ||||
|         float time_total = 0.f; | ||||
|         for (const CoolingLine &line : lines) | ||||
|             time_total += line.time; | ||||
|  | @ -91,7 +91,7 @@ struct PerExtruderAdjustments | |||
|     } | ||||
|     // Calculate the total elapsed time when slowing down 
 | ||||
|     // to the minimum extrusion feed rate defined for the current material.
 | ||||
|     float maximum_time_after_slowdown(bool slowdown_external_perimeters) { | ||||
|     float maximum_time_after_slowdown(bool slowdown_external_perimeters) const { | ||||
|         float time_total = 0.f; | ||||
|         for (const CoolingLine &line : lines) | ||||
|             if (line.adjustable(slowdown_external_perimeters)) { | ||||
|  | @ -104,7 +104,7 @@ struct PerExtruderAdjustments | |||
|         return time_total; | ||||
|     } | ||||
|     // Calculate the adjustable part of the total time.
 | ||||
|     float adjustable_time(bool slowdown_external_perimeters) { | ||||
|     float adjustable_time(bool slowdown_external_perimeters) const { | ||||
|         float time_total = 0.f; | ||||
|         for (const CoolingLine &line : lines) | ||||
|             if (line.adjustable(slowdown_external_perimeters)) | ||||
|  | @ -112,7 +112,7 @@ struct PerExtruderAdjustments | |||
|         return time_total; | ||||
|     } | ||||
|     // Calculate the non-adjustable part of the total time.
 | ||||
|     float non_adjustable_time(bool slowdown_external_perimeters) { | ||||
|     float non_adjustable_time(bool slowdown_external_perimeters) const { | ||||
|         float time_total = 0.f; | ||||
|         for (const CoolingLine &line : lines) | ||||
|             if (! line.adjustable(slowdown_external_perimeters)) | ||||
|  | @ -169,7 +169,7 @@ struct PerExtruderAdjustments | |||
|     // Calculate the maximum time stretch when slowing down to min_feedrate.
 | ||||
|     // Slowdown to min_feedrate shall be allowed for this extruder's material.
 | ||||
|     // Used by non-proportional slow down.
 | ||||
|     float time_stretch_when_slowing_down_to_feedrate(float min_feedrate) { | ||||
|     float time_stretch_when_slowing_down_to_feedrate(float min_feedrate) const { | ||||
|         float time_stretch = 0.f; | ||||
|         assert(this->min_print_speed < min_feedrate + EPSILON); | ||||
|         for (size_t i = 0; i < n_lines_adjustable; ++ i) { | ||||
|  | @ -221,6 +221,63 @@ struct PerExtruderAdjustments | |||
|     size_t                      idx_line_end        = 0; | ||||
| }; | ||||
| 
 | ||||
| // Calculate a new feedrate when slowing down by time_stretch for segments faster than min_feedrate.
 | ||||
| // Used by non-proportional slow down.
 | ||||
| float new_feedrate_to_reach_time_stretch( | ||||
|     std::vector<PerExtruderAdjustments*>::const_iterator it_begin, std::vector<PerExtruderAdjustments*>::const_iterator it_end,  | ||||
|     float min_feedrate, float time_stretch, size_t max_iter = 20) | ||||
| { | ||||
| 	float new_feedrate = min_feedrate; | ||||
|     for (size_t iter = 0; iter < max_iter; ++ iter) { | ||||
|         float nomin = 0; | ||||
|         float denom = time_stretch; | ||||
|         for (auto it = it_begin; it != it_end; ++ it) { | ||||
| 			assert((*it)->min_print_speed < min_feedrate + EPSILON); | ||||
| 			for (size_t i = 0; i < (*it)->n_lines_adjustable; ++i) { | ||||
| 				const CoolingLine &line = (*it)->lines[i]; | ||||
|                 if (line.feedrate > min_feedrate) { | ||||
|                     nomin += line.time * line.feedrate; | ||||
|                     denom += line.time; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         assert(denom > 0); | ||||
|         if (denom < 0) | ||||
|             return min_feedrate; | ||||
|         new_feedrate = nomin / denom; | ||||
|         assert(new_feedrate > min_feedrate - EPSILON); | ||||
|         if (new_feedrate < min_feedrate + EPSILON) | ||||
|             goto finished; | ||||
|         for (auto it = it_begin; it != it_end; ++ it) | ||||
| 			for (size_t i = 0; i < (*it)->n_lines_adjustable; ++i) { | ||||
| 				const CoolingLine &line = (*it)->lines[i]; | ||||
|                 if (line.feedrate > min_feedrate && line.feedrate < new_feedrate) | ||||
|                     // Some of the line segments taken into account in the calculation of nomin / denom are now slower than new_feedrate, 
 | ||||
|                     // which makes the new_feedrate lower than it should be.
 | ||||
|                     // Re-run the calculation with a new min_feedrate limit, so that the segments with current feedrate lower than new_feedrate
 | ||||
|                     // are not taken into account.
 | ||||
|                     goto not_finished_yet; | ||||
|             } | ||||
|         goto finished; | ||||
| not_finished_yet: | ||||
|         min_feedrate = new_feedrate; | ||||
|     } | ||||
|     // Failed to find the new feedrate for the time_stretch.
 | ||||
| 
 | ||||
| finished: | ||||
|     // Test whether the time_stretch was achieved.
 | ||||
| #ifndef NDEBUG | ||||
|     { | ||||
|         float time_stretch_final = 0.f; | ||||
|         for (auto it = it_begin; it != it_end; ++ it) | ||||
|             time_stretch_final += (*it)->time_stretch_when_slowing_down_to_feedrate(new_feedrate); | ||||
|         assert(std::abs(time_stretch - time_stretch_final) < EPSILON); | ||||
|     } | ||||
| #endif /* NDEBUG */ | ||||
| 
 | ||||
| 	return new_feedrate; | ||||
| } | ||||
| 
 | ||||
| std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_id) | ||||
| { | ||||
|     std::vector<PerExtruderAdjustments> per_extruder_adjustments = this->parse_layer_gcode(gcode, m_current_pos); | ||||
|  | @ -241,12 +298,12 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std:: | |||
|     std::vector<PerExtruderAdjustments> per_extruder_adjustments(extruders.size()); | ||||
|     std::vector<size_t>                 map_extruder_to_per_extruder_adjustment(num_extruders, 0); | ||||
|     for (size_t i = 0; i < extruders.size(); ++ i) { | ||||
| 		PerExtruderAdjustments &adj			= per_extruder_adjustments[i]; | ||||
| 		unsigned int			extruder_id = extruders[i].id(); | ||||
| 		adj.extruder_id				  = extruder_id; | ||||
| 		adj.cooling_slow_down_enabled = config.cooling.get_at(extruder_id); | ||||
| 		adj.slowdown_below_layer_time = config.slowdown_below_layer_time.get_at(extruder_id); | ||||
| 		adj.min_print_speed			  = config.min_print_speed.get_at(extruder_id); | ||||
|         PerExtruderAdjustments &adj         = per_extruder_adjustments[i]; | ||||
|         unsigned int            extruder_id = extruders[i].id(); | ||||
|         adj.extruder_id               = extruder_id; | ||||
|         adj.cooling_slow_down_enabled = config.cooling.get_at(extruder_id); | ||||
|         adj.slowdown_below_layer_time = config.slowdown_below_layer_time.get_at(extruder_id); | ||||
|         adj.min_print_speed           = config.min_print_speed.get_at(extruder_id); | ||||
|         map_extruder_to_per_extruder_adjustment[extruder_id] = i; | ||||
|     } | ||||
| 
 | ||||
|  | @ -452,14 +509,14 @@ static inline void extruder_range_slow_down_non_proportional( | |||
|     std::vector<PerExtruderAdjustments*> by_min_print_speed(it_begin, it_end); | ||||
|     // Find the next highest adjustable feedrate among the extruders.
 | ||||
|     float feedrate = 0; | ||||
| 	for (PerExtruderAdjustments *adj : by_min_print_speed) { | ||||
| 		adj->idx_line_begin = 0; | ||||
| 		adj->idx_line_end   = 0; | ||||
| 		assert(adj->idx_line_begin < adj->n_lines_adjustable); | ||||
| 		if (adj->lines[adj->idx_line_begin].feedrate > feedrate) | ||||
| 			feedrate = adj->lines[adj->idx_line_begin].feedrate; | ||||
| 	} | ||||
| 	assert(feedrate > 0.f); | ||||
|     for (PerExtruderAdjustments *adj : by_min_print_speed) { | ||||
|         adj->idx_line_begin = 0; | ||||
|         adj->idx_line_end   = 0; | ||||
|         assert(adj->idx_line_begin < adj->n_lines_adjustable); | ||||
|         if (adj->lines[adj->idx_line_begin].feedrate > feedrate) | ||||
|             feedrate = adj->lines[adj->idx_line_begin].feedrate; | ||||
|     } | ||||
|     assert(feedrate > 0.f); | ||||
|     // Sort by min_print_speed, maximum speed first.
 | ||||
|     std::sort(by_min_print_speed.begin(), by_min_print_speed.end(),  | ||||
|         [](const PerExtruderAdjustments *p1, const PerExtruderAdjustments *p2){ return p1->min_print_speed > p2->min_print_speed; }); | ||||
|  | @ -496,7 +553,7 @@ static inline void extruder_range_slow_down_non_proportional( | |||
|                 for (auto it = adj; it != by_min_print_speed.end(); ++ it) | ||||
|                     time_stretch_max += (*it)->time_stretch_when_slowing_down_to_feedrate(feedrate_limit); | ||||
|                 if (time_stretch_max >= time_stretch) { | ||||
|                     feedrate_limit = feedrate - (feedrate - feedrate_limit) * time_stretch / time_stretch_max; | ||||
|                     feedrate_limit = new_feedrate_to_reach_time_stretch(adj, by_min_print_speed.end(), feedrate_limit, time_stretch, 20); | ||||
|                     done = true; | ||||
|                 } else | ||||
|                     time_stretch -= time_stretch_max; | ||||
|  |  | |||
|  | @ -9,7 +9,7 @@ namespace Slic3r { | |||
| 
 | ||||
| class GCode; | ||||
| class Layer; | ||||
| class PerExtruderAdjustments; | ||||
| struct PerExtruderAdjustments; | ||||
| 
 | ||||
| // A standalone G-code filter, to control cooling of the print.
 | ||||
| // The G-code is processed per layer. Once a layer is collected, fan start / stop commands are edited
 | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ class SpiralVase { | |||
|     SpiralVase(const PrintConfig &config) | ||||
|         : enable(false), _config(&config) | ||||
|     { | ||||
|         this->_reader.z() = this->_config->z_offset; | ||||
|         this->_reader.z() = (float)this->_config->z_offset; | ||||
|         this->_reader.apply_config(*this->_config); | ||||
|     }; | ||||
|     std::string process_layer(const std::string &gcode); | ||||
|  |  | |||
|  | @ -43,7 +43,7 @@ public: | |||
|         } | ||||
|         bool cmd_is(const char *cmd_test) const { | ||||
|             const char *cmd = GCodeReader::skip_whitespaces(m_raw.c_str()); | ||||
|             int len = strlen(cmd_test);  | ||||
|             size_t len = strlen(cmd_test);  | ||||
|             return strncmp(cmd, cmd_test, len) == 0 && GCodeReader::is_end_of_word(cmd[len]); | ||||
|         } | ||||
|         bool extruding(const GCodeReader &reader)  const { return this->cmd_is("G1") && this->dist_E(reader) > 0; } | ||||
|  |  | |||
|  | @ -1182,8 +1182,6 @@ Vec3d extract_euler_angles(const Eigen::Matrix<double, 3, 3, Eigen::DontAlign>& | |||
| { | ||||
| #if ENABLE_NEW_EULER_ANGLES | ||||
|     // reference: http://www.gregslabaugh.net/publications/euler.pdf
 | ||||
|     auto is_approx = [](double value, double test_value) -> bool { return std::abs(value - test_value) < EPSILON; }; | ||||
| 
 | ||||
|     Vec3d angles1 = Vec3d::Zero(); | ||||
|     Vec3d angles2 = Vec3d::Zero(); | ||||
|     if (is_approx(std::abs(rotation_matrix(2, 0)), 1.0)) | ||||
|  |  | |||
|  | @ -547,13 +547,33 @@ void Model::reset_auto_extruder_id() | |||
|     s_auto_extruder_id = 1; | ||||
| } | ||||
| 
 | ||||
| std::string Model::propose_export_file_name() const | ||||
| // Propose a filename including path derived from the ModelObject's input path.
 | ||||
| // If object's name is filled in, use the object name, otherwise use the input name.
 | ||||
| std::string Model::propose_export_file_name_and_path() const | ||||
| { | ||||
|     std::string input_file; | ||||
|     for (const ModelObject *model_object : this->objects) | ||||
|         for (ModelInstance *model_instance : model_object->instances) | ||||
|             if (model_instance->is_printable()) | ||||
|                 return model_object->name.empty() ? model_object->input_file : model_object->name; | ||||
|     return std::string(); | ||||
|             if (model_instance->is_printable()) { | ||||
|                 input_file = model_object->input_file; | ||||
|                 if (! model_object->name.empty()) { | ||||
|                     if (input_file.empty()) | ||||
|                         // model_object->input_file was empty, just use model_object->name
 | ||||
|                         input_file = model_object->name; | ||||
|                     else { | ||||
|                         // Replace file name in input_file with model_object->name, but keep the path and file extension.
 | ||||
| 						input_file = (boost::filesystem::path(model_object->name).parent_path().empty()) ? | ||||
| 							(boost::filesystem::path(input_file).parent_path() / model_object->name).make_preferred().string() : | ||||
| 							model_object->name; | ||||
| 					} | ||||
|                 } | ||||
|                 if (! input_file.empty()) | ||||
|                     goto end; | ||||
|                 // Other instances will produce the same name, skip them.
 | ||||
|                 break; | ||||
|             } | ||||
| end: | ||||
|     return input_file; | ||||
| } | ||||
| 
 | ||||
| ModelObject::~ModelObject() | ||||
|  |  | |||
|  | @ -385,6 +385,8 @@ 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; | ||||
| 
 | ||||
| protected: | ||||
| 	friend class Print; | ||||
|     friend class SLAPrint; | ||||
|  | @ -607,8 +609,8 @@ public: | |||
|     static std::string get_auto_extruder_id_as_string(unsigned int max_extruders); | ||||
|     static void reset_auto_extruder_id(); | ||||
| 
 | ||||
|     // Propose an output file name based on the first printable object's name.
 | ||||
|     std::string         propose_export_file_name() const; | ||||
|     // Propose an output file name & path based on the first printable object's name and source input file's path.
 | ||||
|     std::string         propose_export_file_name_and_path() const; | ||||
| 
 | ||||
| private: | ||||
|     MODELBASE_DERIVED_PRIVATE_COPY_MOVE(Model) | ||||
|  |  | |||
|  | @ -32,7 +32,8 @@ public: | |||
|     void set(const std::string &key, double value)              { this->set(key, new ConfigOptionFloat(value)); } | ||||
|     void set(const std::string &key, const std::vector<std::string> &values) { this->set(key, new ConfigOptionStrings(values)); } | ||||
|     void set(const std::string &key, ConfigOption *opt)         { m_config.set_key_value(key, opt); } | ||||
|     const DynamicConfig&    config() const                      { return m_config; } | ||||
| 	DynamicConfig&			config_writable()					{ return m_config; } | ||||
| 	const DynamicConfig&    config() const                      { return m_config; } | ||||
|     const ConfigOption*     option(const std::string &key) const { return m_config.option(key); } | ||||
| 
 | ||||
|     // Fill in the template using a macro processing language.
 | ||||
|  |  | |||
|  | @ -127,6 +127,7 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option | |||
|         "first_layer_speed", | ||||
|         "gcode_comments", | ||||
|         "gcode_flavor", | ||||
|         "gcode_label_objects", | ||||
|         "infill_acceleration", | ||||
|         "layer_gcode", | ||||
|         "min_fan_speed", | ||||
|  | @ -420,8 +421,6 @@ void Print::add_model_object(ModelObject* model_object, int idx) | |||
|         src_normalized.normalize(); | ||||
|         object->config_apply(src_normalized, true); | ||||
|     } | ||||
|      | ||||
|     this->update_object_placeholders(); | ||||
| } | ||||
| 
 | ||||
| bool Print::apply_config(DynamicPrintConfig config) | ||||
|  | @ -1095,9 +1094,6 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     //FIXME there may be a race condition with the G-code export running at the background thread.
 | ||||
|     this->update_object_placeholders(); | ||||
| 
 | ||||
| #ifdef _DEBUG | ||||
|     check_model_ids_equal(m_model, model); | ||||
| #endif /* _DEBUG */ | ||||
|  | @ -1854,6 +1850,9 @@ int Print::get_extruder(const ExtrusionEntityCollection& fill, const PrintRegion | |||
|                                     std::max<int>(region.config().perimeter_extruder.value - 1, 0); | ||||
| } | ||||
| 
 | ||||
| // Generate a recommended G-code output file name based on the format template, default extension, and template parameters
 | ||||
| // (timestamps, object placeholders derived from the model, current placeholder prameters and print statistics.
 | ||||
| // Use the final print statistics if available, or just keep the print statistics placeholders if not available yet (before G-code is finalized).
 | ||||
| std::string Print::output_filename() const  | ||||
| {  | ||||
|     // Set the placeholders for the data know first after the G-code export is finished.
 | ||||
|  |  | |||
|  | @ -15,7 +15,7 @@ namespace Slic3r | |||
| size_t PrintStateBase::g_last_timestamp = 0; | ||||
| 
 | ||||
| // Update "scale", "input_filename", "input_filename_base" placeholders from the current m_objects.
 | ||||
| void PrintBase::update_object_placeholders() | ||||
| void PrintBase::update_object_placeholders(DynamicConfig &config) const | ||||
| { | ||||
|     // get the first input file name
 | ||||
|     std::string input_file; | ||||
|  | @ -33,27 +33,29 @@ void PrintBase::update_object_placeholders() | |||
| 				"% y:" + boost::lexical_cast<std::string>(printable->get_scaling_factor(Y) * 100) + | ||||
| 				"% z:" + boost::lexical_cast<std::string>(printable->get_scaling_factor(Z) * 100) + "%"); | ||||
| 	        if (input_file.empty()) | ||||
| 	            input_file = model_object->input_file; | ||||
| 	            input_file = model_object->name.empty() ? model_object->input_file : model_object->name; | ||||
| 	    } | ||||
|     } | ||||
|      | ||||
|     PlaceholderParser &pp = m_placeholder_parser; | ||||
|     pp.set("scale", v_scale); | ||||
|     config.set_key_value("year", new ConfigOptionStrings(v_scale)); | ||||
|     if (! input_file.empty()) { | ||||
|         // get basename with and without suffix
 | ||||
|         const std::string input_basename = boost::filesystem::path(input_file).filename().string(); | ||||
|         pp.set("input_filename", input_basename); | ||||
|         config.set_key_value("input_filename", new ConfigOptionString(input_basename)); | ||||
|         const std::string input_basename_base = input_basename.substr(0, input_basename.find_last_of(".")); | ||||
|         pp.set("input_filename_base", input_basename_base); | ||||
|         config.set_key_value("input_filename_base", new ConfigOptionString(input_basename_base)); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // Generate an output file name based on the format template, default extension, and template parameters
 | ||||
| // (timestamps, object placeholders derived from the model, current placeholder prameters, print statistics - config_override)
 | ||||
| std::string PrintBase::output_filename(const std::string &format, const std::string &default_ext, const DynamicConfig *config_override) const | ||||
| { | ||||
|     DynamicConfig cfg; | ||||
|     if (config_override != nullptr) | ||||
|     	cfg = *config_override; | ||||
|     PlaceholderParser::update_timestamp(cfg); | ||||
|     this->update_object_placeholders(cfg); | ||||
|     try { | ||||
|         boost::filesystem::path filename = this->placeholder_parser().process(format, 0, &cfg); | ||||
|         if (filename.extension().empty()) | ||||
|  | @ -67,20 +69,9 @@ std::string PrintBase::output_filename(const std::string &format, const std::str | |||
| std::string PrintBase::output_filepath(const std::string &path) const | ||||
| { | ||||
|     // if we were supplied no path, generate an automatic one based on our first object's input file
 | ||||
|     if (path.empty()) { | ||||
|     if (path.empty()) | ||||
|         // get the first input file name
 | ||||
|         std::string input_file; | ||||
| 		for (const ModelObject *model_object : m_model.objects) { | ||||
| 			for (ModelInstance *model_instance : model_object->instances) | ||||
| 				if (model_instance->is_printable()) { | ||||
| 					input_file = model_object->input_file; | ||||
| 					break; | ||||
| 				} | ||||
|             if (! input_file.empty()) | ||||
|                 break; | ||||
|         } | ||||
|         return (boost::filesystem::path(input_file).parent_path() / this->output_filename()).make_preferred().string(); | ||||
|     } | ||||
|         return (boost::filesystem::path(m_model.propose_export_file_name_and_path()).parent_path() / this->output_filename()).make_preferred().string(); | ||||
|      | ||||
|     // if we were supplied a directory, use it and append our automatically generated filename
 | ||||
|     boost::filesystem::path p(path); | ||||
|  |  | |||
|  | @ -309,7 +309,7 @@ protected: | |||
|     // To be called by this->output_filename() with the format string pulled from the configuration layer.
 | ||||
|     std::string            output_filename(const std::string &format, const std::string &default_ext, const DynamicConfig *config_override = nullptr) const; | ||||
|     // Update "scale", "input_filename", "input_filename_base" placeholders from the current printable ModelObjects.
 | ||||
|     void                   update_object_placeholders(); | ||||
|     void                   update_object_placeholders(DynamicConfig &config) const; | ||||
| 
 | ||||
| 	Model                                   m_model; | ||||
| 
 | ||||
|  |  | |||
|  | @ -930,6 +930,15 @@ void PrintConfigDef::init_fff_params() | |||
|     def->mode = comExpert; | ||||
|     def->default_value = new ConfigOptionEnum<GCodeFlavor>(gcfRepRap); | ||||
| 
 | ||||
|     def = this->add("gcode_label_objects", coBool); | ||||
|     def->label = "Label objects"; | ||||
|     def->tooltip = "Enable this to add comments into the G-Code labeling print moves with what object they belong to," | ||||
|                    " which is useful for the Octoprint CancelObject plugin. This settings is NOT compatible with " | ||||
|                    "Single Extruder Multi Material setup and Wipe into Object / Wipe into Infill."; | ||||
|     def->cli = "gcode-label-objects!"; | ||||
|     def->mode = comAdvanced; | ||||
|     def->default_value = new ConfigOptionBool(0); | ||||
| 
 | ||||
|     def = this->add("high_current_on_filament_swap", coBool); | ||||
|     def->label = L("High extruder current on filament swap"); | ||||
|     def->tooltip = L("It may be beneficial to increase the extruder motor current during the filament exchange" | ||||
|  |  | |||
|  | @ -619,6 +619,7 @@ public: | |||
|     ConfigOptionStrings             filament_ramming_parameters; | ||||
|     ConfigOptionBool                gcode_comments; | ||||
|     ConfigOptionEnum<GCodeFlavor>   gcode_flavor; | ||||
|     ConfigOptionBool                gcode_label_objects; | ||||
|     ConfigOptionString              layer_gcode; | ||||
|     ConfigOptionFloat               max_print_speed; | ||||
|     ConfigOptionFloat               max_volumetric_speed; | ||||
|  | @ -690,6 +691,7 @@ protected: | |||
|         OPT_PTR(filament_ramming_parameters); | ||||
|         OPT_PTR(gcode_comments); | ||||
|         OPT_PTR(gcode_flavor); | ||||
|         OPT_PTR(gcode_label_objects); | ||||
|         OPT_PTR(layer_gcode); | ||||
|         OPT_PTR(max_print_speed); | ||||
|         OPT_PTR(max_volumetric_speed); | ||||
|  | @ -730,7 +732,7 @@ protected: | |||
| class PrintConfig : public MachineEnvelopeConfig, public GCodeConfig | ||||
| { | ||||
|     STATIC_PRINT_CONFIG_CACHE_DERIVED(PrintConfig) | ||||
|     PrintConfig() : GCodeConfig(0) { initialize_cache(); *this = s_cache_PrintConfig.defaults(); } | ||||
| 	PrintConfig() : MachineEnvelopeConfig(0), GCodeConfig(0) { initialize_cache(); *this = s_cache_PrintConfig.defaults(); } | ||||
| public: | ||||
|     double                          min_object_distance() const; | ||||
|     static double                   min_object_distance(const ConfigBase *config); | ||||
|  | @ -808,7 +810,7 @@ public: | |||
|     ConfigOptionFloat               exp_time_first; | ||||
| 
 | ||||
| protected: | ||||
|     PrintConfig(int) : GCodeConfig(1) {} | ||||
| 	PrintConfig(int) : MachineEnvelopeConfig(1), GCodeConfig(1) {} | ||||
|     void initialize(StaticCacheBase &cache, const char *base_ptr) | ||||
|     { | ||||
|         this->MachineEnvelopeConfig::initialize(cache, base_ptr); | ||||
|  |  | |||
|  | @ -15,7 +15,7 @@ namespace Slic3r { | |||
| 
 | ||||
| SLAAutoSupports::SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector<ExPolygons>& slices, const std::vector<float>& heights,  | ||||
|     const Config& config, std::function<void(void)> throw_on_cancel) | ||||
| : m_config(config), m_V(emesh.V), m_F(emesh.F), m_throw_on_cancel(throw_on_cancel) | ||||
| : m_config(config), m_V(emesh.V()), m_F(emesh.F()), m_throw_on_cancel(throw_on_cancel) | ||||
| { | ||||
|     // FIXME: It might be safer to get rid of the rand() calls altogether, because it is probably
 | ||||
|     // not always thread-safe and can be slow if it is.
 | ||||
|  | @ -332,4 +332,4 @@ void SLAAutoSupports::project_upward_onto_mesh(std::vector<Vec3d>& points) const | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| } // namespace Slic3r
 | ||||
| } // namespace Slic3r
 | ||||
|  |  | |||
|  | @ -28,7 +28,7 @@ std::array<double, 3> find_best_rotation(const ModelObject& modelobj, | |||
| 
 | ||||
|     // We will use only one instance of this converted mesh to examine different
 | ||||
|     // rotations
 | ||||
|     EigenMesh3D emesh = to_eigenmesh(modelobj); | ||||
|     EigenMesh3D emesh(modelobj.raw_mesh()); | ||||
| 
 | ||||
|     // For current iteration number
 | ||||
|     unsigned status = 0; | ||||
|  | @ -68,12 +68,12 @@ std::array<double, 3> find_best_rotation(const ModelObject& modelobj, | |||
|         // area. The current function is only an example of how to optimize.
 | ||||
| 
 | ||||
|         // Later we can add more criteria like the number of overhangs, etc...
 | ||||
|         for(int i = 0; i < m.F.rows(); i++) { | ||||
|             auto idx = m.F.row(i); | ||||
|         for(int i = 0; i < m.F().rows(); i++) { | ||||
|             auto idx = m.F().row(i); | ||||
| 
 | ||||
|             Vec3d p1 = m.V.row(idx(0)); | ||||
|             Vec3d p2 = m.V.row(idx(1)); | ||||
|             Vec3d p3 = m.V.row(idx(2)); | ||||
|             Vec3d p1 = m.V().row(idx(0)); | ||||
|             Vec3d p2 = m.V().row(idx(1)); | ||||
|             Vec3d p3 = m.V().row(idx(2)); | ||||
| 
 | ||||
|             Eigen::Vector3d U = p2 - p1; | ||||
|             Eigen::Vector3d V = p3 - p1; | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ | |||
| #include <libslic3r/Model.hpp> | ||||
| 
 | ||||
| #include <boost/log/trivial.hpp> | ||||
| #include <tbb/parallel_for.h> | ||||
| 
 | ||||
| /**
 | ||||
|  * Terminology: | ||||
|  | @ -510,7 +511,6 @@ struct CompactBridge { | |||
| 
 | ||||
| // A wrapper struct around the base pool (pad)
 | ||||
| struct Pad { | ||||
| //    Contour3D mesh;
 | ||||
|     TriangleMesh tmesh; | ||||
|     PoolConfig cfg; | ||||
|     double zlevel = 0; | ||||
|  | @ -543,23 +543,6 @@ struct Pad { | |||
|     bool empty() const { return tmesh.facets_count() == 0; } | ||||
| }; | ||||
| 
 | ||||
| EigenMesh3D to_eigenmesh(const Contour3D& cntr) { | ||||
|     EigenMesh3D emesh; | ||||
| 
 | ||||
|     auto& V = emesh.V; | ||||
|     auto& F = emesh.F; | ||||
| 
 | ||||
|     V.resize(Eigen::Index(cntr.points.size()), 3); | ||||
|     F.resize(Eigen::Index(cntr.indices.size()), 3); | ||||
| 
 | ||||
|     for (int i = 0; i < V.rows(); ++i) { | ||||
|         V.row(i) = cntr.points[size_t(i)]; | ||||
|         F.row(i) = cntr.indices[size_t(i)]; | ||||
|     } | ||||
| 
 | ||||
|     return emesh; | ||||
| } | ||||
| 
 | ||||
| // The minimum distance for two support points to remain valid.
 | ||||
| static const double /*constexpr*/ D_SP   = 0.1; | ||||
| 
 | ||||
|  | @ -567,46 +550,6 @@ enum { // For indexing Eigen vectors as v(X), v(Y), v(Z) instead of numbers | |||
|   X, Y, Z | ||||
| }; | ||||
| 
 | ||||
| EigenMesh3D to_eigenmesh(const TriangleMesh& tmesh) { | ||||
| 
 | ||||
|     const stl_file& stl = tmesh.stl; | ||||
| 
 | ||||
|     EigenMesh3D outmesh; | ||||
| 
 | ||||
|     auto&& bb = tmesh.bounding_box(); | ||||
|     outmesh.ground_level += bb.min(Z); | ||||
| 
 | ||||
|     auto& V = outmesh.V; | ||||
|     auto& F = outmesh.F; | ||||
| 
 | ||||
|     V.resize(3*stl.stats.number_of_facets, 3); | ||||
|     F.resize(stl.stats.number_of_facets, 3); | ||||
|     for (unsigned int i = 0; i < stl.stats.number_of_facets; ++i) { | ||||
|         const stl_facet* facet = stl.facet_start+i; | ||||
|         V(3*i+0, 0) = double(facet->vertex[0](0)); | ||||
|         V(3*i+0, 1) = double(facet->vertex[0](1)); | ||||
|         V(3*i+0, 2) = double(facet->vertex[0](2)); | ||||
| 
 | ||||
|         V(3*i+1, 0) = double(facet->vertex[1](0)); | ||||
|         V(3*i+1, 1) = double(facet->vertex[1](1)); | ||||
|         V(3*i+1, 2) = double(facet->vertex[1](2)); | ||||
| 
 | ||||
|         V(3*i+2, 0) = double(facet->vertex[2](0)); | ||||
|         V(3*i+2, 1) = double(facet->vertex[2](1)); | ||||
|         V(3*i+2, 2) = double(facet->vertex[2](2)); | ||||
| 
 | ||||
|         F(i, 0) = int(3*i+0); | ||||
|         F(i, 1) = int(3*i+1); | ||||
|         F(i, 2) = int(3*i+2); | ||||
|     } | ||||
| 
 | ||||
|     return outmesh; | ||||
| } | ||||
| 
 | ||||
| EigenMesh3D to_eigenmesh(const ModelObject& modelobj) { | ||||
|     return to_eigenmesh(modelobj.raw_mesh()); | ||||
| } | ||||
| 
 | ||||
| PointSet to_point_set(const std::vector<Vec3d> &v) | ||||
| { | ||||
|     PointSet ret(v.size(), 3); | ||||
|  | @ -618,9 +561,170 @@ Vec3d model_coord(const ModelInstance& object, const Vec3f& mesh_coord) { | |||
|     return object.transform_vector(mesh_coord.cast<double>()); | ||||
| } | ||||
| 
 | ||||
| double ray_mesh_intersect(const Vec3d& s, | ||||
|                           const Vec3d& dir, | ||||
|                           const EigenMesh3D& m); | ||||
| inline double ray_mesh_intersect(const Vec3d& s, | ||||
|                                  const Vec3d& dir, | ||||
|                                  const EigenMesh3D& m) | ||||
| { | ||||
|     return m.query_ray_hit(s, dir).distance(); | ||||
| } | ||||
| 
 | ||||
| // This function will test if a future pinhead would not collide with the model
 | ||||
| // geometry. It does not take a 'Head' object because those are created after
 | ||||
| // this test.
 | ||||
| // Parameters:
 | ||||
| // s: The touching point on the model surface.
 | ||||
| // dir: This is the direction of the head from the pin to the back
 | ||||
| // r_pin, r_back: the radiuses of the pin and the back sphere
 | ||||
| // width: This is the full width from the pin center to the back center
 | ||||
| // m: The object mesh
 | ||||
| //
 | ||||
| // Optional:
 | ||||
| // samples: how many rays will be shot
 | ||||
| // safety distance: This will be added to the radiuses to have a safety distance
 | ||||
| // from the mesh.
 | ||||
| double pinhead_mesh_intersect(const Vec3d& s, | ||||
|                               const Vec3d& dir, | ||||
|                               double r_pin, | ||||
|                               double r_back, | ||||
|                               double width, | ||||
|                               const EigenMesh3D& m, | ||||
|                               unsigned samples = 8, | ||||
|                               double safety_distance = 0.001) | ||||
| { | ||||
|     // method based on:
 | ||||
|     // https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space
 | ||||
| 
 | ||||
| 
 | ||||
|     // We will shoot multiple rays from the head pinpoint in the direction of
 | ||||
|     // the pinhead robe (side) surface. The result will be the smallest hit
 | ||||
|     // distance.
 | ||||
| 
 | ||||
|     // Move away slightly from the touching point to avoid raycasting on the
 | ||||
|     // inner surface of the mesh.
 | ||||
|     Vec3d v = dir;     // Our direction (axis)
 | ||||
|     Vec3d c = s + width * dir; | ||||
|     const double& sd = safety_distance; | ||||
| 
 | ||||
|     // Two vectors that will be perpendicular to each other and to the axis.
 | ||||
|     // Values for a(X) and a(Y) are now arbitrary, a(Z) is just a placeholder.
 | ||||
|     Vec3d a(0, 1, 0), b; | ||||
| 
 | ||||
|     // The portions of the circle (the head-back circle) for which we will shoot
 | ||||
|     // rays.
 | ||||
|     std::vector<double> phis(samples); | ||||
|     for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size(); | ||||
| 
 | ||||
|     a(Z) = -(v(X)*a(X) + v(Y)*a(Y)) / v(Z); | ||||
| 
 | ||||
|     b = a.cross(v); | ||||
| 
 | ||||
|     // Now a and b vectors are perpendicular to v and to each other. Together
 | ||||
|     // they define the plane where we have to iterate with the given angles
 | ||||
|     // in the 'phis' vector
 | ||||
| 
 | ||||
|     tbb::parallel_for(size_t(0), phis.size(), | ||||
|                       [&phis, &m, sd, r_pin, r_back, s, a, b, c](size_t i) | ||||
|     { | ||||
|         double& phi = phis[i]; | ||||
|         double sinphi = std::sin(phi); | ||||
|         double cosphi = std::cos(phi); | ||||
| 
 | ||||
|         // Let's have a safety coefficient for the radiuses.
 | ||||
|         double rpscos = (sd + r_pin) * cosphi; | ||||
|         double rpssin = (sd + r_pin) * sinphi; | ||||
|         double rpbcos = (sd + r_back) * cosphi; | ||||
|         double rpbsin = (sd + r_back) * sinphi; | ||||
| 
 | ||||
|         // Point on the circle on the pin sphere
 | ||||
|         Vec3d ps(s(X) + rpscos * a(X) + rpssin * b(X), | ||||
|                  s(Y) + rpscos * a(Y) + rpssin * b(Y), | ||||
|                  s(Z) + rpscos * a(Z) + rpssin * b(Z)); | ||||
| 
 | ||||
|         // Point ps is not on mesh but can be inside or outside as well. This
 | ||||
|         // would cause many problems with ray-casting. So we query the closest
 | ||||
|         // point on the mesh to this.
 | ||||
| //        auto psq = m.signed_distance(ps);
 | ||||
| 
 | ||||
|         // This is the point on the circle on the back sphere
 | ||||
|         Vec3d p(c(X) + rpbcos * a(X) + rpbsin * b(X), | ||||
|                 c(Y) + rpbcos * a(Y) + rpbsin * b(Y), | ||||
|                 c(Z) + rpbcos * a(Z) + rpbsin * b(Z)); | ||||
| 
 | ||||
| //        Vec3d n = (p - psq.point_on_mesh()).normalized();
 | ||||
| //        phi = m.query_ray_hit(psq.point_on_mesh() + sd*n, n);
 | ||||
| 
 | ||||
|         Vec3d n = (p - ps).normalized(); | ||||
|         auto hr = m.query_ray_hit(ps + sd*n, n); | ||||
| 
 | ||||
|         if(hr.is_inside()) { // the hit is inside the model
 | ||||
|             if(hr.distance() > 2*r_pin) phi = 0; | ||||
|             else { | ||||
|                 // re-cast the ray from the outside of the object
 | ||||
|                 auto hr2 = m.query_ray_hit(ps + (hr.distance() + 2*sd)*n, n); | ||||
|                 phi = hr2.distance(); | ||||
|             } | ||||
|         } else phi = hr.distance(); | ||||
|     }); | ||||
| 
 | ||||
|     auto mit = std::min_element(phis.begin(), phis.end()); | ||||
| 
 | ||||
|     return *mit; | ||||
| } | ||||
| 
 | ||||
| // Checking bridge (pillar and stick as well) intersection with the model. If
 | ||||
| // the function is used for headless sticks, the ins_check parameter have to be
 | ||||
| // true as the beginning of the stick might be inside the model geometry.
 | ||||
| double bridge_mesh_intersect(const Vec3d& s, | ||||
|                              const Vec3d& dir, | ||||
|                              double r, | ||||
|                              const EigenMesh3D& m, | ||||
|                              bool ins_check = false, | ||||
|                              unsigned samples = 4, | ||||
|                              double safety_distance = 0.001) | ||||
| { | ||||
|     // helper vector calculations
 | ||||
|     Vec3d a(0, 1, 0), b; | ||||
|     const double& sd = safety_distance; | ||||
| 
 | ||||
|     a(Z) = -(dir(X)*a(X) + dir(Y)*a(Y)) / dir(Z); | ||||
|     b = a.cross(dir); | ||||
| 
 | ||||
|     // circle portions
 | ||||
|     std::vector<double> phis(samples); | ||||
|     for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size(); | ||||
| 
 | ||||
|     tbb::parallel_for(size_t(0), phis.size(), | ||||
|                       [&phis, &m, a, b, sd, dir, r, s, ins_check](size_t i) | ||||
|     { | ||||
|         double& phi = phis[i]; | ||||
|         double sinphi = std::sin(phi); | ||||
|         double cosphi = std::cos(phi); | ||||
| 
 | ||||
|         // Let's have a safety coefficient for the radiuses.
 | ||||
|         double rcos = (sd + r) * cosphi; | ||||
|         double rsin = (sd + r) * sinphi; | ||||
| 
 | ||||
|         // Point on the circle on the pin sphere
 | ||||
|         Vec3d p (s(X) + rcos * a(X) + rsin * b(X), | ||||
|                  s(Y) + rcos * a(Y) + rsin * b(Y), | ||||
|                  s(Z) + rcos * a(Z) + rsin * b(Z)); | ||||
| 
 | ||||
|         auto hr = m.query_ray_hit(p + sd*dir, dir); | ||||
| 
 | ||||
|         if(ins_check && hr.is_inside()) { | ||||
|             if(hr.distance() > 2*r) phi = 0; | ||||
|             else { | ||||
|                 // re-cast the ray from the outside of the object
 | ||||
|                 auto hr2 = m.query_ray_hit(p + (hr.distance() + 2*sd)*dir, dir); | ||||
|                 phi = hr2.distance(); | ||||
|             } | ||||
|         } else phi = hr.distance(); | ||||
|     }); | ||||
| 
 | ||||
|     auto mit = std::min_element(phis.begin(), phis.end()); | ||||
| 
 | ||||
|     return *mit; | ||||
| } | ||||
| 
 | ||||
| PointSet normals(const PointSet& points, const EigenMesh3D& mesh, | ||||
|                  double eps = 0.05,  // min distance from edges
 | ||||
|  | @ -640,6 +744,19 @@ ClusteredPoints cluster( | |||
|         std::function<bool(const SpatElement&, const SpatElement&)> pred, | ||||
|         unsigned max_points = 0); | ||||
| 
 | ||||
| // This class will hold the support tree meshes with some additional bookkeeping
 | ||||
| // as well. Various parts of the support geometry are stored separately and are
 | ||||
| // merged when the caller queries the merged mesh. The merged result is cached
 | ||||
| // for fast subsequent delivery of the merged mesh which can be quite complex.
 | ||||
| // An object of this class will be used as the result type during the support
 | ||||
| // generation algorithm. Parts will be added with the appropriate methods such
 | ||||
| // as add_head or add_pillar which forwards the constructor arguments and fills
 | ||||
| // the IDs of these substructures. The IDs are basically indices into the arrays
 | ||||
| // of the appropriate type (heads, pillars, etc...). One can later query e.g. a
 | ||||
| // pillar for a specific head...
 | ||||
| //
 | ||||
| // The support pad is considered an auxiliary geometry and is not part of the
 | ||||
| // merged mesh. It can be retrieved using a dedicated method (pad())
 | ||||
| class SLASupportTree::Impl { | ||||
|     std::vector<Head> m_heads; | ||||
|     std::vector<Pillar> m_pillars; | ||||
|  | @ -1001,16 +1118,22 @@ bool SLASupportTree::generate(const PointSet &points, | |||
|     // Indices of those who don't touch the ground
 | ||||
|     IndexSet noground_heads; | ||||
| 
 | ||||
|     // Groups of the 'ground_head' indices that belong into one cluster. These
 | ||||
|     // are candidates to be connected to one pillar.
 | ||||
|     ClusteredPoints ground_connectors; | ||||
| 
 | ||||
|     // A help function to translate ground head index to the actual coordinates.
 | ||||
|     auto gnd_head_pt = [&ground_heads, &head_positions] (size_t idx) { | ||||
|         return Vec3d(head_positions.row(ground_heads[idx])); | ||||
|     }; | ||||
| 
 | ||||
|     // This algorithm uses the Impl class as its output stream. It will be
 | ||||
|     // filled gradually with support elements (heads, pillars, bridges, ...)
 | ||||
|     using Result = SLASupportTree::Impl; | ||||
| 
 | ||||
|     Result& result = *m_impl; | ||||
| 
 | ||||
|     // Let's define the individual steps of the processing. We can experiment
 | ||||
|     // later with the ordering and the dependencies between them.
 | ||||
|     enum Steps { | ||||
|         BEGIN, | ||||
|         FILTER, | ||||
|  | @ -1026,14 +1149,15 @@ bool SLASupportTree::generate(const PointSet &points, | |||
|         //...
 | ||||
|     }; | ||||
| 
 | ||||
|     // Debug:
 | ||||
|     // for(int pn = 0; pn < points.rows(); ++pn) {
 | ||||
|     //     std::cout << "p " << pn << " " << points.row(pn) << std::endl;
 | ||||
|     // }
 | ||||
| 
 | ||||
| 
 | ||||
|     // t-hrow i-f c-ance-l-ed: It will be called many times so a shorthand will
 | ||||
|     // come in handy.
 | ||||
|     auto& tifcl = ctl.cancelfn; | ||||
| 
 | ||||
|     // Filtering step: here we will discard inappropriate support points and
 | ||||
|     // decide the future of the appropriate ones. We will check if a pinhead
 | ||||
|     // is applicable and adjust its angle at each support point.
 | ||||
|     // We will also merge the support points that are just too close and can be
 | ||||
|     // considered as one.
 | ||||
|     auto filterfn = [tifcl] ( | ||||
|             const SupportConfig& cfg, | ||||
|             const PointSet& points, | ||||
|  | @ -1044,10 +1168,6 @@ bool SLASupportTree::generate(const PointSet &points, | |||
|             PointSet& headless_pos, | ||||
|             PointSet& headless_norm) | ||||
|     { | ||||
|         /* ******************************************************** */ | ||||
|         /* Filtering step                                           */ | ||||
|         /* ******************************************************** */ | ||||
| 
 | ||||
|         // Get the points that are too close to each other and keep only the
 | ||||
|         // first one
 | ||||
|         auto aliases = | ||||
|  | @ -1108,6 +1228,8 @@ bool SLASupportTree::generate(const PointSet &points, | |||
|                          std::sin(azimuth) * std::sin(polar), | ||||
|                          std::cos(polar)); | ||||
| 
 | ||||
|                 nn.normalize(); | ||||
| 
 | ||||
|                 // save the head (pinpoint) position
 | ||||
|                 Vec3d hp = filt_pts.row(i); | ||||
| 
 | ||||
|  | @ -1118,11 +1240,15 @@ bool SLASupportTree::generate(const PointSet &points, | |||
| 
 | ||||
|                 // We should shoot a ray in the direction of the pinhead and
 | ||||
|                 // see if there is enough space for it
 | ||||
|                 double t = ray_mesh_intersect(hp + 0.1*nn, nn, mesh); | ||||
| 
 | ||||
|                 if(t > 2*w || std::isinf(t)) { | ||||
|                     // 2*w because of lower and upper pinhead
 | ||||
|                 double t = pinhead_mesh_intersect( | ||||
|                             hp, // touching point
 | ||||
|                             nn, | ||||
|                             cfg.head_front_radius_mm, // approx the radius
 | ||||
|                             cfg.head_back_radius_mm, | ||||
|                             w, | ||||
|                             mesh); | ||||
| 
 | ||||
|                 if(t > w || std::isinf(t)) { | ||||
|                     head_pos.row(pcount) = hp; | ||||
| 
 | ||||
|                     // save the verified and corrected normal
 | ||||
|  | @ -1144,7 +1270,8 @@ bool SLASupportTree::generate(const PointSet &points, | |||
|         headless_norm.conservativeResize(hlcount, Eigen::NoChange); | ||||
|     }; | ||||
| 
 | ||||
|     // Function to write the pinheads into the result
 | ||||
|     // Pinhead creation: based on the filtering results, the Head objects will
 | ||||
|     // be constructed (together with their triangle meshes).
 | ||||
|     auto pinheadfn = [tifcl] ( | ||||
|             const SupportConfig& cfg, | ||||
|             PointSet& head_pos, | ||||
|  | @ -1170,8 +1297,13 @@ bool SLASupportTree::generate(const PointSet &points, | |||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     // &filtered_points, &head_positions, &result, &mesh,
 | ||||
|     // &gndidx, &gndheight, &nogndidx, cfg
 | ||||
|     // Further classification of the support points with pinheads. If the
 | ||||
|     // ground is directly reachable through a vertical line parallel to the Z
 | ||||
|     // axis we consider a support point as pillar candidate. If touches the
 | ||||
|     // model geometry, it will be marked as non-ground facing and further steps
 | ||||
|     // will process it. Also, the pillars will be grouped into clusters that can
 | ||||
|     // be interconnected with bridges. Elements of these groups may or may not
 | ||||
|     // be interconnected. Here we only run the clustering algorithm.
 | ||||
|     auto classifyfn = [tifcl] ( | ||||
|             const SupportConfig& cfg, | ||||
|             const EigenMesh3D& mesh, | ||||
|  | @ -1192,23 +1324,81 @@ bool SLASupportTree::generate(const PointSet &points, | |||
|         gndidx.reserve(size_t(head_pos.rows())); | ||||
|         nogndidx.reserve(size_t(head_pos.rows())); | ||||
| 
 | ||||
|         // First we search decide which heads reach the ground and can be full
 | ||||
|         // pillars and which shall be connected to the model surface (or search
 | ||||
|         // a suitable path around the surface that leads to the ground -- TODO)
 | ||||
|         for(unsigned i = 0; i < head_pos.rows(); i++) { | ||||
|             tifcl(); | ||||
|             auto& head = result.heads()[i]; | ||||
|             auto& head = result.head(i); | ||||
| 
 | ||||
|             Vec3d dir(0, 0, -1); | ||||
|             Vec3d startpoint = head.junction_point(); | ||||
|             bool accept = false; | ||||
|             int ri = 1; | ||||
|             double t = std::numeric_limits<double>::infinity(); | ||||
|             double hw = head.width_mm; | ||||
| 
 | ||||
|             double t = ray_mesh_intersect(startpoint, dir, mesh); | ||||
|             // We will try to assign a pillar to all the pinheads. If a pillar
 | ||||
|             // would pierce the model surface, we will try to adjust slightly
 | ||||
|             // the head with so that the pillar can be deployed.
 | ||||
|             while(!accept && head.width_mm > 0) { | ||||
| 
 | ||||
|                 Vec3d startpoint = head.junction_point(); | ||||
| 
 | ||||
|                 // Collision detection
 | ||||
|                 t = bridge_mesh_intersect(startpoint, dir, head.r_back_mm, mesh); | ||||
| 
 | ||||
|                 // Precise distance measurement
 | ||||
|                 double tprec = ray_mesh_intersect(startpoint, dir, mesh); | ||||
| 
 | ||||
|                 if(std::isinf(tprec) && !std::isinf(t)) { | ||||
|                     // This is a damned case where the pillar melds into the
 | ||||
|                     // model but its center ray can reach the ground. We can
 | ||||
|                     // not route this to the ground nor to the model surface.
 | ||||
|                     head.width_mm = hw + (ri % 2? -1 : 1) * ri * head.r_back_mm; | ||||
|                 } else { | ||||
|                     accept = true; t = tprec; | ||||
| 
 | ||||
|                     auto id = head.id; | ||||
|                     // We need to regenerate the head geometry
 | ||||
|                     head = Head(head.r_back_mm, | ||||
|                                 head.r_pin_mm, | ||||
|                                 head.width_mm, | ||||
|                                 head.penetration_mm, | ||||
|                                 head.dir, | ||||
|                                 head.tr); | ||||
|                     head.id = id; | ||||
|                 } | ||||
| 
 | ||||
|                 ri++; | ||||
|             } | ||||
| 
 | ||||
|             // Save the distance from a surface in the Z axis downwards. It may
 | ||||
|             // be infinity but that is telling us that it touches the ground.
 | ||||
|             gndheight.emplace_back(t); | ||||
| 
 | ||||
|             if(std::isinf(t)) gndidx.emplace_back(i); | ||||
|             else nogndidx.emplace_back(i); | ||||
|             if(accept) { | ||||
|                 if(std::isinf(t)) gndidx.emplace_back(i); | ||||
|                 else nogndidx.emplace_back(i); | ||||
|             } else { | ||||
|                 // This is a serious issue. There was no way to deploy a pillar
 | ||||
|                 // for the given pinhead. The whole thing has to be discarded
 | ||||
|                 // leaving the model potentially unprintable.
 | ||||
|                 //
 | ||||
|                 // TODO: In the future this has to be solved by searching for
 | ||||
|                 // a path in 3D space from this support point to a suitable
 | ||||
|                 // pillar position or an existing pillar.
 | ||||
|                 // As a workaround we could mark this head as "sidehead only"
 | ||||
|                 // let it go trough the nearby pillar search in the next step.
 | ||||
|                 BOOST_LOG_TRIVIAL(warning) << "A support point at " | ||||
|                                            << head.tr.transpose() | ||||
|                                            << " had to be discarded as there is" | ||||
|                                            << " nowhere to route it."; | ||||
|                 head.invalidate(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // Transform the ground facing point indices top actual coordinates.
 | ||||
|         PointSet gnd(gndidx.size(), 3); | ||||
| 
 | ||||
|         for(size_t i = 0; i < gndidx.size(); i++) | ||||
|             gnd.row(long(i)) = head_pos.row(gndidx[i]); | ||||
| 
 | ||||
|  | @ -1228,7 +1418,8 @@ bool SLASupportTree::generate(const PointSet &points, | |||
|         }, 3); // max 3 heads to connect to one centroid
 | ||||
|     }; | ||||
| 
 | ||||
|     // Helper function for interconnecting two pillars with zig-zag bridges
 | ||||
|     // Helper function for interconnecting two pillars with zig-zag bridges.
 | ||||
|     // This is not an individual step.
 | ||||
|     auto interconnect = [&cfg]( | ||||
|             const Pillar& pillar, | ||||
|             const Pillar& nextpillar, | ||||
|  | @ -1246,7 +1437,7 @@ bool SLASupportTree::generate(const PointSet &points, | |||
|         double zstep = pillar_dist * std::tan(-cfg.tilt); | ||||
|         ej(Z) = sj(Z) + zstep; | ||||
| 
 | ||||
|         double chkd = ray_mesh_intersect(sj, dirv(sj, ej), emesh); | ||||
|         double chkd = bridge_mesh_intersect(sj, dirv(sj, ej), pillar.r, emesh); | ||||
|         double bridge_distance = pillar_dist / std::cos(-cfg.tilt); | ||||
| 
 | ||||
|         // If the pillars are so close that they touch each other,
 | ||||
|  | @ -1254,7 +1445,7 @@ bool SLASupportTree::generate(const PointSet &points, | |||
|         if(pillar_dist > 2*cfg.head_back_radius_mm && | ||||
|            bridge_distance < cfg.max_bridge_length_mm) | ||||
|             while(sj(Z) > pillar.endpoint(Z) + cfg.base_radius_mm && | ||||
|                   ej(Z) > nextpillar.endpoint(Z) + + cfg.base_radius_mm) | ||||
|                   ej(Z) > nextpillar.endpoint(Z) + cfg.base_radius_mm) | ||||
|         { | ||||
|             if(chkd >= bridge_distance) { | ||||
|                 result.add_bridge(sj, ej, pillar.r); | ||||
|  | @ -1272,9 +1463,11 @@ bool SLASupportTree::generate(const PointSet &points, | |||
|                     Vec3d bej(sj(X), sj(Y), ej(Z)); | ||||
| 
 | ||||
|                     // need to check collision for the cross stick
 | ||||
|                     double backchkd = ray_mesh_intersect(bsj, | ||||
|                                                          dirv(bsj, bej), | ||||
|                                                          emesh); | ||||
|                     double backchkd = bridge_mesh_intersect(bsj, | ||||
|                                                             dirv(bsj, bej), | ||||
|                                                             pillar.r, | ||||
|                                                             emesh); | ||||
| 
 | ||||
| 
 | ||||
|                     if(backchkd >= bridge_distance) { | ||||
|                         result.add_bridge(bsj, bej, pillar.r); | ||||
|  | @ -1283,10 +1476,15 @@ bool SLASupportTree::generate(const PointSet &points, | |||
|             } | ||||
|             sj.swap(ej); | ||||
|             ej(Z) = sj(Z) + zstep; | ||||
|             chkd = ray_mesh_intersect(sj, dirv(sj, ej), emesh); | ||||
|             chkd = bridge_mesh_intersect(sj, dirv(sj, ej), pillar.r, emesh); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     // Step: Routing the ground connected pinheads, and interconnecting them
 | ||||
|     // with additional (angled) bridges. Not all of these pinheads will be
 | ||||
|     // a full pillar (ground connected). Some will connect to a nearby pillar
 | ||||
|     // using a bridge. The max number of such side-heads for a central pillar
 | ||||
|     // is limited to avoid bad weight distribution.
 | ||||
|     auto routing_ground_fn = [gnd_head_pt, interconnect, tifcl]( | ||||
|             const SupportConfig& cfg, | ||||
|             const ClusteredPoints& gnd_clusters, | ||||
|  | @ -1361,12 +1559,12 @@ bool SLASupportTree::generate(const PointSet &points, | |||
|             // is distributed more effectively on the pillar.
 | ||||
| 
 | ||||
|             auto search_nearest = | ||||
|                     [&cfg, &result, &emesh, maxbridgelen, gndlvl] | ||||
|                     [&tifcl, &cfg, &result, &emesh, maxbridgelen, gndlvl, pradius] | ||||
|                     (SpatIndex& spindex, const Vec3d& jsh) | ||||
|             { | ||||
|                 long nearest_id = -1; | ||||
|                 const double max_len = maxbridgelen / 2; | ||||
|                 while(nearest_id < 0 && !spindex.empty()) { | ||||
|                 while(nearest_id < 0 && !spindex.empty()) { tifcl(); | ||||
|                     // loop until a suitable head is not found
 | ||||
|                     // if there is a pillar closer than the cluster center
 | ||||
|                     // (this may happen as the clustering is not perfect)
 | ||||
|  | @ -1391,10 +1589,13 @@ bool SLASupportTree::generate(const PointSet &points, | |||
|                     } | ||||
| 
 | ||||
|                     double d = distance(jp, jn); | ||||
|                     if(jn(Z) <= (gndlvl + 2*cfg.head_width_mm) || d > max_len) | ||||
| 
 | ||||
|                     if(jn(Z) <= gndlvl + 2*cfg.head_width_mm || d > max_len) | ||||
|                         break; | ||||
| 
 | ||||
|                     double chkd = ray_mesh_intersect(jp, dirv(jp, jn), emesh); | ||||
|                     double chkd = bridge_mesh_intersect(jp, dirv(jp, jn), | ||||
|                                                         pradius, | ||||
|                                                         emesh); | ||||
|                     if(chkd >= d) nearest_id = ne.second; | ||||
| 
 | ||||
|                     spindex.remove(ne); | ||||
|  | @ -1480,7 +1681,7 @@ bool SLASupportTree::generate(const PointSet &points, | |||
|             if(!ring.empty()) { | ||||
|                 // inner ring is now in 'newring' and outer ring is in 'ring'
 | ||||
|                 SpatIndex innerring; | ||||
|                 for(unsigned i : newring) { | ||||
|                 for(unsigned i : newring) { tifcl(); | ||||
|                     const Pillar& pill = result.head_pillar(gndidx[i]); | ||||
|                     assert(pill.id >= 0); | ||||
|                     innerring.insert(pill.endpoint, unsigned(pill.id)); | ||||
|  | @ -1489,7 +1690,7 @@ bool SLASupportTree::generate(const PointSet &points, | |||
|                 // For all pillars in the outer ring find the closest in the
 | ||||
|                 // inner ring and connect them. This will create the spider web
 | ||||
|                 // fashioned connections between pillars
 | ||||
|                 for(unsigned i : ring) { | ||||
|                 for(unsigned i : ring) { tifcl(); | ||||
|                     const Pillar& outerpill = result.head_pillar(gndidx[i]); | ||||
|                     auto res = innerring.nearest(outerpill.endpoint, 1); | ||||
|                     if(res.empty()) continue; | ||||
|  | @ -1515,6 +1716,7 @@ bool SLASupportTree::generate(const PointSet &points, | |||
|                 next != ring.end(); | ||||
|                 ++it, ++next) | ||||
|             { | ||||
|                 tifcl(); | ||||
|                 const Pillar& pillar = result.head_pillar(gndidx[*it]); | ||||
|                 const Pillar& nextpillar = result.head_pillar(gndidx[*next]); | ||||
|                 interconnect(pillar, nextpillar, emesh, result); | ||||
|  | @ -1529,6 +1731,11 @@ bool SLASupportTree::generate(const PointSet &points, | |||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     // Step: routing the pinheads that are would connect to the model surface
 | ||||
|     // along the Z axis downwards. For now these will actually be connected with
 | ||||
|     // the model surface with a flipped pinhead. In the future here we could use
 | ||||
|     // some smart algorithms to search for a safe path to the ground or to a
 | ||||
|     // nearby pillar that can hold the supported weight.
 | ||||
|     auto routing_nongnd_fn = [tifcl]( | ||||
|             const SupportConfig& cfg, | ||||
|             const std::vector<double>& gndheight, | ||||
|  | @ -1590,6 +1797,9 @@ bool SLASupportTree::generate(const PointSet &points, | |||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     // Step: process the support points where there is not enough space for a
 | ||||
|     // full pinhead. In this case we will use a rounded sphere as a touching
 | ||||
|     // point and use a thinner bridge (let's call it a stick).
 | ||||
|     auto process_headless = [tifcl]( | ||||
|             const SupportConfig& cfg, | ||||
|             const PointSet& headless_pts, | ||||
|  | @ -1606,32 +1816,48 @@ bool SLASupportTree::generate(const PointSet &points, | |||
|         // We will sink the pins into the model surface for a distance of 1/3 of
 | ||||
|         // the pin radius
 | ||||
|         for(int i = 0; i < headless_pts.rows(); i++) { tifcl(); | ||||
|             Vec3d sp = headless_pts.row(i); | ||||
| 
 | ||||
|             Vec3d n = headless_norm.row(i); | ||||
|             sp = sp - n * HWIDTH_MM; | ||||
|             Vec3d sph = headless_pts.row(i);    // Exact support position
 | ||||
|             Vec3d n = headless_norm.row(i);     // mesh outward normal
 | ||||
|             Vec3d sp = sph - n * HWIDTH_MM;     // stick head start point
 | ||||
| 
 | ||||
|             Vec3d dir = {0, 0, -1}; | ||||
|             Vec3d sj = sp + R * n; | ||||
|             Vec3d sj = sp + R * n;              // stick start point
 | ||||
| 
 | ||||
|             // This is only for checking
 | ||||
|             double idist = bridge_mesh_intersect(sph, dir, R, emesh, true); | ||||
|             double dist = ray_mesh_intersect(sj, dir, emesh); | ||||
| 
 | ||||
|             if(std::isinf(dist) || std::isnan(dist) || dist < 2*R) continue; | ||||
|             if(std::isinf(idist) || std::isnan(idist) || idist < 2*R || | ||||
|                std::isinf(dist)  || std::isnan(dist)   || dist < 2*R) { | ||||
|                 BOOST_LOG_TRIVIAL(warning) << "Can not find route for headless" | ||||
|                                            << " support stick at: " | ||||
|                                            << sj.transpose(); | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             Vec3d ej = sj + (dist + HWIDTH_MM)* dir; | ||||
|             result.add_compact_bridge(sp, ej, n, R); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     using std::ref; | ||||
|     using std::cref; | ||||
|     // Now that the individual blocks are defined, lets connect the wires. We
 | ||||
|     // will create an array of functions which represents a program. Place the
 | ||||
|     // step methods in the array and bind the right arguments to the methods
 | ||||
|     // This way the data dependencies will be easily traceable between
 | ||||
|     // individual steps.
 | ||||
|     // There will be empty steps as well like the begin step or the done or
 | ||||
|     // abort steps. These are slots for future initialization or cleanup.
 | ||||
| 
 | ||||
|     using std::cref;    // Bind inputs with cref (read-only)
 | ||||
|     using std::ref;     // Bind outputs with ref (writable)
 | ||||
|     using std::bind; | ||||
| 
 | ||||
|     // Here we can easily track what goes in and what comes out of each step:
 | ||||
|     // (see the cref-s as inputs and ref-s as outputs)
 | ||||
|     std::array<std::function<void()>, NUM_STEPS> program = { | ||||
|     [] () { | ||||
|         // Begin
 | ||||
|         // clear up the shared data
 | ||||
|         // Begin...
 | ||||
|         // Potentially clear up the shared data (not needed for now)
 | ||||
|     }, | ||||
| 
 | ||||
|     // Filtering unnecessary support points
 | ||||
|  | @ -1674,6 +1900,7 @@ bool SLASupportTree::generate(const PointSet &points, | |||
| 
 | ||||
|     Steps pc = BEGIN, pc_prev = BEGIN; | ||||
| 
 | ||||
|     // Let's define a simple automaton that will run our program.
 | ||||
|     auto progress = [&ctl, &pc, &pc_prev] () { | ||||
|         static const std::array<std::string, NUM_STEPS> stepstr { | ||||
|             "Starting", | ||||
|  | @ -1795,7 +2022,7 @@ SLASupportTree::SLASupportTree(const PointSet &points, | |||
|                                const Controller &ctl): | ||||
|     m_impl(new Impl(ctl)) | ||||
| { | ||||
|     m_impl->ground_level = emesh.ground_level - cfg.object_elevation_mm; | ||||
|     m_impl->ground_level = emesh.ground_level() - cfg.object_elevation_mm; | ||||
|     generate(points, emesh, cfg, ctl); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -78,7 +78,7 @@ struct SupportConfig { | |||
|     double object_elevation_mm = 10; | ||||
| 
 | ||||
|     // The max Z angle for a normal at which it will get completely ignored.
 | ||||
|     double normal_cutoff_angle = 110.0 * M_PI / 180.0; | ||||
|     double normal_cutoff_angle = 150.0 * M_PI / 180.0; | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
|  | @ -104,18 +104,90 @@ struct Controller { | |||
| 
 | ||||
| /// An index-triangle structure for libIGL functions. Also serves as an
 | ||||
| /// alternative (raw) input format for the SLASupportTree
 | ||||
| struct EigenMesh3D { | ||||
|     Eigen::MatrixXd V; | ||||
|     Eigen::MatrixXi F; | ||||
|     double ground_level = 0; | ||||
| class EigenMesh3D { | ||||
|     class AABBImpl; | ||||
| 
 | ||||
|     Eigen::MatrixXd m_V; | ||||
|     Eigen::MatrixXi m_F; | ||||
|     double m_ground_level = 0; | ||||
| 
 | ||||
|     std::unique_ptr<AABBImpl> m_aabb; | ||||
| public: | ||||
| 
 | ||||
|     EigenMesh3D(const TriangleMesh&); | ||||
|     EigenMesh3D(const EigenMesh3D& other); | ||||
|     EigenMesh3D& operator=(const EigenMesh3D&); | ||||
| 
 | ||||
|     ~EigenMesh3D(); | ||||
| 
 | ||||
|     inline double ground_level() const { return m_ground_level; } | ||||
| 
 | ||||
|     inline const Eigen::MatrixXd& V() const { return m_V; } | ||||
|     inline const Eigen::MatrixXi& F() const { return m_F; } | ||||
| 
 | ||||
|     // Result of a raycast
 | ||||
|     class hit_result { | ||||
|         double m_t = std::numeric_limits<double>::infinity(); | ||||
|         int m_face_id = -1; | ||||
|         const EigenMesh3D& m_mesh; | ||||
|         Vec3d m_dir; | ||||
|         inline hit_result(const EigenMesh3D& em): m_mesh(em) {} | ||||
|         friend class EigenMesh3D; | ||||
|     public: | ||||
| 
 | ||||
|         inline double distance() const { return m_t; } | ||||
| 
 | ||||
|         inline int face() const { return m_face_id; } | ||||
| 
 | ||||
|         inline Vec3d normal() const { | ||||
|             if(m_face_id < 0) return {}; | ||||
|             auto trindex    = m_mesh.m_F.row(m_face_id); | ||||
|             const Vec3d& p1 = m_mesh.V().row(trindex(0)); | ||||
|             const Vec3d& p2 = m_mesh.V().row(trindex(1)); | ||||
|             const Vec3d& p3 = m_mesh.V().row(trindex(2)); | ||||
|             Eigen::Vector3d U = p2 - p1; | ||||
|             Eigen::Vector3d V = p3 - p1; | ||||
|             return U.cross(V).normalized(); | ||||
|         } | ||||
| 
 | ||||
|         inline bool is_inside() { | ||||
|             return m_face_id >= 0 && normal().dot(m_dir) > 0; | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     // Casting a ray on the mesh, returns the distance where the hit occures.
 | ||||
|     hit_result query_ray_hit(const Vec3d &s, const Vec3d &dir) const; | ||||
| 
 | ||||
|     class si_result { | ||||
|         double m_value; | ||||
|         int m_fidx; | ||||
|         Vec3d m_p; | ||||
|         si_result(double val, int i, const Vec3d& c): | ||||
|             m_value(val), m_fidx(i), m_p(c) {} | ||||
|         friend class EigenMesh3D; | ||||
|     public: | ||||
| 
 | ||||
|         si_result() = delete; | ||||
| 
 | ||||
|         double value() const { return m_value; } | ||||
|         operator double() const { return m_value; } | ||||
|         const Vec3d& point_on_mesh() const { return m_p; } | ||||
|         int F_idx() const { return m_fidx; } | ||||
|     }; | ||||
| 
 | ||||
|     // The signed distance from a point to the mesh. Outputs the distance,
 | ||||
|     // the index of the triangle and the closest point in mesh coordinate space.
 | ||||
|     si_result signed_distance(const Vec3d& p) const; | ||||
| 
 | ||||
|     bool inside(const Vec3d& p) const; | ||||
| }; | ||||
| 
 | ||||
| using PointSet = Eigen::MatrixXd; | ||||
| 
 | ||||
| EigenMesh3D to_eigenmesh(const TriangleMesh& m); | ||||
| //EigenMesh3D to_eigenmesh(const TriangleMesh& m);
 | ||||
| 
 | ||||
| // needed for find best rotation
 | ||||
| EigenMesh3D to_eigenmesh(const ModelObject& model); | ||||
| //EigenMesh3D to_eigenmesh(const ModelObject& model);
 | ||||
| 
 | ||||
| // Simple conversion of 'vector of points' to an Eigen matrix
 | ||||
| PointSet    to_point_set(const std::vector<Vec3d>&); | ||||
|  |  | |||
|  | @ -3,6 +3,9 @@ | |||
| #include "SLA/SLABoilerPlate.hpp" | ||||
| #include "SLA/SLASpatIndex.hpp" | ||||
| 
 | ||||
| // Workaround: IGL signed_distance.h will define PI in the igl namespace.
 | ||||
| #undef PI | ||||
| 
 | ||||
| // HEAVY headers... takes eternity to compile
 | ||||
| 
 | ||||
| // for concave hull merging decisions
 | ||||
|  | @ -12,6 +15,7 @@ | |||
| #include <igl/ray_mesh_intersect.h> | ||||
| #include <igl/point_mesh_squared_distance.h> | ||||
| #include <igl/remove_duplicate_vertices.h> | ||||
| #include <igl/signed_distance.h> | ||||
| 
 | ||||
| #include "SLASpatIndex.hpp" | ||||
| #include "ClipperUtils.hpp" | ||||
|  | @ -19,6 +23,13 @@ | |||
| namespace Slic3r { | ||||
| namespace sla { | ||||
| 
 | ||||
| // Bring back PI from the igl namespace
 | ||||
| using igl::PI; | ||||
| 
 | ||||
| /* **************************************************************************
 | ||||
|  * SpatIndex implementation | ||||
|  * ************************************************************************** */ | ||||
| 
 | ||||
| class SpatIndex::Impl { | ||||
| public: | ||||
|     using BoostIndex = boost::geometry::index::rtree< SpatElement, | ||||
|  | @ -78,6 +89,101 @@ size_t SpatIndex::size() const | |||
|     return m_impl->m_store.size(); | ||||
| } | ||||
| 
 | ||||
| /* ****************************************************************************
 | ||||
|  * EigenMesh3D implementation | ||||
|  * ****************************************************************************/ | ||||
| 
 | ||||
| class EigenMesh3D::AABBImpl: public igl::AABB<Eigen::MatrixXd, 3> { | ||||
| public: | ||||
|     igl::WindingNumberAABB<Vec3d, Eigen::MatrixXd, Eigen::MatrixXi> windtree; | ||||
| }; | ||||
| 
 | ||||
| EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) { | ||||
|     static const double dEPS = 1e-6; | ||||
| 
 | ||||
|     const stl_file& stl = tmesh.stl; | ||||
| 
 | ||||
|     auto&& bb = tmesh.bounding_box(); | ||||
|     m_ground_level += bb.min(Z); | ||||
| 
 | ||||
|     Eigen::MatrixXd V; | ||||
|     Eigen::MatrixXi F; | ||||
| 
 | ||||
|     V.resize(3*stl.stats.number_of_facets, 3); | ||||
|     F.resize(stl.stats.number_of_facets, 3); | ||||
|     for (unsigned int i = 0; i < stl.stats.number_of_facets; ++i) { | ||||
|         const stl_facet* facet = stl.facet_start+i; | ||||
|         V(3*i+0, 0) = double(facet->vertex[0](0)); | ||||
|         V(3*i+0, 1) = double(facet->vertex[0](1)); | ||||
|         V(3*i+0, 2) = double(facet->vertex[0](2)); | ||||
| 
 | ||||
|         V(3*i+1, 0) = double(facet->vertex[1](0)); | ||||
|         V(3*i+1, 1) = double(facet->vertex[1](1)); | ||||
|         V(3*i+1, 2) = double(facet->vertex[1](2)); | ||||
| 
 | ||||
|         V(3*i+2, 0) = double(facet->vertex[2](0)); | ||||
|         V(3*i+2, 1) = double(facet->vertex[2](1)); | ||||
|         V(3*i+2, 2) = double(facet->vertex[2](2)); | ||||
| 
 | ||||
|         F(i, 0) = int(3*i+0); | ||||
|         F(i, 1) = int(3*i+1); | ||||
|         F(i, 2) = int(3*i+2); | ||||
|     } | ||||
| 
 | ||||
|     // We will convert this to a proper 3d mesh with no duplicate points.
 | ||||
|     Eigen::VectorXi SVI, SVJ; | ||||
|     igl::remove_duplicate_vertices(V, F, dEPS, m_V, SVI, SVJ, m_F); | ||||
| 
 | ||||
|     // Build the AABB accelaration tree
 | ||||
|     m_aabb->init(m_V, m_F); | ||||
|     m_aabb->windtree.set_mesh(m_V, m_F); | ||||
| } | ||||
| 
 | ||||
| EigenMesh3D::~EigenMesh3D() {} | ||||
| 
 | ||||
| EigenMesh3D::EigenMesh3D(const EigenMesh3D &other): | ||||
|     m_V(other.m_V), m_F(other.m_F), m_ground_level(other.m_ground_level), | ||||
|     m_aabb( new AABBImpl(*other.m_aabb) ) {} | ||||
| 
 | ||||
| EigenMesh3D &EigenMesh3D::operator=(const EigenMesh3D &other) | ||||
| { | ||||
|     m_V = other.m_V; | ||||
|     m_F = other.m_F; | ||||
|     m_ground_level = other.m_ground_level; | ||||
|     m_aabb.reset(new AABBImpl(*other.m_aabb)); return *this; | ||||
| } | ||||
| 
 | ||||
| EigenMesh3D::hit_result | ||||
| EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const | ||||
| { | ||||
|     igl::Hit hit; | ||||
|     hit.t = std::numeric_limits<float>::infinity(); | ||||
|     m_aabb->intersect_ray(m_V, m_F, s, dir, hit); | ||||
| 
 | ||||
|     hit_result ret(*this); | ||||
|     ret.m_t = double(hit.t); | ||||
|     ret.m_dir = dir; | ||||
|     if(!std::isinf(hit.t) && !std::isnan(hit.t)) ret.m_face_id = hit.id; | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| EigenMesh3D::si_result EigenMesh3D::signed_distance(const Vec3d &p) const { | ||||
|     double sign = 0; double sqdst = 0; int i = 0;  Vec3d c; | ||||
|     igl::signed_distance_winding_number(*m_aabb, m_V, m_F, m_aabb->windtree, | ||||
|                                         p, sign, sqdst, i, c); | ||||
| 
 | ||||
|     return si_result(sign * std::sqrt(sqdst), i, c); | ||||
| } | ||||
| 
 | ||||
| bool EigenMesh3D::inside(const Vec3d &p) const { | ||||
|     return m_aabb->windtree.inside(p); | ||||
| } | ||||
| 
 | ||||
| /* ****************************************************************************
 | ||||
|  * Misc functions | ||||
|  * ****************************************************************************/ | ||||
| 
 | ||||
| bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2, | ||||
|                    double eps = 0.05) | ||||
| { | ||||
|  | @ -93,35 +199,27 @@ template<class Vec> double distance(const Vec& pp1, const Vec& pp2) { | |||
|     return std::sqrt(p.transpose() * p); | ||||
| } | ||||
| 
 | ||||
| PointSet normals(const PointSet& points, const EigenMesh3D& emesh, | ||||
| PointSet normals(const PointSet& points, const EigenMesh3D& mesh, | ||||
|                  double eps, | ||||
|                  std::function<void()> throw_on_cancel) { | ||||
|     if(points.rows() == 0 || emesh.V.rows() == 0 || emesh.F.rows() == 0) | ||||
|     if(points.rows() == 0 || mesh.V().rows() == 0 || mesh.F().rows() == 0) | ||||
|         return {}; | ||||
| 
 | ||||
|     Eigen::VectorXd dists; | ||||
|     Eigen::VectorXi I; | ||||
|     PointSet C; | ||||
| 
 | ||||
|     // We need to remove duplicate vertices and have a true index triangle
 | ||||
|     // structure
 | ||||
|     EigenMesh3D  mesh; | ||||
|     Eigen::VectorXi SVI, SVJ; | ||||
|     static const double dEPS = 1e-6; | ||||
|     igl::remove_duplicate_vertices(emesh.V, emesh.F, dEPS, | ||||
|                                    mesh.V, SVI, SVJ, mesh.F); | ||||
| 
 | ||||
|     igl::point_mesh_squared_distance( points, mesh.V, mesh.F, dists, I, C); | ||||
|     igl::point_mesh_squared_distance( points, mesh.V(), mesh.F(), dists, I, C); | ||||
| 
 | ||||
|     PointSet ret(I.rows(), 3); | ||||
|     for(int i = 0; i < I.rows(); i++) { | ||||
|         throw_on_cancel(); | ||||
|         auto idx = I(i); | ||||
|         auto trindex = mesh.F.row(idx); | ||||
|         auto trindex = mesh.F().row(idx); | ||||
| 
 | ||||
|         const Vec3d& p1 = mesh.V.row(trindex(0)); | ||||
|         const Vec3d& p2 = mesh.V.row(trindex(1)); | ||||
|         const Vec3d& p3 = mesh.V.row(trindex(2)); | ||||
|         const Vec3d& p1 = mesh.V().row(trindex(0)); | ||||
|         const Vec3d& p2 = mesh.V().row(trindex(1)); | ||||
|         const Vec3d& p3 = mesh.V().row(trindex(2)); | ||||
| 
 | ||||
|         // We should check if the point lies on an edge of the hosting triangle.
 | ||||
|         // If it does than all the other triangles using the same two points
 | ||||
|  | @ -159,18 +257,18 @@ PointSet normals(const PointSet& points, const EigenMesh3D& emesh, | |||
|         // vector for the neigboring triangles including the detected one.
 | ||||
|         std::vector<Vec3i> neigh; | ||||
|         if(ic >= 0) { // The point is right on a vertex of the triangle
 | ||||
|             for(int n = 0; n < mesh.F.rows(); ++n) { | ||||
|             for(int n = 0; n < mesh.F().rows(); ++n) { | ||||
|                 throw_on_cancel(); | ||||
|                 Vec3i ni = mesh.F.row(n); | ||||
|                 Vec3i ni = mesh.F().row(n); | ||||
|                 if((ni(X) == ic || ni(Y) == ic || ni(Z) == ic)) | ||||
|                     neigh.emplace_back(ni); | ||||
|             } | ||||
|         } | ||||
|         else if(ia >= 0 && ib >= 0) { // the point is on and edge
 | ||||
|             // now get all the neigboring triangles
 | ||||
|             for(int n = 0; n < mesh.F.rows(); ++n) { | ||||
|             for(int n = 0; n < mesh.F().rows(); ++n) { | ||||
|                 throw_on_cancel(); | ||||
|                 Vec3i ni = mesh.F.row(n); | ||||
|                 Vec3i ni = mesh.F().row(n); | ||||
|                 if((ni(X) == ia || ni(Y) == ia || ni(Z) == ia) && | ||||
|                    (ni(X) == ib || ni(Y) == ib || ni(Z) == ib)) | ||||
|                     neigh.emplace_back(ni); | ||||
|  | @ -180,9 +278,9 @@ PointSet normals(const PointSet& points, const EigenMesh3D& emesh, | |||
|         // Calculate the normals for the neighboring triangles
 | ||||
|         std::vector<Vec3d> neighnorms; neighnorms.reserve(neigh.size()); | ||||
|         for(const Vec3i& tri : neigh) { | ||||
|             const Vec3d& pt1 = mesh.V.row(tri(0)); | ||||
|             const Vec3d& pt2 = mesh.V.row(tri(1)); | ||||
|             const Vec3d& pt3 = mesh.V.row(tri(2)); | ||||
|             const Vec3d& pt1 = mesh.V().row(tri(0)); | ||||
|             const Vec3d& pt2 = mesh.V().row(tri(1)); | ||||
|             const Vec3d& pt3 = mesh.V().row(tri(2)); | ||||
|             Eigen::Vector3d U = pt2 - pt1; | ||||
|             Eigen::Vector3d V = pt3 - pt1; | ||||
|             neighnorms.emplace_back(U.cross(V).normalized()); | ||||
|  | @ -222,16 +320,6 @@ PointSet normals(const PointSet& points, const EigenMesh3D& emesh, | |||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| double ray_mesh_intersect(const Vec3d& s, | ||||
|                           const Vec3d& dir, | ||||
|                           const EigenMesh3D& m) | ||||
| { | ||||
|     igl::Hit hit; | ||||
|     hit.t = std::numeric_limits<float>::infinity(); | ||||
|     igl::ray_mesh_intersect(s, dir, m.V, m.F, hit); | ||||
|     return double(hit.t); | ||||
| } | ||||
| 
 | ||||
| // Clustering a set of points by the given criteria
 | ||||
| ClusteredPoints cluster( | ||||
|         const sla::PointSet& points, | ||||
|  | @ -309,53 +397,5 @@ ClusteredPoints cluster( | |||
|     return result; | ||||
| } | ||||
| 
 | ||||
| using Segments = std::vector<std::pair<Vec2d, Vec2d>>; | ||||
| 
 | ||||
| Segments model_boundary(const EigenMesh3D& emesh, double offs) | ||||
| { | ||||
|     Segments ret; | ||||
|     Polygons pp; | ||||
|     pp.reserve(size_t(emesh.F.rows())); | ||||
| 
 | ||||
|     for (int i = 0; i < emesh.F.rows(); i++) { | ||||
|         auto trindex = emesh.F.row(i); | ||||
|         auto& p1 = emesh.V.row(trindex(0)); | ||||
|         auto& p2 = emesh.V.row(trindex(1)); | ||||
|         auto& p3 = emesh.V.row(trindex(2)); | ||||
| 
 | ||||
|         Polygon p; | ||||
|         p.points.resize(3); | ||||
|         p.points[0] = Point::new_scale(p1(X), p1(Y)); | ||||
|         p.points[1] = Point::new_scale(p2(X), p2(Y)); | ||||
|         p.points[2] = Point::new_scale(p3(X), p3(Y)); | ||||
|         p.make_counter_clockwise(); | ||||
|         pp.emplace_back(p); | ||||
|     } | ||||
| 
 | ||||
|     ExPolygons merged = union_ex(Slic3r::offset(pp, float(scale_(offs))), true); | ||||
| 
 | ||||
|     for(auto& expoly : merged) { | ||||
|         auto lines = expoly.lines(); | ||||
|         for(Line& l : lines) { | ||||
|             Vec2d a(l.a(X) * SCALING_FACTOR, l.a(Y) * SCALING_FACTOR); | ||||
|             Vec2d b(l.b(X) * SCALING_FACTOR, l.b(Y) * SCALING_FACTOR); | ||||
|             ret.emplace_back(std::make_pair(a, b)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| //struct SegmentIndex {
 | ||||
| 
 | ||||
| //};
 | ||||
| 
 | ||||
| //using SegmentIndexEl = std::pair<Segment, unsigned>;
 | ||||
| 
 | ||||
| //SegmentIndexEl
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| } | ||||
|  |  | |||
|  | @ -29,6 +29,8 @@ public: | |||
|     SupportTreePtr   support_tree_ptr;   // the supports
 | ||||
|     SlicedSupports   support_slices;     // sliced supports
 | ||||
|     std::vector<LevelID>    level_ids; | ||||
| 
 | ||||
|     inline SupportData(const TriangleMesh& trmesh): emesh(trmesh) {} | ||||
| }; | ||||
| 
 | ||||
| namespace { | ||||
|  | @ -385,8 +387,6 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf | |||
|             update_apply_status(false); | ||||
|     } | ||||
| 
 | ||||
|     this->update_object_placeholders(); | ||||
| 
 | ||||
| #ifdef _DEBUG | ||||
|     check_model_ids_equal(m_model, model); | ||||
| #endif /* _DEBUG */ | ||||
|  | @ -503,8 +503,8 @@ void SLAPrint::process() | |||
|     // support points. Then we sprinkle the rest of the mesh.
 | ||||
|     auto support_points = [this, ilh](SLAPrintObject& po) { | ||||
|         const ModelObject& mo = *po.m_model_object; | ||||
|         po.m_supportdata.reset(new SLAPrintObject::SupportData()); | ||||
|         po.m_supportdata->emesh = sla::to_eigenmesh(po.transformed_mesh()); | ||||
|         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; | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ | |||
| // Renders a small sphere in the center of the bounding box of the current selection when no gizmo is active
 | ||||
| #define ENABLE_RENDER_SELECTION_CENTER 0 | ||||
| 
 | ||||
| 
 | ||||
| //====================
 | ||||
| // 1.42.0.alpha1 techs
 | ||||
| //====================
 | ||||
|  | @ -27,16 +28,14 @@ | |||
| // Use wxDataViewRender instead of wxDataViewCustomRenderer
 | ||||
| #define ENABLE_NONCUSTOM_DATA_VIEW_RENDERING (0 && ENABLE_1_42_0_ALPHA1) | ||||
| 
 | ||||
| 
 | ||||
| //====================
 | ||||
| // 1.42.0.alpha2 techs
 | ||||
| //====================
 | ||||
| #define ENABLE_1_42_0_ALPHA2 1 | ||||
| 
 | ||||
| // Improves navigation between sidebar fields
 | ||||
| #define ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION (1 && ENABLE_1_42_0_ALPHA2) | ||||
| // Adds print bed models to 3D scene
 | ||||
| #define ENABLE_PRINT_BED_MODELS (1 && ENABLE_1_42_0_ALPHA2) | ||||
| #endif // _technologies_h_
 | ||||
| 
 | ||||
| 
 | ||||
| //====================
 | ||||
|  | @ -65,3 +64,5 @@ | |||
| 
 | ||||
| // Toolbar items hidden/shown in dependence of the user mode
 | ||||
| #define ENABLE_MODE_AWARE_TOOLBAR_ITEMS (1 && ENABLE_1_42_0_ALPHA5) | ||||
| 
 | ||||
| #endif // _technologies_h_
 | ||||
|  |  | |||
|  | @ -8,6 +8,8 @@ | |||
| 
 | ||||
| #include "libslic3r.h" | ||||
| 
 | ||||
| namespace boost { namespace filesystem { class directory_entry; }} | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| extern void set_logging_level(unsigned int level); | ||||
|  | @ -61,6 +63,12 @@ extern int rename_file(const std::string &from, const std::string &to); | |||
| // Copy a file, adjust the access attributes, so that the target is writable.
 | ||||
| extern int copy_file(const std::string &from, const std::string &to); | ||||
| 
 | ||||
| // Ignore system and hidden files, which may be created by the DropBox synchronisation process.
 | ||||
| // https://github.com/prusa3d/Slic3r/issues/1298
 | ||||
| extern bool is_plain_file(const boost::filesystem::directory_entry &path); | ||||
| extern bool is_ini_file(const boost::filesystem::directory_entry &path); | ||||
| extern bool is_idx_file(const boost::filesystem::directory_entry &path); | ||||
| 
 | ||||
| // File path / name / extension splitting utilities, working with UTF-8,
 | ||||
| // to be published to Perl.
 | ||||
| namespace PerlUtils { | ||||
|  |  | |||
|  | @ -16,6 +16,7 @@ | |||
| #include <stdarg.h> | ||||
| #include <vector> | ||||
| #include <cassert> | ||||
| #include <cmath> | ||||
| 
 | ||||
| #include "Technologies.hpp" | ||||
| 
 | ||||
|  | @ -164,6 +165,12 @@ static inline T lerp(const T& a, const T& b, Number t) | |||
|     return (Number(1) - t) * a + t * b; | ||||
| } | ||||
| 
 | ||||
| template <typename Number> | ||||
| static inline bool is_approx(Number value, Number test_value) | ||||
| { | ||||
|     return std::fabs(double(value) - double(test_value)) < double(EPSILON); | ||||
| }; | ||||
| 
 | ||||
| } // namespace Slic3r
 | ||||
| 
 | ||||
| #endif | ||||
|  |  | |||
|  | @ -30,7 +30,13 @@ | |||
| 
 | ||||
| #include <tbb/task_scheduler_init.h> | ||||
| 
 | ||||
| #include <tbb/task_scheduler_init.h> | ||||
| #if defined(__linux) || defined(__GNUC__ ) | ||||
| #include <strings.h> | ||||
| #endif /* __linux */ | ||||
| 
 | ||||
| #ifdef _MSC_VER  | ||||
|     #define strcasecmp _stricmp | ||||
| #endif | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
|  | @ -248,6 +254,30 @@ int copy_file(const std::string &from, const std::string &to) | |||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| // Ignore system and hidden files, which may be created by the DropBox synchronisation process.
 | ||||
| // https://github.com/prusa3d/Slic3r/issues/1298
 | ||||
| bool is_plain_file(const boost::filesystem::directory_entry &dir_entry) | ||||
| { | ||||
|     if (! boost::filesystem::is_regular_file(dir_entry.status())) | ||||
|         return false; | ||||
| #ifdef _MSC_VER | ||||
|     DWORD attributes = GetFileAttributesW(boost::nowide::widen(dir_entry.path().string()).c_str()); | ||||
|     return (attributes & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) == 0; | ||||
| #else | ||||
|     return true; | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| bool is_ini_file(const boost::filesystem::directory_entry &dir_entry) | ||||
| { | ||||
|     return is_plain_file(dir_entry) && strcasecmp(dir_entry.path().extension().string().c_str(), ".ini") == 0; | ||||
| } | ||||
| 
 | ||||
| bool is_idx_file(const boost::filesystem::directory_entry &dir_entry) | ||||
| { | ||||
| 	return is_plain_file(dir_entry) && strcasecmp(dir_entry.path().extension().string().c_str(), ".idx") == 0; | ||||
| } | ||||
| 
 | ||||
| } // namespace Slic3r
 | ||||
| 
 | ||||
| #ifdef WIN32 | ||||
|  |  | |||
|  | @ -252,10 +252,6 @@ int main(int argc, char **argv) | |||
|                 model.arrange_objects(fff_print.config().min_object_distance()); | ||||
|                 model.center_instances_around_point(cli_config.print_center); | ||||
|             } | ||||
|             if (outfile.empty()) { | ||||
|                 outfile = model.propose_export_file_name(); | ||||
|                 outfile += (printer_technology == ptFFF) ? ".gcode" : ".zip"; | ||||
|             } | ||||
|             if (printer_technology == ptFFF) { | ||||
|                 for (auto* mo : model.objects) | ||||
|                     fff_print.auto_assign_extruders(mo); | ||||
|  | @ -265,6 +261,7 @@ int main(int argc, char **argv) | |||
|             std::string err = print->validate(); | ||||
|             if (err.empty()) { | ||||
|                 if (printer_technology == ptFFF) { | ||||
|                     // The outfile is processed by a PlaceholderParser.
 | ||||
|                     fff_print.export_gcode(outfile, nullptr); | ||||
|                 } else { | ||||
|                     assert(printer_technology == ptSLA); | ||||
|  |  | |||
|  | @ -249,10 +249,10 @@ bool Snapshot::equal_to_active(const AppConfig &app_config) const | |||
|         boost::filesystem::path path2 = snapshot_dir / subdir; | ||||
|         std::vector<std::string> files1, files2; | ||||
|         for (auto &dir_entry : boost::filesystem::directory_iterator(path1)) | ||||
|             if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini")) | ||||
|             if (Slic3r::is_ini_file(dir_entry)) | ||||
|                 files1.emplace_back(dir_entry.path().filename().string()); | ||||
|         for (auto &dir_entry : boost::filesystem::directory_iterator(path2)) | ||||
|             if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini")) | ||||
|             if (Slic3r::is_ini_file(dir_entry)) | ||||
|                 files2.emplace_back(dir_entry.path().filename().string()); | ||||
|         std::sort(files1.begin(), files1.end()); | ||||
|         std::sort(files2.begin(), files2.end()); | ||||
|  | @ -343,7 +343,7 @@ static void copy_config_dir_single_level(const boost::filesystem::path &path_src | |||
|         throw std::runtime_error(std::string("Slic3r was unable to create a directory at ") + path_dst.string()); | ||||
| 
 | ||||
| 	for (auto &dir_entry : boost::filesystem::directory_iterator(path_src)) | ||||
|         if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini")) | ||||
|         if (Slic3r::is_ini_file(dir_entry)) | ||||
| 		    boost::filesystem::copy_file(dir_entry.path(), path_dst / dir_entry.path().filename(), boost::filesystem::copy_option::overwrite_if_exists); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -297,7 +297,7 @@ std::vector<Index> Index::load_db() | |||
|     std::vector<Index> index_db; | ||||
|     std::string errors_cummulative; | ||||
| 	for (auto &dir_entry : boost::filesystem::directory_iterator(cache_dir)) | ||||
|         if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".idx")) { | ||||
|         if (Slic3r::is_idx_file(dir_entry)) { | ||||
|         	Index idx; | ||||
|             try { | ||||
|             	idx.load(dir_entry.path()); | ||||
|  |  | |||
|  | @ -252,6 +252,7 @@ GLVolume::GLVolume(float r, float g, float b, float a) | |||
|     , is_modifier(false) | ||||
|     , is_wipe_tower(false) | ||||
|     , is_extrusion_path(false) | ||||
|     , force_transparent(false) | ||||
|     , tverts_range(0, size_t(-1)) | ||||
|     , qverts_range(0, size_t(-1)) | ||||
| { | ||||
|  | @ -293,6 +294,9 @@ void GLVolume::set_render_color() | |||
|         set_render_color(OUTSIDE_COLOR, 4); | ||||
|     else | ||||
|         set_render_color(color, 4); | ||||
| 
 | ||||
|     if (force_transparent) | ||||
|         render_color[3] = color[3]; | ||||
| } | ||||
| 
 | ||||
| void GLVolume::set_color_from_model_volume(const ModelVolume *model_volume) | ||||
|  |  | |||
|  | @ -295,6 +295,8 @@ public: | |||
|     bool                is_wipe_tower; | ||||
|     // Wheter or not this volume has been generated from an extrusion path
 | ||||
|     bool                is_extrusion_path; | ||||
|     // Wheter or not to always render this volume using its own alpha 
 | ||||
|     bool                force_transparent; | ||||
| 
 | ||||
|     // Interleaved triangles & normals with indexed triangles & quads.
 | ||||
|     GLIndexedVertexArray        indexed_vertex_array; | ||||
|  |  | |||
|  | @ -635,35 +635,32 @@ void ConfigWizard::priv::load_vendors() | |||
| 	const auto rsrc_vendor_dir = fs::path(resources_dir()) / "profiles"; | ||||
| 
 | ||||
| 	// Load vendors from the "vendors" directory in datadir
 | ||||
| 	for (fs::directory_iterator it(vendor_dir); it != fs::directory_iterator(); ++it) { | ||||
| 		if (it->path().extension() == ".ini") { | ||||
|     for (auto &dir_entry : boost::filesystem::directory_iterator(vendor_dir)) | ||||
| 		if (Slic3r::is_ini_file(dir_entry)) { | ||||
| 			try { | ||||
| 				auto vp = VendorProfile::from_ini(it->path()); | ||||
| 				auto vp = VendorProfile::from_ini(dir_entry.path()); | ||||
| 				vendors[vp.id] = std::move(vp); | ||||
| 			} | ||||
| 			catch (const std::exception& e) { | ||||
| 				BOOST_LOG_TRIVIAL(error) << boost::format("Error loading vendor bundle %1%: %2%") % it->path() % e.what(); | ||||
| 				BOOST_LOG_TRIVIAL(error) << boost::format("Error loading vendor bundle %1%: %2%") % dir_entry.path() % e.what(); | ||||
| 			} | ||||
| 
 | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Additionally load up vendors from the application resources directory, but only those not seen in the datadir
 | ||||
| 	for (fs::directory_iterator it(rsrc_vendor_dir); it != fs::directory_iterator(); ++it) { | ||||
| 		if (it->path().extension() == ".ini") { | ||||
| 			const auto id = it->path().stem().string(); | ||||
|     for (auto &dir_entry : boost::filesystem::directory_iterator(rsrc_vendor_dir)) | ||||
| 		if (Slic3r::is_ini_file(dir_entry)) { | ||||
| 			const auto id = dir_entry.path().stem().string(); | ||||
| 			if (vendors.find(id) == vendors.end()) { | ||||
| 				try { | ||||
| 					auto vp = VendorProfile::from_ini(it->path()); | ||||
| 					vendors_rsrc[vp.id] = it->path().filename().string(); | ||||
| 					auto vp = VendorProfile::from_ini(dir_entry.path()); | ||||
| 					vendors_rsrc[vp.id] = dir_entry.path().filename().string(); | ||||
| 					vendors[vp.id] = std::move(vp); | ||||
| 				} | ||||
| 				catch (const std::exception& e) { | ||||
| 					BOOST_LOG_TRIVIAL(error) << boost::format("Error loading vendor bundle %1%: %2%") % it->path() % e.what(); | ||||
| 					BOOST_LOG_TRIVIAL(error) << boost::format("Error loading vendor bundle %1%: %2%") % dir_entry.path() % e.what(); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Load up the set of vendors / models / variants the user has had enabled up till now
 | ||||
| 	const AppConfig *app_config = GUI::get_app_config(); | ||||
|  | @ -672,14 +669,15 @@ void ConfigWizard::priv::load_vendors() | |||
| 	} else { | ||||
| 		// In case of legacy datadir, try to guess the preference based on the printer preset files that are present
 | ||||
| 		const auto printer_dir = fs::path(Slic3r::data_dir()) / "printer"; | ||||
| 		for (fs::directory_iterator it(printer_dir); it != fs::directory_iterator(); ++it) { | ||||
| 			auto needle = legacy_preset_map.find(it->path().filename().string()); | ||||
| 			if (needle == legacy_preset_map.end()) { continue; } | ||||
| 	    for (auto &dir_entry : boost::filesystem::directory_iterator(printer_dir)) | ||||
| 	    	if (Slic3r::is_ini_file(dir_entry)) { | ||||
| 				auto needle = legacy_preset_map.find(dir_entry.path().filename().string()); | ||||
| 				if (needle == legacy_preset_map.end()) { continue; } | ||||
| 
 | ||||
| 			const auto &model = needle->second.first; | ||||
| 			const auto &variant = needle->second.second; | ||||
| 			appconfig_vendors.set_variant("PrusaResearch", model, variant, true); | ||||
| 		} | ||||
| 				const auto &model = needle->second.first; | ||||
| 				const auto &variant = needle->second.second; | ||||
| 				appconfig_vendors.set_variant("PrusaResearch", model, variant, true); | ||||
| 			} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1617,6 +1617,9 @@ void GLCanvas3D::Selection::clear() | |||
| 
 | ||||
|     _update_type(); | ||||
|     m_bounding_box_dirty = true; | ||||
| 
 | ||||
|     // resets the cache in the sidebar
 | ||||
|     wxGetApp().obj_manipul()->reset_cache(); | ||||
| } | ||||
| 
 | ||||
| // Update the selection based on the map from old indices to new indices after m_volumes changed.
 | ||||
|  | @ -1817,7 +1820,7 @@ void GLCanvas3D::Selection::rotate(const Vec3d& rotation, bool local) | |||
|         if (rot_axis_max != 2 && first_volume_idx != -1) { | ||||
|             // Generic rotation, but no rotation around the Z axis.
 | ||||
|             // Always do a local rotation (do not consider the selection to be a rigid body).
 | ||||
|             assert(rotation.z() == 0); | ||||
|             assert(is_approx(rotation.z(), 0.0)); | ||||
|             const GLVolume &first_volume = *(*m_volumes)[first_volume_idx]; | ||||
|             const Vec3d    &rotation     = first_volume.get_instance_rotation(); | ||||
|             double z_diff = rotation_diff_z(m_cache.volumes_data[first_volume_idx].get_instance_rotation(), m_cache.volumes_data[i].get_instance_rotation()); | ||||
|  | @ -1842,7 +1845,7 @@ void GLCanvas3D::Selection::rotate(const Vec3d& rotation, bool local) | |||
|         else if (is_single_volume() || is_single_modifier()) | ||||
|         { | ||||
|             if (local) | ||||
|                 volume.set_volume_rotation(rotation); | ||||
|                 volume.set_volume_rotation(volume.get_volume_rotation() + rotation); | ||||
|             else | ||||
|             { | ||||
|                 Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); | ||||
|  | @ -2259,7 +2262,7 @@ void GLCanvas3D::Selection::render_sidebar_hints(const std::string& sidebar_fiel | |||
|     } | ||||
|     else if (is_single_volume() || is_single_modifier()) | ||||
|     { | ||||
|         Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); | ||||
|         Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true) * (*m_volumes)[*m_list.begin()]->get_volume_transformation().get_matrix(true, false, true, true); | ||||
|         ::glTranslated(center(0), center(1), center(2)); | ||||
|         ::glMultMatrixd(orient_matrix.data()); | ||||
|     } | ||||
|  | @ -3992,13 +3995,12 @@ wxDEFINE_EVENT(EVT_GLCANVAS_VIEWPORT_CHANGED, SimpleEvent); | |||
| wxDEFINE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, Vec2dEvent); | ||||
| wxDEFINE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent); | ||||
| wxDEFINE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent); | ||||
| wxDEFINE_EVENT(EVT_GLCANVAS_SELECT_ALL, SimpleEvent); | ||||
| wxDEFINE_EVENT(EVT_GLCANVAS_QUESTION_MARK, SimpleEvent); | ||||
| wxDEFINE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event<int>); | ||||
| wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent); | ||||
| #if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION | ||||
| wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_ROTATED, SimpleEvent); | ||||
| wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_SCALED, SimpleEvent); | ||||
| #endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
 | ||||
| wxDEFINE_EVENT(EVT_GLCANVAS_WIPETOWER_MOVED, Vec3dEvent); | ||||
| wxDEFINE_EVENT(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, Event<bool>); | ||||
| wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_GEOMETRY, Vec3dsEvent<2>); | ||||
|  | @ -4286,6 +4288,13 @@ bool GLCanvas3D::is_reload_delayed() const | |||
| void GLCanvas3D::enable_layers_editing(bool enable) | ||||
| { | ||||
|     m_layers_editing.set_enabled(enable); | ||||
|     const Selection::IndicesList& idxs = m_selection.get_volume_idxs(); | ||||
|     for (unsigned int idx : idxs) | ||||
|     { | ||||
|         GLVolume* v = m_volumes.volumes[idx]; | ||||
|         if (v->is_modifier) | ||||
|             v->force_transparent = enable; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GLCanvas3D::enable_warning_texture(bool enable) | ||||
|  | @ -4420,6 +4429,7 @@ void GLCanvas3D::update_toolbar_items_visibility() | |||
|     ConfigOptionMode mode = wxGetApp().get_mode(); | ||||
|     m_toolbar.set_item_visible("more", mode != comSimple); | ||||
|     m_toolbar.set_item_visible("fewer", mode != comSimple); | ||||
|     m_toolbar.set_item_visible("splitvolumes", mode != comSimple); | ||||
|     m_dirty = true; | ||||
| } | ||||
| #endif // ENABLE_MODE_AWARE_TOOLBAR_ITEMS
 | ||||
|  | @ -5046,7 +5056,6 @@ void GLCanvas3D::bind_event_handlers() | |||
|         m_canvas->Bind(wxEVT_MIDDLE_DCLICK, &GLCanvas3D::on_mouse, this); | ||||
|         m_canvas->Bind(wxEVT_RIGHT_DCLICK, &GLCanvas3D::on_mouse, this); | ||||
|         m_canvas->Bind(wxEVT_PAINT, &GLCanvas3D::on_paint, this); | ||||
|         m_canvas->Bind(wxEVT_KEY_DOWN, &GLCanvas3D::on_key_down, this); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -5060,7 +5069,7 @@ void GLCanvas3D::unbind_event_handlers() | |||
|         m_canvas->Unbind(wxEVT_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this); | ||||
|         m_canvas->Unbind(wxEVT_TIMER, &GLCanvas3D::on_timer, this); | ||||
|         m_canvas->Unbind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this); | ||||
|         m_canvas->Unbind(wxEVT_LEFT_UP, &GLCanvas3D::on_mouse, this); | ||||
| 		m_canvas->Unbind(wxEVT_LEFT_UP, &GLCanvas3D::on_mouse, this); | ||||
|         m_canvas->Unbind(wxEVT_MIDDLE_DOWN, &GLCanvas3D::on_mouse, this); | ||||
|         m_canvas->Unbind(wxEVT_MIDDLE_UP, &GLCanvas3D::on_mouse, this); | ||||
|         m_canvas->Unbind(wxEVT_RIGHT_DOWN, &GLCanvas3D::on_mouse, this); | ||||
|  | @ -5072,7 +5081,6 @@ void GLCanvas3D::unbind_event_handlers() | |||
|         m_canvas->Unbind(wxEVT_MIDDLE_DCLICK, &GLCanvas3D::on_mouse, this); | ||||
|         m_canvas->Unbind(wxEVT_RIGHT_DCLICK, &GLCanvas3D::on_mouse, this); | ||||
|         m_canvas->Unbind(wxEVT_PAINT, &GLCanvas3D::on_paint, this); | ||||
|         m_canvas->Unbind(wxEVT_KEY_DOWN, &GLCanvas3D::on_key_down, this); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -5091,71 +5099,67 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt) | |||
| 
 | ||||
| void GLCanvas3D::on_char(wxKeyEvent& evt) | ||||
| { | ||||
|     if (evt.HasModifiers()) | ||||
|     // see include/wx/defs.h enum wxKeyCode
 | ||||
|     int keyCode = evt.GetKeyCode(); | ||||
|     int ctrlMask = wxMOD_CONTROL; | ||||
| //#ifdef __APPLE__
 | ||||
| //    ctrlMask |= wxMOD_RAW_CONTROL;
 | ||||
| //#endif /* __APPLE__ */
 | ||||
|     if ((evt.GetModifiers() & ctrlMask) != 0) { | ||||
|         switch (keyCode) { | ||||
| #ifndef __APPLE__ | ||||
|         // Even though Control+A is captured by the accelerator on OSX/wxWidgets in Slic3r, it works in text edit lines.
 | ||||
|         case WXK_CONTROL_A: post_event(SimpleEvent(EVT_GLCANVAS_SELECT_ALL)); break; | ||||
| #endif /* __APPLE__ */ | ||||
| #ifdef __APPLE__ | ||||
|         case WXK_BACK: // the low cost Apple solutions are not equipped with a Delete key, use Backspace instead.
 | ||||
| #endif /* __APPLE__ */ | ||||
|         case WXK_DELETE:    post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); break; | ||||
|         default:            evt.Skip(); | ||||
|         } | ||||
|     } else if (evt.HasModifiers()) { | ||||
|         evt.Skip(); | ||||
|     else | ||||
|     { | ||||
|         int keyCode = evt.GetKeyCode(); | ||||
|         switch (keyCode - 48) | ||||
|     } else { | ||||
|         switch (keyCode) | ||||
|         { | ||||
|         // numerical input
 | ||||
|         case 0: { select_view("iso"); break; } | ||||
|         case 1: { select_view("top"); break; } | ||||
|         case 2: { select_view("bottom"); break; } | ||||
|         case 3: { select_view("front"); break; } | ||||
|         case 4: { select_view("rear"); break; } | ||||
|         case 5: { select_view("left"); break; } | ||||
|         case 6: { select_view("right"); break; } | ||||
|         // key ESC
 | ||||
|         case WXK_ESCAPE: { m_gizmos.reset_all_states(); m_dirty = true;  break; } | ||||
| #ifdef __APPLE__ | ||||
|         case WXK_BACK: // the low cost Apple solutions are not equipped with a Delete key, use Backspace instead.
 | ||||
| #endif /* __APPLE__ */ | ||||
|         case WXK_DELETE: post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE)); break; | ||||
|         case '0': { select_view("iso"); break; } | ||||
|         case '1': { select_view("top"); break; } | ||||
|         case '2': { select_view("bottom"); break; } | ||||
|         case '3': { select_view("front"); break; } | ||||
|         case '4': { select_view("rear"); break; } | ||||
|         case '5': { select_view("left"); break; } | ||||
|         case '6': { select_view("right"); break; } | ||||
|         case '+': { post_event(Event<int>(EVT_GLCANVAS_INCREASE_INSTANCES, +1)); break; } | ||||
|         case '-': { post_event(Event<int>(EVT_GLCANVAS_INCREASE_INSTANCES, -1)); break; } | ||||
|         case '?': { post_event(SimpleEvent(EVT_GLCANVAS_QUESTION_MARK)); break; } | ||||
|         case 'A': | ||||
|         case 'a': { post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE)); break; } | ||||
|         case 'B': | ||||
|         case 'b': { zoom_to_bed(); break; } | ||||
|         case 'I': | ||||
|         case 'i': { set_camera_zoom(1.0f); break; } | ||||
|         case 'O': | ||||
|         case 'o': { set_camera_zoom(-1.0f); break; } | ||||
|         case 'Z': | ||||
|         case 'z': { m_selection.is_empty() ? zoom_to_volumes() : zoom_to_selection(); break; } | ||||
|         default: | ||||
|         { | ||||
|             if (m_gizmos.handle_shortcut(keyCode, m_selection)) | ||||
|             { | ||||
|                 // text input
 | ||||
|                 switch (keyCode) | ||||
|                 { | ||||
|                 // key ESC
 | ||||
|                 case 27: { m_gizmos.reset_all_states(); m_dirty = true;  break; } | ||||
|                 // key +
 | ||||
|                 case 43: { post_event(Event<int>(EVT_GLCANVAS_INCREASE_INSTANCES, +1)); break; } | ||||
|                 // key -
 | ||||
|                 case 45: { post_event(Event<int>(EVT_GLCANVAS_INCREASE_INSTANCES, -1)); break; } | ||||
|                 // key ?
 | ||||
|                 case 63: { post_event(SimpleEvent(EVT_GLCANVAS_QUESTION_MARK)); break; } | ||||
|                 // key A/a
 | ||||
|                 case 65: | ||||
|                 case 97: { post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE)); break; } | ||||
|                 // key B/b
 | ||||
|                 case 66: | ||||
|                 case 98: { zoom_to_bed(); break; } | ||||
|                 // key I/i
 | ||||
|                 case 73: | ||||
|                 case 105: { set_camera_zoom(1.0f); break; } | ||||
|                 // key O/o
 | ||||
|                 case 79: | ||||
|                 case 111: { set_camera_zoom(-1.0f); break; } | ||||
|                 // key Z/z
 | ||||
|                 case 90: | ||||
|                 case 122: | ||||
|                 { | ||||
|                     if (m_selection.is_empty()) | ||||
|                         zoom_to_volumes(); | ||||
|                     else | ||||
|                         zoom_to_selection(); | ||||
| 
 | ||||
|                     break; | ||||
|                 } | ||||
|                 default: | ||||
|                 { | ||||
|                     if (m_gizmos.handle_shortcut(keyCode, m_selection)) | ||||
|                     { | ||||
|                         _update_gizmos_data(); | ||||
|                         m_dirty = true; | ||||
|                     } | ||||
|                     else | ||||
|                         evt.Skip(); | ||||
| 
 | ||||
|                     break; | ||||
|                 } | ||||
|                 } | ||||
|                 _update_gizmos_data(); | ||||
|                 m_dirty = true; | ||||
|             } | ||||
|             else | ||||
|                 evt.Skip(); | ||||
| 
 | ||||
|             break; | ||||
|         } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -5354,9 +5358,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) | |||
|                     bool already_selected = m_selection.contains_volume(m_hover_volume_id); | ||||
|                     bool shift_down = evt.ShiftDown(); | ||||
| 
 | ||||
| #if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION | ||||
|                     Selection::IndicesList curr_idxs = m_selection.get_volume_idxs(); | ||||
| #endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
 | ||||
| 
 | ||||
|                     if (already_selected && shift_down) | ||||
|                         m_selection.remove(m_hover_volume_id); | ||||
|  | @ -5373,21 +5375,14 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) | |||
| #endif // ENABLE_MOVE_MIN_THRESHOLD
 | ||||
|                     } | ||||
| 
 | ||||
| #if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION | ||||
|                     if (curr_idxs != m_selection.get_volume_idxs()) | ||||
|                     { | ||||
| #endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
 | ||||
| 
 | ||||
|                         m_gizmos.update_on_off_state(m_selection); | ||||
|                         _update_gizmos_data(); | ||||
| #if !ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION | ||||
|                         wxGetApp().obj_manipul()->update_settings_value(m_selection); | ||||
| #endif // !ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
 | ||||
|                         post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); | ||||
|                         m_dirty = true; | ||||
| #if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION | ||||
|                     } | ||||
| #endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
 | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|  | @ -5679,24 +5674,6 @@ void GLCanvas3D::on_paint(wxPaintEvent& evt) | |||
|         this->render(); | ||||
| } | ||||
| 
 | ||||
| void GLCanvas3D::on_key_down(wxKeyEvent& evt) | ||||
| { | ||||
|     if (evt.HasModifiers()) | ||||
|         evt.Skip(); | ||||
|     else | ||||
|     { | ||||
|         int key = evt.GetKeyCode(); | ||||
| #ifdef __WXOSX__ | ||||
|         if (key == WXK_BACK) | ||||
| #else | ||||
|         if (key == WXK_DELETE) | ||||
| #endif // __WXOSX__
 | ||||
|             post_event(SimpleEvent(EVT_GLCANVAS_REMOVE_OBJECT)); | ||||
|         else | ||||
|             evt.Skip(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| Size GLCanvas3D::get_canvas_size() const | ||||
| { | ||||
|     int w = 0; | ||||
|  | @ -5782,7 +5759,6 @@ void GLCanvas3D::do_move() | |||
|             ModelObject* model_object = m_model->objects[object_idx]; | ||||
|             if (model_object != nullptr) | ||||
|             { | ||||
| #if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION | ||||
|                 if (selection_mode == Selection::Instance) | ||||
|                     model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); | ||||
|                 else if (selection_mode == Selection::Volume) | ||||
|  | @ -5790,20 +5766,6 @@ void GLCanvas3D::do_move() | |||
| 
 | ||||
|                 object_moved = true; | ||||
|                 model_object->invalidate_bounding_box(); | ||||
| #else | ||||
|                 if (selection_mode == Selection::Instance) | ||||
|                 { | ||||
|                     model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); | ||||
|                     object_moved = true; | ||||
|                 } | ||||
|                 else if (selection_mode == Selection::Volume) | ||||
|                 { | ||||
|                     model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); | ||||
|                     object_moved = true; | ||||
|                 } | ||||
|                 if (object_moved) | ||||
|                     model_object->invalidate_bounding_box(); | ||||
| #endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
 | ||||
|             } | ||||
|         } | ||||
|         else if (object_idx == 1000) | ||||
|  | @ -5874,12 +5836,8 @@ void GLCanvas3D::do_rotate() | |||
|         m->translate_instance(i.second, shift); | ||||
|     } | ||||
| 
 | ||||
| #if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION | ||||
|     if (!done.empty()) | ||||
|         post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED)); | ||||
| #else | ||||
|     post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); | ||||
| #endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
 | ||||
| } | ||||
| 
 | ||||
| void GLCanvas3D::do_scale() | ||||
|  | @ -5930,12 +5888,8 @@ void GLCanvas3D::do_scale() | |||
|         m->translate_instance(i.second, shift); | ||||
|     } | ||||
| 
 | ||||
| #if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION | ||||
|     if (!done.empty()) | ||||
|         post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED)); | ||||
| #else | ||||
|     post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); | ||||
| #endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
 | ||||
| } | ||||
| 
 | ||||
| void GLCanvas3D::do_flatten() | ||||
|  | @ -6097,7 +6051,7 @@ bool GLCanvas3D::_init_toolbar() | |||
|     GLToolbarItem::Data item; | ||||
| 
 | ||||
|     item.name = "add"; | ||||
|     item.tooltip = GUI::L_str("Add... [Ctrl+I]"); | ||||
|     item.tooltip = GUI::L_str("Add...") + " [" + GUI::shortkey_ctrl_prefix() + "I]"; | ||||
|     item.sprite_id = 0; | ||||
|     item.is_toggable = false; | ||||
|     item.action_event = EVT_GLTOOLBAR_ADD; | ||||
|  | @ -6105,7 +6059,7 @@ bool GLCanvas3D::_init_toolbar() | |||
|         return false; | ||||
| 
 | ||||
|     item.name = "delete"; | ||||
|     item.tooltip = GUI::L_str("Delete [Del]"); | ||||
|     item.tooltip = GUI::L_str("Delete") + " [Del]"; | ||||
|     item.sprite_id = 1; | ||||
|     item.is_toggable = false; | ||||
|     item.action_event = EVT_GLTOOLBAR_DELETE; | ||||
|  | @ -6113,7 +6067,7 @@ bool GLCanvas3D::_init_toolbar() | |||
|         return false; | ||||
| 
 | ||||
|     item.name = "deleteall"; | ||||
|     item.tooltip = GUI::L_str("Delete all [Ctrl+Del]"); | ||||
|     item.tooltip = GUI::L_str("Delete all") + " [" + GUI::shortkey_ctrl_prefix() + "Del]"; | ||||
|     item.sprite_id = 2; | ||||
|     item.is_toggable = false; | ||||
|     item.action_event = EVT_GLTOOLBAR_DELETE_ALL; | ||||
|  |  | |||
|  | @ -121,14 +121,13 @@ wxDECLARE_EVENT(EVT_GLCANVAS_VIEWPORT_CHANGED, SimpleEvent); | |||
| wxDECLARE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, Vec2dEvent); | ||||
| wxDECLARE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent); | ||||
| wxDECLARE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent); | ||||
| wxDECLARE_EVENT(EVT_GLCANVAS_SELECT_ALL, SimpleEvent); | ||||
| wxDECLARE_EVENT(EVT_GLCANVAS_QUESTION_MARK, SimpleEvent); | ||||
| wxDECLARE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event<int>); // data: +1 => increase, -1 => decrease
 | ||||
| wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent); | ||||
| wxDECLARE_EVENT(EVT_GLCANVAS_WIPETOWER_MOVED, Vec3dEvent); | ||||
| #if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION | ||||
| wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_ROTATED, SimpleEvent); | ||||
| wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_SCALED, SimpleEvent); | ||||
| #endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
 | ||||
| wxDECLARE_EVENT(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, Event<bool>); | ||||
| wxDECLARE_EVENT(EVT_GLCANVAS_UPDATE_GEOMETRY, Vec3dsEvent<2>); | ||||
| wxDECLARE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent); | ||||
|  | @ -1044,7 +1043,6 @@ public: | |||
|     void on_timer(wxTimerEvent& evt); | ||||
|     void on_mouse(wxMouseEvent& evt); | ||||
|     void on_paint(wxPaintEvent& evt); | ||||
|     void on_key_down(wxKeyEvent& evt); | ||||
| 
 | ||||
|     Size get_canvas_size() const; | ||||
|     Point get_local_mouse_position() const; | ||||
|  |  | |||
|  | @ -75,6 +75,30 @@ void break_to_debugger() | |||
|     #endif /* _WIN32 */ | ||||
| } | ||||
| 
 | ||||
| const std::string& shortkey_ctrl_prefix() | ||||
| { | ||||
| 	static const std::string str =  | ||||
| #ifdef __APPLE__ | ||||
| 		"⌘" | ||||
| #else | ||||
| 		"Ctrl+" | ||||
| #endif | ||||
| 		; | ||||
| 	return str; | ||||
| } | ||||
| 
 | ||||
| const std::string& shortkey_alt_prefix() | ||||
| { | ||||
| 	static const std::string str =  | ||||
| #ifdef __APPLE__ | ||||
| 		"⌥" | ||||
| #else | ||||
| 		"Alt+" | ||||
| #endif | ||||
| 		; | ||||
| 	return str; | ||||
| } | ||||
| 
 | ||||
| bool config_wizard_startup(bool app_config_exists) | ||||
| { | ||||
|     if (!app_config_exists || wxGetApp().preset_bundle->printers.size() <= 1) { | ||||
|  |  | |||
|  | @ -27,7 +27,11 @@ void enable_screensaver(); | |||
| bool debugged(); | ||||
| void break_to_debugger(); | ||||
| 
 | ||||
| AppConfig*		get_app_config(); | ||||
| // Platform specific Ctrl+/Alt+ (Windows, Linux) vs. ⌘/⌥ (OSX) prefixes 
 | ||||
| extern const std::string& shortkey_ctrl_prefix(); | ||||
| extern const std::string& shortkey_alt_prefix(); | ||||
| 
 | ||||
| extern AppConfig* get_app_config(); | ||||
| 
 | ||||
| extern void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_language_change); | ||||
| 
 | ||||
|  |  | |||
|  | @ -562,7 +562,13 @@ void GUI_App::add_config_menu(wxMenuBar *menu) | |||
|     local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _(L("Take Configuration &Snapshot")), _(L("Capture a configuration snapshot"))); | ||||
|     // 	local_menu->Append(config_id_base + ConfigMenuUpdate, 		_(L("Check for updates")), 					_(L("Check for configuration updates")));
 | ||||
|     local_menu->AppendSeparator(); | ||||
|     local_menu->Append(config_id_base + ConfigMenuPreferences, _(L("&Preferences")) + dots + "\tCtrl+P", _(L("Application preferences"))); | ||||
|     local_menu->Append(config_id_base + ConfigMenuPreferences, _(L("&Preferences")) + dots +  | ||||
| #ifdef __APPLE__ | ||||
|         "\tCtrl+,", | ||||
| #else | ||||
|         "\tCtrl+P", | ||||
| #endif | ||||
|         _(L("Application preferences"))); | ||||
|     local_menu->AppendSeparator(); | ||||
|     auto mode_menu = new wxMenu(); | ||||
|     mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeSimple, _(L("Simple")), _(L("Simple View Mode"))); | ||||
|  |  | |||
|  | @ -66,6 +66,12 @@ ObjectList::ObjectList(wxWindow* parent) : | |||
| 
 | ||||
|     // describe control behavior 
 | ||||
|     Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxEvent& event) { | ||||
| #ifndef __APPLE__ | ||||
|         // On Windows and Linux, forces a kill focus emulation on the object manipulator fields because this event handler is called
 | ||||
|         // before the kill focus event handler on the object manipulator when changing selection in the list, invalidating the object
 | ||||
|         // manipulator cache with the following call to selection_changed()
 | ||||
|         wxGetApp().obj_manipul()->emulate_kill_focus(); | ||||
| #endif // __APPLE__
 | ||||
|         selection_changed(); | ||||
| #ifndef __WXMSW__ | ||||
|         set_tooltip_for_item(get_mouse_position_in_control()); | ||||
|  |  | |||
|  | @ -17,103 +17,24 @@ namespace GUI | |||
| 
 | ||||
| ObjectManipulation::ObjectManipulation(wxWindow* parent) : | ||||
|     OG_Settings(parent, true) | ||||
| #ifndef __APPLE__ | ||||
|     , m_focused_option("") | ||||
| #endif // __APPLE__
 | ||||
| { | ||||
|     m_og->set_name(_(L("Object Manipulation"))); | ||||
|     m_og->label_width = 9 * wxGetApp().em_unit();//125;
 | ||||
|     m_og->set_grid_vgap(5); | ||||
|      | ||||
|     m_og->m_on_change = [this](const std::string& opt_key, const boost::any& value) { | ||||
|         std::vector<std::string> axes{ "_x", "_y", "_z" }; | ||||
| 
 | ||||
|         std::string param;  | ||||
|         std::copy(opt_key.begin(), opt_key.end() - 2, std::back_inserter(param)); | ||||
| 
 | ||||
|         size_t i = 0; | ||||
|         Vec3d new_value; | ||||
|         for (auto axis : axes) | ||||
|             new_value(i++) = boost::any_cast<double>(m_og->get_value(param+axis)); | ||||
| 
 | ||||
|         if (param == "position") | ||||
|             change_position_value(new_value); | ||||
|         else if (param == "rotation") | ||||
|             change_rotation_value(new_value); | ||||
|         else if (param == "scale") | ||||
| #if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION | ||||
|             change_scale_value(new_value); | ||||
| #else | ||||
|             change_scale_value(new_value); | ||||
| #endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
 | ||||
|         else if (param == "size") | ||||
| #if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION | ||||
|             change_size_value(new_value); | ||||
| #else | ||||
|             change_size_value(new_value); | ||||
| #endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
 | ||||
| 
 | ||||
|         wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key, false); | ||||
|     }; | ||||
| 
 | ||||
|     m_og->m_fill_empty_value = [this](const std::string& opt_key) | ||||
|     { | ||||
| #if !ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION | ||||
|         this->update_if_dirty(); | ||||
| #endif // !ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
 | ||||
| 
 | ||||
|         std::string param; | ||||
|         std::copy(opt_key.begin(), opt_key.end() - 2, std::back_inserter(param));  | ||||
| 
 | ||||
|         double value = 0.0; | ||||
| 
 | ||||
|         if (param == "position") { | ||||
|             int axis = opt_key.back() == 'x' ? 0 : | ||||
|                        opt_key.back() == 'y' ? 1 : 2; | ||||
| 
 | ||||
| #if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION | ||||
|             value = m_cache.position(axis); | ||||
| #else | ||||
|             value = m_cache_position(axis); | ||||
| #endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
 | ||||
|         } | ||||
|         else if (param == "rotation") { | ||||
|             int axis = opt_key.back() == 'x' ? 0 : | ||||
|                 opt_key.back() == 'y' ? 1 : 2; | ||||
| 
 | ||||
| #if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION | ||||
|             value = m_cache.rotation(axis); | ||||
| #else | ||||
|             value = m_cache_rotation(axis); | ||||
| #endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
 | ||||
|         } | ||||
|         else if (param == "scale") { | ||||
|             int axis = opt_key.back() == 'x' ? 0 : | ||||
|                 opt_key.back() == 'y' ? 1 : 2; | ||||
| 
 | ||||
| #if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION | ||||
|             value = m_cache.scale(axis); | ||||
| #else | ||||
|             value = m_cache_scale(axis); | ||||
| #endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
 | ||||
|         } | ||||
|         else if (param == "size") { | ||||
|             int axis = opt_key.back() == 'x' ? 0 : | ||||
|                 opt_key.back() == 'y' ? 1 : 2; | ||||
| 
 | ||||
| #if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION | ||||
|             value = m_cache.size(axis); | ||||
| #else | ||||
|             value = m_cache_size(axis); | ||||
| #endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
 | ||||
|         } | ||||
| 
 | ||||
|         m_og->set_value(opt_key, double_to_string(value)); | ||||
|         wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key, false); | ||||
|     }; | ||||
|     m_og->m_on_change = std::bind(&ObjectManipulation::on_change, this, std::placeholders::_1, std::placeholders::_2); | ||||
|     m_og->m_fill_empty_value = std::bind(&ObjectManipulation::on_fill_empty_value, this, std::placeholders::_1); | ||||
| 
 | ||||
|     m_og->m_set_focus = [this](const std::string& opt_key) | ||||
|     { | ||||
| #if !ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION | ||||
|         this->update_if_dirty(); | ||||
| #endif // !ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
 | ||||
| #ifndef __APPLE__ | ||||
|         m_focused_option = opt_key; | ||||
| #endif // __APPLE__
 | ||||
| 
 | ||||
|         // needed to show the visual hints in 3D scene
 | ||||
|         wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key, true); | ||||
|     }; | ||||
| 
 | ||||
|  | @ -233,9 +154,6 @@ void ObjectManipulation::UpdateAndShow(const bool show) | |||
| { | ||||
|     if (show) { | ||||
|         update_settings_value(wxGetApp().plater()->canvas3D()->get_selection()); | ||||
| #if !ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION | ||||
|         update_if_dirty(); | ||||
| #endif // !ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
 | ||||
|     } | ||||
| 
 | ||||
|     OG_Settings::UpdateAndShow(show); | ||||
|  | @ -254,7 +172,6 @@ void ObjectManipulation::update_settings_value(const GLCanvas3D::Selection& sele | |||
|         m_new_rotation = volume->get_instance_rotation(); | ||||
|         m_new_scale    = volume->get_instance_scaling_factor(); | ||||
|         int obj_idx = volume->object_idx(); | ||||
| #if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION | ||||
|         int instance_idx = volume->instance_idx(); | ||||
|         if ((0 <= obj_idx) && (obj_idx < (int)wxGetApp().model_objects()->size())) | ||||
|         { | ||||
|  | @ -270,21 +187,12 @@ void ObjectManipulation::update_settings_value(const GLCanvas3D::Selection& sele | |||
|         else | ||||
|             // this should never happen
 | ||||
|             m_new_size = Vec3d::Zero(); | ||||
| #else | ||||
|         if ((0 <= obj_idx) && (obj_idx < (int)wxGetApp().model_objects()->size())) | ||||
|             m_new_size = volume->get_instance_transformation().get_matrix(true, true) * (*wxGetApp().model_objects())[obj_idx]->raw_mesh_bounding_box().size(); | ||||
|         else | ||||
|             // this should never happen
 | ||||
|             m_new_size = Vec3d::Zero(); | ||||
| #endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
 | ||||
| 
 | ||||
|         m_new_enabled  = true; | ||||
|     } | ||||
|     else if (selection.is_single_full_object()) | ||||
|     { | ||||
| #if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION | ||||
|         m_cache.instance.reset(); | ||||
| #endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
 | ||||
| 
 | ||||
|         const BoundingBoxf3& box = selection.get_bounding_box(); | ||||
|         m_new_position = box.center(); | ||||
|  | @ -297,9 +205,7 @@ void ObjectManipulation::update_settings_value(const GLCanvas3D::Selection& sele | |||
|     } | ||||
|     else if (selection.is_single_modifier() || selection.is_single_volume()) | ||||
|     { | ||||
| #if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION | ||||
|         m_cache.instance.reset(); | ||||
| #endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
 | ||||
| 
 | ||||
|         // the selection contains a single volume
 | ||||
|         const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); | ||||
|  | @ -329,7 +235,6 @@ void ObjectManipulation::update_if_dirty() | |||
|     if (!m_dirty) | ||||
|         return; | ||||
| 
 | ||||
| #if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION | ||||
|     if (m_cache.move_label_string != _(m_new_move_label_string)+ ":") | ||||
|     { | ||||
|         m_cache.move_label_string = _(m_new_move_label_string)+ ":"; | ||||
|  | @ -382,16 +287,22 @@ void ObjectManipulation::update_if_dirty() | |||
| 
 | ||||
|     m_cache.size = m_new_size; | ||||
| 
 | ||||
|     Vec3d deg_rotation; | ||||
|     for (size_t i = 0; i < 3; ++i) | ||||
|     { | ||||
|         deg_rotation(i) = Geometry::rad2deg(m_new_rotation(i)); | ||||
|     } | ||||
| 
 | ||||
|     if (m_cache.rotation(0) != m_new_rotation(0)) | ||||
|         m_og->set_value("rotation_x", double_to_string(Geometry::rad2deg(m_new_rotation(0)), 2)); | ||||
|         m_og->set_value("rotation_x", double_to_string(deg_rotation(0), 2)); | ||||
| 
 | ||||
|     if (m_cache.rotation(1) != m_new_rotation(1)) | ||||
|         m_og->set_value("rotation_y", double_to_string(Geometry::rad2deg(m_new_rotation(1)), 2)); | ||||
|         m_og->set_value("rotation_y", double_to_string(deg_rotation(1), 2)); | ||||
| 
 | ||||
|     if (m_cache.rotation(2) != m_new_rotation(2)) | ||||
|         m_og->set_value("rotation_z", double_to_string(Geometry::rad2deg(m_new_rotation(2)), 2)); | ||||
|         m_og->set_value("rotation_z", double_to_string(deg_rotation(2), 2)); | ||||
| 
 | ||||
|     m_cache.rotation = m_new_rotation; | ||||
|     m_cache.rotation = deg_rotation; | ||||
| 
 | ||||
|     if (wxGetApp().plater()->canvas3D()->get_selection().requires_uniform_scale()) { | ||||
|         m_lock_bnt->SetLock(true); | ||||
|  | @ -406,41 +317,27 @@ void ObjectManipulation::update_if_dirty() | |||
|         m_og->enable(); | ||||
|     else | ||||
|         m_og->disable(); | ||||
| #else | ||||
|     m_move_Label->SetLabel(_(m_new_move_label_string)); | ||||
|     m_rotate_Label->SetLabel(_(m_new_rotate_label_string)); | ||||
|     m_scale_Label->SetLabel(_(m_new_scale_label_string)); | ||||
| 
 | ||||
|     m_og->set_value("position_x", double_to_string(m_new_position(0), 2)); | ||||
|     m_og->set_value("position_y", double_to_string(m_new_position(1), 2)); | ||||
|     m_og->set_value("position_z", double_to_string(m_new_position(2), 2)); | ||||
|     m_cache_position = m_new_position; | ||||
| 
 | ||||
|     auto scale = m_new_scale * 100.0; | ||||
|     m_og->set_value("scale_x", double_to_string(scale(0), 2)); | ||||
|     m_og->set_value("scale_y", double_to_string(scale(1), 2)); | ||||
|     m_og->set_value("scale_z", double_to_string(scale(2), 2)); | ||||
|     m_cache_scale = scale; | ||||
| 
 | ||||
|     m_og->set_value("size_x", double_to_string(m_new_size(0), 2)); | ||||
|     m_og->set_value("size_y", double_to_string(m_new_size(1), 2)); | ||||
|     m_og->set_value("size_z", double_to_string(m_new_size(2), 2)); | ||||
|     m_cache_size = m_new_size; | ||||
| 
 | ||||
|     m_og->set_value("rotation_x", double_to_string(round_nearest(Geometry::rad2deg(m_new_rotation(0)), 0), 2)); | ||||
|     m_og->set_value("rotation_y", double_to_string(round_nearest(Geometry::rad2deg(m_new_rotation(1)), 0), 2)); | ||||
|     m_og->set_value("rotation_z", double_to_string(round_nearest(Geometry::rad2deg(m_new_rotation(2)), 0), 2)); | ||||
|     m_cache_rotation = m_new_rotation; | ||||
| 
 | ||||
|     if (m_new_enabled) | ||||
|         m_og->enable(); | ||||
|     else | ||||
|         m_og->disable(); | ||||
| #endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
 | ||||
| 
 | ||||
|     m_dirty = false; | ||||
| } | ||||
| 
 | ||||
| #ifndef __APPLE__ | ||||
| void ObjectManipulation::emulate_kill_focus() | ||||
| { | ||||
|     if (m_focused_option.empty()) | ||||
|         return; | ||||
| 
 | ||||
|     // we need to use a copy because the value of m_focused_option is modified inside on_change() and on_fill_empty_value()
 | ||||
|     std::string option = m_focused_option; | ||||
| 
 | ||||
|     // see TextCtrl::propagate_value()
 | ||||
|     if (static_cast<wxTextCtrl*>(m_og->get_fieldc(option, 0)->getWindow())->GetValue().empty()) | ||||
|         on_fill_empty_value(option); | ||||
|     else | ||||
|         on_change(option, 0); | ||||
| } | ||||
| #endif // __APPLE__
 | ||||
| 
 | ||||
| void ObjectManipulation::reset_settings_value() | ||||
| { | ||||
|     m_new_position = Vec3d::Zero(); | ||||
|  | @ -448,9 +345,7 @@ void ObjectManipulation::reset_settings_value() | |||
|     m_new_scale = Vec3d::Ones(); | ||||
|     m_new_size = Vec3d::Zero(); | ||||
|     m_new_enabled = false; | ||||
| #if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION | ||||
|     m_cache.instance.reset(); | ||||
| #endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
 | ||||
|     m_dirty = true; | ||||
| } | ||||
| 
 | ||||
|  | @ -459,18 +354,10 @@ void ObjectManipulation::change_position_value(const Vec3d& position) | |||
|     auto canvas = wxGetApp().plater()->canvas3D(); | ||||
|     GLCanvas3D::Selection& selection = canvas->get_selection(); | ||||
|     selection.start_dragging(); | ||||
| #if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION | ||||
|     selection.translate(position - m_cache.position, selection.requires_local_axes()); | ||||
| #else | ||||
|     selection.translate(position - m_cache_position, selection.requires_local_axes()); | ||||
| #endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
 | ||||
|     canvas->do_move(); | ||||
| 
 | ||||
| #if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION | ||||
|     m_cache.position = position; | ||||
| #else | ||||
|     m_cache_position = position; | ||||
| #endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
 | ||||
| } | ||||
| 
 | ||||
| void ObjectManipulation::change_rotation_value(const Vec3d& rotation) | ||||
|  | @ -478,23 +365,19 @@ void ObjectManipulation::change_rotation_value(const Vec3d& rotation) | |||
|     GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); | ||||
|     const GLCanvas3D::Selection& selection = canvas->get_selection(); | ||||
| 
 | ||||
|     Vec3d delta_rotation = rotation - m_cache.rotation; | ||||
| 
 | ||||
|     Vec3d rad_rotation; | ||||
|     for (size_t i = 0; i < 3; ++i) | ||||
|     { | ||||
|         rad_rotation(i) = Geometry::deg2rad(rotation(i)); | ||||
|         rad_rotation(i) = Geometry::deg2rad(delta_rotation(i)); | ||||
|     } | ||||
| 
 | ||||
|     canvas->get_selection().start_dragging(); | ||||
| #if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION | ||||
|     canvas->get_selection().rotate(rad_rotation, selection.is_single_full_instance() || selection.requires_local_axes()); | ||||
| #else | ||||
|     canvas->get_selection().rotate(rad_rotation, selection.is_single_full_instance()); | ||||
| #endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
 | ||||
|     canvas->do_rotate(); | ||||
| 
 | ||||
| #if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION | ||||
|     m_cache.rotation = rotation; | ||||
| #endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
 | ||||
| } | ||||
| 
 | ||||
| void ObjectManipulation::change_scale_value(const Vec3d& scale) | ||||
|  | @ -503,11 +386,7 @@ void ObjectManipulation::change_scale_value(const Vec3d& scale) | |||
|     const GLCanvas3D::Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); | ||||
|     if (m_uniform_scale || selection.requires_uniform_scale()) | ||||
|     { | ||||
| #if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION | ||||
|         Vec3d abs_scale_diff = (scale - m_cache.scale).cwiseAbs(); | ||||
| #else | ||||
|         Vec3d abs_scale_diff = (scale - m_cache_scale).cwiseAbs(); | ||||
| #endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
 | ||||
|         double max_diff = abs_scale_diff(X); | ||||
|         Axis max_diff_axis = X; | ||||
|         if (max_diff < abs_scale_diff(Y)) | ||||
|  | @ -530,12 +409,10 @@ void ObjectManipulation::change_scale_value(const Vec3d& scale) | |||
|     canvas->get_selection().scale(scaling_factor, false); | ||||
|     canvas->do_scale(); | ||||
| 
 | ||||
| #if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION | ||||
|     if (!m_cache.scale.isApprox(scale)) | ||||
|         m_cache.instance.instance_idx = -1; | ||||
| 
 | ||||
|     m_cache.scale = scale; | ||||
| #endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
 | ||||
| } | ||||
| 
 | ||||
| void ObjectManipulation::change_size_value(const Vec3d& size) | ||||
|  | @ -543,7 +420,6 @@ void ObjectManipulation::change_size_value(const Vec3d& size) | |||
|     const GLCanvas3D::Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); | ||||
| 
 | ||||
|     Vec3d ref_size = m_cache.size; | ||||
| #if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION | ||||
|     if (selection.is_single_volume() || selection.is_single_modifier()) | ||||
|     { | ||||
|         const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); | ||||
|  | @ -581,15 +457,81 @@ void ObjectManipulation::change_size_value(const Vec3d& size) | |||
|     canvas->do_scale(); | ||||
| 
 | ||||
|     m_cache.size = size; | ||||
| #else | ||||
|     if (selection.is_single_full_instance()) | ||||
|     { | ||||
|         const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); | ||||
|         ref_size = volume->bounding_box.size(); | ||||
| } | ||||
| 
 | ||||
| void ObjectManipulation::on_change(const t_config_option_key& opt_key, const boost::any& value) | ||||
| { | ||||
|     // needed to hide the visual hints in 3D scene
 | ||||
|     wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key, false); | ||||
| #ifndef __APPLE__ | ||||
|     m_focused_option = ""; | ||||
| #endif // __APPLE__
 | ||||
| 
 | ||||
|     if (!m_cache.is_valid()) | ||||
|         return; | ||||
| 
 | ||||
|     std::vector<std::string> axes{ "_x", "_y", "_z" }; | ||||
| 
 | ||||
|     std::string param; | ||||
|     std::copy(opt_key.begin(), opt_key.end() - 2, std::back_inserter(param)); | ||||
| 
 | ||||
|     size_t i = 0; | ||||
|     Vec3d new_value; | ||||
|     for (auto axis : axes) | ||||
|         new_value(i++) = boost::any_cast<double>(m_og->get_value(param + axis)); | ||||
| 
 | ||||
|     if (param == "position") | ||||
|         change_position_value(new_value); | ||||
|     else if (param == "rotation") | ||||
|         change_rotation_value(new_value); | ||||
|     else if (param == "scale") | ||||
|         change_scale_value(new_value); | ||||
|     else if (param == "size") | ||||
|         change_size_value(new_value); | ||||
| } | ||||
| 
 | ||||
| void ObjectManipulation::on_fill_empty_value(const std::string& opt_key) | ||||
| { | ||||
|     // needed to hide the visual hints in 3D scene
 | ||||
|     wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key, false); | ||||
| #ifndef __APPLE__ | ||||
|     m_focused_option = ""; | ||||
| #endif // __APPLE__
 | ||||
| 
 | ||||
|     if (!m_cache.is_valid()) | ||||
|         return; | ||||
| 
 | ||||
|     std::string param; | ||||
|     std::copy(opt_key.begin(), opt_key.end() - 2, std::back_inserter(param)); | ||||
| 
 | ||||
|     double value = 0.0; | ||||
| 
 | ||||
|     if (param == "position") { | ||||
|         int axis = opt_key.back() == 'x' ? 0 : | ||||
|             opt_key.back() == 'y' ? 1 : 2; | ||||
| 
 | ||||
|         value = m_cache.position(axis); | ||||
|     } | ||||
|     else if (param == "rotation") { | ||||
|         int axis = opt_key.back() == 'x' ? 0 : | ||||
|             opt_key.back() == 'y' ? 1 : 2; | ||||
| 
 | ||||
|         value = m_cache.rotation(axis); | ||||
|     } | ||||
|     else if (param == "scale") { | ||||
|         int axis = opt_key.back() == 'x' ? 0 : | ||||
|             opt_key.back() == 'y' ? 1 : 2; | ||||
| 
 | ||||
|         value = m_cache.scale(axis); | ||||
|     } | ||||
|     else if (param == "size") { | ||||
|         int axis = opt_key.back() == 'x' ? 0 : | ||||
|             opt_key.back() == 'y' ? 1 : 2; | ||||
| 
 | ||||
|         value = m_cache.size(axis); | ||||
|     } | ||||
| 
 | ||||
|     change_scale_value(100.0 * Vec3d(size(0) / ref_size(0), size(1) / ref_size(1), size(2) / ref_size(2))); | ||||
| #endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
 | ||||
|     m_og->set_value(opt_key, double_to_string(value)); | ||||
| } | ||||
| 
 | ||||
| } //namespace GUI
 | ||||
|  |  | |||
|  | @ -15,7 +15,6 @@ namespace GUI { | |||
| 
 | ||||
| class ObjectManipulation : public OG_Settings | ||||
| { | ||||
| #if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION | ||||
|     struct Cache | ||||
|     { | ||||
|         Vec3d position; | ||||
|  | @ -43,20 +42,22 @@ class ObjectManipulation : public OG_Settings | |||
| 
 | ||||
|         Instance instance; | ||||
| 
 | ||||
|         Cache() : position(Vec3d(DBL_MAX, DBL_MAX, DBL_MAX)) , rotation(Vec3d(DBL_MAX, DBL_MAX, DBL_MAX)) | ||||
|             , scale(Vec3d(DBL_MAX, DBL_MAX, DBL_MAX)) , size(Vec3d(DBL_MAX, DBL_MAX, DBL_MAX)) | ||||
|             , move_label_string("") , rotate_label_string("") , scale_label_string("") | ||||
|         Cache() { reset(); } | ||||
|         void reset() | ||||
|         { | ||||
|             position = Vec3d(DBL_MAX, DBL_MAX, DBL_MAX); | ||||
|             rotation = Vec3d(DBL_MAX, DBL_MAX, DBL_MAX); | ||||
|             scale = Vec3d(DBL_MAX, DBL_MAX, DBL_MAX); | ||||
|             size = Vec3d(DBL_MAX, DBL_MAX, DBL_MAX); | ||||
|             move_label_string = ""; | ||||
|             rotate_label_string = ""; | ||||
|             scale_label_string = ""; | ||||
|             instance.reset(); | ||||
|         } | ||||
|         bool is_valid() const { return position != Vec3d(DBL_MAX, DBL_MAX, DBL_MAX); } | ||||
|     }; | ||||
| 
 | ||||
|     Cache m_cache; | ||||
| #else | ||||
|     Vec3d       m_cache_position{ 0., 0., 0. }; | ||||
|     Vec3d       m_cache_rotation{ 0., 0., 0. }; | ||||
|     Vec3d       m_cache_scale{ 100., 100., 100. }; | ||||
|     Vec3d       m_cache_size{ 0., 0., 0. }; | ||||
| #endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
 | ||||
| 
 | ||||
|     wxStaticText*   m_move_Label = nullptr; | ||||
|     wxStaticText*   m_scale_Label = nullptr; | ||||
|  | @ -76,6 +77,11 @@ class ObjectManipulation : public OG_Settings | |||
|     bool            m_uniform_scale {true}; | ||||
|     PrusaLockButton* m_lock_bnt{ nullptr }; | ||||
| 
 | ||||
| #ifndef __APPLE__ | ||||
|     // Currently focused option name (empty if none)
 | ||||
|     std::string     m_focused_option; | ||||
| #endif // __APPLE__
 | ||||
| 
 | ||||
| public: | ||||
|     ObjectManipulation(wxWindow* parent); | ||||
|     ~ObjectManipulation() {} | ||||
|  | @ -92,6 +98,14 @@ public: | |||
|     void        set_uniform_scaling(const bool uniform_scale) { m_uniform_scale = uniform_scale;} | ||||
|     bool        get_uniform_scaling() const { return m_uniform_scale; } | ||||
| 
 | ||||
|     void reset_cache() { m_cache.reset(); } | ||||
| #ifndef __APPLE__ | ||||
|     // On Windows and Linux, emulates a kill focus event on the currently focused option (if any)
 | ||||
|     // Used only in ObjectList wxEVT_DATAVIEW_SELECTION_CHANGED handler which is called before the regular kill focus event
 | ||||
|     // bound to this class when changing selection in the objects list
 | ||||
|     void emulate_kill_focus(); | ||||
| #endif // __APPLE__
 | ||||
| 
 | ||||
| private: | ||||
|     void reset_settings_value(); | ||||
| 
 | ||||
|  | @ -105,6 +119,9 @@ private: | |||
|     void    change_rotation_value(const Vec3d& rotation); | ||||
|     void    change_scale_value(const Vec3d& scale); | ||||
|     void    change_size_value(const Vec3d& size); | ||||
| 
 | ||||
|     void on_change(const t_config_option_key& opt_key, const boost::any& value); | ||||
|     void on_fill_empty_value(const std::string& opt_key); | ||||
| }; | ||||
| 
 | ||||
| }} | ||||
|  |  | |||
|  | @ -88,13 +88,8 @@ KBShortcutsDialog::KBShortcutsDialog() | |||
| 
 | ||||
| void KBShortcutsDialog::fill_shortcuts() | ||||
| { | ||||
| #ifdef __WXOSX__ | ||||
|     const std::string ctrl = "⌘"; | ||||
|     const std::string alt = "⌥"; | ||||
| #else | ||||
|     const std::string ctrl = "Ctrl+"; | ||||
|     const std::string alt = "Alt+"; | ||||
| #endif // __WXOSX__
 | ||||
|     const std::string &ctrl = GUI::shortkey_ctrl_prefix(); | ||||
|     const std::string &alt  = GUI::shortkey_alt_prefix(); | ||||
| 
 | ||||
|     m_full_shortcuts.reserve(4); | ||||
| 
 | ||||
|  |  | |||
|  | @ -329,7 +329,19 @@ void MainFrame::init_menubar() | |||
|     if (m_plater != nullptr) | ||||
|     { | ||||
|         editMenu = new wxMenu(); | ||||
|         wxMenuItem* item_select_all = append_menu_item(editMenu, wxID_ANY, _(L("&Select all")) + "\tCtrl+A", _(L("Selects all objects")), | ||||
|         // \xA0 is a non-breaking space. It is entered here to spoil the automatic accelerators,
 | ||||
|         // as the simple numeric accelerators spoil all numeric data entry.
 | ||||
|         wxMenuItem* item_select_all = append_menu_item(editMenu, wxID_ANY, _(L("&Select all")) + | ||||
| #ifdef _MSC_VER | ||||
|             "\t\xA0" + "Ctrl+\xA0" + "A" | ||||
| #else | ||||
| #ifdef __APPLE__ | ||||
|             "\tCtrl+A" | ||||
| #else | ||||
|             " - Ctrl+A" | ||||
| #endif | ||||
| #endif | ||||
|             , _(L("Selects all objects")), | ||||
|             [this](wxCommandEvent&) { m_plater->select_all(); }, ""); | ||||
|         editMenu->AppendSeparator(); | ||||
|         wxMenuItem* item_delete_sel = append_menu_item(editMenu, wxID_ANY, _(L("&Delete selected")) + "\tDel", _(L("Deletes the current selection")), | ||||
|  | @ -391,19 +403,23 @@ void MainFrame::init_menubar() | |||
| 
 | ||||
|     // View menu
 | ||||
|     wxMenu* viewMenu = nullptr; | ||||
|     wxString sep = | ||||
| #ifdef _MSC_VER | ||||
|         "\t"; | ||||
| #else | ||||
|         " - "; | ||||
| #endif | ||||
|     if (m_plater) { | ||||
|         viewMenu = new wxMenu(); | ||||
|         // \xA0 is a non-breaing space. It is entered here to spoil the automatic accelerators,
 | ||||
|         // as the simple numeric accelerators spoil all numeric data entry.
 | ||||
|         // The camera control accelerators are captured by GLCanvas3D::on_char().
 | ||||
|         wxMenuItem* item_iso = append_menu_item(viewMenu, wxID_ANY, _(L("&Iso")) + "\t\xA0" + "0", _(L("Iso View")), [this](wxCommandEvent&) { select_view("iso"); }); | ||||
| 		wxMenuItem* item_iso = append_menu_item(viewMenu, wxID_ANY, _(L("Iso")) + sep + "&0", _(L("Iso View")), [this](wxCommandEvent&) { select_view("iso"); }); | ||||
|         viewMenu->AppendSeparator(); | ||||
|         wxMenuItem* item_top = append_menu_item(viewMenu, wxID_ANY, _(L("&Top")) + "\t\xA0" + "1", _(L("Top View")), [this](wxCommandEvent&) { select_view("top"); }); | ||||
|         wxMenuItem* item_bottom = append_menu_item(viewMenu, wxID_ANY, _(L("&Bottom")) + "\t\xA0" + "2", _(L("Bottom View")), [this](wxCommandEvent&) { select_view("bottom"); }); | ||||
|         wxMenuItem* item_front = append_menu_item(viewMenu, wxID_ANY, _(L("&Front")) + "\t\xA0" + "3", _(L("Front View")), [this](wxCommandEvent&) { select_view("front"); }); | ||||
|         wxMenuItem* item_rear = append_menu_item(viewMenu, wxID_ANY, _(L("R&ear")) + "\t\xA0" + "4", _(L("Rear View")), [this](wxCommandEvent&) { select_view("rear"); }); | ||||
|         wxMenuItem* item_left = append_menu_item(viewMenu, wxID_ANY, _(L("&Left")) + "\t\xA0" + "5", _(L("Left View")), [this](wxCommandEvent&) { select_view("left"); }); | ||||
|         wxMenuItem* item_right = append_menu_item(viewMenu, wxID_ANY, _(L("&Right")) + "\t\xA0" + "6", _(L("Right View")), [this](wxCommandEvent&) { select_view("right"); }); | ||||
| 		wxMenuItem* item_top = append_menu_item(viewMenu, wxID_ANY, _(L("Top")) + sep + "&1", _(L("Top View")), [this](wxCommandEvent&) { select_view("top"); }); | ||||
| 		wxMenuItem* item_bottom = append_menu_item(viewMenu, wxID_ANY, _(L("Bottom")) + sep + "&2", _(L("Bottom View")), [this](wxCommandEvent&) { select_view("bottom"); }); | ||||
| 		wxMenuItem* item_front = append_menu_item(viewMenu, wxID_ANY, _(L("Front")) + sep + "&3", _(L("Front View")), [this](wxCommandEvent&) { select_view("front"); }); | ||||
| 		wxMenuItem* item_rear = append_menu_item(viewMenu, wxID_ANY, _(L("Rear")) + sep + "&4", _(L("Rear View")), [this](wxCommandEvent&) { select_view("rear"); }); | ||||
| 		wxMenuItem* item_left = append_menu_item(viewMenu, wxID_ANY, _(L("Left")) + sep + "&5", _(L("Left View")), [this](wxCommandEvent&) { select_view("left"); }); | ||||
| 		wxMenuItem* item_right = append_menu_item(viewMenu, wxID_ANY, _(L("Right")) + sep + "&6", _(L("Right View")), [this](wxCommandEvent&) { select_view("right"); }); | ||||
| 
 | ||||
|         Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_change_view()); }, item_iso->GetId()); | ||||
|         Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_change_view()); }, item_top->GetId()); | ||||
|  | @ -439,7 +455,7 @@ void MainFrame::init_menubar() | |||
|         append_menu_item(helpMenu, wxID_ANY, _(L("&About Slic3r")), _(L("Show about dialog")), | ||||
|             [this](wxCommandEvent&) { Slic3r::GUI::about(); }); | ||||
|         helpMenu->AppendSeparator(); | ||||
|         append_menu_item(helpMenu, wxID_ANY, _(L("&Keyboard Shortcuts")) + "\t\xA0?", _(L("Show the list of the keyboard shortcuts")), | ||||
|         append_menu_item(helpMenu, wxID_ANY, _(L("Keyboard Shortcuts")) + sep + "&?", _(L("Show the list of the keyboard shortcuts")), | ||||
|             [this](wxCommandEvent&) { wxGetApp().keyboard_shortcuts(); }); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -896,7 +896,7 @@ std::vector<PresetComboBox*>& Sidebar::combos_filament() | |||
| class PlaterDropTarget : public wxFileDropTarget | ||||
| { | ||||
| public: | ||||
|     PlaterDropTarget(Plater *plater) : plater(plater) {} | ||||
| 	PlaterDropTarget(Plater *plater) : plater(plater) { this->SetDefaultAction(wxDragCopy); } | ||||
| 
 | ||||
|     virtual bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames); | ||||
| 
 | ||||
|  | @ -1028,7 +1028,8 @@ struct Plater::priv | |||
|     unsigned int update_background_process(bool force_validation = false); | ||||
|     // Restart background processing thread based on a bitmask of UpdateBackgroundProcessReturnState.
 | ||||
|     bool restart_background_process(unsigned int state); | ||||
|     void update_restart_background_process(bool force_scene_update, bool force_preview_update); | ||||
| 	// returns bit mask of UpdateBackgroundProcessReturnState
 | ||||
| 	unsigned int update_restart_background_process(bool force_scene_update, bool force_preview_update); | ||||
|     void export_gcode(fs::path output_path, PrintHostJob upload_job); | ||||
|     void reload_from_disk(); | ||||
|     void fix_through_netfabb(const int obj_idx, const int vol_idx = -1); | ||||
|  | @ -1170,15 +1171,14 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) | |||
|     view3D_canvas->Bind(EVT_GLCANVAS_RIGHT_CLICK, &priv::on_right_click, this); | ||||
|     view3D_canvas->Bind(EVT_GLCANVAS_REMOVE_OBJECT, [q](SimpleEvent&) { q->remove_selected(); }); | ||||
|     view3D_canvas->Bind(EVT_GLCANVAS_ARRANGE, [this](SimpleEvent&) { arrange(); }); | ||||
|     view3D_canvas->Bind(EVT_GLCANVAS_SELECT_ALL, [this](SimpleEvent&) { this->q->select_all(); }); | ||||
|     view3D_canvas->Bind(EVT_GLCANVAS_QUESTION_MARK, [this](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); }); | ||||
|     view3D_canvas->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [this](Event<int> &evt)  | ||||
|         { if (evt.data == 1) this->q->increase_instances(); else if (this->can_decrease_instances()) this->q->decrease_instances(); }); | ||||
|     view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent&) { update(); }); | ||||
|     view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_MOVED, &priv::on_wipetower_moved, this); | ||||
| #if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION | ||||
|     view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_ROTATED, [this](SimpleEvent&) { update(); }); | ||||
|     view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_SCALED, [this](SimpleEvent&) { update(); }); | ||||
| #endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
 | ||||
|     view3D_canvas->Bind(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, [this](Event<bool> &evt) { this->sidebar->enable_buttons(evt.data); }); | ||||
|     view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_GEOMETRY, &priv::on_update_geometry, this); | ||||
|     view3D_canvas->Bind(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, &priv::on_3dcanvas_mouse_dragging_finished, this); | ||||
|  | @ -1577,7 +1577,7 @@ std::unique_ptr<CheckboxFileDialog> Plater::priv::get_export_file(GUI::FileType | |||
|     // Update printbility state of each of the ModelInstances.
 | ||||
|     this->update_print_volume_state(); | ||||
|     // Find the file name of the first printable object.
 | ||||
| 	fs::path output_file = this->model.propose_export_file_name(); | ||||
| 	fs::path output_file = this->model.propose_export_file_name_and_path(); | ||||
| 
 | ||||
|     switch (file_type) { | ||||
|         case FT_STL: output_file.replace_extension("stl"); break; | ||||
|  | @ -1737,6 +1737,8 @@ void Plater::priv::arrange() | |||
|     // Guard the arrange process
 | ||||
|     arranging.store(true); | ||||
| 
 | ||||
|     wxBusyCursor wait; | ||||
| 
 | ||||
|     // Disable the arrange button (to prevent reentrancies, we will call wxYied)
 | ||||
|     view3D->enable_toolbar_item("arrange", can_arrange()); | ||||
| 
 | ||||
|  | @ -2047,7 +2049,7 @@ void Plater::priv::export_gcode(fs::path output_path, PrintHostJob upload_job) | |||
|     this->restart_background_process(priv::UPDATE_BACKGROUND_PROCESS_FORCE_EXPORT); | ||||
| } | ||||
| 
 | ||||
| void Plater::priv::update_restart_background_process(bool force_update_scene, bool force_update_preview) | ||||
| unsigned int Plater::priv::update_restart_background_process(bool force_update_scene, bool force_update_preview) | ||||
| { | ||||
|     // bitmask of UpdateBackgroundProcessReturnState
 | ||||
|     unsigned int state = this->update_background_process(false); | ||||
|  | @ -2057,6 +2059,7 @@ void Plater::priv::update_restart_background_process(bool force_update_scene, bo | |||
|     if (force_update_preview) | ||||
|         this->preview->reload_print(); | ||||
|     this->restart_background_process(state); | ||||
| 	return state; | ||||
| } | ||||
| 
 | ||||
| void Plater::priv::update_fff_scene() | ||||
|  | @ -2110,31 +2113,10 @@ void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* = | |||
| { | ||||
|     if (obj_idx < 0) | ||||
|         return; | ||||
| 
 | ||||
|     const auto model_object = model.objects[obj_idx]; | ||||
|     Model model_fixed;// = new Model();
 | ||||
|     fix_model_by_win10_sdk_gui(*model_object, this->fff_print, model_fixed); | ||||
| 
 | ||||
|     auto new_obj_idxs = load_model_objects(model_fixed.objects); | ||||
|     if (new_obj_idxs.empty()) | ||||
|         return; | ||||
|      | ||||
|     for(auto new_obj_idx : new_obj_idxs) { | ||||
|         auto o = model.objects[new_obj_idx]; | ||||
|         o->clear_instances(); | ||||
|         for (auto instance: model_object->instances) | ||||
|             o->add_instance(*instance); | ||||
|         o->invalidate_bounding_box(); | ||||
|          | ||||
|         if (o->volumes.size() == model_object->volumes.size()) { | ||||
|             for (int i = 0; i < o->volumes.size(); i++) { | ||||
|                 o->volumes[i]->config.apply(model_object->volumes[i]->config); | ||||
|             } | ||||
|         } | ||||
|         // FIXME restore volumes and their configs, layer_height_ranges, layer_height_profile
 | ||||
|     } | ||||
|      | ||||
|     remove(obj_idx); | ||||
|     fix_model_by_win10_sdk_gui(*model.objects[obj_idx], vol_idx); | ||||
|     this->object_list_changed(); | ||||
|     this->update(); | ||||
|     this->schedule_background_process(); | ||||
| } | ||||
| 
 | ||||
| void Plater::priv::set_current_panel(wxPanel* panel) | ||||
|  | @ -2555,7 +2537,7 @@ void Plater::priv::init_view_toolbar() | |||
|     GLToolbarItem::Data item; | ||||
| 
 | ||||
|     item.name = "3D"; | ||||
|     item.tooltip = GUI::L_str("3D editor view [Ctrl+5]"); | ||||
|     item.tooltip = GUI::L_str("3D editor view") + " [" + GUI::shortkey_ctrl_prefix() + "5]"; | ||||
|     item.sprite_id = 0; | ||||
|     item.action_event = EVT_GLVIEWTOOLBAR_3D; | ||||
|     item.is_toggable = false; | ||||
|  | @ -2563,7 +2545,7 @@ void Plater::priv::init_view_toolbar() | |||
|         return; | ||||
| 
 | ||||
|     item.name = "Preview"; | ||||
|     item.tooltip = GUI::L_str("Preview [Ctrl+6]"); | ||||
|     item.tooltip = GUI::L_str("Preview") + " [" + GUI::shortkey_ctrl_prefix() + "6]"; | ||||
|     item.sprite_id = 1; | ||||
|     item.action_event = EVT_GLVIEWTOOLBAR_PREVIEW; | ||||
|     item.is_toggable = false; | ||||
|  | @ -2848,51 +2830,43 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_uppe | |||
|     p->load_model_objects(new_objects); | ||||
| } | ||||
| 
 | ||||
| void Plater::export_gcode(fs::path output_path) | ||||
| void Plater::export_gcode() | ||||
| { | ||||
|     if (p->model.objects.empty()) | ||||
|         return; | ||||
| 
 | ||||
|     // select output file
 | ||||
|     if (output_path.empty()) { | ||||
|         // XXX: take output path from CLI opts? Ancient Slic3r versions used to do that...
 | ||||
| 
 | ||||
|         // If possible, remove accents from accented latin characters.
 | ||||
|         // This function is useful for generating file names to be processed by legacy firmwares.
 | ||||
|         fs::path default_output_file; | ||||
|         try { | ||||
|             default_output_file = this->p->background_process.current_print()->output_filepath(output_path.string()); | ||||
|         } catch (const std::exception &ex) { | ||||
|             show_error(this, ex.what()); | ||||
|             return; | ||||
|         } | ||||
|         default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string())); | ||||
|         auto start_dir = wxGetApp().app_config->get_last_output_dir(default_output_file.parent_path().string()); | ||||
| 
 | ||||
|         wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _(L("Save G-code file as:")) : _(L("Save Zip file as:")), | ||||
|             start_dir, | ||||
|             from_path(default_output_file.filename()), | ||||
|             GUI::file_wildcards((printer_technology() == ptFFF) ? FT_GCODE : FT_PNGZIP, default_output_file.extension().string()), | ||||
|             wxFD_SAVE | wxFD_OVERWRITE_PROMPT | ||||
|         ); | ||||
| 
 | ||||
|         if (dlg.ShowModal() == wxID_OK) { | ||||
|             fs::path path = into_path(dlg.GetPath()); | ||||
|             wxGetApp().app_config->update_last_output_dir(path.parent_path().string()); | ||||
|             output_path = std::move(path); | ||||
|         } | ||||
|     } else { | ||||
|         try { | ||||
|             output_path = this->p->background_process.current_print()->output_filepath(output_path.string()); | ||||
|         } catch (const std::exception &ex) { | ||||
|             show_error(this, ex.what()); | ||||
|             return; | ||||
|         } | ||||
|     // If possible, remove accents from accented latin characters.
 | ||||
|     // This function is useful for generating file names to be processed by legacy firmwares.
 | ||||
|     fs::path default_output_file; | ||||
|     try { | ||||
| 		// Update the background processing, so that the placeholder parser will get the correct values for the ouput file template.
 | ||||
| 		// Also if there is something wrong with the current configuration, a pop-up dialog will be shown and the export will not be performed.
 | ||||
| 		unsigned int state = this->p->update_restart_background_process(false, false); | ||||
| 		if (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) | ||||
| 			return; | ||||
| 		default_output_file = this->p->background_process.current_print()->output_filepath(""); | ||||
|     } catch (const std::exception &ex) { | ||||
|         show_error(this, ex.what()); | ||||
|         return; | ||||
|     } | ||||
|     default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string())); | ||||
|     auto start_dir = wxGetApp().app_config->get_last_output_dir(default_output_file.parent_path().string()); | ||||
| 
 | ||||
|     if (! output_path.empty()) { | ||||
|     wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _(L("Save G-code file as:")) : _(L("Save Zip file as:")), | ||||
|         start_dir, | ||||
|         from_path(default_output_file.filename()), | ||||
|         GUI::file_wildcards((printer_technology() == ptFFF) ? FT_GCODE : FT_PNGZIP, default_output_file.extension().string()), | ||||
|         wxFD_SAVE | wxFD_OVERWRITE_PROMPT | ||||
|     ); | ||||
| 
 | ||||
|     fs::path output_path; | ||||
|     if (dlg.ShowModal() == wxID_OK) { | ||||
|         fs::path path = into_path(dlg.GetPath()); | ||||
|         wxGetApp().app_config->update_last_output_dir(path.parent_path().string()); | ||||
|         output_path = std::move(path); | ||||
|     } | ||||
|     if (! output_path.empty()) | ||||
|         p->export_gcode(std::move(output_path), PrintHostJob()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void Plater::export_stl(bool selection_only) | ||||
|  | @ -2993,7 +2967,12 @@ void Plater::send_gcode() | |||
|     // Obtain default output path
 | ||||
|     fs::path default_output_file; | ||||
|     try { | ||||
|         default_output_file = this->p->background_process.current_print()->output_filepath(""); | ||||
| 		// Update the background processing, so that the placeholder parser will get the correct values for the ouput file template.
 | ||||
| 		// Also if there is something wrong with the current configuration, a pop-up dialog will be shown and the export will not be performed.
 | ||||
| 		unsigned int state = this->p->update_restart_background_process(false, false); | ||||
| 		if (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) | ||||
| 			return; | ||||
| 		default_output_file = this->p->background_process.current_print()->output_filepath(""); | ||||
|     } catch (const std::exception &ex) { | ||||
|         show_error(this, ex.what()); | ||||
|         return; | ||||
|  |  | |||
|  | @ -140,8 +140,7 @@ public: | |||
| 
 | ||||
|     void cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_upper = true, bool keep_lower = true, bool rotate_lower = false); | ||||
| 
 | ||||
|     // Note: empty path means "use the default"
 | ||||
|     void export_gcode(boost::filesystem::path output_path = boost::filesystem::path()); | ||||
|     void export_gcode(); | ||||
|     void export_stl(bool selection_only = false); | ||||
|     void export_amf(); | ||||
|     void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path()); | ||||
|  |  | |||
|  | @ -336,7 +336,7 @@ const std::vector<std::string>& Preset::print_options() | |||
|         "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers",  | ||||
|         "support_material_interface_spacing", "support_material_interface_contact_loops", "support_material_contact_distance",  | ||||
|         "support_material_buildplate_only", "dont_support_bridges", "notes", "complete_objects", "extruder_clearance_radius",  | ||||
|         "extruder_clearance_height", "gcode_comments", "output_filename_format", "post_process", "perimeter_extruder",  | ||||
|         "extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "perimeter_extruder",  | ||||
|         "infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder",  | ||||
|         "ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width",  | ||||
|         "perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width",  | ||||
|  | @ -517,16 +517,6 @@ void PresetCollection::add_default_preset(const std::vector<std::string> &keys, | |||
|     ++ m_num_default_presets; | ||||
| } | ||||
| 
 | ||||
| bool is_file_plain(const std::string &path) | ||||
| { | ||||
| #ifdef _MSC_VER | ||||
|     DWORD attributes = GetFileAttributesW(boost::nowide::widen(path).c_str()); | ||||
|     return (attributes & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) == 0; | ||||
| #else | ||||
|     return true; | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| // Load all presets found in dir_path.
 | ||||
| // Throws an exception on error.
 | ||||
| void PresetCollection::load_presets(const std::string &dir_path, const std::string &subdir) | ||||
|  | @ -538,10 +528,7 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri | |||
| 	// (see the "Preset already present, not loading" message).
 | ||||
| 	std::deque<Preset> presets_loaded; | ||||
| 	for (auto &dir_entry : boost::filesystem::directory_iterator(dir)) | ||||
|         if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini") && | ||||
|             // Ignore system and hidden files, which may be created by the DropBox synchronisation process.
 | ||||
|             // https://github.com/prusa3d/Slic3r/issues/1298
 | ||||
|             is_file_plain(dir_entry.path().string())) { | ||||
|         if (Slic3r::is_ini_file(dir_entry)) { | ||||
|             std::string name = dir_entry.path().filename().string(); | ||||
|             // Remove the .ini suffix.
 | ||||
|             name.erase(name.size() - 4); | ||||
|  | @ -1163,12 +1150,24 @@ std::string PresetCollection::name() const | |||
|     case Preset::TYPE_PRINT:        return L("print"); | ||||
|     case Preset::TYPE_FILAMENT:     return L("filament"); | ||||
|     case Preset::TYPE_SLA_PRINT:    return L("SLA print"); | ||||
|     case Preset::TYPE_SLA_MATERIAL: return L("SLA material");     | ||||
|     case Preset::TYPE_SLA_MATERIAL: return L("SLA material"); | ||||
|     case Preset::TYPE_PRINTER:      return L("printer"); | ||||
|     default:                        return "invalid"; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| std::string PresetCollection::section_name() const | ||||
| { | ||||
|     switch (this->type()) { | ||||
|     case Preset::TYPE_PRINT:        return "print"; | ||||
|     case Preset::TYPE_FILAMENT:     return "filament"; | ||||
|     case Preset::TYPE_SLA_PRINT:    return "sla_print"; | ||||
|     case Preset::TYPE_SLA_MATERIAL: return "sla_material"; | ||||
|     case Preset::TYPE_PRINTER:      return "printer"; | ||||
|     default:                        return "invalid"; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| std::vector<std::string> PresetCollection::system_preset_names() const | ||||
| { | ||||
|     size_t num = 0; | ||||
|  |  | |||
|  | @ -230,7 +230,10 @@ public: | |||
|     void            reset(bool delete_files); | ||||
| 
 | ||||
|     Preset::Type    type() const { return m_type; } | ||||
|     // Name, to be used on the screen and in error messages. Not localized.
 | ||||
|     std::string     name() const; | ||||
|     // Name, to be used as a section name in config bundle, and as a folder name for presets.
 | ||||
|     std::string     section_name() const; | ||||
|     const std::deque<Preset>& operator()() const { return m_presets; } | ||||
| 
 | ||||
|     // Add default preset at the start of the collection, increment the m_default_preset counter.
 | ||||
|  |  | |||
|  | @ -243,7 +243,7 @@ std::string PresetBundle::load_system_presets() | |||
|     std::string errors_cummulative; | ||||
|     bool        first = true; | ||||
|     for (auto &dir_entry : boost::filesystem::directory_iterator(dir)) | ||||
|         if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini")) { | ||||
|         if (Slic3r::is_ini_file(dir_entry)) { | ||||
|             std::string name = dir_entry.path().filename().string(); | ||||
|             // Remove the .ini suffix.
 | ||||
|             name.erase(name.size() - 4); | ||||
|  | @ -1208,7 +1208,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla | |||
| #else | ||||
|                 // Store the print/filament/printer presets at the same location as the upstream Slic3r.
 | ||||
| #endif | ||||
|                 / presets->name() / file_name).make_preferred(); | ||||
|                 / presets->section_name() / file_name).make_preferred(); | ||||
|             // Load the preset into the list of presets, save it to disk.
 | ||||
|             Preset &loaded = presets->load_preset(file_path.string(), preset_name, std::move(config), false); | ||||
|             if (flags & LOAD_CFGBNDLE_SAVE) | ||||
|  | @ -1365,7 +1365,7 @@ void PresetBundle::export_configbundle(const std::string &path, bool export_syst | |||
|             if (preset.is_default || preset.is_external || (preset.is_system && ! export_system_settings)) | ||||
|                 // Only export the common presets, not external files or the default preset.
 | ||||
|                 continue; | ||||
|             c << std::endl << "[" << presets->name() << ":" << preset.name << "]" << std::endl; | ||||
|             c << std::endl << "[" << presets->section_name() << ":" << preset.name << "]" << std::endl; | ||||
|             for (const std::string &opt_key : preset.config.keys()) | ||||
|                 c << opt_key << " = " << preset.config.serialize(opt_key) << std::endl; | ||||
|         } | ||||
|  |  | |||
|  | @ -1097,6 +1097,7 @@ void TabPrint::build() | |||
| 
 | ||||
| 		optgroup = page->new_optgroup(_(L("Output file"))); | ||||
| 		optgroup->append_single_option_line("gcode_comments"); | ||||
| 		optgroup->append_single_option_line("gcode_label_objects"); | ||||
| 		option = optgroup->get_option("output_filename_format"); | ||||
| 		option.opt.full_width = true; | ||||
| 		optgroup->append_single_option_line(option); | ||||
|  |  | |||
|  | @ -216,7 +216,7 @@ void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path | |||
| 
 | ||||
| 	HRESULT hr = (*s_RoInitialize)(RO_INIT_MULTITHREADED); | ||||
| 	{ | ||||
| 		on_progress(L("Exporting the source model"), 20); | ||||
| 		on_progress(L("Exporting source model"), 20); | ||||
| 
 | ||||
| 		Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IRandomAccessStream>       fileStream; | ||||
| 		hr = winrt_open_file_stream(boost::nowide::widen(path_src), ABI::Windows::Storage::FileAccessMode::FileAccessMode_Read, fileStream.GetAddressOf(), throw_on_cancel); | ||||
|  | @ -239,7 +239,7 @@ void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path | |||
| 		unsigned num_meshes = 0; | ||||
| 		hr = meshes->get_Size(&num_meshes); | ||||
| 		 | ||||
| 		on_progress(L("Repairing the model by the Netfabb service"), 40); | ||||
| 		on_progress(L("Repairing model by the Netfabb service"), 40); | ||||
| 		 | ||||
| 		Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncAction>					  repairAsync; | ||||
| 		hr = model->RepairAsync(repairAsync.GetAddressOf()); | ||||
|  | @ -248,7 +248,7 @@ void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path | |||
| 			throw std::runtime_error(L("Mesh repair failed.")); | ||||
| 		repairAsync->GetResults(); | ||||
| 
 | ||||
| 		on_progress(L("Loading the repaired model"), 60); | ||||
| 		on_progress(L("Loading repaired model"), 60); | ||||
| 
 | ||||
| 		// Verify the number of meshes returned after the repair action.
 | ||||
| 		meshes.Reset(); | ||||
|  | @ -316,7 +316,7 @@ public: | |||
|    const char* what() const throw() { return "Model repair has been canceled"; } | ||||
| }; | ||||
| 
 | ||||
| void fix_model_by_win10_sdk_gui(const ModelObject &model_object, const Print &print, Model &result) | ||||
| void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx) | ||||
| { | ||||
| 	std::mutex 						mutex; | ||||
| 	std::condition_variable			condition; | ||||
|  | @ -329,6 +329,12 @@ void fix_model_by_win10_sdk_gui(const ModelObject &model_object, const Print &pr | |||
| 	std::atomic<bool>				canceled = false; | ||||
| 	std::atomic<bool>				finished = false; | ||||
| 
 | ||||
| 	std::vector<ModelVolume*> volumes; | ||||
| 	if (volume_idx == -1) | ||||
| 		volumes = model_object.volumes; | ||||
| 	else | ||||
| 		volumes.emplace_back(model_object.volumes[volume_idx]); | ||||
| 
 | ||||
| 	// Open a progress dialog.
 | ||||
| 	wxProgressDialog progress_dialog( | ||||
| 		_(L("Model fixing")), | ||||
|  | @ -336,43 +342,64 @@ void fix_model_by_win10_sdk_gui(const ModelObject &model_object, const Print &pr | |||
| 		100, nullptr, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT); | ||||
| 	// Executing the calculation in a background thread, so that the COM context could be created with its own threading model.
 | ||||
| 	// (It seems like wxWidgets initialize the COM contex as single threaded and we need a multi-threaded context).
 | ||||
| 	bool success  = false; | ||||
| 	auto on_progress = [&mutex, &condition, &progress](const char *msg, unsigned prcnt) { | ||||
| 	bool   success = false; | ||||
| 	size_t ivolume = 0; | ||||
| 	auto on_progress = [&mutex, &condition, &ivolume, &volumes, &progress](const char *msg, unsigned prcnt) { | ||||
|         std::lock_guard<std::mutex> lk(mutex); | ||||
| 		progress.message = msg; | ||||
| 		progress.percent = prcnt; | ||||
| 		progress.percent = (int)floor((float(prcnt) + float(ivolume) * 100.f) / float(volumes.size())); | ||||
| 		progress.updated = true; | ||||
| 	    condition.notify_all(); | ||||
| 	}; | ||||
| 	auto worker_thread = boost::thread([&model_object, &print, &result, on_progress, &success, &canceled, &finished]() { | ||||
| 	auto worker_thread = boost::thread([&model_object, &volumes, &ivolume, on_progress, &success, &canceled, &finished]() { | ||||
| 		try { | ||||
| 			on_progress(L("Exporting the source model"), 0); | ||||
| 			boost::filesystem::path path_src = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); | ||||
| 			path_src += ".3mf"; | ||||
| 			Model model; | ||||
|             DynamicPrintConfig config; | ||||
| 			model.add_object(model_object); | ||||
| 			if (! Slic3r::store_3mf(path_src.string().c_str(), &model, &config)) { | ||||
| 			std::vector<TriangleMesh> meshes_repaired; | ||||
| 			meshes_repaired.reserve(volumes.size()); | ||||
| 			for (; ivolume < volumes.size(); ++ ivolume) { | ||||
| 				on_progress(L("Exporting source model"), 0); | ||||
| 				boost::filesystem::path path_src = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); | ||||
| 				path_src += ".3mf"; | ||||
| 				Model model; | ||||
| 				ModelObject *model_object = model.add_object(); | ||||
| 				model_object->add_volume(*volumes[ivolume]); | ||||
| 				model_object->add_instance(); | ||||
| 				if (! Slic3r::store_3mf(path_src.string().c_str(), &model, nullptr)) { | ||||
| 					boost::filesystem::remove(path_src); | ||||
| 					throw std::runtime_error(L("Export of a temporary 3mf file failed")); | ||||
| 				} | ||||
| 				model.clear_objects(); | ||||
| 				model.clear_materials(); | ||||
| 				boost::filesystem::path path_dst = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); | ||||
| 				path_dst += ".3mf"; | ||||
| 				fix_model_by_win10_sdk(path_src.string().c_str(), path_dst.string(), on_progress,  | ||||
| 					[&canceled]() { if (canceled) throw RepairCanceledException(); }); | ||||
| 				boost::filesystem::remove(path_src); | ||||
| 				throw std::runtime_error(L("Export of a temporary 3mf file failed")); | ||||
| 	            // PresetBundle bundle;
 | ||||
| 				on_progress(L("Loading repaired model"), 80); | ||||
| 				DynamicPrintConfig config; | ||||
| 				bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), &config, &model); | ||||
| 			    boost::filesystem::remove(path_dst); | ||||
| 				if (! loaded) | ||||
| 	 				throw std::runtime_error(L("Import of the repaired 3mf file failed")); | ||||
| 	 			if (model.objects.size() == 0) | ||||
| 	 				throw std::runtime_error(L("Repaired 3MF file does not contain any object")); | ||||
| 	 			if (model.objects.size() > 1) | ||||
| 	 				throw std::runtime_error(L("Repaired 3MF file contains more than one object")); | ||||
| 	 			if (model.objects.front()->volumes.size() == 0) | ||||
| 	 				throw std::runtime_error(L("Repaired 3MF file does not contain any volume")); | ||||
| 				if (model.objects.front()->volumes.size() > 1) | ||||
| 	 				throw std::runtime_error(L("Repaired 3MF file contains more than one volume")); | ||||
| 	 			meshes_repaired.emplace_back(std::move(model.objects.front()->volumes.front()->mesh)); | ||||
| 			} | ||||
| 			model.clear_objects();  | ||||
| 			model.clear_materials(); | ||||
| 			boost::filesystem::path path_dst = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); | ||||
| 			path_dst += ".3mf"; | ||||
| 			fix_model_by_win10_sdk(path_src.string().c_str(), path_dst.string(), on_progress,  | ||||
| 				[&canceled]() { if (canceled) throw RepairCanceledException(); }); | ||||
| 			boost::filesystem::remove(path_src); | ||||
|             // PresetBundle bundle;
 | ||||
| 			on_progress(L("Loading the repaired model"), 80); | ||||
|             bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), &config, &result); | ||||
|             result.objects[0]->name = boost::filesystem::path(model_object.name).filename().stem().string() + "_fixed"; | ||||
| 		    boost::filesystem::remove(path_dst); | ||||
| 			if (! loaded) | ||||
|  				throw std::runtime_error(L("Import of the repaired 3mf file failed")); | ||||
| 			for (size_t i = 0; i < volumes.size(); ++ i) { | ||||
| 				volumes[i]->mesh = std::move(meshes_repaired[i]); | ||||
| 				volumes[i]->set_new_unique_id(); | ||||
| 			} | ||||
| 			model_object.invalidate_bounding_box(); | ||||
| 			-- ivolume; | ||||
| 			on_progress(L("Model repair finished"), 100); | ||||
| 			success  = true; | ||||
| 			finished = true; | ||||
| 			on_progress(L("Model repair finished"), 100); | ||||
| 		} catch (RepairCanceledException & /* ex */) { | ||||
| 			canceled = true; | ||||
| 			finished = true; | ||||
|  |  | |||
|  | @ -12,12 +12,12 @@ class Print; | |||
| #ifdef HAS_WIN10SDK | ||||
| 
 | ||||
| extern bool is_windows10(); | ||||
| extern void fix_model_by_win10_sdk_gui(const ModelObject &model_object, const Print &print, Model &result); | ||||
| extern void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx); | ||||
| 
 | ||||
| #else /* HAS_WIN10SDK */ | ||||
| 
 | ||||
| inline bool is_windows10() { return false; } | ||||
| inline void fix_model_by_win10_sdk_gui(const ModelObject &, const Print &, Model &) {} | ||||
| inline void fix_model_by_win10_sdk_gui(ModelObject &, int) {} | ||||
| 
 | ||||
| #endif /* HAS_WIN10SDK */ | ||||
| 
 | ||||
|  |  | |||
|  | @ -180,12 +180,11 @@ bool PresetUpdater::priv::get_file(const std::string &url, const fs::path &targe | |||
| // Remove leftover paritally downloaded files, if any.
 | ||||
| void PresetUpdater::priv::prune_tmps() const | ||||
| { | ||||
| 	for (fs::directory_iterator it(cache_path); it != fs::directory_iterator(); ++it) { | ||||
| 		if (it->path().extension() == TMP_EXTENSION) { | ||||
| 			BOOST_LOG_TRIVIAL(debug) << "Cache prune: " << it->path().string(); | ||||
| 			fs::remove(it->path()); | ||||
|     for (auto &dir_entry : boost::filesystem::directory_iterator(cache_path)) | ||||
| 		if (is_plain_file(dir_entry) && dir_entry.path().extension() == TMP_EXTENSION) { | ||||
| 			BOOST_LOG_TRIVIAL(debug) << "Cache prune: " << dir_entry.path().string(); | ||||
| 			fs::remove(dir_entry.path()); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Get Slic3rPE version available online, save in AppConfig.
 | ||||
|  | @ -299,9 +298,9 @@ void PresetUpdater::priv::check_install_indices() const | |||
| { | ||||
| 	BOOST_LOG_TRIVIAL(info) << "Checking if indices need to be installed from resources..."; | ||||
| 
 | ||||
| 	for (fs::directory_iterator it(rsrc_path); it != fs::directory_iterator(); ++it) { | ||||
| 		const auto &path = it->path(); | ||||
| 		if (path.extension() == ".idx") { | ||||
|     for (auto &dir_entry : boost::filesystem::directory_iterator(rsrc_path)) | ||||
| 		if (is_idx_file(dir_entry)) { | ||||
| 			const auto &path = dir_entry.path(); | ||||
| 			const auto path_in_cache = cache_path / path.filename(); | ||||
| 
 | ||||
| 			if (! fs::exists(path_in_cache)) { | ||||
|  | @ -318,7 +317,6 @@ void PresetUpdater::priv::check_install_indices() const | |||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Generates a list of bundle updates that are to be performed
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 YuSanka
						YuSanka