Merge remote-tracking branch 'origin/master' into ys_sla_time_estimation
| 
		 Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 70 KiB  | 
| 
		 Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 58 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								resources/icons/printers/PrusaResearch_MK2.5S.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 70 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								resources/icons/printers/PrusaResearch_MK2.5SMMU2S.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 58 KiB  | 
| 
		 Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 65 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								resources/icons/printers/PrusaResearch_MK3S.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 65 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								resources/icons/printers/PrusaResearch_MK3SMMU2S.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 70 KiB  | 
| 
		 Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 55 KiB  | 
| 
						 | 
				
			
			@ -1,8 +1,14 @@
 | 
			
		|||
min_slic3r_version = 1.42.0-alpha6
 | 
			
		||||
0.8.0-alpha6
 | 
			
		||||
min_slic3r_version = 1.42.0-alpha
 | 
			
		||||
0.8.0-alpha
 | 
			
		||||
0.4.0-alpha4 Updated SLA profiles
 | 
			
		||||
0.4.0-alpha3 Update of SLA profiles
 | 
			
		||||
0.4.0-alpha2 First SLA profiles
 | 
			
		||||
min_slic3r_version = 1.41.3-alpha
 | 
			
		||||
0.4.0 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt 
 | 
			
		||||
min_slic3r_version = 1.41.1
 | 
			
		||||
0.3.4 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt 
 | 
			
		||||
0.3.3 Prusament PETG released
 | 
			
		||||
0.3.2 New MK2.5 and MK3 FW versions
 | 
			
		||||
0.3.1 New MK2.5 and MK3 FW versions
 | 
			
		||||
| 
						 | 
				
			
			@ -34,6 +40,7 @@ min_slic3r_version = 1.41.0-alpha
 | 
			
		|||
0.2.0-alpha1 added initial profiles for the i3 MK3 Multi Material Upgrade 2.0
 | 
			
		||||
0.2.0-alpha moved machine limits from the start G-code to the new print profile parameters
 | 
			
		||||
min_slic3r_version = 1.40.0
 | 
			
		||||
0.1.12 New MK2.5 and MK3 FW versions
 | 
			
		||||
0.1.11 fw version changed to 3.3.1
 | 
			
		||||
0.1.10 MK3 jerk and acceleration update
 | 
			
		||||
0.1.9 edited support extrusion width for 0.25 and 0.6 nozzles
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -323,7 +323,7 @@ namespace Slic3r {
 | 
			
		|||
        typedef std::map<int, ObjectMetadata> IdToMetadataMap;
 | 
			
		||||
        typedef std::map<int, Geometry> IdToGeometryMap;
 | 
			
		||||
        typedef std::map<int, std::vector<coordf_t>> IdToLayerHeightsProfileMap;
 | 
			
		||||
        typedef std::map<int, std::vector<Vec3f>> IdToSlaSupportPointsMap;
 | 
			
		||||
        typedef std::map<int, std::vector<sla::SupportPoint>> IdToSlaSupportPointsMap;
 | 
			
		||||
 | 
			
		||||
        // Version of the 3mf file
 | 
			
		||||
        unsigned int m_version;
 | 
			
		||||
| 
						 | 
				
			
			@ -776,10 +776,19 @@ namespace Slic3r {
 | 
			
		|||
            std::vector<std::string> objects;
 | 
			
		||||
            boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off);
 | 
			
		||||
 | 
			
		||||
            // Info on format versioning - see 3mf.hpp
 | 
			
		||||
            int version = 0;
 | 
			
		||||
            if (!objects.empty() && objects[0].find("support_points_format_version=") != std::string::npos) {
 | 
			
		||||
                objects[0].erase(objects[0].begin(), objects[0].begin() + 30); // removes the string
 | 
			
		||||
                version = std::stoi(objects[0]);
 | 
			
		||||
                objects.erase(objects.begin()); // pop the header
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            for (const std::string& object : objects)
 | 
			
		||||
            {
 | 
			
		||||
                std::vector<std::string> object_data;
 | 
			
		||||
                boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off);
 | 
			
		||||
 | 
			
		||||
                if (object_data.size() != 2)
 | 
			
		||||
                {
 | 
			
		||||
                    add_error("Error while reading object data");
 | 
			
		||||
| 
						 | 
				
			
			@ -811,10 +820,24 @@ namespace Slic3r {
 | 
			
		|||
                std::vector<std::string> object_data_points;
 | 
			
		||||
                boost::split(object_data_points, object_data[1], boost::is_any_of(" "), boost::token_compress_off);
 | 
			
		||||
 | 
			
		||||
                std::vector<Vec3f> sla_support_points;
 | 
			
		||||
                std::vector<sla::SupportPoint> sla_support_points;
 | 
			
		||||
 | 
			
		||||
                for (unsigned int i=0; i<object_data_points.size(); i+=3)
 | 
			
		||||
                    sla_support_points.push_back(Vec3d(std::atof(object_data_points[i+0].c_str()), std::atof(object_data_points[i+1].c_str()), std::atof(object_data_points[i+2].c_str())).cast<float>());
 | 
			
		||||
                if (version == 0) {
 | 
			
		||||
                    for (unsigned int i=0; i<object_data_points.size(); i+=3)
 | 
			
		||||
                    sla_support_points.emplace_back(std::atof(object_data_points[i+0].c_str()),
 | 
			
		||||
                                                    std::atof(object_data_points[i+1].c_str()),
 | 
			
		||||
                                                    std::atof(object_data_points[i+2].c_str()),
 | 
			
		||||
                                                    0.4f,
 | 
			
		||||
                                                    false);
 | 
			
		||||
                }
 | 
			
		||||
                if (version == 1) {
 | 
			
		||||
                    for (unsigned int i=0; i<object_data_points.size(); i+=5)
 | 
			
		||||
                    sla_support_points.emplace_back(std::atof(object_data_points[i+0].c_str()),
 | 
			
		||||
                                                    std::atof(object_data_points[i+1].c_str()),
 | 
			
		||||
                                                    std::atof(object_data_points[i+2].c_str()),
 | 
			
		||||
                                                    std::atof(object_data_points[i+3].c_str()),
 | 
			
		||||
                                                    std::atof(object_data_points[i+4].c_str()));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (!sla_support_points.empty())
 | 
			
		||||
                    m_sla_support_points.insert(IdToSlaSupportPointsMap::value_type(object_id, sla_support_points));
 | 
			
		||||
| 
						 | 
				
			
			@ -1961,7 +1984,7 @@ namespace Slic3r {
 | 
			
		|||
        for (const ModelObject* object : model.objects)
 | 
			
		||||
        {
 | 
			
		||||
            ++count;
 | 
			
		||||
            const std::vector<Vec3f>& sla_support_points = object->sla_support_points;
 | 
			
		||||
            const std::vector<sla::SupportPoint>& sla_support_points = object->sla_support_points;
 | 
			
		||||
            if (!sla_support_points.empty())
 | 
			
		||||
            {
 | 
			
		||||
                sprintf(buffer, "object_id=%d|", count);
 | 
			
		||||
| 
						 | 
				
			
			@ -1970,7 +1993,7 @@ namespace Slic3r {
 | 
			
		|||
                // Store the layer height profile as a single space separated list.
 | 
			
		||||
                for (size_t i = 0; i < sla_support_points.size(); ++i)
 | 
			
		||||
                {
 | 
			
		||||
                    sprintf(buffer, (i==0 ? "%f %f %f" : " %f %f %f"),  sla_support_points[i](0), sla_support_points[i](1), sla_support_points[i](2));
 | 
			
		||||
                    sprintf(buffer, (i==0 ? "%f %f %f %f %f" : " %f %f %f %f %f"),  sla_support_points[i].pos(0), sla_support_points[i].pos(1), sla_support_points[i].pos(2), sla_support_points[i].head_front_radius, (float)sla_support_points[i].is_new_island);
 | 
			
		||||
                    out += buffer;
 | 
			
		||||
                }
 | 
			
		||||
                out += "\n";
 | 
			
		||||
| 
						 | 
				
			
			@ -1979,6 +2002,9 @@ namespace Slic3r {
 | 
			
		|||
 | 
			
		||||
        if (!out.empty())
 | 
			
		||||
        {
 | 
			
		||||
            // Adds version header at the beginning:
 | 
			
		||||
            out = std::string("support_points_format_version=") + std::to_string(support_points_format_version) + std::string("\n") + out;
 | 
			
		||||
 | 
			
		||||
            if (!mz_zip_writer_add_mem(&archive, SLA_SUPPORT_POINTS_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION))
 | 
			
		||||
            {
 | 
			
		||||
                add_error("Unable to add sla support points file to archive");
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,23 @@
 | 
			
		|||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
 | 
			
		||||
    /* The format for saving the SLA points was changing in the past. This enum holds the latest version that is being currently used.
 | 
			
		||||
     * Examples of the Slic3r_PE_sla_support_points.txt for historically used versions:
 | 
			
		||||
 | 
			
		||||
     *  version 0 : object_id=1|-12.055421 -2.658771 10.000000
 | 
			
		||||
                    object_id=2|-14.051745 -3.570338 5.000000
 | 
			
		||||
        // no header and x,y,z positions of the points)
 | 
			
		||||
 | 
			
		||||
     * version 1 :  ThreeMF_support_points_version=1
 | 
			
		||||
                    object_id=1|-12.055421 -2.658771 10.000000 0.4 0.0
 | 
			
		||||
                    object_id=2|-14.051745 -3.570338 5.000000 0.6 1.0
 | 
			
		||||
        // introduced header with version number; x,y,z,head_size,is_new_island)
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    enum {
 | 
			
		||||
        support_points_format_version = 1
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    class Model;
 | 
			
		||||
    class DynamicPrintConfig;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -583,7 +583,7 @@ void AMFParserContext::endElement(const char * /* name */)
 | 
			
		|||
            else if (m_path.size() == 3 && m_path[1] == NODE_TYPE_OBJECT && m_object && strcmp(opt_key, "sla_support_points") == 0) {
 | 
			
		||||
                // Parse object's layer height profile, a semicolon separated list of floats.
 | 
			
		||||
                unsigned char coord_idx = 0;
 | 
			
		||||
                Vec3f point(Vec3f::Zero());
 | 
			
		||||
                Eigen::Matrix<float, 5, 1, Eigen::DontAlign> point(Eigen::Matrix<float, 5, 1, Eigen::DontAlign>::Zero());
 | 
			
		||||
                char *p = const_cast<char*>(m_value[1].c_str());
 | 
			
		||||
                for (;;) {
 | 
			
		||||
                    char *end = strchr(p, ';');
 | 
			
		||||
| 
						 | 
				
			
			@ -591,8 +591,8 @@ void AMFParserContext::endElement(const char * /* name */)
 | 
			
		|||
	                    *end = 0;
 | 
			
		||||
 | 
			
		||||
                    point(coord_idx) = atof(p);
 | 
			
		||||
                    if (++coord_idx == 3) {
 | 
			
		||||
                        m_object->sla_support_points.push_back(point);
 | 
			
		||||
                    if (++coord_idx == 5) {
 | 
			
		||||
                        m_object->sla_support_points.push_back(sla::SupportPoint(point));
 | 
			
		||||
                        coord_idx = 0;
 | 
			
		||||
                    }
 | 
			
		||||
					if (end == nullptr)
 | 
			
		||||
| 
						 | 
				
			
			@ -900,14 +900,14 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config)
 | 
			
		|||
        }
 | 
			
		||||
        //FIXME Store the layer height ranges (ModelObject::layer_height_ranges)
 | 
			
		||||
 | 
			
		||||
        const std::vector<Vec3f>& sla_support_points = object->sla_support_points;
 | 
			
		||||
        const std::vector<sla::SupportPoint>& sla_support_points = object->sla_support_points;
 | 
			
		||||
        if (!sla_support_points.empty()) {
 | 
			
		||||
            // Store the SLA supports as a single semicolon separated list.
 | 
			
		||||
            stream << "    <metadata type=\"slic3r.sla_support_points\">";
 | 
			
		||||
            for (size_t i = 0; i < sla_support_points.size(); ++i) {
 | 
			
		||||
                if (i != 0)
 | 
			
		||||
                    stream << ";";
 | 
			
		||||
                stream << sla_support_points[i](0) << ";" << sla_support_points[i](1) << ";" << sla_support_points[i](2);
 | 
			
		||||
                stream << sla_support_points[i].pos(0) << ";" << sla_support_points[i].pos(1) << ";" << sla_support_points[i].pos(2) << ";" << sla_support_points[i].head_front_radius << ";" << sla_support_points[i].is_new_island;
 | 
			
		||||
            }
 | 
			
		||||
            stream << "\n    </metadata>\n";
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,6 +12,7 @@
 | 
			
		|||
#include <utility>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include "Geometry.hpp"
 | 
			
		||||
#include <libslic3r/SLA/SLACommon.hpp>
 | 
			
		||||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -175,7 +176,8 @@ public:
 | 
			
		|||
 | 
			
		||||
    // This vector holds position of selected support points for SLA. The data are
 | 
			
		||||
    // saved in mesh coordinates to allow using them for several instances.
 | 
			
		||||
    std::vector<Vec3f>      sla_support_points;
 | 
			
		||||
    // The format is (x, y, z, point_size, supports_island)
 | 
			
		||||
    std::vector<sla::SupportPoint>      sla_support_points;
 | 
			
		||||
 | 
			
		||||
    /* This vector accumulates the total translation applied to the object by the
 | 
			
		||||
        center_around_origin() method. Callers might want to apply the same translation
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,6 +22,7 @@ typedef Point Vector;
 | 
			
		|||
// Vector types with a fixed point coordinate base type.
 | 
			
		||||
typedef Eigen::Matrix<coord_t,  2, 1, Eigen::DontAlign> Vec2crd;
 | 
			
		||||
typedef Eigen::Matrix<coord_t,  3, 1, Eigen::DontAlign> Vec3crd;
 | 
			
		||||
typedef Eigen::Matrix<int,      2, 1, Eigen::DontAlign> Vec2i;
 | 
			
		||||
typedef Eigen::Matrix<int,      3, 1, Eigen::DontAlign> Vec3i;
 | 
			
		||||
typedef Eigen::Matrix<int64_t,  2, 1, Eigen::DontAlign> Vec2i64;
 | 
			
		||||
typedef Eigen::Matrix<int64_t,  3, 1, Eigen::DontAlign> Vec3i64;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -244,8 +244,9 @@ public:
 | 
			
		|||
        // Bitmap of flags.
 | 
			
		||||
        enum FlagBits {
 | 
			
		||||
            DEFAULT,
 | 
			
		||||
            NO_RELOAD_SCENE = 0,
 | 
			
		||||
            RELOAD_SCENE = 1,
 | 
			
		||||
            NO_RELOAD_SCENE                 = 0,
 | 
			
		||||
            RELOAD_SCENE                    = 1 << 1,
 | 
			
		||||
            RELOAD_SLA_SUPPORT_POINTS       = 1 << 2,
 | 
			
		||||
        };
 | 
			
		||||
        // Bitmap of FlagBits
 | 
			
		||||
        unsigned int    flags;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2664,28 +2664,19 @@ void PrintConfigDef::init_sla_params()
 | 
			
		|||
    def->min = 0;
 | 
			
		||||
    def->default_value = new ConfigOptionFloat(5.0);
 | 
			
		||||
 | 
			
		||||
    def = this->add("support_density_at_horizontal", coInt);
 | 
			
		||||
    def->label = L("Density on horizontal surfaces");
 | 
			
		||||
    def = this->add("support_points_density_relative", coInt);
 | 
			
		||||
    def->label = L("Support points density");
 | 
			
		||||
    def->category = L("Supports");
 | 
			
		||||
    def->tooltip = L("How many support points (approximately) should be placed on horizontal surface.");
 | 
			
		||||
    def->sidetext = L("points per square dm");
 | 
			
		||||
    def->tooltip = L("This is a relative measure of support points density.");
 | 
			
		||||
    def->sidetext = L("%");
 | 
			
		||||
    def->cli = "";
 | 
			
		||||
    def->min = 0;
 | 
			
		||||
    def->default_value = new ConfigOptionInt(500);
 | 
			
		||||
    def->default_value = new ConfigOptionInt(100);
 | 
			
		||||
 | 
			
		||||
    def = this->add("support_density_at_45", coInt);
 | 
			
		||||
    def->label = L("Density on surfaces at 45 degrees");
 | 
			
		||||
    def = this->add("support_points_minimal_distance", coFloat);
 | 
			
		||||
    def->label = L("Minimal distance of the support points");
 | 
			
		||||
    def->category = L("Supports");
 | 
			
		||||
    def->tooltip = L("How many support points (approximately) should be placed on surface sloping at 45 degrees.");
 | 
			
		||||
    def->sidetext = L("points per square dm");
 | 
			
		||||
    def->cli = "";
 | 
			
		||||
    def->min = 0;
 | 
			
		||||
    def->default_value = new ConfigOptionInt(250);
 | 
			
		||||
 | 
			
		||||
    def = this->add("support_minimal_z", coFloat);
 | 
			
		||||
    def->label = L("Minimal support point height");
 | 
			
		||||
    def->category = L("Supports");
 | 
			
		||||
    def->tooltip = L("No support points will be placed lower than this value from the bottom.");
 | 
			
		||||
    def->tooltip = L("No support points will be placed closer than this threshold.");
 | 
			
		||||
    def->sidetext = L("mm");
 | 
			
		||||
    def->cli = "";
 | 
			
		||||
    def->min = 0;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1005,9 +1005,8 @@ public:
 | 
			
		|||
    ConfigOptionFloat support_object_elevation /*= 5.0*/;
 | 
			
		||||
 | 
			
		||||
    /////// Following options influence automatic support points placement:
 | 
			
		||||
    ConfigOptionInt support_density_at_horizontal;
 | 
			
		||||
    ConfigOptionInt support_density_at_45;
 | 
			
		||||
    ConfigOptionFloat support_minimal_z;
 | 
			
		||||
    ConfigOptionInt support_points_density_relative;
 | 
			
		||||
    ConfigOptionFloat support_points_minimal_distance;
 | 
			
		||||
 | 
			
		||||
    // Now for the base pool (pad) /////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1044,9 +1043,8 @@ protected:
 | 
			
		|||
        OPT_PTR(support_base_height);
 | 
			
		||||
        OPT_PTR(support_critical_angle);
 | 
			
		||||
        OPT_PTR(support_max_bridge_length);
 | 
			
		||||
        OPT_PTR(support_density_at_horizontal);
 | 
			
		||||
        OPT_PTR(support_density_at_45);
 | 
			
		||||
        OPT_PTR(support_minimal_z);
 | 
			
		||||
        OPT_PTR(support_points_density_relative);
 | 
			
		||||
        OPT_PTR(support_points_minimal_distance);
 | 
			
		||||
        OPT_PTR(support_object_elevation);
 | 
			
		||||
        OPT_PTR(pad_enable);
 | 
			
		||||
        OPT_PTR(pad_wall_thickness);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,47 +1,23 @@
 | 
			
		|||
#include "igl/random_points_on_mesh.h"
 | 
			
		||||
#include "igl/AABB.h"
 | 
			
		||||
 | 
			
		||||
#include <tbb/parallel_for.h>
 | 
			
		||||
 | 
			
		||||
#include "SLAAutoSupports.hpp"
 | 
			
		||||
#include "Model.hpp"
 | 
			
		||||
#include "ExPolygon.hpp"
 | 
			
		||||
#include "SVG.hpp"
 | 
			
		||||
#include "Point.hpp"
 | 
			
		||||
#include "ClipperUtils.hpp"
 | 
			
		||||
#include "Tesselate.hpp"
 | 
			
		||||
#include "libslic3r.h"
 | 
			
		||||
 | 
			
		||||
#include <iostream>
 | 
			
		||||
#include <random>
 | 
			
		||||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
 | 
			
		||||
SLAAutoSupports::SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector<ExPolygons>& slices, const std::vector<float>& heights, 
 | 
			
		||||
    const Config& config, std::function<void(void)> throw_on_cancel)
 | 
			
		||||
: m_config(config), m_V(emesh.V()), m_F(emesh.F()), m_throw_on_cancel(throw_on_cancel)
 | 
			
		||||
{
 | 
			
		||||
    // FIXME: It might be safer to get rid of the rand() calls altogether, because it is probably
 | 
			
		||||
    // not always thread-safe and can be slow if it is.
 | 
			
		||||
    srand(time(NULL)); // rand() is used by igl::random_point_on_mesh
 | 
			
		||||
 | 
			
		||||
    // Find all separate islands that will need support. The coord_t number denotes height
 | 
			
		||||
    // of a point just below the mesh (so that we can later project the point precisely
 | 
			
		||||
    // on the mesh by raycasting (done by igl) and not risking we will place the point inside).
 | 
			
		||||
    std::vector<std::pair<ExPolygon, coord_t>> islands = find_islands(slices, heights);
 | 
			
		||||
 | 
			
		||||
    // Uniformly cover each of the islands with support points.
 | 
			
		||||
    for (const auto& island : islands) {
 | 
			
		||||
        std::vector<Vec3d> points = uniformly_cover(island);
 | 
			
		||||
        m_throw_on_cancel();
 | 
			
		||||
        project_upward_onto_mesh(points);
 | 
			
		||||
        m_output.insert(m_output.end(), points.begin(), points.end());
 | 
			
		||||
        m_throw_on_cancel();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // We are done with the islands. Let's sprinkle the rest of the mesh.
 | 
			
		||||
    // The function appends to m_output.
 | 
			
		||||
    sprinkle_mesh(mesh);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
float SLAAutoSupports::approximate_geodesic_distance(const Vec3d& p1, const Vec3d& p2, Vec3d& n1, Vec3d& n2)
 | 
			
		||||
/*float SLAAutoSupports::approximate_geodesic_distance(const Vec3d& p1, const Vec3d& p2, Vec3d& n1, Vec3d& n2)
 | 
			
		||||
{
 | 
			
		||||
    n1.normalize();
 | 
			
		||||
    n2.normalize();
 | 
			
		||||
| 
						 | 
				
			
			@ -59,115 +35,6 @@ float SLAAutoSupports::approximate_geodesic_distance(const Vec3d& p1, const Vec3
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void SLAAutoSupports::sprinkle_mesh(const TriangleMesh& mesh)
 | 
			
		||||
{
 | 
			
		||||
    std::vector<Vec3d> points;
 | 
			
		||||
    // Loads the ModelObject raw_mesh and transforms it by first instance's transformation matrix (disregarding translation).
 | 
			
		||||
    // Instances only differ in z-rotation, so it does not matter which of them will be used for the calculation.
 | 
			
		||||
    // The supports point will be calculated on this mesh (so scaling ang vertical direction is correctly accounted for).
 | 
			
		||||
    // Results will be inverse-transformed to raw_mesh coordinates.
 | 
			
		||||
    //TriangleMesh mesh = m_model_object.raw_mesh();
 | 
			
		||||
    //Transform3d transformation_matrix = m_model_object.instances[0]->get_matrix(true/*dont_translate*/);
 | 
			
		||||
    //mesh.transform(transformation_matrix);
 | 
			
		||||
 | 
			
		||||
    // Check that the object is thick enough to produce any support points
 | 
			
		||||
    BoundingBoxf3 bb = mesh.bounding_box();
 | 
			
		||||
    if (bb.size()(2) < m_config.minimal_z)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    // All points that we curretly have must be transformed too, so distance to them is correcly calculated.
 | 
			
		||||
    //for (Vec3f& point : m_model_object.sla_support_points)
 | 
			
		||||
    //    point = transformation_matrix.cast<float>() * point;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // In order to calculate distance to already placed points, we must keep know which facet the point lies on.
 | 
			
		||||
    std::vector<Vec3d> facets_normals;
 | 
			
		||||
 | 
			
		||||
    // Only points belonging to islands were added so far - they all lie on horizontal surfaces:
 | 
			
		||||
    for (unsigned int i=0; i<m_output.size(); ++i)
 | 
			
		||||
        facets_normals.push_back(Vec3d(0,0,-1));
 | 
			
		||||
 | 
			
		||||
    // The AABB hierarchy will be used to find normals of already placed points.
 | 
			
		||||
    // The points added automatically will just push_back the new normal on the fly.
 | 
			
		||||
    /*igl::AABB<Eigen::MatrixXf,3> aabb;
 | 
			
		||||
    aabb.init(V, F);
 | 
			
		||||
    for (unsigned int i=0; i<m_model_object.sla_support_points.size(); ++i) {
 | 
			
		||||
        int facet_idx = 0;
 | 
			
		||||
        Eigen::Matrix<float, 1, 3> dump;
 | 
			
		||||
        Eigen::MatrixXf query_point = m_model_object.sla_support_points[i];
 | 
			
		||||
        aabb.squared_distance(V, F, query_point, facet_idx, dump);
 | 
			
		||||
        Vec3f a1 = V.row(F(facet_idx,1)) - V.row(F(facet_idx,0));
 | 
			
		||||
        Vec3f a2 = V.row(F(facet_idx,2)) - V.row(F(facet_idx,0));
 | 
			
		||||
        Vec3f normal = a1.cross(a2);
 | 
			
		||||
        normal.normalize();
 | 
			
		||||
        facets_normals.push_back(normal);
 | 
			
		||||
    }*/
 | 
			
		||||
 | 
			
		||||
    // New potential support point is randomly generated on the mesh and distance to all already placed points is calculated.
 | 
			
		||||
    // In case it is never smaller than certain limit (depends on the new point's facet normal), the point is accepted.
 | 
			
		||||
    // The process stops after certain number of points is refused in a row.
 | 
			
		||||
    Vec3d point;
 | 
			
		||||
    Vec3d normal;
 | 
			
		||||
    int added_points = 0;
 | 
			
		||||
    int refused_points = 0;
 | 
			
		||||
    const int refused_limit = 30;
 | 
			
		||||
    // Angle at which the density reaches zero:
 | 
			
		||||
    const float threshold_angle = std::min(M_PI_2, M_PI_4 * acos(0.f/m_config.density_at_horizontal) / acos(m_config.density_at_45/m_config.density_at_horizontal));
 | 
			
		||||
 | 
			
		||||
    size_t cancel_test_cntr = 0;
 | 
			
		||||
    while (refused_points < refused_limit) {
 | 
			
		||||
        if (++ cancel_test_cntr == 500) {
 | 
			
		||||
            // Don't call the cancellation routine too often as the multi-core cache synchronization
 | 
			
		||||
            // may be pretty expensive.
 | 
			
		||||
            m_throw_on_cancel();
 | 
			
		||||
            cancel_test_cntr = 0;
 | 
			
		||||
        }
 | 
			
		||||
        // Place a random point on the mesh and calculate corresponding facet's normal:
 | 
			
		||||
        Eigen::VectorXi FI;
 | 
			
		||||
        Eigen::MatrixXd B;
 | 
			
		||||
        igl::random_points_on_mesh(1, m_V, m_F, B, FI);
 | 
			
		||||
        point = B(0,0)*m_V.row(m_F(FI(0),0)) +
 | 
			
		||||
                B(0,1)*m_V.row(m_F(FI(0),1)) +
 | 
			
		||||
                B(0,2)*m_V.row(m_F(FI(0),2));
 | 
			
		||||
        if (point(2) - bb.min(2) < m_config.minimal_z)
 | 
			
		||||
            continue;
 | 
			
		||||
 | 
			
		||||
        Vec3d a1 = m_V.row(m_F(FI(0),1)) - m_V.row(m_F(FI(0),0));
 | 
			
		||||
        Vec3d a2 = m_V.row(m_F(FI(0),2)) - m_V.row(m_F(FI(0),0));
 | 
			
		||||
        normal = a1.cross(a2);
 | 
			
		||||
        normal.normalize();
 | 
			
		||||
 | 
			
		||||
        // calculate angle between the normal and vertical:
 | 
			
		||||
        float angle = angle_from_normal(normal.cast<float>());
 | 
			
		||||
        if (angle > threshold_angle)
 | 
			
		||||
            continue;
 | 
			
		||||
 | 
			
		||||
        const float limit = distance_limit(angle);
 | 
			
		||||
        bool add_it = true;
 | 
			
		||||
        for (unsigned int i=0; i<points.size(); ++i) {
 | 
			
		||||
            if (approximate_geodesic_distance(points[i], point, facets_normals[i], normal) < limit) {
 | 
			
		||||
                add_it = false;
 | 
			
		||||
                ++refused_points;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (add_it) {
 | 
			
		||||
            points.push_back(point.cast<double>());
 | 
			
		||||
            facets_normals.push_back(normal);
 | 
			
		||||
            ++added_points;
 | 
			
		||||
            refused_points = 0;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    m_output.insert(m_output.end(), points.begin(), points.end());
 | 
			
		||||
 | 
			
		||||
    // Now transform all support points to mesh coordinates:
 | 
			
		||||
    //for (Vec3f& point : m_model_object.sla_support_points)
 | 
			
		||||
    //    point = transformation_matrix.inverse().cast<float>() * point;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
float SLAAutoSupports::get_required_density(float angle) const
 | 
			
		||||
{
 | 
			
		||||
    // calculation would be density_0 * cos(angle). To provide one more degree of freedom, we will scale the angle
 | 
			
		||||
| 
						 | 
				
			
			@ -179,10 +46,470 @@ float SLAAutoSupports::get_required_density(float angle) const
 | 
			
		|||
float SLAAutoSupports::distance_limit(float angle) const
 | 
			
		||||
{
 | 
			
		||||
    return 1./(2.4*get_required_density(angle));
 | 
			
		||||
}*/
 | 
			
		||||
 | 
			
		||||
SLAAutoSupports::SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector<ExPolygons>& slices, const std::vector<float>& heights,
 | 
			
		||||
                                   const Config& config, std::function<void(void)> throw_on_cancel)
 | 
			
		||||
: m_config(config), m_emesh(emesh), m_throw_on_cancel(throw_on_cancel)
 | 
			
		||||
{
 | 
			
		||||
    process(slices, heights);
 | 
			
		||||
    project_onto_mesh(m_output);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SLAAutoSupports::project_onto_mesh(std::vector<sla::SupportPoint>& points) const
 | 
			
		||||
{
 | 
			
		||||
    // The function  makes sure that all the points are really exactly placed on the mesh.
 | 
			
		||||
    igl::Hit hit_up{0, 0, 0.f, 0.f, 0.f};
 | 
			
		||||
    igl::Hit hit_down{0, 0, 0.f, 0.f, 0.f};
 | 
			
		||||
 | 
			
		||||
    // Use a reasonable granularity to account for the worker thread synchronization cost.
 | 
			
		||||
    tbb::parallel_for(tbb::blocked_range<size_t>(0, points.size(), 64),
 | 
			
		||||
        [this, &points](const tbb::blocked_range<size_t>& range) {
 | 
			
		||||
            for (size_t point_id = range.begin(); point_id < range.end(); ++ point_id) {
 | 
			
		||||
                if ((point_id % 16) == 0)
 | 
			
		||||
                    // Don't call the following function too often as it flushes CPU write caches due to synchronization primitves.
 | 
			
		||||
                    m_throw_on_cancel();
 | 
			
		||||
                Vec3f& p = points[point_id].pos;
 | 
			
		||||
                // Project the point upward and downward and choose the closer intersection with the mesh.
 | 
			
		||||
                //bool up   = igl::ray_mesh_intersect(p.cast<float>(), Vec3f(0., 0., 1.), m_V, m_F, hit_up);
 | 
			
		||||
                //bool down = igl::ray_mesh_intersect(p.cast<float>(), Vec3f(0., 0., -1.), m_V, m_F, hit_down);
 | 
			
		||||
 | 
			
		||||
                sla::EigenMesh3D::hit_result hit_up   = m_emesh.query_ray_hit(p.cast<double>(), Vec3d(0., 0., 1.));
 | 
			
		||||
                sla::EigenMesh3D::hit_result hit_down = m_emesh.query_ray_hit(p.cast<double>(), Vec3d(0., 0., -1.));
 | 
			
		||||
 | 
			
		||||
                bool up   = hit_up.face() != -1;
 | 
			
		||||
                bool down = hit_down.face() != -1;
 | 
			
		||||
 | 
			
		||||
                if (!up && !down)
 | 
			
		||||
                    continue;
 | 
			
		||||
 | 
			
		||||
                sla::EigenMesh3D::hit_result& hit = (!down || (hit_up.distance() < hit_down.distance())) ? hit_up : hit_down;
 | 
			
		||||
                //int fid = hit.face();
 | 
			
		||||
                //Vec3f bc(1-hit.u-hit.v, hit.u, hit.v);
 | 
			
		||||
                //p = (bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2))).cast<float>();
 | 
			
		||||
 | 
			
		||||
                p = p + (hit.distance() * hit.direction()).cast<float>();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static std::vector<SLAAutoSupports::MyLayer> make_layers(
 | 
			
		||||
    const std::vector<ExPolygons>& slices, const std::vector<float>& heights,
 | 
			
		||||
    std::function<void(void)> throw_on_cancel)
 | 
			
		||||
{
 | 
			
		||||
    assert(slices.size() == heights.size());
 | 
			
		||||
 | 
			
		||||
    // Allocate empty layers.
 | 
			
		||||
    std::vector<SLAAutoSupports::MyLayer> layers;
 | 
			
		||||
    layers.reserve(slices.size());
 | 
			
		||||
    for (size_t i = 0; i < slices.size(); ++ i)
 | 
			
		||||
		layers.emplace_back(i, heights[i]);
 | 
			
		||||
 | 
			
		||||
    // FIXME: calculate actual pixel area from printer config:
 | 
			
		||||
    //const float pixel_area = pow(wxGetApp().preset_bundle->project_config.option<ConfigOptionFloat>("display_width") / wxGetApp().preset_bundle->project_config.option<ConfigOptionInt>("display_pixels_x"), 2.f); //
 | 
			
		||||
    const float pixel_area = pow(0.047f, 2.f);
 | 
			
		||||
 | 
			
		||||
    // Use a reasonable granularity to account for the worker thread synchronization cost.
 | 
			
		||||
    tbb::parallel_for(tbb::blocked_range<size_t>(0, layers.size(), 32),
 | 
			
		||||
        [&layers, &slices, &heights, pixel_area, throw_on_cancel](const tbb::blocked_range<size_t>& range) {
 | 
			
		||||
            for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) {
 | 
			
		||||
                if ((layer_id % 8) == 0)
 | 
			
		||||
                    // Don't call the following function too often as it flushes CPU write caches due to synchronization primitves.
 | 
			
		||||
                    throw_on_cancel();
 | 
			
		||||
				SLAAutoSupports::MyLayer &layer   = layers[layer_id];
 | 
			
		||||
                const ExPolygons		 &islands = slices[layer_id];
 | 
			
		||||
                //FIXME WTF?
 | 
			
		||||
                const float height = (layer_id>2 ? heights[layer_id-3] : heights[0]-(heights[1]-heights[0]));
 | 
			
		||||
				layer.islands.reserve(islands.size());
 | 
			
		||||
                for (const ExPolygon &island : islands) {
 | 
			
		||||
                    float area = float(island.area() * SCALING_FACTOR * SCALING_FACTOR);
 | 
			
		||||
                    if (area >= pixel_area)
 | 
			
		||||
                        //FIXME this is not a correct centroid of a polygon with holes.
 | 
			
		||||
						layer.islands.emplace_back(layer, island, get_extents(island.contour), Slic3r::unscale(island.contour.centroid()).cast<float>(), area, height);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
	// Calculate overlap of successive layers. Link overlapping islands.
 | 
			
		||||
	tbb::parallel_for(tbb::blocked_range<size_t>(1, layers.size(), 8),
 | 
			
		||||
		[&layers, &heights, throw_on_cancel](const tbb::blocked_range<size_t>& range) {
 | 
			
		||||
		for (size_t layer_id = range.begin(); layer_id < range.end(); ++layer_id) {
 | 
			
		||||
			if ((layer_id % 2) == 0)
 | 
			
		||||
				// Don't call the following function too often as it flushes CPU write caches due to synchronization primitves.
 | 
			
		||||
				throw_on_cancel();
 | 
			
		||||
			SLAAutoSupports::MyLayer &layer_above = layers[layer_id];
 | 
			
		||||
			SLAAutoSupports::MyLayer &layer_below = layers[layer_id - 1];
 | 
			
		||||
            //FIXME WTF?
 | 
			
		||||
            const float height = (layer_id>2 ? heights[layer_id-3] : heights[0]-(heights[1]-heights[0]));
 | 
			
		||||
            const float layer_height = (layer_id!=0 ? heights[layer_id]-heights[layer_id-1] : heights[0]);
 | 
			
		||||
            const float safe_angle = 5.f * (float(M_PI)/180.f); // smaller number - less supports
 | 
			
		||||
            const float between_layers_offset =  float(scale_(layer_height / std::tan(safe_angle)));
 | 
			
		||||
			//FIXME This has a quadratic time complexity, it will be excessively slow for many tiny islands.
 | 
			
		||||
			for (SLAAutoSupports::Structure &top : layer_above.islands) {
 | 
			
		||||
				for (SLAAutoSupports::Structure &bottom : layer_below.islands) {
 | 
			
		||||
                    float overlap_area = top.overlap_area(bottom);
 | 
			
		||||
                    if (overlap_area > 0) {
 | 
			
		||||
						top.islands_below.emplace_back(&bottom, overlap_area);
 | 
			
		||||
                        bottom.islands_above.emplace_back(&top, overlap_area);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (! top.islands_below.empty()) {
 | 
			
		||||
                    Polygons top_polygons    = to_polygons(*top.polygon);
 | 
			
		||||
					Polygons bottom_polygons = top.polygons_below();
 | 
			
		||||
                    top.overhangs = diff_ex(top_polygons, bottom_polygons);
 | 
			
		||||
                    if (! top.overhangs.empty()) {
 | 
			
		||||
                        top.overhangs_area = 0.f;
 | 
			
		||||
                        std::vector<std::pair<ExPolygon*, float>> expolys_with_areas;
 | 
			
		||||
                        for (ExPolygon &ex : top.overhangs) {
 | 
			
		||||
                            float area = float(ex.area());
 | 
			
		||||
                            expolys_with_areas.emplace_back(&ex, area);
 | 
			
		||||
                            top.overhangs_area += area;
 | 
			
		||||
                        }
 | 
			
		||||
                        std::sort(expolys_with_areas.begin(), expolys_with_areas.end(), 
 | 
			
		||||
                            [](const std::pair<ExPolygon*, float> &p1, const std::pair<ExPolygon*, float> &p2)
 | 
			
		||||
                                { return p1.second > p2.second; });
 | 
			
		||||
                        ExPolygons overhangs_sorted;
 | 
			
		||||
                        for (auto &p : expolys_with_areas)
 | 
			
		||||
                            overhangs_sorted.emplace_back(std::move(*p.first));
 | 
			
		||||
						top.overhangs = std::move(overhangs_sorted);
 | 
			
		||||
                        top.overhangs_area *= float(SCALING_FACTOR * SCALING_FACTOR);
 | 
			
		||||
                        top.dangling_areas = diff_ex(top_polygons, offset(bottom_polygons, between_layers_offset));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
    return layers;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SLAAutoSupports::process(const std::vector<ExPolygons>& slices, const std::vector<float>& heights)
 | 
			
		||||
{
 | 
			
		||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
 | 
			
		||||
    std::vector<std::pair<ExPolygon, coord_t>> islands;
 | 
			
		||||
#endif /* SLA_AUTOSUPPORTS_DEBUG */
 | 
			
		||||
 | 
			
		||||
    std::vector<SLAAutoSupports::MyLayer> layers = make_layers(slices, heights, m_throw_on_cancel);
 | 
			
		||||
 | 
			
		||||
    PointGrid3D point_grid;
 | 
			
		||||
    point_grid.cell_size = Vec3f(10.f, 10.f, 10.f);
 | 
			
		||||
 | 
			
		||||
    for (unsigned int layer_id = 0; layer_id < layers.size(); ++ layer_id) {
 | 
			
		||||
        SLAAutoSupports::MyLayer *layer_top     = &layers[layer_id];
 | 
			
		||||
        SLAAutoSupports::MyLayer *layer_bottom  = (layer_id > 0) ? &layers[layer_id - 1] : nullptr;
 | 
			
		||||
        std::vector<float>        support_force_bottom;
 | 
			
		||||
        if (layer_bottom != nullptr) {
 | 
			
		||||
            support_force_bottom.assign(layer_bottom->islands.size(), 0.f);
 | 
			
		||||
            for (size_t i = 0; i < layer_bottom->islands.size(); ++ i)
 | 
			
		||||
                support_force_bottom[i] = layer_bottom->islands[i].supports_force_total();
 | 
			
		||||
        }
 | 
			
		||||
        for (Structure &top : layer_top->islands)
 | 
			
		||||
			for (Structure::Link &bottom_link : top.islands_below) {
 | 
			
		||||
                Structure &bottom = *bottom_link.island;
 | 
			
		||||
                float centroids_dist = (bottom.centroid - top.centroid).norm();
 | 
			
		||||
                // Penalization resulting from centroid offset:
 | 
			
		||||
//                  bottom.supports_force *= std::min(1.f, 1.f - std::min(1.f, (1600.f * layer_height) * centroids_dist * centroids_dist / bottom.area));
 | 
			
		||||
                float &support_force = support_force_bottom[&bottom - layer_bottom->islands.data()];
 | 
			
		||||
//FIXME this condition does not reflect a bifurcation into a one large island and one tiny island well, it incorrectly resets the support force to zero.
 | 
			
		||||
// One should rather work with the overlap area vs overhang area.                
 | 
			
		||||
//                support_force *= std::min(1.f, 1.f - std::min(1.f, 0.1f * centroids_dist * centroids_dist / bottom.area));
 | 
			
		||||
                // Penalization resulting from increasing polygon area:
 | 
			
		||||
                support_force *= std::min(1.f, 20.f * bottom.area / top.area);
 | 
			
		||||
            }
 | 
			
		||||
        // Let's assign proper support force to each of them:
 | 
			
		||||
        if (layer_id > 0) {
 | 
			
		||||
            for (Structure &below : layer_bottom->islands) {
 | 
			
		||||
                float below_support_force = support_force_bottom[&below - layer_bottom->islands.data()];
 | 
			
		||||
                float above_overlap_area = 0.f;
 | 
			
		||||
				for (Structure::Link &above_link : below.islands_above)
 | 
			
		||||
					above_overlap_area += above_link.overlap_area;
 | 
			
		||||
				for (Structure::Link &above_link : below.islands_above)
 | 
			
		||||
					above_link.island->supports_force_inherited += below_support_force * above_link.overlap_area / above_overlap_area;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // Now iterate over all polygons and append new points if needed.
 | 
			
		||||
        for (Structure &s : layer_top->islands) {
 | 
			
		||||
            // Penalization resulting from large diff from the last layer:
 | 
			
		||||
//            s.supports_force_inherited /= std::max(1.f, (layer_height / 0.3f) * e_area / s.area);
 | 
			
		||||
            s.supports_force_inherited /= std::max(1.f, 0.17f * (s.overhangs_area) / s.area);
 | 
			
		||||
 | 
			
		||||
            float force_deficit = s.support_force_deficit(m_config.tear_pressure());
 | 
			
		||||
            if (s.islands_below.empty()) { // completely new island - needs support no doubt
 | 
			
		||||
                uniformly_cover({ *s.polygon }, s, point_grid, true);
 | 
			
		||||
            } else if (! s.dangling_areas.empty()) {
 | 
			
		||||
                // Let's see if there's anything that overlaps enough to need supports:
 | 
			
		||||
                // What we now have in polygons needs support, regardless of what the forces are, so we can add them.
 | 
			
		||||
                //FIXME is it an island point or not? Vojtech thinks it is.
 | 
			
		||||
                uniformly_cover(s.dangling_areas, s, point_grid);
 | 
			
		||||
            } else if (! s.overhangs.empty()) {
 | 
			
		||||
                //FIXME add the support force deficit as a parameter, only cover until the defficiency is covered.
 | 
			
		||||
                uniformly_cover(s.overhangs, s, point_grid);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        m_throw_on_cancel();
 | 
			
		||||
 | 
			
		||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
 | 
			
		||||
        /*std::string layer_num_str = std::string((i<10 ? "0" : "")) + std::string((i<100 ? "0" : "")) + std::to_string(i);
 | 
			
		||||
        output_expolygons(expolys_top, "top" + layer_num_str + ".svg");
 | 
			
		||||
        output_expolygons(diff, "diff" + layer_num_str + ".svg");
 | 
			
		||||
        if (!islands.empty())
 | 
			
		||||
            output_expolygons(islands, "islands" + layer_num_str + ".svg");*/
 | 
			
		||||
#endif /* SLA_AUTOSUPPORTS_DEBUG */
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<Vec2f> sample_expolygon(const ExPolygon &expoly, float samples_per_mm2, std::mt19937 &rng)
 | 
			
		||||
{    
 | 
			
		||||
    // Triangulate the polygon with holes into triplets of 3D points.
 | 
			
		||||
    std::vector<Vec2f> triangles = Slic3r::triangulate_expolygon_2f(expoly);
 | 
			
		||||
 | 
			
		||||
    std::vector<Vec2f> out;
 | 
			
		||||
    if (! triangles.empty())
 | 
			
		||||
    {
 | 
			
		||||
        // Calculate area of each triangle.
 | 
			
		||||
        std::vector<float> areas;
 | 
			
		||||
        areas.reserve(triangles.size() / 3);
 | 
			
		||||
        for (size_t i = 0; i < triangles.size(); ) {
 | 
			
		||||
            const Vec2f &a  = triangles[i ++];
 | 
			
		||||
            const Vec2f  v1 = triangles[i ++] - a;
 | 
			
		||||
            const Vec2f  v2 = triangles[i ++] - a;
 | 
			
		||||
            areas.emplace_back(0.5f * std::abs(cross2(v1, v2)));
 | 
			
		||||
            if (i != 3)
 | 
			
		||||
                // Prefix sum of the areas.
 | 
			
		||||
                areas.back() += areas[areas.size() - 2];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        size_t num_samples = size_t(ceil(areas.back() * samples_per_mm2));
 | 
			
		||||
        std::uniform_real_distribution<> random_triangle(0., double(areas.back()));
 | 
			
		||||
        std::uniform_real_distribution<> random_float(0., 1.);
 | 
			
		||||
        for (size_t i = 0; i < num_samples; ++ i) {
 | 
			
		||||
            double r = random_triangle(rng);
 | 
			
		||||
            size_t idx_triangle = std::min<size_t>(std::upper_bound(areas.begin(), areas.end(), (float)r) - areas.begin(), areas.size() - 1) * 3;
 | 
			
		||||
            // Select a random point on the triangle.
 | 
			
		||||
            double u = float(sqrt(random_float(rng)));
 | 
			
		||||
            double v = float(random_float(rng));
 | 
			
		||||
            const Vec2f &a = triangles[idx_triangle ++];
 | 
			
		||||
            const Vec2f &b = triangles[idx_triangle++];
 | 
			
		||||
            const Vec2f &c = triangles[idx_triangle];
 | 
			
		||||
            const Vec2f  x = a * (1.f - u) + b * (u * (1.f - v)) + c * (v * u);
 | 
			
		||||
            out.emplace_back(x);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<Vec2f> sample_expolygon_with_boundary(const ExPolygon &expoly, float samples_per_mm2, float samples_per_mm_boundary, std::mt19937 &rng)
 | 
			
		||||
{
 | 
			
		||||
    std::vector<Vec2f> out = sample_expolygon(expoly, samples_per_mm2, rng);
 | 
			
		||||
    double             point_stepping_scaled = scale_(1.f) / samples_per_mm_boundary;
 | 
			
		||||
    for (size_t i_contour = 0; i_contour <= expoly.holes.size(); ++ i_contour) {
 | 
			
		||||
        const Polygon &contour = (i_contour == 0) ? expoly.contour : expoly.holes[i_contour - 1];
 | 
			
		||||
        const Points   pts = contour.equally_spaced_points(point_stepping_scaled);
 | 
			
		||||
        for (size_t i = 0; i < pts.size(); ++ i)
 | 
			
		||||
            out.emplace_back(unscale<float>(pts[i].x()), unscale<float>(pts[i].y()));
 | 
			
		||||
    }
 | 
			
		||||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<Vec2f> sample_expolygon_with_boundary(const ExPolygons &expolys, float samples_per_mm2, float samples_per_mm_boundary, std::mt19937 &rng)
 | 
			
		||||
{
 | 
			
		||||
    std::vector<Vec2f> out;
 | 
			
		||||
    for (const ExPolygon &expoly : expolys)
 | 
			
		||||
        append(out, sample_expolygon_with_boundary(expoly, samples_per_mm2, samples_per_mm_boundary, rng));
 | 
			
		||||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template<typename REFUSE_FUNCTION>
 | 
			
		||||
static inline std::vector<Vec2f> poisson_disk_from_samples(const std::vector<Vec2f> &raw_samples, float radius, REFUSE_FUNCTION refuse_function)
 | 
			
		||||
{
 | 
			
		||||
    Vec2f corner_min(std::numeric_limits<float>::max(), std::numeric_limits<float>::max());
 | 
			
		||||
    for (const Vec2f &pt : raw_samples) {
 | 
			
		||||
        corner_min.x() = std::min(corner_min.x(), pt.x());
 | 
			
		||||
        corner_min.y() = std::min(corner_min.y(), pt.y());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Assign the raw samples to grid cells, sort the grid cells lexicographically.
 | 
			
		||||
    struct RawSample {
 | 
			
		||||
        Vec2f coord;
 | 
			
		||||
        Vec2i cell_id;
 | 
			
		||||
    };
 | 
			
		||||
    std::vector<RawSample> raw_samples_sorted;
 | 
			
		||||
    RawSample sample;
 | 
			
		||||
    for (const Vec2f &pt : raw_samples) {
 | 
			
		||||
        sample.coord   = pt;
 | 
			
		||||
        sample.cell_id = ((pt - corner_min) / radius).cast<int>();
 | 
			
		||||
        raw_samples_sorted.emplace_back(sample);
 | 
			
		||||
    }
 | 
			
		||||
    std::sort(raw_samples_sorted.begin(), raw_samples_sorted.end(), [](const RawSample &lhs, const RawSample &rhs) 
 | 
			
		||||
        { return lhs.cell_id.x() < rhs.cell_id.x() || (lhs.cell_id.x() == rhs.cell_id.x() && lhs.cell_id.y() < rhs.cell_id.y()); });
 | 
			
		||||
 | 
			
		||||
    struct PoissonDiskGridEntry {
 | 
			
		||||
        // Resulting output sample points for this cell:
 | 
			
		||||
        enum {
 | 
			
		||||
            max_positions = 4
 | 
			
		||||
        };
 | 
			
		||||
        Vec2f   poisson_samples[max_positions];
 | 
			
		||||
        int     num_poisson_samples = 0;
 | 
			
		||||
 | 
			
		||||
        // Index into raw_samples:
 | 
			
		||||
        int     first_sample_idx;
 | 
			
		||||
        int     sample_cnt;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    struct CellIDHash {
 | 
			
		||||
        std::size_t operator()(const Vec2i &cell_id) const {
 | 
			
		||||
            return std::hash<int>()(cell_id.x()) ^ std::hash<int>()(cell_id.y() * 593);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Map from cell IDs to hash_data.  Each hash_data points to the range in raw_samples corresponding to that cell.
 | 
			
		||||
    // (We could just store the samples in hash_data.  This implementation is an artifact of the reference paper, which
 | 
			
		||||
    // is optimizing for GPU acceleration that we haven't implemented currently.)
 | 
			
		||||
    typedef std::unordered_map<Vec2i, PoissonDiskGridEntry, CellIDHash> Cells;
 | 
			
		||||
    Cells cells;
 | 
			
		||||
    {
 | 
			
		||||
        typename Cells::iterator last_cell_id_it;
 | 
			
		||||
        Vec2i           last_cell_id(-1, -1);
 | 
			
		||||
        for (int i = 0; i < raw_samples_sorted.size(); ++ i) {
 | 
			
		||||
            const RawSample &sample = raw_samples_sorted[i];
 | 
			
		||||
            if (sample.cell_id == last_cell_id) {
 | 
			
		||||
                // This sample is in the same cell as the previous, so just increase the count.  Cells are
 | 
			
		||||
                // always contiguous, since we've sorted raw_samples_sorted by cell ID.
 | 
			
		||||
                ++ last_cell_id_it->second.sample_cnt;
 | 
			
		||||
            } else {
 | 
			
		||||
                // This is a new cell.
 | 
			
		||||
                PoissonDiskGridEntry data;
 | 
			
		||||
                data.first_sample_idx = i;
 | 
			
		||||
                data.sample_cnt       = 1;
 | 
			
		||||
                auto result     = cells.insert({sample.cell_id, data});
 | 
			
		||||
                last_cell_id    = sample.cell_id;
 | 
			
		||||
                last_cell_id_it = result.first;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const int   max_trials = 5;
 | 
			
		||||
    const float radius_squared = radius * radius;
 | 
			
		||||
    for (int trial = 0; trial < max_trials; ++ trial) {
 | 
			
		||||
        // Create sample points for each entry in cells.
 | 
			
		||||
        for (auto &it : cells) {
 | 
			
		||||
            const Vec2i          &cell_id   = it.first;
 | 
			
		||||
            PoissonDiskGridEntry &cell_data = it.second;
 | 
			
		||||
            // This cell's raw sample points start at first_sample_idx.  On trial 0, try the first one. On trial 1, try first_sample_idx + 1.
 | 
			
		||||
            int next_sample_idx = cell_data.first_sample_idx + trial;
 | 
			
		||||
            if (trial >= cell_data.sample_cnt)
 | 
			
		||||
                // There are no more points to try for this cell.
 | 
			
		||||
                continue;
 | 
			
		||||
            const RawSample &candidate = raw_samples_sorted[next_sample_idx];
 | 
			
		||||
            // See if this point conflicts with any other points in this cell, or with any points in
 | 
			
		||||
            // neighboring cells.  Note that it's possible to have more than one point in the same cell.
 | 
			
		||||
            bool conflict = refuse_function(candidate.coord);
 | 
			
		||||
            for (int i = -1; i < 2 && ! conflict; ++ i) {
 | 
			
		||||
                for (int j = -1; j < 2; ++ j) {
 | 
			
		||||
                    const auto &it_neighbor = cells.find(cell_id + Vec2i(i, j));
 | 
			
		||||
                    if (it_neighbor != cells.end()) {
 | 
			
		||||
                        const PoissonDiskGridEntry &neighbor = it_neighbor->second;
 | 
			
		||||
                        for (int i_sample = 0; i_sample < neighbor.num_poisson_samples; ++ i_sample)
 | 
			
		||||
                            if ((neighbor.poisson_samples[i_sample] - candidate.coord).squaredNorm() < radius_squared) {
 | 
			
		||||
                                conflict = true;
 | 
			
		||||
                                break;
 | 
			
		||||
                            }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (! conflict) {
 | 
			
		||||
                // Store the new sample.
 | 
			
		||||
                assert(cell_data.num_poisson_samples < cell_data.max_positions);
 | 
			
		||||
                if (cell_data.num_poisson_samples < cell_data.max_positions)
 | 
			
		||||
                    cell_data.poisson_samples[cell_data.num_poisson_samples ++] = candidate.coord;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Copy the results to the output.
 | 
			
		||||
    std::vector<Vec2f> out;
 | 
			
		||||
    for (const auto& it : cells)
 | 
			
		||||
        for (int i = 0; i < it.second.num_poisson_samples; ++ i)
 | 
			
		||||
            out.emplace_back(it.second.poisson_samples[i]);
 | 
			
		||||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& structure, PointGrid3D &grid3d, bool is_new_island, bool just_one)
 | 
			
		||||
{
 | 
			
		||||
    //int num_of_points = std::max(1, (int)((island.area()*pow(SCALING_FACTOR, 2) * m_config.tear_pressure)/m_config.support_force));
 | 
			
		||||
 | 
			
		||||
    const float support_force_deficit = structure.support_force_deficit(m_config.tear_pressure());
 | 
			
		||||
    if (support_force_deficit < 0)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    // Number of newly added points.
 | 
			
		||||
    const size_t poisson_samples_target = size_t(ceil(support_force_deficit / m_config.support_force()));
 | 
			
		||||
 | 
			
		||||
    const float density_horizontal = m_config.tear_pressure() / m_config.support_force();
 | 
			
		||||
    //FIXME why?
 | 
			
		||||
    float poisson_radius		= std::max(m_config.minimal_distance, 1.f / (5.f * density_horizontal));
 | 
			
		||||
//    const float poisson_radius     = 1.f / (15.f * density_horizontal);
 | 
			
		||||
    const float samples_per_mm2 = 30.f / (float(M_PI) * poisson_radius * poisson_radius);
 | 
			
		||||
    // Minimum distance between samples, in 3D space.
 | 
			
		||||
//    float min_spacing			= poisson_radius / 3.f;
 | 
			
		||||
    float min_spacing			= poisson_radius;
 | 
			
		||||
 | 
			
		||||
    //FIXME share the random generator. The random generator may be not so cheap to initialize, also we don't want the random generator to be restarted for each polygon.
 | 
			
		||||
    std::random_device  rd;
 | 
			
		||||
    std::mt19937        rng(rd());
 | 
			
		||||
	std::vector<Vec2f>  raw_samples = sample_expolygon_with_boundary(islands, samples_per_mm2, 5.f / poisson_radius, rng);
 | 
			
		||||
    std::vector<Vec2f>  poisson_samples;
 | 
			
		||||
    for (size_t iter = 0; iter < 4; ++ iter) {
 | 
			
		||||
        poisson_samples = poisson_disk_from_samples(raw_samples, poisson_radius, 
 | 
			
		||||
            [&structure, &grid3d, min_spacing](const Vec2f &pos) {
 | 
			
		||||
                return grid3d.collides_with(pos, &structure, min_spacing);
 | 
			
		||||
            });
 | 
			
		||||
        if (poisson_samples.size() >= poisson_samples_target || m_config.minimal_distance > poisson_radius-EPSILON)
 | 
			
		||||
            break;
 | 
			
		||||
        float coeff = 0.5f;
 | 
			
		||||
        if (poisson_samples.size() * 2 > poisson_samples_target)
 | 
			
		||||
            coeff = float(poisson_samples.size()) / float(poisson_samples_target);
 | 
			
		||||
        poisson_radius = std::max(m_config.minimal_distance, poisson_radius * coeff);
 | 
			
		||||
        min_spacing    = std::max(m_config.minimal_distance, min_spacing * coeff);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
 | 
			
		||||
	{
 | 
			
		||||
		static int irun = 0;
 | 
			
		||||
		Slic3r::SVG svg(debug_out_path("SLA_supports-uniformly_cover-%d.svg", irun ++), get_extents(islands));
 | 
			
		||||
        for (const ExPolygon &island : islands)
 | 
			
		||||
            svg.draw(island);
 | 
			
		||||
		for (const Vec2f &pt : raw_samples)
 | 
			
		||||
			svg.draw(Point(scale_(pt.x()), scale_(pt.y())), "red");
 | 
			
		||||
		for (const Vec2f &pt : poisson_samples)
 | 
			
		||||
			svg.draw(Point(scale_(pt.x()), scale_(pt.y())), "blue");
 | 
			
		||||
	}
 | 
			
		||||
#endif /* NDEBUG */
 | 
			
		||||
 | 
			
		||||
//    assert(! poisson_samples.empty());
 | 
			
		||||
    if (poisson_samples_target < poisson_samples.size()) {
 | 
			
		||||
		std::shuffle(poisson_samples.begin(), poisson_samples.end(), rng);
 | 
			
		||||
        poisson_samples.erase(poisson_samples.begin() + poisson_samples_target, poisson_samples.end());
 | 
			
		||||
    }
 | 
			
		||||
    for (const Vec2f &pt : poisson_samples) {
 | 
			
		||||
        m_output.emplace_back(float(pt(0)), float(pt(1)), structure.height, 0.2f, is_new_island);
 | 
			
		||||
        structure.supports_force_this_layer += m_config.support_force();
 | 
			
		||||
        grid3d.insert(pt, &structure);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
 | 
			
		||||
void SLAAutoSupports::output_expolygons(const ExPolygons& expolys, std::string filename) const
 | 
			
		||||
void SLAAutoSupports::output_structures(const std::vector<Structure>& structures)
 | 
			
		||||
{
 | 
			
		||||
    for (unsigned int i=0 ; i<structures.size(); ++i) {
 | 
			
		||||
        std::stringstream ss;
 | 
			
		||||
        ss << structures[i].unique_id.count() << "_" << std::setw(10) << std::setfill('0') << 1000 + (int)structures[i].height/1000 << ".png";
 | 
			
		||||
        output_expolygons(std::vector<ExPolygon>{*structures[i].polygon}, ss.str());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SLAAutoSupports::output_expolygons(const ExPolygons& expolys, const std::string &filename)
 | 
			
		||||
{
 | 
			
		||||
    BoundingBox bb(Point(-30000000, -30000000), Point(30000000, 30000000));
 | 
			
		||||
    Slic3r::SVG svg_cummulative(filename, bb);
 | 
			
		||||
| 
						 | 
				
			
			@ -198,138 +525,6 @@ void SLAAutoSupports::output_expolygons(const ExPolygons& expolys, std::string f
 | 
			
		|||
        svg_cummulative.draw_outline(expolys[i].holes, "blue", scale_(0.05));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
#endif /* SLA_AUTOSUPPORTS_DEBUG */
 | 
			
		||||
 | 
			
		||||
std::vector<std::pair<ExPolygon, coord_t>> SLAAutoSupports::find_islands(const std::vector<ExPolygons>& slices, const std::vector<float>& heights) const
 | 
			
		||||
{
 | 
			
		||||
    std::vector<std::pair<ExPolygon, coord_t>> islands;
 | 
			
		||||
 | 
			
		||||
    struct PointAccessor {
 | 
			
		||||
        const Point* operator()(const Point &pt) const { return &pt; }
 | 
			
		||||
    };
 | 
			
		||||
    typedef ClosestPointInRadiusLookup<Point, PointAccessor> ClosestPointLookupType;
 | 
			
		||||
 | 
			
		||||
    for (unsigned int i = 0; i<slices.size(); ++i) {
 | 
			
		||||
        const ExPolygons& expolys_top = slices[i];
 | 
			
		||||
        const ExPolygons& expolys_bottom = (i == 0 ? ExPolygons() : slices[i-1]);
 | 
			
		||||
 | 
			
		||||
        std::string layer_num_str = std::string((i<10 ? "0" : "")) + std::string((i<100 ? "0" : "")) + std::to_string(i);
 | 
			
		||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
 | 
			
		||||
        output_expolygons(expolys_top, "top" + layer_num_str + ".svg");
 | 
			
		||||
#endif /* SLA_AUTOSUPPORTS_DEBUG */
 | 
			
		||||
        ExPolygons diff = diff_ex(expolys_top, expolys_bottom);
 | 
			
		||||
 | 
			
		||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
 | 
			
		||||
        output_expolygons(diff, "diff" + layer_num_str + ".svg");
 | 
			
		||||
#endif /* SLA_AUTOSUPPORTS_DEBUG */
 | 
			
		||||
 | 
			
		||||
        ClosestPointLookupType cpl(SCALED_EPSILON);
 | 
			
		||||
        for (const ExPolygon& expol : expolys_top) {
 | 
			
		||||
            for (const Point& p : expol.contour.points)
 | 
			
		||||
                cpl.insert(p);
 | 
			
		||||
            for (const Polygon& hole : expol.holes)
 | 
			
		||||
                for (const Point& p : hole.points)
 | 
			
		||||
                    cpl.insert(p);
 | 
			
		||||
            // the lookup structure now contains all points from the top slice
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (const ExPolygon& polygon : diff) {
 | 
			
		||||
            // we want to check all boundary points of the diff polygon
 | 
			
		||||
            bool island = true;
 | 
			
		||||
            for (const Point& p : polygon.contour.points) {
 | 
			
		||||
                if (cpl.find(p).second != 0) { // the point belongs to the bottom slice - this cannot be an island
 | 
			
		||||
                    island = false;
 | 
			
		||||
                    goto NO_ISLAND;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            for (const Polygon& hole : polygon.holes)
 | 
			
		||||
                for (const Point& p : hole.points)
 | 
			
		||||
                if (cpl.find(p).second != 0) {
 | 
			
		||||
                    island = false;
 | 
			
		||||
                    goto NO_ISLAND;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            if (island) { // all points of the diff polygon are from the top slice
 | 
			
		||||
                islands.push_back(std::make_pair(polygon, scale_(i!=0 ? heights[i-1] : heights[0]-(heights[1]-heights[0]))));
 | 
			
		||||
            }
 | 
			
		||||
            NO_ISLAND: ;// continue with next ExPolygon
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
 | 
			
		||||
        //if (!islands.empty())
 | 
			
		||||
          //  output_expolygons(islands, "islands" + layer_num_str + ".svg");
 | 
			
		||||
#endif /* SLA_AUTOSUPPORTS_DEBUG */
 | 
			
		||||
        m_throw_on_cancel();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return islands;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<Vec3d> SLAAutoSupports::uniformly_cover(const std::pair<ExPolygon, coord_t>& island)
 | 
			
		||||
{
 | 
			
		||||
    int num_of_points = std::max(1, (int)(island.first.area()*pow(SCALING_FACTOR, 2) * get_required_density(0)));
 | 
			
		||||
 | 
			
		||||
    // In case there is just one point to place, we'll place it into the polygon's centroid (unless it lies in a hole).
 | 
			
		||||
    if (num_of_points == 1) {
 | 
			
		||||
        Point out(island.first.contour.centroid());
 | 
			
		||||
 | 
			
		||||
        for (const auto& hole : island.first.holes)
 | 
			
		||||
            if (hole.contains(out))
 | 
			
		||||
                goto HOLE_HIT;
 | 
			
		||||
        return std::vector<Vec3d>{unscale(out(0), out(1), island.second)};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
HOLE_HIT:
 | 
			
		||||
    // In this case either the centroid lies in a hole, or there are multiple points
 | 
			
		||||
    // to place. We will cover the island another way.
 | 
			
		||||
    // For now we'll just place the points randomly not too close to the others.
 | 
			
		||||
    std::random_device rd;
 | 
			
		||||
    std::mt19937 gen(rd());
 | 
			
		||||
    std::uniform_real_distribution<> dis(0., 1.);
 | 
			
		||||
 | 
			
		||||
    std::vector<Vec3d> island_new_points;
 | 
			
		||||
    const BoundingBox& bb = get_extents(island.first);
 | 
			
		||||
    const int refused_limit = 30;
 | 
			
		||||
    int refused_points = 0;
 | 
			
		||||
    while (refused_points < refused_limit) {
 | 
			
		||||
        Point out(bb.min(0) + bb.size()(0)  * dis(gen),
 | 
			
		||||
                  bb.min(1) + bb.size()(1)  * dis(gen)) ;
 | 
			
		||||
        Vec3d unscaled_out = unscale(out(0), out(1), island.second);
 | 
			
		||||
        bool add_it = true;
 | 
			
		||||
 | 
			
		||||
        if (!island.first.contour.contains(out))
 | 
			
		||||
            add_it = false;
 | 
			
		||||
        else
 | 
			
		||||
            for (const Polygon& hole : island.first.holes)
 | 
			
		||||
                if (hole.contains(out))
 | 
			
		||||
                    add_it = false;
 | 
			
		||||
 | 
			
		||||
        if (add_it) {
 | 
			
		||||
            for (const Vec3d& p : island_new_points) {
 | 
			
		||||
                if ((p - unscaled_out).squaredNorm() < distance_limit(0)) {
 | 
			
		||||
                    add_it = false;
 | 
			
		||||
                    ++refused_points;
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (add_it)
 | 
			
		||||
            island_new_points.emplace_back(unscaled_out);
 | 
			
		||||
    }
 | 
			
		||||
    return island_new_points;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SLAAutoSupports::project_upward_onto_mesh(std::vector<Vec3d>& points) const
 | 
			
		||||
{
 | 
			
		||||
    Vec3f dir(0., 0., 1.);
 | 
			
		||||
    igl::Hit hit{0, 0, 0.f, 0.f, 0.f};
 | 
			
		||||
    for (Vec3d& p : points) {
 | 
			
		||||
        igl::ray_mesh_intersect(p.cast<float>(), dir, m_V, m_F, hit);
 | 
			
		||||
        int fid = hit.id;
 | 
			
		||||
        Vec3f bc(1-hit.u-hit.v, hit.u, hit.v);
 | 
			
		||||
        p = (bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2))).cast<double>();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
} // namespace Slic3r
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,9 +1,12 @@
 | 
			
		|||
#ifndef SLAAUTOSUPPORTS_HPP_
 | 
			
		||||
#define SLAAUTOSUPPORTS_HPP_
 | 
			
		||||
 | 
			
		||||
#include <libslic3r/ClipperUtils.hpp>
 | 
			
		||||
#include <libslic3r/Point.hpp>
 | 
			
		||||
#include <libslic3r/TriangleMesh.hpp>
 | 
			
		||||
#include <libslic3r/SLA/SLASupportTree.hpp>
 | 
			
		||||
#include <libslic3r/SLA/SLACommon.hpp>
 | 
			
		||||
 | 
			
		||||
#include <boost/container/small_vector.hpp>
 | 
			
		||||
 | 
			
		||||
// #define SLA_AUTOSUPPORTS_DEBUG
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -12,36 +15,184 @@ namespace Slic3r {
 | 
			
		|||
class SLAAutoSupports {
 | 
			
		||||
public:
 | 
			
		||||
    struct Config {
 | 
			
		||||
            float density_at_horizontal;
 | 
			
		||||
            float density_at_45;
 | 
			
		||||
            float minimal_z;
 | 
			
		||||
            float density_relative;
 | 
			
		||||
            float minimal_distance;
 | 
			
		||||
            ///////////////
 | 
			
		||||
            inline float support_force() const { return 10.f / density_relative; } // a force one point can support       (arbitrary force unit)
 | 
			
		||||
            inline float tear_pressure() const { return 1.f; }  // pressure that the display exerts    (the force unit per mm2)
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
    SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector<ExPolygons>& slices, 
 | 
			
		||||
        const std::vector<float>& heights, const Config& config, std::function<void(void)> throw_on_cancel);
 | 
			
		||||
    const std::vector<Vec3d>& output() { return m_output; }
 | 
			
		||||
    SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector<ExPolygons>& slices,
 | 
			
		||||
                     const std::vector<float>& heights, const Config& config, std::function<void(void)> throw_on_cancel);
 | 
			
		||||
    const std::vector<sla::SupportPoint>& output() { return m_output; }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    std::vector<Vec3d> m_output;
 | 
			
		||||
    std::vector<Vec3d> m_normals;
 | 
			
		||||
    TriangleMesh mesh;
 | 
			
		||||
    static float angle_from_normal(const stl_normal& normal) { return acos((-normal.normalized())(2)); }
 | 
			
		||||
    float get_required_density(float angle) const;
 | 
			
		||||
    float distance_limit(float angle) const;
 | 
			
		||||
    static float approximate_geodesic_distance(const Vec3d& p1, const Vec3d& p2, Vec3d& n1, Vec3d& n2);
 | 
			
		||||
    std::vector<std::pair<ExPolygon, coord_t>> find_islands(const std::vector<ExPolygons>& slices, const std::vector<float>& heights) const;
 | 
			
		||||
    void sprinkle_mesh(const TriangleMesh& mesh);
 | 
			
		||||
    std::vector<Vec3d> uniformly_cover(const std::pair<ExPolygon, coord_t>& island);
 | 
			
		||||
    void project_upward_onto_mesh(std::vector<Vec3d>& points) const;
 | 
			
		||||
	struct MyLayer;
 | 
			
		||||
 | 
			
		||||
    struct Structure {
 | 
			
		||||
        Structure(MyLayer &layer, const ExPolygon& poly, const BoundingBox &bbox, const Vec2f ¢roid, float area, float h) : 
 | 
			
		||||
            layer(&layer), polygon(&poly), bbox(bbox), centroid(centroid), area(area), height(h)
 | 
			
		||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
 | 
			
		||||
    void output_expolygons(const ExPolygons& expolys, std::string filename) const;
 | 
			
		||||
            , unique_id(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()))
 | 
			
		||||
#endif /* SLA_AUTOSUPPORTS_DEBUG */
 | 
			
		||||
            {}
 | 
			
		||||
        MyLayer *layer;
 | 
			
		||||
        const ExPolygon* polygon = nullptr;
 | 
			
		||||
        const BoundingBox bbox;
 | 
			
		||||
        const Vec2f centroid = Vec2f::Zero();
 | 
			
		||||
        const float area = 0.f;
 | 
			
		||||
        float height = 0;
 | 
			
		||||
        // How well is this ExPolygon held to the print base?
 | 
			
		||||
        // Positive number, the higher the better.
 | 
			
		||||
        float supports_force_this_layer     = 0.f;
 | 
			
		||||
        float supports_force_inherited      = 0.f;
 | 
			
		||||
        float supports_force_total() const { return this->supports_force_this_layer + this->supports_force_inherited; }
 | 
			
		||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
 | 
			
		||||
        std::chrono::milliseconds unique_id;
 | 
			
		||||
#endif /* SLA_AUTOSUPPORTS_DEBUG */
 | 
			
		||||
 | 
			
		||||
        struct Link {
 | 
			
		||||
			Link(Structure *island, float overlap_area) : island(island), overlap_area(overlap_area) {}
 | 
			
		||||
            Structure   *island;
 | 
			
		||||
            float        overlap_area;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
#ifdef NDEBUG
 | 
			
		||||
		// In release mode, use the optimized container.
 | 
			
		||||
        boost::container::small_vector<Link, 4> islands_above;
 | 
			
		||||
        boost::container::small_vector<Link, 4> islands_below;
 | 
			
		||||
#else
 | 
			
		||||
		// In debug mode, use the standard vector, which is well handled by debugger visualizer.
 | 
			
		||||
		std::vector<Link>					 	islands_above;
 | 
			
		||||
		std::vector<Link>						islands_below;
 | 
			
		||||
#endif
 | 
			
		||||
        ExPolygons                              dangling_areas;
 | 
			
		||||
        ExPolygons                              overhangs;
 | 
			
		||||
        float                                   overhangs_area;
 | 
			
		||||
 | 
			
		||||
        bool overlaps(const Structure &rhs) const { 
 | 
			
		||||
            return this->bbox.overlap(rhs.bbox) && (this->polygon->overlaps(*rhs.polygon) || rhs.polygon->overlaps(*this->polygon)); 
 | 
			
		||||
        }
 | 
			
		||||
        float overlap_area(const Structure &rhs) const { 
 | 
			
		||||
            double out = 0.;
 | 
			
		||||
            if (this->bbox.overlap(rhs.bbox)) {
 | 
			
		||||
                 Polygons polys = intersection(to_polygons(*this->polygon), to_polygons(*rhs.polygon), false);
 | 
			
		||||
                 for (const Polygon &poly : polys)
 | 
			
		||||
                    out += poly.area();
 | 
			
		||||
            }
 | 
			
		||||
            return float(out);
 | 
			
		||||
        }
 | 
			
		||||
        float area_below() const { 
 | 
			
		||||
            float area = 0.f; 
 | 
			
		||||
            for (const Link &below : this->islands_below) 
 | 
			
		||||
                area += below.island->area; 
 | 
			
		||||
            return area;
 | 
			
		||||
        }
 | 
			
		||||
        Polygons polygons_below() const { 
 | 
			
		||||
            size_t cnt = 0;
 | 
			
		||||
			for (const Link &below : this->islands_below)
 | 
			
		||||
                cnt += 1 + below.island->polygon->holes.size();
 | 
			
		||||
            Polygons out;
 | 
			
		||||
            out.reserve(cnt);
 | 
			
		||||
			for (const Link &below : this->islands_below) {
 | 
			
		||||
                out.emplace_back(below.island->polygon->contour);
 | 
			
		||||
				append(out, below.island->polygon->holes);
 | 
			
		||||
            }
 | 
			
		||||
            return out;
 | 
			
		||||
        }
 | 
			
		||||
        ExPolygons expolygons_below() const { 
 | 
			
		||||
            ExPolygons out;
 | 
			
		||||
            out.reserve(this->islands_below.size());
 | 
			
		||||
            for (const Link &below : this->islands_below)
 | 
			
		||||
                out.emplace_back(*below.island->polygon);
 | 
			
		||||
            return out;
 | 
			
		||||
        }
 | 
			
		||||
        // Positive deficit of the supports. If negative, this area is well supported. If positive, more supports need to be added.
 | 
			
		||||
        float support_force_deficit(const float tear_pressure) const { return this->area * tear_pressure - this->supports_force_total(); }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    struct MyLayer {
 | 
			
		||||
		MyLayer(const size_t layer_id, coordf_t print_z) : layer_id(layer_id), print_z(print_z) {}
 | 
			
		||||
        size_t                  layer_id;
 | 
			
		||||
        coordf_t                print_z;
 | 
			
		||||
        std::vector<Structure>  islands;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    struct RichSupportPoint {
 | 
			
		||||
        Vec3f        position;
 | 
			
		||||
        Structure   *island;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    struct PointGrid3D {
 | 
			
		||||
        struct GridHash {
 | 
			
		||||
            std::size_t operator()(const Vec3i &cell_id) const {
 | 
			
		||||
                return std::hash<int>()(cell_id.x()) ^ std::hash<int>()(cell_id.y() * 593) ^ std::hash<int>()(cell_id.z() * 7919);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        typedef std::unordered_multimap<Vec3i, RichSupportPoint, GridHash> Grid;
 | 
			
		||||
 | 
			
		||||
        Vec3f   cell_size;
 | 
			
		||||
        Grid    grid;
 | 
			
		||||
 | 
			
		||||
        Vec3i cell_id(const Vec3f &pos) {
 | 
			
		||||
            return Vec3i(int(floor(pos.x() / cell_size.x())),
 | 
			
		||||
                         int(floor(pos.y() / cell_size.y())),
 | 
			
		||||
                         int(floor(pos.z() / cell_size.z())));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        void insert(const Vec2f &pos, Structure *island) {
 | 
			
		||||
            RichSupportPoint pt;
 | 
			
		||||
			pt.position = Vec3f(pos.x(), pos.y(), float(island->layer->print_z));
 | 
			
		||||
            pt.island   = island;
 | 
			
		||||
            grid.emplace(cell_id(pt.position), pt);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool collides_with(const Vec2f &pos, Structure *island, float radius) {
 | 
			
		||||
            Vec3f pos3d(pos.x(), pos.y(), float(island->layer->print_z));
 | 
			
		||||
            Vec3i cell = cell_id(pos3d);
 | 
			
		||||
            std::pair<Grid::const_iterator, Grid::const_iterator> it_pair = grid.equal_range(cell);
 | 
			
		||||
            if (collides_with(pos3d, radius, it_pair.first, it_pair.second))
 | 
			
		||||
                return true;
 | 
			
		||||
            for (int i = -1; i < 2; ++ i)
 | 
			
		||||
                for (int j = -1; j < 2; ++ j)
 | 
			
		||||
                    for (int k = -1; k < 1; ++ k) {
 | 
			
		||||
                        if (i == 0 && j == 0 && k == 0)
 | 
			
		||||
                            continue;
 | 
			
		||||
                        it_pair = grid.equal_range(cell + Vec3i(i, j, k));
 | 
			
		||||
                        if (collides_with(pos3d, radius, it_pair.first, it_pair.second))
 | 
			
		||||
                            return true;
 | 
			
		||||
                    }
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
        bool collides_with(const Vec3f &pos, float radius, Grid::const_iterator it_begin, Grid::const_iterator it_end) {
 | 
			
		||||
            for (Grid::const_iterator it = it_begin; it != it_end; ++ it) {
 | 
			
		||||
				float dist2 = (it->second.position - pos).squaredNorm();
 | 
			
		||||
                if (dist2 < radius * radius)
 | 
			
		||||
                    return true;
 | 
			
		||||
            }
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    std::vector<sla::SupportPoint> m_output;
 | 
			
		||||
 | 
			
		||||
    SLAAutoSupports::Config m_config;
 | 
			
		||||
 | 
			
		||||
    float m_supports_force_total = 0.f;
 | 
			
		||||
 | 
			
		||||
    void process(const std::vector<ExPolygons>& slices, const std::vector<float>& heights);
 | 
			
		||||
    void uniformly_cover(const ExPolygons& islands, Structure& structure, PointGrid3D &grid3d, bool is_new_island = false, bool just_one = false);
 | 
			
		||||
    void project_onto_mesh(std::vector<sla::SupportPoint>& points) const;
 | 
			
		||||
 | 
			
		||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
 | 
			
		||||
    static void output_expolygons(const ExPolygons& expolys, const std::string &filename);
 | 
			
		||||
    static void output_structures(const std::vector<Structure> &structures);
 | 
			
		||||
#endif // SLA_AUTOSUPPORTS_DEBUG
 | 
			
		||||
 | 
			
		||||
    std::function<void(void)> m_throw_on_cancel;
 | 
			
		||||
    const Eigen::MatrixXd& m_V;
 | 
			
		||||
    const Eigen::MatrixXi& m_F;
 | 
			
		||||
    const sla::EigenMesh3D& m_emesh;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										137
									
								
								src/libslic3r/SLA/SLACommon.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,137 @@
 | 
			
		|||
#ifndef SLACOMMON_HPP
 | 
			
		||||
#define SLACOMMON_HPP
 | 
			
		||||
 | 
			
		||||
#include <Eigen/Geometry>
 | 
			
		||||
 | 
			
		||||
// #define SLIC3R_SLA_NEEDS_WINDTREE
 | 
			
		||||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
    
 | 
			
		||||
// Typedefs from Point.hpp
 | 
			
		||||
typedef Eigen::Matrix<float, 3, 1, Eigen::DontAlign> Vec3f;
 | 
			
		||||
typedef Eigen::Matrix<double, 3, 1, Eigen::DontAlign> Vec3d;
 | 
			
		||||
 | 
			
		||||
class TriangleMesh;
 | 
			
		||||
 | 
			
		||||
namespace sla {
 | 
			
		||||
    
 | 
			
		||||
struct SupportPoint {
 | 
			
		||||
    Vec3f pos;
 | 
			
		||||
    float head_front_radius;
 | 
			
		||||
    bool is_new_island;
 | 
			
		||||
 | 
			
		||||
    SupportPoint() :
 | 
			
		||||
    pos(Vec3f::Zero()), head_front_radius(0.f), is_new_island(false) {}
 | 
			
		||||
 | 
			
		||||
    SupportPoint(float pos_x, float pos_y, float pos_z, float head_radius, bool new_island) :
 | 
			
		||||
    pos(pos_x, pos_y, pos_z), head_front_radius(head_radius), is_new_island(new_island) {}
 | 
			
		||||
 | 
			
		||||
    SupportPoint(Vec3f position, float head_radius, bool new_island) :
 | 
			
		||||
    pos(position), head_front_radius(head_radius), is_new_island(new_island) {}
 | 
			
		||||
 | 
			
		||||
    SupportPoint(Eigen::Matrix<float, 5, 1, Eigen::DontAlign> data) :
 | 
			
		||||
    pos(data(0), data(1), data(2)), head_front_radius(data(3)), is_new_island(data(4) != 0.f) {}
 | 
			
		||||
 | 
			
		||||
    bool operator==(const SupportPoint& sp) const { return (pos==sp.pos) && head_front_radius==sp.head_front_radius && is_new_island==sp.is_new_island; }
 | 
			
		||||
    bool operator!=(const SupportPoint& sp) const { return !(sp == (*this)); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// An index-triangle structure for libIGL functions. Also serves as an
 | 
			
		||||
/// alternative (raw) input format for the SLASupportTree
 | 
			
		||||
/*struct EigenMesh3D {
 | 
			
		||||
    Eigen::MatrixXd V;
 | 
			
		||||
    Eigen::MatrixXi F;
 | 
			
		||||
    double ground_level = 0;
 | 
			
		||||
};*/
 | 
			
		||||
 | 
			
		||||
/// An index-triangle structure for libIGL functions. Also serves as an
 | 
			
		||||
/// alternative (raw) input format for the SLASupportTree
 | 
			
		||||
class EigenMesh3D {
 | 
			
		||||
    class AABBImpl;
 | 
			
		||||
 | 
			
		||||
    Eigen::MatrixXd m_V;
 | 
			
		||||
    Eigen::MatrixXi m_F;
 | 
			
		||||
    double m_ground_level = 0;
 | 
			
		||||
 | 
			
		||||
    std::unique_ptr<AABBImpl> m_aabb;
 | 
			
		||||
public:
 | 
			
		||||
 | 
			
		||||
    EigenMesh3D(const TriangleMesh&);
 | 
			
		||||
    EigenMesh3D(const EigenMesh3D& other);
 | 
			
		||||
    EigenMesh3D& operator=(const EigenMesh3D&);
 | 
			
		||||
 | 
			
		||||
    ~EigenMesh3D();
 | 
			
		||||
 | 
			
		||||
    inline double ground_level() const { return m_ground_level; }
 | 
			
		||||
 | 
			
		||||
    inline const Eigen::MatrixXd& V() const { return m_V; }
 | 
			
		||||
    inline const Eigen::MatrixXi& F() const { return m_F; }
 | 
			
		||||
 | 
			
		||||
    // Result of a raycast
 | 
			
		||||
    class hit_result {
 | 
			
		||||
        double m_t = std::numeric_limits<double>::infinity();
 | 
			
		||||
        int m_face_id = -1;
 | 
			
		||||
        const EigenMesh3D& m_mesh;
 | 
			
		||||
        Vec3d m_dir;
 | 
			
		||||
        inline hit_result(const EigenMesh3D& em): m_mesh(em) {}
 | 
			
		||||
        friend class EigenMesh3D;
 | 
			
		||||
    public:
 | 
			
		||||
 | 
			
		||||
        inline double distance() const { return m_t; }
 | 
			
		||||
        inline const Vec3d& direction() const { return m_dir; }
 | 
			
		||||
        inline int face() const { return m_face_id; }
 | 
			
		||||
 | 
			
		||||
        inline Vec3d normal() const {
 | 
			
		||||
            if(m_face_id < 0) return {};
 | 
			
		||||
            auto trindex    = m_mesh.m_F.row(m_face_id);
 | 
			
		||||
            const Vec3d& p1 = m_mesh.V().row(trindex(0));
 | 
			
		||||
            const Vec3d& p2 = m_mesh.V().row(trindex(1));
 | 
			
		||||
            const Vec3d& p3 = m_mesh.V().row(trindex(2));
 | 
			
		||||
            Eigen::Vector3d U = p2 - p1;
 | 
			
		||||
            Eigen::Vector3d V = p3 - p1;
 | 
			
		||||
            return U.cross(V).normalized();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        inline bool is_inside() {
 | 
			
		||||
            return m_face_id >= 0 && normal().dot(m_dir) > 0;
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Casting a ray on the mesh, returns the distance where the hit occures.
 | 
			
		||||
    hit_result query_ray_hit(const Vec3d &s, const Vec3d &dir) const;
 | 
			
		||||
 | 
			
		||||
    class si_result {
 | 
			
		||||
        double m_value;
 | 
			
		||||
        int m_fidx;
 | 
			
		||||
        Vec3d m_p;
 | 
			
		||||
        si_result(double val, int i, const Vec3d& c):
 | 
			
		||||
            m_value(val), m_fidx(i), m_p(c) {}
 | 
			
		||||
        friend class EigenMesh3D;
 | 
			
		||||
    public:
 | 
			
		||||
 | 
			
		||||
        si_result() = delete;
 | 
			
		||||
 | 
			
		||||
        double value() const { return m_value; }
 | 
			
		||||
        operator double() const { return m_value; }
 | 
			
		||||
        const Vec3d& point_on_mesh() const { return m_p; }
 | 
			
		||||
        int F_idx() const { return m_fidx; }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
#ifdef SLIC3R_SLA_NEEDS_WINDTREE
 | 
			
		||||
    // The signed distance from a point to the mesh. Outputs the distance,
 | 
			
		||||
    // the index of the triangle and the closest point in mesh coordinate space.
 | 
			
		||||
    si_result signed_distance(const Vec3d& p) const;
 | 
			
		||||
 | 
			
		||||
    bool inside(const Vec3d& p) const;
 | 
			
		||||
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
} // namespace sla
 | 
			
		||||
} // namespace Slic3r
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#endif // SLASUPPORTTREE_HPP
 | 
			
		||||
| 
						 | 
				
			
			@ -550,10 +550,16 @@ enum { // For indexing Eigen vectors as v(X), v(Y), v(Z) instead of numbers
 | 
			
		|||
  X, Y, Z
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
PointSet to_point_set(const std::vector<Vec3d> &v)
 | 
			
		||||
PointSet to_point_set(const std::vector<SupportPoint> &v)
 | 
			
		||||
{
 | 
			
		||||
    PointSet ret(v.size(), 3);
 | 
			
		||||
    { long i = 0; for(const Vec3d& p : v) ret.row(i++) = p; }
 | 
			
		||||
    long i = 0;
 | 
			
		||||
    for(const SupportPoint& support_point : v) {
 | 
			
		||||
        ret.row(i)(0) = support_point.pos(0);
 | 
			
		||||
        ret.row(i)(1) = support_point.pos(1);
 | 
			
		||||
        ret.row(i)(2) = support_point.pos(2);
 | 
			
		||||
        ++i;
 | 
			
		||||
    }
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -671,6 +677,7 @@ double pinhead_mesh_intersect(const Vec3d& s,
 | 
			
		|||
    return *mit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Checking bridge (pillar and stick as well) intersection with the model. If
 | 
			
		||||
// the function is used for headless sticks, the ins_check parameter have to be
 | 
			
		||||
// true as the beginning of the stick might be inside the model geometry.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,6 +7,9 @@
 | 
			
		|||
#include <memory>
 | 
			
		||||
#include <Eigen/Geometry>
 | 
			
		||||
 | 
			
		||||
#include "SLACommon.hpp"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
 | 
			
		||||
// Needed types from Point.hpp
 | 
			
		||||
| 
						 | 
				
			
			@ -105,86 +108,6 @@ struct Controller {
 | 
			
		|||
    std::function<void(void)> cancelfn = [](){};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// An index-triangle structure for libIGL functions. Also serves as an
 | 
			
		||||
/// alternative (raw) input format for the SLASupportTree
 | 
			
		||||
class EigenMesh3D {
 | 
			
		||||
    class AABBImpl;
 | 
			
		||||
 | 
			
		||||
    Eigen::MatrixXd m_V;
 | 
			
		||||
    Eigen::MatrixXi m_F;
 | 
			
		||||
    double m_ground_level = 0;
 | 
			
		||||
 | 
			
		||||
    std::unique_ptr<AABBImpl> m_aabb;
 | 
			
		||||
public:
 | 
			
		||||
 | 
			
		||||
    EigenMesh3D(const TriangleMesh&);
 | 
			
		||||
    EigenMesh3D(const EigenMesh3D& other);
 | 
			
		||||
    EigenMesh3D& operator=(const EigenMesh3D&);
 | 
			
		||||
 | 
			
		||||
    ~EigenMesh3D();
 | 
			
		||||
 | 
			
		||||
    inline double ground_level() const { return m_ground_level; }
 | 
			
		||||
 | 
			
		||||
    inline const Eigen::MatrixXd& V() const { return m_V; }
 | 
			
		||||
    inline const Eigen::MatrixXi& F() const { return m_F; }
 | 
			
		||||
 | 
			
		||||
    // Result of a raycast
 | 
			
		||||
    class hit_result {
 | 
			
		||||
        double m_t = std::numeric_limits<double>::infinity();
 | 
			
		||||
        int m_face_id = -1;
 | 
			
		||||
        const EigenMesh3D& m_mesh;
 | 
			
		||||
        Vec3d m_dir;
 | 
			
		||||
        inline hit_result(const EigenMesh3D& em): m_mesh(em) {}
 | 
			
		||||
        friend class EigenMesh3D;
 | 
			
		||||
    public:
 | 
			
		||||
 | 
			
		||||
        inline double distance() const { return m_t; }
 | 
			
		||||
 | 
			
		||||
        inline int face() const { return m_face_id; }
 | 
			
		||||
 | 
			
		||||
        inline Vec3d normal() const {
 | 
			
		||||
            if(m_face_id < 0) return {};
 | 
			
		||||
            auto trindex    = m_mesh.m_F.row(m_face_id);
 | 
			
		||||
            const Vec3d& p1 = m_mesh.V().row(trindex(0));
 | 
			
		||||
            const Vec3d& p2 = m_mesh.V().row(trindex(1));
 | 
			
		||||
            const Vec3d& p3 = m_mesh.V().row(trindex(2));
 | 
			
		||||
            Eigen::Vector3d U = p2 - p1;
 | 
			
		||||
            Eigen::Vector3d V = p3 - p1;
 | 
			
		||||
            return U.cross(V).normalized();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        inline bool is_inside() {
 | 
			
		||||
            return m_face_id >= 0 && normal().dot(m_dir) > 0;
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Casting a ray on the mesh, returns the distance where the hit occures.
 | 
			
		||||
    hit_result query_ray_hit(const Vec3d &s, const Vec3d &dir) const;
 | 
			
		||||
 | 
			
		||||
    class si_result {
 | 
			
		||||
        double m_value;
 | 
			
		||||
        int m_fidx;
 | 
			
		||||
        Vec3d m_p;
 | 
			
		||||
        si_result(double val, int i, const Vec3d& c):
 | 
			
		||||
            m_value(val), m_fidx(i), m_p(c) {}
 | 
			
		||||
        friend class EigenMesh3D;
 | 
			
		||||
    public:
 | 
			
		||||
 | 
			
		||||
        si_result() = delete;
 | 
			
		||||
 | 
			
		||||
        double value() const { return m_value; }
 | 
			
		||||
        operator double() const { return m_value; }
 | 
			
		||||
        const Vec3d& point_on_mesh() const { return m_p; }
 | 
			
		||||
        int F_idx() const { return m_fidx; }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // The signed distance from a point to the mesh. Outputs the distance,
 | 
			
		||||
    // the index of the triangle and the closest point in mesh coordinate space.
 | 
			
		||||
    si_result signed_distance(const Vec3d& p) const;
 | 
			
		||||
 | 
			
		||||
    bool inside(const Vec3d& p) const;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
using PointSet = Eigen::MatrixXd;
 | 
			
		||||
 | 
			
		||||
//EigenMesh3D to_eigenmesh(const TriangleMesh& m);
 | 
			
		||||
| 
						 | 
				
			
			@ -193,7 +116,7 @@ using PointSet = Eigen::MatrixXd;
 | 
			
		|||
//EigenMesh3D to_eigenmesh(const ModelObject& model);
 | 
			
		||||
 | 
			
		||||
// Simple conversion of 'vector of points' to an Eigen matrix
 | 
			
		||||
PointSet    to_point_set(const std::vector<Vec3d>&);
 | 
			
		||||
PointSet    to_point_set(const std::vector<sla::SupportPoint>&);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* ************************************************************************** */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -95,7 +95,9 @@ size_t SpatIndex::size() const
 | 
			
		|||
 | 
			
		||||
class EigenMesh3D::AABBImpl: public igl::AABB<Eigen::MatrixXd, 3> {
 | 
			
		||||
public:
 | 
			
		||||
#ifdef SLIC3R_SLA_NEEDS_WINDTREE
 | 
			
		||||
    igl::WindingNumberAABB<Vec3d, Eigen::MatrixXd, Eigen::MatrixXi> windtree;
 | 
			
		||||
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -136,7 +138,9 @@ EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) {
 | 
			
		|||
 | 
			
		||||
    // Build the AABB accelaration tree
 | 
			
		||||
    m_aabb->init(m_V, m_F);
 | 
			
		||||
#ifdef SLIC3R_SLA_NEEDS_WINDTREE
 | 
			
		||||
    m_aabb->windtree.set_mesh(m_V, m_F);
 | 
			
		||||
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
EigenMesh3D::~EigenMesh3D() {}
 | 
			
		||||
| 
						 | 
				
			
			@ -168,6 +172,7 @@ EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
 | 
			
		|||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef SLIC3R_SLA_NEEDS_WINDTREE
 | 
			
		||||
EigenMesh3D::si_result EigenMesh3D::signed_distance(const Vec3d &p) const {
 | 
			
		||||
    double sign = 0; double sqdst = 0; int i = 0;  Vec3d c;
 | 
			
		||||
    igl::signed_distance_winding_number(*m_aabb, m_V, m_F, m_aabb->windtree,
 | 
			
		||||
| 
						 | 
				
			
			@ -179,6 +184,7 @@ EigenMesh3D::si_result EigenMesh3D::signed_distance(const Vec3d &p) const {
 | 
			
		|||
bool EigenMesh3D::inside(const Vec3d &p) const {
 | 
			
		||||
    return m_aabb->windtree.inside(p);
 | 
			
		||||
}
 | 
			
		||||
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
 | 
			
		||||
 | 
			
		||||
/* ****************************************************************************
 | 
			
		||||
 * Misc functions
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,7 +26,7 @@ using SupportTreePtr = std::unique_ptr<sla::SLASupportTree>;
 | 
			
		|||
class SLAPrintObject::SupportData {
 | 
			
		||||
public:
 | 
			
		||||
    sla::EigenMesh3D emesh;              // index-triangle representation
 | 
			
		||||
    sla::PointSet    support_points;     // all the support points (manual/auto)
 | 
			
		||||
    std::vector<sla::SupportPoint> support_points;     // all the support points (manual/auto)
 | 
			
		||||
    SupportTreePtr   support_tree_ptr;   // the supports
 | 
			
		||||
    SlicedSupports   support_slices;     // sliced supports
 | 
			
		||||
    std::vector<LevelID>    level_ids;
 | 
			
		||||
| 
						 | 
				
			
			@ -355,14 +355,18 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf
 | 
			
		|||
        std::vector<SLAPrintObject::Instance> new_instances = sla_instances(model_object);
 | 
			
		||||
        if (it_print_object_status != print_object_status.end() && it_print_object_status->status != PrintObjectStatus::Deleted) {
 | 
			
		||||
            // The SLAPrintObject is already there.
 | 
			
		||||
            if (new_instances != it_print_object_status->print_object->instances()) {
 | 
			
		||||
                // Instances changed.
 | 
			
		||||
                it_print_object_status->print_object->set_instances(new_instances);
 | 
			
		||||
                update_apply_status(this->invalidate_step(slapsRasterize));
 | 
			
		||||
            }
 | 
			
		||||
            print_objects_new.emplace_back(it_print_object_status->print_object);
 | 
			
		||||
            const_cast<PrintObjectStatus&>(*it_print_object_status).status = PrintObjectStatus::Reused;
 | 
			
		||||
        } else {
 | 
			
		||||
			if (new_instances.empty()) {
 | 
			
		||||
				const_cast<PrintObjectStatus&>(*it_print_object_status).status = PrintObjectStatus::Deleted;
 | 
			
		||||
			} else {
 | 
			
		||||
				if (new_instances != it_print_object_status->print_object->instances()) {
 | 
			
		||||
					// Instances changed.
 | 
			
		||||
					it_print_object_status->print_object->set_instances(new_instances);
 | 
			
		||||
					update_apply_status(this->invalidate_step(slapsRasterize));
 | 
			
		||||
				}
 | 
			
		||||
				print_objects_new.emplace_back(it_print_object_status->print_object);
 | 
			
		||||
				const_cast<PrintObjectStatus&>(*it_print_object_status).status = PrintObjectStatus::Reused;
 | 
			
		||||
			}
 | 
			
		||||
		} else if (! new_instances.empty()) {
 | 
			
		||||
            auto print_object = new SLAPrintObject(this, &model_object);
 | 
			
		||||
 | 
			
		||||
            // FIXME: this invalidates the transformed mesh in SLAPrintObject
 | 
			
		||||
| 
						 | 
				
			
			@ -473,7 +477,7 @@ void SLAPrint::process()
 | 
			
		|||
    const size_t objcount = m_objects.size();
 | 
			
		||||
 | 
			
		||||
    const unsigned min_objstatus = 0;   // where the per object operations start
 | 
			
		||||
    const unsigned max_objstatus = 80;  // where the per object operations end
 | 
			
		||||
    const unsigned max_objstatus = PRINT_STEP_LEVELS[slapsRasterize];  // where the per object operations end
 | 
			
		||||
 | 
			
		||||
    // the coefficient that multiplies the per object status values which
 | 
			
		||||
    // are set up for <0, 100>. They need to be scaled into the whole process
 | 
			
		||||
| 
						 | 
				
			
			@ -533,9 +537,8 @@ void SLAPrint::process()
 | 
			
		|||
            this->throw_if_canceled();
 | 
			
		||||
            SLAAutoSupports::Config config;
 | 
			
		||||
            const SLAPrintObjectConfig& cfg = po.config();
 | 
			
		||||
            config.minimal_z = float(cfg.support_minimal_z);
 | 
			
		||||
            config.density_at_45 = cfg.support_density_at_45 / 10000.f;
 | 
			
		||||
            config.density_at_horizontal = cfg.support_density_at_horizontal / 10000.f;
 | 
			
		||||
            config.density_relative = float(cfg.support_points_density_relative / 100.f); // the config value is in percents
 | 
			
		||||
            config.minimal_distance = float(cfg.support_points_minimal_distance);
 | 
			
		||||
 | 
			
		||||
            // Construction of this object does the calculation.
 | 
			
		||||
            this->throw_if_canceled();
 | 
			
		||||
| 
						 | 
				
			
			@ -547,17 +550,19 @@ void SLAPrint::process()
 | 
			
		|||
                                          [this]() { throw_if_canceled(); });
 | 
			
		||||
 | 
			
		||||
            // Now let's extract the result.
 | 
			
		||||
            const std::vector<Vec3d>& points = auto_supports.output();
 | 
			
		||||
            const std::vector<sla::SupportPoint>& points = auto_supports.output();
 | 
			
		||||
            this->throw_if_canceled();
 | 
			
		||||
            po.m_supportdata->support_points = sla::to_point_set(points);
 | 
			
		||||
            po.m_supportdata->support_points = points;
 | 
			
		||||
 | 
			
		||||
            BOOST_LOG_TRIVIAL(debug) << "Automatic support points: "
 | 
			
		||||
                                     << po.m_supportdata->support_points.rows();
 | 
			
		||||
                                     << po.m_supportdata->support_points.size();
 | 
			
		||||
 | 
			
		||||
            // Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass the update status to GLGizmoSlaSupports
 | 
			
		||||
            report_status(*this, -1, L("Generating support points"), SlicingStatus::RELOAD_SLA_SUPPORT_POINTS);
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            // There are some points on the front-end, no calculation will be done.
 | 
			
		||||
            po.m_supportdata->support_points =
 | 
			
		||||
                    sla::to_point_set(po.transformed_support_points());
 | 
			
		||||
            po.m_supportdata->support_points = po.transformed_support_points();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -588,6 +593,8 @@ void SLAPrint::process()
 | 
			
		|||
 | 
			
		||||
            ctl.statuscb = [this, init, d](unsigned st, const std::string& msg)
 | 
			
		||||
            {
 | 
			
		||||
                //FIXME this status line scaling does not seem to be correct.
 | 
			
		||||
                // How does it account for an increasing object index?
 | 
			
		||||
                report_status(*this, int(init + st*d), msg);
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -595,7 +602,7 @@ void SLAPrint::process()
 | 
			
		|||
            ctl.cancelfn = [this]() { throw_if_canceled(); };
 | 
			
		||||
 | 
			
		||||
            po.m_supportdata->support_tree_ptr.reset(
 | 
			
		||||
                        new SLASupportTree(po.m_supportdata->support_points,
 | 
			
		||||
                        new SLASupportTree(sla::to_point_set(po.m_supportdata->support_points),
 | 
			
		||||
                                           po.m_supportdata->emesh, scfg, ctl));
 | 
			
		||||
 | 
			
		||||
            // Create the unified mesh
 | 
			
		||||
| 
						 | 
				
			
			@ -606,7 +613,7 @@ void SLAPrint::process()
 | 
			
		|||
            po.m_supportdata->support_tree_ptr->merged_mesh();
 | 
			
		||||
 | 
			
		||||
            BOOST_LOG_TRIVIAL(debug) << "Processed support point count "
 | 
			
		||||
                                     << po.m_supportdata->support_points.rows();
 | 
			
		||||
                                     << po.m_supportdata->support_points.size();
 | 
			
		||||
 | 
			
		||||
            // Check the mesh for later troubleshooting.
 | 
			
		||||
            if(po.support_mesh().empty())
 | 
			
		||||
| 
						 | 
				
			
			@ -884,16 +891,6 @@ void SLAPrint::process()
 | 
			
		|||
    using slaposFn = std::function<void(SLAPrintObject&)>;
 | 
			
		||||
    using slapsFn  = std::function<void(void)>;
 | 
			
		||||
 | 
			
		||||
    // This is the actual order of steps done on each PrintObject
 | 
			
		||||
    std::array<SLAPrintObjectStep, slaposCount> objectsteps = {
 | 
			
		||||
        slaposObjectSlice,      // SupportPoints will need this step
 | 
			
		||||
        slaposSupportPoints,
 | 
			
		||||
        slaposSupportTree,
 | 
			
		||||
        slaposBasePool,
 | 
			
		||||
        slaposSliceSupports,
 | 
			
		||||
        slaposIndexSlices
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    std::array<slaposFn, slaposCount> pobj_program =
 | 
			
		||||
    {
 | 
			
		||||
        slice_model,
 | 
			
		||||
| 
						 | 
				
			
			@ -917,28 +914,32 @@ void SLAPrint::process()
 | 
			
		|||
 | 
			
		||||
    // TODO: this loop could run in parallel but should not exhaust all the CPU
 | 
			
		||||
    // power available
 | 
			
		||||
    for(SLAPrintObject * po : m_objects) {
 | 
			
		||||
    // Calculate the support structures first before slicing the supports, so that the preview will get displayed ASAP for all objects.
 | 
			
		||||
    std::vector<SLAPrintObjectStep> step_ranges = { slaposObjectSlice, slaposSliceSupports, slaposCount };
 | 
			
		||||
    for (size_t idx_range = 0; idx_range + 1 < step_ranges.size(); ++ idx_range) {
 | 
			
		||||
        for(SLAPrintObject * po : m_objects) {
 | 
			
		||||
 | 
			
		||||
        BOOST_LOG_TRIVIAL(info) << "Slicing object " << po->model_object()->name;
 | 
			
		||||
            BOOST_LOG_TRIVIAL(info) << "Slicing object " << po->model_object()->name;
 | 
			
		||||
 | 
			
		||||
        for(size_t s = 0; s < objectsteps.size(); ++s) {
 | 
			
		||||
            auto currentstep = objectsteps[s];
 | 
			
		||||
            for (int s = (int)step_ranges[idx_range]; s < (int)step_ranges[idx_range + 1]; ++s) {
 | 
			
		||||
                auto currentstep = (SLAPrintObjectStep)s;
 | 
			
		||||
 | 
			
		||||
            // Cancellation checking. Each step will check for cancellation
 | 
			
		||||
            // on its own and return earlier gracefully. Just after it returns
 | 
			
		||||
            // execution gets to this point and throws the canceled signal.
 | 
			
		||||
            throw_if_canceled();
 | 
			
		||||
 | 
			
		||||
            st += unsigned(incr * ostepd);
 | 
			
		||||
 | 
			
		||||
            if(po->m_stepmask[currentstep] && po->set_started(currentstep)) {
 | 
			
		||||
                report_status(*this, int(st), OBJ_STEP_LABELS[currentstep]);
 | 
			
		||||
                pobj_program[currentstep](*po);
 | 
			
		||||
                // Cancellation checking. Each step will check for cancellation
 | 
			
		||||
                // on its own and return earlier gracefully. Just after it returns
 | 
			
		||||
                // execution gets to this point and throws the canceled signal.
 | 
			
		||||
                throw_if_canceled();
 | 
			
		||||
                po->set_done(currentstep);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            incr = OBJ_STEP_LEVELS[currentstep];
 | 
			
		||||
                st += unsigned(incr * ostepd);
 | 
			
		||||
 | 
			
		||||
                if(po->m_stepmask[currentstep] && po->set_started(currentstep)) {
 | 
			
		||||
                    report_status(*this, int(st), OBJ_STEP_LABELS[currentstep]);
 | 
			
		||||
                    pobj_program[currentstep](*po);
 | 
			
		||||
                    throw_if_canceled();
 | 
			
		||||
                    po->set_done(currentstep);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                incr = OBJ_STEP_LEVELS[currentstep];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1240,7 +1241,10 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf
 | 
			
		|||
    for (const t_config_option_key &opt_key : opt_keys) {
 | 
			
		||||
		if (opt_key == "layer_height") {
 | 
			
		||||
			steps.emplace_back(slaposObjectSlice);
 | 
			
		||||
        } else if (opt_key == "supports_enable") {
 | 
			
		||||
        } else if (
 | 
			
		||||
               opt_key == "supports_enable"
 | 
			
		||||
            || opt_key == "support_points_density_relative"
 | 
			
		||||
            || opt_key == "support_points_minimal_distance") {
 | 
			
		||||
            steps.emplace_back(slaposSupportPoints);
 | 
			
		||||
		} else if (
 | 
			
		||||
               opt_key == "support_head_front_diameter"
 | 
			
		||||
| 
						 | 
				
			
			@ -1343,7 +1347,7 @@ const std::vector<ExPolygons> EMPTY_SLICES;
 | 
			
		|||
const TriangleMesh EMPTY_MESH;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const Eigen::MatrixXd& SLAPrintObject::get_support_points() const
 | 
			
		||||
const std::vector<sla::SupportPoint>& SLAPrintObject::get_support_points() const
 | 
			
		||||
{
 | 
			
		||||
    return m_supportdata->support_points;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1422,15 +1426,19 @@ const TriangleMesh &SLAPrintObject::transformed_mesh() const {
 | 
			
		|||
    return m_transformed_rmesh.get();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<Vec3d> SLAPrintObject::transformed_support_points() const
 | 
			
		||||
std::vector<sla::SupportPoint> SLAPrintObject::transformed_support_points() const
 | 
			
		||||
{
 | 
			
		||||
    assert(m_model_object != nullptr);
 | 
			
		||||
    auto& spts = m_model_object->sla_support_points;
 | 
			
		||||
    std::vector<sla::SupportPoint>& spts = m_model_object->sla_support_points;
 | 
			
		||||
 | 
			
		||||
    // this could be cached as well
 | 
			
		||||
    std::vector<Vec3d> ret; ret.reserve(spts.size());
 | 
			
		||||
    std::vector<sla::SupportPoint> ret;
 | 
			
		||||
    ret.reserve(spts.size());
 | 
			
		||||
 | 
			
		||||
    for(auto& sp : spts) ret.emplace_back( trafo() * Vec3d(sp.cast<double>()));
 | 
			
		||||
    for(sla::SupportPoint& sp : spts) {
 | 
			
		||||
        Vec3d transformed_pos = trafo() * Vec3d(sp.pos(0), sp.pos(1), sp.pos(2));
 | 
			
		||||
        ret.emplace_back(transformed_pos(0), transformed_pos(1), transformed_pos(2), sp.head_front_radius, sp.is_new_island);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -70,7 +70,7 @@ public:
 | 
			
		|||
    // This will return the transformed mesh which is cached
 | 
			
		||||
    const TriangleMesh&     transformed_mesh() const;
 | 
			
		||||
 | 
			
		||||
    std::vector<Vec3d>      transformed_support_points() const;
 | 
			
		||||
    std::vector<sla::SupportPoint>      transformed_support_points() const;
 | 
			
		||||
 | 
			
		||||
    // Get the needed Z elevation for the model geometry if supports should be
 | 
			
		||||
    // displayed. This Z offset should also be applied to the support
 | 
			
		||||
| 
						 | 
				
			
			@ -91,7 +91,7 @@ public:
 | 
			
		|||
    const std::vector<ExPolygons>& get_support_slices() const;
 | 
			
		||||
 | 
			
		||||
    // This method returns the support points of this SLAPrintObject.
 | 
			
		||||
    const Eigen::MatrixXd& get_support_points() const;
 | 
			
		||||
    const std::vector<sla::SupportPoint>& get_support_points() const;
 | 
			
		||||
 | 
			
		||||
    // An index record referencing the slices
 | 
			
		||||
    // (get_model_slices(), get_support_slices()) where the keys are the height
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,8 +23,6 @@
 | 
			
		|||
// Scene's GUI made using imgui library
 | 
			
		||||
#define ENABLE_IMGUI (1 && ENABLE_1_42_0_ALPHA1)
 | 
			
		||||
#define DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI (1 && ENABLE_IMGUI)
 | 
			
		||||
// Modified Sla support gizmo
 | 
			
		||||
#define ENABLE_SLA_SUPPORT_GIZMO_MOD (1 && ENABLE_1_42_0_ALPHA1)
 | 
			
		||||
// Use wxDataViewRender instead of wxDataViewCustomRenderer
 | 
			
		||||
#define ENABLE_NONCUSTOM_DATA_VIEW_RENDERING (0 && ENABLE_1_42_0_ALPHA1)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ public:
 | 
			
		|||
        gluDeleteTess(m_tesselator);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Pointf3s tesselate(const ExPolygon &expoly, double z_, bool flipped_)
 | 
			
		||||
    std::vector<Vec3d> tesselate3d(const ExPolygon &expoly, double z_, bool flipped_)
 | 
			
		||||
    {
 | 
			
		||||
        m_z = z_;
 | 
			
		||||
        m_flipped = flipped_;
 | 
			
		||||
| 
						 | 
				
			
			@ -56,7 +56,7 @@ public:
 | 
			
		|||
        return std::move(m_output_triangles);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Pointf3s tesselate(const ExPolygons &expolygons, double z_, bool flipped_)
 | 
			
		||||
    std::vector<Vec3d> tesselate3d(const ExPolygons &expolygons, double z_, bool flipped_)
 | 
			
		||||
    {
 | 
			
		||||
        m_z = z_;
 | 
			
		||||
        m_flipped = flipped_;
 | 
			
		||||
| 
						 | 
				
			
			@ -189,16 +189,60 @@ private:
 | 
			
		|||
    bool            m_flipped;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Pointf3s triangulate_expolygons_3df(const ExPolygon &poly, coordf_t z, bool flip)
 | 
			
		||||
std::vector<Vec3d> triangulate_expolygon_3d(const ExPolygon &poly, coordf_t z, bool flip)
 | 
			
		||||
{
 | 
			
		||||
    GluTessWrapper tess;
 | 
			
		||||
    return tess.tesselate(poly, z, flip);
 | 
			
		||||
    return tess.tesselate3d(poly, z, flip);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Pointf3s triangulate_expolygons_3df(const ExPolygons &polys, coordf_t z, bool flip)
 | 
			
		||||
std::vector<Vec3d> triangulate_expolygons_3d(const ExPolygons &polys, coordf_t z, bool flip)
 | 
			
		||||
{
 | 
			
		||||
	GluTessWrapper tess;
 | 
			
		||||
    return tess.tesselate(polys, z, flip);
 | 
			
		||||
    return tess.tesselate3d(polys, z, flip);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<Vec2d> triangulate_expolygon_2d(const ExPolygon &poly, bool flip)
 | 
			
		||||
{
 | 
			
		||||
    GluTessWrapper tess;
 | 
			
		||||
    std::vector<Vec3d> triangles = tess.tesselate3d(poly, 0, flip);
 | 
			
		||||
    std::vector<Vec2d> out;
 | 
			
		||||
    out.reserve(triangles.size());
 | 
			
		||||
    for (const Vec3d &pt : triangles)
 | 
			
		||||
        out.emplace_back(pt.x(), pt.y());
 | 
			
		||||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<Vec2d> triangulate_expolygons_2d(const ExPolygons &polys, bool flip)
 | 
			
		||||
{
 | 
			
		||||
    GluTessWrapper tess;
 | 
			
		||||
    std::vector<Vec3d> triangles = tess.tesselate3d(polys, 0, flip);
 | 
			
		||||
    std::vector<Vec2d> out;
 | 
			
		||||
    out.reserve(triangles.size());
 | 
			
		||||
    for (const Vec3d &pt : triangles)
 | 
			
		||||
        out.emplace_back(pt.x(), pt.y());
 | 
			
		||||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<Vec2f> triangulate_expolygon_2f(const ExPolygon &poly, bool flip)
 | 
			
		||||
{
 | 
			
		||||
    GluTessWrapper tess;
 | 
			
		||||
    std::vector<Vec3d> triangles = tess.tesselate3d(poly, 0, flip);
 | 
			
		||||
    std::vector<Vec2f> out;
 | 
			
		||||
    out.reserve(triangles.size());
 | 
			
		||||
    for (const Vec3d &pt : triangles)
 | 
			
		||||
        out.emplace_back(float(pt.x()), float(pt.y()));
 | 
			
		||||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<Vec2f> triangulate_expolygons_2f(const ExPolygons &polys, bool flip)
 | 
			
		||||
{
 | 
			
		||||
    GluTessWrapper tess;
 | 
			
		||||
    std::vector<Vec3d> triangles = tess.tesselate3d(polys, 0, flip);
 | 
			
		||||
    std::vector<Vec2f> out;
 | 
			
		||||
    out.reserve(triangles.size());
 | 
			
		||||
    for (const Vec3d &pt : triangles)
 | 
			
		||||
        out.emplace_back(float(pt.x()), float(pt.y()));
 | 
			
		||||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace Slic3r
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,8 +10,12 @@ namespace Slic3r {
 | 
			
		|||
class ExPolygon;
 | 
			
		||||
typedef std::vector<ExPolygon> ExPolygons;
 | 
			
		||||
 | 
			
		||||
extern Pointf3s triangulate_expolygons_3df(const ExPolygon &poly, coordf_t z = 0, bool flip = false);
 | 
			
		||||
extern Pointf3s triangulate_expolygons_3df(const ExPolygons &polys, coordf_t z = 0, bool flip = false);
 | 
			
		||||
extern std::vector<Vec3d> triangulate_expolygon_3d (const ExPolygon  &poly,  coordf_t z = 0, bool flip = false);
 | 
			
		||||
extern std::vector<Vec3d> triangulate_expolygons_3d(const ExPolygons &polys, coordf_t z = 0, bool flip = false);
 | 
			
		||||
extern std::vector<Vec2d> triangulate_expolygon_2d (const ExPolygon  &poly,  bool flip = false);
 | 
			
		||||
extern std::vector<Vec2d> triangulate_expolygons_2d(const ExPolygons &polys, bool flip = false);
 | 
			
		||||
extern std::vector<Vec2f> triangulate_expolygon_2f (const ExPolygon  &poly,  bool flip = false);
 | 
			
		||||
extern std::vector<Vec2f> triangulate_expolygons_2f(const ExPolygons &polys, bool flip = false);
 | 
			
		||||
 | 
			
		||||
} // namespace Slic3r
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1781,7 +1781,7 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower)
 | 
			
		|||
        BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - triangulating upper part";
 | 
			
		||||
        ExPolygons section;
 | 
			
		||||
        this->make_expolygons_simple(upper_lines, §ion);
 | 
			
		||||
        Pointf3s triangles = triangulate_expolygons_3df(section, z, true);
 | 
			
		||||
        Pointf3s triangles = triangulate_expolygons_3d(section, z, true);
 | 
			
		||||
        stl_facet facet;
 | 
			
		||||
        facet.normal = stl_normal(0, 0, -1.f);
 | 
			
		||||
        for (size_t i = 0; i < triangles.size(); ) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1795,7 +1795,7 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower)
 | 
			
		|||
        BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - triangulating lower part";
 | 
			
		||||
        ExPolygons section;
 | 
			
		||||
        this->make_expolygons_simple(lower_lines, §ion);
 | 
			
		||||
        Pointf3s triangles = triangulate_expolygons_3df(section, z, false);
 | 
			
		||||
        Pointf3s triangles = triangulate_expolygons_3d(section, z, false);
 | 
			
		||||
        stl_facet facet;
 | 
			
		||||
        facet.normal = stl_normal(0, 0, -1.f);
 | 
			
		||||
        for (size_t i = 0; i < triangles.size(); ) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -42,22 +42,27 @@ namespace Slic3r {
 | 
			
		|||
 | 
			
		||||
static boost::log::trivial::severity_level logSeverity = boost::log::trivial::error;
 | 
			
		||||
 | 
			
		||||
void set_logging_level(unsigned int level)
 | 
			
		||||
static boost::log::trivial::severity_level level_to_boost(unsigned level)
 | 
			
		||||
{
 | 
			
		||||
    switch (level) {
 | 
			
		||||
    // Report fatal errors only.
 | 
			
		||||
    case 0: logSeverity = boost::log::trivial::fatal; break;
 | 
			
		||||
    case 0: return boost::log::trivial::fatal;
 | 
			
		||||
    // Report fatal errors and errors.
 | 
			
		||||
    case 1: logSeverity = boost::log::trivial::error; break;
 | 
			
		||||
    case 1: return boost::log::trivial::error;
 | 
			
		||||
    // Report fatal errors, errors and warnings.
 | 
			
		||||
    case 2: logSeverity = boost::log::trivial::warning; break;
 | 
			
		||||
    case 2: return boost::log::trivial::warning;
 | 
			
		||||
    // Report all errors, warnings and infos.
 | 
			
		||||
    case 3: logSeverity = boost::log::trivial::info; break;
 | 
			
		||||
    case 3: return boost::log::trivial::info;
 | 
			
		||||
    // Report all errors, warnings, infos and debugging.
 | 
			
		||||
    case 4: logSeverity = boost::log::trivial::debug; break;
 | 
			
		||||
    case 4: return boost::log::trivial::debug;
 | 
			
		||||
    // Report everyting including fine level tracing information.
 | 
			
		||||
    default: logSeverity = boost::log::trivial::trace; break;
 | 
			
		||||
    default: return boost::log::trivial::trace;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void set_logging_level(unsigned int level)
 | 
			
		||||
{
 | 
			
		||||
    logSeverity = level_to_boost(level);
 | 
			
		||||
 | 
			
		||||
    boost::log::core::get()->set_filter
 | 
			
		||||
    (
 | 
			
		||||
| 
						 | 
				
			
			@ -73,6 +78,7 @@ unsigned get_logging_level()
 | 
			
		|||
    case boost::log::trivial::warning : return 2;
 | 
			
		||||
    case boost::log::trivial::info : return 3;
 | 
			
		||||
    case boost::log::trivial::debug : return 4;
 | 
			
		||||
    case boost::log::trivial::trace : return 5;
 | 
			
		||||
    default: return 1;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -88,21 +94,7 @@ static struct RunOnInit {
 | 
			
		|||
 | 
			
		||||
void trace(unsigned int level, const char *message)
 | 
			
		||||
{
 | 
			
		||||
    boost::log::trivial::severity_level severity = boost::log::trivial::trace;
 | 
			
		||||
    switch (level) {
 | 
			
		||||
    // Report fatal errors only.
 | 
			
		||||
    case 0: severity = boost::log::trivial::fatal; break;
 | 
			
		||||
    // Report fatal errors and errors.
 | 
			
		||||
    case 1: severity = boost::log::trivial::error; break;
 | 
			
		||||
    // Report fatal errors, errors and warnings.
 | 
			
		||||
    case 2: severity = boost::log::trivial::warning; break;
 | 
			
		||||
    // Report all errors, warnings and infos.
 | 
			
		||||
    case 3: severity = boost::log::trivial::info; break;
 | 
			
		||||
    // Report all errors, warnings, infos and debugging.
 | 
			
		||||
    case 4: severity = boost::log::trivial::debug; break;
 | 
			
		||||
    // Report everyting including fine level tracing information.
 | 
			
		||||
    default: severity = boost::log::trivial::trace; break;
 | 
			
		||||
    }
 | 
			
		||||
    boost::log::trivial::severity_level severity = level_to_boost(level);
 | 
			
		||||
 | 
			
		||||
    BOOST_LOG_STREAM_WITH_PARAMS(::boost::log::trivial::logger::get(),\
 | 
			
		||||
        (::boost::log::keywords::severity = severity)) << message;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -793,7 +793,7 @@ void GLVolumeCollection::render_VBOs(GLVolumeCollection::ERenderType type, bool
 | 
			
		|||
    glsafe(::glDisable(GL_BLEND));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLVolumeCollection::render_legacy(ERenderType type, bool disable_cullface) const
 | 
			
		||||
void GLVolumeCollection::render_legacy(ERenderType type, bool disable_cullface, std::function<bool(const GLVolume&)> filter_func) const
 | 
			
		||||
{
 | 
			
		||||
    glsafe(glEnable(GL_BLEND));
 | 
			
		||||
    glsafe(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
 | 
			
		||||
| 
						 | 
				
			
			@ -805,7 +805,7 @@ void GLVolumeCollection::render_legacy(ERenderType type, bool disable_cullface)
 | 
			
		|||
    glsafe(glEnableClientState(GL_VERTEX_ARRAY));
 | 
			
		||||
    glsafe(glEnableClientState(GL_NORMAL_ARRAY));
 | 
			
		||||
 
 | 
			
		||||
	GLVolumesWithZList to_render = volumes_to_render(this->volumes, type, std::function<bool(const GLVolume&)>());
 | 
			
		||||
	GLVolumesWithZList to_render = volumes_to_render(this->volumes, type, filter_func);
 | 
			
		||||
    for (GLVolumeWithZ& volume : to_render)
 | 
			
		||||
    {
 | 
			
		||||
        volume.first->set_render_color();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -456,7 +456,7 @@ public:
 | 
			
		|||
 | 
			
		||||
    // Render the volumes by OpenGL.
 | 
			
		||||
	void render_VBOs(ERenderType type, bool disable_cullface, std::function<bool(const GLVolume&)> filter_func = std::function<bool(const GLVolume&)>()) const;
 | 
			
		||||
    void render_legacy(ERenderType type, bool disable_cullface) const;
 | 
			
		||||
    void render_legacy(ERenderType type, bool disable_cullface, std::function<bool(const GLVolume&)> filter_func = std::function<bool(const GLVolume&)>()) const;
 | 
			
		||||
 | 
			
		||||
    // Finalize the initialization of the geometry & indices,
 | 
			
		||||
    // upload the geometry and indices to OpenGL VBO objects
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,30 +16,30 @@ namespace GUI {
 | 
			
		|||
class ConfigWizard: public wxDialog
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	// Why is the Wizard run
 | 
			
		||||
	enum RunReason {
 | 
			
		||||
		RR_DATA_EMPTY,                  // No or empty datadir
 | 
			
		||||
		RR_DATA_LEGACY,                 // Pre-updating datadir
 | 
			
		||||
		RR_DATA_INCOMPAT,               // Incompatible datadir - Slic3r downgrade situation
 | 
			
		||||
		RR_USER,                        // User requested the Wizard from the menus
 | 
			
		||||
	};
 | 
			
		||||
    // Why is the Wizard run
 | 
			
		||||
    enum RunReason {
 | 
			
		||||
        RR_DATA_EMPTY,                  // No or empty datadir
 | 
			
		||||
        RR_DATA_LEGACY,                 // Pre-updating datadir
 | 
			
		||||
        RR_DATA_INCOMPAT,               // Incompatible datadir - Slic3r downgrade situation
 | 
			
		||||
        RR_USER,                        // User requested the Wizard from the menus
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
	ConfigWizard(wxWindow *parent, RunReason run_reason);
 | 
			
		||||
	ConfigWizard(ConfigWizard &&) = delete;
 | 
			
		||||
	ConfigWizard(const ConfigWizard &) = delete;
 | 
			
		||||
	ConfigWizard &operator=(ConfigWizard &&) = delete;
 | 
			
		||||
	ConfigWizard &operator=(const ConfigWizard &) = delete;
 | 
			
		||||
	~ConfigWizard();
 | 
			
		||||
    ConfigWizard(wxWindow *parent, RunReason run_reason);
 | 
			
		||||
    ConfigWizard(ConfigWizard &&) = delete;
 | 
			
		||||
    ConfigWizard(const ConfigWizard &) = delete;
 | 
			
		||||
    ConfigWizard &operator=(ConfigWizard &&) = delete;
 | 
			
		||||
    ConfigWizard &operator=(const ConfigWizard &) = delete;
 | 
			
		||||
    ~ConfigWizard();
 | 
			
		||||
 | 
			
		||||
	// Run the Wizard. Return whether it was completed.
 | 
			
		||||
	bool run(PresetBundle *preset_bundle, const PresetUpdater *updater);
 | 
			
		||||
    // Run the Wizard. Return whether it was completed.
 | 
			
		||||
    bool run(PresetBundle *preset_bundle, const PresetUpdater *updater);
 | 
			
		||||
 | 
			
		||||
	static const wxString& name(const bool from_menu = false);
 | 
			
		||||
    static const wxString& name(const bool from_menu = false);
 | 
			
		||||
private:
 | 
			
		||||
	struct priv;
 | 
			
		||||
	std::unique_ptr<priv> p;
 | 
			
		||||
    struct priv;
 | 
			
		||||
    std::unique_ptr<priv> p;
 | 
			
		||||
 | 
			
		||||
	friend struct ConfigWizardPage;
 | 
			
		||||
    friend struct ConfigWizardPage;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,7 @@
 | 
			
		|||
#include <vector>
 | 
			
		||||
#include <set>
 | 
			
		||||
#include <unordered_map>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <boost/filesystem.hpp>
 | 
			
		||||
 | 
			
		||||
#include <wx/sizer.h>
 | 
			
		||||
| 
						 | 
				
			
			@ -13,6 +14,7 @@
 | 
			
		|||
#include <wx/button.h>
 | 
			
		||||
#include <wx/choice.h>
 | 
			
		||||
#include <wx/spinctrl.h>
 | 
			
		||||
#include <wx/textctrl.h>
 | 
			
		||||
 | 
			
		||||
#include "libslic3r/PrintConfig.hpp"
 | 
			
		||||
#include "slic3r/Utils/PresetUpdater.hpp"
 | 
			
		||||
| 
						 | 
				
			
			@ -26,211 +28,264 @@ namespace Slic3r {
 | 
			
		|||
namespace GUI {
 | 
			
		||||
 | 
			
		||||
enum {
 | 
			
		||||
	WRAP_WIDTH = 500,
 | 
			
		||||
	MODEL_MIN_WRAP = 150,
 | 
			
		||||
    WRAP_WIDTH = 500,
 | 
			
		||||
    MODEL_MIN_WRAP = 150,
 | 
			
		||||
 | 
			
		||||
	DIALOG_MARGIN = 15,
 | 
			
		||||
	INDEX_MARGIN = 40,
 | 
			
		||||
	BTN_SPACING = 10,
 | 
			
		||||
	INDENT_SPACING = 30,
 | 
			
		||||
	VERTICAL_SPACING = 10,
 | 
			
		||||
    DIALOG_MARGIN = 15,
 | 
			
		||||
    INDEX_MARGIN = 40,
 | 
			
		||||
    BTN_SPACING = 10,
 | 
			
		||||
    INDENT_SPACING = 30,
 | 
			
		||||
    VERTICAL_SPACING = 10,
 | 
			
		||||
 | 
			
		||||
    MAX_COLS = 4,
 | 
			
		||||
    ROW_SPACING = 75,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
typedef std::function<bool(const VendorProfile::PrinterModel&)> ModelFilter;
 | 
			
		||||
 | 
			
		||||
struct PrinterPicker: wxPanel
 | 
			
		||||
{
 | 
			
		||||
	struct Checkbox : wxCheckBox
 | 
			
		||||
	{
 | 
			
		||||
		Checkbox(wxWindow *parent, const wxString &label, const std::string &model, const std::string &variant) :
 | 
			
		||||
			wxCheckBox(parent, wxID_ANY, label),
 | 
			
		||||
			model(model),
 | 
			
		||||
			variant(variant)
 | 
			
		||||
		{}
 | 
			
		||||
    struct Checkbox : wxCheckBox
 | 
			
		||||
    {
 | 
			
		||||
        Checkbox(wxWindow *parent, const wxString &label, const std::string &model, const std::string &variant) :
 | 
			
		||||
            wxCheckBox(parent, wxID_ANY, label),
 | 
			
		||||
            model(model),
 | 
			
		||||
            variant(variant)
 | 
			
		||||
        {}
 | 
			
		||||
 | 
			
		||||
		std::string model;
 | 
			
		||||
		std::string variant;
 | 
			
		||||
	};
 | 
			
		||||
        std::string model;
 | 
			
		||||
        std::string variant;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
	const std::string vendor_id;
 | 
			
		||||
	std::vector<Checkbox*> cboxes;
 | 
			
		||||
	unsigned variants_checked;
 | 
			
		||||
    const std::string vendor_id;
 | 
			
		||||
    std::vector<Checkbox*> cboxes;
 | 
			
		||||
    std::vector<Checkbox*> cboxes_alt;
 | 
			
		||||
 | 
			
		||||
	PrinterPicker(wxWindow *parent, const VendorProfile &vendor, const AppConfig &appconfig_vendors);
 | 
			
		||||
    PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig_vendors, const ModelFilter &filter);
 | 
			
		||||
    PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig_vendors);
 | 
			
		||||
 | 
			
		||||
	void select_all(bool select);
 | 
			
		||||
	void select_one(size_t i, bool select);
 | 
			
		||||
	void on_checkbox(const Checkbox *cbox, bool checked);
 | 
			
		||||
    void select_all(bool select, bool alternates = false);
 | 
			
		||||
    void select_one(size_t i, bool select);
 | 
			
		||||
    void on_checkbox(const Checkbox *cbox, bool checked);
 | 
			
		||||
 | 
			
		||||
    int get_width() const { return width; }
 | 
			
		||||
private:
 | 
			
		||||
    int width;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct ConfigWizardPage: wxPanel
 | 
			
		||||
{
 | 
			
		||||
	ConfigWizard *parent;
 | 
			
		||||
	const wxString shortname;
 | 
			
		||||
	wxBoxSizer *content;
 | 
			
		||||
    ConfigWizard *parent;
 | 
			
		||||
    const wxString shortname;
 | 
			
		||||
    wxBoxSizer *content;
 | 
			
		||||
    const unsigned indent;
 | 
			
		||||
 | 
			
		||||
	ConfigWizardPage(ConfigWizard *parent, wxString title, wxString shortname);
 | 
			
		||||
    ConfigWizardPage(ConfigWizard *parent, wxString title, wxString shortname, unsigned indent = 0);
 | 
			
		||||
    virtual ~ConfigWizardPage();
 | 
			
		||||
 | 
			
		||||
	virtual ~ConfigWizardPage();
 | 
			
		||||
    template<class T>
 | 
			
		||||
    void append(T *thing, int proportion = 0, int flag = wxEXPAND|wxTOP|wxBOTTOM, int border = 10)
 | 
			
		||||
    {
 | 
			
		||||
        content->Add(thing, proportion, flag, border);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	ConfigWizardPage* page_prev() const { return p_prev; }
 | 
			
		||||
	ConfigWizardPage* page_next() const { return p_next; }
 | 
			
		||||
	ConfigWizardPage* chain(ConfigWizardPage *page);
 | 
			
		||||
    void append_text(wxString text);
 | 
			
		||||
    void append_spacer(int space);
 | 
			
		||||
 | 
			
		||||
	template<class T>
 | 
			
		||||
	void append(T *thing, int proportion = 0, int flag = wxEXPAND|wxTOP|wxBOTTOM, int border = 10)
 | 
			
		||||
	{
 | 
			
		||||
		content->Add(thing, proportion, flag, border);
 | 
			
		||||
	}
 | 
			
		||||
    ConfigWizard::priv *wizard_p() const { return parent->p.get(); }
 | 
			
		||||
 | 
			
		||||
	void append_text(wxString text);
 | 
			
		||||
	void append_spacer(int space);
 | 
			
		||||
 | 
			
		||||
	ConfigWizard::priv *wizard_p() const { return parent->p.get(); }
 | 
			
		||||
 | 
			
		||||
	virtual bool Show(bool show = true);
 | 
			
		||||
	virtual bool Hide() { return Show(false); }
 | 
			
		||||
	virtual wxPanel* extra_buttons() { return nullptr; }
 | 
			
		||||
	virtual void on_page_set() {}
 | 
			
		||||
	virtual void apply_custom_config(DynamicPrintConfig &config) {}
 | 
			
		||||
 | 
			
		||||
	void enable_next(bool enable);
 | 
			
		||||
private:
 | 
			
		||||
	ConfigWizardPage *p_prev;
 | 
			
		||||
	ConfigWizardPage *p_next;
 | 
			
		||||
    virtual void apply_custom_config(DynamicPrintConfig &config) {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct PageWelcome: ConfigWizardPage
 | 
			
		||||
{
 | 
			
		||||
	PrinterPicker *printer_picker;
 | 
			
		||||
	wxPanel *others_buttons;
 | 
			
		||||
	wxCheckBox *cbox_reset;
 | 
			
		||||
    wxCheckBox *cbox_reset;
 | 
			
		||||
 | 
			
		||||
	PageWelcome(ConfigWizard *parent, bool check_first_variant);
 | 
			
		||||
    PageWelcome(ConfigWizard *parent);
 | 
			
		||||
 | 
			
		||||
	virtual wxPanel* extra_buttons() { return others_buttons; }
 | 
			
		||||
	virtual void on_page_set();
 | 
			
		||||
    bool reset_user_profile() const { return cbox_reset != nullptr ? cbox_reset->GetValue() : false; }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct PagePrinters: ConfigWizardPage
 | 
			
		||||
{
 | 
			
		||||
    enum Technology {
 | 
			
		||||
        // Bitflag equivalent of PrinterTechnology
 | 
			
		||||
        T_FFF = 0x1,
 | 
			
		||||
        T_SLA = 0x2,
 | 
			
		||||
        T_Any = ~0,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    std::vector<PrinterPicker *> printer_pickers;
 | 
			
		||||
 | 
			
		||||
    PagePrinters(ConfigWizard *parent, wxString title, wxString shortname, const VendorProfile &vendor, unsigned indent, Technology technology);
 | 
			
		||||
 | 
			
		||||
    void select_all(bool select, bool alternates = false);
 | 
			
		||||
    int get_width() const;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct PageCustom: ConfigWizardPage
 | 
			
		||||
{
 | 
			
		||||
    PageCustom(ConfigWizard *parent);
 | 
			
		||||
 | 
			
		||||
    bool custom_wanted() const { return cb_custom->GetValue(); }
 | 
			
		||||
    std::string profile_name() const { return into_u8(tc_profile_name->GetValue()); }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    static const char* default_profile_name;
 | 
			
		||||
 | 
			
		||||
    wxCheckBox *cb_custom;
 | 
			
		||||
    wxTextCtrl *tc_profile_name;
 | 
			
		||||
    wxString profile_name_prev;
 | 
			
		||||
 | 
			
		||||
	bool reset_user_profile() const { return cbox_reset != nullptr ? cbox_reset->GetValue() : false; }
 | 
			
		||||
	void on_variant_checked();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct PageUpdate: ConfigWizardPage
 | 
			
		||||
{
 | 
			
		||||
	bool version_check;
 | 
			
		||||
	bool preset_update;
 | 
			
		||||
    bool version_check;
 | 
			
		||||
    bool preset_update;
 | 
			
		||||
 | 
			
		||||
	PageUpdate(ConfigWizard *parent);
 | 
			
		||||
    PageUpdate(ConfigWizard *parent);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct PageVendors: ConfigWizardPage
 | 
			
		||||
{
 | 
			
		||||
	std::vector<PrinterPicker*> pickers;
 | 
			
		||||
    std::vector<PrinterPicker*> pickers;
 | 
			
		||||
 | 
			
		||||
	PageVendors(ConfigWizard *parent);
 | 
			
		||||
    PageVendors(ConfigWizard *parent);
 | 
			
		||||
 | 
			
		||||
	virtual void on_page_set();
 | 
			
		||||
 | 
			
		||||
	void on_vendor_pick(size_t i);
 | 
			
		||||
	void on_variant_checked();
 | 
			
		||||
    void on_vendor_pick(size_t i);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct PageFirmware: ConfigWizardPage
 | 
			
		||||
{
 | 
			
		||||
	const ConfigOptionDef &gcode_opt;
 | 
			
		||||
	wxChoice *gcode_picker;
 | 
			
		||||
    const ConfigOptionDef &gcode_opt;
 | 
			
		||||
    wxChoice *gcode_picker;
 | 
			
		||||
 | 
			
		||||
	PageFirmware(ConfigWizard *parent);
 | 
			
		||||
	virtual void apply_custom_config(DynamicPrintConfig &config);
 | 
			
		||||
    PageFirmware(ConfigWizard *parent);
 | 
			
		||||
    virtual void apply_custom_config(DynamicPrintConfig &config);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct PageBedShape: ConfigWizardPage
 | 
			
		||||
{
 | 
			
		||||
	BedShapePanel *shape_panel;
 | 
			
		||||
    BedShapePanel *shape_panel;
 | 
			
		||||
 | 
			
		||||
	PageBedShape(ConfigWizard *parent);
 | 
			
		||||
	virtual void apply_custom_config(DynamicPrintConfig &config);
 | 
			
		||||
    PageBedShape(ConfigWizard *parent);
 | 
			
		||||
    virtual void apply_custom_config(DynamicPrintConfig &config);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct PageDiameters: ConfigWizardPage
 | 
			
		||||
{
 | 
			
		||||
	wxSpinCtrlDouble *spin_nozzle;
 | 
			
		||||
	wxSpinCtrlDouble *spin_filam;
 | 
			
		||||
    wxSpinCtrlDouble *spin_nozzle;
 | 
			
		||||
    wxSpinCtrlDouble *spin_filam;
 | 
			
		||||
 | 
			
		||||
	PageDiameters(ConfigWizard *parent);
 | 
			
		||||
	virtual void apply_custom_config(DynamicPrintConfig &config);
 | 
			
		||||
    PageDiameters(ConfigWizard *parent);
 | 
			
		||||
    virtual void apply_custom_config(DynamicPrintConfig &config);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct PageTemperatures: ConfigWizardPage
 | 
			
		||||
{
 | 
			
		||||
	wxSpinCtrlDouble *spin_extr;
 | 
			
		||||
	wxSpinCtrlDouble *spin_bed;
 | 
			
		||||
    wxSpinCtrlDouble *spin_extr;
 | 
			
		||||
    wxSpinCtrlDouble *spin_bed;
 | 
			
		||||
 | 
			
		||||
	PageTemperatures(ConfigWizard *parent);
 | 
			
		||||
	virtual void apply_custom_config(DynamicPrintConfig &config);
 | 
			
		||||
    PageTemperatures(ConfigWizard *parent);
 | 
			
		||||
    virtual void apply_custom_config(DynamicPrintConfig &config);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ConfigWizardIndex: public wxPanel
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	ConfigWizardIndex(wxWindow *parent);
 | 
			
		||||
    ConfigWizardIndex(wxWindow *parent);
 | 
			
		||||
 | 
			
		||||
    void add_page(ConfigWizardPage *page);
 | 
			
		||||
    void add_label(wxString label, unsigned indent = 0);
 | 
			
		||||
 | 
			
		||||
    size_t active_item() const { return item_active; }
 | 
			
		||||
    ConfigWizardPage* active_page() const;
 | 
			
		||||
    bool active_is_last() const { return item_active < items.size() && item_active == last_page; }
 | 
			
		||||
 | 
			
		||||
    void go_prev();
 | 
			
		||||
    void go_next();
 | 
			
		||||
    void go_to(size_t i);
 | 
			
		||||
    void go_to(ConfigWizardPage *page);
 | 
			
		||||
 | 
			
		||||
    void clear();
 | 
			
		||||
 | 
			
		||||
	void load_items(ConfigWizardPage *firstpage);
 | 
			
		||||
	void set_active(ConfigWizardPage *page);
 | 
			
		||||
private:
 | 
			
		||||
	const wxBitmap bg;
 | 
			
		||||
	const wxBitmap bullet_black;
 | 
			
		||||
	const wxBitmap bullet_blue;
 | 
			
		||||
	const wxBitmap bullet_white;
 | 
			
		||||
	int text_height;
 | 
			
		||||
    struct Item
 | 
			
		||||
    {
 | 
			
		||||
        wxString label;
 | 
			
		||||
        unsigned indent;
 | 
			
		||||
        ConfigWizardPage *page;     // nullptr page => label-only item
 | 
			
		||||
 | 
			
		||||
	std::vector<wxString> items;
 | 
			
		||||
	std::vector<wxString>::const_iterator item_active;
 | 
			
		||||
        bool operator==(ConfigWizardPage *page) const { return this->page == page; }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
	void on_paint(wxPaintEvent &evt);
 | 
			
		||||
    int em;
 | 
			
		||||
    int em_h;
 | 
			
		||||
 | 
			
		||||
    const wxBitmap bg;
 | 
			
		||||
    const wxBitmap bullet_black;
 | 
			
		||||
    const wxBitmap bullet_blue;
 | 
			
		||||
    const wxBitmap bullet_white;
 | 
			
		||||
 | 
			
		||||
    std::vector<Item> items;
 | 
			
		||||
    size_t item_active;
 | 
			
		||||
    ssize_t item_hover;
 | 
			
		||||
    size_t last_page;
 | 
			
		||||
 | 
			
		||||
    int item_height() const { return std::max(bullet_black.GetSize().GetHeight(), em) + em; }
 | 
			
		||||
 | 
			
		||||
    void on_paint(wxPaintEvent &evt);
 | 
			
		||||
    void on_mouse_move(wxMouseEvent &evt);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
wxDEFINE_EVENT(EVT_INDEX_PAGE, wxCommandEvent);
 | 
			
		||||
 | 
			
		||||
struct ConfigWizard::priv
 | 
			
		||||
{
 | 
			
		||||
	ConfigWizard *q;
 | 
			
		||||
	ConfigWizard::RunReason run_reason;
 | 
			
		||||
	AppConfig appconfig_vendors;
 | 
			
		||||
	std::unordered_map<std::string, VendorProfile> vendors;
 | 
			
		||||
	std::unordered_map<std::string, std::string> vendors_rsrc;
 | 
			
		||||
	std::unique_ptr<DynamicPrintConfig> custom_config;
 | 
			
		||||
    ConfigWizard *q;
 | 
			
		||||
    ConfigWizard::RunReason run_reason;
 | 
			
		||||
    AppConfig appconfig_vendors;
 | 
			
		||||
    std::unordered_map<std::string, VendorProfile> vendors;
 | 
			
		||||
    std::unordered_map<std::string, std::string> vendors_rsrc;
 | 
			
		||||
    std::unique_ptr<DynamicPrintConfig> custom_config;
 | 
			
		||||
 | 
			
		||||
	wxScrolledWindow *hscroll = nullptr;
 | 
			
		||||
	wxBoxSizer *hscroll_sizer = nullptr;
 | 
			
		||||
	wxBoxSizer *btnsizer = nullptr;
 | 
			
		||||
	ConfigWizardPage *page_current = nullptr;
 | 
			
		||||
	ConfigWizardIndex *index = nullptr;
 | 
			
		||||
	wxButton *btn_prev = nullptr;
 | 
			
		||||
	wxButton *btn_next = nullptr;
 | 
			
		||||
	wxButton *btn_finish = nullptr;
 | 
			
		||||
	wxButton *btn_cancel = nullptr;
 | 
			
		||||
    wxScrolledWindow *hscroll = nullptr;
 | 
			
		||||
    wxBoxSizer *hscroll_sizer = nullptr;
 | 
			
		||||
    wxBoxSizer *btnsizer = nullptr;
 | 
			
		||||
    ConfigWizardPage *page_current = nullptr;
 | 
			
		||||
    ConfigWizardIndex *index = nullptr;
 | 
			
		||||
    wxButton *btn_prev = nullptr;
 | 
			
		||||
    wxButton *btn_next = nullptr;
 | 
			
		||||
    wxButton *btn_finish = nullptr;
 | 
			
		||||
    wxButton *btn_cancel = nullptr;
 | 
			
		||||
 | 
			
		||||
	PageWelcome      *page_welcome = nullptr;
 | 
			
		||||
	PageUpdate       *page_update = nullptr;
 | 
			
		||||
	PageVendors      *page_vendors = nullptr;
 | 
			
		||||
	PageFirmware     *page_firmware = nullptr;
 | 
			
		||||
	PageBedShape     *page_bed = nullptr;
 | 
			
		||||
	PageDiameters    *page_diams = nullptr;
 | 
			
		||||
	PageTemperatures *page_temps = nullptr;
 | 
			
		||||
    PageWelcome      *page_welcome = nullptr;
 | 
			
		||||
    PagePrinters     *page_fff = nullptr;
 | 
			
		||||
    PagePrinters     *page_msla = nullptr;
 | 
			
		||||
    PageCustom       *page_custom = nullptr;
 | 
			
		||||
    PageUpdate       *page_update = nullptr;
 | 
			
		||||
    PageVendors      *page_vendors = nullptr;   // XXX: ?
 | 
			
		||||
 | 
			
		||||
	priv(ConfigWizard *q) : q(q) {}
 | 
			
		||||
    // Custom setup pages
 | 
			
		||||
    PageFirmware     *page_firmware = nullptr;
 | 
			
		||||
    PageBedShape     *page_bed = nullptr;
 | 
			
		||||
    PageDiameters    *page_diams = nullptr;
 | 
			
		||||
    PageTemperatures *page_temps = nullptr;
 | 
			
		||||
 | 
			
		||||
	void load_vendors();
 | 
			
		||||
	void add_page(ConfigWizardPage *page);
 | 
			
		||||
	void index_refresh();
 | 
			
		||||
	void set_page(ConfigWizardPage *page);
 | 
			
		||||
	void layout_fit();
 | 
			
		||||
	void go_prev() { if (page_current != nullptr) { set_page(page_current->page_prev()); } }
 | 
			
		||||
	void go_next() { if (page_current != nullptr) { set_page(page_current->page_next()); } }
 | 
			
		||||
	void enable_next(bool enable);
 | 
			
		||||
    priv(ConfigWizard *q) : q(q) {}
 | 
			
		||||
 | 
			
		||||
	void on_other_vendors();
 | 
			
		||||
	void on_custom_setup();
 | 
			
		||||
    void load_pages(bool custom_setup);
 | 
			
		||||
 | 
			
		||||
	void apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater);
 | 
			
		||||
    bool check_first_variant() const;
 | 
			
		||||
    void load_vendors();
 | 
			
		||||
    void add_page(ConfigWizardPage *page);
 | 
			
		||||
    void enable_next(bool enable);
 | 
			
		||||
 | 
			
		||||
    void on_custom_setup(bool custom_wanted);
 | 
			
		||||
 | 
			
		||||
    void apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3158,6 +3158,7 @@ void GLCanvas3D::Gizmos::update_on_off_state(const GLCanvas3D& canvas, const Vec
 | 
			
		|||
    float cnv_h = (float)canvas.get_canvas_size().get_height();
 | 
			
		||||
    float height = _get_total_overlay_height();
 | 
			
		||||
    float top_y = 0.5f * (cnv_h - height) + m_overlay_border;
 | 
			
		||||
 | 
			
		||||
    for (GizmosMap::iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it)
 | 
			
		||||
    {
 | 
			
		||||
        if ((it->second == nullptr) || !it->second->is_selectable())
 | 
			
		||||
| 
						 | 
				
			
			@ -3440,42 +3441,28 @@ void GLCanvas3D::Gizmos::set_flattening_data(const ModelObject* model_object)
 | 
			
		|||
        reinterpret_cast<GLGizmoFlatten*>(it->second)->set_flattening_data(model_object);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#if ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
void GLCanvas3D::Gizmos::set_sla_support_data(ModelObject* model_object, const GLCanvas3D::Selection& selection)
 | 
			
		||||
#else
 | 
			
		||||
void GLCanvas3D::Gizmos::set_model_object_ptr(ModelObject* model_object)
 | 
			
		||||
#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
{
 | 
			
		||||
    if (!m_enabled)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    GizmosMap::const_iterator it = m_gizmos.find(SlaSupports);
 | 
			
		||||
    if (it != m_gizmos.end())
 | 
			
		||||
#if ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
        reinterpret_cast<GLGizmoSlaSupports*>(it->second)->set_sla_support_data(model_object, selection);
 | 
			
		||||
#else
 | 
			
		||||
        reinterpret_cast<GLGizmoSlaSupports*>(it->second)->set_model_object_ptr(model_object);
 | 
			
		||||
#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLCanvas3D::Gizmos::clicked_on_object(const Vec2d& mouse_position)
 | 
			
		||||
 | 
			
		||||
// Returns true if the gizmo used the event to do something, false otherwise.
 | 
			
		||||
bool GLCanvas3D::Gizmos::mouse_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down)
 | 
			
		||||
{
 | 
			
		||||
    if (!m_enabled)
 | 
			
		||||
        return;
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
    GizmosMap::const_iterator it = m_gizmos.find(SlaSupports);
 | 
			
		||||
    if (it != m_gizmos.end())
 | 
			
		||||
        reinterpret_cast<GLGizmoSlaSupports*>(it->second)->clicked_on_object(mouse_position);
 | 
			
		||||
}
 | 
			
		||||
        return reinterpret_cast<GLGizmoSlaSupports*>(it->second)->mouse_event(action, mouse_position, shift_down);
 | 
			
		||||
 | 
			
		||||
void GLCanvas3D::Gizmos::delete_current_grabber(bool delete_all)
 | 
			
		||||
{
 | 
			
		||||
    if (!m_enabled)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    GizmosMap::const_iterator it = m_gizmos.find(SlaSupports);
 | 
			
		||||
    if (it != m_gizmos.end())
 | 
			
		||||
        reinterpret_cast<GLGizmoSlaSupports*>(it->second)->delete_current_grabber(delete_all);
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLCanvas3D::Gizmos::render_current_gizmo(const GLCanvas3D::Selection& selection) const
 | 
			
		||||
| 
						 | 
				
			
			@ -3690,7 +3677,40 @@ GLCanvas3D::WarningTexture::WarningTexture()
 | 
			
		|||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool GLCanvas3D::WarningTexture::generate(const std::string& msg, const GLCanvas3D& canvas)
 | 
			
		||||
void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool state, const GLCanvas3D& canvas)
 | 
			
		||||
{
 | 
			
		||||
    auto it = std::find(m_warnings.begin(), m_warnings.end(), warning);
 | 
			
		||||
 | 
			
		||||
    if (state) {
 | 
			
		||||
        if (it != m_warnings.end()) // this warning is already set to be shown
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        m_warnings.push_back(warning);
 | 
			
		||||
        std::sort(m_warnings.begin(), m_warnings.end());
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        if (it == m_warnings.end()) // deactivating something that is not active is an easy task
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        m_warnings.erase(it);
 | 
			
		||||
        if (m_warnings.empty()) { // nothing remains to be shown
 | 
			
		||||
            reset();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Look at the end of our vector and generate proper texture.
 | 
			
		||||
    std::string text;
 | 
			
		||||
    switch (m_warnings.back()) {
 | 
			
		||||
        case ObjectOutside      : text = L("Detected object outside print volume"); break;
 | 
			
		||||
        case ToolpathOutside    : text = L("Detected toolpath outside print volume"); break;
 | 
			
		||||
        case SomethingNotShown  : text = L("Some objects are not visible when editing supports"); break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _generate(text, canvas); // GUI::GLTexture::reset() is called at the beginning of generate(...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool GLCanvas3D::WarningTexture::_generate(const std::string& msg, const GLCanvas3D& canvas)
 | 
			
		||||
{
 | 
			
		||||
    reset();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3763,6 +3783,9 @@ bool GLCanvas3D::WarningTexture::generate(const std::string& msg, const GLCanvas
 | 
			
		|||
 | 
			
		||||
void GLCanvas3D::WarningTexture::render(const GLCanvas3D& canvas) const
 | 
			
		||||
{
 | 
			
		||||
    if (m_warnings.empty())
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    if ((m_id > 0) && (m_original_width > 0) && (m_original_height > 0) && (m_width > 0) && (m_height > 0))
 | 
			
		||||
    {
 | 
			
		||||
        ::glDisable(GL_DEPTH_TEST);
 | 
			
		||||
| 
						 | 
				
			
			@ -4087,7 +4110,6 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas)
 | 
			
		|||
    , m_apply_zoom_to_volumes_filter(false)
 | 
			
		||||
    , m_hover_volume_id(-1)
 | 
			
		||||
    , m_toolbar_action_running(false)
 | 
			
		||||
    , m_warning_texture_enabled(false)
 | 
			
		||||
    , m_legend_texture_enabled(false)
 | 
			
		||||
    , m_picking_enabled(false)
 | 
			
		||||
    , m_moving_enabled(false)
 | 
			
		||||
| 
						 | 
				
			
			@ -4097,6 +4119,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas)
 | 
			
		|||
    , m_moving(false)
 | 
			
		||||
    , m_color_by("volume")
 | 
			
		||||
    , m_reload_delayed(false)
 | 
			
		||||
    , m_render_sla_auxiliaries(true)
 | 
			
		||||
#if !ENABLE_IMGUI
 | 
			
		||||
    , m_external_gizmo_widgets_parent(nullptr)
 | 
			
		||||
#endif // not ENABLE_IMGUI
 | 
			
		||||
| 
						 | 
				
			
			@ -4244,8 +4267,7 @@ void GLCanvas3D::reset_volumes()
 | 
			
		|||
        m_dirty = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    enable_warning_texture(false);
 | 
			
		||||
    _reset_warning_texture();
 | 
			
		||||
    _set_warning_texture(WarningTexture::ObjectOutside, false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int GLCanvas3D::check_volumes_outside_state() const
 | 
			
		||||
| 
						 | 
				
			
			@ -4255,6 +4277,34 @@ int GLCanvas3D::check_volumes_outside_state() const
 | 
			
		|||
    return (int)state;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLCanvas3D::toggle_sla_auxiliaries_visibility(bool visible)
 | 
			
		||||
{
 | 
			
		||||
    for (GLVolume* vol : m_volumes.volumes) {
 | 
			
		||||
        if (vol->composite_id.volume_id < 0)
 | 
			
		||||
             vol->is_active = visible;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    m_render_sla_auxiliaries = visible;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject* mo, int instance_idx)
 | 
			
		||||
{
 | 
			
		||||
    for (GLVolume* vol : m_volumes.volumes) {
 | 
			
		||||
        if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo)
 | 
			
		||||
        && (instance_idx == -1 || vol->composite_id.instance_id == instance_idx))
 | 
			
		||||
            vol->is_active = visible;
 | 
			
		||||
    }
 | 
			
		||||
    if (visible && !mo)
 | 
			
		||||
        toggle_sla_auxiliaries_visibility(true);
 | 
			
		||||
 | 
			
		||||
    if (!mo && !visible && !m_model->objects.empty() && (m_model->objects.size() > 1 || m_model->objects.front()->instances.size() > 1))
 | 
			
		||||
        _set_warning_texture(WarningTexture::SomethingNotShown, true);
 | 
			
		||||
 | 
			
		||||
    if (!mo && visible)
 | 
			
		||||
        _set_warning_texture(WarningTexture::SomethingNotShown, false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void GLCanvas3D::set_config(const DynamicPrintConfig* config)
 | 
			
		||||
{
 | 
			
		||||
    m_config = config;
 | 
			
		||||
| 
						 | 
				
			
			@ -4365,11 +4415,6 @@ void GLCanvas3D::enable_layers_editing(bool enable)
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLCanvas3D::enable_warning_texture(bool enable)
 | 
			
		||||
{
 | 
			
		||||
    m_warning_texture_enabled = enable;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLCanvas3D::enable_legend_texture(bool enable)
 | 
			
		||||
{
 | 
			
		||||
    m_legend_texture_enabled = enable;
 | 
			
		||||
| 
						 | 
				
			
			@ -4609,9 +4654,9 @@ void GLCanvas3D::render()
 | 
			
		|||
    // this position is used later into on_mouse() to drag the objects
 | 
			
		||||
    m_mouse.scene_position = _mouse_to_3d(m_mouse.position.cast<int>());
 | 
			
		||||
 | 
			
		||||
    _render_current_gizmo();
 | 
			
		||||
    _render_selection_sidebar_hints();
 | 
			
		||||
 | 
			
		||||
    _render_current_gizmo();
 | 
			
		||||
#if ENABLE_SHOW_CAMERA_TARGET
 | 
			
		||||
    _render_camera_target();
 | 
			
		||||
#endif // ENABLE_SHOW_CAMERA_TARGET
 | 
			
		||||
| 
						 | 
				
			
			@ -4988,22 +5033,19 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
 | 
			
		|||
 | 
			
		||||
        if (!contained)
 | 
			
		||||
        {
 | 
			
		||||
            enable_warning_texture(true);
 | 
			
		||||
            _generate_warning_texture(L("Detected object outside print volume"));
 | 
			
		||||
            _set_warning_texture(WarningTexture::ObjectOutside, true);
 | 
			
		||||
            post_event(Event<bool>(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, state == ModelInstance::PVS_Fully_Outside));
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            enable_warning_texture(false);
 | 
			
		||||
            m_volumes.reset_outside_state();
 | 
			
		||||
            _reset_warning_texture();
 | 
			
		||||
            _set_warning_texture(WarningTexture::ObjectOutside, false);
 | 
			
		||||
            post_event(Event<bool>(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, !m_model->objects.empty()));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        enable_warning_texture(false);
 | 
			
		||||
        _reset_warning_texture();
 | 
			
		||||
        _set_warning_texture(WarningTexture::ObjectOutside, false);
 | 
			
		||||
        post_event(Event<bool>(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, false));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -5119,6 +5161,7 @@ void GLCanvas3D::bind_event_handlers()
 | 
			
		|||
        m_canvas->Bind(wxEVT_SIZE, &GLCanvas3D::on_size, this);
 | 
			
		||||
        m_canvas->Bind(wxEVT_IDLE, &GLCanvas3D::on_idle, this);
 | 
			
		||||
        m_canvas->Bind(wxEVT_CHAR, &GLCanvas3D::on_char, this);
 | 
			
		||||
        m_canvas->Bind(wxEVT_KEY_UP, &GLCanvas3D::on_key_up, this);
 | 
			
		||||
        m_canvas->Bind(wxEVT_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this);
 | 
			
		||||
        m_canvas->Bind(wxEVT_TIMER, &GLCanvas3D::on_timer, this);
 | 
			
		||||
        m_canvas->Bind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this);
 | 
			
		||||
| 
						 | 
				
			
			@ -5144,6 +5187,7 @@ void GLCanvas3D::unbind_event_handlers()
 | 
			
		|||
        m_canvas->Unbind(wxEVT_SIZE, &GLCanvas3D::on_size, this);
 | 
			
		||||
        m_canvas->Unbind(wxEVT_IDLE, &GLCanvas3D::on_idle, this);
 | 
			
		||||
        m_canvas->Unbind(wxEVT_CHAR, &GLCanvas3D::on_char, this);
 | 
			
		||||
        m_canvas->Unbind(wxEVT_KEY_UP, &GLCanvas3D::on_key_up, this);
 | 
			
		||||
        m_canvas->Unbind(wxEVT_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this);
 | 
			
		||||
        m_canvas->Unbind(wxEVT_TIMER, &GLCanvas3D::on_timer, this);
 | 
			
		||||
        m_canvas->Unbind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this);
 | 
			
		||||
| 
						 | 
				
			
			@ -5187,7 +5231,12 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
 | 
			
		|||
        switch (keyCode) {
 | 
			
		||||
        case 'a':
 | 
			
		||||
        case 'A':
 | 
			
		||||
        case WXK_CONTROL_A: post_event(SimpleEvent(EVT_GLCANVAS_SELECT_ALL)); break;
 | 
			
		||||
        case WXK_CONTROL_A:
 | 
			
		||||
            if (m_gizmos.get_current_type() == Gizmos::SlaSupports && m_gizmos.mouse_event(SLAGizmoEventType::SelectAll)) // Sla gizmo selects all support points
 | 
			
		||||
                m_dirty = true;
 | 
			
		||||
            else
 | 
			
		||||
                post_event(SimpleEvent(EVT_GLCANVAS_SELECT_ALL));
 | 
			
		||||
        break;
 | 
			
		||||
#ifdef __APPLE__
 | 
			
		||||
        case WXK_BACK: // the low cost Apple solutions are not equipped with a Delete key, use Backspace instead.
 | 
			
		||||
#else /* __APPLE__ */
 | 
			
		||||
| 
						 | 
				
			
			@ -5208,7 +5257,12 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
 | 
			
		|||
#else /* __APPLE__ */
 | 
			
		||||
		case WXK_DELETE:
 | 
			
		||||
#endif /* __APPLE__ */
 | 
			
		||||
                  post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE)); break;
 | 
			
		||||
                  if (m_gizmos.get_current_type() == Gizmos::SlaSupports && m_gizmos.mouse_event(SLAGizmoEventType::Delete))
 | 
			
		||||
                      m_dirty = true;
 | 
			
		||||
                  else
 | 
			
		||||
                      post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE));
 | 
			
		||||
                  break;
 | 
			
		||||
 | 
			
		||||
		case '0': { select_view("iso"); break; }
 | 
			
		||||
        case '1': { select_view("top"); break; }
 | 
			
		||||
        case '2': { select_view("bottom"); break; }
 | 
			
		||||
| 
						 | 
				
			
			@ -5245,6 +5299,16 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLCanvas3D::on_key_up(wxKeyEvent& evt)
 | 
			
		||||
{
 | 
			
		||||
    // see include/wx/defs.h enum wxKeyCode
 | 
			
		||||
    int keyCode = evt.GetKeyCode();
 | 
			
		||||
 | 
			
		||||
    // shift has been just released - SLA gizmo might want to close rectangular selection.
 | 
			
		||||
    if (m_gizmos.get_current_type() == Gizmos::SlaSupports && keyCode == WXK_SHIFT && m_gizmos.mouse_event(SLAGizmoEventType::ShiftUp))
 | 
			
		||||
        m_dirty = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
 | 
			
		||||
{
 | 
			
		||||
    // Ignore the wheel events if the middle button is pressed.
 | 
			
		||||
| 
						 | 
				
			
			@ -5332,14 +5396,21 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
 | 
			
		|||
        // Set focus in order to remove it from sidebar fields
 | 
			
		||||
        if (m_canvas != nullptr) {
 | 
			
		||||
            // Only set focus, if the top level window of this canvas is active.
 | 
			
		||||
			auto p = dynamic_cast<wxWindow*>(evt.GetEventObject());
 | 
			
		||||
            auto p = dynamic_cast<wxWindow*>(evt.GetEventObject());
 | 
			
		||||
            while (p->GetParent())
 | 
			
		||||
                p = p->GetParent();
 | 
			
		||||
            auto *top_level_wnd = dynamic_cast<wxTopLevelWindow*>(p);
 | 
			
		||||
            if (top_level_wnd && top_level_wnd->IsActive())
 | 
			
		||||
            {
 | 
			
		||||
                m_canvas->SetFocus();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
                // forces a frame render to ensure that m_hover_volume_id 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 
 | 
			
		||||
                m_mouse.position = pos.cast<double>();
 | 
			
		||||
                render();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        m_mouse.set_start_position_2D_as_invalid();
 | 
			
		||||
//#endif
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -5385,22 +5456,16 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
 | 
			
		|||
                m_dirty = true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
#if !ENABLE_IMGUI
 | 
			
		||||
        else if ((m_gizmos.get_current_type() == Gizmos::SlaSupports) && gizmo_reset_rect_contains(*this, pos(0), pos(1)))
 | 
			
		||||
        {
 | 
			
		||||
            if (evt.LeftDown())
 | 
			
		||||
            {
 | 
			
		||||
                m_gizmos.delete_current_grabber(true);
 | 
			
		||||
                m_dirty = true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
#endif // not ENABLE_IMGUI
 | 
			
		||||
        else if (!m_selection.is_empty() && gizmos_overlay_contains_mouse)
 | 
			
		||||
        {
 | 
			
		||||
            m_gizmos.update_on_off_state(*this, m_mouse.position, m_selection);
 | 
			
		||||
            _update_gizmos_data();
 | 
			
		||||
            m_dirty = true;
 | 
			
		||||
        }
 | 
			
		||||
        else if (evt.LeftDown() && m_gizmos.get_current_type() == Gizmos::SlaSupports && evt.ShiftDown() && m_gizmos.mouse_event(SLAGizmoEventType::LeftDown, Vec2d(pos(0), pos(1)), evt.ShiftDown()))
 | 
			
		||||
        {
 | 
			
		||||
            // the gizmo got the event and took some action, there is no need to do anything more
 | 
			
		||||
        }
 | 
			
		||||
        else if (evt.LeftDown() && !m_selection.is_empty() && m_gizmos.grabber_contains_mouse())
 | 
			
		||||
        {
 | 
			
		||||
            _update_gizmos_data();
 | 
			
		||||
| 
						 | 
				
			
			@ -5416,9 +5481,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
 | 
			
		|||
 | 
			
		||||
            m_dirty = true;
 | 
			
		||||
        }
 | 
			
		||||
        else if ((selected_object_idx != -1) && m_gizmos.grabber_contains_mouse() && evt.RightDown()) {
 | 
			
		||||
            if (m_gizmos.get_current_type() == Gizmos::SlaSupports)
 | 
			
		||||
                m_gizmos.delete_current_grabber();
 | 
			
		||||
        else if ((selected_object_idx != -1) && evt.RightDown() && m_gizmos.get_current_type() == Gizmos::SlaSupports && m_gizmos.mouse_event(SLAGizmoEventType::RightDown))
 | 
			
		||||
        {
 | 
			
		||||
            // event was taken care of by the SlaSupports gizmo
 | 
			
		||||
        }
 | 
			
		||||
        else if (view_toolbar_contains_mouse != -1)
 | 
			
		||||
        {
 | 
			
		||||
| 
						 | 
				
			
			@ -5474,7 +5539,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
 | 
			
		|||
            }
 | 
			
		||||
 | 
			
		||||
            // propagate event through callback
 | 
			
		||||
 | 
			
		||||
            if (m_hover_volume_id != -1)
 | 
			
		||||
            {
 | 
			
		||||
                if (evt.LeftDown() && m_moving_enabled && (m_mouse.drag.move_volume_idx == -1))
 | 
			
		||||
| 
						 | 
				
			
			@ -5493,9 +5557,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
 | 
			
		|||
                }
 | 
			
		||||
                else if (evt.RightDown())
 | 
			
		||||
                {
 | 
			
		||||
                    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
 | 
			
		||||
                    // the context menu is already shown, ensuring it to disappear if the mouse is outside any volume
 | 
			
		||||
                    m_mouse.position = Vec2d((double)pos(0), (double)pos(1));
 | 
			
		||||
                    // the context menu is already shown
 | 
			
		||||
                    render();
 | 
			
		||||
                    if (m_hover_volume_id != -1)
 | 
			
		||||
                    {
 | 
			
		||||
| 
						 | 
				
			
			@ -5509,14 +5573,14 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
 | 
			
		|||
                            post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT));
 | 
			
		||||
                            _update_gizmos_data();
 | 
			
		||||
                            wxGetApp().obj_manipul()->update_settings_value(m_selection);
 | 
			
		||||
                            // forces a frame render to update the view before the context menu is shown
 | 
			
		||||
                            render();
 | 
			
		||||
 | 
			
		||||
//                            // forces a frame render to update the view before the context menu is shown
 | 
			
		||||
//                            render();
 | 
			
		||||
                            
 | 
			
		||||
                            Vec2d logical_pos = pos.cast<double>();
 | 
			
		||||
#if ENABLE_RETINA_GL
 | 
			
		||||
                            const float factor = m_retina_helper->get_scale_factor();
 | 
			
		||||
                            logical_pos = logical_pos.cwiseQuotient(Vec2d(factor, factor));
 | 
			
		||||
#endif
 | 
			
		||||
#endif // ENABLE_RETINA_GL
 | 
			
		||||
                            post_event(Vec2dEvent(EVT_GLCANVAS_RIGHT_CLICK, logical_pos));
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
| 
						 | 
				
			
			@ -5524,7 +5588,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
 | 
			
		|||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    else if (evt.Dragging() && evt.LeftIsDown() && !gizmos_overlay_contains_mouse && (m_layers_editing.state == LayersEditing::Unknown) && (m_mouse.drag.move_volume_idx != -1))
 | 
			
		||||
    else if (evt.Dragging() && evt.LeftIsDown() && !gizmos_overlay_contains_mouse && (m_layers_editing.state == LayersEditing::Unknown)
 | 
			
		||||
          && (m_mouse.drag.move_volume_idx != -1) && m_gizmos.get_current_type() != Gizmos::SlaSupports /* don't allow dragging objects with the Sla gizmo on */)
 | 
			
		||||
    {
 | 
			
		||||
#if ENABLE_MOVE_MIN_THRESHOLD
 | 
			
		||||
        if (!m_mouse.drag.move_requires_threshold)
 | 
			
		||||
| 
						 | 
				
			
			@ -5584,6 +5649,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
 | 
			
		|||
 | 
			
		||||
        m_dirty = true;
 | 
			
		||||
    }
 | 
			
		||||
    else if (evt.Dragging() && m_gizmos.get_current_type() == Gizmos::SlaSupports && evt.ShiftDown() && m_gizmos.mouse_event(SLAGizmoEventType::Dragging, Vec2d(pos(0), pos(1)), evt.ShiftDown()))
 | 
			
		||||
    {
 | 
			
		||||
        // the gizmo got the event and took some action, no need to do anything more here
 | 
			
		||||
        m_dirty = true;
 | 
			
		||||
    }
 | 
			
		||||
    else if (evt.Dragging() && !gizmos_overlay_contains_mouse)
 | 
			
		||||
    {
 | 
			
		||||
        m_mouse.dragging = true;
 | 
			
		||||
| 
						 | 
				
			
			@ -5639,6 +5709,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
 | 
			
		|||
            _stop_timer();
 | 
			
		||||
            m_layers_editing.accept_changes(*this);
 | 
			
		||||
        }
 | 
			
		||||
        else if (evt.LeftUp() && m_gizmos.get_current_type() == Gizmos::SlaSupports && !m_gizmos.is_dragging()
 | 
			
		||||
              && !m_mouse.dragging && m_gizmos.mouse_event(SLAGizmoEventType::LeftUp, Vec2d(pos(0), pos(1)), evt.ShiftDown()))
 | 
			
		||||
        {
 | 
			
		||||
            // the gizmo got the event and took some action, no need to do anything more
 | 
			
		||||
        }
 | 
			
		||||
        else if ((m_mouse.drag.move_volume_idx != -1) && m_mouse.dragging)
 | 
			
		||||
        {
 | 
			
		||||
            m_regenerate_volumes = false;
 | 
			
		||||
| 
						 | 
				
			
			@ -5648,16 +5723,12 @@ 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_gizmos.get_current_type() == Gizmos::SlaSupports && m_hover_volume_id != -1)
 | 
			
		||||
        else if (evt.LeftUp() && !m_mouse.dragging && (m_hover_volume_id == -1) && !gizmos_overlay_contains_mouse && !m_gizmos.is_dragging()
 | 
			
		||||
              && !is_layers_editing_enabled() && (m_gizmos.get_current_type() != Gizmos::SlaSupports || !m_gizmos.mouse_event(SLAGizmoEventType::LeftUp, Vec2d(pos(0), pos(1)), evt.ShiftDown())))
 | 
			
		||||
        {
 | 
			
		||||
            int id = m_selection.get_object_idx();
 | 
			
		||||
            // SLA gizmo cannot be deselected by clicking in canvas area to avoid inadvertent unselection and losing manual changes
 | 
			
		||||
            // that's why the mouse_event function was called so that the gizmo can refuse the deselection in manual editing mode
 | 
			
		||||
 | 
			
		||||
            if ((id != -1) && (m_model != nullptr)) {
 | 
			
		||||
                m_gizmos.clicked_on_object(Vec2d(pos(0), pos(1)));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else if (evt.LeftUp() && !m_mouse.dragging && (m_hover_volume_id == -1) && !gizmos_overlay_contains_mouse && !m_gizmos.is_dragging() && !is_layers_editing_enabled())
 | 
			
		||||
        {
 | 
			
		||||
            // deselect and propagate event through callback
 | 
			
		||||
            if (!evt.ShiftDown() && m_picking_enabled && !m_toolbar_action_running && !m_mouse.ignore_up_event)
 | 
			
		||||
            {
 | 
			
		||||
| 
						 | 
				
			
			@ -5690,10 +5761,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
 | 
			
		|||
                do_rotate();
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            case Gizmos::SlaSupports:
 | 
			
		||||
                // End of mouse dragging, update the SLAPrint/SLAPrintObjects with the new support points.
 | 
			
		||||
                post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -6143,7 +6210,6 @@ bool GLCanvas3D::_init_toolbar()
 | 
			
		|||
    item.name = "add";
 | 
			
		||||
    item.tooltip = GUI::L_str("Add...") + " [" + GUI::shortkey_ctrl_prefix() + "I]";
 | 
			
		||||
    item.sprite_id = 0;
 | 
			
		||||
    item.is_toggable = false;
 | 
			
		||||
    item.action_event = EVT_GLTOOLBAR_ADD;
 | 
			
		||||
    if (!m_toolbar.add_item(item))
 | 
			
		||||
        return false;
 | 
			
		||||
| 
						 | 
				
			
			@ -6151,7 +6217,6 @@ bool GLCanvas3D::_init_toolbar()
 | 
			
		|||
    item.name = "delete";
 | 
			
		||||
    item.tooltip = GUI::L_str("Delete") + " [Del]";
 | 
			
		||||
    item.sprite_id = 1;
 | 
			
		||||
    item.is_toggable = false;
 | 
			
		||||
    item.action_event = EVT_GLTOOLBAR_DELETE;
 | 
			
		||||
    if (!m_toolbar.add_item(item))
 | 
			
		||||
        return false;
 | 
			
		||||
| 
						 | 
				
			
			@ -6159,7 +6224,6 @@ bool GLCanvas3D::_init_toolbar()
 | 
			
		|||
    item.name = "deleteall";
 | 
			
		||||
    item.tooltip = GUI::L_str("Delete all") + " [" + GUI::shortkey_ctrl_prefix() + "Del]";
 | 
			
		||||
    item.sprite_id = 2;
 | 
			
		||||
    item.is_toggable = false;
 | 
			
		||||
    item.action_event = EVT_GLTOOLBAR_DELETE_ALL;
 | 
			
		||||
    if (!m_toolbar.add_item(item))
 | 
			
		||||
        return false;
 | 
			
		||||
| 
						 | 
				
			
			@ -6167,7 +6231,6 @@ bool GLCanvas3D::_init_toolbar()
 | 
			
		|||
    item.name = "arrange";
 | 
			
		||||
    item.tooltip = GUI::L_str("Arrange [A]");
 | 
			
		||||
    item.sprite_id = 3;
 | 
			
		||||
    item.is_toggable = false;
 | 
			
		||||
    item.action_event = EVT_GLTOOLBAR_ARRANGE;
 | 
			
		||||
    if (!m_toolbar.add_item(item))
 | 
			
		||||
        return false;
 | 
			
		||||
| 
						 | 
				
			
			@ -6178,7 +6241,6 @@ bool GLCanvas3D::_init_toolbar()
 | 
			
		|||
    item.name = "more";
 | 
			
		||||
    item.tooltip = GUI::L_str("Add instance [+]");
 | 
			
		||||
    item.sprite_id = 4;
 | 
			
		||||
    item.is_toggable = false;
 | 
			
		||||
    item.action_event = EVT_GLTOOLBAR_MORE;
 | 
			
		||||
    if (!m_toolbar.add_item(item))
 | 
			
		||||
        return false;
 | 
			
		||||
| 
						 | 
				
			
			@ -6186,7 +6248,6 @@ bool GLCanvas3D::_init_toolbar()
 | 
			
		|||
    item.name = "fewer";
 | 
			
		||||
    item.tooltip = GUI::L_str("Remove instance [-]");
 | 
			
		||||
    item.sprite_id = 5;
 | 
			
		||||
    item.is_toggable = false;
 | 
			
		||||
    item.action_event = EVT_GLTOOLBAR_FEWER;
 | 
			
		||||
    if (!m_toolbar.add_item(item))
 | 
			
		||||
        return false;
 | 
			
		||||
| 
						 | 
				
			
			@ -6197,7 +6258,6 @@ bool GLCanvas3D::_init_toolbar()
 | 
			
		|||
    item.name = "splitobjects";
 | 
			
		||||
    item.tooltip = GUI::L_str("Split to objects");
 | 
			
		||||
    item.sprite_id = 6;
 | 
			
		||||
    item.is_toggable = false;
 | 
			
		||||
    item.action_event = EVT_GLTOOLBAR_SPLIT_OBJECTS;
 | 
			
		||||
    if (!m_toolbar.add_item(item))
 | 
			
		||||
        return false;
 | 
			
		||||
| 
						 | 
				
			
			@ -6205,7 +6265,6 @@ bool GLCanvas3D::_init_toolbar()
 | 
			
		|||
    item.name = "splitvolumes";
 | 
			
		||||
    item.tooltip = GUI::L_str("Split to parts");
 | 
			
		||||
    item.sprite_id = 8;
 | 
			
		||||
    item.is_toggable = false;
 | 
			
		||||
    item.action_event = EVT_GLTOOLBAR_SPLIT_VOLUMES;
 | 
			
		||||
    if (!m_toolbar.add_item(item))
 | 
			
		||||
        return false;
 | 
			
		||||
| 
						 | 
				
			
			@ -6586,7 +6645,9 @@ void GLCanvas3D::_render_objects() const
 | 
			
		|||
            m_layers_editing.render_volumes(*this, this->m_volumes);
 | 
			
		||||
        } else {
 | 
			
		||||
            // do not cull backfaces to show broken geometry, if any
 | 
			
		||||
            m_volumes.render_VBOs(GLVolumeCollection::Opaque, m_picking_enabled);
 | 
			
		||||
            m_volumes.render_VBOs(GLVolumeCollection::Opaque, m_picking_enabled, [this](const GLVolume& volume) {
 | 
			
		||||
                return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        m_volumes.render_VBOs(GLVolumeCollection::Transparent, false);
 | 
			
		||||
        m_shader.stop_using();
 | 
			
		||||
| 
						 | 
				
			
			@ -6602,7 +6663,9 @@ void GLCanvas3D::_render_objects() const
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        // do not cull backfaces to show broken geometry, if any
 | 
			
		||||
        m_volumes.render_legacy(GLVolumeCollection::Opaque, m_picking_enabled);
 | 
			
		||||
        m_volumes.render_legacy(GLVolumeCollection::Opaque, m_picking_enabled, [this](const GLVolume& volume) {
 | 
			
		||||
                return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0);
 | 
			
		||||
            });
 | 
			
		||||
        m_volumes.render_legacy(GLVolumeCollection::Transparent, false);
 | 
			
		||||
 | 
			
		||||
        if (m_use_clipping_planes)
 | 
			
		||||
| 
						 | 
				
			
			@ -6636,9 +6699,6 @@ void GLCanvas3D::_render_selection_center() const
 | 
			
		|||
 | 
			
		||||
void GLCanvas3D::_render_warning_texture() const
 | 
			
		||||
{
 | 
			
		||||
    if (!m_warning_texture_enabled)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    m_warning_texture.render(*this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -6683,7 +6743,7 @@ void GLCanvas3D::_render_volumes(bool fake_colors) const
 | 
			
		|||
            ::glColor4fv(vol->render_color);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!fake_colors || !vol->disabled)
 | 
			
		||||
        if ((!fake_colors || !vol->disabled) && (vol->composite_id.volume_id >= 0 || m_render_sla_auxiliaries))
 | 
			
		||||
            vol->render();
 | 
			
		||||
 | 
			
		||||
        ++volume_id;
 | 
			
		||||
| 
						 | 
				
			
			@ -6834,20 +6894,20 @@ void GLCanvas3D::_render_sla_slices() const
 | 
			
		|||
            {
 | 
			
		||||
                // calculate model bottom cap
 | 
			
		||||
                if (bottom_obj_triangles.empty() && (it_min_z->second.model_slices_idx < model_slices.size()))
 | 
			
		||||
                    bottom_obj_triangles = triangulate_expolygons_3df(model_slices[it_min_z->second.model_slices_idx], min_z, true);
 | 
			
		||||
                    bottom_obj_triangles = triangulate_expolygons_3d(model_slices[it_min_z->second.model_slices_idx], min_z, true);
 | 
			
		||||
                // calculate support bottom cap
 | 
			
		||||
                if (bottom_sup_triangles.empty() && (it_min_z->second.support_slices_idx < support_slices.size()))
 | 
			
		||||
                    bottom_sup_triangles = triangulate_expolygons_3df(support_slices[it_min_z->second.support_slices_idx], min_z, true);
 | 
			
		||||
                    bottom_sup_triangles = triangulate_expolygons_3d(support_slices[it_min_z->second.support_slices_idx], min_z, true);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (it_max_z != index.end())
 | 
			
		||||
            {
 | 
			
		||||
                // calculate model top cap
 | 
			
		||||
                if (top_obj_triangles.empty() && (it_max_z->second.model_slices_idx < model_slices.size()))
 | 
			
		||||
                    top_obj_triangles = triangulate_expolygons_3df(model_slices[it_max_z->second.model_slices_idx], max_z, false);
 | 
			
		||||
                    top_obj_triangles = triangulate_expolygons_3d(model_slices[it_max_z->second.model_slices_idx], max_z, false);
 | 
			
		||||
                // calculate support top cap
 | 
			
		||||
                if (top_sup_triangles.empty() && (it_max_z->second.support_slices_idx < support_slices.size()))
 | 
			
		||||
					top_sup_triangles = triangulate_expolygons_3df(support_slices[it_max_z->second.support_slices_idx], max_z, false);
 | 
			
		||||
					top_sup_triangles = triangulate_expolygons_3d(support_slices[it_max_z->second.support_slices_idx], max_z, false);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -6960,11 +7020,7 @@ void GLCanvas3D::_update_gizmos_data()
 | 
			
		|||
        m_gizmos.set_rotation(Vec3d::Zero());
 | 
			
		||||
        ModelObject* model_object = m_model->objects[m_selection.get_object_idx()];
 | 
			
		||||
        m_gizmos.set_flattening_data(model_object);
 | 
			
		||||
#if ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
        m_gizmos.set_sla_support_data(model_object, m_selection);
 | 
			
		||||
#else
 | 
			
		||||
        m_gizmos.set_model_object_ptr(model_object);
 | 
			
		||||
#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
    }
 | 
			
		||||
    else if (m_selection.is_single_volume() || m_selection.is_single_modifier())
 | 
			
		||||
    {
 | 
			
		||||
| 
						 | 
				
			
			@ -6972,22 +7028,14 @@ void GLCanvas3D::_update_gizmos_data()
 | 
			
		|||
        m_gizmos.set_scale(volume->get_volume_scaling_factor());
 | 
			
		||||
        m_gizmos.set_rotation(Vec3d::Zero());
 | 
			
		||||
        m_gizmos.set_flattening_data(nullptr);
 | 
			
		||||
#if ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
        m_gizmos.set_sla_support_data(nullptr, m_selection);
 | 
			
		||||
#else
 | 
			
		||||
        m_gizmos.set_model_object_ptr(nullptr);
 | 
			
		||||
#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        m_gizmos.set_scale(Vec3d::Ones());
 | 
			
		||||
        m_gizmos.set_rotation(Vec3d::Zero());
 | 
			
		||||
        m_gizmos.set_flattening_data(m_selection.is_from_single_object() ? m_model->objects[m_selection.get_object_idx()] : nullptr);
 | 
			
		||||
#if ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
        m_gizmos.set_sla_support_data(nullptr, m_selection);
 | 
			
		||||
#else
 | 
			
		||||
        m_gizmos.set_model_object_ptr(nullptr);
 | 
			
		||||
#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -8185,17 +8233,7 @@ void GLCanvas3D::_update_toolpath_volumes_outside_state()
 | 
			
		|||
void GLCanvas3D::_show_warning_texture_if_needed()
 | 
			
		||||
{
 | 
			
		||||
    _set_current();
 | 
			
		||||
 | 
			
		||||
    if (_is_any_volume_outside())
 | 
			
		||||
    {
 | 
			
		||||
        enable_warning_texture(true);
 | 
			
		||||
        _generate_warning_texture(L("Detected toolpath outside print volume"));
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        enable_warning_texture(false);
 | 
			
		||||
        _reset_warning_texture();
 | 
			
		||||
    }
 | 
			
		||||
    _set_warning_texture(WarningTexture::ToolpathOutside, _is_any_volume_outside());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<float> GLCanvas3D::_parse_colors(const std::vector<std::string>& colors)
 | 
			
		||||
| 
						 | 
				
			
			@ -8228,14 +8266,9 @@ void GLCanvas3D::_generate_legend_texture(const GCodePreviewData& preview_data,
 | 
			
		|||
    m_legend_texture.generate(preview_data, tool_colors, *this, m_dynamic_background_enabled && _is_any_volume_outside());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLCanvas3D::_generate_warning_texture(const std::string& msg)
 | 
			
		||||
void GLCanvas3D::_set_warning_texture(WarningTexture::Warning warning, bool state)
 | 
			
		||||
{
 | 
			
		||||
    m_warning_texture.generate(msg, *this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLCanvas3D::_reset_warning_texture()
 | 
			
		||||
{
 | 
			
		||||
    m_warning_texture.reset();
 | 
			
		||||
    m_warning_texture.activate(warning, state, *this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool GLCanvas3D::_is_any_volume_outside() const
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -132,6 +132,18 @@ wxDECLARE_EVENT(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, Event<bool>);
 | 
			
		|||
wxDECLARE_EVENT(EVT_GLCANVAS_UPDATE_GEOMETRY, Vec3dsEvent<2>);
 | 
			
		||||
wxDECLARE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent);
 | 
			
		||||
 | 
			
		||||
// this describes events being passed from GLCanvas3D to SlaSupport gizmo
 | 
			
		||||
enum class SLAGizmoEventType {
 | 
			
		||||
    LeftDown = 1,
 | 
			
		||||
    LeftUp,
 | 
			
		||||
    RightDown,
 | 
			
		||||
    Dragging,
 | 
			
		||||
    Delete,
 | 
			
		||||
    SelectAll,
 | 
			
		||||
    ShiftUp
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GLCanvas3D
 | 
			
		||||
{
 | 
			
		||||
    struct GCodePreviewVolumeIndex
 | 
			
		||||
| 
						 | 
				
			
			@ -788,12 +800,8 @@ private:
 | 
			
		|||
 | 
			
		||||
        void set_flattening_data(const ModelObject* model_object);
 | 
			
		||||
 | 
			
		||||
#if ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
        void set_sla_support_data(ModelObject* model_object, const GLCanvas3D::Selection& selection);
 | 
			
		||||
#else
 | 
			
		||||
        void set_model_object_ptr(ModelObject* model_object);
 | 
			
		||||
#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
        void clicked_on_object(const Vec2d& mouse_position);
 | 
			
		||||
        bool mouse_event(SLAGizmoEventType action, const Vec2d& mouse_position = Vec2d::Zero(), bool shift_down = false);
 | 
			
		||||
        void delete_current_grabber(bool delete_all = false);
 | 
			
		||||
 | 
			
		||||
        void render_current_gizmo(const Selection& selection) const;
 | 
			
		||||
| 
						 | 
				
			
			@ -835,18 +843,32 @@ private:
 | 
			
		|||
 | 
			
		||||
    class WarningTexture : public GUI::GLTexture
 | 
			
		||||
    {
 | 
			
		||||
    public:
 | 
			
		||||
        WarningTexture();
 | 
			
		||||
 | 
			
		||||
        enum Warning {
 | 
			
		||||
            ObjectOutside,
 | 
			
		||||
            ToolpathOutside,
 | 
			
		||||
            SomethingNotShown
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Sets a warning of the given type to be active/inactive. If several warnings are active simultaneously,
 | 
			
		||||
        // only the last one is shown (decided by the order in the enum above).
 | 
			
		||||
        void activate(WarningTexture::Warning warning, bool state, const GLCanvas3D& canvas);
 | 
			
		||||
        void render(const GLCanvas3D& canvas) const;
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
        static const unsigned char Background_Color[3];
 | 
			
		||||
        static const unsigned char Opacity;
 | 
			
		||||
 | 
			
		||||
        int m_original_width;
 | 
			
		||||
        int m_original_height;
 | 
			
		||||
 | 
			
		||||
    public:
 | 
			
		||||
        WarningTexture();
 | 
			
		||||
        // Information about which warnings are currently active.
 | 
			
		||||
        std::vector<Warning> m_warnings;
 | 
			
		||||
 | 
			
		||||
        bool generate(const std::string& msg, const GLCanvas3D& canvas);
 | 
			
		||||
 | 
			
		||||
        void render(const GLCanvas3D& canvas) const;
 | 
			
		||||
        // Generates the texture with given text.
 | 
			
		||||
        bool _generate(const std::string& msg, const GLCanvas3D& canvas);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    class LegendTexture : public GUI::GLTexture
 | 
			
		||||
| 
						 | 
				
			
			@ -923,6 +945,7 @@ private:
 | 
			
		|||
    bool m_multisample_allowed;
 | 
			
		||||
    bool m_regenerate_volumes;
 | 
			
		||||
    bool m_moving;
 | 
			
		||||
    bool m_render_sla_auxiliaries;
 | 
			
		||||
 | 
			
		||||
    std::string m_color_by;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -954,6 +977,9 @@ public:
 | 
			
		|||
    void reset_volumes();
 | 
			
		||||
    int check_volumes_outside_state() const;
 | 
			
		||||
 | 
			
		||||
    void toggle_sla_auxiliaries_visibility(bool visible);
 | 
			
		||||
    void toggle_model_objects_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1);
 | 
			
		||||
 | 
			
		||||
    void set_config(const DynamicPrintConfig* config);
 | 
			
		||||
    void set_process(BackgroundSlicingProcess* process);
 | 
			
		||||
    void set_model(Model* model);
 | 
			
		||||
| 
						 | 
				
			
			@ -991,7 +1017,6 @@ public:
 | 
			
		|||
    bool is_reload_delayed() const;
 | 
			
		||||
 | 
			
		||||
    void enable_layers_editing(bool enable);
 | 
			
		||||
    void enable_warning_texture(bool enable);
 | 
			
		||||
    void enable_legend_texture(bool enable);
 | 
			
		||||
    void enable_picking(bool enable);
 | 
			
		||||
    void enable_moving(bool enable);
 | 
			
		||||
| 
						 | 
				
			
			@ -1050,6 +1075,7 @@ public:
 | 
			
		|||
    void on_size(wxSizeEvent& evt);
 | 
			
		||||
    void on_idle(wxIdleEvent& evt);
 | 
			
		||||
    void on_char(wxKeyEvent& evt);
 | 
			
		||||
    void on_key_up(wxKeyEvent& evt);
 | 
			
		||||
    void on_mouse_wheel(wxMouseEvent& evt);
 | 
			
		||||
    void on_timer(wxTimerEvent& evt);
 | 
			
		||||
    void on_mouse(wxMouseEvent& evt);
 | 
			
		||||
| 
						 | 
				
			
			@ -1176,8 +1202,7 @@ private:
 | 
			
		|||
    void _generate_legend_texture(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
 | 
			
		||||
 | 
			
		||||
    // generates a warning texture containing the given message
 | 
			
		||||
    void _generate_warning_texture(const std::string& msg);
 | 
			
		||||
    void _reset_warning_texture();
 | 
			
		||||
    void _set_warning_texture(WarningTexture::Warning warning, bool state);
 | 
			
		||||
 | 
			
		||||
    bool _is_any_volume_outside() const;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,7 @@
 | 
			
		|||
#include "libslic3r/libslic3r.h"
 | 
			
		||||
#include "libslic3r/Geometry.hpp"
 | 
			
		||||
#include "libslic3r/Utils.hpp"
 | 
			
		||||
#include "libslic3r/SLA/SLASupportTree.hpp"
 | 
			
		||||
#include "libslic3r/SLA/SLACommon.hpp"
 | 
			
		||||
#include "libslic3r/SLAPrint.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cstdio>
 | 
			
		||||
| 
						 | 
				
			
			@ -1741,28 +1741,20 @@ Vec3d GLGizmoFlatten::get_flattening_normal() const
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent)
 | 
			
		||||
#if ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
    : GLGizmoBase(parent), m_starting_center(Vec3d::Zero()), m_quadric(nullptr)
 | 
			
		||||
#else
 | 
			
		||||
    : GLGizmoBase(parent), m_starting_center(Vec3d::Zero())
 | 
			
		||||
#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
{
 | 
			
		||||
#if ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
    m_quadric = ::gluNewQuadric();
 | 
			
		||||
    if (m_quadric != nullptr)
 | 
			
		||||
        // using GLU_FILL does not work when the instance's transformation
 | 
			
		||||
        // contains mirroring (normals are reverted)
 | 
			
		||||
        ::gluQuadricDrawStyle(m_quadric, GLU_FILL);
 | 
			
		||||
#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#if ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
GLGizmoSlaSupports::~GLGizmoSlaSupports()
 | 
			
		||||
{
 | 
			
		||||
    if (m_quadric != nullptr)
 | 
			
		||||
        ::gluDeleteQuadric(m_quadric);
 | 
			
		||||
}
 | 
			
		||||
#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
 | 
			
		||||
bool GLGizmoSlaSupports::on_init()
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -1782,7 +1774,6 @@ bool GLGizmoSlaSupports::on_init()
 | 
			
		|||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#if ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const GLCanvas3D::Selection& selection)
 | 
			
		||||
{
 | 
			
		||||
    m_starting_center = Vec3d::Zero();
 | 
			
		||||
| 
						 | 
				
			
			@ -1791,208 +1782,162 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const G
 | 
			
		|||
    if (selection.is_empty())
 | 
			
		||||
        m_old_instance_id = -1;
 | 
			
		||||
 | 
			
		||||
    if ((model_object != nullptr) && selection.is_from_single_instance())
 | 
			
		||||
    m_active_instance = selection.get_instance_idx();
 | 
			
		||||
 | 
			
		||||
    if (model_object && selection.is_from_single_instance())
 | 
			
		||||
    {
 | 
			
		||||
        if (is_mesh_update_necessary())
 | 
			
		||||
            update_mesh();
 | 
			
		||||
 | 
			
		||||
        // If there are no points, let's ask the backend if it calculated some.
 | 
			
		||||
        if (model_object->sla_support_points.empty() && m_parent.sla_print()->is_step_done(slaposSupportPoints)) {
 | 
			
		||||
            for (const SLAPrintObject* po : m_parent.sla_print()->objects()) {
 | 
			
		||||
                if (po->model_object()->id() == model_object->id()) {
 | 
			
		||||
                    const Eigen::MatrixXd& points = po->get_support_points();
 | 
			
		||||
                    for (unsigned int i=0; i<points.rows();++i)
 | 
			
		||||
                        model_object->sla_support_points.push_back(Vec3f(po->trafo().inverse().cast<float>() * Vec3f(points(i,0), points(i,1), points(i,2))));
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        if (m_editing_mode_cache.empty() && m_parent.sla_print()->is_step_done(slaposSupportPoints))
 | 
			
		||||
            get_data_from_backend();
 | 
			
		||||
 | 
			
		||||
        if (m_model_object != m_old_model_object)
 | 
			
		||||
            m_editing_mode = false;
 | 
			
		||||
        if (m_state == On) {
 | 
			
		||||
            m_parent.toggle_model_objects_visibility(false);
 | 
			
		||||
            m_parent.toggle_model_objects_visibility(true, m_model_object, m_active_instance);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
#else
 | 
			
		||||
void GLGizmoSlaSupports::set_model_object_ptr(ModelObject* model_object)
 | 
			
		||||
{
 | 
			
		||||
    if (model_object != nullptr) {
 | 
			
		||||
        m_starting_center = Vec3d::Zero();
 | 
			
		||||
        m_model_object = model_object;
 | 
			
		||||
 | 
			
		||||
        int selected_instance = m_parent.get_selection().get_instance_idx();
 | 
			
		||||
        assert(selected_instance < (int)model_object->instances.size());
 | 
			
		||||
 | 
			
		||||
        m_instance_matrix = model_object->instances[selected_instance]->get_matrix();
 | 
			
		||||
        if (is_mesh_update_necessary())
 | 
			
		||||
            update_mesh();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
 | 
			
		||||
void GLGizmoSlaSupports::on_render(const GLCanvas3D::Selection& selection) const
 | 
			
		||||
{
 | 
			
		||||
    ::glEnable(GL_BLEND);
 | 
			
		||||
    ::glEnable(GL_DEPTH_TEST);
 | 
			
		||||
 | 
			
		||||
#if !ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
    // the dragged_offset is a vector measuring where was the object moved
 | 
			
		||||
    // with the gizmo being on. This is reset in set_model_object_ptr and
 | 
			
		||||
    // does not work correctly when there are multiple copies.
 | 
			
		||||
    
 | 
			
		||||
    if (m_starting_center == Vec3d::Zero())
 | 
			
		||||
        m_starting_center = selection.get_bounding_box().center();
 | 
			
		||||
    Vec3d dragged_offset = selection.get_bounding_box().center() - m_starting_center;
 | 
			
		||||
#endif // !ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    for (auto& g : m_grabbers) {
 | 
			
		||||
        g.color[0] = 1.f;
 | 
			
		||||
        g.color[1] = 0.f;
 | 
			
		||||
        g.color[2] = 0.f;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
#if ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
    render_grabbers(selection, false);
 | 
			
		||||
#else
 | 
			
		||||
    //::glTranslatef((GLfloat)dragged_offset(0), (GLfloat)dragged_offset(1), (GLfloat)dragged_offset(2));
 | 
			
		||||
    render_grabbers(false);
 | 
			
		||||
#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
    render_points(selection, false);
 | 
			
		||||
    render_selection_rectangle();
 | 
			
		||||
 | 
			
		||||
#if !ENABLE_IMGUI
 | 
			
		||||
    render_tooltip_texture();
 | 
			
		||||
#endif // not ENABLE_IMGUI
 | 
			
		||||
 | 
			
		||||
    ::glDisable(GL_BLEND);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLGizmoSlaSupports::render_selection_rectangle() const
 | 
			
		||||
{
 | 
			
		||||
    if (!m_selection_rectangle_active)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    ::glLineWidth(1.5f);
 | 
			
		||||
    float render_color[3] = {1.f, 0.f, 0.f};
 | 
			
		||||
    ::glColor3fv(render_color);
 | 
			
		||||
 | 
			
		||||
    ::glPushAttrib(GL_TRANSFORM_BIT);   // remember current MatrixMode
 | 
			
		||||
 | 
			
		||||
    ::glMatrixMode(GL_MODELVIEW);       // cache modelview matrix and set to identity
 | 
			
		||||
    ::glPushMatrix();
 | 
			
		||||
    ::glLoadIdentity();
 | 
			
		||||
 | 
			
		||||
    ::glMatrixMode(GL_PROJECTION);      // cache projection matrix and set to identity
 | 
			
		||||
    ::glPushMatrix();
 | 
			
		||||
    ::glLoadIdentity();
 | 
			
		||||
 | 
			
		||||
    ::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):
 | 
			
		||||
    ::glPushAttrib(GL_ENABLE_BIT);
 | 
			
		||||
    ::glLineStipple(4, 0xAAAA);
 | 
			
		||||
    ::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);
 | 
			
		||||
    ::glEnd();
 | 
			
		||||
    ::glPopAttrib();
 | 
			
		||||
 | 
			
		||||
    ::glPopMatrix();                // restore former projection matrix
 | 
			
		||||
    ::glMatrixMode(GL_MODELVIEW);
 | 
			
		||||
    ::glPopMatrix();                // restore former modelview matrix
 | 
			
		||||
    ::glPopAttrib();                // restore former MatrixMode
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLGizmoSlaSupports::on_render_for_picking(const GLCanvas3D::Selection& selection) const
 | 
			
		||||
{
 | 
			
		||||
    ::glEnable(GL_DEPTH_TEST);
 | 
			
		||||
    for (unsigned int i=0; i<m_grabbers.size(); ++i) {
 | 
			
		||||
        m_grabbers[i].color[0] = 1.0f;
 | 
			
		||||
        m_grabbers[i].color[1] = 1.0f;
 | 
			
		||||
        m_grabbers[i].color[2] = picking_color_component(i);
 | 
			
		||||
    }
 | 
			
		||||
#if ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
    render_grabbers(selection, true);
 | 
			
		||||
#else
 | 
			
		||||
    render_grabbers(true);
 | 
			
		||||
#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
 | 
			
		||||
    render_points(selection, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#if ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
void GLGizmoSlaSupports::render_grabbers(const GLCanvas3D::Selection& selection, bool picking) const
 | 
			
		||||
void GLGizmoSlaSupports::render_points(const GLCanvas3D::Selection& selection, bool picking) const
 | 
			
		||||
{
 | 
			
		||||
    if (m_quadric == nullptr)
 | 
			
		||||
    if (m_quadric == nullptr || !selection.is_from_single_instance())
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    if (!selection.is_from_single_instance())
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin());
 | 
			
		||||
    double z_shift = v->get_sla_shift_z();
 | 
			
		||||
 | 
			
		||||
    ::glPushMatrix();
 | 
			
		||||
    ::glTranslated(0.0, 0.0, z_shift);
 | 
			
		||||
 | 
			
		||||
    const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix();
 | 
			
		||||
    ::glMultMatrixd(m.data());
 | 
			
		||||
 | 
			
		||||
    if (!picking)
 | 
			
		||||
        ::glEnable(GL_LIGHTING);
 | 
			
		||||
 | 
			
		||||
    const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin());
 | 
			
		||||
    double z_shift = vol->get_sla_shift_z();
 | 
			
		||||
    const Transform3d& instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse();
 | 
			
		||||
    const Transform3d& instance_matrix = vol->get_instance_transformation().get_matrix();
 | 
			
		||||
 | 
			
		||||
    ::glPushMatrix();
 | 
			
		||||
    ::glTranslated(0.0, 0.0, z_shift);
 | 
			
		||||
    ::glMultMatrixd(instance_matrix.data());
 | 
			
		||||
 | 
			
		||||
    float render_color[3];
 | 
			
		||||
    for (int i = 0; i < (int)m_grabbers.size(); ++i)
 | 
			
		||||
    for (int i = 0; i < (int)m_editing_mode_cache.size(); ++i)
 | 
			
		||||
    {
 | 
			
		||||
        // first precalculate the grabber position in world coordinates, so that the grabber
 | 
			
		||||
        // is not scaled with the object (as it would be if rendered with current gl matrix).
 | 
			
		||||
        Eigen::Matrix<GLfloat, 4, 4> glmatrix;
 | 
			
		||||
        glGetFloatv (GL_MODELVIEW_MATRIX, glmatrix.data());
 | 
			
		||||
        Eigen::Matrix<float, 4, 1> grabber_pos;
 | 
			
		||||
        for (int j=0; j<3; ++j)
 | 
			
		||||
            grabber_pos(j) = m_grabbers[i].center(j);
 | 
			
		||||
        grabber_pos[3] = 1.f;
 | 
			
		||||
        Eigen::Matrix<float, 4, 1> grabber_world_position = glmatrix * grabber_pos;
 | 
			
		||||
        const sla::SupportPoint& support_point = m_editing_mode_cache[i].first;
 | 
			
		||||
        const bool& point_selected = m_editing_mode_cache[i].second;
 | 
			
		||||
 | 
			
		||||
        if (!picking && (m_hover_id == i))
 | 
			
		||||
        {
 | 
			
		||||
            render_color[0] = 1.0f - m_grabbers[i].color[0];
 | 
			
		||||
            render_color[1] = 1.0f - m_grabbers[i].color[1];
 | 
			
		||||
            render_color[2] = 1.0f - m_grabbers[i].color[2];
 | 
			
		||||
        // First decide about the color of the point.
 | 
			
		||||
        if (picking) {
 | 
			
		||||
            render_color[0] = 1.0f;
 | 
			
		||||
            render_color[1] = 1.0f;
 | 
			
		||||
            render_color[2] = picking_color_component(i);
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            if ((m_hover_id == i && m_editing_mode)) { // ignore hover state unless editing mode is active
 | 
			
		||||
                render_color[0] = 0.f;
 | 
			
		||||
                render_color[1] = 1.0f;
 | 
			
		||||
                render_color[2] = 1.0f;
 | 
			
		||||
            }
 | 
			
		||||
            else { // neigher hover nor picking
 | 
			
		||||
                bool supports_new_island = m_lock_unique_islands && m_editing_mode_cache[i].first.is_new_island;
 | 
			
		||||
                if (m_editing_mode) {
 | 
			
		||||
                    render_color[0] = point_selected ? 1.0f : (supports_new_island ? 0.3f : 0.7f);
 | 
			
		||||
                    render_color[1] = point_selected ? 0.3f : (supports_new_island ? 0.3f : 0.7f);
 | 
			
		||||
                    render_color[2] = point_selected ? 0.3f : (supports_new_island ? 1.0f : 0.7f);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                    for (unsigned char i=0; i<3; ++i) render_color[i] = 0.5f;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
            ::memcpy((void*)render_color, (const void*)m_grabbers[i].color, 3 * sizeof(float));
 | 
			
		||||
 | 
			
		||||
        ::glColor3fv(render_color);
 | 
			
		||||
        float render_color_emissive[4] = { 0.5f * render_color[0], 0.5f * render_color[1], 0.5f * render_color[2], 1.f};
 | 
			
		||||
        ::glMaterialfv(GL_FRONT, GL_EMISSION, render_color_emissive);
 | 
			
		||||
 | 
			
		||||
        // Now render the sphere. Inverse matrix of the instance scaling is applied so that the
 | 
			
		||||
        // sphere does not scale with the object.
 | 
			
		||||
        ::glPushMatrix();
 | 
			
		||||
        ::glLoadIdentity();
 | 
			
		||||
        ::glTranslated(grabber_world_position(0), grabber_world_position(1), grabber_world_position(2) + z_shift);
 | 
			
		||||
        const float diameter = 0.8f;
 | 
			
		||||
        ::gluSphere(m_quadric, diameter/2.f, 64, 36);
 | 
			
		||||
        ::glTranslated(support_point.pos(0), support_point.pos(1), support_point.pos(2));
 | 
			
		||||
        ::glMultMatrixd(instance_scaling_matrix_inverse.data());
 | 
			
		||||
        ::gluSphere(m_quadric, m_editing_mode_cache[i].first.head_front_radius * RenderPointScale, 64, 36);
 | 
			
		||||
        ::glPopMatrix();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
        // Reset emissive component to zero (the default value)
 | 
			
		||||
        float render_color_emissive[4] = { 0.f, 0.f, 0.f, 1.f };
 | 
			
		||||
        ::glMaterialfv(GL_FRONT, GL_EMISSION, render_color_emissive);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!picking)
 | 
			
		||||
        ::glDisable(GL_LIGHTING);
 | 
			
		||||
 | 
			
		||||
    ::glPopMatrix();
 | 
			
		||||
}
 | 
			
		||||
#else
 | 
			
		||||
void GLGizmoSlaSupports::render_grabbers(bool picking) const
 | 
			
		||||
{
 | 
			
		||||
	if (m_parent.get_selection().is_empty())
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
    float z_shift = m_parent.get_selection().get_volume(0)->get_sla_shift_z();
 | 
			
		||||
    ::glTranslatef((GLfloat)0, (GLfloat)0, (GLfloat)z_shift);
 | 
			
		||||
 | 
			
		||||
    int selected_instance = m_parent.get_selection().get_instance_idx();
 | 
			
		||||
    assert(selected_instance < (int)m_model_object->instances.size());
 | 
			
		||||
 | 
			
		||||
    float render_color_inactive[3] = { 0.5f, 0.5f, 0.5f };
 | 
			
		||||
 | 
			
		||||
    for (const ModelInstance* inst : m_model_object->instances) {
 | 
			
		||||
		bool active = inst == m_model_object->instances[selected_instance];
 | 
			
		||||
        if (picking && ! active)
 | 
			
		||||
            continue;
 | 
			
		||||
        for (int i = 0; i < (int)m_grabbers.size(); ++i)
 | 
			
		||||
        {
 | 
			
		||||
            if (!m_grabbers[i].enabled)
 | 
			
		||||
                continue;
 | 
			
		||||
 | 
			
		||||
            float render_color[3];
 | 
			
		||||
            if (! picking && active && m_hover_id == i) {
 | 
			
		||||
                render_color[0] = 1.0f - m_grabbers[i].color[0];
 | 
			
		||||
                render_color[1] = 1.0f - m_grabbers[i].color[1];
 | 
			
		||||
                render_color[2] = 1.0f - m_grabbers[i].color[2];
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
                ::memcpy((void*)render_color, active ? (const void*)m_grabbers[i].color : (const void*)render_color_inactive, 3 * sizeof(float));
 | 
			
		||||
            if (!picking)
 | 
			
		||||
                ::glEnable(GL_LIGHTING);
 | 
			
		||||
            ::glColor3f((GLfloat)render_color[0], (GLfloat)render_color[1], (GLfloat)render_color[2]);
 | 
			
		||||
            ::glPushMatrix();
 | 
			
		||||
            Vec3d center = inst->get_matrix() * m_grabbers[i].center;
 | 
			
		||||
            ::glTranslatef((GLfloat)center(0), (GLfloat)center(1), (GLfloat)center(2));
 | 
			
		||||
            GLUquadricObj *quadric;
 | 
			
		||||
            quadric = ::gluNewQuadric();
 | 
			
		||||
            ::gluQuadricDrawStyle(quadric, GLU_FILL );
 | 
			
		||||
            ::gluSphere( quadric , 0.4, 64 , 32 );
 | 
			
		||||
            ::gluDeleteQuadric(quadric);
 | 
			
		||||
            ::glPopMatrix();
 | 
			
		||||
            if (!picking)
 | 
			
		||||
                ::glDisable(GL_LIGHTING);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ::glTranslatef((GLfloat)0, (GLfloat)0, (GLfloat)-z_shift);
 | 
			
		||||
}
 | 
			
		||||
#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
 | 
			
		||||
bool GLGizmoSlaSupports::is_mesh_update_necessary() const
 | 
			
		||||
{
 | 
			
		||||
#if ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
    return (m_state == On) && (m_model_object != nullptr) && (m_model_object != m_old_model_object) && !m_model_object->instances.empty();
 | 
			
		||||
#else
 | 
			
		||||
    return m_state == On && m_model_object && !m_model_object->instances.empty() && !m_instance_matrix.isApprox(m_source_data.matrix);
 | 
			
		||||
#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
 | 
			
		||||
    //if (m_state != On || !m_model_object || m_model_object->instances.empty() || ! m_instance_matrix.isApprox(m_source_data.matrix))
 | 
			
		||||
    //    return false;
 | 
			
		||||
| 
						 | 
				
			
			@ -2027,27 +1972,14 @@ void GLGizmoSlaSupports::update_mesh()
 | 
			
		|||
    m_AABB = igl::AABB<Eigen::MatrixXf,3>();
 | 
			
		||||
    m_AABB.init(m_V, m_F);
 | 
			
		||||
 | 
			
		||||
#if !ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
    m_source_data.matrix = m_instance_matrix;
 | 
			
		||||
#endif // !ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
 | 
			
		||||
    // we'll now reload Grabbers (selection might have changed):
 | 
			
		||||
    m_grabbers.clear();
 | 
			
		||||
 | 
			
		||||
    for (const Vec3f& point : m_model_object->sla_support_points) {
 | 
			
		||||
        m_grabbers.push_back(Grabber());
 | 
			
		||||
        m_grabbers.back().center = point.cast<double>();
 | 
			
		||||
    }
 | 
			
		||||
    // we'll now reload support points (selection might have changed):
 | 
			
		||||
    editing_mode_reload_cache();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Vec3f GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos)
 | 
			
		||||
{
 | 
			
		||||
    // if the gizmo doesn't have the V, F structures for igl, calculate them first:
 | 
			
		||||
#if ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
    if (m_V.size() == 0)
 | 
			
		||||
#else
 | 
			
		||||
    if (m_V.size() == 0 || is_mesh_update_necessary())
 | 
			
		||||
#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
        update_mesh();
 | 
			
		||||
 | 
			
		||||
    Eigen::Matrix<GLint, 4, 1, Eigen::DontAlign> viewport;
 | 
			
		||||
| 
						 | 
				
			
			@ -2064,20 +1996,15 @@ Vec3f GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos)
 | 
			
		|||
 | 
			
		||||
    igl::Hit hit;
 | 
			
		||||
 | 
			
		||||
#if ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
    const GLCanvas3D::Selection& selection = m_parent.get_selection();
 | 
			
		||||
    const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
 | 
			
		||||
    double z_offset = volume->get_sla_shift_z();
 | 
			
		||||
#else
 | 
			
		||||
    double z_offset = m_parent.get_selection().get_volume(0)->get_sla_shift_z();
 | 
			
		||||
#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
 | 
			
		||||
    point1(2) -= z_offset;
 | 
			
		||||
	point2(2) -= z_offset;
 | 
			
		||||
#if ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
 | 
			
		||||
    Transform3d inv = volume->get_instance_transformation().get_matrix().inverse();
 | 
			
		||||
#else
 | 
			
		||||
    Transform3d inv = m_instance_matrix.inverse();
 | 
			
		||||
#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
 | 
			
		||||
    point1 = inv * point1;
 | 
			
		||||
    point2 = inv * point2;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2089,68 +2016,174 @@ Vec3f GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos)
 | 
			
		|||
    return bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLGizmoSlaSupports::clicked_on_object(const Vec2d& mouse_position)
 | 
			
		||||
// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event.
 | 
			
		||||
// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is
 | 
			
		||||
// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo
 | 
			
		||||
// concludes that the event was not intended for it, it should return false.
 | 
			
		||||
bool GLGizmoSlaSupports::mouse_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down)
 | 
			
		||||
{
 | 
			
		||||
#if ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
    int instance_id = m_parent.get_selection().get_instance_idx();
 | 
			
		||||
    if (m_old_instance_id != instance_id)
 | 
			
		||||
    {
 | 
			
		||||
        bool something_selected = (m_old_instance_id != -1);
 | 
			
		||||
        m_old_instance_id = instance_id;
 | 
			
		||||
        if (something_selected)
 | 
			
		||||
            return;
 | 
			
		||||
    if (!m_editing_mode)
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
    // left down - show the selection rectangle:
 | 
			
		||||
    if (action == SLAGizmoEventType::LeftDown && shift_down) {
 | 
			
		||||
        if (m_hover_id == -1) {
 | 
			
		||||
            m_selection_rectangle_active = true;
 | 
			
		||||
            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();
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
            select_point(m_hover_id);
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    if (instance_id == -1)
 | 
			
		||||
        return;
 | 
			
		||||
#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
 | 
			
		||||
    Vec3f new_pos;
 | 
			
		||||
    try {
 | 
			
		||||
        new_pos = unproject_on_mesh(mouse_position); // this can throw - we don't want to create a new grabber in that case
 | 
			
		||||
    // dragging the selection rectangle:
 | 
			
		||||
    if (action == SLAGizmoEventType::Dragging && m_selection_rectangle_active) {
 | 
			
		||||
        m_selection_rectangle_end_corner = mouse_position;
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    catch (...) { return; }
 | 
			
		||||
 | 
			
		||||
    m_grabbers.push_back(Grabber());
 | 
			
		||||
    m_grabbers.back().center = new_pos.cast<double>();
 | 
			
		||||
    m_model_object->sla_support_points.push_back(new_pos);
 | 
			
		||||
    // mouse up without selection rectangle - place point on the mesh:
 | 
			
		||||
    if (action == SLAGizmoEventType::LeftUp && !m_selection_rectangle_active && !shift_down) {
 | 
			
		||||
        if (m_ignore_up_event) {
 | 
			
		||||
            m_ignore_up_event = false;
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    // This should trigger the support generation
 | 
			
		||||
    // wxGetApp().plater()->reslice();
 | 
			
		||||
        int instance_id = m_parent.get_selection().get_instance_idx();
 | 
			
		||||
        if (m_old_instance_id != instance_id)
 | 
			
		||||
        {
 | 
			
		||||
            bool something_selected = (m_old_instance_id != -1);
 | 
			
		||||
            m_old_instance_id = instance_id;
 | 
			
		||||
            if (something_selected)
 | 
			
		||||
                return false;
 | 
			
		||||
        }
 | 
			
		||||
        if (instance_id == -1)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
    m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
 | 
			
		||||
        // Regardless of whether the user clicked the object or not, we will unselect all points:
 | 
			
		||||
        select_point(NoPoints);
 | 
			
		||||
 | 
			
		||||
        Vec3f new_pos;
 | 
			
		||||
        try {
 | 
			
		||||
            new_pos = unproject_on_mesh(mouse_position); // this can throw - we don't want to create a new point in that case
 | 
			
		||||
            m_editing_mode_cache.emplace_back(std::make_pair(sla::SupportPoint(new_pos, m_new_point_head_diameter/2.f, false), true));
 | 
			
		||||
            m_unsaved_changes = true;
 | 
			
		||||
        }
 | 
			
		||||
        catch (...) {      // not clicked on object
 | 
			
		||||
            return true;   // prevents deselection of the gizmo by GLCanvas3D
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // left up with selection rectangle - select points inside the rectangle:
 | 
			
		||||
    if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp)
 | 
			
		||||
      && m_selection_rectangle_active) {
 | 
			
		||||
        if (action == SLAGizmoEventType::ShiftUp)
 | 
			
		||||
            m_ignore_up_event = true;
 | 
			
		||||
        const Transform3d& instance_matrix = m_model_object->instances[m_active_instance]->get_transformation().get_matrix();
 | 
			
		||||
        GLint viewport[4];
 | 
			
		||||
        ::glGetIntegerv(GL_VIEWPORT, viewport);
 | 
			
		||||
        GLdouble modelview_matrix[16];
 | 
			
		||||
        ::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix);
 | 
			
		||||
        GLdouble projection_matrix[16];
 | 
			
		||||
        ::glGetDoublev(GL_PROJECTION_MATRIX, projection_matrix);
 | 
			
		||||
 | 
			
		||||
        const GLCanvas3D::Selection& selection = m_parent.get_selection();
 | 
			
		||||
        const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
 | 
			
		||||
        double z_offset = volume->get_sla_shift_z();
 | 
			
		||||
 | 
			
		||||
        // 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 = volume->get_instance_transformation().get_matrix(true);
 | 
			
		||||
        // we'll recover current look direction from the modelview matrix (in world coords)...
 | 
			
		||||
        Vec3f direction_to_camera(modelview_matrix[2], modelview_matrix[6], modelview_matrix[10]);
 | 
			
		||||
        // ...and transform it to model coords.
 | 
			
		||||
        direction_to_camera = instance_matrix_no_translation.inverse().cast<float>() * direction_to_camera.eval();
 | 
			
		||||
 | 
			
		||||
        // Iterate over all points, check if they're in the rectangle and if so, check that they are not obscured by the mesh:
 | 
			
		||||
        for (std::pair<sla::SupportPoint, bool>& point_and_selection : m_editing_mode_cache) {
 | 
			
		||||
            const sla::SupportPoint& support_point = point_and_selection.first;
 | 
			
		||||
            Vec3f pos = instance_matrix.cast<float>() * support_point.pos;
 | 
			
		||||
            pos(2) += z_offset;
 | 
			
		||||
              GLdouble out_x, out_y, out_z;
 | 
			
		||||
             ::gluProject((GLdouble)pos(0), (GLdouble)pos(1), (GLdouble)pos(2), modelview_matrix, projection_matrix, viewport, &out_x, &out_y, &out_z);
 | 
			
		||||
             out_y = m_canvas_height - out_y;
 | 
			
		||||
 | 
			
		||||
            if (rectangle.contains(Point(out_x, out_y))) {
 | 
			
		||||
                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;
 | 
			
		||||
                if (m_AABB.intersect_ray(m_V, m_F, support_point.pos, direction_to_camera, hits))
 | 
			
		||||
                    // FIXME: the intersection could in theory be behind the camera, but as of now we only have camera direction.
 | 
			
		||||
                    // Also, the threshold is in mesh coordinates, not in actual dimensions.
 | 
			
		||||
                    if (hits.size() > 1 || hits.front().t > 0.001f)
 | 
			
		||||
                        is_obscured = true;
 | 
			
		||||
 | 
			
		||||
                if (!is_obscured)
 | 
			
		||||
                    point_and_selection.second = true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        m_selection_rectangle_active = false;
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (action == SLAGizmoEventType::Delete) {
 | 
			
		||||
        // delete key pressed
 | 
			
		||||
        delete_selected_points();
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (action == SLAGizmoEventType::RightDown) {
 | 
			
		||||
        if (m_hover_id != -1) {
 | 
			
		||||
            select_point(NoPoints);
 | 
			
		||||
            select_point(m_hover_id);
 | 
			
		||||
            delete_selected_points();
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (action == SLAGizmoEventType::SelectAll) {
 | 
			
		||||
        select_point(AllPoints);
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLGizmoSlaSupports::delete_current_grabber(bool delete_all)
 | 
			
		||||
void GLGizmoSlaSupports::delete_selected_points()
 | 
			
		||||
{
 | 
			
		||||
    if (delete_all) {
 | 
			
		||||
        m_grabbers.clear();
 | 
			
		||||
        m_model_object->sla_support_points.clear();
 | 
			
		||||
 | 
			
		||||
        // This should trigger the support generation
 | 
			
		||||
        // wxGetApp().plater()->reslice();
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
        if (m_hover_id != -1) {
 | 
			
		||||
            m_grabbers.erase(m_grabbers.begin() + m_hover_id);
 | 
			
		||||
            m_model_object->sla_support_points.erase(m_model_object->sla_support_points.begin() + m_hover_id);
 | 
			
		||||
            m_hover_id = -1;
 | 
			
		||||
    if (!m_editing_mode)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    for (unsigned int idx=0; idx<m_editing_mode_cache.size(); ++idx) {
 | 
			
		||||
        if (m_editing_mode_cache[idx].second && (!m_editing_mode_cache[idx].first.is_new_island || !m_lock_unique_islands)) {
 | 
			
		||||
            m_editing_mode_cache.erase(m_editing_mode_cache.begin() + (idx--));
 | 
			
		||||
            m_unsaved_changes = true;
 | 
			
		||||
        }
 | 
			
		||||
            // This should trigger the support generation
 | 
			
		||||
            // wxGetApp().plater()->reslice();
 | 
			
		||||
        }
 | 
			
		||||
    m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLGizmoSlaSupports::on_update(const UpdateData& data, const GLCanvas3D::Selection& selection)
 | 
			
		||||
{
 | 
			
		||||
    if (m_hover_id != -1 && data.mouse_pos) {
 | 
			
		||||
    if (m_editing_mode && m_hover_id != -1 && data.mouse_pos && (!m_editing_mode_cache[m_hover_id].first.is_new_island || !m_lock_unique_islands)) {
 | 
			
		||||
        Vec3f new_pos;
 | 
			
		||||
        try {
 | 
			
		||||
            new_pos = unproject_on_mesh(Vec2d((*data.mouse_pos)(0), (*data.mouse_pos)(1)));
 | 
			
		||||
        }
 | 
			
		||||
        catch (...) { return; }
 | 
			
		||||
        m_grabbers[m_hover_id].center = new_pos.cast<double>();
 | 
			
		||||
        m_model_object->sla_support_points[m_hover_id] = new_pos;
 | 
			
		||||
        m_editing_mode_cache[m_hover_id].first.pos = new_pos;
 | 
			
		||||
        m_editing_mode_cache[m_hover_id].first.is_new_island = false;
 | 
			
		||||
        m_unsaved_changes = true;
 | 
			
		||||
        // Do not update immediately, wait until the mouse is released.
 | 
			
		||||
        // m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -2196,37 +2229,165 @@ void GLGizmoSlaSupports::on_render_input_window(float x, float y, const GLCanvas
 | 
			
		|||
RENDER_AGAIN:
 | 
			
		||||
    m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
 | 
			
		||||
    m_imgui->set_next_window_bg_alpha(0.5f);
 | 
			
		||||
    m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
 | 
			
		||||
    m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove |/* ImGuiWindowFlags_NoResize | */ImGuiWindowFlags_NoCollapse);
 | 
			
		||||
 | 
			
		||||
    ImGui::PushItemWidth(100.0f);
 | 
			
		||||
    m_imgui->text(_(L("Left mouse click - add point")));
 | 
			
		||||
    m_imgui->text(_(L("Right mouse click - remove point")));
 | 
			
		||||
    m_imgui->text(" ");
 | 
			
		||||
 | 
			
		||||
    bool generate = m_imgui->button(_(L("Generate points automatically")));
 | 
			
		||||
    bool remove_all_clicked = m_imgui->button(_(L("Remove all points")) + (m_model_object == nullptr ? "" : " (" + std::to_string(m_model_object->sla_support_points.size())+")"));
 | 
			
		||||
    bool force_refresh = false;
 | 
			
		||||
    bool remove_selected = false;
 | 
			
		||||
    bool old_editing_state = m_editing_mode;
 | 
			
		||||
 | 
			
		||||
    if (m_editing_mode) {
 | 
			
		||||
        m_imgui->text(_(L("Left mouse click - add point")));
 | 
			
		||||
        m_imgui->text(_(L("Right mouse click - remove point")));
 | 
			
		||||
        m_imgui->text(_(L("Shift + Left (+ drag) - select point(s)")));
 | 
			
		||||
        m_imgui->text(" ");  // vertical gap
 | 
			
		||||
 | 
			
		||||
        std::vector<wxString> options = {"0.2", "0.4", "0.6", "0.8", "1.0"};
 | 
			
		||||
        std::stringstream ss;
 | 
			
		||||
        ss << std::setprecision(1) << m_new_point_head_diameter;
 | 
			
		||||
        wxString str = ss.str();
 | 
			
		||||
 | 
			
		||||
        bool old_combo_state = m_combo_box_open;
 | 
			
		||||
        m_combo_box_open = m_imgui->combo(_(L("Head diameter")), options, str);
 | 
			
		||||
        force_refresh |= (old_combo_state != m_combo_box_open);
 | 
			
		||||
 | 
			
		||||
        float current_number = atof(str);
 | 
			
		||||
        if (old_combo_state && !m_combo_box_open) // closing the combo must always change the sizes (even if the selection did not change)
 | 
			
		||||
            for (auto& point_and_selection : m_editing_mode_cache)
 | 
			
		||||
                if (point_and_selection.second) {
 | 
			
		||||
                    point_and_selection.first.head_front_radius = current_number / 2.f;
 | 
			
		||||
                    m_unsaved_changes = true;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
        if (std::abs(current_number - m_new_point_head_diameter) > 0.001) {
 | 
			
		||||
            force_refresh = true;
 | 
			
		||||
            m_new_point_head_diameter = current_number;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool changed = m_lock_unique_islands;
 | 
			
		||||
        m_imgui->checkbox(_(L("Lock supports under new islands")), m_lock_unique_islands);
 | 
			
		||||
        force_refresh |= changed != m_lock_unique_islands;
 | 
			
		||||
 | 
			
		||||
        remove_selected = m_imgui->button(_(L("Remove selected points")));
 | 
			
		||||
 | 
			
		||||
        m_imgui->text(" "); // vertical gap
 | 
			
		||||
 | 
			
		||||
        bool apply_changes = m_imgui->button(_(L("Apply changes")));
 | 
			
		||||
        if (apply_changes) {
 | 
			
		||||
            editing_mode_apply_changes();
 | 
			
		||||
            force_refresh = true;
 | 
			
		||||
            m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
 | 
			
		||||
        }
 | 
			
		||||
        ImGui::SameLine();
 | 
			
		||||
        bool discard_changes = m_imgui->button(_(L("Discard changes")));
 | 
			
		||||
        if (discard_changes) {
 | 
			
		||||
            editing_mode_discard_changes();
 | 
			
		||||
            force_refresh = true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
       /* ImGui::PushItemWidth(50.0f);
 | 
			
		||||
        m_imgui->text(_(L("Minimal points distance: ")));
 | 
			
		||||
        ImGui::SameLine();
 | 
			
		||||
        bool value_changed = ImGui::InputDouble("mm", &m_minimal_point_distance, 0.0f, 0.0f, "%.2f");
 | 
			
		||||
        m_imgui->text(_(L("Support points density: ")));
 | 
			
		||||
        ImGui::SameLine();
 | 
			
		||||
        value_changed |= ImGui::InputDouble("%", &m_density, 0.0f, 0.0f, "%.f");*/
 | 
			
		||||
 | 
			
		||||
        bool generate = m_imgui->button(_(L("Auto-generate points")));
 | 
			
		||||
 | 
			
		||||
        if (generate) {
 | 
			
		||||
#if SLAGIZMO_IMGUI_MODAL
 | 
			
		||||
            ImGui::OpenPopup(_(L("Warning")));
 | 
			
		||||
            m_show_modal = true;
 | 
			
		||||
            force_refresh = true;
 | 
			
		||||
#else
 | 
			
		||||
            wxMessageDialog dlg(GUI::wxGetApp().plater(), _(L(
 | 
			
		||||
                        "Autogeneration will erase all manually edited points.\n\n"
 | 
			
		||||
                        "Are you sure you want to do it?\n"
 | 
			
		||||
                        )), _(L("Warning")), wxICON_WARNING | wxYES | wxNO);
 | 
			
		||||
            if (m_model_object->sla_support_points.empty() || dlg.ShowModal() == wxID_YES) {
 | 
			
		||||
                m_model_object->sla_support_points.clear();
 | 
			
		||||
                m_editing_mode_cache.clear();
 | 
			
		||||
                wxGetApp().plater()->reslice();
 | 
			
		||||
            }
 | 
			
		||||
#endif
 | 
			
		||||
        }
 | 
			
		||||
#if SLAGIZMO_IMGUI_MODAL
 | 
			
		||||
        if (m_show_modal) {
 | 
			
		||||
            if (ImGui::BeginPopupModal(_(L("Warning")), &m_show_modal/*, ImGuiWindowFlags_NoDecoration*/))
 | 
			
		||||
            {
 | 
			
		||||
                m_imgui->text(_(L("Autogeneration will erase all manually edited points.")));
 | 
			
		||||
                m_imgui->text("");
 | 
			
		||||
                m_imgui->text(_(L("Are you sure you want to do it?")));
 | 
			
		||||
 | 
			
		||||
                if (m_imgui->button(_(L("Continue"))))
 | 
			
		||||
                {
 | 
			
		||||
                    ImGui::CloseCurrentPopup();
 | 
			
		||||
                    m_show_modal = false;
 | 
			
		||||
 | 
			
		||||
                    m_model_object->sla_support_points.clear();
 | 
			
		||||
                    m_editing_mode_cache.clear();
 | 
			
		||||
                    wxGetApp().plater()->reslice();
 | 
			
		||||
                }
 | 
			
		||||
                ImGui::SameLine();
 | 
			
		||||
                if (m_imgui->button(_(L("Cancel")))) {
 | 
			
		||||
                    ImGui::CloseCurrentPopup();
 | 
			
		||||
                    m_show_modal = false;
 | 
			
		||||
                }
 | 
			
		||||
            ImGui::EndPopup();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!m_show_modal)
 | 
			
		||||
                force_refresh = true;
 | 
			
		||||
        }
 | 
			
		||||
#endif
 | 
			
		||||
        m_imgui->text("");
 | 
			
		||||
        m_imgui->text("");
 | 
			
		||||
        bool editing_clicked = m_imgui->button(_(L("Manual editing")));
 | 
			
		||||
        if (editing_clicked) {
 | 
			
		||||
            editing_mode_reload_cache();
 | 
			
		||||
            m_editing_mode = true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    m_imgui->end();
 | 
			
		||||
 | 
			
		||||
    if (remove_all_clicked) {
 | 
			
		||||
        delete_current_grabber(true);
 | 
			
		||||
    if (m_editing_mode != old_editing_state) { // user just toggled between editing/non-editing mode
 | 
			
		||||
        m_parent.toggle_sla_auxiliaries_visibility(!m_editing_mode);
 | 
			
		||||
        force_refresh = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    if (remove_selected) {
 | 
			
		||||
        force_refresh = false;
 | 
			
		||||
        m_parent.reload_scene(true);
 | 
			
		||||
        delete_selected_points();
 | 
			
		||||
        if (first_run) {
 | 
			
		||||
            first_run = false;
 | 
			
		||||
            goto RENDER_AGAIN;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (remove_all_clicked || generate) {
 | 
			
		||||
    if (force_refresh)
 | 
			
		||||
        m_parent.reload_scene(true);
 | 
			
		||||
        m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
#endif // ENABLE_IMGUI
 | 
			
		||||
 | 
			
		||||
bool GLGizmoSlaSupports::on_is_activable(const GLCanvas3D::Selection& selection) const
 | 
			
		||||
{
 | 
			
		||||
    return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA)
 | 
			
		||||
        && selection.is_from_single_instance();
 | 
			
		||||
    if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA
 | 
			
		||||
        || !selection.is_from_single_instance())
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
    // Check that none of the selected volumes is outside.
 | 
			
		||||
    const GLCanvas3D::Selection::IndicesList& list = selection.get_volume_idxs();
 | 
			
		||||
    for (const auto& idx : list)
 | 
			
		||||
        if (selection.get_volume(idx)->is_outside)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool GLGizmoSlaSupports::on_is_selectable() const
 | 
			
		||||
| 
						 | 
				
			
			@ -2239,6 +2400,105 @@ std::string GLGizmoSlaSupports::on_get_name() const
 | 
			
		|||
    return L("SLA Support Points [L]");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLGizmoSlaSupports::on_set_state()
 | 
			
		||||
{
 | 
			
		||||
    if (m_state == On) {
 | 
			
		||||
        if (is_mesh_update_necessary())
 | 
			
		||||
            update_mesh();
 | 
			
		||||
 | 
			
		||||
        m_parent.toggle_model_objects_visibility(false);
 | 
			
		||||
        if (m_model_object)
 | 
			
		||||
            m_parent.toggle_model_objects_visibility(true, m_model_object, m_active_instance);
 | 
			
		||||
    }
 | 
			
		||||
    if (m_state == Off) {
 | 
			
		||||
        if (m_old_state != Off && m_model_object) { // the gizmo was just turned Off
 | 
			
		||||
 | 
			
		||||
            if (m_unsaved_changes) {
 | 
			
		||||
                wxMessageDialog dlg(GUI::wxGetApp().plater(), _(L("Do you want to save your manually edited support points ?\n")),
 | 
			
		||||
                                    _(L("Save changes?")), wxICON_QUESTION | wxYES | wxNO);
 | 
			
		||||
                if (dlg.ShowModal() == wxID_YES)
 | 
			
		||||
                    editing_mode_apply_changes();
 | 
			
		||||
                else
 | 
			
		||||
                    editing_mode_discard_changes();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            m_parent.toggle_model_objects_visibility(true);
 | 
			
		||||
            m_editing_mode = false; // so it is not active next time the gizmo opens
 | 
			
		||||
 | 
			
		||||
#if SLAGIZMO_IMGUI_MODAL
 | 
			
		||||
            if (m_show_modal) {
 | 
			
		||||
                m_show_modal = false;
 | 
			
		||||
                on_render_input_window(0,0,m_parent.get_selection()); // this is necessary to allow ImGui to terminate the modal dialog correctly
 | 
			
		||||
            }
 | 
			
		||||
#endif
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    m_old_state = m_state;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLGizmoSlaSupports::on_start_dragging(const GLCanvas3D::Selection& selection)
 | 
			
		||||
{
 | 
			
		||||
    if (m_hover_id != -1) {
 | 
			
		||||
        select_point(NoPoints);
 | 
			
		||||
        select_point(m_hover_id);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLGizmoSlaSupports::select_point(int i)
 | 
			
		||||
{
 | 
			
		||||
    if (i == AllPoints || i == NoPoints) {
 | 
			
		||||
        for (auto& point_and_selection : m_editing_mode_cache)
 | 
			
		||||
            point_and_selection.second = ( i == AllPoints ? true : false);
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
        m_editing_mode_cache[i].second = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLGizmoSlaSupports::editing_mode_discard_changes()
 | 
			
		||||
{
 | 
			
		||||
    m_editing_mode_cache.clear();
 | 
			
		||||
    for (const sla::SupportPoint& point : m_model_object->sla_support_points)
 | 
			
		||||
        m_editing_mode_cache.push_back(std::make_pair(point, false));
 | 
			
		||||
    m_editing_mode = false;
 | 
			
		||||
    m_unsaved_changes = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLGizmoSlaSupports::editing_mode_apply_changes()
 | 
			
		||||
{
 | 
			
		||||
    // If there are no changes, don't touch the front-end. The data in the cache could have been
 | 
			
		||||
    // taken from the backend and copying them to ModelObject would needlessly invalidate them.
 | 
			
		||||
    if (m_unsaved_changes) {
 | 
			
		||||
        m_model_object->sla_support_points.clear();
 | 
			
		||||
        for (const std::pair<sla::SupportPoint, bool>& point_and_selection : m_editing_mode_cache)
 | 
			
		||||
            m_model_object->sla_support_points.push_back(point_and_selection.first);
 | 
			
		||||
    }
 | 
			
		||||
    m_editing_mode = false;
 | 
			
		||||
    m_unsaved_changes = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLGizmoSlaSupports::editing_mode_reload_cache()
 | 
			
		||||
{
 | 
			
		||||
    m_editing_mode_cache.clear();
 | 
			
		||||
    for (const sla::SupportPoint& point : m_model_object->sla_support_points)
 | 
			
		||||
        m_editing_mode_cache.push_back(std::make_pair(point, false));
 | 
			
		||||
    m_unsaved_changes = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLGizmoSlaSupports::get_data_from_backend()
 | 
			
		||||
{
 | 
			
		||||
    for (const SLAPrintObject* po : m_parent.sla_print()->objects()) {
 | 
			
		||||
        if (po->model_object()->id() == m_model_object->id()) {
 | 
			
		||||
            const std::vector<sla::SupportPoint>& points = po->get_support_points();
 | 
			
		||||
            auto mat = po->trafo().inverse().cast<float>();
 | 
			
		||||
            for (unsigned int i=0; i<points.size();++i)
 | 
			
		||||
                m_editing_mode_cache.emplace_back(sla::SupportPoint(mat * points[i].pos, points[i].head_front_radius, points[i].is_new_island), false);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    m_unsaved_changes = false;
 | 
			
		||||
 | 
			
		||||
    // We don't copy the data into ModelObject, as this would stop the background processing.
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// GLGizmoCut
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -437,31 +437,26 @@ protected:
 | 
			
		|||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#define SLAGIZMO_IMGUI_MODAL 0
 | 
			
		||||
class GLGizmoSlaSupports : public GLGizmoBase
 | 
			
		||||
{
 | 
			
		||||
private:
 | 
			
		||||
    ModelObject* m_model_object = nullptr;
 | 
			
		||||
#if ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
    ModelObject* m_old_model_object = nullptr;
 | 
			
		||||
    int m_active_instance = -1;
 | 
			
		||||
    int m_old_instance_id = -1;
 | 
			
		||||
#else
 | 
			
		||||
    Transform3d m_instance_matrix;
 | 
			
		||||
#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
    Vec3f unproject_on_mesh(const Vec2d& mouse_pos);
 | 
			
		||||
 | 
			
		||||
#if ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
    GLUquadricObj* m_quadric;
 | 
			
		||||
#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
    const float RenderPointScale = 1.f;
 | 
			
		||||
 | 
			
		||||
    GLUquadricObj* m_quadric;
 | 
			
		||||
    Eigen::MatrixXf m_V; // vertices
 | 
			
		||||
    Eigen::MatrixXi m_F; // facets indices
 | 
			
		||||
    igl::AABB<Eigen::MatrixXf,3> m_AABB;
 | 
			
		||||
 | 
			
		||||
    struct SourceDataSummary {
 | 
			
		||||
#if !ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
        BoundingBoxf3 bounding_box;
 | 
			
		||||
        Transform3d matrix;
 | 
			
		||||
#endif // !ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
        Vec3d mesh_first_point;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -472,14 +467,10 @@ private:
 | 
			
		|||
 | 
			
		||||
public:
 | 
			
		||||
    explicit GLGizmoSlaSupports(GLCanvas3D& parent);
 | 
			
		||||
#if ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
    virtual ~GLGizmoSlaSupports();
 | 
			
		||||
    void set_sla_support_data(ModelObject* model_object, const GLCanvas3D::Selection& selection);
 | 
			
		||||
#else
 | 
			
		||||
    void set_model_object_ptr(ModelObject* model_object);
 | 
			
		||||
#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
    void clicked_on_object(const Vec2d& mouse_position);
 | 
			
		||||
    void delete_current_grabber(bool delete_all);
 | 
			
		||||
    bool mouse_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down);
 | 
			
		||||
    void delete_selected_points();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    bool on_init();
 | 
			
		||||
| 
						 | 
				
			
			@ -487,11 +478,8 @@ private:
 | 
			
		|||
    virtual void on_render(const GLCanvas3D::Selection& selection) const;
 | 
			
		||||
    virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const;
 | 
			
		||||
 | 
			
		||||
#if ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
    void render_grabbers(const GLCanvas3D::Selection& selection, bool picking = false) const;
 | 
			
		||||
#else
 | 
			
		||||
    void render_grabbers(bool picking = false) const;
 | 
			
		||||
#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD
 | 
			
		||||
    void render_selection_rectangle() const;
 | 
			
		||||
    void render_points(const GLCanvas3D::Selection& selection, bool picking = false) const;
 | 
			
		||||
    bool is_mesh_update_necessary() const;
 | 
			
		||||
    void update_mesh();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -501,12 +489,41 @@ private:
 | 
			
		|||
    mutable GLTexture m_reset_texture;
 | 
			
		||||
#endif // not ENABLE_IMGUI
 | 
			
		||||
 | 
			
		||||
    bool m_lock_unique_islands = false;
 | 
			
		||||
    bool m_editing_mode = false;
 | 
			
		||||
    float m_new_point_head_diameter = 0.4f;
 | 
			
		||||
    double m_minimal_point_distance = 20.;
 | 
			
		||||
    double m_density = 100.;
 | 
			
		||||
    std::vector<std::pair<sla::SupportPoint, bool>> m_editing_mode_cache; // a support point and whether it is currently selected
 | 
			
		||||
 | 
			
		||||
    bool m_selection_rectangle_active = false;
 | 
			
		||||
    Vec2d m_selection_rectangle_start_corner;
 | 
			
		||||
    Vec2d m_selection_rectangle_end_corner;
 | 
			
		||||
    bool m_ignore_up_event = false;
 | 
			
		||||
    bool m_combo_box_open = false;
 | 
			
		||||
    bool m_unsaved_changes = false;
 | 
			
		||||
    EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state)
 | 
			
		||||
#if SLAGIZMO_IMGUI_MODAL
 | 
			
		||||
    bool m_show_modal = false;
 | 
			
		||||
#endif
 | 
			
		||||
    int m_canvas_width;
 | 
			
		||||
    int m_canvas_height;
 | 
			
		||||
 | 
			
		||||
    // Methods that do the model_object and editing cache synchronization,
 | 
			
		||||
    // editing mode selection, etc:
 | 
			
		||||
    enum {
 | 
			
		||||
        AllPoints = -2,
 | 
			
		||||
        NoPoints,
 | 
			
		||||
    };
 | 
			
		||||
    void select_point(int i);
 | 
			
		||||
    void editing_mode_apply_changes();
 | 
			
		||||
    void editing_mode_discard_changes();
 | 
			
		||||
    void editing_mode_reload_cache();
 | 
			
		||||
    void get_data_from_backend();
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
    void on_set_state() override {
 | 
			
		||||
        if (m_state == On && is_mesh_update_necessary()) {
 | 
			
		||||
            update_mesh();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    void on_set_state() override;
 | 
			
		||||
    void on_start_dragging(const GLCanvas3D::Selection& selection) override;
 | 
			
		||||
 | 
			
		||||
#if ENABLE_IMGUI
 | 
			
		||||
    virtual void on_render_input_window(float x, float y, const GLCanvas3D::Selection& selection) override;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -355,21 +355,6 @@ boost::filesystem::path into_path(const wxString &str)
 | 
			
		|||
	return boost::filesystem::path(str.wx_str());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool get_current_screen_size(wxWindow *window, unsigned &width, unsigned &height)
 | 
			
		||||
{
 | 
			
		||||
	const auto idx = wxDisplay::GetFromWindow(window);
 | 
			
		||||
	if (idx == wxNOT_FOUND) {
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	wxDisplay display(idx);
 | 
			
		||||
	const auto disp_size = display.GetClientArea();
 | 
			
		||||
	width = disp_size.GetWidth();
 | 
			
		||||
	height = disp_size.GetHeight();
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void about()
 | 
			
		||||
{
 | 
			
		||||
    AboutDialog dlg;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -71,9 +71,6 @@ wxString	from_path(const boost::filesystem::path &path);
 | 
			
		|||
// boost path from wxString
 | 
			
		||||
boost::filesystem::path	into_path(const wxString &str);
 | 
			
		||||
 | 
			
		||||
// Returns the dimensions of the screen on which the main frame is displayed
 | 
			
		||||
bool get_current_screen_size(wxWindow *window, unsigned &width, unsigned &height);
 | 
			
		||||
 | 
			
		||||
// Display an About dialog
 | 
			
		||||
extern void about();
 | 
			
		||||
// Ask the destop to open the datadir using the default file explorer.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,8 +3,10 @@
 | 
			
		|||
#include "GUI_ObjectManipulation.hpp"
 | 
			
		||||
#include "I18N.hpp"
 | 
			
		||||
 | 
			
		||||
#include <exception>
 | 
			
		||||
#include <boost/lexical_cast.hpp>
 | 
			
		||||
#include <boost/algorithm/string.hpp>
 | 
			
		||||
#include <boost/log/trivial.hpp>
 | 
			
		||||
 | 
			
		||||
#include <wx/stdpaths.h>
 | 
			
		||||
#include <wx/imagpng.h>
 | 
			
		||||
| 
						 | 
				
			
			@ -125,6 +127,10 @@ bool GUI_App::OnInit()
 | 
			
		|||
    app_config->save();
 | 
			
		||||
 | 
			
		||||
    preset_updater = new PresetUpdater();
 | 
			
		||||
    Bind(EVT_SLIC3R_VERSION_ONLINE, [this](const wxCommandEvent &evt) {
 | 
			
		||||
        app_config->set("version_online", into_u8(evt.GetString()));
 | 
			
		||||
        app_config->save();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    load_language();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -181,7 +187,6 @@ bool GUI_App::OnInit()
 | 
			
		|||
                mainframe->Close();
 | 
			
		||||
        } catch (const std::exception &ex) {
 | 
			
		||||
            show_error(nullptr, ex.what());
 | 
			
		||||
            mainframe->Close();
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -351,21 +356,10 @@ void GUI_App::persist_window_geometry(wxTopLevelWindow *window)
 | 
			
		|||
    });
 | 
			
		||||
 | 
			
		||||
    window_pos_restore(window, name);
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
    // On windows, the wxEVT_SHOW is not received if the window is created maximized
 | 
			
		||||
    // cf. https://groups.google.com/forum/#!topic/wx-users/c7ntMt6piRI
 | 
			
		||||
    // so we sanitize the position right away
 | 
			
		||||
    window_pos_sanitize(window);
 | 
			
		||||
#else
 | 
			
		||||
    // On other platforms on the other hand it's needed to wait before the window is actually on screen
 | 
			
		||||
    // and some initial round of events is complete otherwise position / display index is not reported correctly.
 | 
			
		||||
    window->Bind(wxEVT_SHOW, [=](wxShowEvent &event) {
 | 
			
		||||
        CallAfter([=]() {
 | 
			
		||||
            window_pos_sanitize(window);
 | 
			
		||||
        });
 | 
			
		||||
        event.Skip();
 | 
			
		||||
 | 
			
		||||
    on_window_geometry(window, [=]() {
 | 
			
		||||
        window_pos_sanitize(window);
 | 
			
		||||
    });
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GUI_App::load_project(wxWindow *parent, wxString& input_file)
 | 
			
		||||
| 
						 | 
				
			
			@ -418,6 +412,7 @@ bool GUI_App::select_language(  wxArrayString & names,
 | 
			
		|||
		//FIXME This is a temporary workaround, the correct solution is to switch to "C" locale during file import / export only.
 | 
			
		||||
		wxSetlocale(LC_NUMERIC, "C");
 | 
			
		||||
        Preset::update_suffix_modified();
 | 
			
		||||
		m_imgui->set_language(m_wxLocale->GetCanonicalName().ToUTF8().data());
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
| 
						 | 
				
			
			@ -446,6 +441,7 @@ bool GUI_App::load_language()
 | 
			
		|||
			//FIXME This is a temporary workaround, the correct solution is to switch to "C" locale during file import / export only.
 | 
			
		||||
            wxSetlocale(LC_NUMERIC, "C");
 | 
			
		||||
			Preset::update_suffix_modified();
 | 
			
		||||
			m_imgui->set_language(m_wxLocale->GetCanonicalName().ToUTF8().data());
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -573,7 +569,10 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
 | 
			
		|||
    mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeSimple, _(L("Simple")), _(L("Simple View Mode")));
 | 
			
		||||
    mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _(L("Advanced")), _(L("Advanced View Mode")));
 | 
			
		||||
    mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeExpert, _(L("Expert")), _(L("Expert View Mode")));
 | 
			
		||||
    mode_menu->Check(config_id_base + ConfigMenuModeSimple + get_mode(), true);
 | 
			
		||||
    Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Check(get_mode() == comSimple); }, config_id_base + ConfigMenuModeSimple);
 | 
			
		||||
    Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Check(get_mode() == comAdvanced); }, config_id_base + ConfigMenuModeAdvanced);
 | 
			
		||||
    Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Check(get_mode() == comExpert); }, config_id_base + ConfigMenuModeExpert);
 | 
			
		||||
 | 
			
		||||
    local_menu->AppendSubMenu(mode_menu, _(L("Mode")), _(L("Slic3r View Mode")));
 | 
			
		||||
    local_menu->AppendSeparator();
 | 
			
		||||
    local_menu->Append(config_id_base + ConfigMenuLanguage, _(L("Change Application &Language")));
 | 
			
		||||
| 
						 | 
				
			
			@ -692,6 +691,23 @@ void GUI_App::load_current_presets()
 | 
			
		|||
		}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool GUI_App::OnExceptionInMainLoop()
 | 
			
		||||
{
 | 
			
		||||
    try {
 | 
			
		||||
        throw;
 | 
			
		||||
    } catch (const std::exception &ex) {
 | 
			
		||||
        const std::string error = (boost::format("Uncaught exception: %1%") % ex.what()).str();
 | 
			
		||||
        BOOST_LOG_TRIVIAL(error) << error;
 | 
			
		||||
        show_error(nullptr, from_u8(error));
 | 
			
		||||
    } catch (...) {
 | 
			
		||||
        const char *error = "Uncaught exception: Unknown error";
 | 
			
		||||
        BOOST_LOG_TRIVIAL(error) << error;
 | 
			
		||||
        show_error(nullptr, from_u8(error));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef __APPLE__
 | 
			
		||||
// wxWidgets override to get an event on open files.
 | 
			
		||||
void GUI_App::MacOpenFiles(const wxArrayString &fileNames)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -137,6 +137,8 @@ public:
 | 
			
		|||
    bool            checked_tab(Tab* tab);
 | 
			
		||||
    void            load_current_presets();
 | 
			
		||||
 | 
			
		||||
    virtual bool OnExceptionInMainLoop();
 | 
			
		||||
 | 
			
		||||
#ifdef __APPLE__
 | 
			
		||||
    // wxWidgets override to get an event on open files.
 | 
			
		||||
    void            MacOpenFiles(const wxArrayString &fileNames) override;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,6 +26,28 @@ wxTopLevelWindow* find_toplevel_parent(wxWindow *window)
 | 
			
		|||
    return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void on_window_geometry(wxTopLevelWindow *tlw, std::function<void()> callback)
 | 
			
		||||
{
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
    // On windows, the wxEVT_SHOW is not received if the window is created maximized
 | 
			
		||||
    // cf. https://groups.google.com/forum/#!topic/wx-users/c7ntMt6piRI
 | 
			
		||||
    // OTOH the geometry is available very soon, so we can call the callback right away
 | 
			
		||||
    callback();
 | 
			
		||||
#elif defined __linux__
 | 
			
		||||
    tlw->Bind(wxEVT_SHOW, [=](wxShowEvent &evt) {
 | 
			
		||||
        // On Linux, the geometry is only available after wxEVT_SHOW + CallAfter
 | 
			
		||||
        // cf. https://groups.google.com/forum/?pli=1#!topic/wx-users/fERSXdpVwAI
 | 
			
		||||
        tlw->CallAfter([=]() { callback(); });
 | 
			
		||||
        evt.Skip();
 | 
			
		||||
    });
 | 
			
		||||
#elif defined __APPLE__
 | 
			
		||||
    tlw->Bind(wxEVT_SHOW, [=](wxShowEvent &evt) {
 | 
			
		||||
        callback();
 | 
			
		||||
        evt.Skip();
 | 
			
		||||
    });
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CheckboxFileDialog::ExtraPanel::ExtraPanel(wxWindow *parent)
 | 
			
		||||
    : wxPanel(parent, wxID_ANY)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,7 @@
 | 
			
		|||
#include <memory>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <ostream>
 | 
			
		||||
#include <functional>
 | 
			
		||||
 | 
			
		||||
#include <boost/optional.hpp>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -24,6 +25,8 @@ namespace GUI {
 | 
			
		|||
 | 
			
		||||
wxTopLevelWindow* find_toplevel_parent(wxWindow *window);
 | 
			
		||||
 | 
			
		||||
void on_window_geometry(wxTopLevelWindow *tlw, std::function<void()> callback);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EventGuard
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,7 +25,8 @@ namespace GUI {
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
ImGuiWrapper::ImGuiWrapper()
 | 
			
		||||
    : m_font_texture(0)
 | 
			
		||||
    : m_glyph_ranges(nullptr)
 | 
			
		||||
    , m_font_texture(0)
 | 
			
		||||
    , m_style_scaling(1.0)
 | 
			
		||||
    , m_mouse_buttons(0)
 | 
			
		||||
    , m_disabled(false)
 | 
			
		||||
| 
						 | 
				
			
			@ -49,6 +50,35 @@ bool ImGuiWrapper::init()
 | 
			
		|||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ImGuiWrapper::set_language(const std::string &language)
 | 
			
		||||
{
 | 
			
		||||
    const ImWchar *ranges = nullptr;
 | 
			
		||||
    size_t idx = language.find('_');
 | 
			
		||||
    std::string lang = (idx == std::string::npos) ? language : language.substr(0, idx);
 | 
			
		||||
    static const ImWchar ranges_latin2[] =
 | 
			
		||||
    {
 | 
			
		||||
        0x0020, 0x00FF, // Basic Latin + Latin Supplement
 | 
			
		||||
        0x0100, 0x017F, // Latin Extended-A
 | 
			
		||||
        0,
 | 
			
		||||
    };
 | 
			
		||||
    if (lang == "cs" || lang == "pl") {
 | 
			
		||||
        ranges = ranges_latin2;
 | 
			
		||||
    } else if (lang == "ru" || lang == "uk") {
 | 
			
		||||
        ranges = ImGui::GetIO().Fonts->GetGlyphRangesCyrillic();
 | 
			
		||||
    } else if (lang == "jp") {
 | 
			
		||||
        ranges = ImGui::GetIO().Fonts->GetGlyphRangesJapanese();
 | 
			
		||||
    } else if (lang == "kr") {
 | 
			
		||||
        ranges = ImGui::GetIO().Fonts->GetGlyphRangesKorean();
 | 
			
		||||
    } else if (lang == "zh") {
 | 
			
		||||
        ranges = ImGui::GetIO().Fonts->GetGlyphRangesChineseSimplifiedCommon();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (ranges != m_glyph_ranges) {
 | 
			
		||||
        m_glyph_ranges = ranges;
 | 
			
		||||
        init_default_font(m_style_scaling);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ImGuiWrapper::set_display_size(float w, float h)
 | 
			
		||||
{
 | 
			
		||||
    ImGuiIO& io = ImGui::GetIO();
 | 
			
		||||
| 
						 | 
				
			
			@ -125,6 +155,12 @@ bool ImGuiWrapper::button(const wxString &label)
 | 
			
		|||
    return ImGui::Button(label_utf8.c_str());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ImGuiWrapper::radio_button(const wxString &label, bool active)
 | 
			
		||||
{
 | 
			
		||||
    auto label_utf8 = into_u8(label);
 | 
			
		||||
    return ImGui::RadioButton(label_utf8.c_str(), active);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ImGuiWrapper::input_double(const std::string &label, const double &value, const std::string &format)
 | 
			
		||||
{
 | 
			
		||||
    return ImGui::InputDouble(label.c_str(), const_cast<double*>(&value), 0.0f, 0.0f, format.c_str());
 | 
			
		||||
| 
						 | 
				
			
			@ -161,6 +197,28 @@ void ImGuiWrapper::text(const wxString &label)
 | 
			
		|||
    ImGui::Text(label_utf8.c_str(), NULL);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
bool ImGuiWrapper::combo(const wxString& label, const std::vector<wxString>& options, wxString& selection)
 | 
			
		||||
{
 | 
			
		||||
    std::string selection_u8 = into_u8(selection);
 | 
			
		||||
 | 
			
		||||
    // this is to force the label to the left of the widget:
 | 
			
		||||
    text(label);
 | 
			
		||||
    ImGui::SameLine();
 | 
			
		||||
    
 | 
			
		||||
    if (ImGui::BeginCombo("", selection_u8.c_str())) {
 | 
			
		||||
        for (const wxString& option : options) {
 | 
			
		||||
            std::string option_u8 = into_u8(option);
 | 
			
		||||
            bool is_selected = (selection_u8.empty()) ? false : (option_u8 == selection_u8);
 | 
			
		||||
            if (ImGui::Selectable(option_u8.c_str(), is_selected))
 | 
			
		||||
                selection = option_u8;
 | 
			
		||||
        }
 | 
			
		||||
        ImGui::EndCombo();
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ImGuiWrapper::disabled_begin(bool disabled)
 | 
			
		||||
{
 | 
			
		||||
    wxCHECK_RET(!m_disabled, "ImGUI: Unbalanced disabled_begin() call");
 | 
			
		||||
| 
						 | 
				
			
			@ -210,7 +268,7 @@ void ImGuiWrapper::init_default_font(float scaling)
 | 
			
		|||
 | 
			
		||||
    ImGuiIO& io = ImGui::GetIO();
 | 
			
		||||
    io.Fonts->Clear();
 | 
			
		||||
    ImFont* font = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/NotoSans-Regular.ttf").c_str(), font_size * scaling);
 | 
			
		||||
    ImFont* font = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/NotoSans-Regular.ttf").c_str(), font_size * scaling, nullptr, m_glyph_ranges);
 | 
			
		||||
    if (font == nullptr) {
 | 
			
		||||
        font = io.Fonts->AddFontDefault();
 | 
			
		||||
        if (font == nullptr) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,6 +20,7 @@ class ImGuiWrapper
 | 
			
		|||
    typedef std::map<std::string, ImFont*> FontsMap;
 | 
			
		||||
 | 
			
		||||
    FontsMap m_fonts;
 | 
			
		||||
    const ImWchar *m_glyph_ranges;
 | 
			
		||||
    unsigned m_font_texture;
 | 
			
		||||
    float m_style_scaling;
 | 
			
		||||
    unsigned m_mouse_buttons;
 | 
			
		||||
| 
						 | 
				
			
			@ -32,6 +33,7 @@ public:
 | 
			
		|||
    bool init();
 | 
			
		||||
    void read_glsl_version();
 | 
			
		||||
 | 
			
		||||
    void set_language(const std::string &language);
 | 
			
		||||
    void set_display_size(float w, float h);
 | 
			
		||||
    void set_style_scaling(float scaling);
 | 
			
		||||
    bool update_mouse_data(wxMouseEvent &evt);
 | 
			
		||||
| 
						 | 
				
			
			@ -47,10 +49,12 @@ public:
 | 
			
		|||
    void end();
 | 
			
		||||
 | 
			
		||||
    bool button(const wxString &label);
 | 
			
		||||
    bool radio_button(const wxString &label, bool active);
 | 
			
		||||
    bool input_double(const std::string &label, const double &value, const std::string &format = "%.3f");
 | 
			
		||||
    bool input_vec3(const std::string &label, const Vec3d &value, float width, const std::string &format = "%.3f");
 | 
			
		||||
    bool checkbox(const wxString &label, bool &value);
 | 
			
		||||
    void text(const wxString &label);
 | 
			
		||||
    bool combo(const wxString& label, const std::vector<wxString>& options, wxString& current_selection);
 | 
			
		||||
 | 
			
		||||
    void disabled_begin(bool disabled);
 | 
			
		||||
    void disabled_end();
 | 
			
		||||
| 
						 | 
				
			
			@ -59,6 +63,7 @@ public:
 | 
			
		|||
    bool want_keyboard() const;
 | 
			
		||||
    bool want_text_input() const;
 | 
			
		||||
    bool want_any_input() const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    void init_default_font(float scaling);
 | 
			
		||||
    void create_device_objects();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -105,6 +105,12 @@ wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAUL
 | 
			
		|||
        event.Skip();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    Bind(wxEVT_ACTIVATE, [this](wxActivateEvent& event) {
 | 
			
		||||
        if (m_plater != nullptr && event.GetActive())
 | 
			
		||||
            m_plater->on_activate();
 | 
			
		||||
        event.Skip();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    wxGetApp().persist_window_geometry(this);
 | 
			
		||||
 | 
			
		||||
    update_ui_from_settings();    // FIXME (?)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,7 +12,6 @@
 | 
			
		|||
 | 
			
		||||
#include <wx/sizer.h>
 | 
			
		||||
#include <wx/stattext.h>
 | 
			
		||||
#include <wx/notebook.h>
 | 
			
		||||
#include <wx/button.h>
 | 
			
		||||
#include <wx/bmpcbox.h>
 | 
			
		||||
#include <wx/statbox.h>
 | 
			
		||||
| 
						 | 
				
			
			@ -1008,6 +1007,7 @@ struct Plater::priv
 | 
			
		|||
    std::atomic<bool>           arranging;
 | 
			
		||||
    std::atomic<bool>           rotoptimizing;
 | 
			
		||||
    bool                        delayed_scene_refresh;
 | 
			
		||||
    std::string                 delayed_error_message;
 | 
			
		||||
 | 
			
		||||
    wxTimer                     background_process_timer;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1993,6 +1993,8 @@ unsigned int Plater::priv::update_background_process(bool force_validation)
 | 
			
		|||
    this->background_process_timer.Stop();
 | 
			
		||||
    // Update the "out of print bed" state of ModelInstances.
 | 
			
		||||
    this->update_print_volume_state();
 | 
			
		||||
    // The delayed error message is no more valid.
 | 
			
		||||
    this->delayed_error_message.clear();
 | 
			
		||||
    // Apply new config to the possibly running background task.
 | 
			
		||||
    bool               was_running = this->background_process.running();
 | 
			
		||||
    Print::ApplyStatus invalidated = this->background_process.apply(this->q->model(), wxGetApp().preset_bundle->full_config());
 | 
			
		||||
| 
						 | 
				
			
			@ -2031,8 +2033,18 @@ unsigned int Plater::priv::update_background_process(bool force_validation)
 | 
			
		|||
                return_state |= UPDATE_BACKGROUND_PROCESS_RESTART;
 | 
			
		||||
        } else {
 | 
			
		||||
            // The print is not valid.
 | 
			
		||||
            // The error returned from the Print needs to be translated into the local language.
 | 
			
		||||
            GUI::show_error(this->q, _(err));
 | 
			
		||||
            // Only show the error message immediately, if the top level parent of this window is active.
 | 
			
		||||
            auto p = dynamic_cast<wxWindow*>(this->q);
 | 
			
		||||
            while (p->GetParent())
 | 
			
		||||
                p = p->GetParent();
 | 
			
		||||
            auto *top_level_wnd = dynamic_cast<wxTopLevelWindow*>(p);
 | 
			
		||||
            if (top_level_wnd && top_level_wnd->IsActive()) {
 | 
			
		||||
                // The error returned from the Print needs to be translated into the local language.
 | 
			
		||||
                GUI::show_error(this->q, _(err));
 | 
			
		||||
            } else {
 | 
			
		||||
                // Show the error message once the main window gets activated.
 | 
			
		||||
                this->delayed_error_message = _(err);
 | 
			
		||||
            }
 | 
			
		||||
            return_state |= UPDATE_BACKGROUND_PROCESS_INVALID;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -2277,6 +2289,10 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt)
 | 
			
		|||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if (evt.status.flags & PrintBase::SlicingStatus::RELOAD_SLA_SUPPORT_POINTS) {
 | 
			
		||||
        // Update SLA gizmo  (reload_scene calls update_gizmos_data)
 | 
			
		||||
        q->canvas3D()->reload_scene(true);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Plater::priv::on_slicing_completed(wxCommandEvent &)
 | 
			
		||||
| 
						 | 
				
			
			@ -3141,6 +3157,26 @@ void Plater::on_config_change(const DynamicPrintConfig &config)
 | 
			
		|||
        this->p->schedule_background_process();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Plater::on_activate()
 | 
			
		||||
{
 | 
			
		||||
#ifdef __linux__
 | 
			
		||||
    wxWindow *focus_window = wxWindow::FindFocus();
 | 
			
		||||
    // Activating the main frame, and no window has keyboard focus.
 | 
			
		||||
    // Set the keyboard focus to the visible Canvas3D.
 | 
			
		||||
    if (this->p->view3D->IsShown() && (!focus_window || focus_window == this->p->view3D->get_wxglcanvas()))
 | 
			
		||||
        this->p->view3D->get_wxglcanvas()->SetFocus();
 | 
			
		||||
 | 
			
		||||
    else if (this->p->preview->IsShown() && (!focus_window || focus_window == this->p->view3D->get_wxglcanvas()))
 | 
			
		||||
        this->p->preview->get_wxglcanvas()->SetFocus();
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    if (! this->p->delayed_error_message.empty()) {
 | 
			
		||||
		std::string msg = std::move(this->p->delayed_error_message);
 | 
			
		||||
		this->p->delayed_error_message.clear();
 | 
			
		||||
        GUI::show_error(this, msg);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const wxString& Plater::get_project_filename() const
 | 
			
		||||
{
 | 
			
		||||
    return p->project_filename;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -151,6 +151,8 @@ public:
 | 
			
		|||
 | 
			
		||||
    void on_extruders_change(int extruders_count);
 | 
			
		||||
    void on_config_change(const DynamicPrintConfig &config);
 | 
			
		||||
    // On activating the parent window.
 | 
			
		||||
    void on_activate();
 | 
			
		||||
 | 
			
		||||
    void update_object_menu();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,8 +11,10 @@
 | 
			
		|||
    #include <Windows.h>
 | 
			
		||||
#endif /* _MSC_VER */
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <fstream>
 | 
			
		||||
#include <stdexcept>
 | 
			
		||||
#include <unordered_map>
 | 
			
		||||
#include <boost/format.hpp>
 | 
			
		||||
#include <boost/filesystem.hpp>
 | 
			
		||||
#include <boost/filesystem/fstream.hpp>
 | 
			
		||||
| 
						 | 
				
			
			@ -83,6 +85,16 @@ VendorProfile VendorProfile::from_ini(const boost::filesystem::path &path, bool
 | 
			
		|||
    return VendorProfile::from_ini(tree, path, load_all);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const std::unordered_map<std::string, std::string> pre_family_model_map {{
 | 
			
		||||
    { "MK3",        "MK3" },
 | 
			
		||||
    { "MK3MMU2",    "MK3" },
 | 
			
		||||
    { "MK2.5",      "MK2.5" },
 | 
			
		||||
    { "MK2.5MMU2",  "MK2.5" },
 | 
			
		||||
    { "MK2S",       "MK2" },
 | 
			
		||||
    { "MK2SMM",     "MK2" },
 | 
			
		||||
    { "SL1",        "SL1" },
 | 
			
		||||
}};
 | 
			
		||||
 | 
			
		||||
VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem::path &path, bool load_all)
 | 
			
		||||
{
 | 
			
		||||
    static const std::string printer_model_key = "printer_model:";
 | 
			
		||||
| 
						 | 
				
			
			@ -128,11 +140,21 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem
 | 
			
		|||
            VendorProfile::PrinterModel model;
 | 
			
		||||
            model.id = section.first.substr(printer_model_key.size());
 | 
			
		||||
            model.name = section.second.get<std::string>("name", model.id);
 | 
			
		||||
            auto technology_field = section.second.get<std::string>("technology", "FFF");
 | 
			
		||||
 | 
			
		||||
            const char *technology_fallback = boost::algorithm::starts_with(model.id, "SL") ? "SLA" : "FFF";
 | 
			
		||||
 | 
			
		||||
            auto technology_field = section.second.get<std::string>("technology", technology_fallback);
 | 
			
		||||
            if (! ConfigOptionEnum<PrinterTechnology>::from_string(technology_field, model.technology)) {
 | 
			
		||||
                BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: Invalid printer technology field: `%2%`") % id % technology_field;
 | 
			
		||||
                model.technology = ptFFF;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            model.family = section.second.get<std::string>("family", std::string());
 | 
			
		||||
            if (model.family.empty() && res.name == "Prusa Research") {
 | 
			
		||||
                // If no family is specified, it can be inferred for known printers
 | 
			
		||||
                const auto from_pre_map = pre_family_model_map.find(model.id);
 | 
			
		||||
                if (from_pre_map != pre_family_model_map.end()) { model.family = from_pre_map->second; }
 | 
			
		||||
            }
 | 
			
		||||
#if 0
 | 
			
		||||
			// Remove SLA printers from the initial alpha.
 | 
			
		||||
			if (model.technology == ptSLA)
 | 
			
		||||
| 
						 | 
				
			
			@ -157,6 +179,20 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem
 | 
			
		|||
    return res;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<std::string> VendorProfile::families() const
 | 
			
		||||
{
 | 
			
		||||
    std::vector<std::string> res;
 | 
			
		||||
    unsigned num_familiies = 0;
 | 
			
		||||
 | 
			
		||||
    for (auto &model : models) {
 | 
			
		||||
        if (std::find(res.begin(), res.end(), model.family) == res.end()) {
 | 
			
		||||
            res.push_back(model.family);
 | 
			
		||||
            num_familiies++;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return res;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Suffix to be added to a modified preset name in the combo box.
 | 
			
		||||
static std::string g_suffix_modified = " (modified)";
 | 
			
		||||
| 
						 | 
				
			
			@ -422,9 +458,8 @@ const std::vector<std::string>& Preset::sla_print_options()
 | 
			
		|||
            "support_critical_angle",
 | 
			
		||||
            "support_max_bridge_length",
 | 
			
		||||
            "support_object_elevation",
 | 
			
		||||
            "support_density_at_horizontal",
 | 
			
		||||
            "support_density_at_45",
 | 
			
		||||
            "support_minimal_z",
 | 
			
		||||
            "support_points_density_relative",
 | 
			
		||||
            "support_points_minimal_distance",
 | 
			
		||||
            "pad_enable",
 | 
			
		||||
            "pad_wall_thickness",
 | 
			
		||||
            "pad_wall_height",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -54,13 +54,16 @@ public:
 | 
			
		|||
        std::string                 id;
 | 
			
		||||
        std::string                 name;
 | 
			
		||||
        PrinterTechnology           technology;
 | 
			
		||||
        std::string                 family;
 | 
			
		||||
        std::vector<PrinterVariant> variants;
 | 
			
		||||
 | 
			
		||||
        PrinterVariant*       variant(const std::string &name) {
 | 
			
		||||
            for (auto &v : this->variants)
 | 
			
		||||
                if (v.name == name)
 | 
			
		||||
                    return &v;
 | 
			
		||||
            return nullptr;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const PrinterVariant* variant(const std::string &name) const { return const_cast<PrinterModel*>(this)->variant(name); }
 | 
			
		||||
    };
 | 
			
		||||
    std::vector<PrinterModel>          models;
 | 
			
		||||
| 
						 | 
				
			
			@ -72,6 +75,7 @@ public:
 | 
			
		|||
    static VendorProfile from_ini(const boost::property_tree::ptree &tree, const boost::filesystem::path &path, bool load_all=true);
 | 
			
		||||
 | 
			
		||||
    size_t      num_variants() const { size_t n = 0; for (auto &model : models) n += model.variants.size(); return n; }
 | 
			
		||||
    std::vector<std::string> families() const;
 | 
			
		||||
 | 
			
		||||
    bool        operator< (const VendorProfile &rhs) const { return this->id <  rhs.id; }
 | 
			
		||||
    bool        operator==(const VendorProfile &rhs) const { return this->id == rhs.id; }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,6 +15,7 @@
 | 
			
		|||
 | 
			
		||||
#include "GUI.hpp"
 | 
			
		||||
#include "GUI_App.hpp"
 | 
			
		||||
#include "AppConfig.hpp"
 | 
			
		||||
#include "MsgDialog.hpp"
 | 
			
		||||
#include "I18N.hpp"
 | 
			
		||||
#include "../Utils/PrintHost.hpp"
 | 
			
		||||
| 
						 | 
				
			
			@ -24,10 +25,12 @@ namespace fs = boost::filesystem;
 | 
			
		|||
namespace Slic3r {
 | 
			
		||||
namespace GUI {
 | 
			
		||||
 | 
			
		||||
static const char *CONFIG_KEY_PATH  = "printhost_path";
 | 
			
		||||
static const char *CONFIG_KEY_PRINT = "printhost_print";
 | 
			
		||||
 | 
			
		||||
PrintHostSendDialog::PrintHostSendDialog(const fs::path &path)
 | 
			
		||||
    : MsgDialog(nullptr, _(L("Send G-Code to printer host")), _(L("Upload to Printer Host with the following filename:")), wxID_NONE)
 | 
			
		||||
    , txt_filename(new wxTextCtrl(this, wxID_ANY, path.filename().wstring()))
 | 
			
		||||
    , txt_filename(new wxTextCtrl(this, wxID_ANY))
 | 
			
		||||
    , box_print(new wxCheckBox(this, wxID_ANY, _(L("Start printing after upload"))))
 | 
			
		||||
{
 | 
			
		||||
#ifdef __APPLE__
 | 
			
		||||
| 
						 | 
				
			
			@ -44,11 +47,30 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path)
 | 
			
		|||
 | 
			
		||||
    btn_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL));
 | 
			
		||||
 | 
			
		||||
    txt_filename->SetFocus();
 | 
			
		||||
    const AppConfig *app_config = wxGetApp().app_config;
 | 
			
		||||
    box_print->SetValue(app_config->get("recent", CONFIG_KEY_PRINT) == "1");
 | 
			
		||||
 | 
			
		||||
    wxString recent_path = from_u8(app_config->get("recent", CONFIG_KEY_PATH));
 | 
			
		||||
    if (recent_path.Length() > 0 && recent_path[recent_path.Length() - 1] != '/') {
 | 
			
		||||
        recent_path += '/';
 | 
			
		||||
    }
 | 
			
		||||
    const auto recent_path_len = recent_path.Length();
 | 
			
		||||
    recent_path += path.filename().wstring();
 | 
			
		||||
    wxString stem(path.stem().wstring());
 | 
			
		||||
    txt_filename->SetSelection(0, stem.Length());
 | 
			
		||||
    const auto stem_len = stem.Length();
 | 
			
		||||
 | 
			
		||||
    txt_filename->SetValue(recent_path);
 | 
			
		||||
    txt_filename->SetFocus();
 | 
			
		||||
 | 
			
		||||
    Fit();
 | 
			
		||||
 | 
			
		||||
    Bind(wxEVT_SHOW, [=](const wxShowEvent &) {
 | 
			
		||||
        // Another similar case where the function only works with EVT_SHOW + CallAfter,
 | 
			
		||||
        // this time on Mac.
 | 
			
		||||
        CallAfter([=]() {
 | 
			
		||||
            txt_filename->SetSelection(recent_path_len, recent_path_len + stem_len);
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fs::path PrintHostSendDialog::filename() const
 | 
			
		||||
| 
						 | 
				
			
			@ -61,6 +83,24 @@ bool PrintHostSendDialog::start_print() const
 | 
			
		|||
    return box_print->GetValue();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PrintHostSendDialog::EndModal(int ret)
 | 
			
		||||
{
 | 
			
		||||
    if (ret == wxID_OK) {
 | 
			
		||||
        // Persist path and print settings
 | 
			
		||||
        wxString path = txt_filename->GetValue();
 | 
			
		||||
        int last_slash = path.Find('/', true);
 | 
			
		||||
        if (last_slash != wxNOT_FOUND) {
 | 
			
		||||
            path = path.SubString(0, last_slash);
 | 
			
		||||
            wxGetApp().app_config->set("recent", CONFIG_KEY_PATH, into_u8(path));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool print = box_print->GetValue();
 | 
			
		||||
        GUI::get_app_config()->set("recent", CONFIG_KEY_PRINT, print ? "1" : "0");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    MsgDialog::EndModal(ret);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
wxDEFINE_EVENT(EVT_PRINTHOST_PROGRESS, PrintHostQueueDialog::Event);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,6 +33,7 @@ public:
 | 
			
		|||
    boost::filesystem::path filename() const;
 | 
			
		||||
    bool start_print() const;
 | 
			
		||||
 | 
			
		||||
    virtual void EndModal(int ret) override;
 | 
			
		||||
private:
 | 
			
		||||
    wxTextCtrl *txt_filename;
 | 
			
		||||
    wxCheckBox *box_print;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,8 @@
 | 
			
		|||
#include "3DScene.hpp"
 | 
			
		||||
#include "GUI.hpp"
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
#include <wx/clipbrd.h>
 | 
			
		||||
#include <wx/platinfo.h>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3210,9 +3210,8 @@ void TabSLAPrint::build()
 | 
			
		|||
    optgroup->append_single_option_line("support_max_bridge_length");
 | 
			
		||||
 | 
			
		||||
    optgroup = page->new_optgroup(_(L("Automatic generation")));
 | 
			
		||||
    optgroup->append_single_option_line("support_density_at_horizontal");
 | 
			
		||||
    optgroup->append_single_option_line("support_density_at_45");
 | 
			
		||||
    optgroup->append_single_option_line("support_minimal_z");
 | 
			
		||||
    optgroup->append_single_option_line("support_points_density_relative");
 | 
			
		||||
    optgroup->append_single_option_line("support_points_minimal_distance");
 | 
			
		||||
 | 
			
		||||
    page = add_options_page(_(L("Pad")), "brick.png");
 | 
			
		||||
    optgroup = page->new_optgroup(_(L("Pad")));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -244,7 +244,7 @@ void Http::priv::http_perform()
 | 
			
		|||
	::curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, static_cast<void*>(this));
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	::curl_easy_setopt(curl, CURLOPT_VERBOSE, get_logging_level() >= 4);
 | 
			
		||||
	::curl_easy_setopt(curl, CURLOPT_VERBOSE, get_logging_level() >= 5);
 | 
			
		||||
 | 
			
		||||
	if (headerlist != nullptr) {
 | 
			
		||||
		::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,7 +13,6 @@
 | 
			
		|||
#include <boost/log/trivial.hpp>
 | 
			
		||||
 | 
			
		||||
#include <wx/app.h>
 | 
			
		||||
#include <wx/event.h>
 | 
			
		||||
#include <wx/msgdlg.h>
 | 
			
		||||
 | 
			
		||||
#include "libslic3r/libslic3r.h"
 | 
			
		||||
| 
						 | 
				
			
			@ -90,9 +89,25 @@ struct Updates
 | 
			
		|||
	std::vector<Update> updates;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static Semver get_slic3r_version()
 | 
			
		||||
{
 | 
			
		||||
	auto res = Semver::parse(SLIC3R_VERSION);
 | 
			
		||||
 | 
			
		||||
	if (! res) {
 | 
			
		||||
		const char *error = "Could not parse Slic3r version string: " SLIC3R_VERSION;
 | 
			
		||||
		BOOST_LOG_TRIVIAL(error) << error;
 | 
			
		||||
		throw std::runtime_error(error);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return *res;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
wxDEFINE_EVENT(EVT_SLIC3R_VERSION_ONLINE, wxCommandEvent);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
struct PresetUpdater::priv
 | 
			
		||||
{
 | 
			
		||||
	const Semver ver_slic3r;
 | 
			
		||||
	std::vector<Index> index_db;
 | 
			
		||||
 | 
			
		||||
	bool enabled_version_check;
 | 
			
		||||
| 
						 | 
				
			
			@ -122,12 +137,13 @@ struct PresetUpdater::priv
 | 
			
		|||
	static void copy_file(const fs::path &from, const fs::path &to);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
PresetUpdater::priv::priv() :
 | 
			
		||||
	had_config_update(false),
 | 
			
		||||
	cache_path(fs::path(Slic3r::data_dir()) / "cache"),
 | 
			
		||||
	rsrc_path(fs::path(resources_dir()) / "profiles"),
 | 
			
		||||
	vendor_path(fs::path(Slic3r::data_dir()) / "vendor"),
 | 
			
		||||
	cancel(false)
 | 
			
		||||
PresetUpdater::priv::priv()
 | 
			
		||||
	: ver_slic3r(get_slic3r_version())
 | 
			
		||||
	, had_config_update(false)
 | 
			
		||||
	, cache_path(fs::path(Slic3r::data_dir()) / "cache")
 | 
			
		||||
	, rsrc_path(fs::path(resources_dir()) / "profiles")
 | 
			
		||||
	, vendor_path(fs::path(Slic3r::data_dir()) / "vendor")
 | 
			
		||||
	, cancel(false)
 | 
			
		||||
{
 | 
			
		||||
	set_download_prefs(GUI::wxGetApp().app_config);
 | 
			
		||||
	check_install_indices();
 | 
			
		||||
| 
						 | 
				
			
			@ -209,11 +225,10 @@ void PresetUpdater::priv::sync_version() const
 | 
			
		|||
		.on_complete([&](std::string body, unsigned /* http_status */) {
 | 
			
		||||
			boost::trim(body);
 | 
			
		||||
			BOOST_LOG_TRIVIAL(info) << boost::format("Got Slic3rPE online version: `%1%`. Sending to GUI thread...") % body;
 | 
			
		||||
// 			wxCommandEvent* evt = new wxCommandEvent(version_online_event);
 | 
			
		||||
// 			evt->SetString(body);
 | 
			
		||||
// 			GUI::get_app()->QueueEvent(evt);
 | 
			
		||||
	        GUI::wxGetApp().app_config->set("version_online", body);
 | 
			
		||||
	        GUI::wxGetApp().app_config->save();
 | 
			
		||||
 | 
			
		||||
			wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_VERSION_ONLINE);
 | 
			
		||||
			evt->SetString(GUI::from_u8(body));
 | 
			
		||||
			GUI::wxGetApp().QueueEvent(evt);
 | 
			
		||||
		})
 | 
			
		||||
		.perform_sync();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -260,7 +275,7 @@ void PresetUpdater::priv::sync_config(const std::set<VendorProfile> vendors)
 | 
			
		|||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
			if (new_index.version() < index.version()) {
 | 
			
		||||
				BOOST_LOG_TRIVIAL(error) << boost::format("The downloaded index %1% for vendor %2% is older than the active one. Ignoring the downloaded index.") % idx_path_temp % vendor.name;
 | 
			
		||||
				BOOST_LOG_TRIVIAL(warning) << boost::format("The downloaded index %1% for vendor %2% is older than the active one. Ignoring the downloaded index.") % idx_path_temp % vendor.name;
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
			Slic3r::rename_file(idx_path_temp, idx_path);
 | 
			
		||||
| 
						 | 
				
			
			@ -275,6 +290,7 @@ void PresetUpdater::priv::sync_config(const std::set<VendorProfile> vendors)
 | 
			
		|||
			BOOST_LOG_TRIVIAL(error) << boost::format("No recommended version for vendor: %1%, invalid index?") % vendor.name;
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const auto recommended = recommended_it->config_version;
 | 
			
		||||
 | 
			
		||||
		BOOST_LOG_TRIVIAL(debug) << boost::format("Got index for vendor: %1%: current version: %2%, recommended version: %3%")
 | 
			
		||||
| 
						 | 
				
			
			@ -341,7 +357,8 @@ Updates PresetUpdater::priv::get_config_updates() const
 | 
			
		|||
		if (ver_current == idx.end()) {
 | 
			
		||||
			auto message = (boost::format("Preset bundle `%1%` version not found in index: %2%") % idx.vendor() % vp.config_version.to_string()).str();
 | 
			
		||||
			BOOST_LOG_TRIVIAL(error) << message;
 | 
			
		||||
			throw std::runtime_error(message);
 | 
			
		||||
			GUI::show_error(nullptr, GUI::from_u8(message));
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Getting a recommended version from the latest index, wich may have been downloaded
 | 
			
		||||
| 
						 | 
				
			
			@ -528,18 +545,14 @@ void PresetUpdater::slic3r_update_notify()
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	auto* app_config = GUI::wxGetApp().app_config;
 | 
			
		||||
	const auto ver_slic3r = Semver::parse(SLIC3R_VERSION);
 | 
			
		||||
	const auto ver_online_str = app_config->get("version_online");
 | 
			
		||||
	const auto ver_online = Semver::parse(ver_online_str);
 | 
			
		||||
	const auto ver_online_seen = Semver::parse(app_config->get("version_online_seen"));
 | 
			
		||||
	if (! ver_slic3r) {
 | 
			
		||||
		throw std::runtime_error("Could not parse Slic3r version string: " SLIC3R_VERSION);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (ver_online) {
 | 
			
		||||
		// Only display the notification if the version available online is newer AND if we haven't seen it before
 | 
			
		||||
		if (*ver_online > *ver_slic3r && (! ver_online_seen || *ver_online_seen < *ver_online)) {
 | 
			
		||||
			GUI::MsgUpdateSlic3r notification(*ver_slic3r, *ver_online);
 | 
			
		||||
		if (*ver_online > p->ver_slic3r && (! ver_online_seen || *ver_online_seen < *ver_online)) {
 | 
			
		||||
			GUI::MsgUpdateSlic3r notification(p->ver_slic3r, *ver_online);
 | 
			
		||||
			notification.ShowModal();
 | 
			
		||||
			if (notification.disable_version_check()) {
 | 
			
		||||
				app_config->set("version_check", "0");
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,8 @@
 | 
			
		|||
#include <memory>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#include <wx/event.h>
 | 
			
		||||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -37,6 +39,8 @@ private:
 | 
			
		|||
	std::unique_ptr<priv> p;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
wxDECLARE_EVENT(EVT_SLIC3R_VERSION_ONLINE, wxCommandEvent);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||