mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-30 20:21:12 -06:00 
			
		
		
		
	Merge branch 'master' into ys_menuicons
This commit is contained in:
		
						commit
						347a65bc4a
					
				
					 41 changed files with 2572 additions and 1562 deletions
				
			
		|  | @ -234,7 +234,11 @@ if(SLIC3R_STATIC) | |||
| endif() | ||||
| #set(Boost_DEBUG ON) | ||||
| # set(Boost_COMPILER "-vc120") | ||||
| find_package(Boost REQUIRED COMPONENTS system filesystem thread log locale regex) | ||||
| if(NOT WIN32) | ||||
|     # boost::process was introduced first in version 1.64.0 | ||||
|     set(MINIMUM_BOOST_VERSION "1.64.0") | ||||
| endif() | ||||
| find_package(Boost ${MINIMUM_BOOST_VERSION} REQUIRED COMPONENTS system filesystem thread log locale regex) | ||||
| if(Boost_FOUND) | ||||
|     include_directories(${Boost_INCLUDE_DIRS}) | ||||
|     if (APPLE) | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -201,12 +201,13 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T | |||
|     // Let the m_writer know the current extruder_id, but ignore the generated G-code.
 | ||||
| 	if (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id)) | ||||
|         gcodegen.writer().toolchange(new_extruder_id); | ||||
|     gcodegen.placeholder_parser().set("current_extruder", new_extruder_id); | ||||
| 
 | ||||
|     // Always append the filament start G-code even if the extruder did not switch,
 | ||||
|     // because the wipe tower resets the linear advance and we want it to be re-enabled.
 | ||||
|     const std::string &start_filament_gcode = gcodegen.config().start_filament_gcode.get_at(new_extruder_id); | ||||
|     if (! start_filament_gcode.empty()) { | ||||
|         // Process the start_filament_gcode for the active filament only.
 | ||||
|         gcodegen.placeholder_parser().set("current_extruder", new_extruder_id); | ||||
|         DynamicConfig config; | ||||
|         config.set_key_value("filament_extruder_id", new ConfigOptionInt(new_extruder_id)); | ||||
|         gcode += gcodegen.placeholder_parser_process("start_filament_gcode", start_filament_gcode, new_extruder_id, &config); | ||||
|  |  | |||
|  | @ -1451,6 +1451,50 @@ std::string ModelObject::get_export_filename() const | |||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| stl_stats ModelObject::get_object_stl_stats() const | ||||
| { | ||||
|     if (this->volumes.size() == 1) | ||||
|         return this->volumes[0]->mesh.stl.stats; | ||||
| 
 | ||||
|     stl_stats full_stats = this->volumes[0]->mesh.stl.stats; | ||||
| 
 | ||||
|     // fill full_stats from all objet's meshes
 | ||||
|     for (ModelVolume* volume : this->volumes) | ||||
|     { | ||||
|         if (volume->id() == this->volumes[0]->id()) | ||||
|             continue; | ||||
| 
 | ||||
|         const stl_stats& stats = volume->mesh.stl.stats; | ||||
| 
 | ||||
|         // initialize full_stats (for repaired errors)
 | ||||
|         full_stats.degenerate_facets    += stats.degenerate_facets; | ||||
|         full_stats.edges_fixed          += stats.edges_fixed; | ||||
|         full_stats.facets_removed       += stats.facets_removed; | ||||
|         full_stats.facets_added         += stats.facets_added; | ||||
|         full_stats.facets_reversed      += stats.facets_reversed; | ||||
|         full_stats.backwards_edges      += stats.backwards_edges; | ||||
| 
 | ||||
|         // another used satistics value
 | ||||
|         if (volume->is_model_part()) { | ||||
|             full_stats.volume           += stats.volume; | ||||
|             full_stats.number_of_parts  += stats.number_of_parts; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return full_stats; | ||||
| } | ||||
| 
 | ||||
| int ModelObject::get_mesh_errors_count(const int vol_idx /*= -1*/) const | ||||
| { | ||||
|     if (vol_idx >= 0) | ||||
|         return this->volumes[vol_idx]->get_mesh_errors_count(); | ||||
| 
 | ||||
|     const stl_stats& stats = get_object_stl_stats(); | ||||
| 
 | ||||
|     return  stats.degenerate_facets + stats.edges_fixed     + stats.facets_removed + | ||||
|             stats.facets_added      + stats.facets_reversed + stats.backwards_edges; | ||||
| } | ||||
| 
 | ||||
| void ModelVolume::set_material_id(t_model_material_id material_id) | ||||
| { | ||||
|     m_material_id = material_id; | ||||
|  | @ -1516,6 +1560,14 @@ void ModelVolume::calculate_convex_hull() | |||
|     m_convex_hull = mesh.convex_hull_3d(); | ||||
| } | ||||
| 
 | ||||
| int ModelVolume::get_mesh_errors_count() const | ||||
| { | ||||
|     const stl_stats& stats = this->mesh.stl.stats; | ||||
| 
 | ||||
|     return  stats.degenerate_facets + stats.edges_fixed     + stats.facets_removed + | ||||
|             stats.facets_added      + stats.facets_reversed + stats.backwards_edges; | ||||
| } | ||||
| 
 | ||||
| const TriangleMesh& ModelVolume::get_convex_hull() const | ||||
| { | ||||
|     return m_convex_hull; | ||||
|  |  | |||
|  | @ -277,6 +277,11 @@ public: | |||
| 
 | ||||
|     std::string get_export_filename() const; | ||||
| 
 | ||||
|     // Get full stl statistics for all object's meshes 
 | ||||
|     stl_stats   get_object_stl_stats() const; | ||||
|     // Get count of errors in the mesh( or all object's meshes, if volume index isn't defined) 
 | ||||
|     int         get_mesh_errors_count(const int vol_idx = -1) const; | ||||
| 
 | ||||
| protected: | ||||
|     friend class Print; | ||||
|     friend class SLAPrint; | ||||
|  | @ -370,6 +375,8 @@ public: | |||
| 
 | ||||
|     void                calculate_convex_hull(); | ||||
|     const TriangleMesh& get_convex_hull() const; | ||||
|     // Get count of errors in the mesh
 | ||||
|     int                 get_mesh_errors_count() const; | ||||
| 
 | ||||
|     // Helpers for loading / storing into AMF / 3MF files.
 | ||||
|     static ModelVolumeType type_from_string(const std::string &s); | ||||
|  |  | |||
|  | @ -1473,7 +1473,7 @@ void Print::process() | |||
|     BOOST_LOG_TRIVIAL(info) << "Staring the slicing process." << log_memory_info(); | ||||
|     for (PrintObject *obj : m_objects) | ||||
|         obj->make_perimeters(); | ||||
|     this->set_status(70, "Infilling layers"); | ||||
|     this->set_status(70, L("Infilling layers")); | ||||
|     for (PrintObject *obj : m_objects) | ||||
|         obj->infill(); | ||||
|     for (PrintObject *obj : m_objects) | ||||
|  | @ -1481,7 +1481,7 @@ void Print::process() | |||
|     if (this->set_started(psSkirt)) { | ||||
|         m_skirt.clear(); | ||||
|         if (this->has_skirt()) { | ||||
|             this->set_status(88, "Generating skirt"); | ||||
|             this->set_status(88, L("Generating skirt")); | ||||
|             this->_make_skirt(); | ||||
|         } | ||||
|         this->set_done(psSkirt); | ||||
|  | @ -1489,7 +1489,7 @@ void Print::process() | |||
| 	if (this->set_started(psBrim)) { | ||||
|         m_brim.clear(); | ||||
|         if (m_config.brim_width > 0) { | ||||
|             this->set_status(88, "Generating brim"); | ||||
|             this->set_status(88, L("Generating brim")); | ||||
|             this->_make_brim(); | ||||
|         } | ||||
|        this->set_done(psBrim); | ||||
|  | @ -1497,7 +1497,7 @@ void Print::process() | |||
|     if (this->set_started(psWipeTower)) { | ||||
|         m_wipe_tower_data.clear(); | ||||
|         if (this->has_wipe_tower()) { | ||||
|             //this->set_status(95, "Generating wipe tower");
 | ||||
|             //this->set_status(95, L("Generating wipe tower"));
 | ||||
|             this->_make_wipe_tower(); | ||||
|         } | ||||
|        this->set_done(psWipeTower); | ||||
|  | @ -1514,7 +1514,8 @@ std::string Print::export_gcode(const std::string &path_template, GCodePreviewDa | |||
|     // output everything to a G-code file
 | ||||
|     // The following call may die if the output_filename_format template substitution fails.
 | ||||
|     std::string path = this->output_filepath(path_template); | ||||
|     std::string message = "Exporting G-code"; | ||||
|     std::string message = L("Exporting G-code"); | ||||
|     // #ys_FIXME_localization
 | ||||
|     if (! path.empty() && preview_data == nullptr) { | ||||
|         // Only show the path if preview_data is not set -> running from command line.
 | ||||
|         message += " to "; | ||||
|  |  | |||
|  | @ -92,9 +92,9 @@ void PrintConfigDef::init_common_params() | |||
|     def->default_value = new ConfigOptionString(""); | ||||
|      | ||||
|     def = this->add("printhost_cafile", coString); | ||||
|     def->label = "HTTPS CA File"; | ||||
|     def->tooltip = "Custom CA certificate file can be specified for HTTPS OctoPrint connections, in crt/pem format. " | ||||
|                    "If left blank, the default OS CA certificate repository is used."; | ||||
|     def->label = L("HTTPS CA File"); | ||||
|     def->tooltip = L("Custom CA certificate file can be specified for HTTPS OctoPrint connections, in crt/pem format. " | ||||
|                    "If left blank, the default OS CA certificate repository is used."); | ||||
|     def->mode = comAdvanced; | ||||
|     def->default_value = new ConfigOptionString(""); | ||||
| } | ||||
|  | @ -145,6 +145,7 @@ void PrintConfigDef::init_fff_params() | |||
|     def->default_value = new ConfigOptionString(""); | ||||
| 
 | ||||
|     def = this->add("bottom_solid_layers", coInt); | ||||
|     //TRN To be shown in Print Settings "Bottom solid layers"
 | ||||
|     def->label = L("Bottom"); | ||||
|     def->category = L("Layers and Perimeters"); | ||||
|     def->tooltip = L("Number of solid layers to generate on bottom surfaces."); | ||||
|  | @ -913,10 +914,10 @@ void PrintConfigDef::init_fff_params() | |||
|     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," | ||||
|     def->label = L("Label objects"); | ||||
|     def->tooltip = L("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."; | ||||
|                    "Single Extruder Multi Material setup and Wipe into Object / Wipe into Infill."); | ||||
|     def->mode = comAdvanced; | ||||
|     def->default_value = new ConfigOptionBool(0); | ||||
| 
 | ||||
|  | @ -2038,6 +2039,7 @@ void PrintConfigDef::init_fff_params() | |||
|     def->default_value = new ConfigOptionFloatOrPercent(15, false); | ||||
| 
 | ||||
|     def = this->add("top_solid_layers", coInt); | ||||
|     //TRN To be shown in Print Settings "Top solid layers"
 | ||||
|     def->label = L("Top"); | ||||
|     def->category = L("Layers and Perimeters"); | ||||
|     def->tooltip = L("Number of solid layers to generate on top surfaces."); | ||||
|  | @ -2141,7 +2143,7 @@ void PrintConfigDef::init_fff_params() | |||
|     def = this->add("wipe_tower_rotation_angle", coFloat); | ||||
|     def->label = L("Wipe tower rotation angle"); | ||||
|     def->tooltip = L("Wipe tower rotation angle with respect to x-axis "); | ||||
|     def->sidetext = L("degrees"); | ||||
|     def->sidetext = L("°"); | ||||
|     def->mode = comAdvanced; | ||||
|     def->default_value = new ConfigOptionFloat(0.); | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ | |||
| #include "BoundingBox.hpp" | ||||
| #include "ClipperUtils.hpp" | ||||
| #include "Geometry.hpp" | ||||
| #include "I18N.hpp" | ||||
| #include "SupportMaterial.hpp" | ||||
| #include "Surface.hpp" | ||||
| #include "Slicing.hpp" | ||||
|  | @ -17,6 +18,10 @@ | |||
| 
 | ||||
| #include <Shiny/Shiny.h> | ||||
| 
 | ||||
| //! macro used to mark string used at localization, 
 | ||||
| //! return same string
 | ||||
| #define L(s) Slic3r::I18N::translate(s) | ||||
| 
 | ||||
| #ifdef SLIC3R_DEBUG_SLICE_PROCESSING | ||||
| #define SLIC3R_DEBUG | ||||
| #endif | ||||
|  | @ -102,7 +107,7 @@ void PrintObject::slice() | |||
| { | ||||
|     if (! this->set_started(posSlice)) | ||||
|         return; | ||||
|     m_print->set_status(10, "Processing triangulated mesh"); | ||||
|     m_print->set_status(10, L("Processing triangulated mesh")); | ||||
|     std::vector<coordf_t> layer_height_profile; | ||||
|     this->update_layer_height_profile(*this->model_object(), m_slicing_params, layer_height_profile); | ||||
|     m_print->throw_if_canceled(); | ||||
|  | @ -133,7 +138,7 @@ void PrintObject::make_perimeters() | |||
|     if (! this->set_started(posPerimeters)) | ||||
|         return; | ||||
| 
 | ||||
|     m_print->set_status(20, "Generating perimeters"); | ||||
|     m_print->set_status(20, L("Generating perimeters")); | ||||
|     BOOST_LOG_TRIVIAL(info) << "Generating perimeters..." << log_memory_info(); | ||||
|      | ||||
|     // merge slices if they were split into types
 | ||||
|  | @ -243,7 +248,7 @@ void PrintObject::prepare_infill() | |||
|     if (! this->set_started(posPrepareInfill)) | ||||
|         return; | ||||
| 
 | ||||
|     m_print->set_status(30, "Preparing infill"); | ||||
|     m_print->set_status(30, L("Preparing infill")); | ||||
| 
 | ||||
|     // This will assign a type (top/bottom/internal) to $layerm->slices.
 | ||||
|     // Then the classifcation of $layerm->slices is transfered onto 
 | ||||
|  | @ -383,7 +388,7 @@ void PrintObject::generate_support_material() | |||
|     if (this->set_started(posSupportMaterial)) { | ||||
|         this->clear_support_layers(); | ||||
|         if ((m_config.support_material || m_config.raft_layers > 0) && m_layers.size() > 1) { | ||||
|             m_print->set_status(85, "Generating support material");     | ||||
|             m_print->set_status(85, L("Generating support material"));     | ||||
|             this->_generate_support_material(); | ||||
|             m_print->throw_if_canceled(); | ||||
|         } else { | ||||
|  |  | |||
|  | @ -176,6 +176,8 @@ private: | |||
|         { | ||||
|             return level<T>(r1) < level<T>(r2); | ||||
|         }); | ||||
|          | ||||
|         if(it == cont.end()) return it; | ||||
| 
 | ||||
|         T diff = std::abs(level<T>(*it) - lvl); | ||||
| 
 | ||||
|  |  | |||
|  | @ -11,6 +11,8 @@ | |||
| #define ENABLE_SELECTION_DEBUG_OUTPUT 0 | ||||
| // 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 | ||||
| // Shows an imgui dialog with render related data
 | ||||
| #define ENABLE_RENDER_STATISTICS 0 | ||||
| 
 | ||||
| 
 | ||||
| //====================
 | ||||
|  |  | |||
|  | @ -1847,116 +1847,101 @@ TriangleMesh make_cube(double x, double y, double z) { | |||
| // Generate the mesh for a cylinder and return it, using 
 | ||||
| // the generated angle to calculate the top mesh triangles.
 | ||||
| // Default is 360 sides, angle fa is in radians.
 | ||||
| TriangleMesh make_cylinder(double r, double h, double fa) { | ||||
|     Pointf3s vertices; | ||||
|     std::vector<Vec3crd> facets; | ||||
| TriangleMesh make_cylinder(double r, double h, double fa) | ||||
| { | ||||
| 	size_t n_steps    = (size_t)ceil(2. * PI / fa); | ||||
| 	double angle_step = 2. * PI / n_steps; | ||||
| 
 | ||||
| 	Pointf3s				vertices; | ||||
| 	std::vector<Vec3crd>	facets; | ||||
| 	vertices.reserve(2 * n_steps + 2); | ||||
| 	facets.reserve(4 * n_steps); | ||||
| 
 | ||||
|     // 2 special vertices, top and bottom center, rest are relative to this
 | ||||
|     vertices.emplace_back(Vec3d(0.0, 0.0, 0.0)); | ||||
|     vertices.emplace_back(Vec3d(0.0, 0.0, h)); | ||||
| 
 | ||||
|     // adjust via rounding to get an even multiple for any provided angle.
 | ||||
|     double angle = (2*PI / floor(2*PI / fa)); | ||||
| 
 | ||||
|     // for each line along the polygon approximating the top/bottom of the
 | ||||
|     // circle, generate four points and four facets (2 for the wall, 2 for the
 | ||||
|     // top and bottom.
 | ||||
|     // Special case: Last line shares 2 vertices with the first line.
 | ||||
|     unsigned id = vertices.size() - 1; | ||||
|     vertices.emplace_back(Vec3d(sin(0) * r , cos(0) * r, 0)); | ||||
|     vertices.emplace_back(Vec3d(sin(0) * r , cos(0) * r, h)); | ||||
|     for (double i = 0; i < 2*PI; i+=angle) { | ||||
|         Vec2d p = Eigen::Rotation2Dd(i) * Eigen::Vector2d(0, r); | ||||
| 	Vec2d p = Eigen::Rotation2Dd(0.) * Eigen::Vector2d(0, r); | ||||
| 	vertices.emplace_back(Vec3d(p(0), p(1), 0.)); | ||||
| 	vertices.emplace_back(Vec3d(p(0), p(1), h)); | ||||
| 	for (size_t i = 1; i < n_steps; ++i) { | ||||
|         p = Eigen::Rotation2Dd(angle_step * i) * Eigen::Vector2d(0, r); | ||||
|         vertices.emplace_back(Vec3d(p(0), p(1), 0.)); | ||||
|         vertices.emplace_back(Vec3d(p(0), p(1), h)); | ||||
|         id = vertices.size() - 1; | ||||
|         int id = (int)vertices.size() - 1; | ||||
|         facets.emplace_back(Vec3crd( 0, id - 1, id - 3)); // top
 | ||||
|         facets.emplace_back(Vec3crd(id,      1, id - 2)); // bottom
 | ||||
|         facets.emplace_back(Vec3crd(id, id - 2, id - 3)); // upper-right of side
 | ||||
| 		facets.emplace_back(Vec3crd(id, id - 2, id - 3)); // upper-right of side
 | ||||
|         facets.emplace_back(Vec3crd(id, id - 3, id - 1)); // bottom-left of side
 | ||||
|     } | ||||
|     // Connect the last set of vertices with the first.
 | ||||
|     facets.emplace_back(Vec3crd( 2, 0, id - 1)); | ||||
|     facets.emplace_back(Vec3crd( 1, 3,     id)); | ||||
|     facets.emplace_back(Vec3crd(id, 3,      2)); | ||||
|     facets.emplace_back(Vec3crd(id, 2, id - 1)); | ||||
| 	int id = (int)vertices.size() - 1; | ||||
|     facets.emplace_back(Vec3crd( 0, 2, id - 1)); | ||||
|     facets.emplace_back(Vec3crd( 3, 1,     id)); | ||||
| 	facets.emplace_back(Vec3crd(id, 2,      3)); | ||||
|     facets.emplace_back(Vec3crd(id, id - 1, 2)); | ||||
|      | ||||
|     TriangleMesh mesh(vertices, facets); | ||||
|     return mesh; | ||||
| 	return TriangleMesh(std::move(vertices), std::move(facets)); | ||||
| } | ||||
| 
 | ||||
| // Generates mesh for a sphere centered about the origin, using the generated angle
 | ||||
| // to determine the granularity. 
 | ||||
| // Default angle is 1 degree.
 | ||||
| TriangleMesh make_sphere(double rho, double fa) { | ||||
|     Pointf3s vertices; | ||||
|     std::vector<Vec3crd> facets; | ||||
| //FIXME better to discretize an Icosahedron recursively http://www.songho.ca/opengl/gl_sphere.html
 | ||||
| TriangleMesh make_sphere(double radius, double fa) | ||||
| { | ||||
| 	int   sectorCount = ceil(2. * M_PI / fa); | ||||
| 	int   stackCount  = ceil(M_PI / fa); | ||||
| 	float sectorStep  = 2. * M_PI / sectorCount; | ||||
| 	float stackStep   = M_PI / stackCount; | ||||
| 
 | ||||
|     // Algorithm: 
 | ||||
|     // Add points one-by-one to the sphere grid and form facets using relative coordinates.
 | ||||
|     // Sphere is composed effectively of a mesh of stacked circles.
 | ||||
| 	Pointf3s vertices; | ||||
| 	vertices.reserve((stackCount - 1) * sectorCount + 2); | ||||
| 	for (int i = 0; i <= stackCount; ++ i) { | ||||
| 		// from pi/2 to -pi/2
 | ||||
| 		double stackAngle = 0.5 * M_PI - stackStep * i; | ||||
| 		double xy = radius * cos(stackAngle); | ||||
| 		double z  = radius * sin(stackAngle); | ||||
| 		if (i == 0 || i == stackCount) | ||||
| 			vertices.emplace_back(Vec3d(xy, 0., z)); | ||||
| 		else | ||||
| 			for (int j = 0; j < sectorCount; ++ j) { | ||||
| 				// from 0 to 2pi
 | ||||
| 				double sectorAngle = sectorStep * j; | ||||
| 				vertices.emplace_back(Vec3d(xy * cos(sectorAngle), xy * sin(sectorAngle), z)); | ||||
| 			} | ||||
| 	} | ||||
| 
 | ||||
|     // adjust via rounding to get an even multiple for any provided angle.
 | ||||
|     double angle = (2*PI / floor(2*PI / fa)); | ||||
| 
 | ||||
|     // Ring to be scaled to generate the steps of the sphere
 | ||||
|     std::vector<double> ring; | ||||
|     for (double i = 0; i < 2*PI; i+=angle) { | ||||
|         ring.emplace_back(i); | ||||
|     } | ||||
|     const size_t steps = ring.size();  | ||||
|     const double increment = (double)(1.0 / (double)steps); | ||||
| 
 | ||||
|     // special case: first ring connects to 0,0,0
 | ||||
|     // insert and form facets.
 | ||||
|     vertices.emplace_back(Vec3d(0.0, 0.0, -rho)); | ||||
|     size_t id = vertices.size(); | ||||
|     for (size_t i = 0; i < ring.size(); i++) { | ||||
|         // Fixed scaling 
 | ||||
|         const double z = -rho + increment*rho*2.0; | ||||
|         // radius of the circle for this step.
 | ||||
|         const double r = sqrt(abs(rho*rho - z*z)); | ||||
|         Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r); | ||||
|         vertices.emplace_back(Vec3d(b(0), b(1), z)); | ||||
|         facets.emplace_back((i == 0) ? Vec3crd(1, 0, ring.size()) : Vec3crd(id, 0, id - 1)); | ||||
|         ++ id; | ||||
|     } | ||||
| 
 | ||||
|     // General case: insert and form facets for each step, joining it to the ring below it.
 | ||||
|     for (size_t s = 2; s < steps - 1; s++) { | ||||
|         const double z = -rho + increment*(double)s*2.0*rho; | ||||
|         const double r = sqrt(abs(rho*rho - z*z)); | ||||
| 
 | ||||
|         for (size_t i = 0; i < ring.size(); i++) { | ||||
|             Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r); | ||||
|             vertices.emplace_back(Vec3d(b(0), b(1), z)); | ||||
|             if (i == 0) { | ||||
|                 // wrap around
 | ||||
|                 facets.emplace_back(Vec3crd(id + ring.size() - 1 , id, id - 1));  | ||||
|                 facets.emplace_back(Vec3crd(id, id - ring.size(),  id - 1));  | ||||
|             } else { | ||||
|                 facets.emplace_back(Vec3crd(id , id - ring.size(), (id - 1) - ring.size()));  | ||||
|                 facets.emplace_back(Vec3crd(id, id - 1 - ring.size() ,  id - 1));  | ||||
|             } | ||||
|             id++; | ||||
|         }  | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     // special case: last ring connects to 0,0,rho*2.0
 | ||||
|     // only form facets.
 | ||||
|     vertices.emplace_back(Vec3d(0.0, 0.0, rho)); | ||||
|     for (size_t i = 0; i < ring.size(); i++) { | ||||
|         if (i == 0) { | ||||
|             // third vertex is on the other side of the ring.
 | ||||
|             facets.emplace_back(Vec3crd(id, id - ring.size(),  id - 1)); | ||||
|         } else { | ||||
|             facets.emplace_back(Vec3crd(id, id - ring.size() + i,  id - ring.size() + (i - 1))); | ||||
|         } | ||||
|     } | ||||
|     id++; | ||||
|     TriangleMesh mesh(vertices, facets); | ||||
|     return mesh; | ||||
| 	std::vector<Vec3crd> facets; | ||||
| 	facets.reserve(2 * (stackCount - 1) * sectorCount); | ||||
| 	for (int i = 0; i < stackCount; ++ i) { | ||||
| 		// Beginning of current stack.
 | ||||
| 		int k1 = (i == 0) ? 0 : (1 + (i - 1) * sectorCount); | ||||
| 		int k1_first = k1; | ||||
| 		// Beginning of next stack.
 | ||||
| 		int k2 = (i == 0) ? 1 : (k1 + sectorCount); | ||||
| 		int k2_first = k2; | ||||
| 		for (int j = 0; j < sectorCount; ++ j) { | ||||
| 			// 2 triangles per sector excluding first and last stacks
 | ||||
| 			int k1_next = k1; | ||||
| 			int k2_next = k2; | ||||
| 			if (i != 0) { | ||||
| 				k1_next = (j + 1 == sectorCount) ? k1_first : (k1 + 1); | ||||
| 				facets.emplace_back(Vec3crd(k1, k2, k1_next)); | ||||
| 			} | ||||
| 			if (i + 1 != stackCount) { | ||||
| 				k2_next = (j + 1 == sectorCount) ? k2_first : (k2 + 1); | ||||
| 				facets.emplace_back(Vec3crd(k1_next, k2, k2_next)); | ||||
| 			} | ||||
| 			k1 = k1_next; | ||||
| 			k2 = k2_next; | ||||
| 		} | ||||
| 	} | ||||
| 	return TriangleMesh(std::move(vertices), std::move(facets)); | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -628,7 +628,7 @@ std::string CLI::output_filepath(const Model &model, IO::ExportFormat format) co | |||
| { | ||||
|     std::string ext; | ||||
|     switch (format) { | ||||
|         case IO::AMF: ext = ".amf"; break; | ||||
|         case IO::AMF: ext = ".zip.amf"; break; | ||||
|         case IO::OBJ: ext = ".obj"; break; | ||||
|         case IO::STL: ext = ".stl"; break; | ||||
| 		case IO::TMF: ext = ".3mf"; break; | ||||
|  |  | |||
|  | @ -47,6 +47,8 @@ set(SLIC3R_GUI_SOURCES | |||
|     GUI/Gizmos/GLGizmoFlatten.hpp | ||||
|     GUI/Gizmos/GLGizmoCut.cpp | ||||
|     GUI/Gizmos/GLGizmoCut.hpp | ||||
|     GUI/GLSelectionRectangle.cpp | ||||
|     GUI/GLSelectionRectangle.hpp | ||||
|     GUI/GLTexture.hpp | ||||
|     GUI/GLTexture.cpp | ||||
|     GUI/GLToolbar.hpp | ||||
|  |  | |||
|  | @ -223,7 +223,8 @@ void GLIndexedVertexArray::render( | |||
| } | ||||
| 
 | ||||
| const float GLVolume::SELECTED_COLOR[4] = { 0.0f, 1.0f, 0.0f, 1.0f }; | ||||
| const float GLVolume::HOVER_COLOR[4] = { 0.4f, 0.9f, 0.1f, 1.0f }; | ||||
| const float GLVolume::HOVER_SELECT_COLOR[4] = { 0.4f, 0.9f, 0.1f, 1.0f }; | ||||
| const float GLVolume::HOVER_DESELECT_COLOR[4] = { 1.0f, 0.75f, 0.75f, 1.0f }; | ||||
| const float GLVolume::OUTSIDE_COLOR[4] = { 0.0f, 0.38f, 0.8f, 1.0f }; | ||||
| const float GLVolume::SELECTED_OUTSIDE_COLOR[4] = { 0.19f, 0.58f, 1.0f, 1.0f }; | ||||
| const float GLVolume::DISABLED_COLOR[4] = { 0.25f, 0.25f, 0.25f, 1.0f }; | ||||
|  | @ -251,7 +252,7 @@ GLVolume::GLVolume(float r, float g, float b, float a) | |||
|     , zoom_to_volumes(true) | ||||
|     , shader_outside_printer_detection_enabled(false) | ||||
|     , is_outside(false) | ||||
|     , hover(false) | ||||
|     , hover(HS_None) | ||||
|     , is_modifier(false) | ||||
|     , is_wipe_tower(false) | ||||
|     , is_extrusion_path(false) | ||||
|  | @ -291,10 +292,12 @@ void GLVolume::set_render_color() | |||
|     if (force_native_color) | ||||
|         set_render_color(color, 4); | ||||
|     else { | ||||
|         if (selected) | ||||
|         if (hover == HS_Select) | ||||
|             set_render_color(HOVER_SELECT_COLOR, 4); | ||||
|         else if (hover == HS_Deselect) | ||||
|             set_render_color(HOVER_DESELECT_COLOR, 4); | ||||
|         else if (selected) | ||||
|             set_render_color(is_outside ? SELECTED_OUTSIDE_COLOR : SELECTED_COLOR, 4); | ||||
|         else if (hover) | ||||
|             set_render_color(HOVER_COLOR, 4); | ||||
|         else if (disabled) | ||||
|             set_render_color(DISABLED_COLOR, 4); | ||||
|         else if (is_outside && shader_outside_printer_detection_enabled) | ||||
|  |  | |||
|  | @ -225,7 +225,8 @@ private: | |||
| class GLVolume { | ||||
| public: | ||||
|     static const float SELECTED_COLOR[4]; | ||||
|     static const float HOVER_COLOR[4]; | ||||
|     static const float HOVER_SELECT_COLOR[4]; | ||||
|     static const float HOVER_DESELECT_COLOR[4]; | ||||
|     static const float OUTSIDE_COLOR[4]; | ||||
|     static const float SELECTED_OUTSIDE_COLOR[4]; | ||||
|     static const float DISABLED_COLOR[4]; | ||||
|  | @ -233,6 +234,13 @@ public: | |||
|     static const float SLA_SUPPORT_COLOR[4]; | ||||
|     static const float SLA_PAD_COLOR[4]; | ||||
| 
 | ||||
|     enum EHoverState : unsigned char | ||||
|     { | ||||
|         HS_None, | ||||
|         HS_Select, | ||||
|         HS_Deselect | ||||
|     }; | ||||
| 
 | ||||
|     GLVolume(float r = 1.f, float g = 1.f, float b = 1.f, float a = 1.f); | ||||
|     GLVolume(const float *rgba) : GLVolume(rgba[0], rgba[1], rgba[2], rgba[3]) {} | ||||
|     ~GLVolume(); | ||||
|  | @ -296,8 +304,8 @@ public: | |||
|     bool                shader_outside_printer_detection_enabled; | ||||
|     // Wheter or not this volume is outside print volume.
 | ||||
|     bool                is_outside; | ||||
|     // Boolean: Is mouse over this object?
 | ||||
|     bool                hover; | ||||
|     // Is mouse or rectangle selection over this object to select/deselect it ?
 | ||||
|     EHoverState         hover; | ||||
|     // Wheter or not this volume has been generated from a modifier
 | ||||
|     bool                is_modifier; | ||||
|     // Wheter or not this volume has been generated from the wipe tower
 | ||||
|  |  | |||
|  | @ -28,6 +28,7 @@ | |||
| #include <boost/filesystem.hpp> | ||||
| #include <boost/log/trivial.hpp> | ||||
| #include <boost/nowide/cstdio.hpp> | ||||
| #include "I18N.hpp" | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
|  | @ -81,13 +82,14 @@ void BackgroundSlicingProcess::process_fff() | |||
| 	    	std::string export_path = m_fff_print->print_statistics().finalize_output_path(m_export_path); | ||||
| 		    if (copy_file(m_temp_output_path, export_path) != 0) | ||||
| 	    		throw std::runtime_error("Copying of the temporary G-code to the output G-code failed"); | ||||
| 	    	m_print->set_status(95, "Running post-processing scripts"); | ||||
| 	    	m_print->set_status(95, L("Running post-processing scripts")); | ||||
| 	    	run_post_process_scripts(export_path, m_fff_print->config()); | ||||
| 	    	m_print->set_status(100, "G-code file exported to " + export_path); | ||||
|             // #ys_FIXME_localization  
 | ||||
| 	    	m_print->set_status(100, L("G-code file exported to ") + export_path); | ||||
| 	    } else if (! m_upload_job.empty()) { | ||||
| 			prepare_upload(); | ||||
| 	    } else { | ||||
| 	    	m_print->set_status(100, "Slicing complete"); | ||||
| 	    	m_print->set_status(100, L("Slicing complete")); | ||||
| 	    } | ||||
| 		this->set_step_done(bspsGCodeFinalize); | ||||
| 	} | ||||
|  | @ -101,11 +103,12 @@ void BackgroundSlicingProcess::process_sla() | |||
|         if (! m_export_path.empty()) { | ||||
|         	const std::string export_path = m_sla_print->print_statistics().finalize_output_path(m_export_path); | ||||
|             m_sla_print->export_raster(export_path); | ||||
|             m_print->set_status(100, "Masked SLA file exported to " + export_path); | ||||
|             // #ys_FIXME_localization  
 | ||||
|             m_print->set_status(100, L("Masked SLA file exported to ") + export_path); | ||||
|         } else if (! m_upload_job.empty()) { | ||||
|             prepare_upload(); | ||||
|         } else { | ||||
|             m_print->set_status(100, "Slicing complete"); | ||||
|             m_print->set_status(100, L("Slicing complete")); | ||||
|         } | ||||
|         this->set_step_done(bspsGCodeFinalize); | ||||
|     } | ||||
|  | @ -394,7 +397,7 @@ void BackgroundSlicingProcess::prepare_upload() | |||
| 		/ boost::filesystem::unique_path("." SLIC3R_APP_KEY ".upload.%%%%-%%%%-%%%%-%%%%"); | ||||
| 
 | ||||
| 	if (m_print == m_fff_print) { | ||||
| 		m_print->set_status(95, "Running post-processing scripts"); | ||||
| 		m_print->set_status(95, L("Running post-processing scripts")); | ||||
| 		if (copy_file(m_temp_output_path, source_path.string()) != 0) { | ||||
| 			throw std::runtime_error("Copying of the temporary G-code to the output G-code failed"); | ||||
| 		} | ||||
|  | @ -405,7 +408,8 @@ void BackgroundSlicingProcess::prepare_upload() | |||
|         m_sla_print->export_raster(source_path.string(), m_upload_job.upload_data.upload_path.string()); | ||||
| 	} | ||||
| 
 | ||||
| 	m_print->set_status(100, (boost::format("Scheduling upload to `%1%`. See Window -> Print Host Upload Queue") % m_upload_job.printhost->get_host()).str()); | ||||
|     // #ys_FIXME_localization  
 | ||||
| 	m_print->set_status(100, (boost::format(L("Scheduling upload to `%1%`. See Window -> Print Host Upload Queue")) % m_upload_job.printhost->get_host()).str()); | ||||
| 
 | ||||
| 	m_upload_job.upload_data.source_path = std::move(source_path); | ||||
| 
 | ||||
|  |  | |||
|  | @ -52,6 +52,9 @@ | |||
| #include <float.h> | ||||
| #include <algorithm> | ||||
| #include <cmath> | ||||
| #if ENABLE_RENDER_STATISTICS | ||||
| #include <chrono> | ||||
| #endif // ENABLE_RENDER_STATISTICS
 | ||||
| 
 | ||||
| static const float TRACKBALLSIZE = 0.8f; | ||||
| static const float GROUND_Z = -0.02f; | ||||
|  | @ -258,7 +261,7 @@ void GLCanvas3D::LayersEditing::select_object(const Model &model, int object_id) | |||
|     // Maximum height of an object changes when the object gets rotated or scaled.
 | ||||
|     // Changing maximum height of an object will invalidate the layer heigth editing profile.
 | ||||
|     // m_model_object->raw_bounding_box() is cached, therefore it is cheap even if this method is called frequently.
 | ||||
|     float new_max_z = (m_model_object == nullptr) ? 0.f : m_model_object->raw_bounding_box().size().z(); | ||||
| 	float new_max_z = (model_object_new == nullptr) ? 0.f : model_object_new->raw_bounding_box().size().z(); | ||||
| 	if (m_model_object != model_object_new || this->last_object_id != object_id || m_object_max_z != new_max_z || | ||||
|         (model_object_new != nullptr && m_model_object->id() != model_object_new->id())) { | ||||
|         m_layer_height_profile.clear(); | ||||
|  | @ -672,6 +675,7 @@ GLCanvas3D::Mouse::Mouse() | |||
|     : dragging(false) | ||||
|     , position(DBL_MAX, DBL_MAX) | ||||
|     , scene_position(DBL_MAX, DBL_MAX, DBL_MAX) | ||||
|     , ignore_left_up(false) | ||||
| { | ||||
| } | ||||
| 
 | ||||
|  | @ -791,7 +795,7 @@ bool GLCanvas3D::WarningTexture::_generate(const std::string& msg_utf8, const GL | |||
|     if (msg_utf8.empty()) | ||||
|         return false; | ||||
| 
 | ||||
|     wxString msg = GUI::from_u8(msg_utf8); | ||||
|     wxString msg = _(msg_utf8);//GUI::from_u8(msg_utf8);
 | ||||
| 
 | ||||
|     wxMemoryDC memDC; | ||||
| 
 | ||||
|  | @ -1228,7 +1232,6 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar | |||
|     , m_initialized(false) | ||||
|     , m_use_VBOs(false) | ||||
|     , m_apply_zoom_to_volumes_filter(false) | ||||
|     , m_hover_volume_id(-1) | ||||
|     , m_legend_texture_enabled(false) | ||||
|     , m_picking_enabled(false) | ||||
|     , m_moving_enabled(false) | ||||
|  | @ -1237,6 +1240,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar | |||
|     , m_regenerate_volumes(true) | ||||
|     , m_moving(false) | ||||
|     , m_tab_down(false) | ||||
|     , m_cursor_type(Standard) | ||||
|     , m_color_by("volume") | ||||
|     , m_reload_delayed(false) | ||||
|     , m_render_sla_auxiliaries(true) | ||||
|  | @ -1588,6 +1592,10 @@ void GLCanvas3D::render() | |||
|     if (!_set_current() || !_3DScene::init(m_canvas)) | ||||
|         return; | ||||
| 
 | ||||
| #if ENABLE_RENDER_STATISTICS | ||||
|     auto start_time = std::chrono::high_resolution_clock::now(); | ||||
| #endif // ENABLE_RENDER_STATISTICS
 | ||||
| 
 | ||||
|     if (m_bed.get_shape().empty()) | ||||
|     { | ||||
|         // this happens at startup when no data is still saved under <>\AppData\Roaming\Slic3rPE
 | ||||
|  | @ -1617,8 +1625,15 @@ void GLCanvas3D::render() | |||
| 
 | ||||
|     wxGetApp().imgui()->new_frame(); | ||||
| 
 | ||||
|     // picking pass
 | ||||
|     _picking_pass(); | ||||
|     if (m_picking_enabled) | ||||
|     { | ||||
|         if (m_rectangle_selection.is_dragging()) | ||||
|             // picking pass using rectangle selection
 | ||||
|             _rectangular_selection_picking_pass(); | ||||
|         else | ||||
|             // regular picking pass
 | ||||
|             _picking_pass(); | ||||
|     } | ||||
| 
 | ||||
|     // draw scene
 | ||||
|     glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); | ||||
|  | @ -1654,6 +1669,9 @@ void GLCanvas3D::render() | |||
|     _render_camera_target(); | ||||
| #endif // ENABLE_SHOW_CAMERA_TARGET
 | ||||
| 
 | ||||
|     if (m_picking_enabled && m_rectangle_selection.is_dragging()) | ||||
|         m_rectangle_selection.render(*this); | ||||
| 
 | ||||
|     // draw overlays
 | ||||
|     _render_gizmos_overlay(); | ||||
|     _render_warning_texture(); | ||||
|  | @ -1666,9 +1684,26 @@ void GLCanvas3D::render() | |||
|     if ((m_layers_editing.last_object_id >= 0) && (m_layers_editing.object_max_z() > 0.0f)) | ||||
|         m_layers_editing.render_overlay(*this); | ||||
| 
 | ||||
| #if ENABLE_RENDER_STATISTICS | ||||
|     ImGuiWrapper& imgui = *wxGetApp().imgui(); | ||||
|     imgui.set_next_window_bg_alpha(0.5f); | ||||
|     imgui.begin(std::string("Render statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); | ||||
|     imgui.text("Last frame: "); | ||||
|     ImGui::SameLine(); | ||||
|     imgui.text(std::to_string(m_render_stats.last_frame)); | ||||
|     ImGui::SameLine(); | ||||
|     imgui.text(" ms"); | ||||
|     imgui.end(); | ||||
| #endif // ENABLE_RENDER_STATISTICS
 | ||||
| 
 | ||||
|     wxGetApp().imgui()->render(); | ||||
| 
 | ||||
|     m_canvas->SwapBuffers(); | ||||
| 
 | ||||
| #if ENABLE_RENDER_STATISTICS | ||||
|     auto end_time = std::chrono::high_resolution_clock::now(); | ||||
|     m_render_stats.last_frame = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time).count(); | ||||
| #endif // ENABLE_RENDER_STATISTICS
 | ||||
| } | ||||
| 
 | ||||
| void GLCanvas3D::select_all() | ||||
|  | @ -2347,9 +2382,51 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) | |||
|                     // m_canvas->HandleAsNavigationKey(evt);   // XXX: Doesn't work in some cases / on Linux
 | ||||
|                     post_event(SimpleEvent(EVT_GLCANVAS_TAB)); | ||||
|                 } | ||||
|                 else if (keyCode == WXK_SHIFT) | ||||
|                 { | ||||
|                     if (m_picking_enabled && m_rectangle_selection.is_dragging()) | ||||
|                     { | ||||
|                         _update_selection_from_hover(); | ||||
|                         m_rectangle_selection.stop_dragging(); | ||||
|                         m_mouse.ignore_left_up = true; | ||||
|                         m_dirty = true; | ||||
|                     } | ||||
| //                    set_cursor(Standard);
 | ||||
|                 } | ||||
|                 else if (keyCode == WXK_ALT) | ||||
|                 { | ||||
|                     if (m_picking_enabled && m_rectangle_selection.is_dragging()) | ||||
|                     { | ||||
|                         _update_selection_from_hover(); | ||||
|                         m_rectangle_selection.stop_dragging(); | ||||
|                         m_mouse.ignore_left_up = true; | ||||
|                         m_dirty = true; | ||||
|                     } | ||||
| //                    set_cursor(Standard);
 | ||||
|                 } | ||||
|                 else if (keyCode == WXK_CONTROL) | ||||
|                     m_dirty = true; | ||||
|             } | ||||
|             else if (evt.GetEventType() == wxEVT_KEY_DOWN) { | ||||
|                 m_tab_down = keyCode == WXK_TAB && !evt.HasAnyModifiers(); | ||||
|                 if (keyCode == WXK_SHIFT) | ||||
|                 { | ||||
|                     if (m_picking_enabled && (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports)) | ||||
|                     { | ||||
|                         m_mouse.ignore_left_up = false; | ||||
| //                        set_cursor(Cross);
 | ||||
|                     } | ||||
|                 } | ||||
|                 else if (keyCode == WXK_ALT) | ||||
|                 { | ||||
|                     if (m_picking_enabled && (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports)) | ||||
|                     { | ||||
|                         m_mouse.ignore_left_up = false; | ||||
| //                        set_cursor(Cross);
 | ||||
|                     } | ||||
|                 } | ||||
|                 else if (keyCode == WXK_CONTROL) | ||||
|                     m_dirty = true; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | @ -2468,6 +2545,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) | |||
|         m_mouse.set_start_position_3D_as_invalid(); | ||||
|         m_mouse.set_start_position_2D_as_invalid(); | ||||
|         m_mouse.dragging = false; | ||||
|         m_mouse.ignore_left_up = false; | ||||
|         m_dirty = true; | ||||
| 
 | ||||
|         if (m_canvas->HasCapture()) | ||||
|  | @ -2566,7 +2644,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) | |||
|             if (top_level_wnd && top_level_wnd->IsActive()) | ||||
|                 m_canvas->SetFocus(); | ||||
|             m_mouse.position = pos.cast<double>(); | ||||
|             // 1) forces a frame render to ensure that m_hover_volume_id is updated even when the user right clicks while
 | ||||
|             // 1) forces a frame render to ensure that m_hover_volume_idxs is updated even when the user right clicks while
 | ||||
|             // the context menu is shown, ensuring it to disappear if the mouse is outside any volume and to
 | ||||
|             // change the volume hover state if any is under the mouse 
 | ||||
|             // 2) when switching between 3d view and preview the size of the canvas changes if the side panels are visible,
 | ||||
|  | @ -2606,26 +2684,35 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) | |||
|                 m_dirty = true; | ||||
|             } | ||||
|         } | ||||
|         else if (evt.LeftDown() && (evt.ShiftDown() || evt.AltDown()) && m_picking_enabled) | ||||
|         { | ||||
|             if (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports) | ||||
|             { | ||||
|                 m_rectangle_selection.start_dragging(m_mouse.position, evt.ShiftDown() ? GLSelectionRectangle::Select : GLSelectionRectangle::Deselect); | ||||
|                 m_dirty = true; | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             // Select volume in this 3D canvas.
 | ||||
|             // Don't deselect a volume if layer editing is enabled. We want the object to stay selected
 | ||||
|             // during the scene manipulation.
 | ||||
| 
 | ||||
|             if (m_picking_enabled && ((m_hover_volume_id != -1) || !is_layers_editing_enabled())) | ||||
|             if (m_picking_enabled && (!m_hover_volume_idxs.empty() || !is_layers_editing_enabled())) | ||||
|             { | ||||
|                 if (evt.LeftDown() && (m_hover_volume_id != -1)) | ||||
|                 if (evt.LeftDown() && !m_hover_volume_idxs.empty()) | ||||
|                 { | ||||
|                     bool already_selected = m_selection.contains_volume(m_hover_volume_id); | ||||
|                     int volume_idx = get_first_hover_volume_idx(); | ||||
|                     bool already_selected = m_selection.contains_volume(volume_idx); | ||||
|                     bool ctrl_down = evt.CmdDown(); | ||||
| 
 | ||||
|                     Selection::IndicesList curr_idxs = m_selection.get_volume_idxs(); | ||||
| 
 | ||||
|                     if (already_selected && ctrl_down) | ||||
|                         m_selection.remove(m_hover_volume_id); | ||||
|                         m_selection.remove(volume_idx); | ||||
|                     else | ||||
|                     { | ||||
|                         m_selection.add(m_hover_volume_id, !ctrl_down, true); | ||||
|                         m_selection.add(volume_idx, !ctrl_down, true); | ||||
|                         m_mouse.drag.move_requires_threshold = !already_selected; | ||||
|                         if (already_selected) | ||||
|                             m_mouse.set_move_start_threshold_position_2D_as_invalid(); | ||||
|  | @ -2633,6 +2720,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) | |||
|                             m_mouse.drag.move_start_threshold_position_2D = pos; | ||||
|                     } | ||||
| 
 | ||||
|                     // propagate event through callback
 | ||||
|                     if (curr_idxs != m_selection.get_volume_idxs()) | ||||
|                     { | ||||
|                         m_gizmos.refresh_on_off_state(m_selection); | ||||
|  | @ -2643,18 +2731,18 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) | |||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             // propagate event through callback
 | ||||
|             if (m_hover_volume_id != -1) | ||||
|             if (!m_hover_volume_idxs.empty()) | ||||
|             { | ||||
|                 if (evt.LeftDown() && m_moving_enabled && (m_mouse.drag.move_volume_idx == -1)) | ||||
|                 { | ||||
|                     // Only accept the initial position, if it is inside the volume bounding box.
 | ||||
|                     BoundingBoxf3 volume_bbox = m_volumes.volumes[m_hover_volume_id]->transformed_bounding_box(); | ||||
|                     int volume_idx = get_first_hover_volume_idx(); | ||||
|                     BoundingBoxf3 volume_bbox = m_volumes.volumes[volume_idx]->transformed_bounding_box(); | ||||
|                     volume_bbox.offset(1.0); | ||||
|                     if (volume_bbox.contains(m_mouse.scene_position)) | ||||
|                     { | ||||
|                         // The dragging operation is initiated.
 | ||||
|                         m_mouse.drag.move_volume_idx = m_hover_volume_id; | ||||
|                         m_mouse.drag.move_volume_idx = volume_idx; | ||||
|                         m_selection.start_dragging(); | ||||
|                         m_mouse.drag.start_position_3D = m_mouse.scene_position; | ||||
|                         m_moving = true; | ||||
|  | @ -2671,7 +2759,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) | |||
| 
 | ||||
|             Vec3d cur_pos = m_mouse.drag.start_position_3D; | ||||
|             // we do not want to translate objects if the user just clicked on an object while pressing shift to remove it from the selection and then drag
 | ||||
|             if (m_selection.contains_volume(m_hover_volume_id)) | ||||
|             if (m_selection.contains_volume(get_first_hover_volume_idx())) | ||||
|             { | ||||
|                 if (m_camera.get_theta() == 90.0f) | ||||
|                 { | ||||
|  | @ -2709,10 +2797,14 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) | |||
|             m_regenerate_volumes = false; | ||||
|             m_selection.translate(cur_pos - m_mouse.drag.start_position_3D); | ||||
|             wxGetApp().obj_manipul()->update_settings_value(m_selection); | ||||
| 
 | ||||
|             m_dirty = true; | ||||
|         } | ||||
|     } | ||||
|     else if (evt.Dragging() && evt.LeftIsDown() && m_picking_enabled && m_rectangle_selection.is_dragging()) | ||||
|     { | ||||
|         m_rectangle_selection.dragging(pos.cast<double>()); | ||||
|         m_dirty = true; | ||||
|     } | ||||
|     else if (evt.Dragging()) | ||||
|     { | ||||
|         m_mouse.dragging = true; | ||||
|  | @ -2726,7 +2818,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) | |||
|         else if (evt.LeftIsDown()) | ||||
|         { | ||||
|             // if dragging over blank area with left button, rotate
 | ||||
|             if ((m_hover_volume_id == -1) && m_mouse.is_start_position_3D_defined()) | ||||
|             if (m_hover_volume_idxs.empty() && m_mouse.is_start_position_3D_defined()) | ||||
|             { | ||||
|                 const Vec3d& orig = m_mouse.drag.start_position_3D; | ||||
|                 float sign = m_camera.inverted_phi ? -1.0f : 1.0f; | ||||
|  | @ -2769,7 +2861,14 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) | |||
|             // of the scene with the background processing data should be performed.
 | ||||
|             post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); | ||||
|         } | ||||
|         else if (evt.LeftUp() && !m_mouse.dragging && (m_hover_volume_id == -1) && !is_layers_editing_enabled()) | ||||
|         else if (evt.LeftUp() && m_picking_enabled && m_rectangle_selection.is_dragging()) | ||||
|         { | ||||
|             if (evt.ShiftDown() || evt.AltDown()) | ||||
|                 _update_selection_from_hover(); | ||||
| 
 | ||||
|             m_rectangle_selection.stop_dragging(); | ||||
|         } | ||||
|         else if (evt.LeftUp() && !m_mouse.ignore_left_up && !m_mouse.dragging && m_hover_volume_idxs.empty() && !is_layers_editing_enabled()) | ||||
|         { | ||||
|             // deselect and propagate event through callback
 | ||||
|             if (!evt.ShiftDown() && m_picking_enabled) | ||||
|  | @ -2788,18 +2887,18 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) | |||
|         else if (evt.RightUp()) | ||||
|         { | ||||
|             m_mouse.position = pos.cast<double>(); | ||||
|             // forces a frame render to ensure that m_hover_volume_id is updated even when the user right clicks while
 | ||||
|             // forces a frame render to ensure that m_hover_volume_idxs is updated even when the user right clicks while
 | ||||
|             // the context menu is already shown
 | ||||
|             render(); | ||||
|             if (m_hover_volume_id != -1) | ||||
|             if (!m_hover_volume_idxs.empty()) | ||||
|             { | ||||
|                 // if right clicking on volume, propagate event through callback (shows context menu)
 | ||||
|                 if (m_volumes.volumes[m_hover_volume_id]->hover | ||||
|                     && !m_volumes.volumes[m_hover_volume_id]->is_wipe_tower // no context menu for the wipe tower
 | ||||
|                 int volume_idx = get_first_hover_volume_idx(); | ||||
|                 if (!m_volumes.volumes[volume_idx]->is_wipe_tower // no context menu for the wipe tower
 | ||||
|                     && m_gizmos.get_current_type() != GLGizmosManager::SlaSupports)  // disable context menu when the gizmo is open
 | ||||
|                 { | ||||
|                     // forces the selection of the volume
 | ||||
|                     m_selection.add(m_hover_volume_id); | ||||
|                     m_selection.add(volume_idx); | ||||
|                     m_gizmos.refresh_on_off_state(m_selection); | ||||
|                     post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); | ||||
|                     m_gizmos.update_data(*this); | ||||
|  | @ -3199,6 +3298,20 @@ double GLCanvas3D::get_size_proportional_to_max_bed_size(double factor) const | |||
|     return factor * m_bed.get_bounding_box().max_size(); | ||||
| } | ||||
| 
 | ||||
| void GLCanvas3D::set_cursor(ECursorType type) | ||||
| { | ||||
|     if ((m_canvas != nullptr) && (m_cursor_type != type)) | ||||
|     { | ||||
|         switch (type) | ||||
|         { | ||||
|         case Standard: { m_canvas->SetCursor(*wxSTANDARD_CURSOR); break; } | ||||
|         case Cross: { m_canvas->SetCursor(*wxCROSS_CURSOR); break; } | ||||
|         } | ||||
| 
 | ||||
|         m_cursor_type = type; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GLCanvas3D::msw_rescale() | ||||
| { | ||||
|     m_warning_texture.msw_rescale(*this); | ||||
|  | @ -3377,7 +3490,7 @@ bool GLCanvas3D::_init_toolbar() | |||
| 
 | ||||
|     item.name = "layersediting"; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     item.icon_filename = "layers.svg"; | ||||
|     item.icon_filename = "layers_white.svg"; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     item.tooltip = GUI::L_str("Layers editing"); | ||||
|     item.sprite_id = 10; | ||||
|  | @ -3569,10 +3682,10 @@ void GLCanvas3D::_refresh_if_shown_on_screen() | |||
| 
 | ||||
| void GLCanvas3D::_picking_pass() const | ||||
| { | ||||
|     const Vec2d& pos = m_mouse.position; | ||||
| 
 | ||||
|     if (m_picking_enabled && !m_mouse.dragging && (pos != Vec2d(DBL_MAX, DBL_MAX))) | ||||
|     if (m_picking_enabled && !m_mouse.dragging && (m_mouse.position != Vec2d(DBL_MAX, DBL_MAX))) | ||||
|     { | ||||
|         m_hover_volume_idxs.clear(); | ||||
| 
 | ||||
|         // Render the object for picking.
 | ||||
|         // FIXME This cannot possibly work in a multi - sampled context as the color gets mangled by the anti - aliasing.
 | ||||
|         // Better to use software ray - casting on a bounding - box hierarchy.
 | ||||
|  | @ -3603,27 +3716,98 @@ void GLCanvas3D::_picking_pass() const | |||
| 
 | ||||
|         GLubyte color[4] = { 0, 0, 0, 0 }; | ||||
|         const Size& cnv_size = get_canvas_size(); | ||||
|         bool inside = (0 <= pos(0)) && (pos(0) < cnv_size.get_width()) && (0 <= pos(1)) && (pos(1) < cnv_size.get_height()); | ||||
|         bool inside = (0 <= m_mouse.position(0)) && (m_mouse.position(0) < cnv_size.get_width()) && (0 <= m_mouse.position(1)) && (m_mouse.position(1) < cnv_size.get_height()); | ||||
|         if (inside) | ||||
|         { | ||||
|             glsafe(::glReadPixels(pos(0), cnv_size.get_height() - pos(1) - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color)); | ||||
|             volume_id = color[0] + color[1] * 256 + color[2] * 256 * 256; | ||||
|             glsafe(::glReadPixels(m_mouse.position(0), cnv_size.get_height() - m_mouse.position(1) - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color)); | ||||
|             volume_id = color[0] + (color[1] << 8) + (color[2] << 16); | ||||
|         } | ||||
|         if ((0 <= volume_id) && (volume_id < (int)m_volumes.volumes.size())) | ||||
|         { | ||||
|             m_hover_volume_id = volume_id; | ||||
|             m_hover_volume_idxs.push_back(volume_id); | ||||
|             m_gizmos.set_hover_id(-1); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             m_hover_volume_id = -1; | ||||
|             m_gizmos.set_hover_id(inside && volume_id <= GLGizmoBase::BASE_ID ? (GLGizmoBase::BASE_ID - volume_id) : -1); | ||||
|         } | ||||
| 
 | ||||
|         _update_volumes_hover_state(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GLCanvas3D::_rectangular_selection_picking_pass() const | ||||
| { | ||||
|     m_gizmos.set_hover_id(-1); | ||||
| 
 | ||||
|     std::set<int> idxs; | ||||
| 
 | ||||
|     if (m_picking_enabled) | ||||
|     { | ||||
|         if (m_multisample_allowed) | ||||
|             glsafe(::glDisable(GL_MULTISAMPLE)); | ||||
| 
 | ||||
|         glsafe(::glDisable(GL_BLEND)); | ||||
|         glsafe(::glEnable(GL_DEPTH_TEST)); | ||||
| 
 | ||||
|         glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); | ||||
| 
 | ||||
|         _render_volumes_for_picking(); | ||||
| 
 | ||||
|         if (m_multisample_allowed) | ||||
|             glsafe(::glEnable(GL_MULTISAMPLE)); | ||||
| 
 | ||||
|         int width = std::max((int)m_rectangle_selection.get_width(), 1); | ||||
|         int height = std::max((int)m_rectangle_selection.get_height(), 1); | ||||
|         int px_count = width * height; | ||||
| 
 | ||||
|         int left = (int)m_rectangle_selection.get_left(); | ||||
|         int top = get_canvas_size().get_height() - (int)m_rectangle_selection.get_top(); | ||||
|         if ((left >= 0) && (top >= 0)) | ||||
|         { | ||||
| #define USE_PARALLEL 1 | ||||
| #if USE_PARALLEL | ||||
|             struct Pixel | ||||
|             { | ||||
|                 std::array<GLubyte, 4> data; | ||||
|                 int id() const { return data[0] + (data[1] << 8) + (data[2] << 16); } | ||||
|             }; | ||||
| 
 | ||||
|             std::vector<Pixel> frame(px_count); | ||||
|             glsafe(::glReadPixels(left, top, width, height, GL_RGBA, GL_UNSIGNED_BYTE, (void*)frame.data())); | ||||
| 
 | ||||
|             tbb::spin_mutex mutex; | ||||
|             tbb::parallel_for(tbb::blocked_range<size_t>(0, frame.size(), (size_t)width), | ||||
|                 [this, &frame, &idxs, &mutex](const tbb::blocked_range<size_t>& range) { | ||||
|                 for (size_t i = range.begin(); i < range.end(); ++i) | ||||
|                 { | ||||
|                     int volume_id = frame[i].id(); | ||||
|                     if ((0 <= volume_id) && (volume_id < (int)m_volumes.volumes.size())) | ||||
|                     { | ||||
|                         mutex.lock(); | ||||
|                         idxs.insert(volume_id); | ||||
|                         mutex.unlock(); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             ); | ||||
| #else | ||||
|             std::vector<GLubyte> frame(4 * px_count); | ||||
|             glsafe(::glReadPixels(left, top, width, height, GL_RGBA, GL_UNSIGNED_BYTE, (void*)frame.data())); | ||||
| 
 | ||||
|             for (int i = 0; i < px_count; ++i) | ||||
|             { | ||||
|                 int px_id = 4 * i; | ||||
|                 int volume_id = frame[px_id] + (frame[px_id + 1] << 8) + (frame[px_id + 2] << 16); | ||||
|                 if ((0 <= volume_id) && (volume_id < (int)m_volumes.volumes.size())) | ||||
|                     idxs.insert(volume_id); | ||||
|             } | ||||
| #endif // USE_PARALLEL
 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     m_hover_volume_idxs.assign(idxs.begin(), idxs.end()); | ||||
|     _update_volumes_hover_state(); | ||||
| } | ||||
| 
 | ||||
| void GLCanvas3D::_render_background() const | ||||
| { | ||||
|     glsafe(::glPushMatrix()); | ||||
|  | @ -4109,24 +4293,93 @@ void GLCanvas3D::_update_volumes_hover_state() const | |||
| { | ||||
|     for (GLVolume* v : m_volumes.volumes) | ||||
|     { | ||||
|         v->hover = false; | ||||
|         v->hover = GLVolume::HS_None; | ||||
|     } | ||||
| 
 | ||||
|     if (m_hover_volume_id == -1) | ||||
|     if (m_hover_volume_idxs.empty()) | ||||
|         return; | ||||
| 
 | ||||
|     GLVolume* volume = m_volumes.volumes[m_hover_volume_id]; | ||||
|     if (volume->is_modifier) | ||||
|         volume->hover = true; | ||||
|     else | ||||
|     { | ||||
|         int object_idx = volume->object_idx(); | ||||
|         int instance_idx = volume->instance_idx(); | ||||
|     bool ctrl_pressed = wxGetKeyState(WXK_CONTROL); // additive select/deselect
 | ||||
|     bool shift_pressed = wxGetKeyState(WXK_SHIFT);  // select by rectangle
 | ||||
|     bool alt_pressed = wxGetKeyState(WXK_ALT);      // deselect by rectangle
 | ||||
| 
 | ||||
|         for (GLVolume* v : m_volumes.volumes) | ||||
|     if (alt_pressed && (shift_pressed || ctrl_pressed)) | ||||
|     { | ||||
|         // illegal combinations of keys
 | ||||
|         m_hover_volume_idxs.clear(); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     bool selection_modifiers_only = m_selection.is_empty() || m_selection.is_any_modifier(); | ||||
| 
 | ||||
|     bool hover_modifiers_only = true; | ||||
|     for (int i : m_hover_volume_idxs) | ||||
|     { | ||||
|         if (!m_volumes.volumes[i]->is_modifier) | ||||
|         { | ||||
|             if ((v->object_idx() == object_idx) && (v->instance_idx() == instance_idx)) | ||||
|                 v->hover = true; | ||||
|             hover_modifiers_only = false; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     std::set<std::pair<int, int>> hover_instances; | ||||
|     for (int i : m_hover_volume_idxs) | ||||
|     { | ||||
|         const GLVolume& v = *m_volumes.volumes[i]; | ||||
|         hover_instances.insert(std::make_pair(v.object_idx(), v.instance_idx())); | ||||
|     } | ||||
| 
 | ||||
|     bool hover_from_single_instance = hover_instances.size() == 1; | ||||
| 
 | ||||
|     if (hover_modifiers_only && !hover_from_single_instance) | ||||
|     { | ||||
|         // do not allow to select volumes from different instances
 | ||||
|         m_hover_volume_idxs.clear(); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     for (int i : m_hover_volume_idxs) | ||||
|     { | ||||
|         GLVolume& volume = *m_volumes.volumes[i]; | ||||
|         if (volume.hover != GLVolume::HS_None) | ||||
|             continue; | ||||
| 
 | ||||
|         bool deselect = volume.selected && ((ctrl_pressed && !shift_pressed) || alt_pressed); | ||||
|         // (volume->is_modifier && !selection_modifiers_only && !is_ctrl_pressed) -> allows hovering on selected modifiers belonging to selection of type Instance
 | ||||
|         bool select = (!volume.selected || (volume.is_modifier && !selection_modifiers_only && !ctrl_pressed)) && !alt_pressed; | ||||
| 
 | ||||
|         if (select || deselect) | ||||
|         { | ||||
|             bool as_volume = | ||||
|                 volume.is_modifier && hover_from_single_instance && !ctrl_pressed && | ||||
|                 ( | ||||
|                 (!deselect) || | ||||
|                 (deselect && !m_selection.is_single_full_instance() && (volume.object_idx() == m_selection.get_object_idx()) && (volume.instance_idx() == m_selection.get_instance_idx())) | ||||
|                 ); | ||||
| 
 | ||||
|             if (as_volume) | ||||
|             { | ||||
|                 if (deselect) | ||||
|                     volume.hover = GLVolume::HS_Deselect; | ||||
|                 else | ||||
|                     volume.hover = GLVolume::HS_Select; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 int object_idx = volume.object_idx(); | ||||
|                 int instance_idx = volume.instance_idx(); | ||||
| 
 | ||||
|                 for (GLVolume* v : m_volumes.volumes) | ||||
|                 { | ||||
|                     if ((v->object_idx() == object_idx) && (v->instance_idx() == instance_idx)) | ||||
|                     { | ||||
|                         if (deselect) | ||||
|                             v->hover = GLVolume::HS_Deselect; | ||||
|                         else | ||||
|                             v->hover = GLVolume::HS_Select; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -5405,6 +5658,55 @@ void GLCanvas3D::_resize_toolbars() const | |||
| } | ||||
| #endif // !ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
| void GLCanvas3D::_update_selection_from_hover() | ||||
| { | ||||
|     bool ctrl_pressed = wxGetKeyState(WXK_CONTROL); | ||||
| 
 | ||||
|     if (m_hover_volume_idxs.empty()) | ||||
|     { | ||||
|         if (!ctrl_pressed && (m_rectangle_selection.get_state() == GLSelectionRectangle::Select)) | ||||
|             m_selection.clear(); | ||||
| 
 | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     GLSelectionRectangle::EState state = m_rectangle_selection.get_state(); | ||||
| 
 | ||||
|     bool hover_modifiers_only = true; | ||||
|     for (int i : m_hover_volume_idxs) | ||||
|     { | ||||
|         if (!m_volumes.volumes[i]->is_modifier) | ||||
|         { | ||||
|             hover_modifiers_only = false; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if ((state == GLSelectionRectangle::Select) && !ctrl_pressed) | ||||
|         m_selection.clear(); | ||||
| 
 | ||||
|     for (int i : m_hover_volume_idxs) | ||||
|     { | ||||
|         if (state == GLSelectionRectangle::Select) | ||||
|         { | ||||
|             if (hover_modifiers_only) | ||||
|             { | ||||
|                 const GLVolume& v = *m_volumes.volumes[i]; | ||||
|                 m_selection.add_volume(v.object_idx(), v.volume_idx(), v.instance_idx(), false); | ||||
|             } | ||||
|             else | ||||
|                 m_selection.add(i, false); | ||||
|         } | ||||
|         else | ||||
|             m_selection.remove(i); | ||||
|     } | ||||
| 
 | ||||
|     m_gizmos.refresh_on_off_state(m_selection); | ||||
|     m_gizmos.update_data(*this); | ||||
|     post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); | ||||
|     m_dirty = true; | ||||
| } | ||||
| 
 | ||||
| const Print* GLCanvas3D::fff_print() const | ||||
| { | ||||
|     return (m_process == nullptr) ? nullptr : m_process->fff_print(); | ||||
|  |  | |||
|  | @ -303,6 +303,7 @@ class GLCanvas3D | |||
|         Vec2d position; | ||||
|         Vec3d scene_position; | ||||
|         Drag drag; | ||||
|         bool ignore_left_up; | ||||
| 
 | ||||
|         Mouse(); | ||||
| 
 | ||||
|  | @ -319,7 +320,6 @@ class GLCanvas3D | |||
|         } | ||||
|     }; | ||||
| 
 | ||||
| private: | ||||
|     struct SlaCap | ||||
|     { | ||||
|         struct Triangles | ||||
|  | @ -399,6 +399,23 @@ private: | |||
|         void render(const GLCanvas3D& canvas) const; | ||||
|     }; | ||||
| 
 | ||||
| #if ENABLE_RENDER_STATISTICS | ||||
|     struct RenderStats | ||||
|     { | ||||
|         long long last_frame; | ||||
| 
 | ||||
|         RenderStats() : last_frame(0) {} | ||||
|     }; | ||||
| #endif // ENABLE_RENDER_STATISTICS
 | ||||
| 
 | ||||
| public: | ||||
|     enum ECursorType : unsigned char | ||||
|     { | ||||
|         Standard, | ||||
|         Cross | ||||
|     }; | ||||
| 
 | ||||
| private: | ||||
|     wxGLCanvas* m_canvas; | ||||
|     wxGLContext* m_context; | ||||
| #if ENABLE_RETINA_GL | ||||
|  | @ -433,7 +450,7 @@ private: | |||
|     bool m_initialized; | ||||
|     bool m_use_VBOs; | ||||
|     bool m_apply_zoom_to_volumes_filter; | ||||
|     mutable int m_hover_volume_id; | ||||
|     mutable std::vector<int> m_hover_volume_idxs; | ||||
|     bool m_warning_texture_enabled; | ||||
|     bool m_legend_texture_enabled; | ||||
|     bool m_picking_enabled; | ||||
|  | @ -443,6 +460,8 @@ private: | |||
|     bool m_regenerate_volumes; | ||||
|     bool m_moving; | ||||
|     bool m_tab_down; | ||||
|     ECursorType m_cursor_type; | ||||
|     GLSelectionRectangle m_rectangle_selection; | ||||
| 
 | ||||
|     // Following variable is obsolete and it should be safe to remove it.
 | ||||
|     // I just don't want to do it now before a release (Lukas Matena 24.3.2019)
 | ||||
|  | @ -454,6 +473,10 @@ private: | |||
| 
 | ||||
|     GCodePreviewVolumeIndex m_gcode_preview_volume_index; | ||||
| 
 | ||||
| #if ENABLE_RENDER_STATISTICS | ||||
|     RenderStats m_render_stats; | ||||
| #endif // ENABLE_RENDER_STATISTICS
 | ||||
| 
 | ||||
| public: | ||||
|     GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar); | ||||
|     ~GLCanvas3D(); | ||||
|  | @ -582,7 +605,7 @@ public: | |||
|     float get_view_toolbar_height() const { return m_view_toolbar.get_height(); } | ||||
| 
 | ||||
|     int get_move_volume_id() const { return m_mouse.drag.move_volume_idx; } | ||||
|     int get_hover_volume_id() const { return m_hover_volume_id; } | ||||
|     int get_first_hover_volume_idx() const { return m_hover_volume_idxs.empty() ? -1 : m_hover_volume_idxs.front(); } | ||||
| 
 | ||||
|     // Returns the view ray line, in world coordinate, at the given mouse position.
 | ||||
|     Linef3 mouse_ray(const Point& mouse_pos); | ||||
|  | @ -594,6 +617,7 @@ public: | |||
| 
 | ||||
|     double get_size_proportional_to_max_bed_size(double factor) const; | ||||
| 
 | ||||
|     void set_cursor(ECursorType type); | ||||
|     void msw_rescale(); | ||||
| 
 | ||||
| private: | ||||
|  | @ -612,6 +636,7 @@ private: | |||
|     void _refresh_if_shown_on_screen(); | ||||
| 
 | ||||
|     void _picking_pass() const; | ||||
|     void _rectangular_selection_picking_pass() const; | ||||
|     void _render_background() const; | ||||
|     void _render_bed(float theta) const; | ||||
|     void _render_axes() const; | ||||
|  | @ -690,6 +715,9 @@ private: | |||
|     void _resize_toolbars() const; | ||||
| #endif // !ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     // updates the selection from the content of m_hover_volume_idxs
 | ||||
|     void _update_selection_from_hover(); | ||||
| 
 | ||||
|     static std::vector<float> _parse_colors(const std::vector<std::string>& colors); | ||||
| 
 | ||||
| public: | ||||
|  |  | |||
							
								
								
									
										117
									
								
								src/slic3r/GUI/GLSelectionRectangle.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								src/slic3r/GUI/GLSelectionRectangle.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,117 @@ | |||
| #include "GLSelectionRectangle.hpp" | ||||
| #include "Camera.hpp" | ||||
| #include "3DScene.hpp" | ||||
| #include "GLCanvas3D.hpp" | ||||
| 
 | ||||
| #include <GL/glew.h> | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace GUI { | ||||
| 
 | ||||
|     void GLSelectionRectangle::start_dragging(const Vec2d& mouse_position, EState state) | ||||
|     { | ||||
|         if (is_dragging() || (state == Off)) | ||||
|             return; | ||||
| 
 | ||||
|         m_state = state; | ||||
|         m_start_corner = mouse_position; | ||||
|         m_end_corner = mouse_position; | ||||
|     } | ||||
| 
 | ||||
|     void GLSelectionRectangle::dragging(const Vec2d& mouse_position) | ||||
|     { | ||||
|         if (!is_dragging()) | ||||
|             return; | ||||
| 
 | ||||
|         m_end_corner = mouse_position; | ||||
|     } | ||||
| 
 | ||||
|     std::vector<unsigned int> GLSelectionRectangle::stop_dragging(const GLCanvas3D& canvas, const std::vector<Vec3d>& points) | ||||
|     { | ||||
|         std::vector<unsigned int> out; | ||||
| 
 | ||||
|         if (!is_dragging()) | ||||
|             return out; | ||||
| 
 | ||||
|         m_state = Off; | ||||
| 
 | ||||
|         const Camera& camera = canvas.get_camera(); | ||||
|         const std::array<int, 4>& viewport = camera.get_viewport(); | ||||
|         const Transform3d& modelview_matrix = camera.get_view_matrix(); | ||||
|         const Transform3d& projection_matrix = camera.get_projection_matrix(); | ||||
| 
 | ||||
|         // bounding box created from the rectangle corners - will take care of order of the corners
 | ||||
|         BoundingBox rectangle(Points{ Point(m_start_corner.cast<int>()), Point(m_end_corner.cast<int>()) }); | ||||
| 
 | ||||
|         // Iterate over all points and determine whether they're in the rectangle.
 | ||||
|         for (unsigned int i = 0; i<points.size(); ++i) { | ||||
|             const Vec3d& point = points[i]; | ||||
|             GLdouble out_x, out_y, out_z; | ||||
|             ::gluProject((GLdouble)point(0), (GLdouble)point(1), (GLdouble)point(2), (GLdouble*)modelview_matrix.data(), (GLdouble*)projection_matrix.data(), (GLint*)viewport.data(), &out_x, &out_y, &out_z); | ||||
|             out_y = canvas.get_canvas_size().get_height() - out_y; | ||||
| 
 | ||||
|             if (rectangle.contains(Point(out_x, out_y))) | ||||
|                 out.push_back(i); | ||||
|         } | ||||
| 
 | ||||
|         return out; | ||||
|     } | ||||
| 
 | ||||
|     void GLSelectionRectangle::stop_dragging() | ||||
|     { | ||||
|         if (is_dragging()) | ||||
|             m_state = Off; | ||||
|     } | ||||
| 
 | ||||
|     void GLSelectionRectangle::render(const GLCanvas3D& canvas) const | ||||
|     { | ||||
|         if (!is_dragging()) | ||||
|             return; | ||||
| 
 | ||||
|         float zoom = canvas.get_camera().zoom; | ||||
|         float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; | ||||
| 
 | ||||
|         Size cnv_size = canvas.get_canvas_size(); | ||||
|         float cnv_half_width = 0.5f * (float)cnv_size.get_width(); | ||||
|         float cnv_half_height = 0.5f * (float)cnv_size.get_height(); | ||||
|         if ((cnv_half_width == 0.0f) || (cnv_half_height == 0.0f)) | ||||
|             return; | ||||
| 
 | ||||
|         Vec2d start(m_start_corner(0) - cnv_half_width, cnv_half_height - m_start_corner(1)); | ||||
|         Vec2d end(m_end_corner(0) - cnv_half_width, cnv_half_height - m_end_corner(1)); | ||||
| 
 | ||||
|         float left = (float)std::min(start(0), end(0)) * inv_zoom; | ||||
|         float top = (float)std::max(start(1), end(1)) * inv_zoom; | ||||
|         float right = (float)std::max(start(0), end(0)) * inv_zoom; | ||||
|         float bottom = (float)std::min(start(1), end(1)) * inv_zoom; | ||||
| 
 | ||||
|         glsafe(::glLineWidth(1.5f)); | ||||
|         float color[3]; | ||||
|         color[0] = (m_state == Select) ? 0.3f : 1.0f; | ||||
|         color[1] = (m_state == Select) ? 1.0f : 0.3f; | ||||
|         color[2] = 0.3f; | ||||
|         glsafe(::glColor3fv(color)); | ||||
| 
 | ||||
|         glsafe(::glDisable(GL_DEPTH_TEST)); | ||||
| 
 | ||||
|         glsafe(::glPushMatrix()); | ||||
|         glsafe(::glLoadIdentity()); | ||||
| 
 | ||||
|         glsafe(::glPushAttrib(GL_ENABLE_BIT)); | ||||
|         glsafe(::glLineStipple(4, 0xAAAA)); | ||||
|         glsafe(::glEnable(GL_LINE_STIPPLE)); | ||||
| 
 | ||||
|         ::glBegin(GL_LINE_LOOP); | ||||
|         ::glVertex2f((GLfloat)left, (GLfloat)bottom); | ||||
|         ::glVertex2f((GLfloat)right, (GLfloat)bottom); | ||||
|         ::glVertex2f((GLfloat)right, (GLfloat)top); | ||||
|         ::glVertex2f((GLfloat)left, (GLfloat)top); | ||||
|         glsafe(::glEnd()); | ||||
| 
 | ||||
|         glsafe(::glPopAttrib()); | ||||
| 
 | ||||
|         glsafe(::glPopMatrix()); | ||||
|     } | ||||
| 
 | ||||
| } // namespace GUI
 | ||||
| } // namespace Slic3r
 | ||||
							
								
								
									
										56
									
								
								src/slic3r/GUI/GLSelectionRectangle.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/slic3r/GUI/GLSelectionRectangle.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,56 @@ | |||
| #ifndef slic3r_GLSelectionRectangle_hpp_ | ||||
| #define slic3r_GLSelectionRectangle_hpp_ | ||||
| 
 | ||||
| #include "libslic3r/Point.hpp" | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace GUI { | ||||
|      | ||||
| struct Camera; | ||||
| class GLCanvas3D; | ||||
| 
 | ||||
| class GLSelectionRectangle { | ||||
| public: | ||||
|     enum EState { | ||||
|             Off, | ||||
|             Select, | ||||
|             Deselect | ||||
|     }; | ||||
| 
 | ||||
|     // Initiates the rectangle. 
 | ||||
|     void start_dragging(const Vec2d& mouse_position, EState state); | ||||
| 
 | ||||
|     // To be called on mouse move.
 | ||||
|     void dragging(const Vec2d& mouse_position); | ||||
| 
 | ||||
|     // Given a vector of points in world coordinates, the function returns indices of those
 | ||||
|     // that are in the rectangle. It then disables the rectangle.
 | ||||
|     std::vector<unsigned int> stop_dragging(const GLCanvas3D& canvas, const std::vector<Vec3d>& points); | ||||
| 
 | ||||
|     // Disables the rectangle.
 | ||||
|     void stop_dragging(); | ||||
| 
 | ||||
|     void render(const GLCanvas3D& canvas) const; | ||||
| 
 | ||||
|     bool is_dragging() const { return m_state != Off; } | ||||
|     EState get_state() const { return m_state; } | ||||
| 
 | ||||
|     float get_width() const { return std::abs(m_start_corner(0) - m_end_corner(0)); } | ||||
|     float get_height() const { return std::abs(m_start_corner(1) - m_end_corner(1)); } | ||||
|     float get_left() const { return std::min(m_start_corner(0), m_end_corner(0)); } | ||||
|     float get_right() const { return std::max(m_start_corner(0), m_end_corner(0)); } | ||||
|     float get_top() const { return std::max(m_start_corner(1), m_end_corner(1)); } | ||||
|     float get_bottom() const { return std::min(m_start_corner(1), m_end_corner(1)); } | ||||
| 
 | ||||
| private: | ||||
|     EState m_state = Off; | ||||
|     Vec2d m_start_corner; | ||||
|     Vec2d m_end_corner; | ||||
| }; | ||||
| 
 | ||||
|      | ||||
| } // namespace GUI
 | ||||
| } // namespace Slic3r
 | ||||
| 
 | ||||
| 
 | ||||
| #endif // slic3r_GLGizmoSlaSupports_hpp_
 | ||||
|  | @ -44,6 +44,10 @@ | |||
| #include "SysInfoDialog.hpp" | ||||
| #include "KBShortcutsDialog.hpp" | ||||
| 
 | ||||
| #ifdef __WXMSW__ | ||||
| #include <Shlobj.h> | ||||
| #endif // __WXMSW__
 | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace GUI { | ||||
| 
 | ||||
|  | @ -182,6 +186,10 @@ bool GUI_App::on_init_inner() | |||
|     app_config->set("version", SLIC3R_VERSION); | ||||
|     app_config->save(); | ||||
| 
 | ||||
| #ifdef __WXMSW__ | ||||
|     associate_3mf_files(); | ||||
| #endif // __WXMSW__
 | ||||
| 
 | ||||
|     preset_updater = new PresetUpdater(); | ||||
|     Bind(EVT_SLIC3R_VERSION_ONLINE, [this](const wxCommandEvent &evt) { | ||||
|         app_config->set("version_online", into_u8(evt.GetString())); | ||||
|  | @ -303,13 +311,13 @@ bool GUI_App::dark_mode_menus() | |||
| void GUI_App::init_label_colours() | ||||
| { | ||||
|     if (dark_mode()) { | ||||
|         m_color_label_modified = wxColour(252, 77, 1); | ||||
|         m_color_label_sys = wxColour(26, 132, 57); | ||||
|     } | ||||
|     else { | ||||
|         m_color_label_modified = wxColour(253, 111, 40); | ||||
|         m_color_label_sys = wxColour(115, 220, 103); | ||||
|     } | ||||
|     else { | ||||
|         m_color_label_modified = wxColour(252, 77, 1); | ||||
|         m_color_label_sys = wxColour(26, 132, 57); | ||||
|     } | ||||
|     m_color_label_default = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); | ||||
| } | ||||
| 
 | ||||
|  | @ -340,15 +348,19 @@ void GUI_App::init_fonts() | |||
| #endif /*__WXMAC__*/ | ||||
| } | ||||
| 
 | ||||
| void GUI_App::update_fonts() | ||||
| void GUI_App::update_fonts(const MainFrame *main_frame) | ||||
| { | ||||
|     /* Only normal and bold fonts are used for an application rescale,
 | ||||
|      * because of under MSW small and normal fonts are the same. | ||||
|      * To avoid same rescaling twice, just fill this values | ||||
|      * from rescaled MainFrame | ||||
|      */ | ||||
|     m_normal_font   = mainframe->normal_font(); | ||||
|     m_bold_font     = mainframe->normal_font().Bold(); | ||||
| 	if (main_frame == nullptr) | ||||
| 		main_frame = this->mainframe; | ||||
|     m_normal_font   = main_frame->normal_font(); | ||||
|     m_small_font    = m_normal_font; | ||||
|     m_bold_font     = main_frame->normal_font().Bold(); | ||||
|     m_em_unit       = main_frame->em_unit(); | ||||
| } | ||||
| 
 | ||||
| void GUI_App::set_label_clr_modified(const wxColour& clr) { | ||||
|  | @ -773,14 +785,14 @@ void GUI_App::add_config_menu(wxMenuBar *menu) | |||
| // to notify the user whether he is aware that some preset changes will be lost.
 | ||||
| bool GUI_App::check_unsaved_changes() | ||||
| { | ||||
|     std::string dirty; | ||||
|     wxString dirty; | ||||
|     PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); | ||||
|     for (Tab *tab : tabs_list) | ||||
|         if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) | ||||
|             if (dirty.empty()) | ||||
|                 dirty = tab->name(); | ||||
|                 dirty = _(tab->name()); | ||||
|             else | ||||
|                 dirty += std::string(", ") + tab->name(); | ||||
|                 dirty += wxString(", ") + _(tab->name()); | ||||
|     if (dirty.empty()) | ||||
|         // No changes, the application may close or reload presets.
 | ||||
|         return true; | ||||
|  | @ -947,5 +959,64 @@ void GUI_App::window_pos_sanitize(wxTopLevelWindow* window) | |||
| // }
 | ||||
| 
 | ||||
| 
 | ||||
| #ifdef __WXMSW__ | ||||
| void GUI_App::associate_3mf_files() | ||||
| { | ||||
|     // see as reference: https://stackoverflow.com/questions/20245262/c-program-needs-an-file-association
 | ||||
| 
 | ||||
|     auto reg_set = [](HKEY hkeyHive, const wchar_t* pszVar, const wchar_t* pszValue) | ||||
|     { | ||||
|         wchar_t szValueCurrent[1000]; | ||||
|         DWORD dwType; | ||||
|         DWORD dwSize = sizeof(szValueCurrent); | ||||
| 
 | ||||
|         int iRC = ::RegGetValueW(hkeyHive, pszVar, nullptr, RRF_RT_ANY, &dwType, szValueCurrent, &dwSize); | ||||
| 
 | ||||
|         bool bDidntExist = iRC == ERROR_FILE_NOT_FOUND; | ||||
| 
 | ||||
|         if ((iRC != ERROR_SUCCESS) && !bDidntExist) | ||||
|             // an error occurred
 | ||||
|             return; | ||||
| 
 | ||||
|         if (!bDidntExist) | ||||
|         { | ||||
|             if (dwType != REG_SZ) | ||||
|                 // invalid type
 | ||||
|                 return; | ||||
| 
 | ||||
|             if (::wcscmp(szValueCurrent, pszValue) == 0) | ||||
|                 // value already set
 | ||||
|                 return; | ||||
|         } | ||||
| 
 | ||||
|         DWORD dwDisposition; | ||||
|         HKEY hkey; | ||||
|         iRC = ::RegCreateKeyExW(hkeyHive, pszVar, 0, 0, 0, KEY_ALL_ACCESS, nullptr, &hkey, &dwDisposition); | ||||
|         if (iRC == ERROR_SUCCESS) | ||||
|             iRC = ::RegSetValueExW(hkey, L"", 0, REG_SZ, (BYTE*)pszValue, (::wcslen(pszValue) + 1) * sizeof(wchar_t)); | ||||
| 
 | ||||
|         RegCloseKey(hkey); | ||||
|     }; | ||||
| 
 | ||||
|     wchar_t app_path[MAX_PATH]; | ||||
|     ::GetModuleFileNameW(nullptr, app_path, sizeof(app_path)); | ||||
| 
 | ||||
|     std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\""; | ||||
|     std::wstring prog_id = L"Prusa.Slicer.1"; | ||||
|     std::wstring prog_desc = L"PrusaSlicer"; | ||||
|     std::wstring prog_command = prog_path + L" \"%1\""; | ||||
|     std::wstring reg_base = L"Software\\Classes"; | ||||
|     std::wstring reg_extension = reg_base + L"\\.3mf"; | ||||
|     std::wstring reg_prog_id = reg_base + L"\\" + prog_id; | ||||
|     std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command"; | ||||
| 
 | ||||
|     reg_set(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str()); | ||||
|     reg_set(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str()); | ||||
|     reg_set(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str()); | ||||
| 
 | ||||
|     ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); | ||||
| } | ||||
| #endif // __WXMSW__
 | ||||
| 
 | ||||
| } // GUI
 | ||||
| } //Slic3r
 | ||||
|  |  | |||
|  | @ -100,7 +100,7 @@ public: | |||
|     void            init_label_colours(); | ||||
|     void            update_label_colours_from_appconfig(); | ||||
|     void            init_fonts(); | ||||
|     void            update_fonts(); | ||||
| 	void            update_fonts(const MainFrame *main_frame = nullptr); | ||||
|     void            set_label_clr_modified(const wxColour& clr); | ||||
|     void            set_label_clr_sys(const wxColour& clr); | ||||
| 
 | ||||
|  | @ -178,6 +178,9 @@ private: | |||
|     void            window_pos_save(wxTopLevelWindow* window, const std::string &name); | ||||
|     void            window_pos_restore(wxTopLevelWindow* window, const std::string &name, bool default_maximized = false); | ||||
|     void            window_pos_sanitize(wxTopLevelWindow* window); | ||||
| #ifdef __WXMSW__ | ||||
|     void associate_3mf_files(); | ||||
| #endif // __WXMSW__
 | ||||
| }; | ||||
| DECLARE_APP(GUI_App) | ||||
| 
 | ||||
|  |  | |||
|  | @ -193,6 +193,76 @@ void ObjectList::create_popup_menus() | |||
|     create_instance_popupmenu(&m_menu_instance); | ||||
| } | ||||
| 
 | ||||
| void ObjectList::get_selected_item_indexes(int& obj_idx, int& vol_idx, const wxDataViewItem& input_item/* = wxDataViewItem(0)*/) | ||||
| { | ||||
|     const wxDataViewItem item = input_item == wxDataViewItem(0) ? GetSelection() : input_item; | ||||
| 
 | ||||
|     if (!item) | ||||
|     { | ||||
|         obj_idx = vol_idx = -1; | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     const ItemType type = m_objects_model->GetItemType(item); | ||||
| 
 | ||||
|     obj_idx =   type & itObject ? m_objects_model->GetIdByItem(item) : | ||||
|                 type & itVolume ? m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)) : -1; | ||||
| 
 | ||||
|     vol_idx =   type & itVolume ? m_objects_model->GetVolumeIdByItem(item) : -1; | ||||
| } | ||||
| 
 | ||||
| int ObjectList::get_mesh_errors_count(const int obj_idx, const int vol_idx /*= -1*/) const | ||||
| { | ||||
|     if (obj_idx < 0) | ||||
|         return 0; | ||||
| 
 | ||||
|     return (*m_objects)[obj_idx]->get_mesh_errors_count(vol_idx); | ||||
| } | ||||
| 
 | ||||
| wxString ObjectList::get_mesh_errors_list(const int obj_idx, const int vol_idx /*= -1*/) const | ||||
| {     | ||||
|     const int errors = get_mesh_errors_count(obj_idx, vol_idx); | ||||
| 
 | ||||
|     if (errors == 0) | ||||
|         return ""; // hide tooltip
 | ||||
| 
 | ||||
|     // Create tooltip string, if there are errors 
 | ||||
|     wxString tooltip = wxString::Format(_(L("Auto-repaired (%d errors):\n")), errors); | ||||
| 
 | ||||
|     const stl_stats& stats = vol_idx == -1 ? | ||||
|                             (*m_objects)[obj_idx]->get_object_stl_stats() : | ||||
|                             (*m_objects)[obj_idx]->volumes[vol_idx]->mesh.stl.stats; | ||||
| 
 | ||||
|     std::map<std::string, int> error_msg = { | ||||
|         { L("degenerate facets"),   stats.degenerate_facets }, | ||||
|         { L("edges fixed"),         stats.edges_fixed       }, | ||||
|         { L("facets removed"),      stats.facets_removed    }, | ||||
|         { L("facets added"),        stats.facets_added      }, | ||||
|         { L("facets reversed"),     stats.facets_reversed   }, | ||||
|         { L("backwards edges"),     stats.backwards_edges   } | ||||
|     }; | ||||
| 
 | ||||
|     for (const auto& error : error_msg) | ||||
|         if (error.second > 0) | ||||
|             tooltip += wxString::Format("\t%d %s\n", error.second, _(error.first)); | ||||
| 
 | ||||
|     if (is_windows10()) | ||||
|         tooltip += _(L("Right button click the icon to fix STL through Netfabb")); | ||||
| 
 | ||||
|     return tooltip; | ||||
| } | ||||
| 
 | ||||
| wxString ObjectList::get_mesh_errors_list() | ||||
| { | ||||
|     if (!GetSelection()) | ||||
|         return ""; | ||||
| 
 | ||||
|     int obj_idx, vol_idx; | ||||
|     get_selected_item_indexes(obj_idx, vol_idx); | ||||
| 
 | ||||
|     return get_mesh_errors_list(obj_idx, vol_idx); | ||||
| } | ||||
| 
 | ||||
| void ObjectList::set_tooltip_for_item(const wxPoint& pt) | ||||
| { | ||||
|     wxDataViewItem item; | ||||
|  | @ -200,40 +270,24 @@ void ObjectList::set_tooltip_for_item(const wxPoint& pt) | |||
|     HitTest(pt, item, col); | ||||
|     if (!item) return; | ||||
| 
 | ||||
|     /* GetMainWindow() return window, associated with wxDataViewCtrl.
 | ||||
|      * And for this window we should to set tooltips. | ||||
|      * Just this->SetToolTip(tooltip) => has no effect. | ||||
|      */ | ||||
| 
 | ||||
|     if (col->GetTitle() == " " && GetSelectedItemsCount()<2) | ||||
|         GetMainWindow()->SetToolTip(_(L("Right button click the icon to change the object settings"))); | ||||
|     else if (col->GetTitle() == _("Name") && | ||||
|         m_objects_model->GetBitmap(item).GetRefData() == m_bmp_manifold_warning.bmp().GetRefData()) { | ||||
|         int obj_idx = m_objects_model->GetIdByItem(item); | ||||
|         auto& stats = (*m_objects)[obj_idx]->volumes[0]->mesh.stl.stats; | ||||
|         int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + | ||||
|             stats.facets_added + stats.facets_reversed + stats.backwards_edges; | ||||
| 
 | ||||
|         wxString tooltip = wxString::Format(_(L("Auto-repaired (%d errors):\n")), errors); | ||||
| 
 | ||||
|         std::map<std::string, int> error_msg; | ||||
|         error_msg[L("degenerate facets")] = stats.degenerate_facets; | ||||
|         error_msg[L("edges fixed")] = stats.edges_fixed; | ||||
|         error_msg[L("facets removed")] = stats.facets_removed; | ||||
|         error_msg[L("facets added")] = stats.facets_added; | ||||
|         error_msg[L("facets reversed")] = stats.facets_reversed; | ||||
|         error_msg[L("backwards edges")] = stats.backwards_edges; | ||||
| 
 | ||||
|         for (auto error : error_msg) | ||||
|         { | ||||
|             if (error.second > 0) | ||||
|                 tooltip += wxString::Format(_("\t%d %s\n"), error.second, error.first); | ||||
|     else if (col->GetTitle() == _("Name")) | ||||
|     { | ||||
| #ifdef __WXMSW__ | ||||
|         if (pt.x < 2 * wxGetApp().em_unit() || pt.x > 4 * wxGetApp().em_unit()) { | ||||
|             GetMainWindow()->SetToolTip(""); // hide tooltip
 | ||||
|             return; | ||||
|         } | ||||
| // OR
 | ||||
| //             tooltip += wxString::Format(_(L("%d degenerate facets, %d edges fixed, %d facets removed, "
 | ||||
| //                                             "%d facets added, %d facets reversed, %d backwards edges")),
 | ||||
| //                                             stats.degenerate_facets, stats.edges_fixed, stats.facets_removed,
 | ||||
| //                                             stats.facets_added, stats.facets_reversed, stats.backwards_edges);
 | ||||
| 
 | ||||
|         if (is_windows10()) | ||||
|             tooltip += _(L("Right button click the icon to fix STL through Netfabb")); | ||||
| 
 | ||||
|         GetMainWindow()->SetToolTip(tooltip); | ||||
| #endif //__WXMSW__
 | ||||
|         int obj_idx, vol_idx; | ||||
|         get_selected_item_indexes(obj_idx, vol_idx, item); | ||||
|         GetMainWindow()->SetToolTip(get_mesh_errors_list(obj_idx, vol_idx)); | ||||
|     } | ||||
|     else | ||||
|         GetMainWindow()->SetToolTip(""); // hide tooltip
 | ||||
|  | @ -395,8 +449,8 @@ void ObjectList::update_name_in_model(const wxDataViewItem& item) const | |||
| 
 | ||||
| void ObjectList::init_icons() | ||||
| { | ||||
|     m_bmp_modifiermesh      = ScalableBitmap(nullptr, "add_modifier");    // Add part 
 | ||||
|     m_bmp_solidmesh         = ScalableBitmap(nullptr, "add_part");        // Add modifier 
 | ||||
|     m_bmp_solidmesh         = ScalableBitmap(nullptr, "add_part");        // Add part 
 | ||||
|     m_bmp_modifiermesh      = ScalableBitmap(nullptr, "add_modifier");    // Add modifier 
 | ||||
|     m_bmp_support_enforcer  = ScalableBitmap(nullptr, "support_enforcer");// Add support enforcer 
 | ||||
|     m_bmp_support_blocker   = ScalableBitmap(nullptr, "support_blocker"); // Add support blocker  
 | ||||
| 
 | ||||
|  | @ -412,6 +466,8 @@ void ObjectList::init_icons() | |||
| 
 | ||||
|     // init icon for manifold warning
 | ||||
|     m_bmp_manifold_warning  = ScalableBitmap(nullptr, "exclamation"); | ||||
|     // Set warning bitmap for the model
 | ||||
|     m_objects_model->SetWarningBitmap(&m_bmp_manifold_warning.bmp()); | ||||
| 
 | ||||
|     // init bitmap for "Split to sub-objects" context menu
 | ||||
|     m_bmp_split             = ScalableBitmap(nullptr, "split_parts_SMALL"); | ||||
|  | @ -425,8 +481,8 @@ void ObjectList::rescale_icons() | |||
|     m_bmp_vector.clear(); | ||||
|     m_bmp_vector.reserve(4); // bitmaps for different types of parts 
 | ||||
|     for (ScalableBitmap* bitmap : std::vector<ScalableBitmap*> { | ||||
|                                     &m_bmp_modifiermesh,         // Add part
 | ||||
|                                     &m_bmp_solidmesh,             // Add modifier
 | ||||
|                                     &m_bmp_solidmesh,            // Add part
 | ||||
|                                     &m_bmp_modifiermesh,         // Add modifier
 | ||||
|                                     &m_bmp_support_enforcer,     // Add support enforcer
 | ||||
|                                     &m_bmp_support_blocker })    // Add support blocker                                                           
 | ||||
|     { | ||||
|  | @ -437,6 +493,9 @@ void ObjectList::rescale_icons() | |||
|     m_objects_model->SetVolumeBitmaps(m_bmp_vector); | ||||
| 
 | ||||
|     m_bmp_manifold_warning.msw_rescale(); | ||||
|     // Set warning bitmap for the model
 | ||||
|     m_objects_model->SetWarningBitmap(&m_bmp_manifold_warning.bmp()); | ||||
| 
 | ||||
|     m_bmp_split.msw_rescale(); | ||||
|     m_bmp_cog.msw_rescale(); | ||||
| 
 | ||||
|  | @ -498,7 +557,8 @@ void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& vol | |||
| 
 | ||||
|     for (const ModelVolume* volume : volumes) | ||||
|     { | ||||
|         auto vol_item = m_objects_model->AddVolumeChild(object_item, volume->name, volume->type(), | ||||
|         const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(object_item, volume->name, volume->type(),  | ||||
|             volume->get_mesh_errors_count()>0 , | ||||
|             volume->config.has("extruder") ? volume->config.option<ConfigOptionInt>("extruder")->value : 0); | ||||
|         auto opt_keys = volume->config.keys(); | ||||
|         if (!opt_keys.empty() && !((opt_keys.size() == 1) && (opt_keys[0] == "extruder"))) | ||||
|  | @ -574,10 +634,13 @@ void ObjectList::OnContextMenu(wxDataViewEvent&) | |||
| 
 | ||||
|     if (title == " ") | ||||
|         show_context_menu(); | ||||
|     else if (title == _("Name") && pt.x >15 && | ||||
|              m_objects_model->GetBitmap(item).GetRefData() == m_bmp_manifold_warning.bmp().GetRefData()) | ||||
|     else if (title == _("Name")) | ||||
|     { | ||||
|         if (is_windows10()) | ||||
|         int obj_idx, vol_idx; | ||||
|         get_selected_item_indexes(obj_idx, vol_idx, item); | ||||
| 
 | ||||
|         if (is_windows10() && get_mesh_errors_count(obj_idx, vol_idx) > 0 &&  | ||||
|             pt.x > 2*wxGetApp().em_unit() && pt.x < 4*wxGetApp().em_unit() ) | ||||
|             fix_through_netfabb(); | ||||
|     } | ||||
| 
 | ||||
|  | @ -937,6 +1000,7 @@ void ObjectList::get_freq_settings_choice(const wxString& bundle_name) | |||
| { | ||||
|     const std::vector<std::string>& options = get_options_for_bundle(bundle_name); | ||||
| 
 | ||||
|     assert(m_config); | ||||
|     auto opt_keys = m_config->keys(); | ||||
| 
 | ||||
|     const DynamicPrintConfig& from_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; | ||||
|  | @ -966,6 +1030,10 @@ void ObjectList::update_settings_item() | |||
|         const auto settings_item = m_objects_model->IsSettingsItem(item) ? item : m_objects_model->GetSettingsItem(item); | ||||
|         select_item(settings_item ? settings_item : | ||||
|             m_objects_model->AddSettingsChild(item)); | ||||
| 
 | ||||
|         // update object selection on Plater
 | ||||
|         if (!m_prevent_canvas_selection_update) | ||||
|             update_selections_on_canvas(); | ||||
|     } | ||||
|     else { | ||||
|         auto panel = wxGetApp().sidebar().scrolled_panel(); | ||||
|  | @ -1135,19 +1203,21 @@ void ObjectList::append_menu_items_osx(wxMenu* menu) | |||
|     menu->AppendSeparator(); | ||||
| } | ||||
| 
 | ||||
| void ObjectList::append_menu_item_fix_through_netfabb(wxMenu* menu) | ||||
| wxMenuItem* ObjectList::append_menu_item_fix_through_netfabb(wxMenu* menu) | ||||
| { | ||||
|     if (!is_windows10()) | ||||
|         return; | ||||
|     append_menu_item(menu, wxID_ANY, _(L("Fix through the Netfabb")), "", | ||||
|         return nullptr; | ||||
|     wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _(L("Fix through the Netfabb")), "", | ||||
|         [this](wxCommandEvent&) { fix_through_netfabb(); }, "", menu); | ||||
|     menu->AppendSeparator(); | ||||
| 
 | ||||
|     return menu_item; | ||||
| } | ||||
| 
 | ||||
| void ObjectList::append_menu_item_export_stl(wxMenu* menu) const  | ||||
| { | ||||
|     append_menu_item(menu, wxID_ANY, _(L("Export as STL")) + dots, "", | ||||
|         [](wxCommandEvent&) { wxGetApp().plater()->export_stl(true); }, "", menu); | ||||
|         [](wxCommandEvent&) { wxGetApp().plater()->export_stl(false, true); }, "", menu); | ||||
|     menu->AppendSeparator(); | ||||
| } | ||||
| 
 | ||||
|  | @ -1327,21 +1397,23 @@ void ObjectList::load_subobject(ModelVolumeType type) | |||
|     int obj_idx = m_objects_model->GetIdByItem(item); | ||||
| 
 | ||||
|     if (obj_idx < 0) return; | ||||
|     wxArrayString part_names; | ||||
|     load_part((*m_objects)[obj_idx], part_names, type); | ||||
| 
 | ||||
|     std::vector<std::pair<wxString, bool>> volumes_info; | ||||
|     load_part((*m_objects)[obj_idx], volumes_info, type); | ||||
| 
 | ||||
| 
 | ||||
|     changed_object(obj_idx); | ||||
| 
 | ||||
|     for (int i = 0; i < part_names.size(); ++i) { | ||||
|         const wxDataViewItem sel_item = m_objects_model->AddVolumeChild(item, part_names.Item(i), type); | ||||
| 
 | ||||
|         if (i == part_names.size() - 1) | ||||
|             select_item(sel_item); | ||||
|     } | ||||
|     wxDataViewItem sel_item; | ||||
|     for (const auto& volume : volumes_info ) | ||||
|         sel_item = m_objects_model->AddVolumeChild(item, volume.first, type, volume.second); | ||||
|          | ||||
|     if (sel_item) | ||||
|         select_item(sel_item); | ||||
| } | ||||
| 
 | ||||
| void ObjectList::load_part( ModelObject* model_object, | ||||
|                             wxArrayString& part_names,  | ||||
|                             std::vector<std::pair<wxString, bool>> &volumes_info, | ||||
|                             ModelVolumeType type) | ||||
| { | ||||
|     wxWindow* parent = wxGetApp().tab_panel()->GetPage(0); | ||||
|  | @ -1377,7 +1449,7 @@ void ObjectList::load_part( ModelObject* model_object, | |||
|                 new_volume->set_type(type); | ||||
|                 new_volume->name = boost::filesystem::path(input_file).filename().string(); | ||||
| 
 | ||||
|                 part_names.Add(from_u8(new_volume->name)); | ||||
|                 volumes_info.push_back(std::make_pair(from_u8(new_volume->name), new_volume->get_mesh_errors_count()>0)); | ||||
| 
 | ||||
|                 // set a default extruder value, since user can't add it manually
 | ||||
|                 new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); | ||||
|  | @ -1533,7 +1605,8 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode | |||
|     changed_object(obj_idx); | ||||
| 
 | ||||
|     const auto object_item = m_objects_model->GetTopParent(GetSelection()); | ||||
|     select_item(m_objects_model->AddVolumeChild(object_item, name, type)); | ||||
|     select_item(m_objects_model->AddVolumeChild(object_item, name, type,  | ||||
|         new_volume->get_mesh_errors_count()>0)); | ||||
| #ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME
 | ||||
|     selection_changed(); | ||||
| #endif //no __WXOSX__ //__WXMSW__
 | ||||
|  | @ -1565,6 +1638,10 @@ void ObjectList::del_subobject_item(wxDataViewItem& item) | |||
|     else if (!del_subobject_from_object(obj_idx, idx, type)) | ||||
|         return; | ||||
| 
 | ||||
|     // If last volume item with warning was deleted, unmark object item
 | ||||
|     if (type == itVolume && (*m_objects)[obj_idx]->get_mesh_errors_count() == 0) | ||||
|         m_objects_model->DeleteWarningIcon(m_objects_model->GetParent(item)); | ||||
| 
 | ||||
|     m_objects_model->Delete(item); | ||||
| } | ||||
| 
 | ||||
|  | @ -1671,18 +1748,18 @@ void ObjectList::split() | |||
|     else | ||||
|         parent = item; | ||||
| 
 | ||||
|     for (auto id = 0; id < model_object->volumes.size(); id++) { | ||||
|         const auto vol_item = m_objects_model->AddVolumeChild(parent, from_u8(model_object->volumes[id]->name), | ||||
|             model_object->volumes[id]->is_modifier() ? | ||||
|             ModelVolumeType::PARAMETER_MODIFIER : ModelVolumeType::MODEL_PART, | ||||
|             model_object->volumes[id]->config.has("extruder") ? | ||||
|             model_object->volumes[id]->config.option<ConfigOptionInt>("extruder")->value : 0, | ||||
|     for (const ModelVolume* volume : model_object->volumes) { | ||||
|         const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(parent, from_u8(volume->name), | ||||
|             volume->is_modifier() ? ModelVolumeType::PARAMETER_MODIFIER : ModelVolumeType::MODEL_PART, | ||||
|             volume->get_mesh_errors_count()>0, | ||||
|             volume->config.has("extruder") ? | ||||
|             volume->config.option<ConfigOptionInt>("extruder")->value : 0, | ||||
|             false); | ||||
|         // add settings to the part, if it has those
 | ||||
|         auto opt_keys = model_object->volumes[id]->config.keys(); | ||||
|         auto opt_keys = volume->config.keys(); | ||||
|         if ( !(opt_keys.size() == 1 && opt_keys[0] == "extruder") ) { | ||||
|             select_item(m_objects_model->AddSettingsChild(vol_item)); | ||||
|             /*Collapse*/Expand(vol_item); | ||||
|             Expand(vol_item); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -1755,6 +1832,7 @@ void ObjectList::changed_object(const int obj_idx/* = -1*/) const | |||
| void ObjectList::part_selection_changed() | ||||
| { | ||||
|     int obj_idx = -1; | ||||
|     int volume_id = -1; | ||||
|     m_config = nullptr; | ||||
|     wxString og_name = wxEmptyString; | ||||
| 
 | ||||
|  | @ -1801,7 +1879,7 @@ void ObjectList::part_selection_changed() | |||
|                 } | ||||
|                 else if (m_objects_model->GetItemType(item) == itVolume) { | ||||
|                     og_name = _(L("Part manipulation")); | ||||
|                     const auto volume_id = m_objects_model->GetVolumeIdByItem(item); | ||||
|                     volume_id = m_objects_model->GetVolumeIdByItem(item); | ||||
|                     m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; | ||||
|                     update_and_show_manipulations = true; | ||||
|                 } | ||||
|  | @ -1821,7 +1899,11 @@ void ObjectList::part_selection_changed() | |||
| 
 | ||||
|     if (update_and_show_manipulations) { | ||||
|         wxGetApp().obj_manipul()->get_og()->set_name(" " + og_name + " "); | ||||
|         wxGetApp().obj_manipul()->get_og()->set_value("object_name", m_objects_model->GetName(GetSelection())); | ||||
| 
 | ||||
|         if (item) { | ||||
|             wxGetApp().obj_manipul()->get_og()->set_value("object_name", m_objects_model->GetName(item)); | ||||
|             wxGetApp().obj_manipul()->update_warning_icon_state(get_mesh_errors_list(obj_idx, volume_id)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (update_and_show_settings) | ||||
|  | @ -1841,34 +1923,26 @@ void ObjectList::part_selection_changed() | |||
| void ObjectList::add_object_to_list(size_t obj_idx) | ||||
| { | ||||
|     auto model_object = (*m_objects)[obj_idx]; | ||||
|     wxString item_name = from_u8(model_object->name); | ||||
|     const wxString& item_name = from_u8(model_object->name); | ||||
|     const auto item = m_objects_model->Add(item_name, | ||||
|                       !model_object->config.has("extruder") ? 0 : | ||||
|                       model_object->config.option<ConfigOptionInt>("extruder")->value); | ||||
| 
 | ||||
|     // Add error icon if detected auto-repaire
 | ||||
|     auto stats = model_object->volumes[0]->mesh.stl.stats; | ||||
|     int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + | ||||
|         stats.facets_added + stats.facets_reversed + stats.backwards_edges; | ||||
|     if (errors > 0) { | ||||
|         wxVariant variant; | ||||
|         variant << DataViewBitmapText(item_name, m_bmp_manifold_warning.bmp()); | ||||
|         m_objects_model->SetValue(variant, item, 0); | ||||
|     } | ||||
|                       model_object->config.option<ConfigOptionInt>("extruder")->value, | ||||
|                       get_mesh_errors_count(obj_idx) > 0); | ||||
| 
 | ||||
|     // add volumes to the object
 | ||||
|     if (model_object->volumes.size() > 1) { | ||||
|         for (auto id = 0; id < model_object->volumes.size(); id++) { | ||||
|             auto vol_item = m_objects_model->AddVolumeChild(item, | ||||
|                 from_u8(model_object->volumes[id]->name), | ||||
|                 model_object->volumes[id]->type(), | ||||
|                 !model_object->volumes[id]->config.has("extruder") ? 0 : | ||||
|                 model_object->volumes[id]->config.option<ConfigOptionInt>("extruder")->value, | ||||
|         for (const ModelVolume* volume : model_object->volumes) { | ||||
|             const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(item, | ||||
|                 from_u8(volume->name), | ||||
|                 volume->type(), | ||||
|                 volume->get_mesh_errors_count()>0, | ||||
|                 !volume->config.has("extruder") ? 0 : | ||||
|                 volume->config.option<ConfigOptionInt>("extruder")->value, | ||||
|                 false); | ||||
|             auto opt_keys = model_object->volumes[id]->config.keys(); | ||||
|             auto opt_keys = volume->config.keys(); | ||||
|             if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) { | ||||
|                 select_item(m_objects_model->AddSettingsChild(vol_item)); | ||||
|                 /*Collapse*/Expand(vol_item); | ||||
|                 Expand(vol_item); | ||||
|             } | ||||
|         } | ||||
|         Expand(item); | ||||
|  | @ -1882,7 +1956,7 @@ void ObjectList::add_object_to_list(size_t obj_idx) | |||
|     auto opt_keys = model_object->config.keys(); | ||||
|     if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) { | ||||
|         select_item(m_objects_model->AddSettingsChild(item)); | ||||
|         /*Collapse*/Expand(item); | ||||
|         Expand(item); | ||||
|     } | ||||
| 
 | ||||
| #ifndef __WXOSX__  | ||||
|  | @ -2076,6 +2150,11 @@ void ObjectList::update_selections() | |||
|             if (m_objects_model->GetVolumeIdByItem(m_objects_model->GetParent(item)) == gl_vol->volume_idx()) | ||||
|                 return; | ||||
|         } | ||||
| 
 | ||||
|         // but if there is selected only one of several instances by context menu,
 | ||||
|         // then select this instance in ObjectList
 | ||||
|         if (selection.is_single_full_instance()) | ||||
|             sels.Add(m_objects_model->GetItemByInstanceId(selection.get_object_idx(), selection.get_instance_idx())); | ||||
|     } | ||||
|     else if (selection.is_single_full_object() || selection.is_multiple_full_object()) | ||||
|     { | ||||
|  | @ -2666,18 +2745,10 @@ void ObjectList::rename_item() | |||
|     update_name_in_model(item); | ||||
| } | ||||
| 
 | ||||
| void ObjectList::fix_through_netfabb() const  | ||||
| void ObjectList::fix_through_netfabb()  | ||||
| { | ||||
|     const wxDataViewItem item = GetSelection(); | ||||
|     if (!item) | ||||
|         return; | ||||
|      | ||||
|     const ItemType type = m_objects_model->GetItemType(item); | ||||
| 
 | ||||
|     const int obj_idx = type & itObject ? m_objects_model->GetIdByItem(item) : | ||||
|                         type & itVolume ? m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)) : -1; | ||||
| 
 | ||||
|     const int vol_idx = type & itVolume ? m_objects_model->GetVolumeIdByItem(item) : -1; | ||||
|     int obj_idx, vol_idx; | ||||
|     get_selected_item_indexes(obj_idx, vol_idx); | ||||
| 
 | ||||
|     wxGetApp().plater()->fix_through_netfabb(obj_idx, vol_idx); | ||||
|      | ||||
|  | @ -2691,28 +2762,27 @@ void ObjectList::update_item_error_icon(const int obj_idx, const int vol_idx) co | |||
|     if (!item) | ||||
|         return; | ||||
| 
 | ||||
|     auto model_object = (*m_objects)[obj_idx]; | ||||
| 
 | ||||
|     const stl_stats& stats = model_object->volumes[vol_idx<0 ? 0 : vol_idx]->mesh.stl.stats; | ||||
|     const int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + | ||||
|                        stats.facets_added + stats.facets_reversed + stats.backwards_edges; | ||||
| 
 | ||||
|     if (errors == 0) { | ||||
|         // delete Error_icon if all errors are fixed
 | ||||
|         wxVariant variant; | ||||
|         variant << DataViewBitmapText(from_u8(model_object->name), wxNullBitmap); | ||||
|         m_objects_model->SetValue(variant, item, 0); | ||||
|     if (get_mesh_errors_count(obj_idx, vol_idx) == 0) | ||||
|     { | ||||
|         // if whole object has no errors more,
 | ||||
|         if (get_mesh_errors_count(obj_idx) == 0) | ||||
|             // unmark all items in the object
 | ||||
|             m_objects_model->DeleteWarningIcon(vol_idx >= 0 ? m_objects_model->GetParent(item) : item, true); | ||||
|         else | ||||
|             // unmark fixed item only
 | ||||
|             m_objects_model->DeleteWarningIcon(item); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void ObjectList::msw_rescale() | ||||
| { | ||||
|     const int em = wxGetApp().em_unit(); | ||||
|     // update min size !!! A width of control shouldn't be a wxDefaultCoord
 | ||||
|     SetMinSize(wxSize(1, 15 * wxGetApp().em_unit())); | ||||
|     SetMinSize(wxSize(1, 15 * em)); | ||||
| 
 | ||||
|     GetColumn(0)->SetWidth(19 * wxGetApp().em_unit()); | ||||
|     GetColumn(1)->SetWidth(8 * wxGetApp().em_unit()); | ||||
|     GetColumn(2)->SetWidth(int(2 * wxGetApp().em_unit())); | ||||
|     GetColumn(0)->SetWidth(19 * em); | ||||
|     GetColumn(1)->SetWidth( 8 * em); | ||||
|     GetColumn(2)->SetWidth( 2 * em); | ||||
| 
 | ||||
|     // rescale all icons, used by ObjectList
 | ||||
|     rescale_icons(); | ||||
|  |  | |||
|  | @ -175,6 +175,16 @@ public: | |||
|     void                init_icons(); | ||||
|     void                rescale_icons(); | ||||
| 
 | ||||
|     // Get obj_idx and vol_idx values for the selected (by default) or an adjusted item
 | ||||
|     void                get_selected_item_indexes(int& obj_idx, int& vol_idx, const wxDataViewItem& item = wxDataViewItem(0)); | ||||
|     // Get count of errors in the mesh
 | ||||
|     int                 get_mesh_errors_count(const int obj_idx, const int vol_idx = -1) const; | ||||
|     /* Get list of errors in the mesh. Return value is a string, used for the tooltip
 | ||||
|      * Function without parameters is for a call from Manipulation panel,  | ||||
|      * when we don't know parameters of selected item  | ||||
|      */ | ||||
|     wxString            get_mesh_errors_list(const int obj_idx, const int vol_idx = -1) const; | ||||
|     wxString            get_mesh_errors_list(); | ||||
|     void                set_tooltip_for_item(const wxPoint& pt); | ||||
| 
 | ||||
|     void                selection_changed(); | ||||
|  | @ -192,7 +202,7 @@ public: | |||
|     wxMenuItem*         append_menu_item_change_type(wxMenu* menu); | ||||
|     wxMenuItem*         append_menu_item_instance_to_object(wxMenu* menu); | ||||
|     void                append_menu_items_osx(wxMenu* menu); | ||||
|     void                append_menu_item_fix_through_netfabb(wxMenu* menu); | ||||
|     wxMenuItem*         append_menu_item_fix_through_netfabb(wxMenu* menu); | ||||
|     void                append_menu_item_export_stl(wxMenu* menu) const ; | ||||
|     void                append_menu_item_change_extruder(wxMenu* menu) const; | ||||
|     void                append_menu_item_delete(wxMenu* menu); | ||||
|  | @ -206,7 +216,7 @@ public: | |||
|     void                update_opt_keys(t_config_option_keys& t_optopt_keys); | ||||
| 
 | ||||
|     void                load_subobject(ModelVolumeType type); | ||||
|     void                load_part(ModelObject* model_object, wxArrayString& part_names, ModelVolumeType type); | ||||
|     void                load_part(ModelObject* model_object, std::vector<std::pair<wxString, bool>> &volumes_info, ModelVolumeType type); | ||||
| 	void                load_generic_subobject(const std::string& type_name, const ModelVolumeType type); | ||||
|     void                del_object(const int obj_idx); | ||||
|     void                del_subobject_item(wxDataViewItem& item); | ||||
|  | @ -280,7 +290,7 @@ public: | |||
|     void instances_to_separated_objects(const int obj_idx); | ||||
|     void split_instances(); | ||||
|     void rename_item(); | ||||
|     void fix_through_netfabb() const; | ||||
|     void fix_through_netfabb(); | ||||
|     void update_item_error_icon(const int obj_idx, int vol_idx) const ; | ||||
| 
 | ||||
|     void paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& volumes); | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ | |||
| #include "Selection.hpp" | ||||
| 
 | ||||
| #include <boost/algorithm/string.hpp> | ||||
| #include "slic3r/Utils/FixModelByWin10.hpp" | ||||
| 
 | ||||
| namespace Slic3r | ||||
| { | ||||
|  | @ -22,6 +23,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : | |||
|     , m_focused_option("") | ||||
| #endif // __APPLE__
 | ||||
| { | ||||
|     m_manifold_warning_bmp = ScalableBitmap(parent, "exclamation"); | ||||
|     m_og->set_name(_(L("Object Manipulation"))); | ||||
|     m_og->label_width = 12;//125;
 | ||||
|     m_og->set_grid_vgap(5); | ||||
|  | @ -42,17 +44,50 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : | |||
|     ConfigOptionDef def; | ||||
| 
 | ||||
|     // Objects(sub-objects) name
 | ||||
|     def.label = L("Name"); | ||||
| //     def.label = L("Name");
 | ||||
| //     def.gui_type = "legend";
 | ||||
| //     def.tooltip = L("Object name");
 | ||||
| //     def.width = 21 * wxGetApp().em_unit();
 | ||||
| //     def.default_value = new ConfigOptionString{ " " };
 | ||||
| //     m_og->append_single_option_line(Option(def, "object_name"));
 | ||||
| 
 | ||||
|     Line line = Line{ "Name", "Object name" }; | ||||
| 
 | ||||
|     auto manifold_warning_icon = [this](wxWindow* parent) { | ||||
|         m_fix_throught_netfab_bitmap = new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap); | ||||
|         auto sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|         sizer->Add(m_fix_throught_netfab_bitmap); | ||||
| 
 | ||||
|         if (is_windows10()) | ||||
|             m_fix_throught_netfab_bitmap->Bind(wxEVT_CONTEXT_MENU, [this](wxCommandEvent &e) | ||||
|             { | ||||
|                 // if object/sub-object has no errors
 | ||||
|                 if (m_fix_throught_netfab_bitmap->GetBitmap().GetRefData() == wxNullBitmap.GetRefData()) | ||||
|                     return; | ||||
| 
 | ||||
|                 wxGetApp().obj_list()->fix_through_netfabb(); | ||||
|                 update_warning_icon_state(wxGetApp().obj_list()->get_mesh_errors_list()); | ||||
|             }); | ||||
| 
 | ||||
|         return sizer; | ||||
|     }; | ||||
| 
 | ||||
|     line.append_widget(manifold_warning_icon); | ||||
|     def.label = ""; | ||||
|     def.gui_type = "legend"; | ||||
|     def.tooltip = L("Object name"); | ||||
|     def.width = 21; | ||||
| #ifdef __APPLE__ | ||||
|     def.width = 19; | ||||
| #endif | ||||
|     def.default_value = new ConfigOptionString{ " " }; | ||||
|     m_og->append_single_option_line(Option(def, "object_name")); | ||||
|     line.append_option(Option(def, "object_name")); | ||||
|     m_og->append_line(line); | ||||
| 
 | ||||
|     const int field_width = 5; | ||||
| 
 | ||||
|     // Legend for object modification
 | ||||
|     auto line = Line{ "", "" }; | ||||
|     line = Line{ "", "" }; | ||||
|     def.label = ""; | ||||
|     def.type = coString; | ||||
|     def.width = field_width/*50*/; | ||||
|  | @ -334,6 +369,12 @@ void ObjectManipulation::emulate_kill_focus() | |||
| } | ||||
| #endif // __APPLE__
 | ||||
| 
 | ||||
| void ObjectManipulation::update_warning_icon_state(const wxString& tooltip) | ||||
| { | ||||
|     m_fix_throught_netfab_bitmap->SetBitmap(tooltip.IsEmpty() ? wxNullBitmap : m_manifold_warning_bmp.bmp()); | ||||
|     m_fix_throught_netfab_bitmap->SetToolTip(tooltip); | ||||
| } | ||||
| 
 | ||||
| void ObjectManipulation::reset_settings_value() | ||||
| { | ||||
|     m_new_position = Vec3d::Zero(); | ||||
|  | @ -519,5 +560,13 @@ void ObjectManipulation::on_fill_empty_value(const std::string& opt_key) | |||
|     m_og->set_value(opt_key, double_to_string(value)); | ||||
| } | ||||
| 
 | ||||
| void ObjectManipulation::msw_rescale() | ||||
| { | ||||
|     m_manifold_warning_bmp.msw_rescale(); | ||||
|     m_fix_throught_netfab_bitmap->SetBitmap(m_manifold_warning_bmp.bmp()); | ||||
| 
 | ||||
|     get_og()->msw_rescale(); | ||||
| } | ||||
| 
 | ||||
| } //namespace GUI
 | ||||
| } //namespace Slic3r 
 | ||||
|  | @ -8,6 +8,7 @@ | |||
| 
 | ||||
| class wxStaticText; | ||||
| class LockButton; | ||||
| class wxStaticBitmap; | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace GUI { | ||||
|  | @ -78,6 +79,9 @@ class ObjectManipulation : public OG_Settings | |||
|     bool            m_uniform_scale {true}; | ||||
|     LockButton*     m_lock_bnt{ nullptr }; | ||||
| 
 | ||||
|     ScalableBitmap  m_manifold_warning_bmp; | ||||
|     wxStaticBitmap* m_fix_throught_netfab_bitmap; | ||||
| 
 | ||||
| #ifndef __APPLE__ | ||||
|     // Currently focused option name (empty if none)
 | ||||
|     std::string     m_focused_option; | ||||
|  | @ -107,6 +111,9 @@ public: | |||
|     void emulate_kill_focus(); | ||||
| #endif // __APPLE__
 | ||||
| 
 | ||||
|     void update_warning_icon_state(const wxString& tooltip); | ||||
|     void msw_rescale(); | ||||
| 
 | ||||
| private: | ||||
|     void reset_settings_value(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -121,7 +121,7 @@ void ObjectSettings::update_settings_list() | |||
|                 if (cat.second.size() == 1 && cat.second[0] == "extruder") | ||||
|                     continue; | ||||
| 
 | ||||
|                 auto optgroup = std::make_shared<ConfigOptionsGroup>(m_og->ctrl_parent(), cat.first, config, false, extra_column); | ||||
|                 auto optgroup = std::make_shared<ConfigOptionsGroup>(m_og->ctrl_parent(), _(cat.first), config, false, extra_column); | ||||
|                 optgroup->label_width = 15; | ||||
|                 optgroup->sidetext_width = 5.5; | ||||
| 
 | ||||
|  |  | |||
|  | @ -64,6 +64,7 @@ template<class F> typename F::FN winapi_get_function(const wchar_t *dll, const c | |||
| } | ||||
| #endif | ||||
| 
 | ||||
| // If called with nullptr, a DPI for the primary monitor is returned.
 | ||||
| int get_dpi_for_window(wxWindow *window) | ||||
| { | ||||
| #ifdef _WIN32 | ||||
|  | @ -82,7 +83,8 @@ int get_dpi_for_window(wxWindow *window) | |||
|     static auto GetDpiForWindow_fn = winapi_get_function<GetDpiForWindow_t>(L"User32.dll", "GetDpiForWindow"); | ||||
|     static auto GetDpiForMonitor_fn = winapi_get_function<GetDpiForMonitor_t>(L"Shcore.dll", "GetDpiForMonitor"); | ||||
| 
 | ||||
|     const HWND hwnd = window->GetHandle(); | ||||
| 	// Desktop Window is the window of the primary monitor.
 | ||||
| 	const HWND hwnd = (window == nullptr) ? ::GetDesktopWindow() : window->GetHandle(); | ||||
| 
 | ||||
|     if (GetDpiForWindow_fn != nullptr) { | ||||
|         // We're on Windows 10, we have per-screen DPI settings
 | ||||
|  |  | |||
|  | @ -59,13 +59,16 @@ public: | |||
|         : P(parent, id, title, pos, size, style, name) | ||||
|     { | ||||
|         m_scale_factor = (float)get_dpi_for_window(this) / (float)DPI_DEFAULT; | ||||
|         m_normal_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); | ||||
|         m_prev_scale_factor = m_scale_factor; | ||||
| 		float scale_primary_display = (float)get_dpi_for_window(nullptr) / (float)DPI_DEFAULT; | ||||
| 		m_normal_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); | ||||
| 		if (std::abs(m_scale_factor - scale_primary_display) > 1e-6) | ||||
| 			m_normal_font = m_normal_font.Scale(m_scale_factor / scale_primary_display); | ||||
| 
 | ||||
|         // An analog of em_unit value from GUI_App.
 | ||||
|         m_em_unit = std::max<size_t>(10, 10 * m_scale_factor); | ||||
| 
 | ||||
|         m_prev_scale_factor = m_scale_factor; | ||||
| 
 | ||||
|         recalc_font(); | ||||
| //        recalc_font();
 | ||||
| 
 | ||||
|         this->Bind(EVT_DPI_CHANGED, [this](const DpiChangedEvent &evt) { | ||||
|             m_scale_factor = (float)evt.dpi / (float)DPI_DEFAULT; | ||||
|  | @ -107,7 +110,7 @@ public: | |||
|     float   prev_scale_factor() const   { return m_prev_scale_factor; } | ||||
| 
 | ||||
|     int     em_unit() const             { return m_em_unit; } | ||||
|     int     font_size() const           { return m_font_size; } | ||||
| //    int     font_size() const           { return m_font_size; }
 | ||||
|     const wxFont& normal_font() const   { return m_normal_font; } | ||||
| 
 | ||||
| protected: | ||||
|  | @ -116,19 +119,19 @@ protected: | |||
| private: | ||||
|     float m_scale_factor; | ||||
|     int m_em_unit; | ||||
|     int m_font_size; | ||||
| //    int m_font_size;
 | ||||
| 
 | ||||
|     wxFont m_normal_font; | ||||
|     float m_prev_scale_factor; | ||||
|     bool  m_can_rescale{ true }; | ||||
| 
 | ||||
|     void recalc_font() | ||||
|     { | ||||
|         wxClientDC dc(this); | ||||
|         const auto metrics = dc.GetFontMetrics(); | ||||
|         m_font_size = metrics.height; | ||||
| //    void recalc_font()
 | ||||
| //    {
 | ||||
| //        wxClientDC dc(this);
 | ||||
| //        const auto metrics = dc.GetFontMetrics();
 | ||||
| //        m_font_size = metrics.height;
 | ||||
| //         m_em_unit = metrics.averageWidth;
 | ||||
|     } | ||||
| //    }
 | ||||
| 
 | ||||
|     // check if new scale is differ from previous
 | ||||
|     bool    is_new_scale_factor() const { return fabs(m_scale_factor - m_prev_scale_factor) > 0.001; } | ||||
|  |  | |||
|  | @ -100,7 +100,7 @@ void GLGizmoSlaSupports::on_render(const Selection& selection) const | |||
|     if (m_quadric != nullptr && selection.is_from_single_instance()) | ||||
|         render_points(selection, false); | ||||
| 
 | ||||
|     render_selection_rectangle(); | ||||
|     m_selection_rectangle.render(m_parent); | ||||
|     render_clipping_plane(selection); | ||||
| 
 | ||||
|     glsafe(::glDisable(GL_BLEND)); | ||||
|  | @ -240,52 +240,6 @@ void GLGizmoSlaSupports::render_clipping_plane(const Selection& selection) const | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| void GLGizmoSlaSupports::render_selection_rectangle() const | ||||
| { | ||||
|     if (m_selection_rectangle_status == srOff) | ||||
|         return; | ||||
| 
 | ||||
|     glsafe(::glLineWidth(1.5f)); | ||||
|     float render_color[3] = {0.f, 1.f, 0.f}; | ||||
|     if (m_selection_rectangle_status == srDeselect) { | ||||
|         render_color[0] = 1.f; | ||||
|         render_color[1] = 0.3f; | ||||
|         render_color[2] = 0.3f; | ||||
|     } | ||||
|     glsafe(::glColor3fv(render_color)); | ||||
| 
 | ||||
|     glsafe(::glPushAttrib(GL_TRANSFORM_BIT));   // remember current MatrixMode
 | ||||
| 
 | ||||
|     glsafe(::glMatrixMode(GL_MODELVIEW));       // cache modelview matrix and set to identity
 | ||||
|     glsafe(::glPushMatrix()); | ||||
|     glsafe(::glLoadIdentity()); | ||||
| 
 | ||||
|     glsafe(::glMatrixMode(GL_PROJECTION));      // cache projection matrix and set to identity
 | ||||
|     glsafe(::glPushMatrix()); | ||||
|     glsafe(::glLoadIdentity()); | ||||
| 
 | ||||
|     glsafe(::glOrtho(0.f, m_canvas_width, m_canvas_height, 0.f, -1.f, 1.f)); // set projection matrix so that world coords = window coords
 | ||||
| 
 | ||||
|     // render the selection  rectangle (window coordinates):
 | ||||
|     glsafe(::glPushAttrib(GL_ENABLE_BIT)); | ||||
|     glsafe(::glLineStipple(4, 0xAAAA)); | ||||
|     glsafe(::glEnable(GL_LINE_STIPPLE)); | ||||
| 
 | ||||
|     ::glBegin(GL_LINE_LOOP); | ||||
|     ::glVertex3f((GLfloat)m_selection_rectangle_start_corner(0), (GLfloat)m_selection_rectangle_start_corner(1), (GLfloat)0.5f); | ||||
|     ::glVertex3f((GLfloat)m_selection_rectangle_end_corner(0), (GLfloat)m_selection_rectangle_start_corner(1), (GLfloat)0.5f); | ||||
|     ::glVertex3f((GLfloat)m_selection_rectangle_end_corner(0), (GLfloat)m_selection_rectangle_end_corner(1), (GLfloat)0.5f); | ||||
|     ::glVertex3f((GLfloat)m_selection_rectangle_start_corner(0), (GLfloat)m_selection_rectangle_end_corner(1), (GLfloat)0.5f); | ||||
|     glsafe(::glEnd()); | ||||
|     glsafe(::glPopAttrib()); | ||||
| 
 | ||||
|     glsafe(::glPopMatrix());                // restore former projection matrix
 | ||||
|     glsafe(::glMatrixMode(GL_MODELVIEW)); | ||||
|     glsafe(::glPopMatrix());                // restore former modelview matrix
 | ||||
|     glsafe(::glPopAttrib());                // restore former MatrixMode
 | ||||
| } | ||||
| 
 | ||||
| void GLGizmoSlaSupports::on_render_for_picking(const Selection& selection) const | ||||
| { | ||||
|     glsafe(::glEnable(GL_DEPTH_TEST)); | ||||
|  | @ -513,11 +467,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous | |||
|         if (action == SLAGizmoEventType::LeftDown && (shift_down || alt_down || control_down)) { | ||||
|             if (m_hover_id == -1) { | ||||
|                 if (shift_down || alt_down) { | ||||
|                     m_selection_rectangle_status = shift_down ? srSelect : srDeselect; | ||||
|                     m_selection_rectangle_start_corner = mouse_position; | ||||
|                     m_selection_rectangle_end_corner = mouse_position; | ||||
|                     m_canvas_width = m_parent.get_canvas_size().get_width(); | ||||
|                     m_canvas_height = m_parent.get_canvas_size().get_height(); | ||||
|                     m_selection_rectangle.start_dragging(mouse_position, shift_down ? GLSelectionRectangle::Select : GLSelectionRectangle::Deselect); | ||||
|                 } | ||||
|             } | ||||
|             else { | ||||
|  | @ -533,7 +483,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous | |||
|         } | ||||
| 
 | ||||
|         // left down without selection rectangle - place point on the mesh:
 | ||||
|         if (action == SLAGizmoEventType::LeftDown && m_selection_rectangle_status == srOff && !shift_down) { | ||||
|         if (action == SLAGizmoEventType::LeftDown && !m_selection_rectangle.is_dragging() && !shift_down) { | ||||
|             // If any point is in hover state, this should initiate its move - return control back to GLCanvas:
 | ||||
|             if (m_hover_id != -1) | ||||
|                 return false; | ||||
|  | @ -558,38 +508,36 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous | |||
|         } | ||||
| 
 | ||||
|         // left up with selection rectangle - select points inside the rectangle:
 | ||||
|         if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp || action == SLAGizmoEventType::AltUp) && m_selection_rectangle_status != srOff) { | ||||
|             const Transform3d& instance_matrix = m_model_object->instances[m_active_instance]->get_transformation().get_matrix(); | ||||
|             const Camera& camera = m_parent.get_camera(); | ||||
|             const std::array<int, 4>& viewport = camera.get_viewport(); | ||||
|             const Transform3d& modelview_matrix = camera.get_view_matrix(); | ||||
|             const Transform3d& projection_matrix = camera.get_projection_matrix(); | ||||
|         if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp || action == SLAGizmoEventType::AltUp) && m_selection_rectangle.is_dragging()) { | ||||
|             // Is this a selection or deselection rectangle?
 | ||||
|             GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state(); | ||||
| 
 | ||||
|             // First collect positions of all the points in world coordinates.
 | ||||
|             const Transform3d& instance_matrix = m_model_object->instances[m_active_instance]->get_transformation().get_matrix(); | ||||
|             std::vector<Vec3d> points; | ||||
|             for (unsigned int i=0; i<m_editing_mode_cache.size(); ++i) { | ||||
|                 const sla::SupportPoint &support_point = m_editing_mode_cache[i].support_point; | ||||
|                 points.push_back(instance_matrix * support_point.pos.cast<double>()); | ||||
|                 points.back()(2) += m_z_shift; | ||||
|             } | ||||
|             // Now ask the rectangle which of the points are inside.
 | ||||
|             const Camera& camera = m_parent.get_camera(); | ||||
|             std::vector<unsigned int> selected_idxs = m_selection_rectangle.stop_dragging(m_parent, points); | ||||
| 
 | ||||
|             // we'll recover current look direction (in world coords) and transform it to model coords.
 | ||||
|             const Selection& selection = m_parent.get_selection(); | ||||
|             const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); | ||||
| 
 | ||||
|             // bounding box created from the rectangle corners - will take care of order of the corners
 | ||||
|             BoundingBox rectangle(Points{Point(m_selection_rectangle_start_corner.cast<int>()), Point(m_selection_rectangle_end_corner.cast<int>())}); | ||||
| 
 | ||||
|             const Transform3d& instance_matrix_no_translation_no_scaling = volume->get_instance_transformation().get_matrix(true,false,true); | ||||
| 
 | ||||
|             // we'll recover current look direction from the modelview matrix (in world coords)...
 | ||||
|             Vec3f direction_to_camera = camera.get_dir_forward().cast<float>(); | ||||
|             // ...and transform it to model coords.
 | ||||
|             Vec3f direction_to_camera_mesh = (instance_matrix_no_translation_no_scaling.inverse().cast<float>() * direction_to_camera).normalized().eval(); | ||||
|             Vec3f scaling = volume->get_instance_scaling_factor().cast<float>(); | ||||
|             direction_to_camera_mesh = Vec3f(direction_to_camera_mesh(0)*scaling(0), direction_to_camera_mesh(1)*scaling(1), direction_to_camera_mesh(2)*scaling(2)); | ||||
| 
 | ||||
|             // Iterate over all points, check if they're in the rectangle and if so, check that they are not obscured by the mesh:
 | ||||
|             for (unsigned int i=0; i<m_editing_mode_cache.size(); ++i) { | ||||
|             // Iterate over all points in the rectangle and check that they are neither clipped by the
 | ||||
|             // clipping plane nor obscured by the mesh.
 | ||||
|             for (const unsigned int i : selected_idxs) { | ||||
|                 const sla::SupportPoint &support_point = m_editing_mode_cache[i].support_point; | ||||
|                 Vec3f pos = instance_matrix.cast<float>() * support_point.pos; | ||||
|                 pos(2) += m_z_shift; | ||||
|                   GLdouble out_x, out_y, out_z; | ||||
|                   ::gluProject((GLdouble)pos(0), (GLdouble)pos(1), (GLdouble)pos(2), (GLdouble*)modelview_matrix.data(), (GLdouble*)projection_matrix.data(), (GLint*)viewport.data(), &out_x, &out_y, &out_z); | ||||
|                   out_y = m_canvas_height - out_y; | ||||
| 
 | ||||
|                 if (rectangle.contains(Point(out_x, out_y)) && !is_point_clipped(support_point.pos.cast<double>())) { | ||||
|                 if (!is_point_clipped(support_point.pos.cast<double>())) { | ||||
|                     bool is_obscured = false; | ||||
|                     // Cast a ray in the direction of the camera and look for intersection with the mesh:
 | ||||
|                     std::vector<igl::Hit> hits; | ||||
|  | @ -627,14 +575,13 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous | |||
|                     } | ||||
| 
 | ||||
|                     if (!is_obscured) { | ||||
|                         if (m_selection_rectangle_status == srDeselect) | ||||
|                         if (rectangle_status == GLSelectionRectangle::Deselect) | ||||
|                             unselect_point(i); | ||||
|                         else | ||||
|                             select_point(i); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             m_selection_rectangle_status = srOff; | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|  | @ -652,9 +599,8 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous | |||
|                 return true; // point has been placed and the button not released yet
 | ||||
|                              // this prevents GLCanvas from starting scene rotation
 | ||||
| 
 | ||||
|             if (m_selection_rectangle_status != srOff)  { | ||||
|                 m_selection_rectangle_end_corner = mouse_position; | ||||
|                 m_selection_rectangle_status = shift_down ? srSelect : srDeselect; | ||||
|             if (m_selection_rectangle.is_dragging())  { | ||||
|                 m_selection_rectangle.dragging(mouse_position); | ||||
|                 return true; | ||||
|             } | ||||
| 
 | ||||
|  | @ -977,7 +923,7 @@ RENDER_AGAIN: | |||
|     // Following is rendered in both editing and non-editing mode:
 | ||||
|     m_imgui->text(""); | ||||
|     if (m_clipping_plane_distance == 0.f) | ||||
|         m_imgui->text("Clipping of view: "); | ||||
|         m_imgui->text(_(L("Clipping of view:"))+ " "); | ||||
|     else { | ||||
|         if (m_imgui->button(_(L("Reset direction")))) { | ||||
|             wxGetApp().CallAfter([this](){ | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ | |||
| 
 | ||||
| #include "GLGizmoBase.hpp" | ||||
| #include "GLGizmos.hpp" | ||||
| #include "slic3r/GUI/GLSelectionRectangle.hpp" | ||||
| 
 | ||||
| // There is an L function in igl that would be overridden by our localization macro - let's undefine it...
 | ||||
| #undef L | ||||
|  | @ -67,13 +68,16 @@ public: | |||
|     void delete_selected_points(bool force = false); | ||||
|     ClippingPlane get_sla_clipping_plane() const; | ||||
| 
 | ||||
|     bool is_in_editing_mode() const { return m_editing_mode; } | ||||
|     bool is_selection_rectangle_dragging() const { return m_selection_rectangle.is_dragging(); } | ||||
| 
 | ||||
| private: | ||||
|     bool on_init(); | ||||
|     void on_update(const UpdateData& data, const Selection& selection); | ||||
|     virtual void on_render(const Selection& selection) const; | ||||
|     virtual void on_render_for_picking(const Selection& selection) const; | ||||
| 
 | ||||
|     void render_selection_rectangle() const; | ||||
|     //void render_selection_rectangle() const;
 | ||||
|     void render_points(const Selection& selection, bool picking = false) const; | ||||
|     void render_clipping_plane(const Selection& selection) const; | ||||
|     bool is_mesh_update_necessary() const; | ||||
|  | @ -91,20 +95,12 @@ private: | |||
|     mutable Vec3d m_old_clipping_plane_normal; | ||||
|     mutable Vec3d m_clipping_plane_normal = Vec3d::Zero(); | ||||
| 
 | ||||
|     enum SelectionRectangleStatus { | ||||
|         srOff = 0, | ||||
|         srSelect = 1, | ||||
|         srDeselect = 2 | ||||
|     }m_selection_rectangle_status = srOff; | ||||
|     GLSelectionRectangle m_selection_rectangle; | ||||
| 
 | ||||
|     Vec2d m_selection_rectangle_start_corner; | ||||
|     Vec2d m_selection_rectangle_end_corner; | ||||
|     bool m_wait_for_up_event = false; | ||||
|     bool m_unsaved_changes = false; // Are there unsaved changes in manual mode?
 | ||||
|     bool m_selection_empty = true; | ||||
|     EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state)
 | ||||
|     int m_canvas_width; | ||||
|     int m_canvas_height; | ||||
| 
 | ||||
|     mutable std::unique_ptr<TriangleMeshSlicer> m_tms; | ||||
|     mutable std::unique_ptr<TriangleMeshSlicer> m_supports_tms; | ||||
|  |  | |||
|  | @ -693,7 +693,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) | |||
|             gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()); | ||||
|             processed = true; | ||||
|         } | ||||
|         else if (evt.LeftUp() && (m_current == Flatten) && ((canvas.get_hover_volume_id() != -1) || grabber_contains_mouse())) | ||||
|         else if (evt.LeftUp() && (m_current == Flatten) && ((canvas.get_first_hover_volume_idx() != -1) || grabber_contains_mouse())) | ||||
|         { | ||||
|             // to avoid to loose the selection when user clicks an object while the Flatten gizmo is active
 | ||||
|             processed = true; | ||||
|  | @ -843,13 +843,34 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt, GLCanvas3D& canvas) | |||
| 
 | ||||
|     if (evt.GetEventType() == wxEVT_KEY_UP) | ||||
|     { | ||||
|         if ((m_current == SlaSupports) && (keyCode == WXK_SHIFT) && gizmo_event(SLAGizmoEventType::ShiftUp)) | ||||
|             // shift has been just released - SLA gizmo might want to close rectangular selection.
 | ||||
|             processed = true; | ||||
|         if (m_current == SlaSupports) | ||||
|         { | ||||
|             GLGizmoSlaSupports* gizmo = reinterpret_cast<GLGizmoSlaSupports*>(get_current()); | ||||
| 
 | ||||
|         if ((m_current == SlaSupports) && (keyCode == WXK_ALT) && gizmo_event(SLAGizmoEventType::AltUp)) | ||||
|             // alt has been just released - SLA gizmo might want to close rectangular selection.
 | ||||
|             if (keyCode == WXK_SHIFT) | ||||
|             { | ||||
|                 // shift has been just released - SLA gizmo might want to close rectangular selection.
 | ||||
|                 if (gizmo_event(SLAGizmoEventType::ShiftUp) || (gizmo->is_in_editing_mode() && gizmo->is_selection_rectangle_dragging())) | ||||
|                     processed = true; | ||||
|             } | ||||
|             else if (keyCode == WXK_ALT) | ||||
|             { | ||||
|                 // alt has been just released - SLA gizmo might want to close rectangular selection.
 | ||||
|                 if (gizmo_event(SLAGizmoEventType::AltUp) || (gizmo->is_in_editing_mode() && gizmo->is_selection_rectangle_dragging())) | ||||
|                     processed = true; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| //        if (processed)
 | ||||
| //            canvas.set_cursor(GLCanvas3D::Standard);
 | ||||
|     } | ||||
|     else if (evt.GetEventType() == wxEVT_KEY_DOWN) | ||||
|     { | ||||
|         if ((m_current == SlaSupports) && ((keyCode == WXK_SHIFT) || (keyCode == WXK_ALT)) && reinterpret_cast<GLGizmoSlaSupports*>(get_current())->is_in_editing_mode()) | ||||
|         { | ||||
| //            canvas.set_cursor(GLCanvas3D::Cross);
 | ||||
|             processed = true; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (processed) | ||||
|  |  | |||
|  | @ -48,9 +48,9 @@ KBShortcutsDialog::KBShortcutsDialog() | |||
|     m_head_bitmaps.reserve(m_full_shortcuts.size()); | ||||
|     const wxSize topic_size = wxSize(10 * wxGetApp().em_unit(), -1); | ||||
| 
 | ||||
|     for (auto& sc : m_full_shortcuts) | ||||
|     for (auto& shortcut : m_full_shortcuts) | ||||
|     { | ||||
|         auto sizer = sc.second.second == szLeft ? l_sizer : r_sizer; | ||||
|         auto sizer = shortcut.second.second == szLeft ? l_sizer : r_sizer; | ||||
|         wxBoxSizer* hsizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|         sizer->Add(hsizer, 0, wxEXPAND | wxTOP | wxBOTTOM, 10); | ||||
| 
 | ||||
|  | @ -59,7 +59,7 @@ KBShortcutsDialog::KBShortcutsDialog() | |||
|         hsizer->Add(m_head_bitmaps.back(), 0, wxEXPAND | wxLEFT | wxRIGHT, 15); | ||||
| 
 | ||||
|         // head
 | ||||
|         wxStaticText* head = new wxStaticText(panel, wxID_ANY, sc.first, wxDefaultPosition, topic_size); | ||||
|         wxStaticText* head = new wxStaticText(panel, wxID_ANY, shortcut.first, wxDefaultPosition, topic_size); | ||||
|         head->SetFont(head_font); | ||||
|         hsizer->Add(head, 0, wxALIGN_CENTER_VERTICAL); | ||||
| 
 | ||||
|  | @ -68,7 +68,7 @@ KBShortcutsDialog::KBShortcutsDialog() | |||
|         auto grid_sizer = new wxFlexGridSizer(2, 5, 15); | ||||
|         sizer->Add(grid_sizer, 0, wxEXPAND | wxLEFT| wxRIGHT, 15); | ||||
| 
 | ||||
|         for (auto pair : sc.second.first) | ||||
|         for (auto pair : shortcut.second.first) | ||||
|         { | ||||
|             auto shortcut = new wxStaticText(panel, wxID_ANY, _(pair.first)); | ||||
|             shortcut->SetFont(bold_font); | ||||
|  | @ -124,7 +124,7 @@ void KBShortcutsDialog::fill_shortcuts() | |||
|     main_shortcuts.push_back(Shortcut("+"               ,L("Add Instance to selected object "))); | ||||
|     main_shortcuts.push_back(Shortcut("-"               ,L("Remove Instance from selected object"))); | ||||
|     main_shortcuts.push_back(Shortcut("?"               ,L("Show keyboard shortcuts list"))); | ||||
|     main_shortcuts.push_back(Shortcut(ctrl+"LeftMouse"  ,L("Select multiple object/Move multiple object"))); | ||||
|     main_shortcuts.push_back(Shortcut(ctrl/*+"LeftMouse"*/,L("Press to select multiple object or move multiple object with mouse"))); | ||||
| 
 | ||||
|     m_full_shortcuts.push_back(std::make_pair(_(L("Main Shortcuts")), std::make_pair(main_shortcuts, szLeft))); | ||||
| 
 | ||||
|  |  | |||
|  | @ -14,6 +14,7 @@ | |||
| 
 | ||||
| #include "libslic3r/Print.hpp" | ||||
| #include "libslic3r/Polygon.hpp" | ||||
| #include "libslic3r/SLAPrint.hpp" | ||||
| 
 | ||||
| #include "Tab.hpp" | ||||
| #include "PresetBundle.hpp" | ||||
|  | @ -35,6 +36,12 @@ MainFrame::MainFrame() : | |||
| DPIFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE, "mainframe"), | ||||
|         m_printhost_queue_dlg(new PrintHostQueueDialog(this)) | ||||
| { | ||||
|     // Fonts were created by the DPIFrame constructor for the monitor, on which the window opened.
 | ||||
|     wxGetApp().update_fonts(this); | ||||
|     this->SetFont(this->normal_font()); | ||||
|     // initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window.
 | ||||
|     wxGetApp().set_em_unit(std::max<size_t>(10, GetTextExtent("m").x - 1)); | ||||
| 
 | ||||
|     // Load the icon either from the exe, or from the ico file.
 | ||||
| #if _WIN32 | ||||
|     { | ||||
|  | @ -53,11 +60,6 @@ DPIFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAU | |||
| 		SLIC3R_VERSION + | ||||
| 		_(L(" - Remember to check for updates at http://github.com/prusa3d/slic3r/releases"))); | ||||
| 
 | ||||
| 
 | ||||
|     // initialize default width_unit according to the width of the one symbol ("x") of the current system font
 | ||||
|     const wxSize size = GetTextExtent("m"); | ||||
|     wxGetApp().set_em_unit(std::max<size_t>(10, size.x - 1)); | ||||
| 
 | ||||
|     /* Load default preset bitmaps before a tabpanel initialization,
 | ||||
|      * but after filling of an em_unit value  | ||||
|      */ | ||||
|  | @ -140,6 +142,7 @@ void MainFrame::init_tabpanel() | |||
|     // wxNB_NOPAGETHEME: Disable Windows Vista theme for the Notebook background. The theme performance is terrible on Windows 10
 | ||||
|     // with multiple high resolution displays connected.
 | ||||
|     m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME); | ||||
|     m_tabpanel->SetFont(Slic3r::GUI::wxGetApp().normal_font()); | ||||
| 
 | ||||
|     m_tabpanel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, [this](wxEvent&) { | ||||
|         auto panel = m_tabpanel->GetCurrentPage(); | ||||
|  | @ -205,12 +208,30 @@ void MainFrame::add_created_tab(Tab* panel) | |||
| 
 | ||||
| bool MainFrame::can_save() const | ||||
| { | ||||
|     return (m_plater != nullptr) ? !m_plater->model().objects.empty() : false; | ||||
|     return (m_plater != nullptr) && !m_plater->model().objects.empty(); | ||||
| } | ||||
| 
 | ||||
| bool MainFrame::can_export_model() const | ||||
| { | ||||
|     return (m_plater != nullptr) ? !m_plater->model().objects.empty() : false; | ||||
|     return (m_plater != nullptr) && !m_plater->model().objects.empty(); | ||||
| } | ||||
| 
 | ||||
| bool MainFrame::can_export_supports() const | ||||
| { | ||||
|     if ((m_plater == nullptr) || (m_plater->printer_technology() != ptSLA) || m_plater->model().objects.empty()) | ||||
|         return false; | ||||
| 
 | ||||
|     bool can_export = false; | ||||
|     const PrintObjects& objects = m_plater->sla_print().objects(); | ||||
|     for (const SLAPrintObject* object : objects) | ||||
|     { | ||||
|         if (object->has_mesh(slaposBasePool) || object->has_mesh(slaposSupportTree)) | ||||
|         { | ||||
|             can_export = true; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     return can_export; | ||||
| } | ||||
| 
 | ||||
| bool MainFrame::can_export_gcode() const | ||||
|  | @ -243,26 +264,25 @@ bool MainFrame::can_change_view() const | |||
| 
 | ||||
| bool MainFrame::can_select() const | ||||
| { | ||||
|     return (m_plater != nullptr) ? !m_plater->model().objects.empty() : false; | ||||
|     return (m_plater != nullptr) && !m_plater->model().objects.empty(); | ||||
| } | ||||
| 
 | ||||
| bool MainFrame::can_delete() const | ||||
| { | ||||
|     return (m_plater != nullptr) ? !m_plater->is_selection_empty() : false; | ||||
|     return (m_plater != nullptr) && !m_plater->is_selection_empty(); | ||||
| } | ||||
| 
 | ||||
| bool MainFrame::can_delete_all() const | ||||
| { | ||||
|     return (m_plater != nullptr) ? !m_plater->model().objects.empty() : false; | ||||
|     return (m_plater != nullptr) && !m_plater->model().objects.empty(); | ||||
| } | ||||
| 
 | ||||
| void MainFrame::on_dpi_changed(const wxRect &suggested_rect) | ||||
| { | ||||
|     wxGetApp().update_fonts(); | ||||
| 
 | ||||
|     // _strange_ workaround for correct em_unit calculation
 | ||||
|     const int new_em_unit = scale_factor() * 10; | ||||
|     wxGetApp().set_em_unit(std::max<size_t>(10, new_em_unit)); | ||||
|     this->SetFont(this->normal_font()); | ||||
|     // initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window.
 | ||||
|     wxGetApp().set_em_unit(std::max<size_t>(10, GetTextExtent("m").x - 1)); | ||||
| 
 | ||||
|     /* Load default preset bitmaps before a tabpanel initialization,
 | ||||
|      * but after filling of an em_unit value | ||||
|  | @ -353,6 +373,8 @@ void MainFrame::init_menubar() | |||
|         export_menu->AppendSeparator(); | ||||
|         wxMenuItem* item_export_stl = append_menu_item(export_menu, wxID_ANY, _(L("Export plate as &STL")) + dots, _(L("Export current plate as STL")), | ||||
|             [this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(); }, menu_icon("export_plater")); | ||||
|         wxMenuItem* item_export_stl_sla = append_menu_item(export_menu, wxID_ANY, _(L("Export plate as STL including supports")) + dots, _(L("Export current plate as STL including supports")), | ||||
|             [this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(true); }, menu_icon("export_plater")); | ||||
|         wxMenuItem* item_export_amf = append_menu_item(export_menu, wxID_ANY, _(L("Export plate as &AMF")) + dots, _(L("Export current plate as AMF")), | ||||
|             [this](wxCommandEvent&) { if (m_plater) m_plater->export_amf(); }, menu_icon("export_plater")); | ||||
|         export_menu->AppendSeparator(); | ||||
|  | @ -422,10 +444,11 @@ void MainFrame::init_menubar() | |||
|         Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable((m_plater != nullptr) && can_save()); }, item_save->GetId()); | ||||
|         Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable((m_plater != nullptr) && can_save()); }, item_save_as->GetId()); | ||||
|         Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(m_plater != nullptr); }, item_import_model->GetId()); | ||||
|         Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable((m_plater != nullptr) && can_export_gcode()); }, item_export_gcode->GetId()); | ||||
|         Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable((m_plater != nullptr) && can_export_model()); }, item_export_stl->GetId()); | ||||
|         Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable((m_plater != nullptr) && can_export_model()); }, item_export_amf->GetId()); | ||||
|         Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable((m_plater != nullptr) && can_slice()); }, m_menu_item_reslice_now->GetId()); | ||||
|         Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_export_gcode()); }, item_export_gcode->GetId()); | ||||
|         Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_export_model()); }, item_export_stl->GetId()); | ||||
|         Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_export_supports()); }, item_export_stl_sla->GetId()); | ||||
|         Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_export_model()); }, item_export_amf->GetId()); | ||||
|         Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_slice()); }, m_menu_item_reslice_now->GetId()); | ||||
|     } | ||||
| 
 | ||||
| #ifdef _MSC_VER | ||||
|  | @ -451,12 +474,12 @@ void MainFrame::init_menubar() | |||
|         wxString hotkey_delete = "Del"; | ||||
|     #endif | ||||
|         wxMenuItem* item_select_all = append_menu_item(editMenu, wxID_ANY, _(L("&Select all")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "A", _(L("Selects all objects")), | ||||
|             [this](wxCommandEvent&) { m_plater->select_all(); }, ""); | ||||
|             [this](wxCommandEvent&) { if (m_plater != nullptr) m_plater->select_all(); }, ""); | ||||
|         editMenu->AppendSeparator(); | ||||
|         wxMenuItem* item_delete_sel = append_menu_item(editMenu, wxID_ANY, _(L("&Delete selected")) + sep + hotkey_delete, _(L("Deletes the current selection")), | ||||
|             [this](wxCommandEvent&) { m_plater->remove_selected(); }, menu_icon("remove_menu")); | ||||
|         wxMenuItem* item_delete_all = append_menu_item(editMenu, wxID_ANY, _(L("Delete &all")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + hotkey_delete, _(L("Deletes all objects")), | ||||
|             [this](wxCommandEvent&) { m_plater->reset(); }, menu_icon("delete_all_menu")); | ||||
|             [this](wxCommandEvent&) { m_plater->reset_with_confirm(); }, menu_icon("delete_all_menu")); | ||||
| 
 | ||||
|         editMenu->AppendSeparator(); | ||||
| 
 | ||||
|  | @ -528,7 +551,9 @@ void MainFrame::init_menubar() | |||
|         // The camera control accelerators are captured by GLCanvas3D::on_char().
 | ||||
| 		wxMenuItem* item_iso = append_menu_item(viewMenu, wxID_ANY, _(L("Iso")) + sep + "&0", _(L("Iso View")), [this](wxCommandEvent&) { select_view("iso"); }); | ||||
|         viewMenu->AppendSeparator(); | ||||
|         //TRN To be shown in the main menu View->Top 
 | ||||
| 		wxMenuItem* item_top = append_menu_item(viewMenu, wxID_ANY, _(L("Top")) + sep + "&1", _(L("Top View")), [this](wxCommandEvent&) { select_view("top"); }); | ||||
| 		//TRN To be shown in the main menu View->Bottom 
 | ||||
| 		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"); }); | ||||
|  |  | |||
|  | @ -63,6 +63,7 @@ class MainFrame : public DPIFrame | |||
| 
 | ||||
|     bool can_save() const; | ||||
|     bool can_export_model() const; | ||||
|     bool can_export_supports() const; | ||||
|     bool can_export_gcode() const; | ||||
|     bool can_slice() const; | ||||
|     bool can_change_view() const; | ||||
|  |  | |||
|  | @ -724,7 +724,7 @@ Sidebar::Sidebar(Plater *parent) | |||
| 
 | ||||
|     auto init_btn = [this](wxButton **btn, wxString label) { | ||||
|         *btn = new wxButton(this, wxID_ANY, label, wxDefaultPosition,  | ||||
|                             wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); | ||||
|                             wxDefaultSize, wxBU_EXACTFIT); | ||||
|         (*btn)->SetFont(wxGetApp().bold_font()); | ||||
|     }; | ||||
| 
 | ||||
|  | @ -896,8 +896,7 @@ void Sidebar::msw_rescale() | |||
|     p->frequently_changed_parameters->get_og(false)->msw_rescale(); | ||||
| 
 | ||||
|     p->object_list->msw_rescale(); | ||||
| 
 | ||||
|     p->object_manipulation->get_og()->msw_rescale(); | ||||
|     p->object_manipulation->msw_rescale(); | ||||
|     p->object_settings->msw_rescale(); | ||||
| 
 | ||||
|     p->object_info->msw_rescale(); | ||||
|  | @ -970,7 +969,7 @@ void Sidebar::show_info_sizer() | |||
|     p->object_info->info_size->SetLabel(wxString::Format("%.2f x %.2f x %.2f",size(0), size(1), size(2))); | ||||
|     p->object_info->info_materials->SetLabel(wxString::Format("%d", static_cast<int>(model_object->materials_count()))); | ||||
| 
 | ||||
|     auto& stats = model_object->volumes.front()->mesh.stl.stats; | ||||
|     const auto& stats = model_object->get_object_stl_stats();//model_object->volumes.front()->mesh.stl.stats;
 | ||||
|     p->object_info->info_volume->SetLabel(wxString::Format("%.2f", stats.volume)); | ||||
|     p->object_info->info_facets->SetLabel(wxString::Format(_(L("%d (%d shells)")), static_cast<int>(model_object->facets_count()), stats.number_of_parts)); | ||||
| 
 | ||||
|  | @ -990,7 +989,7 @@ void Sidebar::show_info_sizer() | |||
|         p->object_info->manifold_warning_icon->SetToolTip(tooltip); | ||||
|     }  | ||||
|     else { | ||||
|         p->object_info->info_manifold->SetLabel(L("Yes")); | ||||
|         p->object_info->info_manifold->SetLabel(_(L("Yes"))); | ||||
|         p->object_info->showing_manifold_warning_icon = false; | ||||
|         p->object_info->info_manifold->SetToolTip(""); | ||||
|         p->object_info->manifold_warning_icon->SetToolTip(""); | ||||
|  | @ -1328,6 +1327,7 @@ struct Plater::priv | |||
|     bool can_split_to_volumes() const; | ||||
|     bool can_arrange() const; | ||||
|     bool can_layers_editing() const; | ||||
|     bool can_fix_through_netfabb() const; | ||||
| 
 | ||||
|     void msw_rescale_object_menu(); | ||||
| 
 | ||||
|  | @ -1379,6 +1379,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) | |||
|     , view_toolbar(GLToolbar::Radio) | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| { | ||||
| 	this->q->SetFont(Slic3r::GUI::wxGetApp().normal_font()); | ||||
| 
 | ||||
|     arranging = false; | ||||
|     rotoptimizing = false; | ||||
|     background_process.set_fff_print(&fff_print); | ||||
|  | @ -1449,7 +1451,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) | |||
|     // 3DScene/Toolbar:
 | ||||
|     view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this); | ||||
|     view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE, [q](SimpleEvent&) { q->remove_selected(); }); | ||||
|     view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE_ALL, [this](SimpleEvent&) { reset(); }); | ||||
|     view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE_ALL, [q](SimpleEvent&) { q->reset_with_confirm(); }); | ||||
|     view3D_canvas->Bind(EVT_GLTOOLBAR_ARRANGE, [this](SimpleEvent&) { arrange(); }); | ||||
|     view3D_canvas->Bind(EVT_GLTOOLBAR_COPY, [q](SimpleEvent&) { q->copy_selection_to_clipboard(); }); | ||||
|     view3D_canvas->Bind(EVT_GLTOOLBAR_PASTE, [q](SimpleEvent&) { q->paste_from_clipboard(); }); | ||||
|  | @ -2405,7 +2407,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation) | |||
|         // Background data is valid.
 | ||||
|         if ((return_state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0 || | ||||
|             (return_state & UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE) != 0 ) | ||||
|             this->statusbar()->set_status_text(L("Ready to slice")); | ||||
|             this->statusbar()->set_status_text(_(L("Ready to slice"))); | ||||
| 
 | ||||
|         sidebar->set_btn_label(ActionButtonType::abExport, _(label_btn_export)); | ||||
|         sidebar->set_btn_label(ActionButtonType::abSendGCode, _(label_btn_send)); | ||||
|  | @ -2443,7 +2445,7 @@ bool Plater::priv::restart_background_process(unsigned int state) | |||
|         // The print is valid and it can be started.
 | ||||
|         if (this->background_process.start()) { | ||||
|             this->statusbar()->set_cancel_callback([this]() { | ||||
|                 this->statusbar()->set_status_text(L("Cancelling")); | ||||
|                 this->statusbar()->set_status_text(_(L("Cancelling"))); | ||||
|                 this->background_process.stop(); | ||||
|             }); | ||||
|             return true; | ||||
|  | @ -2667,7 +2669,7 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) | |||
|         } | ||||
| 
 | ||||
|         this->statusbar()->set_progress(evt.status.percent); | ||||
|         this->statusbar()->set_status_text(_(L(evt.status.text)) + wxString::FromUTF8("…")); | ||||
|         this->statusbar()->set_status_text(_(evt.status.text) + wxString::FromUTF8("…")); | ||||
|     } | ||||
|     if (evt.status.flags & (PrintBase::SlicingStatus::RELOAD_SCENE || PrintBase::SlicingStatus::RELOAD_SLA_SUPPORT_POINTS)) { | ||||
|         switch (this->printer_technology) { | ||||
|  | @ -2726,7 +2728,7 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) | |||
|         this->statusbar()->set_status_text(message); | ||||
|     } | ||||
| 	if (canceled) | ||||
| 		this->statusbar()->set_status_text(L("Cancelled")); | ||||
| 		this->statusbar()->set_status_text(_(L("Cancelled"))); | ||||
| 
 | ||||
|     this->sidebar->show_sliced_info_sizer(success); | ||||
| 
 | ||||
|  | @ -2933,12 +2935,12 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ | |||
|             [this](wxCommandEvent&) { reload_from_disk(); }); | ||||
| 
 | ||||
|         append_menu_item(menu, wxID_ANY, _(L("Export as STL")) + dots, _(L("Export the selected object as STL file")), | ||||
|             [this](wxCommandEvent&) { q->export_stl(true); }); | ||||
|             [this](wxCommandEvent&) { q->export_stl(false, true); }); | ||||
| 
 | ||||
|         menu->AppendSeparator(); | ||||
|     } | ||||
| 
 | ||||
|     sidebar->obj_list()->append_menu_item_fix_through_netfabb(menu); | ||||
|     wxMenuItem* item_fix_through_netfabb = sidebar->obj_list()->append_menu_item_fix_through_netfabb(menu); | ||||
| 
 | ||||
|     wxMenu* mirror_menu = new wxMenu(); | ||||
|     if (mirror_menu == nullptr) | ||||
|  | @ -2958,6 +2960,8 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ | |||
|     { | ||||
|         q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_mirror()); }, item_mirror->GetId()); | ||||
|         q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_delete()); }, item_delete->GetId()); | ||||
|         if (item_fix_through_netfabb) | ||||
|             q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_fix_through_netfabb()); }, item_fix_through_netfabb->GetId()); | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
|  | @ -3127,6 +3131,15 @@ bool Plater::priv::can_delete_all() const | |||
|     return !model.objects.empty(); | ||||
| } | ||||
| 
 | ||||
| bool Plater::priv::can_fix_through_netfabb() const | ||||
| { | ||||
|     int obj_idx = get_selected_object_idx(); | ||||
|     if (obj_idx < 0) | ||||
|         return false; | ||||
| 
 | ||||
|     return model.objects[obj_idx]->get_mesh_errors_count() > 0; | ||||
| } | ||||
| 
 | ||||
| bool Plater::priv::can_increase_instances() const | ||||
| { | ||||
|     if (arranging || rotoptimizing) { | ||||
|  | @ -3290,6 +3303,11 @@ void Plater::select_all() { p->select_all(); } | |||
| 
 | ||||
| void Plater::remove(size_t obj_idx) { p->remove(obj_idx); } | ||||
| void Plater::reset() { p->reset(); } | ||||
| void Plater::reset_with_confirm() | ||||
| { | ||||
|     if (wxMessageDialog((wxWindow*)this, _(L("All objects will be removed, continue ?")), _(L("Delete all")), wxYES_NO | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES) | ||||
|         reset(); | ||||
| } | ||||
| 
 | ||||
| void Plater::delete_object_from_model(size_t obj_idx) { p->delete_object_from_model(obj_idx); } | ||||
| 
 | ||||
|  | @ -3439,7 +3457,7 @@ void Plater::export_gcode() | |||
|         p->export_gcode(std::move(output_path), PrintHostJob()); | ||||
| } | ||||
| 
 | ||||
| void Plater::export_stl(bool selection_only) | ||||
| void Plater::export_stl(bool extended, bool selection_only) | ||||
| { | ||||
|     if (p->model.objects.empty()) { return; } | ||||
| 
 | ||||
|  | @ -3474,8 +3492,65 @@ void Plater::export_stl(bool selection_only) | |||
|         } | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         mesh = p->model.mesh(); | ||||
| 
 | ||||
|         if (extended && (p->printer_technology == ptSLA)) | ||||
|         { | ||||
|             const PrintObjects& objects = p->sla_print.objects(); | ||||
|             for (const SLAPrintObject* object : objects) | ||||
|             { | ||||
|                 const ModelObject* model_object = object->model_object(); | ||||
|                 Transform3d mesh_trafo_inv = object->trafo().inverse(); | ||||
|                 bool is_left_handed = object->is_left_handed(); | ||||
| 
 | ||||
|                 TriangleMesh pad_mesh; | ||||
|                 bool has_pad_mesh = object->has_mesh(slaposBasePool); | ||||
|                 if (has_pad_mesh) | ||||
|                 { | ||||
|                     pad_mesh = object->get_mesh(slaposBasePool); | ||||
|                     pad_mesh.transform(mesh_trafo_inv); | ||||
|                 } | ||||
| 
 | ||||
|                 TriangleMesh supports_mesh; | ||||
|                 bool has_supports_mesh = object->has_mesh(slaposSupportTree); | ||||
|                 if (has_supports_mesh) | ||||
|                 { | ||||
|                     supports_mesh = object->get_mesh(slaposSupportTree); | ||||
|                     supports_mesh.transform(mesh_trafo_inv); | ||||
|                 } | ||||
| 
 | ||||
|                 const std::vector<SLAPrintObject::Instance>& obj_instances = object->instances(); | ||||
|                 for (const SLAPrintObject::Instance& obj_instance : obj_instances) | ||||
|                 { | ||||
|                     auto it = std::find_if(model_object->instances.begin(), model_object->instances.end(), | ||||
|                         [&obj_instance](const ModelInstance *mi) { return mi->id() == obj_instance.instance_id; }); | ||||
|                     assert(it != model_object->instances.end()); | ||||
| 
 | ||||
|                     if (it != model_object->instances.end()) | ||||
|                     { | ||||
|                         int instance_idx = it - model_object->instances.begin(); | ||||
|                         const Transform3d& inst_transform = object->model_object()->instances[instance_idx]->get_transformation().get_matrix(); | ||||
| 
 | ||||
|                         if (has_pad_mesh) | ||||
|                         { | ||||
|                             TriangleMesh inst_pad_mesh = pad_mesh; | ||||
|                             inst_pad_mesh.transform(inst_transform, is_left_handed); | ||||
|                             mesh.merge(inst_pad_mesh); | ||||
|                         } | ||||
| 
 | ||||
|                         if (has_supports_mesh) | ||||
|                         { | ||||
|                             TriangleMesh inst_supports_mesh = supports_mesh; | ||||
|                             inst_supports_mesh.transform(inst_transform, is_left_handed); | ||||
|                             mesh.merge(inst_supports_mesh); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     Slic3r::store_stl(path_u8.c_str(), &mesh, true); | ||||
|     p->statusbar()->set_status_text(wxString::Format(_(L("STL file exported to %s")), path)); | ||||
| } | ||||
|  |  | |||
|  | @ -153,6 +153,7 @@ public: | |||
|     void select_all(); | ||||
|     void remove(size_t obj_idx); | ||||
|     void reset(); | ||||
|     void reset_with_confirm(); | ||||
|     void delete_object_from_model(size_t obj_idx); | ||||
|     void remove_selected(); | ||||
|     void increase_instances(size_t num = 1); | ||||
|  | @ -163,7 +164,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); | ||||
| 
 | ||||
|     void export_gcode(); | ||||
|     void export_stl(bool selection_only = false); | ||||
|     void export_stl(bool extended = false, bool selection_only = false); | ||||
|     void export_amf(); | ||||
|     void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path()); | ||||
|     void reslice(); | ||||
|  |  | |||
|  | @ -32,6 +32,7 @@ ProgressStatusBar::ProgressStatusBar(wxWindow *parent, int id): | |||
|     m_prog->Hide(); | ||||
|     m_cancelbutton->Hide(); | ||||
| 
 | ||||
| 	self->SetFont(GUI::wxGetApp().normal_font()); | ||||
|     self->SetFieldsCount(3); | ||||
|     int w[] = {-1, 150, 155}; | ||||
|     self->SetStatusWidths(3, w); | ||||
|  |  | |||
|  | @ -42,6 +42,7 @@ Tab::Tab(wxNotebook* parent, const wxString& title, const char* name) : | |||
| 	m_parent(parent), m_title(title), m_name(name) | ||||
| { | ||||
| 	Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL, name); | ||||
| 	this->SetFont(Slic3r::GUI::wxGetApp().normal_font()); | ||||
|     set_type(); | ||||
| 
 | ||||
| 	m_compatible_printers.type			= Preset::TYPE_PRINTER; | ||||
|  | @ -1678,7 +1679,7 @@ void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup) | |||
| 	} | ||||
| 
 | ||||
| 	auto printhost_browse = [=](wxWindow* parent) { | ||||
|         add_scaled_button(parent, &m_printhost_browse_btn, "browse", _(L(" Browse ")) + dots, wxBU_LEFT | wxBU_EXACTFIT); | ||||
|         add_scaled_button(parent, &m_printhost_browse_btn, "browse", _(L("Browse")) + " "+ dots, wxBU_LEFT | wxBU_EXACTFIT); | ||||
|         ScalableButton* btn = m_printhost_browse_btn; | ||||
| 		btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); | ||||
| 
 | ||||
|  | @ -2057,7 +2058,7 @@ void TabPrinter::build_sla() | |||
|     optgroup->append_single_option_line("area_fill"); | ||||
| 
 | ||||
|     optgroup = page->new_optgroup(_(L("Corrections"))); | ||||
|     line = Line{ m_config->def()->get("relative_correction")->full_label, "" }; | ||||
|     line = Line{ _(m_config->def()->get("relative_correction")->full_label), "" }; | ||||
| //    std::vector<std::string> axes{ "X", "Y", "Z" };
 | ||||
|     std::vector<std::string> axes{ "XY", "Z" }; | ||||
|     int id = 0; | ||||
|  | @ -3313,7 +3314,7 @@ void TabSLAMaterial::build() | |||
| //    std::vector<std::string> axes{ "X", "Y", "Z" };
 | ||||
|     std::vector<std::string> axes{ "XY", "Z" }; | ||||
|     for (auto& opt_key : corrections) { | ||||
|         auto line = Line{ m_config->def()->get(opt_key)->full_label, "" }; | ||||
|         auto line = Line{ _(m_config->def()->get(opt_key)->full_label), "" }; | ||||
|         int id = 0; | ||||
|         for (auto& axis : axes) { | ||||
|             auto opt = optgroup->get_option(opt_key, id); | ||||
|  |  | |||
|  | @ -370,9 +370,13 @@ int em_unit(wxWindow* win) | |||
| { | ||||
|     if (win) | ||||
|     { | ||||
|         Slic3r::GUI::DPIDialog* dlg = dynamic_cast<Slic3r::GUI::DPIDialog*>(Slic3r::GUI::find_toplevel_parent(win)); | ||||
|         wxTopLevelWindow *toplevel = Slic3r::GUI::find_toplevel_parent(win); | ||||
|         Slic3r::GUI::DPIDialog* dlg = dynamic_cast<Slic3r::GUI::DPIDialog*>(toplevel); | ||||
|         if (dlg) | ||||
|             return dlg->em_unit(); | ||||
|         Slic3r::GUI::DPIFrame* frame = dynamic_cast<Slic3r::GUI::DPIFrame*>(toplevel); | ||||
|         if (frame) | ||||
|             return frame->em_unit(); | ||||
|     } | ||||
|      | ||||
|     return Slic3r::GUI::wxGetApp().em_unit(); | ||||
|  | @ -417,6 +421,28 @@ wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name_in, con | |||
| // ObjectDataViewModelNode
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const ItemType type) : | ||||
|     m_parent(parent), | ||||
|     m_type(type), | ||||
|     m_extruder(wxEmptyString) | ||||
| { | ||||
|     if (type == itSettings) { | ||||
|         m_name = "Settings to modified"; | ||||
|     } | ||||
|     else if (type == itInstanceRoot) { | ||||
|         m_name = _(L("Instances")); | ||||
| #ifdef __WXGTK__ | ||||
|         m_container = true; | ||||
| #endif  //__WXGTK__
 | ||||
|     } | ||||
|     else if (type == itInstance) { | ||||
|         m_idx = parent->GetChildCount(); | ||||
|         m_name = wxString::Format(_(L("Instance_%d")), m_idx + 1); | ||||
| 
 | ||||
|         set_action_icon(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void ObjectDataViewModelNode::set_action_icon() | ||||
| { | ||||
|     m_action_icon_name = m_type == itObject ? "advanced_plus" :  | ||||
|  | @ -455,7 +481,7 @@ bool ObjectDataViewModelNode::update_settings_digest(const std::vector<std::stri | |||
|     m_name = wxEmptyString; | ||||
| 
 | ||||
|     for (auto& cat : m_opt_categories) | ||||
|         m_name += cat + "; "; | ||||
|         m_name += _(cat) + "; "; | ||||
|     if (!m_name.IsEmpty()) | ||||
|         m_name.erase(m_name.Length()-2, 2); // Delete last "; "
 | ||||
| 
 | ||||
|  | @ -491,14 +517,21 @@ ObjectDataViewModel::~ObjectDataViewModel() | |||
|     m_bitmap_cache = nullptr; | ||||
| } | ||||
| 
 | ||||
| wxDataViewItem ObjectDataViewModel::Add(const wxString &name, const int extruder) | ||||
| wxDataViewItem ObjectDataViewModel::Add(const wxString &name,  | ||||
|                                         const int extruder, | ||||
|                                         const bool has_errors/* = false*/) | ||||
| { | ||||
|     const wxString extruder_str = extruder == 0 ? "default" : wxString::Format("%d", extruder); | ||||
| 	auto root = new ObjectDataViewModelNode(name, extruder_str); | ||||
|     // Add error icon if detected auto-repaire
 | ||||
|     if (has_errors) | ||||
|         root->m_bmp = *m_warning_bmp; | ||||
| 
 | ||||
| 	m_objects.push_back(root); | ||||
| 	// notify control
 | ||||
| 	wxDataViewItem child((void*)root); | ||||
| 	wxDataViewItem parent((void*)NULL); | ||||
| 
 | ||||
| 	ItemAdded(parent, child); | ||||
| 	return child; | ||||
| } | ||||
|  | @ -506,6 +539,7 @@ wxDataViewItem ObjectDataViewModel::Add(const wxString &name, const int extruder | |||
| wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent_item, | ||||
|                                                     const wxString &name, | ||||
|                                                     const Slic3r::ModelVolumeType volume_type, | ||||
|                                                     const bool has_errors/* = false*/, | ||||
|                                                     const int extruder/* = 0*/, | ||||
|                                                     const bool create_frst_child/* = true*/) | ||||
| { | ||||
|  | @ -519,9 +553,14 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent | |||
|     if (insert_position < 0 || root->GetNthChild(insert_position)->m_type != itInstanceRoot) | ||||
|         insert_position = -1; | ||||
| 
 | ||||
|     const bool obj_errors = root->m_bmp.IsOk(); | ||||
| 
 | ||||
|     if (create_frst_child && root->m_volumes_cnt == 0) | ||||
|     { | ||||
| 		const auto node = new ObjectDataViewModelNode(root, root->m_name, *m_volume_bmps[0], extruder_str, 0); | ||||
|         const Slic3r::ModelVolumeType type = Slic3r::ModelVolumeType::MODEL_PART; | ||||
|         const auto node = new ObjectDataViewModelNode(root, root->m_name, GetVolumeIcon(type, obj_errors), extruder_str, 0); | ||||
|         node->m_volume_type = type; | ||||
| 
 | ||||
|         insert_position < 0 ? root->Append(node) : root->Insert(node, insert_position); | ||||
| 		// notify control
 | ||||
| 		const wxDataViewItem child((void*)node); | ||||
|  | @ -529,12 +568,15 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent | |||
| 
 | ||||
|         root->m_volumes_cnt++; | ||||
|         if (insert_position > 0) insert_position++; | ||||
| 
 | ||||
|         node->m_volume_type = volume_type; | ||||
| 	} | ||||
| 
 | ||||
|     const auto node = new ObjectDataViewModelNode(root, name, *m_volume_bmps[int(volume_type)], extruder_str, root->m_volumes_cnt); | ||||
|     const auto node = new ObjectDataViewModelNode(root, name, GetVolumeIcon(volume_type, has_errors), extruder_str, root->m_volumes_cnt); | ||||
|     insert_position < 0 ? root->Append(node) : root->Insert(node, insert_position); | ||||
| 
 | ||||
|     // if part with errors is added, but object wasn't marked, then mark it
 | ||||
|     if (!obj_errors && has_errors) | ||||
|         root->SetBitmap(*m_warning_bmp); | ||||
| 
 | ||||
| 	// notify control
 | ||||
| 	const wxDataViewItem child((void*)node); | ||||
|     ItemAdded(parent_item, child); | ||||
|  | @ -1299,15 +1341,61 @@ void ObjectDataViewModel::Rescale() | |||
|         node->msw_rescale(); | ||||
| 
 | ||||
|         if (node->m_type & itVolume) | ||||
|             node->m_bmp = *m_volume_bmps[node->volume_type()]; | ||||
|             node->m_bmp = GetVolumeIcon(node->m_volume_type, node->m_bmp.GetWidth() != node->m_bmp.GetHeight()); | ||||
| 
 | ||||
|         if (node->m_type & itObject && node->m_bmp.IsOk()) | ||||
|             node->m_bmp = create_scaled_bitmap(nullptr, "exclamation"); | ||||
|             node->m_bmp = *m_warning_bmp; | ||||
| 
 | ||||
|         ItemChanged(item); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| wxBitmap ObjectDataViewModel::GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, const bool is_marked/* = false*/) | ||||
| { | ||||
|     if (!is_marked) | ||||
|         return *m_volume_bmps[static_cast<int>(vol_type)]; | ||||
| 
 | ||||
|     std::string scaled_bitmap_name = "warning" + std::to_string(static_cast<int>(vol_type)); | ||||
|     scaled_bitmap_name += "-em" + std::to_string(Slic3r::GUI::wxGetApp().em_unit()); | ||||
| 
 | ||||
|     wxBitmap *bmp = m_bitmap_cache->find(scaled_bitmap_name); | ||||
|     if (bmp == nullptr) { | ||||
|         std::vector<wxBitmap> bmps; | ||||
| 
 | ||||
|         bmps.emplace_back(*m_warning_bmp); | ||||
|         bmps.emplace_back(*m_volume_bmps[static_cast<int>(vol_type)]); | ||||
| 
 | ||||
|         bmp = m_bitmap_cache->insert(scaled_bitmap_name, bmps); | ||||
|     } | ||||
| 
 | ||||
|     return *bmp; | ||||
| } | ||||
| 
 | ||||
| void ObjectDataViewModel::DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object/* = false*/) | ||||
| { | ||||
|     if (!item.IsOk()) | ||||
|         return; | ||||
| 
 | ||||
|     ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); | ||||
| 
 | ||||
|     if (!node->GetBitmap().IsOk() || !(node->GetType() & (itVolume | itObject))) | ||||
|         return; | ||||
| 
 | ||||
|     if (node->GetType() & itVolume) { | ||||
|         node->SetBitmap(*m_volume_bmps[static_cast<int>(node->volume_type())]); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     node->SetBitmap(wxNullBitmap); | ||||
|     if (unmark_object) | ||||
|     { | ||||
|         wxDataViewItemArray children; | ||||
|         GetChildren(item, children); | ||||
|         for (const wxDataViewItem& child : children) | ||||
|             DeleteWarningIcon(child); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // PrusaDataViewBitmapText
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
|  | @ -2450,11 +2538,7 @@ ModeSizer::ModeSizer(wxWindow *parent, int hgap/* = 10*/) : | |||
| 
 | ||||
|     m_mode_btns.reserve(3); | ||||
|     for (const auto& button : buttons) { | ||||
| //         int x, y;
 | ||||
| //         parent->GetTextExtent(button.first, &x, &y, nullptr, nullptr, &Slic3r::GUI::wxGetApp().bold_font());
 | ||||
| //         const wxSize size = wxSize(x + button.second.GetWidth() + Slic3r::GUI::wxGetApp().em_unit(), 
 | ||||
| //                                    y + Slic3r::GUI::wxGetApp().em_unit());
 | ||||
|         m_mode_btns.push_back(new ModeButton(parent, wxID_ANY, button.second, button.first/*, size*/)); | ||||
|         m_mode_btns.push_back(new ModeButton(parent, wxID_ANY, button.second, button.first)); | ||||
|     } | ||||
| 
 | ||||
|     for (auto btn : m_mode_btns) | ||||
|  |  | |||
|  | @ -224,28 +224,7 @@ public: | |||
|         set_action_icon(); | ||||
|     } | ||||
| 
 | ||||
|     ObjectDataViewModelNode(ObjectDataViewModelNode* parent,  | ||||
|                             const ItemType type) : | ||||
|         m_parent(parent), | ||||
|         m_type(type), | ||||
|         m_extruder(wxEmptyString) | ||||
| 	{ | ||||
|         if (type == itSettings) { | ||||
|             m_name = "Settings to modified"; | ||||
|         } | ||||
|         else if (type == itInstanceRoot) { | ||||
|             m_name = "Instances";  | ||||
| #ifdef __WXGTK__ | ||||
|             m_container = true; | ||||
| #endif  //__WXGTK__
 | ||||
|         } | ||||
|         else if (type == itInstance) { | ||||
|             m_idx = parent->GetChildCount(); | ||||
|             m_name = wxString::Format("Instance_%d", m_idx+1); | ||||
| 
 | ||||
|             set_action_icon(); | ||||
|         } | ||||
| 	} | ||||
|     ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const ItemType type); | ||||
| 
 | ||||
| 	~ObjectDataViewModelNode() | ||||
| 	{ | ||||
|  | @ -328,14 +307,10 @@ public: | |||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	void SetBitmap(const wxBitmap &icon) | ||||
| 	{ | ||||
| 		m_bmp = icon; | ||||
| 	} | ||||
| 
 | ||||
|     ItemType GetType() const { | ||||
|         return m_type; | ||||
|     } | ||||
| 	void            SetBitmap(const wxBitmap &icon) { m_bmp = icon; } | ||||
|     const wxBitmap& GetBitmap() const               { return m_bmp; } | ||||
|     const wxString& GetName() const                 { return m_name; } | ||||
|     ItemType        GetType() const                 { return m_type; } | ||||
| 
 | ||||
| 	void SetIdx(const int& idx) { | ||||
| 		m_idx = idx; | ||||
|  | @ -344,9 +319,7 @@ public: | |||
|             m_name = wxString::Format("Instance_%d", m_idx + 1); | ||||
| 	} | ||||
| 
 | ||||
| 	int GetIdx() const { | ||||
| 		return m_idx; | ||||
| 	} | ||||
| 	int             GetIdx() const                   { return m_idx; } | ||||
| 
 | ||||
| 	// use this function only for childrens
 | ||||
| 	void AssignAllVal(ObjectDataViewModelNode& from_node) | ||||
|  | @ -379,10 +352,10 @@ public: | |||
| 	// Set action icons for node
 | ||||
|     void set_action_icon(); | ||||
| 
 | ||||
|     void    update_settings_digest_bitmaps(); | ||||
| 	bool    update_settings_digest(const std::vector<std::string>& categories); | ||||
|     int     volume_type() const { return int(m_volume_type); } | ||||
|     void    msw_rescale(); | ||||
|     void        update_settings_digest_bitmaps(); | ||||
| 	bool        update_settings_digest(const std::vector<std::string>& categories); | ||||
|     int         volume_type() const { return int(m_volume_type); } | ||||
|     void        msw_rescale(); | ||||
| private: | ||||
|     friend class ObjectDataViewModel; | ||||
| }; | ||||
|  | @ -398,6 +371,7 @@ class ObjectDataViewModel :public wxDataViewModel | |||
| { | ||||
| 	std::vector<ObjectDataViewModelNode*>       m_objects; | ||||
|     std::vector<wxBitmap*>                      m_volume_bmps; | ||||
|     wxBitmap*                                   m_warning_bmp; | ||||
| 
 | ||||
|     wxDataViewCtrl*                             m_ctrl{ nullptr }; | ||||
| 
 | ||||
|  | @ -405,10 +379,13 @@ public: | |||
|     ObjectDataViewModel(); | ||||
|     ~ObjectDataViewModel(); | ||||
| 
 | ||||
| 	wxDataViewItem Add(const wxString &name, const int extruder); | ||||
| 	wxDataViewItem Add( const wxString &name,  | ||||
|                         const int extruder, | ||||
|                         const bool has_errors = false); | ||||
|     wxDataViewItem AddVolumeChild(  const wxDataViewItem &parent_item, | ||||
|                                     const wxString &name, | ||||
|                                     const Slic3r::ModelVolumeType volume_type, | ||||
|                                     const bool has_errors = false, | ||||
|                                     const int extruder = 0, | ||||
|                                     const bool create_frst_child = true); | ||||
|     wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item); | ||||
|  | @ -480,11 +457,16 @@ public: | |||
|                                     const std::vector<std::string>& categories); | ||||
| 
 | ||||
|     void    SetVolumeBitmaps(const std::vector<wxBitmap*>& volume_bmps) { m_volume_bmps = volume_bmps; } | ||||
|     void    SetWarningBitmap(wxBitmap* bitmap)                          { m_warning_bmp = bitmap; } | ||||
|     void    SetVolumeType(const wxDataViewItem &item, const Slic3r::ModelVolumeType type); | ||||
| 
 | ||||
|     void    SetAssociatedControl(wxDataViewCtrl* ctrl) { m_ctrl = ctrl; } | ||||
|     // Rescale bitmaps for existing Items
 | ||||
|     void    Rescale(); | ||||
| 
 | ||||
|     wxBitmap    GetVolumeIcon(const Slic3r::ModelVolumeType vol_type,  | ||||
|                               const bool is_marked = false); | ||||
|     void        DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object = false); | ||||
| }; | ||||
| 
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 bubnikv
						bubnikv