Merge remote-tracking branch 'origin/master' into ys_sla_time_estimation

This commit is contained in:
YuSanka 2019-02-20 15:14:53 +01:00
commit fb8c66f223
61 changed files with 5158 additions and 3475 deletions

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Before After
Before After

View file

@ -1,8 +1,14 @@
min_slic3r_version = 1.42.0-alpha6
0.8.0-alpha6
min_slic3r_version = 1.42.0-alpha min_slic3r_version = 1.42.0-alpha
0.8.0-alpha
0.4.0-alpha4 Updated SLA profiles 0.4.0-alpha4 Updated SLA profiles
0.4.0-alpha3 Update of SLA profiles 0.4.0-alpha3 Update of SLA profiles
0.4.0-alpha2 First 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 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.3 Prusament PETG released
0.3.2 New MK2.5 and MK3 FW versions 0.3.2 New MK2.5 and MK3 FW versions
0.3.1 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-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 0.2.0-alpha moved machine limits from the start G-code to the new print profile parameters
min_slic3r_version = 1.40.0 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.11 fw version changed to 3.3.1
0.1.10 MK3 jerk and acceleration update 0.1.10 MK3 jerk and acceleration update
0.1.9 edited support extrusion width for 0.25 and 0.6 nozzles 0.1.9 edited support extrusion width for 0.25 and 0.6 nozzles

File diff suppressed because it is too large Load diff

View file

@ -323,7 +323,7 @@ namespace Slic3r {
typedef std::map<int, ObjectMetadata> IdToMetadataMap; typedef std::map<int, ObjectMetadata> IdToMetadataMap;
typedef std::map<int, Geometry> IdToGeometryMap; typedef std::map<int, Geometry> IdToGeometryMap;
typedef std::map<int, std::vector<coordf_t>> IdToLayerHeightsProfileMap; 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 // Version of the 3mf file
unsigned int m_version; unsigned int m_version;
@ -776,10 +776,19 @@ namespace Slic3r {
std::vector<std::string> objects; std::vector<std::string> objects;
boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off); 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) for (const std::string& object : objects)
{ {
std::vector<std::string> object_data; std::vector<std::string> object_data;
boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off); boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off);
if (object_data.size() != 2) if (object_data.size() != 2)
{ {
add_error("Error while reading object data"); add_error("Error while reading object data");
@ -811,10 +820,24 @@ namespace Slic3r {
std::vector<std::string> object_data_points; std::vector<std::string> object_data_points;
boost::split(object_data_points, object_data[1], boost::is_any_of(" "), boost::token_compress_off); 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) if (version == 0) {
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>()); 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()) if (!sla_support_points.empty())
m_sla_support_points.insert(IdToSlaSupportPointsMap::value_type(object_id, sla_support_points)); 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) for (const ModelObject* object : model.objects)
{ {
++count; ++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()) if (!sla_support_points.empty())
{ {
sprintf(buffer, "object_id=%d|", count); sprintf(buffer, "object_id=%d|", count);
@ -1970,7 +1993,7 @@ namespace Slic3r {
// Store the layer height profile as a single space separated list. // Store the layer height profile as a single space separated list.
for (size_t i = 0; i < sla_support_points.size(); ++i) 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 += buffer;
} }
out += "\n"; out += "\n";
@ -1979,6 +2002,9 @@ namespace Slic3r {
if (!out.empty()) 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)) 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"); add_error("Unable to add sla support points file to archive");

View file

@ -3,6 +3,23 @@
namespace Slic3r { 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 Model;
class DynamicPrintConfig; class DynamicPrintConfig;

View file

@ -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) { 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. // Parse object's layer height profile, a semicolon separated list of floats.
unsigned char coord_idx = 0; 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()); char *p = const_cast<char*>(m_value[1].c_str());
for (;;) { for (;;) {
char *end = strchr(p, ';'); char *end = strchr(p, ';');
@ -591,8 +591,8 @@ void AMFParserContext::endElement(const char * /* name */)
*end = 0; *end = 0;
point(coord_idx) = atof(p); point(coord_idx) = atof(p);
if (++coord_idx == 3) { if (++coord_idx == 5) {
m_object->sla_support_points.push_back(point); m_object->sla_support_points.push_back(sla::SupportPoint(point));
coord_idx = 0; coord_idx = 0;
} }
if (end == nullptr) 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) //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()) { if (!sla_support_points.empty()) {
// Store the SLA supports as a single semicolon separated list. // Store the SLA supports as a single semicolon separated list.
stream << " <metadata type=\"slic3r.sla_support_points\">"; stream << " <metadata type=\"slic3r.sla_support_points\">";
for (size_t i = 0; i < sla_support_points.size(); ++i) { for (size_t i = 0; i < sla_support_points.size(); ++i) {
if (i != 0) if (i != 0)
stream << ";"; 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"; stream << "\n </metadata>\n";
} }

View file

@ -12,6 +12,7 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "Geometry.hpp" #include "Geometry.hpp"
#include <libslic3r/SLA/SLACommon.hpp>
namespace Slic3r { namespace Slic3r {
@ -175,7 +176,8 @@ public:
// This vector holds position of selected support points for SLA. The data are // This vector holds position of selected support points for SLA. The data are
// saved in mesh coordinates to allow using them for several instances. // 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 /* This vector accumulates the total translation applied to the object by the
center_around_origin() method. Callers might want to apply the same translation center_around_origin() method. Callers might want to apply the same translation

View file

@ -22,6 +22,7 @@ typedef Point Vector;
// Vector types with a fixed point coordinate base type. // Vector types with a fixed point coordinate base type.
typedef Eigen::Matrix<coord_t, 2, 1, Eigen::DontAlign> Vec2crd; typedef Eigen::Matrix<coord_t, 2, 1, Eigen::DontAlign> Vec2crd;
typedef Eigen::Matrix<coord_t, 3, 1, Eigen::DontAlign> Vec3crd; 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<int, 3, 1, Eigen::DontAlign> Vec3i;
typedef Eigen::Matrix<int64_t, 2, 1, Eigen::DontAlign> Vec2i64; typedef Eigen::Matrix<int64_t, 2, 1, Eigen::DontAlign> Vec2i64;
typedef Eigen::Matrix<int64_t, 3, 1, Eigen::DontAlign> Vec3i64; typedef Eigen::Matrix<int64_t, 3, 1, Eigen::DontAlign> Vec3i64;

View file

@ -244,8 +244,9 @@ public:
// Bitmap of flags. // Bitmap of flags.
enum FlagBits { enum FlagBits {
DEFAULT, DEFAULT,
NO_RELOAD_SCENE = 0, NO_RELOAD_SCENE = 0,
RELOAD_SCENE = 1, RELOAD_SCENE = 1 << 1,
RELOAD_SLA_SUPPORT_POINTS = 1 << 2,
}; };
// Bitmap of FlagBits // Bitmap of FlagBits
unsigned int flags; unsigned int flags;

View file

@ -2664,28 +2664,19 @@ void PrintConfigDef::init_sla_params()
def->min = 0; def->min = 0;
def->default_value = new ConfigOptionFloat(5.0); def->default_value = new ConfigOptionFloat(5.0);
def = this->add("support_density_at_horizontal", coInt); def = this->add("support_points_density_relative", coInt);
def->label = L("Density on horizontal surfaces"); def->label = L("Support points density");
def->category = L("Supports"); def->category = L("Supports");
def->tooltip = L("How many support points (approximately) should be placed on horizontal surface."); def->tooltip = L("This is a relative measure of support points density.");
def->sidetext = L("points per square dm"); def->sidetext = L("%");
def->cli = ""; def->cli = "";
def->min = 0; def->min = 0;
def->default_value = new ConfigOptionInt(500); def->default_value = new ConfigOptionInt(100);
def = this->add("support_density_at_45", coInt); def = this->add("support_points_minimal_distance", coFloat);
def->label = L("Density on surfaces at 45 degrees"); def->label = L("Minimal distance of the support points");
def->category = L("Supports"); def->category = L("Supports");
def->tooltip = L("How many support points (approximately) should be placed on surface sloping at 45 degrees."); def->tooltip = L("No support points will be placed closer than this threshold.");
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->sidetext = L("mm"); def->sidetext = L("mm");
def->cli = ""; def->cli = "";
def->min = 0; def->min = 0;

View file

@ -1005,9 +1005,8 @@ public:
ConfigOptionFloat support_object_elevation /*= 5.0*/; ConfigOptionFloat support_object_elevation /*= 5.0*/;
/////// Following options influence automatic support points placement: /////// Following options influence automatic support points placement:
ConfigOptionInt support_density_at_horizontal; ConfigOptionInt support_points_density_relative;
ConfigOptionInt support_density_at_45; ConfigOptionFloat support_points_minimal_distance;
ConfigOptionFloat support_minimal_z;
// Now for the base pool (pad) ///////////////////////////////////////////// // Now for the base pool (pad) /////////////////////////////////////////////
@ -1044,9 +1043,8 @@ protected:
OPT_PTR(support_base_height); OPT_PTR(support_base_height);
OPT_PTR(support_critical_angle); OPT_PTR(support_critical_angle);
OPT_PTR(support_max_bridge_length); OPT_PTR(support_max_bridge_length);
OPT_PTR(support_density_at_horizontal); OPT_PTR(support_points_density_relative);
OPT_PTR(support_density_at_45); OPT_PTR(support_points_minimal_distance);
OPT_PTR(support_minimal_z);
OPT_PTR(support_object_elevation); OPT_PTR(support_object_elevation);
OPT_PTR(pad_enable); OPT_PTR(pad_enable);
OPT_PTR(pad_wall_thickness); OPT_PTR(pad_wall_thickness);

View file

@ -1,47 +1,23 @@
#include "igl/random_points_on_mesh.h" #include "igl/random_points_on_mesh.h"
#include "igl/AABB.h" #include "igl/AABB.h"
#include <tbb/parallel_for.h>
#include "SLAAutoSupports.hpp" #include "SLAAutoSupports.hpp"
#include "Model.hpp" #include "Model.hpp"
#include "ExPolygon.hpp" #include "ExPolygon.hpp"
#include "SVG.hpp" #include "SVG.hpp"
#include "Point.hpp" #include "Point.hpp"
#include "ClipperUtils.hpp" #include "ClipperUtils.hpp"
#include "Tesselate.hpp"
#include "libslic3r.h"
#include <iostream> #include <iostream>
#include <random> #include <random>
namespace Slic3r { namespace Slic3r {
SLAAutoSupports::SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector<ExPolygons>& slices, const std::vector<float>& heights, /*float SLAAutoSupports::approximate_geodesic_distance(const Vec3d& p1, const Vec3d& p2, Vec3d& n1, Vec3d& n2)
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)
{ {
n1.normalize(); n1.normalize();
n2.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 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 // 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 float SLAAutoSupports::distance_limit(float angle) const
{ {
return 1./(2.4*get_required_density(angle)); 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 #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)); BoundingBox bb(Point(-30000000, -30000000), Point(30000000, 30000000));
Slic3r::SVG svg_cummulative(filename, bb); 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)); svg_cummulative.draw_outline(expolys[i].holes, "blue", scale_(0.05));
} }
} }
#endif /* SLA_AUTOSUPPORTS_DEBUG */ #endif
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>();
}
}
} // namespace Slic3r } // namespace Slic3r

View file

@ -1,9 +1,12 @@
#ifndef SLAAUTOSUPPORTS_HPP_ #ifndef SLAAUTOSUPPORTS_HPP_
#define SLAAUTOSUPPORTS_HPP_ #define SLAAUTOSUPPORTS_HPP_
#include <libslic3r/ClipperUtils.hpp>
#include <libslic3r/Point.hpp> #include <libslic3r/Point.hpp>
#include <libslic3r/TriangleMesh.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 // #define SLA_AUTOSUPPORTS_DEBUG
@ -12,36 +15,184 @@ namespace Slic3r {
class SLAAutoSupports { class SLAAutoSupports {
public: public:
struct Config { struct Config {
float density_at_horizontal; float density_relative;
float density_at_45; float minimal_distance;
float minimal_z; ///////////////
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, 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<float>& heights, const Config& config, std::function<void(void)> throw_on_cancel);
const std::vector<Vec3d>& output() { return m_output; } const std::vector<sla::SupportPoint>& output() { return m_output; }
private: struct MyLayer;
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 Structure {
Structure(MyLayer &layer, const ExPolygon& poly, const BoundingBox &bbox, const Vec2f &centroid, float area, float h) :
layer(&layer), polygon(&poly), bbox(bbox), centroid(centroid), area(area), height(h)
#ifdef SLA_AUTOSUPPORTS_DEBUG #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 */ #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; 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; std::function<void(void)> m_throw_on_cancel;
const Eigen::MatrixXd& m_V; const sla::EigenMesh3D& m_emesh;
const Eigen::MatrixXi& m_F;
}; };

View 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

View file

@ -550,10 +550,16 @@ enum { // For indexing Eigen vectors as v(X), v(Y), v(Z) instead of numbers
X, Y, Z 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); 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; return ret;
} }
@ -671,6 +677,7 @@ double pinhead_mesh_intersect(const Vec3d& s,
return *mit; return *mit;
} }
// Checking bridge (pillar and stick as well) intersection with the model. If // 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 // 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. // true as the beginning of the stick might be inside the model geometry.

View file

@ -7,6 +7,9 @@
#include <memory> #include <memory>
#include <Eigen/Geometry> #include <Eigen/Geometry>
#include "SLACommon.hpp"
namespace Slic3r { namespace Slic3r {
// Needed types from Point.hpp // Needed types from Point.hpp
@ -105,86 +108,6 @@ struct Controller {
std::function<void(void)> cancelfn = [](){}; 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; using PointSet = Eigen::MatrixXd;
//EigenMesh3D to_eigenmesh(const TriangleMesh& m); //EigenMesh3D to_eigenmesh(const TriangleMesh& m);
@ -193,7 +116,7 @@ using PointSet = Eigen::MatrixXd;
//EigenMesh3D to_eigenmesh(const ModelObject& model); //EigenMesh3D to_eigenmesh(const ModelObject& model);
// Simple conversion of 'vector of points' to an Eigen matrix // 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>&);
/* ************************************************************************** */ /* ************************************************************************** */

View file

@ -95,7 +95,9 @@ size_t SpatIndex::size() const
class EigenMesh3D::AABBImpl: public igl::AABB<Eigen::MatrixXd, 3> { class EigenMesh3D::AABBImpl: public igl::AABB<Eigen::MatrixXd, 3> {
public: public:
#ifdef SLIC3R_SLA_NEEDS_WINDTREE
igl::WindingNumberAABB<Vec3d, Eigen::MatrixXd, Eigen::MatrixXi> windtree; igl::WindingNumberAABB<Vec3d, Eigen::MatrixXd, Eigen::MatrixXi> windtree;
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
}; };
EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) { 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 // Build the AABB accelaration tree
m_aabb->init(m_V, m_F); m_aabb->init(m_V, m_F);
#ifdef SLIC3R_SLA_NEEDS_WINDTREE
m_aabb->windtree.set_mesh(m_V, m_F); m_aabb->windtree.set_mesh(m_V, m_F);
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
} }
EigenMesh3D::~EigenMesh3D() {} EigenMesh3D::~EigenMesh3D() {}
@ -168,6 +172,7 @@ EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
return ret; return ret;
} }
#ifdef SLIC3R_SLA_NEEDS_WINDTREE
EigenMesh3D::si_result EigenMesh3D::signed_distance(const Vec3d &p) const { EigenMesh3D::si_result EigenMesh3D::signed_distance(const Vec3d &p) const {
double sign = 0; double sqdst = 0; int i = 0; Vec3d c; 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, 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 { bool EigenMesh3D::inside(const Vec3d &p) const {
return m_aabb->windtree.inside(p); return m_aabb->windtree.inside(p);
} }
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
/* **************************************************************************** /* ****************************************************************************
* Misc functions * Misc functions

View file

@ -26,7 +26,7 @@ using SupportTreePtr = std::unique_ptr<sla::SLASupportTree>;
class SLAPrintObject::SupportData { class SLAPrintObject::SupportData {
public: public:
sla::EigenMesh3D emesh; // index-triangle representation 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 SupportTreePtr support_tree_ptr; // the supports
SlicedSupports support_slices; // sliced supports SlicedSupports support_slices; // sliced supports
std::vector<LevelID> level_ids; 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); 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) { if (it_print_object_status != print_object_status.end() && it_print_object_status->status != PrintObjectStatus::Deleted) {
// The SLAPrintObject is already there. // The SLAPrintObject is already there.
if (new_instances != it_print_object_status->print_object->instances()) { if (new_instances.empty()) {
// Instances changed. const_cast<PrintObjectStatus&>(*it_print_object_status).status = PrintObjectStatus::Deleted;
it_print_object_status->print_object->set_instances(new_instances); } else {
update_apply_status(this->invalidate_step(slapsRasterize)); if (new_instances != it_print_object_status->print_object->instances()) {
} // Instances changed.
print_objects_new.emplace_back(it_print_object_status->print_object); it_print_object_status->print_object->set_instances(new_instances);
const_cast<PrintObjectStatus&>(*it_print_object_status).status = PrintObjectStatus::Reused; update_apply_status(this->invalidate_step(slapsRasterize));
} else { }
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); auto print_object = new SLAPrintObject(this, &model_object);
// FIXME: this invalidates the transformed mesh in SLAPrintObject // FIXME: this invalidates the transformed mesh in SLAPrintObject
@ -473,7 +477,7 @@ void SLAPrint::process()
const size_t objcount = m_objects.size(); const size_t objcount = m_objects.size();
const unsigned min_objstatus = 0; // where the per object operations start 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 // 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 // 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(); this->throw_if_canceled();
SLAAutoSupports::Config config; SLAAutoSupports::Config config;
const SLAPrintObjectConfig& cfg = po.config(); const SLAPrintObjectConfig& cfg = po.config();
config.minimal_z = float(cfg.support_minimal_z); config.density_relative = float(cfg.support_points_density_relative / 100.f); // the config value is in percents
config.density_at_45 = cfg.support_density_at_45 / 10000.f; config.minimal_distance = float(cfg.support_points_minimal_distance);
config.density_at_horizontal = cfg.support_density_at_horizontal / 10000.f;
// Construction of this object does the calculation. // Construction of this object does the calculation.
this->throw_if_canceled(); this->throw_if_canceled();
@ -547,17 +550,19 @@ void SLAPrint::process()
[this]() { throw_if_canceled(); }); [this]() { throw_if_canceled(); });
// Now let's extract the result. // 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(); 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: " 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 { else {
// There are some points on the front-end, no calculation will be done. // There are some points on the front-end, no calculation will be done.
po.m_supportdata->support_points = po.m_supportdata->support_points = po.transformed_support_points();
sla::to_point_set(po.transformed_support_points());
} }
}; };
@ -588,6 +593,8 @@ void SLAPrint::process()
ctl.statuscb = [this, init, d](unsigned st, const std::string& msg) 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); report_status(*this, int(init + st*d), msg);
}; };
@ -595,7 +602,7 @@ void SLAPrint::process()
ctl.cancelfn = [this]() { throw_if_canceled(); }; ctl.cancelfn = [this]() { throw_if_canceled(); };
po.m_supportdata->support_tree_ptr.reset( 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)); po.m_supportdata->emesh, scfg, ctl));
// Create the unified mesh // Create the unified mesh
@ -606,7 +613,7 @@ void SLAPrint::process()
po.m_supportdata->support_tree_ptr->merged_mesh(); po.m_supportdata->support_tree_ptr->merged_mesh();
BOOST_LOG_TRIVIAL(debug) << "Processed support point count " 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. // Check the mesh for later troubleshooting.
if(po.support_mesh().empty()) if(po.support_mesh().empty())
@ -884,16 +891,6 @@ void SLAPrint::process()
using slaposFn = std::function<void(SLAPrintObject&)>; using slaposFn = std::function<void(SLAPrintObject&)>;
using slapsFn = std::function<void(void)>; 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 = std::array<slaposFn, slaposCount> pobj_program =
{ {
slice_model, slice_model,
@ -917,28 +914,32 @@ void SLAPrint::process()
// TODO: this loop could run in parallel but should not exhaust all the CPU // TODO: this loop could run in parallel but should not exhaust all the CPU
// power available // 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) { for (int s = (int)step_ranges[idx_range]; s < (int)step_ranges[idx_range + 1]; ++s) {
auto currentstep = objectsteps[s]; auto currentstep = (SLAPrintObjectStep)s;
// Cancellation checking. Each step will check for cancellation // Cancellation checking. Each step will check for cancellation
// on its own and return earlier gracefully. Just after it returns // on its own and return earlier gracefully. Just after it returns
// execution gets to this point and throws the canceled signal. // 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);
throw_if_canceled(); 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) { for (const t_config_option_key &opt_key : opt_keys) {
if (opt_key == "layer_height") { if (opt_key == "layer_height") {
steps.emplace_back(slaposObjectSlice); 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); steps.emplace_back(slaposSupportPoints);
} else if ( } else if (
opt_key == "support_head_front_diameter" opt_key == "support_head_front_diameter"
@ -1343,7 +1347,7 @@ const std::vector<ExPolygons> EMPTY_SLICES;
const TriangleMesh EMPTY_MESH; 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; return m_supportdata->support_points;
} }
@ -1422,15 +1426,19 @@ const TriangleMesh &SLAPrintObject::transformed_mesh() const {
return m_transformed_rmesh.get(); 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); 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 // 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; return ret;
} }

View file

@ -70,7 +70,7 @@ public:
// This will return the transformed mesh which is cached // This will return the transformed mesh which is cached
const TriangleMesh& transformed_mesh() const; 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 // Get the needed Z elevation for the model geometry if supports should be
// displayed. This Z offset should also be applied to the support // displayed. This Z offset should also be applied to the support
@ -91,7 +91,7 @@ public:
const std::vector<ExPolygons>& get_support_slices() const; const std::vector<ExPolygons>& get_support_slices() const;
// This method returns the support points of this SLAPrintObject. // 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 // An index record referencing the slices
// (get_model_slices(), get_support_slices()) where the keys are the height // (get_model_slices(), get_support_slices()) where the keys are the height

View file

@ -23,8 +23,6 @@
// Scene's GUI made using imgui library // Scene's GUI made using imgui library
#define ENABLE_IMGUI (1 && ENABLE_1_42_0_ALPHA1) #define ENABLE_IMGUI (1 && ENABLE_1_42_0_ALPHA1)
#define DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI (1 && ENABLE_IMGUI) #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 // Use wxDataViewRender instead of wxDataViewCustomRenderer
#define ENABLE_NONCUSTOM_DATA_VIEW_RENDERING (0 && ENABLE_1_42_0_ALPHA1) #define ENABLE_NONCUSTOM_DATA_VIEW_RENDERING (0 && ENABLE_1_42_0_ALPHA1)

View file

@ -20,7 +20,7 @@ public:
gluDeleteTess(m_tesselator); 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_z = z_;
m_flipped = flipped_; m_flipped = flipped_;
@ -56,7 +56,7 @@ public:
return std::move(m_output_triangles); 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_z = z_;
m_flipped = flipped_; m_flipped = flipped_;
@ -189,16 +189,60 @@ private:
bool m_flipped; 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; 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; 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 } // namespace Slic3r

View file

@ -10,8 +10,12 @@ namespace Slic3r {
class ExPolygon; class ExPolygon;
typedef std::vector<ExPolygon> ExPolygons; typedef std::vector<ExPolygon> ExPolygons;
extern Pointf3s triangulate_expolygons_3df(const ExPolygon &poly, 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 Pointf3s triangulate_expolygons_3df(const ExPolygons &polys, 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 } // namespace Slic3r

View file

@ -1781,7 +1781,7 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower)
BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - triangulating upper part"; BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - triangulating upper part";
ExPolygons section; ExPolygons section;
this->make_expolygons_simple(upper_lines, &section); this->make_expolygons_simple(upper_lines, &section);
Pointf3s triangles = triangulate_expolygons_3df(section, z, true); Pointf3s triangles = triangulate_expolygons_3d(section, z, true);
stl_facet facet; stl_facet facet;
facet.normal = stl_normal(0, 0, -1.f); facet.normal = stl_normal(0, 0, -1.f);
for (size_t i = 0; i < triangles.size(); ) { 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"; BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - triangulating lower part";
ExPolygons section; ExPolygons section;
this->make_expolygons_simple(lower_lines, &section); this->make_expolygons_simple(lower_lines, &section);
Pointf3s triangles = triangulate_expolygons_3df(section, z, false); Pointf3s triangles = triangulate_expolygons_3d(section, z, false);
stl_facet facet; stl_facet facet;
facet.normal = stl_normal(0, 0, -1.f); facet.normal = stl_normal(0, 0, -1.f);
for (size_t i = 0; i < triangles.size(); ) { for (size_t i = 0; i < triangles.size(); ) {

View file

@ -42,22 +42,27 @@ namespace Slic3r {
static boost::log::trivial::severity_level logSeverity = boost::log::trivial::error; 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) { switch (level) {
// Report fatal errors only. // Report fatal errors only.
case 0: logSeverity = boost::log::trivial::fatal; break; case 0: return boost::log::trivial::fatal;
// Report fatal errors and errors. // 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. // 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. // 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. // 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. // 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 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::warning : return 2;
case boost::log::trivial::info : return 3; case boost::log::trivial::info : return 3;
case boost::log::trivial::debug : return 4; case boost::log::trivial::debug : return 4;
case boost::log::trivial::trace : return 5;
default: return 1; default: return 1;
} }
} }
@ -88,21 +94,7 @@ static struct RunOnInit {
void trace(unsigned int level, const char *message) void trace(unsigned int level, const char *message)
{ {
boost::log::trivial::severity_level severity = boost::log::trivial::trace; boost::log::trivial::severity_level severity = level_to_boost(level);
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_STREAM_WITH_PARAMS(::boost::log::trivial::logger::get(),\ BOOST_LOG_STREAM_WITH_PARAMS(::boost::log::trivial::logger::get(),\
(::boost::log::keywords::severity = severity)) << message; (::boost::log::keywords::severity = severity)) << message;

View file

@ -793,7 +793,7 @@ void GLVolumeCollection::render_VBOs(GLVolumeCollection::ERenderType type, bool
glsafe(::glDisable(GL_BLEND)); 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(glEnable(GL_BLEND));
glsafe(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); 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_VERTEX_ARRAY));
glsafe(glEnableClientState(GL_NORMAL_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) for (GLVolumeWithZ& volume : to_render)
{ {
volume.first->set_render_color(); volume.first->set_render_color();

View file

@ -456,7 +456,7 @@ public:
// Render the volumes by OpenGL. // 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_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, // Finalize the initialization of the geometry & indices,
// upload the geometry and indices to OpenGL VBO objects // upload the geometry and indices to OpenGL VBO objects

File diff suppressed because it is too large Load diff

View file

@ -16,30 +16,30 @@ namespace GUI {
class ConfigWizard: public wxDialog class ConfigWizard: public wxDialog
{ {
public: public:
// Why is the Wizard run // Why is the Wizard run
enum RunReason { enum RunReason {
RR_DATA_EMPTY, // No or empty datadir RR_DATA_EMPTY, // No or empty datadir
RR_DATA_LEGACY, // Pre-updating datadir RR_DATA_LEGACY, // Pre-updating datadir
RR_DATA_INCOMPAT, // Incompatible datadir - Slic3r downgrade situation RR_DATA_INCOMPAT, // Incompatible datadir - Slic3r downgrade situation
RR_USER, // User requested the Wizard from the menus RR_USER, // User requested the Wizard from the menus
}; };
ConfigWizard(wxWindow *parent, RunReason run_reason); ConfigWizard(wxWindow *parent, RunReason run_reason);
ConfigWizard(ConfigWizard &&) = delete; ConfigWizard(ConfigWizard &&) = delete;
ConfigWizard(const ConfigWizard &) = delete; ConfigWizard(const ConfigWizard &) = delete;
ConfigWizard &operator=(ConfigWizard &&) = delete; ConfigWizard &operator=(ConfigWizard &&) = delete;
ConfigWizard &operator=(const ConfigWizard &) = delete; ConfigWizard &operator=(const ConfigWizard &) = delete;
~ConfigWizard(); ~ConfigWizard();
// Run the Wizard. Return whether it was completed. // Run the Wizard. Return whether it was completed.
bool run(PresetBundle *preset_bundle, const PresetUpdater *updater); 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: private:
struct priv; struct priv;
std::unique_ptr<priv> p; std::unique_ptr<priv> p;
friend struct ConfigWizardPage; friend struct ConfigWizardPage;
}; };

View file

@ -6,6 +6,7 @@
#include <vector> #include <vector>
#include <set> #include <set>
#include <unordered_map> #include <unordered_map>
#include <functional>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <wx/sizer.h> #include <wx/sizer.h>
@ -13,6 +14,7 @@
#include <wx/button.h> #include <wx/button.h>
#include <wx/choice.h> #include <wx/choice.h>
#include <wx/spinctrl.h> #include <wx/spinctrl.h>
#include <wx/textctrl.h>
#include "libslic3r/PrintConfig.hpp" #include "libslic3r/PrintConfig.hpp"
#include "slic3r/Utils/PresetUpdater.hpp" #include "slic3r/Utils/PresetUpdater.hpp"
@ -26,211 +28,264 @@ namespace Slic3r {
namespace GUI { namespace GUI {
enum { enum {
WRAP_WIDTH = 500, WRAP_WIDTH = 500,
MODEL_MIN_WRAP = 150, MODEL_MIN_WRAP = 150,
DIALOG_MARGIN = 15, DIALOG_MARGIN = 15,
INDEX_MARGIN = 40, INDEX_MARGIN = 40,
BTN_SPACING = 10, BTN_SPACING = 10,
INDENT_SPACING = 30, INDENT_SPACING = 30,
VERTICAL_SPACING = 10, VERTICAL_SPACING = 10,
MAX_COLS = 4,
ROW_SPACING = 75,
}; };
typedef std::function<bool(const VendorProfile::PrinterModel&)> ModelFilter;
struct PrinterPicker: wxPanel struct PrinterPicker: wxPanel
{ {
struct Checkbox : wxCheckBox struct Checkbox : wxCheckBox
{ {
Checkbox(wxWindow *parent, const wxString &label, const std::string &model, const std::string &variant) : Checkbox(wxWindow *parent, const wxString &label, const std::string &model, const std::string &variant) :
wxCheckBox(parent, wxID_ANY, label), wxCheckBox(parent, wxID_ANY, label),
model(model), model(model),
variant(variant) variant(variant)
{} {}
std::string model; std::string model;
std::string variant; std::string variant;
}; };
const std::string vendor_id; const std::string vendor_id;
std::vector<Checkbox*> cboxes; std::vector<Checkbox*> cboxes;
unsigned variants_checked; 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_all(bool select, bool alternates = false);
void select_one(size_t i, bool select); void select_one(size_t i, bool select);
void on_checkbox(const Checkbox *cbox, bool checked); void on_checkbox(const Checkbox *cbox, bool checked);
int get_width() const { return width; }
private:
int width;
}; };
struct ConfigWizardPage: wxPanel struct ConfigWizardPage: wxPanel
{ {
ConfigWizard *parent; ConfigWizard *parent;
const wxString shortname; const wxString shortname;
wxBoxSizer *content; 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; } void append_text(wxString text);
ConfigWizardPage* page_next() const { return p_next; } void append_spacer(int space);
ConfigWizardPage* chain(ConfigWizardPage *page);
template<class T> ConfigWizard::priv *wizard_p() const { return parent->p.get(); }
void append(T *thing, int proportion = 0, int flag = wxEXPAND|wxTOP|wxBOTTOM, int border = 10)
{
content->Add(thing, proportion, flag, border);
}
void append_text(wxString text); virtual void apply_custom_config(DynamicPrintConfig &config) {}
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;
}; };
struct PageWelcome: ConfigWizardPage struct PageWelcome: ConfigWizardPage
{ {
PrinterPicker *printer_picker; wxCheckBox *cbox_reset;
wxPanel *others_buttons;
wxCheckBox *cbox_reset;
PageWelcome(ConfigWizard *parent, bool check_first_variant); PageWelcome(ConfigWizard *parent);
virtual wxPanel* extra_buttons() { return others_buttons; } bool reset_user_profile() const { return cbox_reset != nullptr ? cbox_reset->GetValue() : false; }
virtual void on_page_set(); };
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 struct PageUpdate: ConfigWizardPage
{ {
bool version_check; bool version_check;
bool preset_update; bool preset_update;
PageUpdate(ConfigWizard *parent); PageUpdate(ConfigWizard *parent);
}; };
struct PageVendors: ConfigWizardPage 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_vendor_pick(size_t i);
void on_variant_checked();
}; };
struct PageFirmware: ConfigWizardPage struct PageFirmware: ConfigWizardPage
{ {
const ConfigOptionDef &gcode_opt; const ConfigOptionDef &gcode_opt;
wxChoice *gcode_picker; wxChoice *gcode_picker;
PageFirmware(ConfigWizard *parent); PageFirmware(ConfigWizard *parent);
virtual void apply_custom_config(DynamicPrintConfig &config); virtual void apply_custom_config(DynamicPrintConfig &config);
}; };
struct PageBedShape: ConfigWizardPage struct PageBedShape: ConfigWizardPage
{ {
BedShapePanel *shape_panel; BedShapePanel *shape_panel;
PageBedShape(ConfigWizard *parent); PageBedShape(ConfigWizard *parent);
virtual void apply_custom_config(DynamicPrintConfig &config); virtual void apply_custom_config(DynamicPrintConfig &config);
}; };
struct PageDiameters: ConfigWizardPage struct PageDiameters: ConfigWizardPage
{ {
wxSpinCtrlDouble *spin_nozzle; wxSpinCtrlDouble *spin_nozzle;
wxSpinCtrlDouble *spin_filam; wxSpinCtrlDouble *spin_filam;
PageDiameters(ConfigWizard *parent); PageDiameters(ConfigWizard *parent);
virtual void apply_custom_config(DynamicPrintConfig &config); virtual void apply_custom_config(DynamicPrintConfig &config);
}; };
struct PageTemperatures: ConfigWizardPage struct PageTemperatures: ConfigWizardPage
{ {
wxSpinCtrlDouble *spin_extr; wxSpinCtrlDouble *spin_extr;
wxSpinCtrlDouble *spin_bed; wxSpinCtrlDouble *spin_bed;
PageTemperatures(ConfigWizard *parent); PageTemperatures(ConfigWizard *parent);
virtual void apply_custom_config(DynamicPrintConfig &config); virtual void apply_custom_config(DynamicPrintConfig &config);
}; };
class ConfigWizardIndex: public wxPanel class ConfigWizardIndex: public wxPanel
{ {
public: 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: private:
const wxBitmap bg; struct Item
const wxBitmap bullet_black; {
const wxBitmap bullet_blue; wxString label;
const wxBitmap bullet_white; unsigned indent;
int text_height; ConfigWizardPage *page; // nullptr page => label-only item
std::vector<wxString> items; bool operator==(ConfigWizardPage *page) const { return this->page == page; }
std::vector<wxString>::const_iterator item_active; };
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 struct ConfigWizard::priv
{ {
ConfigWizard *q; ConfigWizard *q;
ConfigWizard::RunReason run_reason; ConfigWizard::RunReason run_reason;
AppConfig appconfig_vendors; AppConfig appconfig_vendors;
std::unordered_map<std::string, VendorProfile> vendors; std::unordered_map<std::string, VendorProfile> vendors;
std::unordered_map<std::string, std::string> vendors_rsrc; std::unordered_map<std::string, std::string> vendors_rsrc;
std::unique_ptr<DynamicPrintConfig> custom_config; std::unique_ptr<DynamicPrintConfig> custom_config;
wxScrolledWindow *hscroll = nullptr; wxScrolledWindow *hscroll = nullptr;
wxBoxSizer *hscroll_sizer = nullptr; wxBoxSizer *hscroll_sizer = nullptr;
wxBoxSizer *btnsizer = nullptr; wxBoxSizer *btnsizer = nullptr;
ConfigWizardPage *page_current = nullptr; ConfigWizardPage *page_current = nullptr;
ConfigWizardIndex *index = nullptr; ConfigWizardIndex *index = nullptr;
wxButton *btn_prev = nullptr; wxButton *btn_prev = nullptr;
wxButton *btn_next = nullptr; wxButton *btn_next = nullptr;
wxButton *btn_finish = nullptr; wxButton *btn_finish = nullptr;
wxButton *btn_cancel = nullptr; wxButton *btn_cancel = nullptr;
PageWelcome *page_welcome = nullptr; PageWelcome *page_welcome = nullptr;
PageUpdate *page_update = nullptr; PagePrinters *page_fff = nullptr;
PageVendors *page_vendors = nullptr; PagePrinters *page_msla = nullptr;
PageFirmware *page_firmware = nullptr; PageCustom *page_custom = nullptr;
PageBedShape *page_bed = nullptr; PageUpdate *page_update = nullptr;
PageDiameters *page_diams = nullptr; PageVendors *page_vendors = nullptr; // XXX: ?
PageTemperatures *page_temps = nullptr;
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(); priv(ConfigWizard *q) : q(q) {}
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);
void on_other_vendors(); void load_pages(bool custom_setup);
void on_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);
}; };

View file

@ -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 cnv_h = (float)canvas.get_canvas_size().get_height();
float height = _get_total_overlay_height(); float height = _get_total_overlay_height();
float top_y = 0.5f * (cnv_h - height) + m_overlay_border; float top_y = 0.5f * (cnv_h - height) + m_overlay_border;
for (GizmosMap::iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) for (GizmosMap::iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it)
{ {
if ((it->second == nullptr) || !it->second->is_selectable()) 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); 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) 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) if (!m_enabled)
return; return;
GizmosMap::const_iterator it = m_gizmos.find(SlaSupports); GizmosMap::const_iterator it = m_gizmos.find(SlaSupports);
if (it != m_gizmos.end()) if (it != m_gizmos.end())
#if ENABLE_SLA_SUPPORT_GIZMO_MOD
reinterpret_cast<GLGizmoSlaSupports*>(it->second)->set_sla_support_data(model_object, selection); 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) if (!m_enabled)
return; return false;
GizmosMap::const_iterator it = m_gizmos.find(SlaSupports); GizmosMap::const_iterator it = m_gizmos.find(SlaSupports);
if (it != m_gizmos.end()) 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) return false;
{
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);
} }
void GLCanvas3D::Gizmos::render_current_gizmo(const GLCanvas3D::Selection& selection) const 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(); reset();
@ -3763,6 +3783,9 @@ bool GLCanvas3D::WarningTexture::generate(const std::string& msg, const GLCanvas
void GLCanvas3D::WarningTexture::render(const GLCanvas3D& canvas) const 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)) if ((m_id > 0) && (m_original_width > 0) && (m_original_height > 0) && (m_width > 0) && (m_height > 0))
{ {
::glDisable(GL_DEPTH_TEST); ::glDisable(GL_DEPTH_TEST);
@ -4087,7 +4110,6 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas)
, m_apply_zoom_to_volumes_filter(false) , m_apply_zoom_to_volumes_filter(false)
, m_hover_volume_id(-1) , m_hover_volume_id(-1)
, m_toolbar_action_running(false) , m_toolbar_action_running(false)
, m_warning_texture_enabled(false)
, m_legend_texture_enabled(false) , m_legend_texture_enabled(false)
, m_picking_enabled(false) , m_picking_enabled(false)
, m_moving_enabled(false) , m_moving_enabled(false)
@ -4097,6 +4119,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas)
, m_moving(false) , m_moving(false)
, m_color_by("volume") , m_color_by("volume")
, m_reload_delayed(false) , m_reload_delayed(false)
, m_render_sla_auxiliaries(true)
#if !ENABLE_IMGUI #if !ENABLE_IMGUI
, m_external_gizmo_widgets_parent(nullptr) , m_external_gizmo_widgets_parent(nullptr)
#endif // not ENABLE_IMGUI #endif // not ENABLE_IMGUI
@ -4244,8 +4267,7 @@ void GLCanvas3D::reset_volumes()
m_dirty = true; m_dirty = true;
} }
enable_warning_texture(false); _set_warning_texture(WarningTexture::ObjectOutside, false);
_reset_warning_texture();
} }
int GLCanvas3D::check_volumes_outside_state() const int GLCanvas3D::check_volumes_outside_state() const
@ -4255,6 +4277,34 @@ int GLCanvas3D::check_volumes_outside_state() const
return (int)state; 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) void GLCanvas3D::set_config(const DynamicPrintConfig* config)
{ {
m_config = 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) void GLCanvas3D::enable_legend_texture(bool enable)
{ {
m_legend_texture_enabled = 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 // this position is used later into on_mouse() to drag the objects
m_mouse.scene_position = _mouse_to_3d(m_mouse.position.cast<int>()); m_mouse.scene_position = _mouse_to_3d(m_mouse.position.cast<int>());
_render_current_gizmo();
_render_selection_sidebar_hints(); _render_selection_sidebar_hints();
_render_current_gizmo();
#if ENABLE_SHOW_CAMERA_TARGET #if ENABLE_SHOW_CAMERA_TARGET
_render_camera_target(); _render_camera_target();
#endif // ENABLE_SHOW_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) if (!contained)
{ {
enable_warning_texture(true); _set_warning_texture(WarningTexture::ObjectOutside, true);
_generate_warning_texture(L("Detected object outside print volume"));
post_event(Event<bool>(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, state == ModelInstance::PVS_Fully_Outside)); post_event(Event<bool>(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, state == ModelInstance::PVS_Fully_Outside));
} }
else else
{ {
enable_warning_texture(false);
m_volumes.reset_outside_state(); 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())); post_event(Event<bool>(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, !m_model->objects.empty()));
} }
} }
else else
{ {
enable_warning_texture(false); _set_warning_texture(WarningTexture::ObjectOutside, false);
_reset_warning_texture();
post_event(Event<bool>(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, 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_SIZE, &GLCanvas3D::on_size, this);
m_canvas->Bind(wxEVT_IDLE, &GLCanvas3D::on_idle, this); m_canvas->Bind(wxEVT_IDLE, &GLCanvas3D::on_idle, this);
m_canvas->Bind(wxEVT_CHAR, &GLCanvas3D::on_char, 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_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this);
m_canvas->Bind(wxEVT_TIMER, &GLCanvas3D::on_timer, this); m_canvas->Bind(wxEVT_TIMER, &GLCanvas3D::on_timer, this);
m_canvas->Bind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, 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_SIZE, &GLCanvas3D::on_size, this);
m_canvas->Unbind(wxEVT_IDLE, &GLCanvas3D::on_idle, this); m_canvas->Unbind(wxEVT_IDLE, &GLCanvas3D::on_idle, this);
m_canvas->Unbind(wxEVT_CHAR, &GLCanvas3D::on_char, 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_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this);
m_canvas->Unbind(wxEVT_TIMER, &GLCanvas3D::on_timer, this); m_canvas->Unbind(wxEVT_TIMER, &GLCanvas3D::on_timer, this);
m_canvas->Unbind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this); m_canvas->Unbind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this);
@ -5187,7 +5231,12 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
switch (keyCode) { switch (keyCode) {
case 'a': case 'a':
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__ #ifdef __APPLE__
case WXK_BACK: // the low cost Apple solutions are not equipped with a Delete key, use Backspace instead. case WXK_BACK: // the low cost Apple solutions are not equipped with a Delete key, use Backspace instead.
#else /* __APPLE__ */ #else /* __APPLE__ */
@ -5208,7 +5257,12 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
#else /* __APPLE__ */ #else /* __APPLE__ */
case WXK_DELETE: case WXK_DELETE:
#endif /* __APPLE__ */ #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 '0': { select_view("iso"); break; }
case '1': { select_view("top"); break; } case '1': { select_view("top"); break; }
case '2': { select_view("bottom"); 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) void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
{ {
// Ignore the wheel events if the middle button is pressed. // 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 // Set focus in order to remove it from sidebar fields
if (m_canvas != nullptr) { if (m_canvas != nullptr) {
// Only set focus, if the top level window of this canvas is active. // 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()) while (p->GetParent())
p = p->GetParent(); p = p->GetParent();
auto *top_level_wnd = dynamic_cast<wxTopLevelWindow*>(p); auto *top_level_wnd = dynamic_cast<wxTopLevelWindow*>(p);
if (top_level_wnd && top_level_wnd->IsActive()) if (top_level_wnd && top_level_wnd->IsActive())
{
m_canvas->SetFocus(); 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(); m_mouse.set_start_position_2D_as_invalid();
//#endif //#endif
} }
@ -5385,22 +5456,16 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
m_dirty = true; 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) else if (!m_selection.is_empty() && gizmos_overlay_contains_mouse)
{ {
m_gizmos.update_on_off_state(*this, m_mouse.position, m_selection); m_gizmos.update_on_off_state(*this, m_mouse.position, m_selection);
_update_gizmos_data(); _update_gizmos_data();
m_dirty = true; 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()) else if (evt.LeftDown() && !m_selection.is_empty() && m_gizmos.grabber_contains_mouse())
{ {
_update_gizmos_data(); _update_gizmos_data();
@ -5416,9 +5481,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
m_dirty = true; m_dirty = true;
} }
else if ((selected_object_idx != -1) && m_gizmos.grabber_contains_mouse() && evt.RightDown()) { else if ((selected_object_idx != -1) && evt.RightDown() && m_gizmos.get_current_type() == Gizmos::SlaSupports && m_gizmos.mouse_event(SLAGizmoEventType::RightDown))
if (m_gizmos.get_current_type() == Gizmos::SlaSupports) {
m_gizmos.delete_current_grabber(); // event was taken care of by the SlaSupports gizmo
} }
else if (view_toolbar_contains_mouse != -1) else if (view_toolbar_contains_mouse != -1)
{ {
@ -5474,7 +5539,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
} }
// propagate event through callback // propagate event through callback
if (m_hover_volume_id != -1) if (m_hover_volume_id != -1)
{ {
if (evt.LeftDown() && m_moving_enabled && (m_mouse.drag.move_volume_idx == -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()) 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 // 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 // the context menu is already shown
m_mouse.position = Vec2d((double)pos(0), (double)pos(1));
render(); render();
if (m_hover_volume_id != -1) if (m_hover_volume_id != -1)
{ {
@ -5509,14 +5573,14 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT));
_update_gizmos_data(); _update_gizmos_data();
wxGetApp().obj_manipul()->update_settings_value(m_selection); wxGetApp().obj_manipul()->update_settings_value(m_selection);
// forces a frame render to update the view before the context menu is shown // // forces a frame render to update the view before the context menu is shown
render(); // render();
Vec2d logical_pos = pos.cast<double>(); Vec2d logical_pos = pos.cast<double>();
#if ENABLE_RETINA_GL #if ENABLE_RETINA_GL
const float factor = m_retina_helper->get_scale_factor(); const float factor = m_retina_helper->get_scale_factor();
logical_pos = logical_pos.cwiseQuotient(Vec2d(factor, factor)); logical_pos = logical_pos.cwiseQuotient(Vec2d(factor, factor));
#endif #endif // ENABLE_RETINA_GL
post_event(Vec2dEvent(EVT_GLCANVAS_RIGHT_CLICK, logical_pos)); 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 ENABLE_MOVE_MIN_THRESHOLD
if (!m_mouse.drag.move_requires_threshold) if (!m_mouse.drag.move_requires_threshold)
@ -5584,6 +5649,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
m_dirty = true; 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) else if (evt.Dragging() && !gizmos_overlay_contains_mouse)
{ {
m_mouse.dragging = true; m_mouse.dragging = true;
@ -5639,6 +5709,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
_stop_timer(); _stop_timer();
m_layers_editing.accept_changes(*this); 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) else if ((m_mouse.drag.move_volume_idx != -1) && m_mouse.dragging)
{ {
m_regenerate_volumes = false; 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. // of the scene with the background processing data should be performed.
post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); 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 // deselect and propagate event through callback
if (!evt.ShiftDown() && m_picking_enabled && !m_toolbar_action_running && !m_mouse.ignore_up_event) 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(); do_rotate();
break; 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: default:
break; break;
} }
@ -6143,7 +6210,6 @@ bool GLCanvas3D::_init_toolbar()
item.name = "add"; item.name = "add";
item.tooltip = GUI::L_str("Add...") + " [" + GUI::shortkey_ctrl_prefix() + "I]"; item.tooltip = GUI::L_str("Add...") + " [" + GUI::shortkey_ctrl_prefix() + "I]";
item.sprite_id = 0; item.sprite_id = 0;
item.is_toggable = false;
item.action_event = EVT_GLTOOLBAR_ADD; item.action_event = EVT_GLTOOLBAR_ADD;
if (!m_toolbar.add_item(item)) if (!m_toolbar.add_item(item))
return false; return false;
@ -6151,7 +6217,6 @@ bool GLCanvas3D::_init_toolbar()
item.name = "delete"; item.name = "delete";
item.tooltip = GUI::L_str("Delete") + " [Del]"; item.tooltip = GUI::L_str("Delete") + " [Del]";
item.sprite_id = 1; item.sprite_id = 1;
item.is_toggable = false;
item.action_event = EVT_GLTOOLBAR_DELETE; item.action_event = EVT_GLTOOLBAR_DELETE;
if (!m_toolbar.add_item(item)) if (!m_toolbar.add_item(item))
return false; return false;
@ -6159,7 +6224,6 @@ bool GLCanvas3D::_init_toolbar()
item.name = "deleteall"; item.name = "deleteall";
item.tooltip = GUI::L_str("Delete all") + " [" + GUI::shortkey_ctrl_prefix() + "Del]"; item.tooltip = GUI::L_str("Delete all") + " [" + GUI::shortkey_ctrl_prefix() + "Del]";
item.sprite_id = 2; item.sprite_id = 2;
item.is_toggable = false;
item.action_event = EVT_GLTOOLBAR_DELETE_ALL; item.action_event = EVT_GLTOOLBAR_DELETE_ALL;
if (!m_toolbar.add_item(item)) if (!m_toolbar.add_item(item))
return false; return false;
@ -6167,7 +6231,6 @@ bool GLCanvas3D::_init_toolbar()
item.name = "arrange"; item.name = "arrange";
item.tooltip = GUI::L_str("Arrange [A]"); item.tooltip = GUI::L_str("Arrange [A]");
item.sprite_id = 3; item.sprite_id = 3;
item.is_toggable = false;
item.action_event = EVT_GLTOOLBAR_ARRANGE; item.action_event = EVT_GLTOOLBAR_ARRANGE;
if (!m_toolbar.add_item(item)) if (!m_toolbar.add_item(item))
return false; return false;
@ -6178,7 +6241,6 @@ bool GLCanvas3D::_init_toolbar()
item.name = "more"; item.name = "more";
item.tooltip = GUI::L_str("Add instance [+]"); item.tooltip = GUI::L_str("Add instance [+]");
item.sprite_id = 4; item.sprite_id = 4;
item.is_toggable = false;
item.action_event = EVT_GLTOOLBAR_MORE; item.action_event = EVT_GLTOOLBAR_MORE;
if (!m_toolbar.add_item(item)) if (!m_toolbar.add_item(item))
return false; return false;
@ -6186,7 +6248,6 @@ bool GLCanvas3D::_init_toolbar()
item.name = "fewer"; item.name = "fewer";
item.tooltip = GUI::L_str("Remove instance [-]"); item.tooltip = GUI::L_str("Remove instance [-]");
item.sprite_id = 5; item.sprite_id = 5;
item.is_toggable = false;
item.action_event = EVT_GLTOOLBAR_FEWER; item.action_event = EVT_GLTOOLBAR_FEWER;
if (!m_toolbar.add_item(item)) if (!m_toolbar.add_item(item))
return false; return false;
@ -6197,7 +6258,6 @@ bool GLCanvas3D::_init_toolbar()
item.name = "splitobjects"; item.name = "splitobjects";
item.tooltip = GUI::L_str("Split to objects"); item.tooltip = GUI::L_str("Split to objects");
item.sprite_id = 6; item.sprite_id = 6;
item.is_toggable = false;
item.action_event = EVT_GLTOOLBAR_SPLIT_OBJECTS; item.action_event = EVT_GLTOOLBAR_SPLIT_OBJECTS;
if (!m_toolbar.add_item(item)) if (!m_toolbar.add_item(item))
return false; return false;
@ -6205,7 +6265,6 @@ bool GLCanvas3D::_init_toolbar()
item.name = "splitvolumes"; item.name = "splitvolumes";
item.tooltip = GUI::L_str("Split to parts"); item.tooltip = GUI::L_str("Split to parts");
item.sprite_id = 8; item.sprite_id = 8;
item.is_toggable = false;
item.action_event = EVT_GLTOOLBAR_SPLIT_VOLUMES; item.action_event = EVT_GLTOOLBAR_SPLIT_VOLUMES;
if (!m_toolbar.add_item(item)) if (!m_toolbar.add_item(item))
return false; return false;
@ -6586,7 +6645,9 @@ void GLCanvas3D::_render_objects() const
m_layers_editing.render_volumes(*this, this->m_volumes); m_layers_editing.render_volumes(*this, this->m_volumes);
} else { } else {
// do not cull backfaces to show broken geometry, if any // 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_volumes.render_VBOs(GLVolumeCollection::Transparent, false);
m_shader.stop_using(); m_shader.stop_using();
@ -6602,7 +6663,9 @@ void GLCanvas3D::_render_objects() const
} }
// do not cull backfaces to show broken geometry, if any // 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); m_volumes.render_legacy(GLVolumeCollection::Transparent, false);
if (m_use_clipping_planes) if (m_use_clipping_planes)
@ -6636,9 +6699,6 @@ void GLCanvas3D::_render_selection_center() const
void GLCanvas3D::_render_warning_texture() const void GLCanvas3D::_render_warning_texture() const
{ {
if (!m_warning_texture_enabled)
return;
m_warning_texture.render(*this); m_warning_texture.render(*this);
} }
@ -6683,7 +6743,7 @@ void GLCanvas3D::_render_volumes(bool fake_colors) const
::glColor4fv(vol->render_color); ::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(); vol->render();
++volume_id; ++volume_id;
@ -6834,20 +6894,20 @@ void GLCanvas3D::_render_sla_slices() const
{ {
// calculate model bottom cap // calculate model bottom cap
if (bottom_obj_triangles.empty() && (it_min_z->second.model_slices_idx < model_slices.size())) 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 // calculate support bottom cap
if (bottom_sup_triangles.empty() && (it_min_z->second.support_slices_idx < support_slices.size())) 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()) if (it_max_z != index.end())
{ {
// calculate model top cap // calculate model top cap
if (top_obj_triangles.empty() && (it_max_z->second.model_slices_idx < model_slices.size())) 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 // calculate support top cap
if (top_sup_triangles.empty() && (it_max_z->second.support_slices_idx < support_slices.size())) 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()); m_gizmos.set_rotation(Vec3d::Zero());
ModelObject* model_object = m_model->objects[m_selection.get_object_idx()]; ModelObject* model_object = m_model->objects[m_selection.get_object_idx()];
m_gizmos.set_flattening_data(model_object); m_gizmos.set_flattening_data(model_object);
#if ENABLE_SLA_SUPPORT_GIZMO_MOD
m_gizmos.set_sla_support_data(model_object, m_selection); 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()) 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_scale(volume->get_volume_scaling_factor());
m_gizmos.set_rotation(Vec3d::Zero()); m_gizmos.set_rotation(Vec3d::Zero());
m_gizmos.set_flattening_data(nullptr); m_gizmos.set_flattening_data(nullptr);
#if ENABLE_SLA_SUPPORT_GIZMO_MOD
m_gizmos.set_sla_support_data(nullptr, m_selection); m_gizmos.set_sla_support_data(nullptr, m_selection);
#else
m_gizmos.set_model_object_ptr(nullptr);
#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD
} }
else else
{ {
m_gizmos.set_scale(Vec3d::Ones()); m_gizmos.set_scale(Vec3d::Ones());
m_gizmos.set_rotation(Vec3d::Zero()); 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); 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); 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() void GLCanvas3D::_show_warning_texture_if_needed()
{ {
_set_current(); _set_current();
_set_warning_texture(WarningTexture::ToolpathOutside, _is_any_volume_outside());
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();
}
} }
std::vector<float> GLCanvas3D::_parse_colors(const std::vector<std::string>& colors) 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()); 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); m_warning_texture.activate(warning, state, *this);
}
void GLCanvas3D::_reset_warning_texture()
{
m_warning_texture.reset();
} }
bool GLCanvas3D::_is_any_volume_outside() const bool GLCanvas3D::_is_any_volume_outside() const

View file

@ -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_UPDATE_GEOMETRY, Vec3dsEvent<2>);
wxDECLARE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent); 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 class GLCanvas3D
{ {
struct GCodePreviewVolumeIndex struct GCodePreviewVolumeIndex
@ -788,12 +800,8 @@ private:
void set_flattening_data(const ModelObject* model_object); 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); void set_sla_support_data(ModelObject* model_object, const GLCanvas3D::Selection& selection);
#else bool mouse_event(SLAGizmoEventType action, const Vec2d& mouse_position = Vec2d::Zero(), bool shift_down = false);
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 = false); void delete_current_grabber(bool delete_all = false);
void render_current_gizmo(const Selection& selection) const; void render_current_gizmo(const Selection& selection) const;
@ -835,18 +843,32 @@ private:
class WarningTexture : public GUI::GLTexture 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 Background_Color[3];
static const unsigned char Opacity; static const unsigned char Opacity;
int m_original_width; int m_original_width;
int m_original_height; int m_original_height;
public: // Information about which warnings are currently active.
WarningTexture(); std::vector<Warning> m_warnings;
bool generate(const std::string& msg, const GLCanvas3D& canvas); // Generates the texture with given text.
bool _generate(const std::string& msg, const GLCanvas3D& canvas);
void render(const GLCanvas3D& canvas) const;
}; };
class LegendTexture : public GUI::GLTexture class LegendTexture : public GUI::GLTexture
@ -923,6 +945,7 @@ private:
bool m_multisample_allowed; bool m_multisample_allowed;
bool m_regenerate_volumes; bool m_regenerate_volumes;
bool m_moving; bool m_moving;
bool m_render_sla_auxiliaries;
std::string m_color_by; std::string m_color_by;
@ -954,6 +977,9 @@ public:
void reset_volumes(); void reset_volumes();
int check_volumes_outside_state() const; 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_config(const DynamicPrintConfig* config);
void set_process(BackgroundSlicingProcess* process); void set_process(BackgroundSlicingProcess* process);
void set_model(Model* model); void set_model(Model* model);
@ -991,7 +1017,6 @@ public:
bool is_reload_delayed() const; bool is_reload_delayed() const;
void enable_layers_editing(bool enable); void enable_layers_editing(bool enable);
void enable_warning_texture(bool enable);
void enable_legend_texture(bool enable); void enable_legend_texture(bool enable);
void enable_picking(bool enable); void enable_picking(bool enable);
void enable_moving(bool enable); void enable_moving(bool enable);
@ -1050,6 +1075,7 @@ public:
void on_size(wxSizeEvent& evt); void on_size(wxSizeEvent& evt);
void on_idle(wxIdleEvent& evt); void on_idle(wxIdleEvent& evt);
void on_char(wxKeyEvent& evt); void on_char(wxKeyEvent& evt);
void on_key_up(wxKeyEvent& evt);
void on_mouse_wheel(wxMouseEvent& evt); void on_mouse_wheel(wxMouseEvent& evt);
void on_timer(wxTimerEvent& evt); void on_timer(wxTimerEvent& evt);
void on_mouse(wxMouseEvent& 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); void _generate_legend_texture(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
// generates a warning texture containing the given message // generates a warning texture containing the given message
void _generate_warning_texture(const std::string& msg); void _set_warning_texture(WarningTexture::Warning warning, bool state);
void _reset_warning_texture();
bool _is_any_volume_outside() const; bool _is_any_volume_outside() const;

View file

@ -7,7 +7,7 @@
#include "libslic3r/libslic3r.h" #include "libslic3r/libslic3r.h"
#include "libslic3r/Geometry.hpp" #include "libslic3r/Geometry.hpp"
#include "libslic3r/Utils.hpp" #include "libslic3r/Utils.hpp"
#include "libslic3r/SLA/SLASupportTree.hpp" #include "libslic3r/SLA/SLACommon.hpp"
#include "libslic3r/SLAPrint.hpp" #include "libslic3r/SLAPrint.hpp"
#include <cstdio> #include <cstdio>
@ -1741,28 +1741,20 @@ Vec3d GLGizmoFlatten::get_flattening_normal() const
} }
GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent) GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent)
#if ENABLE_SLA_SUPPORT_GIZMO_MOD
: GLGizmoBase(parent), m_starting_center(Vec3d::Zero()), m_quadric(nullptr) : 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(); m_quadric = ::gluNewQuadric();
if (m_quadric != nullptr) if (m_quadric != nullptr)
// using GLU_FILL does not work when the instance's transformation // using GLU_FILL does not work when the instance's transformation
// contains mirroring (normals are reverted) // contains mirroring (normals are reverted)
::gluQuadricDrawStyle(m_quadric, GLU_FILL); ::gluQuadricDrawStyle(m_quadric, GLU_FILL);
#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD
} }
#if ENABLE_SLA_SUPPORT_GIZMO_MOD
GLGizmoSlaSupports::~GLGizmoSlaSupports() GLGizmoSlaSupports::~GLGizmoSlaSupports()
{ {
if (m_quadric != nullptr) if (m_quadric != nullptr)
::gluDeleteQuadric(m_quadric); ::gluDeleteQuadric(m_quadric);
} }
#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD
bool GLGizmoSlaSupports::on_init() bool GLGizmoSlaSupports::on_init()
{ {
@ -1782,7 +1774,6 @@ bool GLGizmoSlaSupports::on_init()
return true; return true;
} }
#if ENABLE_SLA_SUPPORT_GIZMO_MOD
void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const GLCanvas3D::Selection& selection) void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const GLCanvas3D::Selection& selection)
{ {
m_starting_center = Vec3d::Zero(); m_starting_center = Vec3d::Zero();
@ -1791,208 +1782,162 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const G
if (selection.is_empty()) if (selection.is_empty())
m_old_instance_id = -1; 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()) if (is_mesh_update_necessary())
update_mesh(); update_mesh();
// If there are no points, let's ask the backend if it calculated some. // 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)) { if (m_editing_mode_cache.empty() && m_parent.sla_print()->is_step_done(slaposSupportPoints))
for (const SLAPrintObject* po : m_parent.sla_print()->objects()) { get_data_from_backend();
if (po->model_object()->id() == model_object->id()) {
const Eigen::MatrixXd& points = po->get_support_points(); if (m_model_object != m_old_model_object)
for (unsigned int i=0; i<points.rows();++i) m_editing_mode = false;
model_object->sla_support_points.push_back(Vec3f(po->trafo().inverse().cast<float>() * Vec3f(points(i,0), points(i,1), points(i,2)))); if (m_state == On) {
break; 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 void GLGizmoSlaSupports::on_render(const GLCanvas3D::Selection& selection) const
{ {
::glEnable(GL_BLEND); ::glEnable(GL_BLEND);
::glEnable(GL_DEPTH_TEST); ::glEnable(GL_DEPTH_TEST);
#if !ENABLE_SLA_SUPPORT_GIZMO_MOD render_points(selection, false);
// the dragged_offset is a vector measuring where was the object moved render_selection_rectangle();
// 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
#if !ENABLE_IMGUI #if !ENABLE_IMGUI
render_tooltip_texture(); render_tooltip_texture();
#endif // not ENABLE_IMGUI #endif // not ENABLE_IMGUI
::glDisable(GL_BLEND); ::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 void GLGizmoSlaSupports::on_render_for_picking(const GLCanvas3D::Selection& selection) const
{ {
::glEnable(GL_DEPTH_TEST); ::glEnable(GL_DEPTH_TEST);
for (unsigned int i=0; i<m_grabbers.size(); ++i) {
m_grabbers[i].color[0] = 1.0f; render_points(selection, true);
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
} }
#if ENABLE_SLA_SUPPORT_GIZMO_MOD void GLGizmoSlaSupports::render_points(const GLCanvas3D::Selection& selection, bool picking) const
void GLGizmoSlaSupports::render_grabbers(const GLCanvas3D::Selection& selection, bool picking) const
{ {
if (m_quadric == nullptr) if (m_quadric == nullptr || !selection.is_from_single_instance())
return; 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) if (!picking)
::glEnable(GL_LIGHTING); ::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]; 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 const sla::SupportPoint& support_point = m_editing_mode_cache[i].first;
// is not scaled with the object (as it would be if rendered with current gl matrix). const bool& point_selected = m_editing_mode_cache[i].second;
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;
if (!picking && (m_hover_id == i)) // First decide about the color of the point.
{ if (picking) {
render_color[0] = 1.0f - m_grabbers[i].color[0]; render_color[0] = 1.0f;
render_color[1] = 1.0f - m_grabbers[i].color[1]; render_color[1] = 1.0f;
render_color[2] = 1.0f - m_grabbers[i].color[2]; 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); ::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(); ::glPushMatrix();
::glLoadIdentity(); ::glTranslated(support_point.pos(0), support_point.pos(1), support_point.pos(2));
::glTranslated(grabber_world_position(0), grabber_world_position(1), grabber_world_position(2) + z_shift); ::glMultMatrixd(instance_scaling_matrix_inverse.data());
const float diameter = 0.8f; ::gluSphere(m_quadric, m_editing_mode_cache[i].first.head_front_radius * RenderPointScale, 64, 36);
::gluSphere(m_quadric, diameter/2.f, 64, 36);
::glPopMatrix(); ::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) if (!picking)
::glDisable(GL_LIGHTING); ::glDisable(GL_LIGHTING);
::glPopMatrix(); ::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 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(); 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)) //if (m_state != On || !m_model_object || m_model_object->instances.empty() || ! m_instance_matrix.isApprox(m_source_data.matrix))
// return false; // return false;
@ -2027,27 +1972,14 @@ void GLGizmoSlaSupports::update_mesh()
m_AABB = igl::AABB<Eigen::MatrixXf,3>(); m_AABB = igl::AABB<Eigen::MatrixXf,3>();
m_AABB.init(m_V, m_F); m_AABB.init(m_V, m_F);
#if !ENABLE_SLA_SUPPORT_GIZMO_MOD // we'll now reload support points (selection might have changed):
m_source_data.matrix = m_instance_matrix; editing_mode_reload_cache();
#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>();
}
} }
Vec3f GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos) 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 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) if (m_V.size() == 0)
#else
if (m_V.size() == 0 || is_mesh_update_necessary())
#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD
update_mesh(); update_mesh();
Eigen::Matrix<GLint, 4, 1, Eigen::DontAlign> viewport; Eigen::Matrix<GLint, 4, 1, Eigen::DontAlign> viewport;
@ -2064,20 +1996,15 @@ Vec3f GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos)
igl::Hit hit; igl::Hit hit;
#if ENABLE_SLA_SUPPORT_GIZMO_MOD
const GLCanvas3D::Selection& selection = m_parent.get_selection(); const GLCanvas3D::Selection& selection = m_parent.get_selection();
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
double z_offset = volume->get_sla_shift_z(); 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; point1(2) -= z_offset;
point2(2) -= z_offset; point2(2) -= z_offset;
#if ENABLE_SLA_SUPPORT_GIZMO_MOD
Transform3d inv = volume->get_instance_transformation().get_matrix().inverse(); 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; point1 = inv * point1;
point2 = inv * point2; 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)); 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 if (!m_editing_mode)
int instance_id = m_parent.get_selection().get_instance_idx(); return false;
if (m_old_instance_id != instance_id)
{ // left down - show the selection rectangle:
bool something_selected = (m_old_instance_id != -1); if (action == SLAGizmoEventType::LeftDown && shift_down) {
m_old_instance_id = instance_id; if (m_hover_id == -1) {
if (something_selected) m_selection_rectangle_active = true;
return; 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; // dragging the selection rectangle:
try { if (action == SLAGizmoEventType::Dragging && m_selection_rectangle_active) {
new_pos = unproject_on_mesh(mouse_position); // this can throw - we don't want to create a new grabber in that case m_selection_rectangle_end_corner = mouse_position;
return true;
} }
catch (...) { return; }
m_grabbers.push_back(Grabber()); // mouse up without selection rectangle - place point on the mesh:
m_grabbers.back().center = new_pos.cast<double>(); if (action == SLAGizmoEventType::LeftUp && !m_selection_rectangle_active && !shift_down) {
m_model_object->sla_support_points.push_back(new_pos); if (m_ignore_up_event) {
m_ignore_up_event = false;
return false;
}
// This should trigger the support generation int instance_id = m_parent.get_selection().get_instance_idx();
// wxGetApp().plater()->reslice(); 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) { if (!m_editing_mode)
m_grabbers.clear(); return;
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;
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 // This should trigger the support generation
// wxGetApp().plater()->reslice(); // 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) 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; Vec3f new_pos;
try { try {
new_pos = unproject_on_mesh(Vec2d((*data.mouse_pos)(0), (*data.mouse_pos)(1))); new_pos = unproject_on_mesh(Vec2d((*data.mouse_pos)(0), (*data.mouse_pos)(1)));
} }
catch (...) { return; } catch (...) { return; }
m_grabbers[m_hover_id].center = new_pos.cast<double>(); m_editing_mode_cache[m_hover_id].first.pos = new_pos;
m_model_object->sla_support_points[m_hover_id] = 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. // Do not update immediately, wait until the mouse is released.
// m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); // 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: RENDER_AGAIN:
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
m_imgui->set_next_window_bg_alpha(0.5f); 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); 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 force_refresh = false;
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 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(); m_imgui->end();
if (remove_all_clicked) { if (m_editing_mode != old_editing_state) { // user just toggled between editing/non-editing mode
delete_current_grabber(true); 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) { if (first_run) {
first_run = false; first_run = false;
goto RENDER_AGAIN; goto RENDER_AGAIN;
} }
} }
if (remove_all_clicked || generate) { if (force_refresh)
m_parent.reload_scene(true); m_parent.reload_scene(true);
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
}
} }
#endif // ENABLE_IMGUI #endif // ENABLE_IMGUI
bool GLGizmoSlaSupports::on_is_activable(const GLCanvas3D::Selection& selection) const bool GLGizmoSlaSupports::on_is_activable(const GLCanvas3D::Selection& selection) const
{ {
return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA
&& selection.is_from_single_instance(); || !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 bool GLGizmoSlaSupports::on_is_selectable() const
@ -2239,6 +2400,105 @@ std::string GLGizmoSlaSupports::on_get_name() const
return L("SLA Support Points [L]"); 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 // GLGizmoCut

View file

@ -437,31 +437,26 @@ protected:
} }
}; };
#define SLAGIZMO_IMGUI_MODAL 0
class GLGizmoSlaSupports : public GLGizmoBase class GLGizmoSlaSupports : public GLGizmoBase
{ {
private: private:
ModelObject* m_model_object = nullptr; ModelObject* m_model_object = nullptr;
#if ENABLE_SLA_SUPPORT_GIZMO_MOD
ModelObject* m_old_model_object = nullptr; ModelObject* m_old_model_object = nullptr;
int m_active_instance = -1;
int m_old_instance_id = -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); Vec3f unproject_on_mesh(const Vec2d& mouse_pos);
#if ENABLE_SLA_SUPPORT_GIZMO_MOD const float RenderPointScale = 1.f;
GLUquadricObj* m_quadric;
#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD
GLUquadricObj* m_quadric;
Eigen::MatrixXf m_V; // vertices Eigen::MatrixXf m_V; // vertices
Eigen::MatrixXi m_F; // facets indices Eigen::MatrixXi m_F; // facets indices
igl::AABB<Eigen::MatrixXf,3> m_AABB; igl::AABB<Eigen::MatrixXf,3> m_AABB;
struct SourceDataSummary { struct SourceDataSummary {
#if !ENABLE_SLA_SUPPORT_GIZMO_MOD
BoundingBoxf3 bounding_box;
Transform3d matrix;
#endif // !ENABLE_SLA_SUPPORT_GIZMO_MOD
Vec3d mesh_first_point; Vec3d mesh_first_point;
}; };
@ -472,14 +467,10 @@ private:
public: public:
explicit GLGizmoSlaSupports(GLCanvas3D& parent); explicit GLGizmoSlaSupports(GLCanvas3D& parent);
#if ENABLE_SLA_SUPPORT_GIZMO_MOD
virtual ~GLGizmoSlaSupports(); virtual ~GLGizmoSlaSupports();
void set_sla_support_data(ModelObject* model_object, const GLCanvas3D::Selection& selection); void set_sla_support_data(ModelObject* model_object, const GLCanvas3D::Selection& selection);
#else bool mouse_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down);
void set_model_object_ptr(ModelObject* model_object); void delete_selected_points();
#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD
void clicked_on_object(const Vec2d& mouse_position);
void delete_current_grabber(bool delete_all);
private: private:
bool on_init(); bool on_init();
@ -487,11 +478,8 @@ private:
virtual void on_render(const GLCanvas3D::Selection& selection) const; virtual void on_render(const GLCanvas3D::Selection& selection) const;
virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const; virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const;
#if ENABLE_SLA_SUPPORT_GIZMO_MOD void render_selection_rectangle() const;
void render_grabbers(const GLCanvas3D::Selection& selection, bool picking = false) const; void render_points(const GLCanvas3D::Selection& selection, bool picking = false) const;
#else
void render_grabbers(bool picking = false) const;
#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD
bool is_mesh_update_necessary() const; bool is_mesh_update_necessary() const;
void update_mesh(); void update_mesh();
@ -501,12 +489,41 @@ private:
mutable GLTexture m_reset_texture; mutable GLTexture m_reset_texture;
#endif // not ENABLE_IMGUI #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: protected:
void on_set_state() override { void on_set_state() override;
if (m_state == On && is_mesh_update_necessary()) { void on_start_dragging(const GLCanvas3D::Selection& selection) override;
update_mesh();
}
}
#if ENABLE_IMGUI #if ENABLE_IMGUI
virtual void on_render_input_window(float x, float y, const GLCanvas3D::Selection& selection) override; virtual void on_render_input_window(float x, float y, const GLCanvas3D::Selection& selection) override;

View file

@ -355,21 +355,6 @@ boost::filesystem::path into_path(const wxString &str)
return boost::filesystem::path(str.wx_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() void about()
{ {
AboutDialog dlg; AboutDialog dlg;

View file

@ -71,9 +71,6 @@ wxString from_path(const boost::filesystem::path &path);
// boost path from wxString // boost path from wxString
boost::filesystem::path into_path(const wxString &str); 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 // Display an About dialog
extern void about(); extern void about();
// Ask the destop to open the datadir using the default file explorer. // Ask the destop to open the datadir using the default file explorer.

View file

@ -3,8 +3,10 @@
#include "GUI_ObjectManipulation.hpp" #include "GUI_ObjectManipulation.hpp"
#include "I18N.hpp" #include "I18N.hpp"
#include <exception>
#include <boost/lexical_cast.hpp> #include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <boost/log/trivial.hpp>
#include <wx/stdpaths.h> #include <wx/stdpaths.h>
#include <wx/imagpng.h> #include <wx/imagpng.h>
@ -125,6 +127,10 @@ bool GUI_App::OnInit()
app_config->save(); app_config->save();
preset_updater = new PresetUpdater(); 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(); load_language();
@ -181,7 +187,6 @@ bool GUI_App::OnInit()
mainframe->Close(); mainframe->Close();
} catch (const std::exception &ex) { } catch (const std::exception &ex) {
show_error(nullptr, ex.what()); show_error(nullptr, ex.what());
mainframe->Close();
} }
}); });
@ -351,21 +356,10 @@ void GUI_App::persist_window_geometry(wxTopLevelWindow *window)
}); });
window_pos_restore(window, name); window_pos_restore(window, name);
#ifdef _WIN32
// On windows, the wxEVT_SHOW is not received if the window is created maximized on_window_geometry(window, [=]() {
// cf. https://groups.google.com/forum/#!topic/wx-users/c7ntMt6piRI window_pos_sanitize(window);
// 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();
}); });
#endif
} }
void GUI_App::load_project(wxWindow *parent, wxString& input_file) 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. //FIXME This is a temporary workaround, the correct solution is to switch to "C" locale during file import / export only.
wxSetlocale(LC_NUMERIC, "C"); wxSetlocale(LC_NUMERIC, "C");
Preset::update_suffix_modified(); Preset::update_suffix_modified();
m_imgui->set_language(m_wxLocale->GetCanonicalName().ToUTF8().data());
return true; return true;
} }
return false; 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. //FIXME This is a temporary workaround, the correct solution is to switch to "C" locale during file import / export only.
wxSetlocale(LC_NUMERIC, "C"); wxSetlocale(LC_NUMERIC, "C");
Preset::update_suffix_modified(); Preset::update_suffix_modified();
m_imgui->set_language(m_wxLocale->GetCanonicalName().ToUTF8().data());
return true; 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 + 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 + ConfigMenuModeAdvanced, _(L("Advanced")), _(L("Advanced View Mode")));
mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeExpert, _(L("Expert")), _(L("Expert 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->AppendSubMenu(mode_menu, _(L("Mode")), _(L("Slic3r View Mode")));
local_menu->AppendSeparator(); local_menu->AppendSeparator();
local_menu->Append(config_id_base + ConfigMenuLanguage, _(L("Change Application &Language"))); 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__ #ifdef __APPLE__
// wxWidgets override to get an event on open files. // wxWidgets override to get an event on open files.
void GUI_App::MacOpenFiles(const wxArrayString &fileNames) void GUI_App::MacOpenFiles(const wxArrayString &fileNames)

View file

@ -137,6 +137,8 @@ public:
bool checked_tab(Tab* tab); bool checked_tab(Tab* tab);
void load_current_presets(); void load_current_presets();
virtual bool OnExceptionInMainLoop();
#ifdef __APPLE__ #ifdef __APPLE__
// wxWidgets override to get an event on open files. // wxWidgets override to get an event on open files.
void MacOpenFiles(const wxArrayString &fileNames) override; void MacOpenFiles(const wxArrayString &fileNames) override;

View file

@ -26,6 +26,28 @@ wxTopLevelWindow* find_toplevel_parent(wxWindow *window)
return nullptr; 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) CheckboxFileDialog::ExtraPanel::ExtraPanel(wxWindow *parent)
: wxPanel(parent, wxID_ANY) : wxPanel(parent, wxID_ANY)

View file

@ -4,6 +4,7 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include <ostream> #include <ostream>
#include <functional>
#include <boost/optional.hpp> #include <boost/optional.hpp>
@ -24,6 +25,8 @@ namespace GUI {
wxTopLevelWindow* find_toplevel_parent(wxWindow *window); wxTopLevelWindow* find_toplevel_parent(wxWindow *window);
void on_window_geometry(wxTopLevelWindow *tlw, std::function<void()> callback);
class EventGuard class EventGuard
{ {

View file

@ -25,7 +25,8 @@ namespace GUI {
ImGuiWrapper::ImGuiWrapper() ImGuiWrapper::ImGuiWrapper()
: m_font_texture(0) : m_glyph_ranges(nullptr)
, m_font_texture(0)
, m_style_scaling(1.0) , m_style_scaling(1.0)
, m_mouse_buttons(0) , m_mouse_buttons(0)
, m_disabled(false) , m_disabled(false)
@ -49,6 +50,35 @@ bool ImGuiWrapper::init()
return true; 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) void ImGuiWrapper::set_display_size(float w, float h)
{ {
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
@ -125,6 +155,12 @@ bool ImGuiWrapper::button(const wxString &label)
return ImGui::Button(label_utf8.c_str()); 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) 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()); 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); 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) void ImGuiWrapper::disabled_begin(bool disabled)
{ {
wxCHECK_RET(!m_disabled, "ImGUI: Unbalanced disabled_begin() call"); wxCHECK_RET(!m_disabled, "ImGUI: Unbalanced disabled_begin() call");
@ -210,7 +268,7 @@ void ImGuiWrapper::init_default_font(float scaling)
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
io.Fonts->Clear(); 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) { if (font == nullptr) {
font = io.Fonts->AddFontDefault(); font = io.Fonts->AddFontDefault();
if (font == nullptr) { if (font == nullptr) {

View file

@ -20,6 +20,7 @@ class ImGuiWrapper
typedef std::map<std::string, ImFont*> FontsMap; typedef std::map<std::string, ImFont*> FontsMap;
FontsMap m_fonts; FontsMap m_fonts;
const ImWchar *m_glyph_ranges;
unsigned m_font_texture; unsigned m_font_texture;
float m_style_scaling; float m_style_scaling;
unsigned m_mouse_buttons; unsigned m_mouse_buttons;
@ -32,6 +33,7 @@ public:
bool init(); bool init();
void read_glsl_version(); void read_glsl_version();
void set_language(const std::string &language);
void set_display_size(float w, float h); void set_display_size(float w, float h);
void set_style_scaling(float scaling); void set_style_scaling(float scaling);
bool update_mouse_data(wxMouseEvent &evt); bool update_mouse_data(wxMouseEvent &evt);
@ -47,10 +49,12 @@ public:
void end(); void end();
bool button(const wxString &label); 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_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 input_vec3(const std::string &label, const Vec3d &value, float width, const std::string &format = "%.3f");
bool checkbox(const wxString &label, bool &value); bool checkbox(const wxString &label, bool &value);
void text(const wxString &label); 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_begin(bool disabled);
void disabled_end(); void disabled_end();
@ -59,6 +63,7 @@ public:
bool want_keyboard() const; bool want_keyboard() const;
bool want_text_input() const; bool want_text_input() const;
bool want_any_input() const; bool want_any_input() const;
private: private:
void init_default_font(float scaling); void init_default_font(float scaling);
void create_device_objects(); void create_device_objects();

View file

@ -105,6 +105,12 @@ wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAUL
event.Skip(); 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); wxGetApp().persist_window_geometry(this);
update_ui_from_settings(); // FIXME (?) update_ui_from_settings(); // FIXME (?)

View file

@ -12,7 +12,6 @@
#include <wx/sizer.h> #include <wx/sizer.h>
#include <wx/stattext.h> #include <wx/stattext.h>
#include <wx/notebook.h>
#include <wx/button.h> #include <wx/button.h>
#include <wx/bmpcbox.h> #include <wx/bmpcbox.h>
#include <wx/statbox.h> #include <wx/statbox.h>
@ -1008,6 +1007,7 @@ struct Plater::priv
std::atomic<bool> arranging; std::atomic<bool> arranging;
std::atomic<bool> rotoptimizing; std::atomic<bool> rotoptimizing;
bool delayed_scene_refresh; bool delayed_scene_refresh;
std::string delayed_error_message;
wxTimer background_process_timer; wxTimer background_process_timer;
@ -1993,6 +1993,8 @@ unsigned int Plater::priv::update_background_process(bool force_validation)
this->background_process_timer.Stop(); this->background_process_timer.Stop();
// Update the "out of print bed" state of ModelInstances. // Update the "out of print bed" state of ModelInstances.
this->update_print_volume_state(); 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. // Apply new config to the possibly running background task.
bool was_running = this->background_process.running(); bool was_running = this->background_process.running();
Print::ApplyStatus invalidated = this->background_process.apply(this->q->model(), wxGetApp().preset_bundle->full_config()); 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; return_state |= UPDATE_BACKGROUND_PROCESS_RESTART;
} else { } else {
// The print is not valid. // The print is not valid.
// The error returned from the Print needs to be translated into the local language. // Only show the error message immediately, if the top level parent of this window is active.
GUI::show_error(this->q, _(err)); 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; return_state |= UPDATE_BACKGROUND_PROCESS_INVALID;
} }
} }
@ -2277,6 +2289,10 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt)
break; 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 &) void Plater::priv::on_slicing_completed(wxCommandEvent &)
@ -3141,6 +3157,26 @@ void Plater::on_config_change(const DynamicPrintConfig &config)
this->p->schedule_background_process(); 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 const wxString& Plater::get_project_filename() const
{ {
return p->project_filename; return p->project_filename;

View file

@ -151,6 +151,8 @@ public:
void on_extruders_change(int extruders_count); void on_extruders_change(int extruders_count);
void on_config_change(const DynamicPrintConfig &config); void on_config_change(const DynamicPrintConfig &config);
// On activating the parent window.
void on_activate();
void update_object_menu(); void update_object_menu();

View file

@ -11,8 +11,10 @@
#include <Windows.h> #include <Windows.h>
#endif /* _MSC_VER */ #endif /* _MSC_VER */
#include <algorithm>
#include <fstream> #include <fstream>
#include <stdexcept> #include <stdexcept>
#include <unordered_map>
#include <boost/format.hpp> #include <boost/format.hpp>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.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); 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) VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem::path &path, bool load_all)
{ {
static const std::string printer_model_key = "printer_model:"; 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; VendorProfile::PrinterModel model;
model.id = section.first.substr(printer_model_key.size()); model.id = section.first.substr(printer_model_key.size());
model.name = section.second.get<std::string>("name", model.id); 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)) { 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; BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: Invalid printer technology field: `%2%`") % id % technology_field;
model.technology = ptFFF; 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 #if 0
// Remove SLA printers from the initial alpha. // Remove SLA printers from the initial alpha.
if (model.technology == ptSLA) if (model.technology == ptSLA)
@ -157,6 +179,20 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem
return res; 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. // Suffix to be added to a modified preset name in the combo box.
static std::string g_suffix_modified = " (modified)"; static std::string g_suffix_modified = " (modified)";
@ -422,9 +458,8 @@ const std::vector<std::string>& Preset::sla_print_options()
"support_critical_angle", "support_critical_angle",
"support_max_bridge_length", "support_max_bridge_length",
"support_object_elevation", "support_object_elevation",
"support_density_at_horizontal", "support_points_density_relative",
"support_density_at_45", "support_points_minimal_distance",
"support_minimal_z",
"pad_enable", "pad_enable",
"pad_wall_thickness", "pad_wall_thickness",
"pad_wall_height", "pad_wall_height",

View file

@ -54,13 +54,16 @@ public:
std::string id; std::string id;
std::string name; std::string name;
PrinterTechnology technology; PrinterTechnology technology;
std::string family;
std::vector<PrinterVariant> variants; std::vector<PrinterVariant> variants;
PrinterVariant* variant(const std::string &name) { PrinterVariant* variant(const std::string &name) {
for (auto &v : this->variants) for (auto &v : this->variants)
if (v.name == name) if (v.name == name)
return &v; return &v;
return nullptr; return nullptr;
} }
const PrinterVariant* variant(const std::string &name) const { return const_cast<PrinterModel*>(this)->variant(name); } const PrinterVariant* variant(const std::string &name) const { return const_cast<PrinterModel*>(this)->variant(name); }
}; };
std::vector<PrinterModel> models; 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); 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; } 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; }
bool operator==(const VendorProfile &rhs) const { return this->id == rhs.id; } bool operator==(const VendorProfile &rhs) const { return this->id == rhs.id; }

View file

@ -15,6 +15,7 @@
#include "GUI.hpp" #include "GUI.hpp"
#include "GUI_App.hpp" #include "GUI_App.hpp"
#include "AppConfig.hpp"
#include "MsgDialog.hpp" #include "MsgDialog.hpp"
#include "I18N.hpp" #include "I18N.hpp"
#include "../Utils/PrintHost.hpp" #include "../Utils/PrintHost.hpp"
@ -24,10 +25,12 @@ namespace fs = boost::filesystem;
namespace Slic3r { namespace Slic3r {
namespace GUI { namespace GUI {
static const char *CONFIG_KEY_PATH = "printhost_path";
static const char *CONFIG_KEY_PRINT = "printhost_print";
PrintHostSendDialog::PrintHostSendDialog(const fs::path &path) 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) : 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")))) , box_print(new wxCheckBox(this, wxID_ANY, _(L("Start printing after upload"))))
{ {
#ifdef __APPLE__ #ifdef __APPLE__
@ -44,11 +47,30 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path)
btn_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL)); 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()); 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(); 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 fs::path PrintHostSendDialog::filename() const
@ -61,6 +83,24 @@ bool PrintHostSendDialog::start_print() const
return box_print->GetValue(); 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); wxDEFINE_EVENT(EVT_PRINTHOST_PROGRESS, PrintHostQueueDialog::Event);

View file

@ -33,6 +33,7 @@ public:
boost::filesystem::path filename() const; boost::filesystem::path filename() const;
bool start_print() const; bool start_print() const;
virtual void EndModal(int ret) override;
private: private:
wxTextCtrl *txt_filename; wxTextCtrl *txt_filename;
wxCheckBox *box_print; wxCheckBox *box_print;

View file

@ -3,6 +3,8 @@
#include "3DScene.hpp" #include "3DScene.hpp"
#include "GUI.hpp" #include "GUI.hpp"
#include <string>
#include <wx/clipbrd.h> #include <wx/clipbrd.h>
#include <wx/platinfo.h> #include <wx/platinfo.h>

View file

@ -3210,9 +3210,8 @@ void TabSLAPrint::build()
optgroup->append_single_option_line("support_max_bridge_length"); optgroup->append_single_option_line("support_max_bridge_length");
optgroup = page->new_optgroup(_(L("Automatic generation"))); optgroup = page->new_optgroup(_(L("Automatic generation")));
optgroup->append_single_option_line("support_density_at_horizontal"); optgroup->append_single_option_line("support_points_density_relative");
optgroup->append_single_option_line("support_density_at_45"); optgroup->append_single_option_line("support_points_minimal_distance");
optgroup->append_single_option_line("support_minimal_z");
page = add_options_page(_(L("Pad")), "brick.png"); page = add_options_page(_(L("Pad")), "brick.png");
optgroup = page->new_optgroup(_(L("Pad"))); optgroup = page->new_optgroup(_(L("Pad")));

View file

@ -244,7 +244,7 @@ void Http::priv::http_perform()
::curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, static_cast<void*>(this)); ::curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, static_cast<void*>(this));
#endif #endif
::curl_easy_setopt(curl, CURLOPT_VERBOSE, get_logging_level() >= 4); ::curl_easy_setopt(curl, CURLOPT_VERBOSE, get_logging_level() >= 5);
if (headerlist != nullptr) { if (headerlist != nullptr) {
::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist); ::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);

View file

@ -13,7 +13,6 @@
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <wx/app.h> #include <wx/app.h>
#include <wx/event.h>
#include <wx/msgdlg.h> #include <wx/msgdlg.h>
#include "libslic3r/libslic3r.h" #include "libslic3r/libslic3r.h"
@ -90,9 +89,25 @@ struct Updates
std::vector<Update> 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 struct PresetUpdater::priv
{ {
const Semver ver_slic3r;
std::vector<Index> index_db; std::vector<Index> index_db;
bool enabled_version_check; bool enabled_version_check;
@ -122,12 +137,13 @@ struct PresetUpdater::priv
static void copy_file(const fs::path &from, const fs::path &to); static void copy_file(const fs::path &from, const fs::path &to);
}; };
PresetUpdater::priv::priv() : PresetUpdater::priv::priv()
had_config_update(false), : ver_slic3r(get_slic3r_version())
cache_path(fs::path(Slic3r::data_dir()) / "cache"), , had_config_update(false)
rsrc_path(fs::path(resources_dir()) / "profiles"), , cache_path(fs::path(Slic3r::data_dir()) / "cache")
vendor_path(fs::path(Slic3r::data_dir()) / "vendor"), , rsrc_path(fs::path(resources_dir()) / "profiles")
cancel(false) , vendor_path(fs::path(Slic3r::data_dir()) / "vendor")
, cancel(false)
{ {
set_download_prefs(GUI::wxGetApp().app_config); set_download_prefs(GUI::wxGetApp().app_config);
check_install_indices(); check_install_indices();
@ -209,11 +225,10 @@ void PresetUpdater::priv::sync_version() const
.on_complete([&](std::string body, unsigned /* http_status */) { .on_complete([&](std::string body, unsigned /* http_status */) {
boost::trim(body); boost::trim(body);
BOOST_LOG_TRIVIAL(info) << boost::format("Got Slic3rPE online version: `%1%`. Sending to GUI thread...") % 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); wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_VERSION_ONLINE);
// GUI::get_app()->QueueEvent(evt); evt->SetString(GUI::from_u8(body));
GUI::wxGetApp().app_config->set("version_online", body); GUI::wxGetApp().QueueEvent(evt);
GUI::wxGetApp().app_config->save();
}) })
.perform_sync(); .perform_sync();
} }
@ -260,7 +275,7 @@ void PresetUpdater::priv::sync_config(const std::set<VendorProfile> vendors)
continue; continue;
} }
if (new_index.version() < index.version()) { 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; continue;
} }
Slic3r::rename_file(idx_path_temp, idx_path); 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; BOOST_LOG_TRIVIAL(error) << boost::format("No recommended version for vendor: %1%, invalid index?") % vendor.name;
continue; continue;
} }
const auto recommended = recommended_it->config_version; const auto recommended = recommended_it->config_version;
BOOST_LOG_TRIVIAL(debug) << boost::format("Got index for vendor: %1%: current version: %2%, recommended version: %3%") 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()) { 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(); 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; 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 // 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; 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_str = app_config->get("version_online");
const auto ver_online = Semver::parse(ver_online_str); const auto ver_online = Semver::parse(ver_online_str);
const auto ver_online_seen = Semver::parse(app_config->get("version_online_seen")); 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) { if (ver_online) {
// Only display the notification if the version available online is newer AND if we haven't seen it before // 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)) { if (*ver_online > p->ver_slic3r && (! ver_online_seen || *ver_online_seen < *ver_online)) {
GUI::MsgUpdateSlic3r notification(*ver_slic3r, *ver_online); GUI::MsgUpdateSlic3r notification(p->ver_slic3r, *ver_online);
notification.ShowModal(); notification.ShowModal();
if (notification.disable_version_check()) { if (notification.disable_version_check()) {
app_config->set("version_check", "0"); app_config->set("version_check", "0");

View file

@ -4,6 +4,8 @@
#include <memory> #include <memory>
#include <vector> #include <vector>
#include <wx/event.h>
namespace Slic3r { namespace Slic3r {
@ -37,6 +39,8 @@ private:
std::unique_ptr<priv> p; std::unique_ptr<priv> p;
}; };
wxDECLARE_EVENT(EVT_SLIC3R_VERSION_ONLINE, wxCommandEvent);
} }
#endif #endif