mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-11 16:57:53 -06:00
Merge branch 'master' into lm_sla_supports_auto2
This commit is contained in:
commit
d154e75ad7
23 changed files with 810 additions and 473 deletions
|
@ -83,7 +83,7 @@ struct CoolingLine
|
||||||
struct PerExtruderAdjustments
|
struct PerExtruderAdjustments
|
||||||
{
|
{
|
||||||
// Calculate the total elapsed time per this extruder, adjusted for the slowdown.
|
// Calculate the total elapsed time per this extruder, adjusted for the slowdown.
|
||||||
float elapsed_time_total() {
|
float elapsed_time_total() const {
|
||||||
float time_total = 0.f;
|
float time_total = 0.f;
|
||||||
for (const CoolingLine &line : lines)
|
for (const CoolingLine &line : lines)
|
||||||
time_total += line.time;
|
time_total += line.time;
|
||||||
|
@ -91,7 +91,7 @@ struct PerExtruderAdjustments
|
||||||
}
|
}
|
||||||
// Calculate the total elapsed time when slowing down
|
// Calculate the total elapsed time when slowing down
|
||||||
// to the minimum extrusion feed rate defined for the current material.
|
// to the minimum extrusion feed rate defined for the current material.
|
||||||
float maximum_time_after_slowdown(bool slowdown_external_perimeters) {
|
float maximum_time_after_slowdown(bool slowdown_external_perimeters) const {
|
||||||
float time_total = 0.f;
|
float time_total = 0.f;
|
||||||
for (const CoolingLine &line : lines)
|
for (const CoolingLine &line : lines)
|
||||||
if (line.adjustable(slowdown_external_perimeters)) {
|
if (line.adjustable(slowdown_external_perimeters)) {
|
||||||
|
@ -104,7 +104,7 @@ struct PerExtruderAdjustments
|
||||||
return time_total;
|
return time_total;
|
||||||
}
|
}
|
||||||
// Calculate the adjustable part of the total time.
|
// Calculate the adjustable part of the total time.
|
||||||
float adjustable_time(bool slowdown_external_perimeters) {
|
float adjustable_time(bool slowdown_external_perimeters) const {
|
||||||
float time_total = 0.f;
|
float time_total = 0.f;
|
||||||
for (const CoolingLine &line : lines)
|
for (const CoolingLine &line : lines)
|
||||||
if (line.adjustable(slowdown_external_perimeters))
|
if (line.adjustable(slowdown_external_perimeters))
|
||||||
|
@ -112,7 +112,7 @@ struct PerExtruderAdjustments
|
||||||
return time_total;
|
return time_total;
|
||||||
}
|
}
|
||||||
// Calculate the non-adjustable part of the total time.
|
// Calculate the non-adjustable part of the total time.
|
||||||
float non_adjustable_time(bool slowdown_external_perimeters) {
|
float non_adjustable_time(bool slowdown_external_perimeters) const {
|
||||||
float time_total = 0.f;
|
float time_total = 0.f;
|
||||||
for (const CoolingLine &line : lines)
|
for (const CoolingLine &line : lines)
|
||||||
if (! line.adjustable(slowdown_external_perimeters))
|
if (! line.adjustable(slowdown_external_perimeters))
|
||||||
|
@ -169,7 +169,7 @@ struct PerExtruderAdjustments
|
||||||
// Calculate the maximum time stretch when slowing down to min_feedrate.
|
// Calculate the maximum time stretch when slowing down to min_feedrate.
|
||||||
// Slowdown to min_feedrate shall be allowed for this extruder's material.
|
// Slowdown to min_feedrate shall be allowed for this extruder's material.
|
||||||
// Used by non-proportional slow down.
|
// Used by non-proportional slow down.
|
||||||
float time_stretch_when_slowing_down_to_feedrate(float min_feedrate) {
|
float time_stretch_when_slowing_down_to_feedrate(float min_feedrate) const {
|
||||||
float time_stretch = 0.f;
|
float time_stretch = 0.f;
|
||||||
assert(this->min_print_speed < min_feedrate + EPSILON);
|
assert(this->min_print_speed < min_feedrate + EPSILON);
|
||||||
for (size_t i = 0; i < n_lines_adjustable; ++ i) {
|
for (size_t i = 0; i < n_lines_adjustable; ++ i) {
|
||||||
|
@ -221,6 +221,61 @@ struct PerExtruderAdjustments
|
||||||
size_t idx_line_end = 0;
|
size_t idx_line_end = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Calculate a new feedrate when slowing down by time_stretch for segments faster than min_feedrate.
|
||||||
|
// Used by non-proportional slow down.
|
||||||
|
float new_feedrate_to_reach_time_stretch(
|
||||||
|
std::vector<PerExtruderAdjustments*>::const_iterator it_begin, std::vector<PerExtruderAdjustments*>::const_iterator it_end,
|
||||||
|
float min_feedrate, float time_stretch, size_t max_iter = 20)
|
||||||
|
{
|
||||||
|
float new_feedrate = min_feedrate;
|
||||||
|
for (size_t iter = 0; iter < max_iter; ++ iter) {
|
||||||
|
float nomin = 0;
|
||||||
|
float denom = time_stretch;
|
||||||
|
for (auto it = it_begin; it != it_end; ++ it) {
|
||||||
|
assert((*it)->min_print_speed < min_feedrate + EPSILON);
|
||||||
|
for (size_t i = 0; i < (*it)->n_lines_adjustable; ++i) {
|
||||||
|
const CoolingLine &line = (*it)->lines[i];
|
||||||
|
if (line.feedrate > min_feedrate) {
|
||||||
|
nomin += line.time * line.feedrate;
|
||||||
|
denom += line.time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(denom > 0);
|
||||||
|
if (denom < 0)
|
||||||
|
return min_feedrate;
|
||||||
|
new_feedrate = nomin / denom;
|
||||||
|
assert(new_feedrate > min_feedrate - EPSILON);
|
||||||
|
if (new_feedrate < min_feedrate + EPSILON)
|
||||||
|
goto finished;
|
||||||
|
for (auto it = it_begin; it != it_end; ++ it)
|
||||||
|
for (size_t i = 0; i < (*it)->n_lines_adjustable; ++i) {
|
||||||
|
const CoolingLine &line = (*it)->lines[i];
|
||||||
|
if (line.feedrate > min_feedrate && line.feedrate < new_feedrate)
|
||||||
|
// Some of the line segments taken into account in the calculation of nomin / denom are now slower than new_feedrate.
|
||||||
|
// Re-run the calculation with a new min_feedrate limit.
|
||||||
|
goto not_finished_yet;
|
||||||
|
}
|
||||||
|
goto finished;
|
||||||
|
not_finished_yet:
|
||||||
|
min_feedrate = new_feedrate;
|
||||||
|
}
|
||||||
|
// Failed to find the new feedrate for the time_stretch.
|
||||||
|
|
||||||
|
finished:
|
||||||
|
// Test whether the time_stretch was achieved.
|
||||||
|
#ifndef NDEBUG
|
||||||
|
{
|
||||||
|
float time_stretch_final = 0.f;
|
||||||
|
for (auto it = it_begin; it != it_end; ++ it)
|
||||||
|
time_stretch_final += (*it)->time_stretch_when_slowing_down_to_feedrate(new_feedrate);
|
||||||
|
assert(std::abs(time_stretch - time_stretch_final) < EPSILON);
|
||||||
|
}
|
||||||
|
#endif /* NDEBUG */
|
||||||
|
|
||||||
|
return new_feedrate;
|
||||||
|
}
|
||||||
|
|
||||||
std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_id)
|
std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_id)
|
||||||
{
|
{
|
||||||
std::vector<PerExtruderAdjustments> per_extruder_adjustments = this->parse_layer_gcode(gcode, m_current_pos);
|
std::vector<PerExtruderAdjustments> per_extruder_adjustments = this->parse_layer_gcode(gcode, m_current_pos);
|
||||||
|
@ -241,12 +296,12 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
|
||||||
std::vector<PerExtruderAdjustments> per_extruder_adjustments(extruders.size());
|
std::vector<PerExtruderAdjustments> per_extruder_adjustments(extruders.size());
|
||||||
std::vector<size_t> map_extruder_to_per_extruder_adjustment(num_extruders, 0);
|
std::vector<size_t> map_extruder_to_per_extruder_adjustment(num_extruders, 0);
|
||||||
for (size_t i = 0; i < extruders.size(); ++ i) {
|
for (size_t i = 0; i < extruders.size(); ++ i) {
|
||||||
PerExtruderAdjustments &adj = per_extruder_adjustments[i];
|
PerExtruderAdjustments &adj = per_extruder_adjustments[i];
|
||||||
unsigned int extruder_id = extruders[i].id();
|
unsigned int extruder_id = extruders[i].id();
|
||||||
adj.extruder_id = extruder_id;
|
adj.extruder_id = extruder_id;
|
||||||
adj.cooling_slow_down_enabled = config.cooling.get_at(extruder_id);
|
adj.cooling_slow_down_enabled = config.cooling.get_at(extruder_id);
|
||||||
adj.slowdown_below_layer_time = config.slowdown_below_layer_time.get_at(extruder_id);
|
adj.slowdown_below_layer_time = config.slowdown_below_layer_time.get_at(extruder_id);
|
||||||
adj.min_print_speed = config.min_print_speed.get_at(extruder_id);
|
adj.min_print_speed = config.min_print_speed.get_at(extruder_id);
|
||||||
map_extruder_to_per_extruder_adjustment[extruder_id] = i;
|
map_extruder_to_per_extruder_adjustment[extruder_id] = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -452,14 +507,14 @@ static inline void extruder_range_slow_down_non_proportional(
|
||||||
std::vector<PerExtruderAdjustments*> by_min_print_speed(it_begin, it_end);
|
std::vector<PerExtruderAdjustments*> by_min_print_speed(it_begin, it_end);
|
||||||
// Find the next highest adjustable feedrate among the extruders.
|
// Find the next highest adjustable feedrate among the extruders.
|
||||||
float feedrate = 0;
|
float feedrate = 0;
|
||||||
for (PerExtruderAdjustments *adj : by_min_print_speed) {
|
for (PerExtruderAdjustments *adj : by_min_print_speed) {
|
||||||
adj->idx_line_begin = 0;
|
adj->idx_line_begin = 0;
|
||||||
adj->idx_line_end = 0;
|
adj->idx_line_end = 0;
|
||||||
assert(adj->idx_line_begin < adj->n_lines_adjustable);
|
assert(adj->idx_line_begin < adj->n_lines_adjustable);
|
||||||
if (adj->lines[adj->idx_line_begin].feedrate > feedrate)
|
if (adj->lines[adj->idx_line_begin].feedrate > feedrate)
|
||||||
feedrate = adj->lines[adj->idx_line_begin].feedrate;
|
feedrate = adj->lines[adj->idx_line_begin].feedrate;
|
||||||
}
|
}
|
||||||
assert(feedrate > 0.f);
|
assert(feedrate > 0.f);
|
||||||
// Sort by min_print_speed, maximum speed first.
|
// Sort by min_print_speed, maximum speed first.
|
||||||
std::sort(by_min_print_speed.begin(), by_min_print_speed.end(),
|
std::sort(by_min_print_speed.begin(), by_min_print_speed.end(),
|
||||||
[](const PerExtruderAdjustments *p1, const PerExtruderAdjustments *p2){ return p1->min_print_speed > p2->min_print_speed; });
|
[](const PerExtruderAdjustments *p1, const PerExtruderAdjustments *p2){ return p1->min_print_speed > p2->min_print_speed; });
|
||||||
|
@ -496,7 +551,7 @@ static inline void extruder_range_slow_down_non_proportional(
|
||||||
for (auto it = adj; it != by_min_print_speed.end(); ++ it)
|
for (auto it = adj; it != by_min_print_speed.end(); ++ it)
|
||||||
time_stretch_max += (*it)->time_stretch_when_slowing_down_to_feedrate(feedrate_limit);
|
time_stretch_max += (*it)->time_stretch_when_slowing_down_to_feedrate(feedrate_limit);
|
||||||
if (time_stretch_max >= time_stretch) {
|
if (time_stretch_max >= time_stretch) {
|
||||||
feedrate_limit = feedrate - (feedrate - feedrate_limit) * time_stretch / time_stretch_max;
|
feedrate_limit = new_feedrate_to_reach_time_stretch(adj, by_min_print_speed.end(), feedrate_limit, time_stretch, 20);
|
||||||
done = true;
|
done = true;
|
||||||
} else
|
} else
|
||||||
time_stretch -= time_stretch_max;
|
time_stretch -= time_stretch_max;
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace Slic3r {
|
||||||
|
|
||||||
class GCode;
|
class GCode;
|
||||||
class Layer;
|
class Layer;
|
||||||
class PerExtruderAdjustments;
|
struct PerExtruderAdjustments;
|
||||||
|
|
||||||
// A standalone G-code filter, to control cooling of the print.
|
// A standalone G-code filter, to control cooling of the print.
|
||||||
// The G-code is processed per layer. Once a layer is collected, fan start / stop commands are edited
|
// The G-code is processed per layer. Once a layer is collected, fan start / stop commands are edited
|
||||||
|
|
|
@ -13,7 +13,7 @@ class SpiralVase {
|
||||||
SpiralVase(const PrintConfig &config)
|
SpiralVase(const PrintConfig &config)
|
||||||
: enable(false), _config(&config)
|
: enable(false), _config(&config)
|
||||||
{
|
{
|
||||||
this->_reader.z() = this->_config->z_offset;
|
this->_reader.z() = (float)this->_config->z_offset;
|
||||||
this->_reader.apply_config(*this->_config);
|
this->_reader.apply_config(*this->_config);
|
||||||
};
|
};
|
||||||
std::string process_layer(const std::string &gcode);
|
std::string process_layer(const std::string &gcode);
|
||||||
|
|
|
@ -43,7 +43,7 @@ public:
|
||||||
}
|
}
|
||||||
bool cmd_is(const char *cmd_test) const {
|
bool cmd_is(const char *cmd_test) const {
|
||||||
const char *cmd = GCodeReader::skip_whitespaces(m_raw.c_str());
|
const char *cmd = GCodeReader::skip_whitespaces(m_raw.c_str());
|
||||||
int len = strlen(cmd_test);
|
size_t len = strlen(cmd_test);
|
||||||
return strncmp(cmd, cmd_test, len) == 0 && GCodeReader::is_end_of_word(cmd[len]);
|
return strncmp(cmd, cmd_test, len) == 0 && GCodeReader::is_end_of_word(cmd[len]);
|
||||||
}
|
}
|
||||||
bool extruding(const GCodeReader &reader) const { return this->cmd_is("G1") && this->dist_E(reader) > 0; }
|
bool extruding(const GCodeReader &reader) const { return this->cmd_is("G1") && this->dist_E(reader) > 0; }
|
||||||
|
|
|
@ -1182,8 +1182,6 @@ Vec3d extract_euler_angles(const Eigen::Matrix<double, 3, 3, Eigen::DontAlign>&
|
||||||
{
|
{
|
||||||
#if ENABLE_NEW_EULER_ANGLES
|
#if ENABLE_NEW_EULER_ANGLES
|
||||||
// reference: http://www.gregslabaugh.net/publications/euler.pdf
|
// reference: http://www.gregslabaugh.net/publications/euler.pdf
|
||||||
auto is_approx = [](double value, double test_value) -> bool { return std::abs(value - test_value) < EPSILON; };
|
|
||||||
|
|
||||||
Vec3d angles1 = Vec3d::Zero();
|
Vec3d angles1 = Vec3d::Zero();
|
||||||
Vec3d angles2 = Vec3d::Zero();
|
Vec3d angles2 = Vec3d::Zero();
|
||||||
if (is_approx(std::abs(rotation_matrix(2, 0)), 1.0))
|
if (is_approx(std::abs(rotation_matrix(2, 0)), 1.0))
|
||||||
|
|
|
@ -549,11 +549,18 @@ void Model::reset_auto_extruder_id()
|
||||||
|
|
||||||
std::string Model::propose_export_file_name() const
|
std::string Model::propose_export_file_name() const
|
||||||
{
|
{
|
||||||
|
std::string input_file;
|
||||||
for (const ModelObject *model_object : this->objects)
|
for (const ModelObject *model_object : this->objects)
|
||||||
for (ModelInstance *model_instance : model_object->instances)
|
for (ModelInstance *model_instance : model_object->instances)
|
||||||
if (model_instance->is_printable())
|
if (model_instance->is_printable()) {
|
||||||
return model_object->name.empty() ? model_object->input_file : model_object->name;
|
input_file = model_object->name.empty() ? model_object->input_file : model_object->name;
|
||||||
return std::string();
|
if (! input_file.empty())
|
||||||
|
goto end;
|
||||||
|
// Other instances will produce the same name, skip them.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
end:
|
||||||
|
return input_file;
|
||||||
}
|
}
|
||||||
|
|
||||||
ModelObject::~ModelObject()
|
ModelObject::~ModelObject()
|
||||||
|
|
|
@ -67,20 +67,9 @@ std::string PrintBase::output_filename(const std::string &format, const std::str
|
||||||
std::string PrintBase::output_filepath(const std::string &path) const
|
std::string PrintBase::output_filepath(const std::string &path) const
|
||||||
{
|
{
|
||||||
// if we were supplied no path, generate an automatic one based on our first object's input file
|
// if we were supplied no path, generate an automatic one based on our first object's input file
|
||||||
if (path.empty()) {
|
if (path.empty())
|
||||||
// get the first input file name
|
// get the first input file name
|
||||||
std::string input_file;
|
return (boost::filesystem::path(m_model.propose_export_file_name()).parent_path() / this->output_filename()).make_preferred().string();
|
||||||
for (const ModelObject *model_object : m_model.objects) {
|
|
||||||
for (ModelInstance *model_instance : model_object->instances)
|
|
||||||
if (model_instance->is_printable()) {
|
|
||||||
input_file = model_object->input_file;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (! input_file.empty())
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return (boost::filesystem::path(input_file).parent_path() / this->output_filename()).make_preferred().string();
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we were supplied a directory, use it and append our automatically generated filename
|
// if we were supplied a directory, use it and append our automatically generated filename
|
||||||
boost::filesystem::path p(path);
|
boost::filesystem::path p(path);
|
||||||
|
|
|
@ -13,8 +13,7 @@
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
/*
|
/*float SLAAutoSupports::approximate_geodesic_distance(const Vec3d& p1, const Vec3d& p2, Vec3d& n1, Vec3d& n2)
|
||||||
float SLAAutoSupports::approximate_geodesic_distance(const Vec3d& p1, const Vec3d& p2, Vec3d& n1, Vec3d& n2)
|
|
||||||
{
|
{
|
||||||
n1.normalize();
|
n1.normalize();
|
||||||
n2.normalize();
|
n2.normalize();
|
||||||
|
@ -50,7 +49,7 @@ float SLAAutoSupports::distance_limit(float angle) const
|
||||||
|
|
||||||
SLAAutoSupports::SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector<ExPolygons>& slices, const std::vector<float>& heights,
|
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)
|
const Config& config, std::function<void(void)> throw_on_cancel)
|
||||||
: m_config(config), m_V(emesh.V), m_F(emesh.F), m_throw_on_cancel(throw_on_cancel)
|
: m_config(config), m_V(emesh.V()), m_F(emesh.F()), m_throw_on_cancel(throw_on_cancel)
|
||||||
{
|
{
|
||||||
process(slices, heights);
|
process(slices, heights);
|
||||||
project_onto_mesh(m_output);
|
project_onto_mesh(m_output);
|
||||||
|
|
|
@ -30,7 +30,7 @@ private:
|
||||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
|
#ifdef SLA_AUTOSUPPORTS_DEBUG
|
||||||
void output_expolygons(const ExPolygons& expolys, std::string filename) const;
|
void output_expolygons(const ExPolygons& expolys, std::string filename) const;
|
||||||
void output_structures() const;
|
void output_structures() const;
|
||||||
#endif /* SLA_AUTOSUPPORTS_DEBUG */
|
#endif // SLA_AUTOSUPPORTS_DEBUG
|
||||||
|
|
||||||
SLAAutoSupports::Config m_config;
|
SLAAutoSupports::Config m_config;
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,11 @@
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
// Typedef from Point.hpp
|
// Typedefs from Point.hpp
|
||||||
typedef Eigen::Matrix<float, 3, 1, Eigen::DontAlign> Vec3f;
|
typedef Eigen::Matrix<float, 3, 1, Eigen::DontAlign> Vec3f;
|
||||||
|
typedef Eigen::Matrix<double, 3, 1, Eigen::DontAlign> Vec3d;
|
||||||
|
|
||||||
|
class TriangleMesh;
|
||||||
namespace sla {
|
namespace sla {
|
||||||
|
|
||||||
struct SupportPoint {
|
struct SupportPoint {
|
||||||
|
@ -34,13 +36,95 @@ struct SupportPoint {
|
||||||
|
|
||||||
/// An index-triangle structure for libIGL functions. Also serves as an
|
/// An index-triangle structure for libIGL functions. Also serves as an
|
||||||
/// alternative (raw) input format for the SLASupportTree
|
/// alternative (raw) input format for the SLASupportTree
|
||||||
struct EigenMesh3D {
|
/*struct EigenMesh3D {
|
||||||
Eigen::MatrixXd V;
|
Eigen::MatrixXd V;
|
||||||
Eigen::MatrixXi F;
|
Eigen::MatrixXi F;
|
||||||
double ground_level = 0;
|
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 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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace sla
|
} // namespace sla
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ std::array<double, 3> find_best_rotation(const ModelObject& modelobj,
|
||||||
|
|
||||||
// We will use only one instance of this converted mesh to examine different
|
// We will use only one instance of this converted mesh to examine different
|
||||||
// rotations
|
// rotations
|
||||||
EigenMesh3D emesh = to_eigenmesh(modelobj);
|
EigenMesh3D emesh(modelobj.raw_mesh());
|
||||||
|
|
||||||
// For current iteration number
|
// For current iteration number
|
||||||
unsigned status = 0;
|
unsigned status = 0;
|
||||||
|
@ -68,12 +68,12 @@ std::array<double, 3> find_best_rotation(const ModelObject& modelobj,
|
||||||
// area. The current function is only an example of how to optimize.
|
// area. The current function is only an example of how to optimize.
|
||||||
|
|
||||||
// Later we can add more criteria like the number of overhangs, etc...
|
// Later we can add more criteria like the number of overhangs, etc...
|
||||||
for(int i = 0; i < m.F.rows(); i++) {
|
for(int i = 0; i < m.F().rows(); i++) {
|
||||||
auto idx = m.F.row(i);
|
auto idx = m.F().row(i);
|
||||||
|
|
||||||
Vec3d p1 = m.V.row(idx(0));
|
Vec3d p1 = m.V().row(idx(0));
|
||||||
Vec3d p2 = m.V.row(idx(1));
|
Vec3d p2 = m.V().row(idx(1));
|
||||||
Vec3d p3 = m.V.row(idx(2));
|
Vec3d p3 = m.V().row(idx(2));
|
||||||
|
|
||||||
Eigen::Vector3d U = p2 - p1;
|
Eigen::Vector3d U = p2 - p1;
|
||||||
Eigen::Vector3d V = p3 - p1;
|
Eigen::Vector3d V = p3 - p1;
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include <libslic3r/Model.hpp>
|
#include <libslic3r/Model.hpp>
|
||||||
|
|
||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
|
#include <tbb/parallel_for.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Terminology:
|
* Terminology:
|
||||||
|
@ -510,7 +511,6 @@ struct CompactBridge {
|
||||||
|
|
||||||
// A wrapper struct around the base pool (pad)
|
// A wrapper struct around the base pool (pad)
|
||||||
struct Pad {
|
struct Pad {
|
||||||
// Contour3D mesh;
|
|
||||||
TriangleMesh tmesh;
|
TriangleMesh tmesh;
|
||||||
PoolConfig cfg;
|
PoolConfig cfg;
|
||||||
double zlevel = 0;
|
double zlevel = 0;
|
||||||
|
@ -543,23 +543,6 @@ struct Pad {
|
||||||
bool empty() const { return tmesh.facets_count() == 0; }
|
bool empty() const { return tmesh.facets_count() == 0; }
|
||||||
};
|
};
|
||||||
|
|
||||||
EigenMesh3D to_eigenmesh(const Contour3D& cntr) {
|
|
||||||
EigenMesh3D emesh;
|
|
||||||
|
|
||||||
auto& V = emesh.V;
|
|
||||||
auto& F = emesh.F;
|
|
||||||
|
|
||||||
V.resize(Eigen::Index(cntr.points.size()), 3);
|
|
||||||
F.resize(Eigen::Index(cntr.indices.size()), 3);
|
|
||||||
|
|
||||||
for (int i = 0; i < V.rows(); ++i) {
|
|
||||||
V.row(i) = cntr.points[size_t(i)];
|
|
||||||
F.row(i) = cntr.indices[size_t(i)];
|
|
||||||
}
|
|
||||||
|
|
||||||
return emesh;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The minimum distance for two support points to remain valid.
|
// The minimum distance for two support points to remain valid.
|
||||||
static const double /*constexpr*/ D_SP = 0.1;
|
static const double /*constexpr*/ D_SP = 0.1;
|
||||||
|
|
||||||
|
@ -567,46 +550,6 @@ enum { // For indexing Eigen vectors as v(X), v(Y), v(Z) instead of numbers
|
||||||
X, Y, Z
|
X, Y, Z
|
||||||
};
|
};
|
||||||
|
|
||||||
EigenMesh3D to_eigenmesh(const TriangleMesh& tmesh) {
|
|
||||||
|
|
||||||
const stl_file& stl = tmesh.stl;
|
|
||||||
|
|
||||||
EigenMesh3D outmesh;
|
|
||||||
|
|
||||||
auto&& bb = tmesh.bounding_box();
|
|
||||||
outmesh.ground_level += bb.min(Z);
|
|
||||||
|
|
||||||
auto& V = outmesh.V;
|
|
||||||
auto& F = outmesh.F;
|
|
||||||
|
|
||||||
V.resize(3*stl.stats.number_of_facets, 3);
|
|
||||||
F.resize(stl.stats.number_of_facets, 3);
|
|
||||||
for (unsigned int i = 0; i < stl.stats.number_of_facets; ++i) {
|
|
||||||
const stl_facet* facet = stl.facet_start+i;
|
|
||||||
V(3*i+0, 0) = double(facet->vertex[0](0));
|
|
||||||
V(3*i+0, 1) = double(facet->vertex[0](1));
|
|
||||||
V(3*i+0, 2) = double(facet->vertex[0](2));
|
|
||||||
|
|
||||||
V(3*i+1, 0) = double(facet->vertex[1](0));
|
|
||||||
V(3*i+1, 1) = double(facet->vertex[1](1));
|
|
||||||
V(3*i+1, 2) = double(facet->vertex[1](2));
|
|
||||||
|
|
||||||
V(3*i+2, 0) = double(facet->vertex[2](0));
|
|
||||||
V(3*i+2, 1) = double(facet->vertex[2](1));
|
|
||||||
V(3*i+2, 2) = double(facet->vertex[2](2));
|
|
||||||
|
|
||||||
F(i, 0) = int(3*i+0);
|
|
||||||
F(i, 1) = int(3*i+1);
|
|
||||||
F(i, 2) = int(3*i+2);
|
|
||||||
}
|
|
||||||
|
|
||||||
return outmesh;
|
|
||||||
}
|
|
||||||
|
|
||||||
EigenMesh3D to_eigenmesh(const ModelObject& modelobj) {
|
|
||||||
return to_eigenmesh(modelobj.raw_mesh());
|
|
||||||
}
|
|
||||||
|
|
||||||
PointSet to_point_set(const std::vector<SupportPoint> &v)
|
PointSet to_point_set(const std::vector<SupportPoint> &v)
|
||||||
{
|
{
|
||||||
PointSet ret(v.size(), 5);
|
PointSet ret(v.size(), 5);
|
||||||
|
@ -626,9 +569,171 @@ Vec3d model_coord(const ModelInstance& object, const Vec3f& mesh_coord) {
|
||||||
return object.transform_vector(mesh_coord.cast<double>());
|
return object.transform_vector(mesh_coord.cast<double>());
|
||||||
}
|
}
|
||||||
|
|
||||||
double ray_mesh_intersect(const Vec3d& s,
|
inline double ray_mesh_intersect(const Vec3d& s,
|
||||||
const Vec3d& dir,
|
const Vec3d& dir,
|
||||||
const EigenMesh3D& m);
|
const EigenMesh3D& m)
|
||||||
|
{
|
||||||
|
return m.query_ray_hit(s, dir).distance();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function will test if a future pinhead would not collide with the model
|
||||||
|
// geometry. It does not take a 'Head' object because those are created after
|
||||||
|
// this test.
|
||||||
|
// Parameters:
|
||||||
|
// s: The touching point on the model surface.
|
||||||
|
// dir: This is the direction of the head from the pin to the back
|
||||||
|
// r_pin, r_back: the radiuses of the pin and the back sphere
|
||||||
|
// width: This is the full width from the pin center to the back center
|
||||||
|
// m: The object mesh
|
||||||
|
//
|
||||||
|
// Optional:
|
||||||
|
// samples: how many rays will be shot
|
||||||
|
// safety distance: This will be added to the radiuses to have a safety distance
|
||||||
|
// from the mesh.
|
||||||
|
double pinhead_mesh_intersect(const Vec3d& s,
|
||||||
|
const Vec3d& dir,
|
||||||
|
double r_pin,
|
||||||
|
double r_back,
|
||||||
|
double width,
|
||||||
|
const EigenMesh3D& m,
|
||||||
|
unsigned samples = 8,
|
||||||
|
double safety_distance = 0.001)
|
||||||
|
{
|
||||||
|
// method based on:
|
||||||
|
// https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space
|
||||||
|
|
||||||
|
|
||||||
|
// We will shoot multiple rays from the head pinpoint in the direction of
|
||||||
|
// the pinhead robe (side) surface. The result will be the smallest hit
|
||||||
|
// distance.
|
||||||
|
|
||||||
|
// Move away slightly from the touching point to avoid raycasting on the
|
||||||
|
// inner surface of the mesh.
|
||||||
|
Vec3d v = dir; // Our direction (axis)
|
||||||
|
Vec3d c = s + width * dir;
|
||||||
|
const double& sd = safety_distance;
|
||||||
|
|
||||||
|
// Two vectors that will be perpendicular to each other and to the axis.
|
||||||
|
// Values for a(X) and a(Y) are now arbitrary, a(Z) is just a placeholder.
|
||||||
|
Vec3d a(0, 1, 0), b;
|
||||||
|
|
||||||
|
// The portions of the circle (the head-back circle) for which we will shoot
|
||||||
|
// rays.
|
||||||
|
std::vector<double> phis(samples);
|
||||||
|
for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size();
|
||||||
|
|
||||||
|
a(Z) = -(v(X)*a(X) + v(Y)*a(Y)) / v(Z);
|
||||||
|
|
||||||
|
b = a.cross(v);
|
||||||
|
|
||||||
|
// Now a and b vectors are perpendicular to v and to each other. Together
|
||||||
|
// they define the plane where we have to iterate with the given angles
|
||||||
|
// in the 'phis' vector
|
||||||
|
|
||||||
|
tbb::parallel_for(size_t(0), phis.size(),
|
||||||
|
[&phis, &m, sd, r_pin, r_back, s, a, b, c](size_t i)
|
||||||
|
{
|
||||||
|
double& phi = phis[i];
|
||||||
|
double sinphi = std::sin(phi);
|
||||||
|
double cosphi = std::cos(phi);
|
||||||
|
|
||||||
|
// Let's have a safety coefficient for the radiuses.
|
||||||
|
double rpscos = (sd + r_pin) * cosphi;
|
||||||
|
double rpssin = (sd + r_pin) * sinphi;
|
||||||
|
double rpbcos = (sd + r_back) * cosphi;
|
||||||
|
double rpbsin = (sd + r_back) * sinphi;
|
||||||
|
|
||||||
|
// Point on the circle on the pin sphere
|
||||||
|
Vec3d ps(s(X) + rpscos * a(X) + rpssin * b(X),
|
||||||
|
s(Y) + rpscos * a(Y) + rpssin * b(Y),
|
||||||
|
s(Z) + rpscos * a(Z) + rpssin * b(Z));
|
||||||
|
|
||||||
|
// Point ps is not on mesh but can be inside or outside as well. This
|
||||||
|
// would cause many problems with ray-casting. So we query the closest
|
||||||
|
// point on the mesh to this.
|
||||||
|
// auto psq = m.signed_distance(ps);
|
||||||
|
|
||||||
|
// This is the point on the circle on the back sphere
|
||||||
|
Vec3d p(c(X) + rpbcos * a(X) + rpbsin * b(X),
|
||||||
|
c(Y) + rpbcos * a(Y) + rpbsin * b(Y),
|
||||||
|
c(Z) + rpbcos * a(Z) + rpbsin * b(Z));
|
||||||
|
|
||||||
|
// Vec3d n = (p - psq.point_on_mesh()).normalized();
|
||||||
|
// phi = m.query_ray_hit(psq.point_on_mesh() + sd*n, n);
|
||||||
|
|
||||||
|
Vec3d n = (p - ps).normalized();
|
||||||
|
auto hr = m.query_ray_hit(ps + sd*n, n);
|
||||||
|
|
||||||
|
if(hr.is_inside()) { // the hit is inside the model
|
||||||
|
if(hr.distance() > 2*r_pin) phi = 0;
|
||||||
|
else {
|
||||||
|
// re-cast the ray from the outside of the object
|
||||||
|
auto hr2 = m.query_ray_hit(ps + (hr.distance() + 2*sd)*n, n);
|
||||||
|
phi = hr2.distance();
|
||||||
|
}
|
||||||
|
} else phi = hr.distance();
|
||||||
|
});
|
||||||
|
|
||||||
|
auto mit = std::min_element(phis.begin(), phis.end());
|
||||||
|
|
||||||
|
return *mit;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Checking bridge (pillar and stick as well) intersection with the model. If
|
||||||
|
// the function is used for headless sticks, the ins_check parameter have to be
|
||||||
|
// true as the beginning of the stick might be inside the model geometry.
|
||||||
|
double bridge_mesh_intersect(const Vec3d& s,
|
||||||
|
const Vec3d& dir,
|
||||||
|
double r,
|
||||||
|
const EigenMesh3D& m,
|
||||||
|
bool ins_check = false,
|
||||||
|
unsigned samples = 4,
|
||||||
|
double safety_distance = 0.001)
|
||||||
|
{
|
||||||
|
// helper vector calculations
|
||||||
|
Vec3d a(0, 1, 0), b;
|
||||||
|
const double& sd = safety_distance;
|
||||||
|
|
||||||
|
a(Z) = -(dir(X)*a(X) + dir(Y)*a(Y)) / dir(Z);
|
||||||
|
b = a.cross(dir);
|
||||||
|
|
||||||
|
// circle portions
|
||||||
|
std::vector<double> phis(samples);
|
||||||
|
for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size();
|
||||||
|
|
||||||
|
tbb::parallel_for(size_t(0), phis.size(),
|
||||||
|
[&phis, &m, a, b, sd, dir, r, s, ins_check](size_t i)
|
||||||
|
{
|
||||||
|
double& phi = phis[i];
|
||||||
|
double sinphi = std::sin(phi);
|
||||||
|
double cosphi = std::cos(phi);
|
||||||
|
|
||||||
|
// Let's have a safety coefficient for the radiuses.
|
||||||
|
double rcos = (sd + r) * cosphi;
|
||||||
|
double rsin = (sd + r) * sinphi;
|
||||||
|
|
||||||
|
// Point on the circle on the pin sphere
|
||||||
|
Vec3d p (s(X) + rcos * a(X) + rsin * b(X),
|
||||||
|
s(Y) + rcos * a(Y) + rsin * b(Y),
|
||||||
|
s(Z) + rcos * a(Z) + rsin * b(Z));
|
||||||
|
|
||||||
|
auto hr = m.query_ray_hit(p + sd*dir, dir);
|
||||||
|
|
||||||
|
if(ins_check && hr.is_inside()) {
|
||||||
|
if(hr.distance() > 2*r) phi = 0;
|
||||||
|
else {
|
||||||
|
// re-cast the ray from the outside of the object
|
||||||
|
auto hr2 = m.query_ray_hit(p + (hr.distance() + 2*sd)*dir, dir);
|
||||||
|
phi = hr2.distance();
|
||||||
|
}
|
||||||
|
} else phi = hr.distance();
|
||||||
|
});
|
||||||
|
|
||||||
|
auto mit = std::min_element(phis.begin(), phis.end());
|
||||||
|
|
||||||
|
return *mit;
|
||||||
|
}
|
||||||
|
|
||||||
PointSet normals(const PointSet& points, const EigenMesh3D& mesh,
|
PointSet normals(const PointSet& points, const EigenMesh3D& mesh,
|
||||||
double eps = 0.05, // min distance from edges
|
double eps = 0.05, // min distance from edges
|
||||||
|
@ -648,6 +753,19 @@ ClusteredPoints cluster(
|
||||||
std::function<bool(const SpatElement&, const SpatElement&)> pred,
|
std::function<bool(const SpatElement&, const SpatElement&)> pred,
|
||||||
unsigned max_points = 0);
|
unsigned max_points = 0);
|
||||||
|
|
||||||
|
// This class will hold the support tree meshes with some additional bookkeeping
|
||||||
|
// as well. Various parts of the support geometry are stored separately and are
|
||||||
|
// merged when the caller queries the merged mesh. The merged result is cached
|
||||||
|
// for fast subsequent delivery of the merged mesh which can be quite complex.
|
||||||
|
// An object of this class will be used as the result type during the support
|
||||||
|
// generation algorithm. Parts will be added with the appropriate methods such
|
||||||
|
// as add_head or add_pillar which forwards the constructor arguments and fills
|
||||||
|
// the IDs of these substructures. The IDs are basically indices into the arrays
|
||||||
|
// of the appropriate type (heads, pillars, etc...). One can later query e.g. a
|
||||||
|
// pillar for a specific head...
|
||||||
|
//
|
||||||
|
// The support pad is considered an auxiliary geometry and is not part of the
|
||||||
|
// merged mesh. It can be retrieved using a dedicated method (pad())
|
||||||
class SLASupportTree::Impl {
|
class SLASupportTree::Impl {
|
||||||
std::vector<Head> m_heads;
|
std::vector<Head> m_heads;
|
||||||
std::vector<Pillar> m_pillars;
|
std::vector<Pillar> m_pillars;
|
||||||
|
@ -1009,16 +1127,22 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||||
// Indices of those who don't touch the ground
|
// Indices of those who don't touch the ground
|
||||||
IndexSet noground_heads;
|
IndexSet noground_heads;
|
||||||
|
|
||||||
|
// Groups of the 'ground_head' indices that belong into one cluster. These
|
||||||
|
// are candidates to be connected to one pillar.
|
||||||
ClusteredPoints ground_connectors;
|
ClusteredPoints ground_connectors;
|
||||||
|
|
||||||
|
// A help function to translate ground head index to the actual coordinates.
|
||||||
auto gnd_head_pt = [&ground_heads, &head_positions] (size_t idx) {
|
auto gnd_head_pt = [&ground_heads, &head_positions] (size_t idx) {
|
||||||
return Vec3d(head_positions.row(ground_heads[idx]));
|
return Vec3d(head_positions.row(ground_heads[idx]));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// This algorithm uses the Impl class as its output stream. It will be
|
||||||
|
// filled gradually with support elements (heads, pillars, bridges, ...)
|
||||||
using Result = SLASupportTree::Impl;
|
using Result = SLASupportTree::Impl;
|
||||||
|
|
||||||
Result& result = *m_impl;
|
Result& result = *m_impl;
|
||||||
|
|
||||||
|
// Let's define the individual steps of the processing. We can experiment
|
||||||
|
// later with the ordering and the dependencies between them.
|
||||||
enum Steps {
|
enum Steps {
|
||||||
BEGIN,
|
BEGIN,
|
||||||
FILTER,
|
FILTER,
|
||||||
|
@ -1034,14 +1158,15 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||||
//...
|
//...
|
||||||
};
|
};
|
||||||
|
|
||||||
// Debug:
|
// t-hrow i-f c-ance-l-ed: It will be called many times so a shorthand will
|
||||||
// for(int pn = 0; pn < points.rows(); ++pn) {
|
// come in handy.
|
||||||
// std::cout << "p " << pn << " " << points.row(pn) << std::endl;
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
auto& tifcl = ctl.cancelfn;
|
auto& tifcl = ctl.cancelfn;
|
||||||
|
|
||||||
|
// Filtering step: here we will discard inappropriate support points and
|
||||||
|
// decide the future of the appropriate ones. We will check if a pinhead
|
||||||
|
// is applicable and adjust its angle at each support point.
|
||||||
|
// We will also merge the support points that are just too close and can be
|
||||||
|
// considered as one.
|
||||||
auto filterfn = [tifcl] (
|
auto filterfn = [tifcl] (
|
||||||
const SupportConfig& cfg,
|
const SupportConfig& cfg,
|
||||||
const PointSet& points,
|
const PointSet& points,
|
||||||
|
@ -1052,10 +1177,6 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||||
PointSet& headless_pos,
|
PointSet& headless_pos,
|
||||||
PointSet& headless_norm)
|
PointSet& headless_norm)
|
||||||
{
|
{
|
||||||
/* ******************************************************** */
|
|
||||||
/* Filtering step */
|
|
||||||
/* ******************************************************** */
|
|
||||||
|
|
||||||
// Get the points that are too close to each other and keep only the
|
// Get the points that are too close to each other and keep only the
|
||||||
// first one
|
// first one
|
||||||
auto aliases =
|
auto aliases =
|
||||||
|
@ -1116,6 +1237,8 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||||
std::sin(azimuth) * std::sin(polar),
|
std::sin(azimuth) * std::sin(polar),
|
||||||
std::cos(polar));
|
std::cos(polar));
|
||||||
|
|
||||||
|
nn.normalize();
|
||||||
|
|
||||||
// save the head (pinpoint) position
|
// save the head (pinpoint) position
|
||||||
Vec3d hp = filt_pts.row(i);
|
Vec3d hp = filt_pts.row(i);
|
||||||
|
|
||||||
|
@ -1126,11 +1249,15 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||||
|
|
||||||
// We should shoot a ray in the direction of the pinhead and
|
// We should shoot a ray in the direction of the pinhead and
|
||||||
// see if there is enough space for it
|
// see if there is enough space for it
|
||||||
double t = ray_mesh_intersect(hp + 0.1*nn, nn, mesh);
|
double t = pinhead_mesh_intersect(
|
||||||
|
hp, // touching point
|
||||||
if(t > 2*w || std::isinf(t)) {
|
nn,
|
||||||
// 2*w because of lower and upper pinhead
|
cfg.head_front_radius_mm, // approx the radius
|
||||||
|
cfg.head_back_radius_mm,
|
||||||
|
w,
|
||||||
|
mesh);
|
||||||
|
|
||||||
|
if(t > w || std::isinf(t)) {
|
||||||
head_pos.row(pcount) = hp;
|
head_pos.row(pcount) = hp;
|
||||||
|
|
||||||
// save the verified and corrected normal
|
// save the verified and corrected normal
|
||||||
|
@ -1152,7 +1279,8 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||||
headless_norm.conservativeResize(hlcount, Eigen::NoChange);
|
headless_norm.conservativeResize(hlcount, Eigen::NoChange);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Function to write the pinheads into the result
|
// Pinhead creation: based on the filtering results, the Head objects will
|
||||||
|
// be constructed (together with their triangle meshes).
|
||||||
auto pinheadfn = [tifcl] (
|
auto pinheadfn = [tifcl] (
|
||||||
const SupportConfig& cfg,
|
const SupportConfig& cfg,
|
||||||
PointSet& head_pos,
|
PointSet& head_pos,
|
||||||
|
@ -1178,8 +1306,13 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// &filtered_points, &head_positions, &result, &mesh,
|
// Further classification of the support points with pinheads. If the
|
||||||
// &gndidx, &gndheight, &nogndidx, cfg
|
// ground is directly reachable through a vertical line parallel to the Z
|
||||||
|
// axis we consider a support point as pillar candidate. If touches the
|
||||||
|
// model geometry, it will be marked as non-ground facing and further steps
|
||||||
|
// will process it. Also, the pillars will be grouped into clusters that can
|
||||||
|
// be interconnected with bridges. Elements of these groups may or may not
|
||||||
|
// be interconnected. Here we only run the clustering algorithm.
|
||||||
auto classifyfn = [tifcl] (
|
auto classifyfn = [tifcl] (
|
||||||
const SupportConfig& cfg,
|
const SupportConfig& cfg,
|
||||||
const EigenMesh3D& mesh,
|
const EigenMesh3D& mesh,
|
||||||
|
@ -1200,23 +1333,81 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||||
gndidx.reserve(size_t(head_pos.rows()));
|
gndidx.reserve(size_t(head_pos.rows()));
|
||||||
nogndidx.reserve(size_t(head_pos.rows()));
|
nogndidx.reserve(size_t(head_pos.rows()));
|
||||||
|
|
||||||
|
// First we search decide which heads reach the ground and can be full
|
||||||
|
// pillars and which shall be connected to the model surface (or search
|
||||||
|
// a suitable path around the surface that leads to the ground -- TODO)
|
||||||
for(unsigned i = 0; i < head_pos.rows(); i++) {
|
for(unsigned i = 0; i < head_pos.rows(); i++) {
|
||||||
tifcl();
|
tifcl();
|
||||||
auto& head = result.heads()[i];
|
auto& head = result.head(i);
|
||||||
|
|
||||||
Vec3d dir(0, 0, -1);
|
Vec3d dir(0, 0, -1);
|
||||||
Vec3d startpoint = head.junction_point();
|
bool accept = false;
|
||||||
|
int ri = 1;
|
||||||
|
double t = std::numeric_limits<double>::infinity();
|
||||||
|
double hw = head.width_mm;
|
||||||
|
|
||||||
double t = ray_mesh_intersect(startpoint, dir, mesh);
|
// We will try to assign a pillar to all the pinheads. If a pillar
|
||||||
|
// would pierce the model surface, we will try to adjust slightly
|
||||||
|
// the head with so that the pillar can be deployed.
|
||||||
|
while(!accept && head.width_mm > 0) {
|
||||||
|
|
||||||
|
Vec3d startpoint = head.junction_point();
|
||||||
|
|
||||||
|
// Collision detection
|
||||||
|
t = bridge_mesh_intersect(startpoint, dir, head.r_back_mm, mesh);
|
||||||
|
|
||||||
|
// Precise distance measurement
|
||||||
|
double tprec = ray_mesh_intersect(startpoint, dir, mesh);
|
||||||
|
|
||||||
|
if(std::isinf(tprec) && !std::isinf(t)) {
|
||||||
|
// This is a damned case where the pillar melds into the
|
||||||
|
// model but its center ray can reach the ground. We can
|
||||||
|
// not route this to the ground nor to the model surface.
|
||||||
|
head.width_mm = hw + (ri % 2? -1 : 1) * ri * head.r_back_mm;
|
||||||
|
} else {
|
||||||
|
accept = true; t = tprec;
|
||||||
|
|
||||||
|
auto id = head.id;
|
||||||
|
// We need to regenerate the head geometry
|
||||||
|
head = Head(head.r_back_mm,
|
||||||
|
head.r_pin_mm,
|
||||||
|
head.width_mm,
|
||||||
|
head.penetration_mm,
|
||||||
|
head.dir,
|
||||||
|
head.tr);
|
||||||
|
head.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
ri++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the distance from a surface in the Z axis downwards. It may
|
||||||
|
// be infinity but that is telling us that it touches the ground.
|
||||||
gndheight.emplace_back(t);
|
gndheight.emplace_back(t);
|
||||||
|
|
||||||
if(std::isinf(t)) gndidx.emplace_back(i);
|
if(accept) {
|
||||||
else nogndidx.emplace_back(i);
|
if(std::isinf(t)) gndidx.emplace_back(i);
|
||||||
|
else nogndidx.emplace_back(i);
|
||||||
|
} else {
|
||||||
|
// This is a serious issue. There was no way to deploy a pillar
|
||||||
|
// for the given pinhead. The whole thing has to be discarded
|
||||||
|
// leaving the model potentially unprintable.
|
||||||
|
//
|
||||||
|
// TODO: In the future this has to be solved by searching for
|
||||||
|
// a path in 3D space from this support point to a suitable
|
||||||
|
// pillar position or an existing pillar.
|
||||||
|
// As a workaround we could mark this head as "sidehead only"
|
||||||
|
// let it go trough the nearby pillar search in the next step.
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "A support point at "
|
||||||
|
<< head.tr.transpose()
|
||||||
|
<< " had to be discarded as there is"
|
||||||
|
<< " nowhere to route it.";
|
||||||
|
head.invalidate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Transform the ground facing point indices top actual coordinates.
|
||||||
PointSet gnd(gndidx.size(), 3);
|
PointSet gnd(gndidx.size(), 3);
|
||||||
|
|
||||||
for(size_t i = 0; i < gndidx.size(); i++)
|
for(size_t i = 0; i < gndidx.size(); i++)
|
||||||
gnd.row(long(i)) = head_pos.row(gndidx[i]);
|
gnd.row(long(i)) = head_pos.row(gndidx[i]);
|
||||||
|
|
||||||
|
@ -1236,7 +1427,8 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||||
}, 3); // max 3 heads to connect to one centroid
|
}, 3); // max 3 heads to connect to one centroid
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper function for interconnecting two pillars with zig-zag bridges
|
// Helper function for interconnecting two pillars with zig-zag bridges.
|
||||||
|
// This is not an individual step.
|
||||||
auto interconnect = [&cfg](
|
auto interconnect = [&cfg](
|
||||||
const Pillar& pillar,
|
const Pillar& pillar,
|
||||||
const Pillar& nextpillar,
|
const Pillar& nextpillar,
|
||||||
|
@ -1254,7 +1446,7 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||||
double zstep = pillar_dist * std::tan(-cfg.tilt);
|
double zstep = pillar_dist * std::tan(-cfg.tilt);
|
||||||
ej(Z) = sj(Z) + zstep;
|
ej(Z) = sj(Z) + zstep;
|
||||||
|
|
||||||
double chkd = ray_mesh_intersect(sj, dirv(sj, ej), emesh);
|
double chkd = bridge_mesh_intersect(sj, dirv(sj, ej), pillar.r, emesh);
|
||||||
double bridge_distance = pillar_dist / std::cos(-cfg.tilt);
|
double bridge_distance = pillar_dist / std::cos(-cfg.tilt);
|
||||||
|
|
||||||
// If the pillars are so close that they touch each other,
|
// If the pillars are so close that they touch each other,
|
||||||
|
@ -1262,7 +1454,7 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||||
if(pillar_dist > 2*cfg.head_back_radius_mm &&
|
if(pillar_dist > 2*cfg.head_back_radius_mm &&
|
||||||
bridge_distance < cfg.max_bridge_length_mm)
|
bridge_distance < cfg.max_bridge_length_mm)
|
||||||
while(sj(Z) > pillar.endpoint(Z) + cfg.base_radius_mm &&
|
while(sj(Z) > pillar.endpoint(Z) + cfg.base_radius_mm &&
|
||||||
ej(Z) > nextpillar.endpoint(Z) + + cfg.base_radius_mm)
|
ej(Z) > nextpillar.endpoint(Z) + cfg.base_radius_mm)
|
||||||
{
|
{
|
||||||
if(chkd >= bridge_distance) {
|
if(chkd >= bridge_distance) {
|
||||||
result.add_bridge(sj, ej, pillar.r);
|
result.add_bridge(sj, ej, pillar.r);
|
||||||
|
@ -1280,9 +1472,11 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||||
Vec3d bej(sj(X), sj(Y), ej(Z));
|
Vec3d bej(sj(X), sj(Y), ej(Z));
|
||||||
|
|
||||||
// need to check collision for the cross stick
|
// need to check collision for the cross stick
|
||||||
double backchkd = ray_mesh_intersect(bsj,
|
double backchkd = bridge_mesh_intersect(bsj,
|
||||||
dirv(bsj, bej),
|
dirv(bsj, bej),
|
||||||
emesh);
|
pillar.r,
|
||||||
|
emesh);
|
||||||
|
|
||||||
|
|
||||||
if(backchkd >= bridge_distance) {
|
if(backchkd >= bridge_distance) {
|
||||||
result.add_bridge(bsj, bej, pillar.r);
|
result.add_bridge(bsj, bej, pillar.r);
|
||||||
|
@ -1291,10 +1485,15 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||||
}
|
}
|
||||||
sj.swap(ej);
|
sj.swap(ej);
|
||||||
ej(Z) = sj(Z) + zstep;
|
ej(Z) = sj(Z) + zstep;
|
||||||
chkd = ray_mesh_intersect(sj, dirv(sj, ej), emesh);
|
chkd = bridge_mesh_intersect(sj, dirv(sj, ej), pillar.r, emesh);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Step: Routing the ground connected pinheads, and interconnecting them
|
||||||
|
// with additional (angled) bridges. Not all of these pinheads will be
|
||||||
|
// a full pillar (ground connected). Some will connect to a nearby pillar
|
||||||
|
// using a bridge. The max number of such side-heads for a central pillar
|
||||||
|
// is limited to avoid bad weight distribution.
|
||||||
auto routing_ground_fn = [gnd_head_pt, interconnect, tifcl](
|
auto routing_ground_fn = [gnd_head_pt, interconnect, tifcl](
|
||||||
const SupportConfig& cfg,
|
const SupportConfig& cfg,
|
||||||
const ClusteredPoints& gnd_clusters,
|
const ClusteredPoints& gnd_clusters,
|
||||||
|
@ -1369,12 +1568,12 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||||
// is distributed more effectively on the pillar.
|
// is distributed more effectively on the pillar.
|
||||||
|
|
||||||
auto search_nearest =
|
auto search_nearest =
|
||||||
[&cfg, &result, &emesh, maxbridgelen, gndlvl]
|
[&tifcl, &cfg, &result, &emesh, maxbridgelen, gndlvl, pradius]
|
||||||
(SpatIndex& spindex, const Vec3d& jsh)
|
(SpatIndex& spindex, const Vec3d& jsh)
|
||||||
{
|
{
|
||||||
long nearest_id = -1;
|
long nearest_id = -1;
|
||||||
const double max_len = maxbridgelen / 2;
|
const double max_len = maxbridgelen / 2;
|
||||||
while(nearest_id < 0 && !spindex.empty()) {
|
while(nearest_id < 0 && !spindex.empty()) { tifcl();
|
||||||
// loop until a suitable head is not found
|
// loop until a suitable head is not found
|
||||||
// if there is a pillar closer than the cluster center
|
// if there is a pillar closer than the cluster center
|
||||||
// (this may happen as the clustering is not perfect)
|
// (this may happen as the clustering is not perfect)
|
||||||
|
@ -1399,10 +1598,13 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||||
}
|
}
|
||||||
|
|
||||||
double d = distance(jp, jn);
|
double d = distance(jp, jn);
|
||||||
if(jn(Z) <= (gndlvl + 2*cfg.head_width_mm) || d > max_len)
|
|
||||||
|
if(jn(Z) <= gndlvl + 2*cfg.head_width_mm || d > max_len)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
double chkd = ray_mesh_intersect(jp, dirv(jp, jn), emesh);
|
double chkd = bridge_mesh_intersect(jp, dirv(jp, jn),
|
||||||
|
pradius,
|
||||||
|
emesh);
|
||||||
if(chkd >= d) nearest_id = ne.second;
|
if(chkd >= d) nearest_id = ne.second;
|
||||||
|
|
||||||
spindex.remove(ne);
|
spindex.remove(ne);
|
||||||
|
@ -1488,7 +1690,7 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||||
if(!ring.empty()) {
|
if(!ring.empty()) {
|
||||||
// inner ring is now in 'newring' and outer ring is in 'ring'
|
// inner ring is now in 'newring' and outer ring is in 'ring'
|
||||||
SpatIndex innerring;
|
SpatIndex innerring;
|
||||||
for(unsigned i : newring) {
|
for(unsigned i : newring) { tifcl();
|
||||||
const Pillar& pill = result.head_pillar(gndidx[i]);
|
const Pillar& pill = result.head_pillar(gndidx[i]);
|
||||||
assert(pill.id >= 0);
|
assert(pill.id >= 0);
|
||||||
innerring.insert(pill.endpoint, unsigned(pill.id));
|
innerring.insert(pill.endpoint, unsigned(pill.id));
|
||||||
|
@ -1497,7 +1699,7 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||||
// For all pillars in the outer ring find the closest in the
|
// For all pillars in the outer ring find the closest in the
|
||||||
// inner ring and connect them. This will create the spider web
|
// inner ring and connect them. This will create the spider web
|
||||||
// fashioned connections between pillars
|
// fashioned connections between pillars
|
||||||
for(unsigned i : ring) {
|
for(unsigned i : ring) { tifcl();
|
||||||
const Pillar& outerpill = result.head_pillar(gndidx[i]);
|
const Pillar& outerpill = result.head_pillar(gndidx[i]);
|
||||||
auto res = innerring.nearest(outerpill.endpoint, 1);
|
auto res = innerring.nearest(outerpill.endpoint, 1);
|
||||||
if(res.empty()) continue;
|
if(res.empty()) continue;
|
||||||
|
@ -1523,6 +1725,7 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||||
next != ring.end();
|
next != ring.end();
|
||||||
++it, ++next)
|
++it, ++next)
|
||||||
{
|
{
|
||||||
|
tifcl();
|
||||||
const Pillar& pillar = result.head_pillar(gndidx[*it]);
|
const Pillar& pillar = result.head_pillar(gndidx[*it]);
|
||||||
const Pillar& nextpillar = result.head_pillar(gndidx[*next]);
|
const Pillar& nextpillar = result.head_pillar(gndidx[*next]);
|
||||||
interconnect(pillar, nextpillar, emesh, result);
|
interconnect(pillar, nextpillar, emesh, result);
|
||||||
|
@ -1537,6 +1740,11 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Step: routing the pinheads that are would connect to the model surface
|
||||||
|
// along the Z axis downwards. For now these will actually be connected with
|
||||||
|
// the model surface with a flipped pinhead. In the future here we could use
|
||||||
|
// some smart algorithms to search for a safe path to the ground or to a
|
||||||
|
// nearby pillar that can hold the supported weight.
|
||||||
auto routing_nongnd_fn = [tifcl](
|
auto routing_nongnd_fn = [tifcl](
|
||||||
const SupportConfig& cfg,
|
const SupportConfig& cfg,
|
||||||
const std::vector<double>& gndheight,
|
const std::vector<double>& gndheight,
|
||||||
|
@ -1598,6 +1806,9 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Step: process the support points where there is not enough space for a
|
||||||
|
// full pinhead. In this case we will use a rounded sphere as a touching
|
||||||
|
// point and use a thinner bridge (let's call it a stick).
|
||||||
auto process_headless = [tifcl](
|
auto process_headless = [tifcl](
|
||||||
const SupportConfig& cfg,
|
const SupportConfig& cfg,
|
||||||
const PointSet& headless_pts,
|
const PointSet& headless_pts,
|
||||||
|
@ -1614,32 +1825,48 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||||
// We will sink the pins into the model surface for a distance of 1/3 of
|
// We will sink the pins into the model surface for a distance of 1/3 of
|
||||||
// the pin radius
|
// the pin radius
|
||||||
for(int i = 0; i < headless_pts.rows(); i++) { tifcl();
|
for(int i = 0; i < headless_pts.rows(); i++) { tifcl();
|
||||||
Vec3d sp = headless_pts.row(i);
|
Vec3d sph = headless_pts.row(i); // Exact support position
|
||||||
|
Vec3d n = headless_norm.row(i); // mesh outward normal
|
||||||
Vec3d n = headless_norm.row(i);
|
Vec3d sp = sph - n * HWIDTH_MM; // stick head start point
|
||||||
sp = sp - n * HWIDTH_MM;
|
|
||||||
|
|
||||||
Vec3d dir = {0, 0, -1};
|
Vec3d dir = {0, 0, -1};
|
||||||
Vec3d sj = sp + R * n;
|
Vec3d sj = sp + R * n; // stick start point
|
||||||
|
|
||||||
|
// This is only for checking
|
||||||
|
double idist = bridge_mesh_intersect(sph, dir, R, emesh, true);
|
||||||
double dist = ray_mesh_intersect(sj, dir, emesh);
|
double dist = ray_mesh_intersect(sj, dir, emesh);
|
||||||
|
|
||||||
if(std::isinf(dist) || std::isnan(dist) || dist < 2*R) continue;
|
if(std::isinf(idist) || std::isnan(idist) || idist < 2*R ||
|
||||||
|
std::isinf(dist) || std::isnan(dist) || dist < 2*R) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "Can not find route for headless"
|
||||||
|
<< " support stick at: "
|
||||||
|
<< sj.transpose();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
Vec3d ej = sj + (dist + HWIDTH_MM)* dir;
|
Vec3d ej = sj + (dist + HWIDTH_MM)* dir;
|
||||||
result.add_compact_bridge(sp, ej, n, R);
|
result.add_compact_bridge(sp, ej, n, R);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
using std::ref;
|
// Now that the individual blocks are defined, lets connect the wires. We
|
||||||
using std::cref;
|
// will create an array of functions which represents a program. Place the
|
||||||
|
// step methods in the array and bind the right arguments to the methods
|
||||||
|
// This way the data dependencies will be easily traceable between
|
||||||
|
// individual steps.
|
||||||
|
// There will be empty steps as well like the begin step or the done or
|
||||||
|
// abort steps. These are slots for future initialization or cleanup.
|
||||||
|
|
||||||
|
using std::cref; // Bind inputs with cref (read-only)
|
||||||
|
using std::ref; // Bind outputs with ref (writable)
|
||||||
using std::bind;
|
using std::bind;
|
||||||
|
|
||||||
// Here we can easily track what goes in and what comes out of each step:
|
// Here we can easily track what goes in and what comes out of each step:
|
||||||
// (see the cref-s as inputs and ref-s as outputs)
|
// (see the cref-s as inputs and ref-s as outputs)
|
||||||
std::array<std::function<void()>, NUM_STEPS> program = {
|
std::array<std::function<void()>, NUM_STEPS> program = {
|
||||||
[] () {
|
[] () {
|
||||||
// Begin
|
// Begin...
|
||||||
// clear up the shared data
|
// Potentially clear up the shared data (not needed for now)
|
||||||
},
|
},
|
||||||
|
|
||||||
// Filtering unnecessary support points
|
// Filtering unnecessary support points
|
||||||
|
@ -1682,6 +1909,7 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||||
|
|
||||||
Steps pc = BEGIN, pc_prev = BEGIN;
|
Steps pc = BEGIN, pc_prev = BEGIN;
|
||||||
|
|
||||||
|
// Let's define a simple automaton that will run our program.
|
||||||
auto progress = [&ctl, &pc, &pc_prev] () {
|
auto progress = [&ctl, &pc, &pc_prev] () {
|
||||||
static const std::array<std::string, NUM_STEPS> stepstr {
|
static const std::array<std::string, NUM_STEPS> stepstr {
|
||||||
"Starting",
|
"Starting",
|
||||||
|
@ -1803,7 +2031,7 @@ SLASupportTree::SLASupportTree(const PointSet &points,
|
||||||
const Controller &ctl):
|
const Controller &ctl):
|
||||||
m_impl(new Impl(ctl))
|
m_impl(new Impl(ctl))
|
||||||
{
|
{
|
||||||
m_impl->ground_level = emesh.ground_level - cfg.object_elevation_mm;
|
m_impl->ground_level = emesh.ground_level() - cfg.object_elevation_mm;
|
||||||
generate(points, emesh, cfg, ctl);
|
generate(points, emesh, cfg, ctl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -81,7 +81,7 @@ struct SupportConfig {
|
||||||
double object_elevation_mm = 10;
|
double object_elevation_mm = 10;
|
||||||
|
|
||||||
// The max Z angle for a normal at which it will get completely ignored.
|
// The max Z angle for a normal at which it will get completely ignored.
|
||||||
double normal_cutoff_angle = 110.0 * M_PI / 180.0;
|
double normal_cutoff_angle = 150.0 * M_PI / 180.0;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -107,10 +107,10 @@ struct Controller {
|
||||||
|
|
||||||
using PointSet = Eigen::MatrixXd;
|
using PointSet = Eigen::MatrixXd;
|
||||||
|
|
||||||
EigenMesh3D to_eigenmesh(const TriangleMesh& m);
|
//EigenMesh3D to_eigenmesh(const TriangleMesh& m);
|
||||||
|
|
||||||
// needed for find best rotation
|
// needed for find best rotation
|
||||||
EigenMesh3D to_eigenmesh(const ModelObject& model);
|
//EigenMesh3D to_eigenmesh(const ModelObject& model);
|
||||||
|
|
||||||
// Simple conversion of 'vector of points' to an Eigen matrix
|
// Simple conversion of 'vector of points' to an Eigen matrix
|
||||||
PointSet to_point_set(const std::vector<sla::SupportPoint>&);
|
PointSet to_point_set(const std::vector<sla::SupportPoint>&);
|
||||||
|
|
|
@ -3,6 +3,9 @@
|
||||||
#include "SLA/SLABoilerPlate.hpp"
|
#include "SLA/SLABoilerPlate.hpp"
|
||||||
#include "SLA/SLASpatIndex.hpp"
|
#include "SLA/SLASpatIndex.hpp"
|
||||||
|
|
||||||
|
// Workaround: IGL signed_distance.h will define PI in the igl namespace.
|
||||||
|
#undef PI
|
||||||
|
|
||||||
// HEAVY headers... takes eternity to compile
|
// HEAVY headers... takes eternity to compile
|
||||||
|
|
||||||
// for concave hull merging decisions
|
// for concave hull merging decisions
|
||||||
|
@ -12,6 +15,7 @@
|
||||||
#include <igl/ray_mesh_intersect.h>
|
#include <igl/ray_mesh_intersect.h>
|
||||||
#include <igl/point_mesh_squared_distance.h>
|
#include <igl/point_mesh_squared_distance.h>
|
||||||
#include <igl/remove_duplicate_vertices.h>
|
#include <igl/remove_duplicate_vertices.h>
|
||||||
|
#include <igl/signed_distance.h>
|
||||||
|
|
||||||
#include "SLASpatIndex.hpp"
|
#include "SLASpatIndex.hpp"
|
||||||
#include "ClipperUtils.hpp"
|
#include "ClipperUtils.hpp"
|
||||||
|
@ -19,6 +23,13 @@
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
namespace sla {
|
namespace sla {
|
||||||
|
|
||||||
|
// Bring back PI from the igl namespace
|
||||||
|
using igl::PI;
|
||||||
|
|
||||||
|
/* **************************************************************************
|
||||||
|
* SpatIndex implementation
|
||||||
|
* ************************************************************************** */
|
||||||
|
|
||||||
class SpatIndex::Impl {
|
class SpatIndex::Impl {
|
||||||
public:
|
public:
|
||||||
using BoostIndex = boost::geometry::index::rtree< SpatElement,
|
using BoostIndex = boost::geometry::index::rtree< SpatElement,
|
||||||
|
@ -78,6 +89,101 @@ size_t SpatIndex::size() const
|
||||||
return m_impl->m_store.size();
|
return m_impl->m_store.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ****************************************************************************
|
||||||
|
* EigenMesh3D implementation
|
||||||
|
* ****************************************************************************/
|
||||||
|
|
||||||
|
class EigenMesh3D::AABBImpl: public igl::AABB<Eigen::MatrixXd, 3> {
|
||||||
|
public:
|
||||||
|
igl::WindingNumberAABB<Vec3d, Eigen::MatrixXd, Eigen::MatrixXi> windtree;
|
||||||
|
};
|
||||||
|
|
||||||
|
EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) {
|
||||||
|
static const double dEPS = 1e-6;
|
||||||
|
|
||||||
|
const stl_file& stl = tmesh.stl;
|
||||||
|
|
||||||
|
auto&& bb = tmesh.bounding_box();
|
||||||
|
m_ground_level += bb.min(Z);
|
||||||
|
|
||||||
|
Eigen::MatrixXd V;
|
||||||
|
Eigen::MatrixXi F;
|
||||||
|
|
||||||
|
V.resize(3*stl.stats.number_of_facets, 3);
|
||||||
|
F.resize(stl.stats.number_of_facets, 3);
|
||||||
|
for (unsigned int i = 0; i < stl.stats.number_of_facets; ++i) {
|
||||||
|
const stl_facet* facet = stl.facet_start+i;
|
||||||
|
V(3*i+0, 0) = double(facet->vertex[0](0));
|
||||||
|
V(3*i+0, 1) = double(facet->vertex[0](1));
|
||||||
|
V(3*i+0, 2) = double(facet->vertex[0](2));
|
||||||
|
|
||||||
|
V(3*i+1, 0) = double(facet->vertex[1](0));
|
||||||
|
V(3*i+1, 1) = double(facet->vertex[1](1));
|
||||||
|
V(3*i+1, 2) = double(facet->vertex[1](2));
|
||||||
|
|
||||||
|
V(3*i+2, 0) = double(facet->vertex[2](0));
|
||||||
|
V(3*i+2, 1) = double(facet->vertex[2](1));
|
||||||
|
V(3*i+2, 2) = double(facet->vertex[2](2));
|
||||||
|
|
||||||
|
F(i, 0) = int(3*i+0);
|
||||||
|
F(i, 1) = int(3*i+1);
|
||||||
|
F(i, 2) = int(3*i+2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We will convert this to a proper 3d mesh with no duplicate points.
|
||||||
|
Eigen::VectorXi SVI, SVJ;
|
||||||
|
igl::remove_duplicate_vertices(V, F, dEPS, m_V, SVI, SVJ, m_F);
|
||||||
|
|
||||||
|
// Build the AABB accelaration tree
|
||||||
|
m_aabb->init(m_V, m_F);
|
||||||
|
m_aabb->windtree.set_mesh(m_V, m_F);
|
||||||
|
}
|
||||||
|
|
||||||
|
EigenMesh3D::~EigenMesh3D() {}
|
||||||
|
|
||||||
|
EigenMesh3D::EigenMesh3D(const EigenMesh3D &other):
|
||||||
|
m_V(other.m_V), m_F(other.m_F), m_ground_level(other.m_ground_level),
|
||||||
|
m_aabb( new AABBImpl(*other.m_aabb) ) {}
|
||||||
|
|
||||||
|
EigenMesh3D &EigenMesh3D::operator=(const EigenMesh3D &other)
|
||||||
|
{
|
||||||
|
m_V = other.m_V;
|
||||||
|
m_F = other.m_F;
|
||||||
|
m_ground_level = other.m_ground_level;
|
||||||
|
m_aabb.reset(new AABBImpl(*other.m_aabb)); return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
EigenMesh3D::hit_result
|
||||||
|
EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
|
||||||
|
{
|
||||||
|
igl::Hit hit;
|
||||||
|
hit.t = std::numeric_limits<float>::infinity();
|
||||||
|
m_aabb->intersect_ray(m_V, m_F, s, dir, hit);
|
||||||
|
|
||||||
|
hit_result ret(*this);
|
||||||
|
ret.m_t = double(hit.t);
|
||||||
|
ret.m_dir = dir;
|
||||||
|
if(!std::isinf(hit.t) && !std::isnan(hit.t)) ret.m_face_id = hit.id;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
EigenMesh3D::si_result EigenMesh3D::signed_distance(const Vec3d &p) const {
|
||||||
|
double sign = 0; double sqdst = 0; int i = 0; Vec3d c;
|
||||||
|
igl::signed_distance_winding_number(*m_aabb, m_V, m_F, m_aabb->windtree,
|
||||||
|
p, sign, sqdst, i, c);
|
||||||
|
|
||||||
|
return si_result(sign * std::sqrt(sqdst), i, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EigenMesh3D::inside(const Vec3d &p) const {
|
||||||
|
return m_aabb->windtree.inside(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ****************************************************************************
|
||||||
|
* Misc functions
|
||||||
|
* ****************************************************************************/
|
||||||
|
|
||||||
bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2,
|
bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2,
|
||||||
double eps = 0.05)
|
double eps = 0.05)
|
||||||
{
|
{
|
||||||
|
@ -93,35 +199,27 @@ template<class Vec> double distance(const Vec& pp1, const Vec& pp2) {
|
||||||
return std::sqrt(p.transpose() * p);
|
return std::sqrt(p.transpose() * p);
|
||||||
}
|
}
|
||||||
|
|
||||||
PointSet normals(const PointSet& points, const EigenMesh3D& emesh,
|
PointSet normals(const PointSet& points, const EigenMesh3D& mesh,
|
||||||
double eps,
|
double eps,
|
||||||
std::function<void()> throw_on_cancel) {
|
std::function<void()> throw_on_cancel) {
|
||||||
if(points.rows() == 0 || emesh.V.rows() == 0 || emesh.F.rows() == 0)
|
if(points.rows() == 0 || mesh.V().rows() == 0 || mesh.F().rows() == 0)
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
Eigen::VectorXd dists;
|
Eigen::VectorXd dists;
|
||||||
Eigen::VectorXi I;
|
Eigen::VectorXi I;
|
||||||
PointSet C;
|
PointSet C;
|
||||||
|
|
||||||
// We need to remove duplicate vertices and have a true index triangle
|
igl::point_mesh_squared_distance( points, mesh.V(), mesh.F(), dists, I, C);
|
||||||
// structure
|
|
||||||
EigenMesh3D mesh;
|
|
||||||
Eigen::VectorXi SVI, SVJ;
|
|
||||||
static const double dEPS = 1e-6;
|
|
||||||
igl::remove_duplicate_vertices(emesh.V, emesh.F, dEPS,
|
|
||||||
mesh.V, SVI, SVJ, mesh.F);
|
|
||||||
|
|
||||||
igl::point_mesh_squared_distance( points, mesh.V, mesh.F, dists, I, C);
|
|
||||||
|
|
||||||
PointSet ret(I.rows(), 3);
|
PointSet ret(I.rows(), 3);
|
||||||
for(int i = 0; i < I.rows(); i++) {
|
for(int i = 0; i < I.rows(); i++) {
|
||||||
throw_on_cancel();
|
throw_on_cancel();
|
||||||
auto idx = I(i);
|
auto idx = I(i);
|
||||||
auto trindex = mesh.F.row(idx);
|
auto trindex = mesh.F().row(idx);
|
||||||
|
|
||||||
const Vec3d& p1 = mesh.V.row(trindex(0));
|
const Vec3d& p1 = mesh.V().row(trindex(0));
|
||||||
const Vec3d& p2 = mesh.V.row(trindex(1));
|
const Vec3d& p2 = mesh.V().row(trindex(1));
|
||||||
const Vec3d& p3 = mesh.V.row(trindex(2));
|
const Vec3d& p3 = mesh.V().row(trindex(2));
|
||||||
|
|
||||||
// We should check if the point lies on an edge of the hosting triangle.
|
// We should check if the point lies on an edge of the hosting triangle.
|
||||||
// If it does than all the other triangles using the same two points
|
// If it does than all the other triangles using the same two points
|
||||||
|
@ -159,18 +257,18 @@ PointSet normals(const PointSet& points, const EigenMesh3D& emesh,
|
||||||
// vector for the neigboring triangles including the detected one.
|
// vector for the neigboring triangles including the detected one.
|
||||||
std::vector<Vec3i> neigh;
|
std::vector<Vec3i> neigh;
|
||||||
if(ic >= 0) { // The point is right on a vertex of the triangle
|
if(ic >= 0) { // The point is right on a vertex of the triangle
|
||||||
for(int n = 0; n < mesh.F.rows(); ++n) {
|
for(int n = 0; n < mesh.F().rows(); ++n) {
|
||||||
throw_on_cancel();
|
throw_on_cancel();
|
||||||
Vec3i ni = mesh.F.row(n);
|
Vec3i ni = mesh.F().row(n);
|
||||||
if((ni(X) == ic || ni(Y) == ic || ni(Z) == ic))
|
if((ni(X) == ic || ni(Y) == ic || ni(Z) == ic))
|
||||||
neigh.emplace_back(ni);
|
neigh.emplace_back(ni);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(ia >= 0 && ib >= 0) { // the point is on and edge
|
else if(ia >= 0 && ib >= 0) { // the point is on and edge
|
||||||
// now get all the neigboring triangles
|
// now get all the neigboring triangles
|
||||||
for(int n = 0; n < mesh.F.rows(); ++n) {
|
for(int n = 0; n < mesh.F().rows(); ++n) {
|
||||||
throw_on_cancel();
|
throw_on_cancel();
|
||||||
Vec3i ni = mesh.F.row(n);
|
Vec3i ni = mesh.F().row(n);
|
||||||
if((ni(X) == ia || ni(Y) == ia || ni(Z) == ia) &&
|
if((ni(X) == ia || ni(Y) == ia || ni(Z) == ia) &&
|
||||||
(ni(X) == ib || ni(Y) == ib || ni(Z) == ib))
|
(ni(X) == ib || ni(Y) == ib || ni(Z) == ib))
|
||||||
neigh.emplace_back(ni);
|
neigh.emplace_back(ni);
|
||||||
|
@ -180,9 +278,9 @@ PointSet normals(const PointSet& points, const EigenMesh3D& emesh,
|
||||||
// Calculate the normals for the neighboring triangles
|
// Calculate the normals for the neighboring triangles
|
||||||
std::vector<Vec3d> neighnorms; neighnorms.reserve(neigh.size());
|
std::vector<Vec3d> neighnorms; neighnorms.reserve(neigh.size());
|
||||||
for(const Vec3i& tri : neigh) {
|
for(const Vec3i& tri : neigh) {
|
||||||
const Vec3d& pt1 = mesh.V.row(tri(0));
|
const Vec3d& pt1 = mesh.V().row(tri(0));
|
||||||
const Vec3d& pt2 = mesh.V.row(tri(1));
|
const Vec3d& pt2 = mesh.V().row(tri(1));
|
||||||
const Vec3d& pt3 = mesh.V.row(tri(2));
|
const Vec3d& pt3 = mesh.V().row(tri(2));
|
||||||
Eigen::Vector3d U = pt2 - pt1;
|
Eigen::Vector3d U = pt2 - pt1;
|
||||||
Eigen::Vector3d V = pt3 - pt1;
|
Eigen::Vector3d V = pt3 - pt1;
|
||||||
neighnorms.emplace_back(U.cross(V).normalized());
|
neighnorms.emplace_back(U.cross(V).normalized());
|
||||||
|
@ -222,16 +320,6 @@ PointSet normals(const PointSet& points, const EigenMesh3D& emesh,
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
double ray_mesh_intersect(const Vec3d& s,
|
|
||||||
const Vec3d& dir,
|
|
||||||
const EigenMesh3D& m)
|
|
||||||
{
|
|
||||||
igl::Hit hit;
|
|
||||||
hit.t = std::numeric_limits<float>::infinity();
|
|
||||||
igl::ray_mesh_intersect(s, dir, m.V, m.F, hit);
|
|
||||||
return double(hit.t);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clustering a set of points by the given criteria
|
// Clustering a set of points by the given criteria
|
||||||
ClusteredPoints cluster(
|
ClusteredPoints cluster(
|
||||||
const sla::PointSet& points,
|
const sla::PointSet& points,
|
||||||
|
@ -309,53 +397,5 @@ ClusteredPoints cluster(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
using Segments = std::vector<std::pair<Vec2d, Vec2d>>;
|
|
||||||
|
|
||||||
Segments model_boundary(const EigenMesh3D& emesh, double offs)
|
|
||||||
{
|
|
||||||
Segments ret;
|
|
||||||
Polygons pp;
|
|
||||||
pp.reserve(size_t(emesh.F.rows()));
|
|
||||||
|
|
||||||
for (int i = 0; i < emesh.F.rows(); i++) {
|
|
||||||
auto trindex = emesh.F.row(i);
|
|
||||||
auto& p1 = emesh.V.row(trindex(0));
|
|
||||||
auto& p2 = emesh.V.row(trindex(1));
|
|
||||||
auto& p3 = emesh.V.row(trindex(2));
|
|
||||||
|
|
||||||
Polygon p;
|
|
||||||
p.points.resize(3);
|
|
||||||
p.points[0] = Point::new_scale(p1(X), p1(Y));
|
|
||||||
p.points[1] = Point::new_scale(p2(X), p2(Y));
|
|
||||||
p.points[2] = Point::new_scale(p3(X), p3(Y));
|
|
||||||
p.make_counter_clockwise();
|
|
||||||
pp.emplace_back(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
ExPolygons merged = union_ex(Slic3r::offset(pp, float(scale_(offs))), true);
|
|
||||||
|
|
||||||
for(auto& expoly : merged) {
|
|
||||||
auto lines = expoly.lines();
|
|
||||||
for(Line& l : lines) {
|
|
||||||
Vec2d a(l.a(X) * SCALING_FACTOR, l.a(Y) * SCALING_FACTOR);
|
|
||||||
Vec2d b(l.b(X) * SCALING_FACTOR, l.b(Y) * SCALING_FACTOR);
|
|
||||||
ret.emplace_back(std::make_pair(a, b));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
//struct SegmentIndex {
|
|
||||||
|
|
||||||
//};
|
|
||||||
|
|
||||||
//using SegmentIndexEl = std::pair<Segment, unsigned>;
|
|
||||||
|
|
||||||
//SegmentIndexEl
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,8 @@ public:
|
||||||
SupportTreePtr support_tree_ptr; // the supports
|
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;
|
||||||
|
|
||||||
|
inline SupportData(const TriangleMesh& trmesh): emesh(trmesh) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -503,8 +505,8 @@ void SLAPrint::process()
|
||||||
// support points. Then we sprinkle the rest of the mesh.
|
// support points. Then we sprinkle the rest of the mesh.
|
||||||
auto support_points = [this, ilh](SLAPrintObject& po) {
|
auto support_points = [this, ilh](SLAPrintObject& po) {
|
||||||
const ModelObject& mo = *po.m_model_object;
|
const ModelObject& mo = *po.m_model_object;
|
||||||
po.m_supportdata.reset(new SLAPrintObject::SupportData());
|
po.m_supportdata.reset(
|
||||||
po.m_supportdata->emesh = sla::to_eigenmesh(po.transformed_mesh());
|
new SLAPrintObject::SupportData(po.transformed_mesh()) );
|
||||||
|
|
||||||
// If supports are disabled, we can skip the model scan.
|
// If supports are disabled, we can skip the model scan.
|
||||||
if(!po.m_config.supports_enable.getBool()) return;
|
if(!po.m_config.supports_enable.getBool()) return;
|
||||||
|
|
|
@ -30,8 +30,6 @@
|
||||||
//====================
|
//====================
|
||||||
#define ENABLE_1_42_0_ALPHA2 1
|
#define ENABLE_1_42_0_ALPHA2 1
|
||||||
|
|
||||||
// Improves navigation between sidebar fields
|
|
||||||
#define ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION (1 && ENABLE_1_42_0_ALPHA2)
|
|
||||||
// Adds print bed models to 3D scene
|
// Adds print bed models to 3D scene
|
||||||
#define ENABLE_PRINT_BED_MODELS (1 && ENABLE_1_42_0_ALPHA2)
|
#define ENABLE_PRINT_BED_MODELS (1 && ENABLE_1_42_0_ALPHA2)
|
||||||
#endif // _technologies_h_
|
#endif // _technologies_h_
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
#include "Technologies.hpp"
|
#include "Technologies.hpp"
|
||||||
|
|
||||||
|
@ -164,6 +165,12 @@ static inline T lerp(const T& a, const T& b, Number t)
|
||||||
return (Number(1) - t) * a + t * b;
|
return (Number(1) - t) * a + t * b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename Number>
|
||||||
|
static inline bool is_approx(Number value, Number test_value)
|
||||||
|
{
|
||||||
|
return std::fabs(double(value) - double(test_value)) < double(EPSILON);
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1617,6 +1617,9 @@ void GLCanvas3D::Selection::clear()
|
||||||
|
|
||||||
_update_type();
|
_update_type();
|
||||||
m_bounding_box_dirty = true;
|
m_bounding_box_dirty = true;
|
||||||
|
|
||||||
|
// resets the cache in the sidebar
|
||||||
|
wxGetApp().obj_manipul()->reset_cache();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the selection based on the map from old indices to new indices after m_volumes changed.
|
// Update the selection based on the map from old indices to new indices after m_volumes changed.
|
||||||
|
@ -1817,7 +1820,7 @@ void GLCanvas3D::Selection::rotate(const Vec3d& rotation, bool local)
|
||||||
if (rot_axis_max != 2 && first_volume_idx != -1) {
|
if (rot_axis_max != 2 && first_volume_idx != -1) {
|
||||||
// Generic rotation, but no rotation around the Z axis.
|
// Generic rotation, but no rotation around the Z axis.
|
||||||
// Always do a local rotation (do not consider the selection to be a rigid body).
|
// Always do a local rotation (do not consider the selection to be a rigid body).
|
||||||
assert(rotation.z() == 0);
|
assert(is_approx(rotation.z(), 0.0));
|
||||||
const GLVolume &first_volume = *(*m_volumes)[first_volume_idx];
|
const GLVolume &first_volume = *(*m_volumes)[first_volume_idx];
|
||||||
const Vec3d &rotation = first_volume.get_instance_rotation();
|
const Vec3d &rotation = first_volume.get_instance_rotation();
|
||||||
double z_diff = rotation_diff_z(m_cache.volumes_data[first_volume_idx].get_instance_rotation(), m_cache.volumes_data[i].get_instance_rotation());
|
double z_diff = rotation_diff_z(m_cache.volumes_data[first_volume_idx].get_instance_rotation(), m_cache.volumes_data[i].get_instance_rotation());
|
||||||
|
@ -1842,7 +1845,7 @@ void GLCanvas3D::Selection::rotate(const Vec3d& rotation, bool local)
|
||||||
else if (is_single_volume() || is_single_modifier())
|
else if (is_single_volume() || is_single_modifier())
|
||||||
{
|
{
|
||||||
if (local)
|
if (local)
|
||||||
volume.set_volume_rotation(rotation);
|
volume.set_volume_rotation(volume.get_volume_rotation() + rotation);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation);
|
Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation);
|
||||||
|
@ -2259,7 +2262,7 @@ void GLCanvas3D::Selection::render_sidebar_hints(const std::string& sidebar_fiel
|
||||||
}
|
}
|
||||||
else if (is_single_volume() || is_single_modifier())
|
else if (is_single_volume() || is_single_modifier())
|
||||||
{
|
{
|
||||||
Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
|
Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true) * (*m_volumes)[*m_list.begin()]->get_volume_transformation().get_matrix(true, false, true, true);
|
||||||
::glTranslated(center(0), center(1), center(2));
|
::glTranslated(center(0), center(1), center(2));
|
||||||
::glMultMatrixd(orient_matrix.data());
|
::glMultMatrixd(orient_matrix.data());
|
||||||
}
|
}
|
||||||
|
@ -3987,10 +3990,8 @@ wxDEFINE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent);
|
||||||
wxDEFINE_EVENT(EVT_GLCANVAS_QUESTION_MARK, SimpleEvent);
|
wxDEFINE_EVENT(EVT_GLCANVAS_QUESTION_MARK, SimpleEvent);
|
||||||
wxDEFINE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event<int>);
|
wxDEFINE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event<int>);
|
||||||
wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent);
|
wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent);
|
||||||
#if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_ROTATED, SimpleEvent);
|
wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_ROTATED, SimpleEvent);
|
||||||
wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_SCALED, SimpleEvent);
|
wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_SCALED, SimpleEvent);
|
||||||
#endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
wxDEFINE_EVENT(EVT_GLCANVAS_WIPETOWER_MOVED, Vec3dEvent);
|
wxDEFINE_EVENT(EVT_GLCANVAS_WIPETOWER_MOVED, Vec3dEvent);
|
||||||
wxDEFINE_EVENT(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, Event<bool>);
|
wxDEFINE_EVENT(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, Event<bool>);
|
||||||
wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_GEOMETRY, Vec3dsEvent<2>);
|
wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_GEOMETRY, Vec3dsEvent<2>);
|
||||||
|
@ -5375,9 +5376,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||||
bool already_selected = m_selection.contains_volume(m_hover_volume_id);
|
bool already_selected = m_selection.contains_volume(m_hover_volume_id);
|
||||||
bool shift_down = evt.ShiftDown();
|
bool shift_down = evt.ShiftDown();
|
||||||
|
|
||||||
#if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
Selection::IndicesList curr_idxs = m_selection.get_volume_idxs();
|
Selection::IndicesList curr_idxs = m_selection.get_volume_idxs();
|
||||||
#endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
|
|
||||||
if (already_selected && shift_down)
|
if (already_selected && shift_down)
|
||||||
m_selection.remove(m_hover_volume_id);
|
m_selection.remove(m_hover_volume_id);
|
||||||
|
@ -5394,21 +5393,14 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||||
#endif // ENABLE_MOVE_MIN_THRESHOLD
|
#endif // ENABLE_MOVE_MIN_THRESHOLD
|
||||||
}
|
}
|
||||||
|
|
||||||
#if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
if (curr_idxs != m_selection.get_volume_idxs())
|
if (curr_idxs != m_selection.get_volume_idxs())
|
||||||
{
|
{
|
||||||
#endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
|
|
||||||
m_gizmos.update_on_off_state(m_selection);
|
m_gizmos.update_on_off_state(m_selection);
|
||||||
_update_gizmos_data();
|
_update_gizmos_data();
|
||||||
#if !ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
wxGetApp().obj_manipul()->update_settings_value(m_selection);
|
|
||||||
#endif // !ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT));
|
post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT));
|
||||||
m_dirty = true;
|
m_dirty = true;
|
||||||
#if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
}
|
}
|
||||||
#endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5803,7 +5795,6 @@ void GLCanvas3D::do_move()
|
||||||
ModelObject* model_object = m_model->objects[object_idx];
|
ModelObject* model_object = m_model->objects[object_idx];
|
||||||
if (model_object != nullptr)
|
if (model_object != nullptr)
|
||||||
{
|
{
|
||||||
#if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
if (selection_mode == Selection::Instance)
|
if (selection_mode == Selection::Instance)
|
||||||
model_object->instances[instance_idx]->set_offset(v->get_instance_offset());
|
model_object->instances[instance_idx]->set_offset(v->get_instance_offset());
|
||||||
else if (selection_mode == Selection::Volume)
|
else if (selection_mode == Selection::Volume)
|
||||||
|
@ -5811,20 +5802,6 @@ void GLCanvas3D::do_move()
|
||||||
|
|
||||||
object_moved = true;
|
object_moved = true;
|
||||||
model_object->invalidate_bounding_box();
|
model_object->invalidate_bounding_box();
|
||||||
#else
|
|
||||||
if (selection_mode == Selection::Instance)
|
|
||||||
{
|
|
||||||
model_object->instances[instance_idx]->set_offset(v->get_instance_offset());
|
|
||||||
object_moved = true;
|
|
||||||
}
|
|
||||||
else if (selection_mode == Selection::Volume)
|
|
||||||
{
|
|
||||||
model_object->volumes[volume_idx]->set_offset(v->get_volume_offset());
|
|
||||||
object_moved = true;
|
|
||||||
}
|
|
||||||
if (object_moved)
|
|
||||||
model_object->invalidate_bounding_box();
|
|
||||||
#endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (object_idx == 1000)
|
else if (object_idx == 1000)
|
||||||
|
@ -5895,12 +5872,8 @@ void GLCanvas3D::do_rotate()
|
||||||
m->translate_instance(i.second, shift);
|
m->translate_instance(i.second, shift);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
if (!done.empty())
|
if (!done.empty())
|
||||||
post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED));
|
post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED));
|
||||||
#else
|
|
||||||
post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
|
|
||||||
#endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLCanvas3D::do_scale()
|
void GLCanvas3D::do_scale()
|
||||||
|
@ -5951,12 +5924,8 @@ void GLCanvas3D::do_scale()
|
||||||
m->translate_instance(i.second, shift);
|
m->translate_instance(i.second, shift);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
if (!done.empty())
|
if (!done.empty())
|
||||||
post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED));
|
post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED));
|
||||||
#else
|
|
||||||
post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
|
|
||||||
#endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLCanvas3D::do_flatten()
|
void GLCanvas3D::do_flatten()
|
||||||
|
|
|
@ -125,10 +125,8 @@ wxDECLARE_EVENT(EVT_GLCANVAS_QUESTION_MARK, SimpleEvent);
|
||||||
wxDECLARE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event<int>); // data: +1 => increase, -1 => decrease
|
wxDECLARE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event<int>); // data: +1 => increase, -1 => decrease
|
||||||
wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent);
|
wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent);
|
||||||
wxDECLARE_EVENT(EVT_GLCANVAS_WIPETOWER_MOVED, Vec3dEvent);
|
wxDECLARE_EVENT(EVT_GLCANVAS_WIPETOWER_MOVED, Vec3dEvent);
|
||||||
#if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_ROTATED, SimpleEvent);
|
wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_ROTATED, SimpleEvent);
|
||||||
wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_SCALED, SimpleEvent);
|
wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_SCALED, SimpleEvent);
|
||||||
#endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
wxDECLARE_EVENT(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, Event<bool>);
|
wxDECLARE_EVENT(EVT_GLCANVAS_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);
|
||||||
|
|
|
@ -66,6 +66,12 @@ ObjectList::ObjectList(wxWindow* parent) :
|
||||||
|
|
||||||
// describe control behavior
|
// describe control behavior
|
||||||
Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxEvent& event) {
|
Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxEvent& event) {
|
||||||
|
#ifndef __APPLE__
|
||||||
|
// On Windows and Linux, forces a kill focus emulation on the object manipulator fields because this event handler is called
|
||||||
|
// before the kill focus event handler on the object manipulator when changing selection in the list, invalidating the object
|
||||||
|
// manipulator cache with the following call to selection_changed()
|
||||||
|
wxGetApp().obj_manipul()->emulate_kill_focus();
|
||||||
|
#endif // __APPLE__
|
||||||
selection_changed();
|
selection_changed();
|
||||||
#ifndef __WXMSW__
|
#ifndef __WXMSW__
|
||||||
set_tooltip_for_item(get_mouse_position_in_control());
|
set_tooltip_for_item(get_mouse_position_in_control());
|
||||||
|
|
|
@ -17,103 +17,24 @@ namespace GUI
|
||||||
|
|
||||||
ObjectManipulation::ObjectManipulation(wxWindow* parent) :
|
ObjectManipulation::ObjectManipulation(wxWindow* parent) :
|
||||||
OG_Settings(parent, true)
|
OG_Settings(parent, true)
|
||||||
|
#ifndef __APPLE__
|
||||||
|
, m_focused_option("")
|
||||||
|
#endif // __APPLE__
|
||||||
{
|
{
|
||||||
m_og->set_name(_(L("Object Manipulation")));
|
m_og->set_name(_(L("Object Manipulation")));
|
||||||
m_og->label_width = 125;
|
m_og->label_width = 125;
|
||||||
m_og->set_grid_vgap(5);
|
m_og->set_grid_vgap(5);
|
||||||
|
|
||||||
m_og->m_on_change = [this](const std::string& opt_key, const boost::any& value) {
|
m_og->m_on_change = std::bind(&ObjectManipulation::on_change, this, std::placeholders::_1, std::placeholders::_2);
|
||||||
std::vector<std::string> axes{ "_x", "_y", "_z" };
|
m_og->m_fill_empty_value = std::bind(&ObjectManipulation::on_fill_empty_value, this, std::placeholders::_1);
|
||||||
|
|
||||||
std::string param;
|
|
||||||
std::copy(opt_key.begin(), opt_key.end() - 2, std::back_inserter(param));
|
|
||||||
|
|
||||||
size_t i = 0;
|
|
||||||
Vec3d new_value;
|
|
||||||
for (auto axis : axes)
|
|
||||||
new_value(i++) = boost::any_cast<double>(m_og->get_value(param+axis));
|
|
||||||
|
|
||||||
if (param == "position")
|
|
||||||
change_position_value(new_value);
|
|
||||||
else if (param == "rotation")
|
|
||||||
change_rotation_value(new_value);
|
|
||||||
else if (param == "scale")
|
|
||||||
#if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
change_scale_value(new_value);
|
|
||||||
#else
|
|
||||||
change_scale_value(new_value);
|
|
||||||
#endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
else if (param == "size")
|
|
||||||
#if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
change_size_value(new_value);
|
|
||||||
#else
|
|
||||||
change_size_value(new_value);
|
|
||||||
#endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
|
|
||||||
wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key, false);
|
|
||||||
};
|
|
||||||
|
|
||||||
m_og->m_fill_empty_value = [this](const std::string& opt_key)
|
|
||||||
{
|
|
||||||
#if !ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
this->update_if_dirty();
|
|
||||||
#endif // !ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
|
|
||||||
std::string param;
|
|
||||||
std::copy(opt_key.begin(), opt_key.end() - 2, std::back_inserter(param));
|
|
||||||
|
|
||||||
double value = 0.0;
|
|
||||||
|
|
||||||
if (param == "position") {
|
|
||||||
int axis = opt_key.back() == 'x' ? 0 :
|
|
||||||
opt_key.back() == 'y' ? 1 : 2;
|
|
||||||
|
|
||||||
#if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
value = m_cache.position(axis);
|
|
||||||
#else
|
|
||||||
value = m_cache_position(axis);
|
|
||||||
#endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
}
|
|
||||||
else if (param == "rotation") {
|
|
||||||
int axis = opt_key.back() == 'x' ? 0 :
|
|
||||||
opt_key.back() == 'y' ? 1 : 2;
|
|
||||||
|
|
||||||
#if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
value = m_cache.rotation(axis);
|
|
||||||
#else
|
|
||||||
value = m_cache_rotation(axis);
|
|
||||||
#endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
}
|
|
||||||
else if (param == "scale") {
|
|
||||||
int axis = opt_key.back() == 'x' ? 0 :
|
|
||||||
opt_key.back() == 'y' ? 1 : 2;
|
|
||||||
|
|
||||||
#if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
value = m_cache.scale(axis);
|
|
||||||
#else
|
|
||||||
value = m_cache_scale(axis);
|
|
||||||
#endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
}
|
|
||||||
else if (param == "size") {
|
|
||||||
int axis = opt_key.back() == 'x' ? 0 :
|
|
||||||
opt_key.back() == 'y' ? 1 : 2;
|
|
||||||
|
|
||||||
#if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
value = m_cache.size(axis);
|
|
||||||
#else
|
|
||||||
value = m_cache_size(axis);
|
|
||||||
#endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
}
|
|
||||||
|
|
||||||
m_og->set_value(opt_key, double_to_string(value));
|
|
||||||
wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key, false);
|
|
||||||
};
|
|
||||||
|
|
||||||
m_og->m_set_focus = [this](const std::string& opt_key)
|
m_og->m_set_focus = [this](const std::string& opt_key)
|
||||||
{
|
{
|
||||||
#if !ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
#ifndef __APPLE__
|
||||||
this->update_if_dirty();
|
m_focused_option = opt_key;
|
||||||
#endif // !ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
#endif // __APPLE__
|
||||||
|
|
||||||
|
// needed to show the visual hints in 3D scene
|
||||||
wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key, true);
|
wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -233,9 +154,6 @@ void ObjectManipulation::UpdateAndShow(const bool show)
|
||||||
{
|
{
|
||||||
if (show) {
|
if (show) {
|
||||||
update_settings_value(wxGetApp().plater()->canvas3D()->get_selection());
|
update_settings_value(wxGetApp().plater()->canvas3D()->get_selection());
|
||||||
#if !ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
update_if_dirty();
|
|
||||||
#endif // !ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
}
|
}
|
||||||
|
|
||||||
OG_Settings::UpdateAndShow(show);
|
OG_Settings::UpdateAndShow(show);
|
||||||
|
@ -254,7 +172,6 @@ void ObjectManipulation::update_settings_value(const GLCanvas3D::Selection& sele
|
||||||
m_new_rotation = volume->get_instance_rotation();
|
m_new_rotation = volume->get_instance_rotation();
|
||||||
m_new_scale = volume->get_instance_scaling_factor();
|
m_new_scale = volume->get_instance_scaling_factor();
|
||||||
int obj_idx = volume->object_idx();
|
int obj_idx = volume->object_idx();
|
||||||
#if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
int instance_idx = volume->instance_idx();
|
int instance_idx = volume->instance_idx();
|
||||||
if ((0 <= obj_idx) && (obj_idx < (int)wxGetApp().model_objects()->size()))
|
if ((0 <= obj_idx) && (obj_idx < (int)wxGetApp().model_objects()->size()))
|
||||||
{
|
{
|
||||||
|
@ -270,21 +187,12 @@ void ObjectManipulation::update_settings_value(const GLCanvas3D::Selection& sele
|
||||||
else
|
else
|
||||||
// this should never happen
|
// this should never happen
|
||||||
m_new_size = Vec3d::Zero();
|
m_new_size = Vec3d::Zero();
|
||||||
#else
|
|
||||||
if ((0 <= obj_idx) && (obj_idx < (int)wxGetApp().model_objects()->size()))
|
|
||||||
m_new_size = volume->get_instance_transformation().get_matrix(true, true) * (*wxGetApp().model_objects())[obj_idx]->raw_mesh_bounding_box().size();
|
|
||||||
else
|
|
||||||
// this should never happen
|
|
||||||
m_new_size = Vec3d::Zero();
|
|
||||||
#endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
|
|
||||||
m_new_enabled = true;
|
m_new_enabled = true;
|
||||||
}
|
}
|
||||||
else if (selection.is_single_full_object())
|
else if (selection.is_single_full_object())
|
||||||
{
|
{
|
||||||
#if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
m_cache.instance.reset();
|
m_cache.instance.reset();
|
||||||
#endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
|
|
||||||
const BoundingBoxf3& box = selection.get_bounding_box();
|
const BoundingBoxf3& box = selection.get_bounding_box();
|
||||||
m_new_position = box.center();
|
m_new_position = box.center();
|
||||||
|
@ -297,9 +205,7 @@ void ObjectManipulation::update_settings_value(const GLCanvas3D::Selection& sele
|
||||||
}
|
}
|
||||||
else if (selection.is_single_modifier() || selection.is_single_volume())
|
else if (selection.is_single_modifier() || selection.is_single_volume())
|
||||||
{
|
{
|
||||||
#if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
m_cache.instance.reset();
|
m_cache.instance.reset();
|
||||||
#endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
|
|
||||||
// the selection contains a single volume
|
// the selection contains a single volume
|
||||||
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
|
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
|
||||||
|
@ -329,7 +235,6 @@ void ObjectManipulation::update_if_dirty()
|
||||||
if (!m_dirty)
|
if (!m_dirty)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
#if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
if (m_cache.move_label_string != _(m_new_move_label_string)+ ":")
|
if (m_cache.move_label_string != _(m_new_move_label_string)+ ":")
|
||||||
{
|
{
|
||||||
m_cache.move_label_string = _(m_new_move_label_string)+ ":";
|
m_cache.move_label_string = _(m_new_move_label_string)+ ":";
|
||||||
|
@ -382,16 +287,22 @@ void ObjectManipulation::update_if_dirty()
|
||||||
|
|
||||||
m_cache.size = m_new_size;
|
m_cache.size = m_new_size;
|
||||||
|
|
||||||
|
Vec3d deg_rotation;
|
||||||
|
for (size_t i = 0; i < 3; ++i)
|
||||||
|
{
|
||||||
|
deg_rotation(i) = Geometry::rad2deg(m_new_rotation(i));
|
||||||
|
}
|
||||||
|
|
||||||
if (m_cache.rotation(0) != m_new_rotation(0))
|
if (m_cache.rotation(0) != m_new_rotation(0))
|
||||||
m_og->set_value("rotation_x", double_to_string(Geometry::rad2deg(m_new_rotation(0)), 2));
|
m_og->set_value("rotation_x", double_to_string(deg_rotation(0), 2));
|
||||||
|
|
||||||
if (m_cache.rotation(1) != m_new_rotation(1))
|
if (m_cache.rotation(1) != m_new_rotation(1))
|
||||||
m_og->set_value("rotation_y", double_to_string(Geometry::rad2deg(m_new_rotation(1)), 2));
|
m_og->set_value("rotation_y", double_to_string(deg_rotation(1), 2));
|
||||||
|
|
||||||
if (m_cache.rotation(2) != m_new_rotation(2))
|
if (m_cache.rotation(2) != m_new_rotation(2))
|
||||||
m_og->set_value("rotation_z", double_to_string(Geometry::rad2deg(m_new_rotation(2)), 2));
|
m_og->set_value("rotation_z", double_to_string(deg_rotation(2), 2));
|
||||||
|
|
||||||
m_cache.rotation = m_new_rotation;
|
m_cache.rotation = deg_rotation;
|
||||||
|
|
||||||
if (wxGetApp().plater()->canvas3D()->get_selection().requires_uniform_scale()) {
|
if (wxGetApp().plater()->canvas3D()->get_selection().requires_uniform_scale()) {
|
||||||
m_lock_bnt->SetLock(true);
|
m_lock_bnt->SetLock(true);
|
||||||
|
@ -406,41 +317,27 @@ void ObjectManipulation::update_if_dirty()
|
||||||
m_og->enable();
|
m_og->enable();
|
||||||
else
|
else
|
||||||
m_og->disable();
|
m_og->disable();
|
||||||
#else
|
|
||||||
m_move_Label->SetLabel(_(m_new_move_label_string));
|
|
||||||
m_rotate_Label->SetLabel(_(m_new_rotate_label_string));
|
|
||||||
m_scale_Label->SetLabel(_(m_new_scale_label_string));
|
|
||||||
|
|
||||||
m_og->set_value("position_x", double_to_string(m_new_position(0), 2));
|
|
||||||
m_og->set_value("position_y", double_to_string(m_new_position(1), 2));
|
|
||||||
m_og->set_value("position_z", double_to_string(m_new_position(2), 2));
|
|
||||||
m_cache_position = m_new_position;
|
|
||||||
|
|
||||||
auto scale = m_new_scale * 100.0;
|
|
||||||
m_og->set_value("scale_x", double_to_string(scale(0), 2));
|
|
||||||
m_og->set_value("scale_y", double_to_string(scale(1), 2));
|
|
||||||
m_og->set_value("scale_z", double_to_string(scale(2), 2));
|
|
||||||
m_cache_scale = scale;
|
|
||||||
|
|
||||||
m_og->set_value("size_x", double_to_string(m_new_size(0), 2));
|
|
||||||
m_og->set_value("size_y", double_to_string(m_new_size(1), 2));
|
|
||||||
m_og->set_value("size_z", double_to_string(m_new_size(2), 2));
|
|
||||||
m_cache_size = m_new_size;
|
|
||||||
|
|
||||||
m_og->set_value("rotation_x", double_to_string(round_nearest(Geometry::rad2deg(m_new_rotation(0)), 0), 2));
|
|
||||||
m_og->set_value("rotation_y", double_to_string(round_nearest(Geometry::rad2deg(m_new_rotation(1)), 0), 2));
|
|
||||||
m_og->set_value("rotation_z", double_to_string(round_nearest(Geometry::rad2deg(m_new_rotation(2)), 0), 2));
|
|
||||||
m_cache_rotation = m_new_rotation;
|
|
||||||
|
|
||||||
if (m_new_enabled)
|
|
||||||
m_og->enable();
|
|
||||||
else
|
|
||||||
m_og->disable();
|
|
||||||
#endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
|
|
||||||
m_dirty = false;
|
m_dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef __APPLE__
|
||||||
|
void ObjectManipulation::emulate_kill_focus()
|
||||||
|
{
|
||||||
|
if (m_focused_option.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// we need to use a copy because the value of m_focused_option is modified inside on_change() and on_fill_empty_value()
|
||||||
|
std::string option = m_focused_option;
|
||||||
|
|
||||||
|
// see TextCtrl::propagate_value()
|
||||||
|
if (static_cast<wxTextCtrl*>(m_og->get_fieldc(option, 0)->getWindow())->GetValue().empty())
|
||||||
|
on_fill_empty_value(option);
|
||||||
|
else
|
||||||
|
on_change(option, 0);
|
||||||
|
}
|
||||||
|
#endif // __APPLE__
|
||||||
|
|
||||||
void ObjectManipulation::reset_settings_value()
|
void ObjectManipulation::reset_settings_value()
|
||||||
{
|
{
|
||||||
m_new_position = Vec3d::Zero();
|
m_new_position = Vec3d::Zero();
|
||||||
|
@ -448,9 +345,7 @@ void ObjectManipulation::reset_settings_value()
|
||||||
m_new_scale = Vec3d::Ones();
|
m_new_scale = Vec3d::Ones();
|
||||||
m_new_size = Vec3d::Zero();
|
m_new_size = Vec3d::Zero();
|
||||||
m_new_enabled = false;
|
m_new_enabled = false;
|
||||||
#if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
m_cache.instance.reset();
|
m_cache.instance.reset();
|
||||||
#endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
m_dirty = true;
|
m_dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -459,18 +354,10 @@ void ObjectManipulation::change_position_value(const Vec3d& position)
|
||||||
auto canvas = wxGetApp().plater()->canvas3D();
|
auto canvas = wxGetApp().plater()->canvas3D();
|
||||||
GLCanvas3D::Selection& selection = canvas->get_selection();
|
GLCanvas3D::Selection& selection = canvas->get_selection();
|
||||||
selection.start_dragging();
|
selection.start_dragging();
|
||||||
#if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
selection.translate(position - m_cache.position, selection.requires_local_axes());
|
selection.translate(position - m_cache.position, selection.requires_local_axes());
|
||||||
#else
|
|
||||||
selection.translate(position - m_cache_position, selection.requires_local_axes());
|
|
||||||
#endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
canvas->do_move();
|
canvas->do_move();
|
||||||
|
|
||||||
#if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
m_cache.position = position;
|
m_cache.position = position;
|
||||||
#else
|
|
||||||
m_cache_position = position;
|
|
||||||
#endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ObjectManipulation::change_rotation_value(const Vec3d& rotation)
|
void ObjectManipulation::change_rotation_value(const Vec3d& rotation)
|
||||||
|
@ -478,23 +365,19 @@ void ObjectManipulation::change_rotation_value(const Vec3d& rotation)
|
||||||
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
|
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
|
||||||
const GLCanvas3D::Selection& selection = canvas->get_selection();
|
const GLCanvas3D::Selection& selection = canvas->get_selection();
|
||||||
|
|
||||||
|
Vec3d delta_rotation = rotation - m_cache.rotation;
|
||||||
|
|
||||||
Vec3d rad_rotation;
|
Vec3d rad_rotation;
|
||||||
for (size_t i = 0; i < 3; ++i)
|
for (size_t i = 0; i < 3; ++i)
|
||||||
{
|
{
|
||||||
rad_rotation(i) = Geometry::deg2rad(rotation(i));
|
rad_rotation(i) = Geometry::deg2rad(delta_rotation(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas->get_selection().start_dragging();
|
canvas->get_selection().start_dragging();
|
||||||
#if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
canvas->get_selection().rotate(rad_rotation, selection.is_single_full_instance() || selection.requires_local_axes());
|
canvas->get_selection().rotate(rad_rotation, selection.is_single_full_instance() || selection.requires_local_axes());
|
||||||
#else
|
|
||||||
canvas->get_selection().rotate(rad_rotation, selection.is_single_full_instance());
|
|
||||||
#endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
canvas->do_rotate();
|
canvas->do_rotate();
|
||||||
|
|
||||||
#if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
m_cache.rotation = rotation;
|
m_cache.rotation = rotation;
|
||||||
#endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ObjectManipulation::change_scale_value(const Vec3d& scale)
|
void ObjectManipulation::change_scale_value(const Vec3d& scale)
|
||||||
|
@ -503,11 +386,7 @@ void ObjectManipulation::change_scale_value(const Vec3d& scale)
|
||||||
const GLCanvas3D::Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
|
const GLCanvas3D::Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
|
||||||
if (m_uniform_scale || selection.requires_uniform_scale())
|
if (m_uniform_scale || selection.requires_uniform_scale())
|
||||||
{
|
{
|
||||||
#if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
Vec3d abs_scale_diff = (scale - m_cache.scale).cwiseAbs();
|
Vec3d abs_scale_diff = (scale - m_cache.scale).cwiseAbs();
|
||||||
#else
|
|
||||||
Vec3d abs_scale_diff = (scale - m_cache_scale).cwiseAbs();
|
|
||||||
#endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
double max_diff = abs_scale_diff(X);
|
double max_diff = abs_scale_diff(X);
|
||||||
Axis max_diff_axis = X;
|
Axis max_diff_axis = X;
|
||||||
if (max_diff < abs_scale_diff(Y))
|
if (max_diff < abs_scale_diff(Y))
|
||||||
|
@ -530,12 +409,10 @@ void ObjectManipulation::change_scale_value(const Vec3d& scale)
|
||||||
canvas->get_selection().scale(scaling_factor, false);
|
canvas->get_selection().scale(scaling_factor, false);
|
||||||
canvas->do_scale();
|
canvas->do_scale();
|
||||||
|
|
||||||
#if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
if (!m_cache.scale.isApprox(scale))
|
if (!m_cache.scale.isApprox(scale))
|
||||||
m_cache.instance.instance_idx = -1;
|
m_cache.instance.instance_idx = -1;
|
||||||
|
|
||||||
m_cache.scale = scale;
|
m_cache.scale = scale;
|
||||||
#endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ObjectManipulation::change_size_value(const Vec3d& size)
|
void ObjectManipulation::change_size_value(const Vec3d& size)
|
||||||
|
@ -543,7 +420,6 @@ void ObjectManipulation::change_size_value(const Vec3d& size)
|
||||||
const GLCanvas3D::Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
|
const GLCanvas3D::Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
|
||||||
|
|
||||||
Vec3d ref_size = m_cache.size;
|
Vec3d ref_size = m_cache.size;
|
||||||
#if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
if (selection.is_single_volume() || selection.is_single_modifier())
|
if (selection.is_single_volume() || selection.is_single_modifier())
|
||||||
{
|
{
|
||||||
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
|
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
|
||||||
|
@ -581,15 +457,81 @@ void ObjectManipulation::change_size_value(const Vec3d& size)
|
||||||
canvas->do_scale();
|
canvas->do_scale();
|
||||||
|
|
||||||
m_cache.size = size;
|
m_cache.size = size;
|
||||||
#else
|
}
|
||||||
if (selection.is_single_full_instance())
|
|
||||||
{
|
void ObjectManipulation::on_change(const t_config_option_key& opt_key, const boost::any& value)
|
||||||
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
|
{
|
||||||
ref_size = volume->bounding_box.size();
|
// needed to hide the visual hints in 3D scene
|
||||||
|
wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key, false);
|
||||||
|
#ifndef __APPLE__
|
||||||
|
m_focused_option = "";
|
||||||
|
#endif // __APPLE__
|
||||||
|
|
||||||
|
if (!m_cache.is_valid())
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::vector<std::string> axes{ "_x", "_y", "_z" };
|
||||||
|
|
||||||
|
std::string param;
|
||||||
|
std::copy(opt_key.begin(), opt_key.end() - 2, std::back_inserter(param));
|
||||||
|
|
||||||
|
size_t i = 0;
|
||||||
|
Vec3d new_value;
|
||||||
|
for (auto axis : axes)
|
||||||
|
new_value(i++) = boost::any_cast<double>(m_og->get_value(param + axis));
|
||||||
|
|
||||||
|
if (param == "position")
|
||||||
|
change_position_value(new_value);
|
||||||
|
else if (param == "rotation")
|
||||||
|
change_rotation_value(new_value);
|
||||||
|
else if (param == "scale")
|
||||||
|
change_scale_value(new_value);
|
||||||
|
else if (param == "size")
|
||||||
|
change_size_value(new_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ObjectManipulation::on_fill_empty_value(const std::string& opt_key)
|
||||||
|
{
|
||||||
|
// needed to hide the visual hints in 3D scene
|
||||||
|
wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key, false);
|
||||||
|
#ifndef __APPLE__
|
||||||
|
m_focused_option = "";
|
||||||
|
#endif // __APPLE__
|
||||||
|
|
||||||
|
if (!m_cache.is_valid())
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::string param;
|
||||||
|
std::copy(opt_key.begin(), opt_key.end() - 2, std::back_inserter(param));
|
||||||
|
|
||||||
|
double value = 0.0;
|
||||||
|
|
||||||
|
if (param == "position") {
|
||||||
|
int axis = opt_key.back() == 'x' ? 0 :
|
||||||
|
opt_key.back() == 'y' ? 1 : 2;
|
||||||
|
|
||||||
|
value = m_cache.position(axis);
|
||||||
|
}
|
||||||
|
else if (param == "rotation") {
|
||||||
|
int axis = opt_key.back() == 'x' ? 0 :
|
||||||
|
opt_key.back() == 'y' ? 1 : 2;
|
||||||
|
|
||||||
|
value = m_cache.rotation(axis);
|
||||||
|
}
|
||||||
|
else if (param == "scale") {
|
||||||
|
int axis = opt_key.back() == 'x' ? 0 :
|
||||||
|
opt_key.back() == 'y' ? 1 : 2;
|
||||||
|
|
||||||
|
value = m_cache.scale(axis);
|
||||||
|
}
|
||||||
|
else if (param == "size") {
|
||||||
|
int axis = opt_key.back() == 'x' ? 0 :
|
||||||
|
opt_key.back() == 'y' ? 1 : 2;
|
||||||
|
|
||||||
|
value = m_cache.size(axis);
|
||||||
}
|
}
|
||||||
|
|
||||||
change_scale_value(100.0 * Vec3d(size(0) / ref_size(0), size(1) / ref_size(1), size(2) / ref_size(2)));
|
m_og->set_value(opt_key, double_to_string(value));
|
||||||
#endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} //namespace GUI
|
} //namespace GUI
|
||||||
|
|
|
@ -15,7 +15,6 @@ namespace GUI {
|
||||||
|
|
||||||
class ObjectManipulation : public OG_Settings
|
class ObjectManipulation : public OG_Settings
|
||||||
{
|
{
|
||||||
#if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
struct Cache
|
struct Cache
|
||||||
{
|
{
|
||||||
Vec3d position;
|
Vec3d position;
|
||||||
|
@ -43,20 +42,22 @@ class ObjectManipulation : public OG_Settings
|
||||||
|
|
||||||
Instance instance;
|
Instance instance;
|
||||||
|
|
||||||
Cache() : position(Vec3d(DBL_MAX, DBL_MAX, DBL_MAX)) , rotation(Vec3d(DBL_MAX, DBL_MAX, DBL_MAX))
|
Cache() { reset(); }
|
||||||
, scale(Vec3d(DBL_MAX, DBL_MAX, DBL_MAX)) , size(Vec3d(DBL_MAX, DBL_MAX, DBL_MAX))
|
void reset()
|
||||||
, move_label_string("") , rotate_label_string("") , scale_label_string("")
|
|
||||||
{
|
{
|
||||||
|
position = Vec3d(DBL_MAX, DBL_MAX, DBL_MAX);
|
||||||
|
rotation = Vec3d(DBL_MAX, DBL_MAX, DBL_MAX);
|
||||||
|
scale = Vec3d(DBL_MAX, DBL_MAX, DBL_MAX);
|
||||||
|
size = Vec3d(DBL_MAX, DBL_MAX, DBL_MAX);
|
||||||
|
move_label_string = "";
|
||||||
|
rotate_label_string = "";
|
||||||
|
scale_label_string = "";
|
||||||
|
instance.reset();
|
||||||
}
|
}
|
||||||
|
bool is_valid() const { return position != Vec3d(DBL_MAX, DBL_MAX, DBL_MAX); }
|
||||||
};
|
};
|
||||||
|
|
||||||
Cache m_cache;
|
Cache m_cache;
|
||||||
#else
|
|
||||||
Vec3d m_cache_position{ 0., 0., 0. };
|
|
||||||
Vec3d m_cache_rotation{ 0., 0., 0. };
|
|
||||||
Vec3d m_cache_scale{ 100., 100., 100. };
|
|
||||||
Vec3d m_cache_size{ 0., 0., 0. };
|
|
||||||
#endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
|
|
||||||
wxStaticText* m_move_Label = nullptr;
|
wxStaticText* m_move_Label = nullptr;
|
||||||
wxStaticText* m_scale_Label = nullptr;
|
wxStaticText* m_scale_Label = nullptr;
|
||||||
|
@ -76,6 +77,11 @@ class ObjectManipulation : public OG_Settings
|
||||||
bool m_uniform_scale {true};
|
bool m_uniform_scale {true};
|
||||||
PrusaLockButton* m_lock_bnt{ nullptr };
|
PrusaLockButton* m_lock_bnt{ nullptr };
|
||||||
|
|
||||||
|
#ifndef __APPLE__
|
||||||
|
// Currently focused option name (empty if none)
|
||||||
|
std::string m_focused_option;
|
||||||
|
#endif // __APPLE__
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ObjectManipulation(wxWindow* parent);
|
ObjectManipulation(wxWindow* parent);
|
||||||
~ObjectManipulation() {}
|
~ObjectManipulation() {}
|
||||||
|
@ -92,6 +98,14 @@ public:
|
||||||
void set_uniform_scaling(const bool uniform_scale) { m_uniform_scale = uniform_scale;}
|
void set_uniform_scaling(const bool uniform_scale) { m_uniform_scale = uniform_scale;}
|
||||||
bool get_uniform_scaling() const { return m_uniform_scale; }
|
bool get_uniform_scaling() const { return m_uniform_scale; }
|
||||||
|
|
||||||
|
void reset_cache() { m_cache.reset(); }
|
||||||
|
#ifndef __APPLE__
|
||||||
|
// On Windows and Linux, emulates a kill focus event on the currently focused option (if any)
|
||||||
|
// Used only in ObjectList wxEVT_DATAVIEW_SELECTION_CHANGED handler which is called before the regular kill focus event
|
||||||
|
// bound to this class when changing selection in the objects list
|
||||||
|
void emulate_kill_focus();
|
||||||
|
#endif // __APPLE__
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void reset_settings_value();
|
void reset_settings_value();
|
||||||
|
|
||||||
|
@ -105,6 +119,9 @@ private:
|
||||||
void change_rotation_value(const Vec3d& rotation);
|
void change_rotation_value(const Vec3d& rotation);
|
||||||
void change_scale_value(const Vec3d& scale);
|
void change_scale_value(const Vec3d& scale);
|
||||||
void change_size_value(const Vec3d& size);
|
void change_size_value(const Vec3d& size);
|
||||||
|
|
||||||
|
void on_change(const t_config_option_key& opt_key, const boost::any& value);
|
||||||
|
void on_fill_empty_value(const std::string& opt_key);
|
||||||
};
|
};
|
||||||
|
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -896,7 +896,7 @@ std::vector<PresetComboBox*>& Sidebar::combos_filament()
|
||||||
class PlaterDropTarget : public wxFileDropTarget
|
class PlaterDropTarget : public wxFileDropTarget
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PlaterDropTarget(Plater *plater) : plater(plater) {}
|
PlaterDropTarget(Plater *plater) : plater(plater) { this->SetDefaultAction(wxDragCopy); }
|
||||||
|
|
||||||
virtual bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames);
|
virtual bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames);
|
||||||
|
|
||||||
|
@ -1175,10 +1175,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
||||||
{ if (evt.data == 1) this->q->increase_instances(); else if (this->can_decrease_instances()) this->q->decrease_instances(); });
|
{ if (evt.data == 1) this->q->increase_instances(); else if (this->can_decrease_instances()) this->q->decrease_instances(); });
|
||||||
view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent&) { update(); });
|
view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent&) { update(); });
|
||||||
view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_MOVED, &priv::on_wipetower_moved, this);
|
view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_MOVED, &priv::on_wipetower_moved, this);
|
||||||
#if ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_ROTATED, [this](SimpleEvent&) { update(); });
|
view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_ROTATED, [this](SimpleEvent&) { update(); });
|
||||||
view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_SCALED, [this](SimpleEvent&) { update(); });
|
view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_SCALED, [this](SimpleEvent&) { update(); });
|
||||||
#endif // ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION
|
|
||||||
view3D_canvas->Bind(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, [this](Event<bool> &evt) { this->sidebar->enable_buttons(evt.data); });
|
view3D_canvas->Bind(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, [this](Event<bool> &evt) { this->sidebar->enable_buttons(evt.data); });
|
||||||
view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_GEOMETRY, &priv::on_update_geometry, this);
|
view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_GEOMETRY, &priv::on_update_geometry, this);
|
||||||
view3D_canvas->Bind(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, &priv::on_3dcanvas_mouse_dragging_finished, this);
|
view3D_canvas->Bind(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, &priv::on_3dcanvas_mouse_dragging_finished, this);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue