This commit is contained in:
enricoturri1966 2023-02-17 10:59:46 +01:00
commit 04b7a52ef1
40 changed files with 765 additions and 596 deletions

View File

@ -237,14 +237,14 @@ documentation_link = https://help.prusa3d.com/article/prusaslicer-printables-com
weight = 3 weight = 3
[hint:Cut tool] [hint:Cut tool]
text = Cut tool\nDid you know that you can cut a model at any angle and even create aligning pins with the updated Cut tool? Learn more in the documentation. text = Cut tool\nDid you know that you can cut a model at any angle and even create aligning pins with the updated <a>Cut tool</a>? Learn more in the documentation.
documentation_link = https://help.prusa3d.com/article/cut-tool_1779 documentation_link = https://help.prusa3d.com/article/cut-tool_1779
hypertext_type = gizmo hypertext_type = gizmo
hypertext_gizmo_item = cut hypertext_gizmo_item = cut
weight = 3 weight = 3
[hint:Measurement tool] [hint:Measurement tool]
text = Measurement tool\nDid you know that you can measure the distances between points, edges and planes, the radius of a hole or the angle between edges or planes? Learn more in the documentation. text = Measurement tool\nDid you know that you can <a>measure</a> the distances between points, edges and planes, the radius of a hole or the angle between edges or planes? Learn more in the documentation.
documentation_link = https://help.prusa3d.com/article/measurement-tool_399451 documentation_link = https://help.prusa3d.com/article/measurement-tool_399451
hypertext_type = gizmo hypertext_type = gizmo
hypertext_gizmo_item = measure hypertext_gizmo_item = measure

View File

@ -720,28 +720,26 @@ void Transformation::reset()
} }
#if ENABLE_WORLD_COORDINATE #if ENABLE_WORLD_COORDINATE
void Transformation::reset_rotation()
{
const Geometry::TransformationSVD svd(*this);
m_matrix = get_offset_matrix() * Transform3d(svd.v * svd.s * svd.v.transpose()) * svd.mirror_matrix();
}
void Transformation::reset_scaling_factor()
{
const Geometry::TransformationSVD svd(*this);
m_matrix = get_offset_matrix() * Transform3d(svd.u) * Transform3d(svd.v.transpose()) * svd.mirror_matrix();
}
void Transformation::reset_skew() void Transformation::reset_skew()
{ {
Matrix3d rotation; auto new_scale_factor = [](const Matrix3d& s) {
Matrix3d scale; return pow(s(0, 0) * s(1, 1) * s(2, 2), 1. / 3.); // scale average
m_matrix.computeRotationScaling(&rotation, &scale); };
const double average_scale = std::cbrt(scale(0, 0) * scale(1, 1) * scale(2, 2)); const Geometry::TransformationSVD svd(*this);
m_matrix = get_offset_matrix() * Transform3d(svd.u) * scale_transform(new_scale_factor(svd.s)) * Transform3d(svd.v.transpose()) * svd.mirror_matrix();
scale(0, 0) = is_left_handed() ? -average_scale : average_scale;
scale(1, 1) = average_scale;
scale(2, 2) = average_scale;
scale(0, 1) = 0.0;
scale(0, 2) = 0.0;
scale(1, 0) = 0.0;
scale(1, 2) = 0.0;
scale(2, 0) = 0.0;
scale(2, 1) = 0.0;
const Vec3d offset = get_offset();
m_matrix = rotation * scale;
m_matrix.translation() = offset;
} }
Transform3d Transformation::get_matrix_no_offset() const Transform3d Transformation::get_matrix_no_offset() const
@ -838,6 +836,43 @@ Transformation Transformation::volume_to_bed_transformation(const Transformation
} }
#endif // !ENABLE_WORLD_COORDINATE #endif // !ENABLE_WORLD_COORDINATE
#if ENABLE_WORLD_COORDINATE
TransformationSVD::TransformationSVD(const Transform3d& trafo)
{
const auto &m0 = trafo.matrix().block<3, 3>(0, 0);
mirror = m0.determinant() < 0.0;
Matrix3d m;
if (mirror)
m = m0 * Eigen::DiagonalMatrix<double, 3, 3>(-1.0, 1.0, 1.0);
else
m = m0;
const Eigen::JacobiSVD<Matrix3d> svd(m, Eigen::ComputeFullU | Eigen::ComputeFullV);
u = svd.matrixU();
v = svd.matrixV();
s = svd.singularValues().asDiagonal();
scale = !s.isApprox(Matrix3d::Identity());
anisotropic_scale = ! is_approx(s(0, 0), s(1, 1)) || ! is_approx(s(1, 1), s(2, 2));
rotation = !v.isApprox(u);
if (anisotropic_scale) {
rotation_90_degrees = true;
for (int i = 0; i < 3; ++i) {
const Vec3d row = v.row(i).cwiseAbs();
size_t num_zeros = is_approx(row[0], 0.) + is_approx(row[1], 0.) + is_approx(row[2], 0.);
size_t num_ones = is_approx(row[0], 1.) + is_approx(row[1], 1.) + is_approx(row[2], 1.);
if (num_zeros != 2 || num_ones != 1) {
rotation_90_degrees = false;
break;
}
}
skew = ! rotation_90_degrees;
} else
skew = false;
}
#endif // ENABLE_WORLD_COORDINATE
// For parsing a transformation matrix from 3MF / AMF. // For parsing a transformation matrix from 3MF / AMF.
Transform3d transform3d_from_string(const std::string& transform_str) Transform3d transform3d_from_string(const std::string& transform_str)
{ {

View File

@ -492,8 +492,8 @@ public:
void reset(); void reset();
#if ENABLE_WORLD_COORDINATE #if ENABLE_WORLD_COORDINATE
void reset_offset() { set_offset(Vec3d::Zero()); } void reset_offset() { set_offset(Vec3d::Zero()); }
void reset_rotation() { set_rotation(Vec3d::Zero()); } void reset_rotation();
void reset_scaling_factor() { set_scaling_factor(Vec3d::Ones()); } void reset_scaling_factor();
void reset_mirror() { set_mirror(Vec3d::Ones()); } void reset_mirror() { set_mirror(Vec3d::Ones()); }
void reset_skew(); void reset_skew();
@ -538,6 +538,27 @@ private:
#endif // ENABLE_WORLD_COORDINATE #endif // ENABLE_WORLD_COORDINATE
}; };
#if ENABLE_WORLD_COORDINATE
struct TransformationSVD
{
Matrix3d u = Matrix3d::Identity();
Matrix3d s = Matrix3d::Identity();
Matrix3d v = Matrix3d::Identity();
bool mirror{ false };
bool scale{ false };
bool anisotropic_scale{ false };
bool rotation{ false };
bool rotation_90_degrees{ false };
bool skew{ false };
explicit TransformationSVD(const Transformation& trafo) : TransformationSVD(trafo.get_matrix()) {}
explicit TransformationSVD(const Transform3d& trafo);
Eigen::DiagonalMatrix<double, 3, 3> mirror_matrix() const { return Eigen::DiagonalMatrix<double, 3, 3>(this->mirror ? -1. : 1., 1., 1.); }
};
#endif // ENABLE_WORLD_COORDINATE
// For parsing a transformation matrix from 3MF / AMF. // For parsing a transformation matrix from 3MF / AMF.
extern Transform3d transform3d_from_string(const std::string& transform_str); extern Transform3d transform3d_from_string(const std::string& transform_str);

View File

@ -127,9 +127,7 @@ static void connect_layer_slices(
{ {
#ifndef NDEBUG #ifndef NDEBUG
auto assert_intersection_valid = [this](int i, int j) { auto assert_intersection_valid = [this](int i, int j) {
assert(i != j); assert(i < j);
if (i > j)
std::swap(i, j);
assert(i >= m_offset_below); assert(i >= m_offset_below);
assert(i < m_offset_above); assert(i < m_offset_above);
assert(j >= m_offset_above); assert(j >= m_offset_above);
@ -140,35 +138,47 @@ static void connect_layer_slices(
if (polynode.Contour.size() >= 3) { if (polynode.Contour.size() >= 3) {
// If there is an intersection point, it should indicate which contours (one from layer below, the other from layer above) intersect. // If there is an intersection point, it should indicate which contours (one from layer below, the other from layer above) intersect.
// Otherwise the contour is fully inside another contour. // Otherwise the contour is fully inside another contour.
int32_t i = 0, j = 0; int32_t i = -1, j = -1;
for (int icontour = 0; icontour <= polynode.ChildCount(); ++ icontour) { for (int icontour = 0; icontour <= polynode.ChildCount(); ++ icontour) {
const bool first = icontour == 0; const ClipperLib_Z::Path &contour = icontour == 0 ? polynode.Contour : polynode.Childs[icontour - 1]->Contour;
const ClipperLib_Z::Path &contour = first ? polynode.Contour : polynode.Childs[icontour - 1]->Contour;
if (contour.size() >= 3) { if (contour.size() >= 3) {
if (first) { for (const ClipperLib_Z::IntPoint &pt : contour) {
i = contour.front().z();
j = i;
if (i < 0) {
std::tie(i, j) = m_intersections[-i - 1];
assert(assert_intersection_valid(i, j));
goto end;
}
}
for (const ClipperLib_Z::IntPoint& pt : contour) {
j = pt.z(); j = pt.z();
if (j < 0) { if (j < 0) {
const auto &intersection = m_intersections[-j - 1];
assert(intersection.first <= intersection.second);
if (intersection.second < m_offset_above) {
// Ignore intersection of polygons on the 1st layer.
assert(intersection.first >= m_offset_below);
j = i;
} else if (intersection.first >= m_offset_above) {
// Ignore intersection of polygons on the 2nd layer
assert(intersection.second < m_offset_end);
j = i;
} else {
std::tie(i, j) = m_intersections[-j - 1]; std::tie(i, j) = m_intersections[-j - 1];
assert(assert_intersection_valid(i, j)); assert(assert_intersection_valid(i, j));
goto end; goto end;
} }
else if (i != j) } else if (i == -1) {
// First source contour of this expolygon was found.
i = j;
} else if (i != j) {
// Second source contour of this expolygon was found.
if (i > j)
std::swap(i, j);
assert(assert_intersection_valid(i, j));
goto end; goto end;
} }
} }
} }
}
end: end:
bool found = false; bool found = false;
if (i == j) { if (i == -1) {
// This should not happen. It may only happen if the source contours had just self intersections or intersections with contours at the same layer.
assert(false);
} else if (i == j) {
// The contour is completely inside another contour. // The contour is completely inside another contour.
Point pt(polynode.Contour.front().x(), polynode.Contour.front().y()); Point pt(polynode.Contour.front().x(), polynode.Contour.front().y());
if (i < m_offset_above) { if (i < m_offset_above) {
@ -202,8 +212,6 @@ static void connect_layer_slices(
} }
} else { } else {
assert(assert_intersection_valid(i, j)); assert(assert_intersection_valid(i, j));
if (i > j)
std::swap(i, j);
i -= m_offset_below; i -= m_offset_below;
j -= m_offset_above; j -= m_offset_above;
assert(i >= 0 && i < m_below.lslices_ex.size()); assert(i >= 0 && i < m_below.lslices_ex.size());

View File

@ -458,7 +458,7 @@ static std::vector<std::string> s_Preset_print_options {
"infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder", "infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder",
"ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width", "ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width",
"perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width", "perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width",
"top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "infill_anchor", "infill_anchor_max", "bridge_flow_ratio", "clip_multipart_objects", "top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "infill_anchor", "infill_anchor_max", "bridge_flow_ratio",
"elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "gcode_resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y", "elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "gcode_resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y",
"wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width",
"wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits", "wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits",

View File

@ -598,14 +598,6 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced; def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.f)); def->set_default_value(new ConfigOptionFloat(0.f));
def = this->add("clip_multipart_objects", coBool);
def->label = L("Clip multi-part objects");
def->tooltip = L("When printing multi-material objects, this settings will make Slic3r "
"to clip the overlapping object parts one by the other "
"(2nd part will be clipped by the 1st, 3rd part will be clipped by the 1st and 2nd etc).");
def->mode = comExpert;
def->set_default_value(new ConfigOptionBool(true));
def = this->add("colorprint_heights", coFloats); def = this->add("colorprint_heights", coFloats);
def->label = L("Colorprint height"); def->label = L("Colorprint height");
def->tooltip = L("Heights at which a filament change is to occur."); def->tooltip = L("Heights at which a filament change is to occur.");
@ -4052,6 +4044,22 @@ void PrintConfigDef::init_sla_params()
def->set_default_value(new ConfigOptionFloat(0.001)); def->set_default_value(new ConfigOptionFloat(0.001));
} }
// Ignore the following obsolete configuration keys:
static std::set<std::string> PrintConfigDef_ignore = {
"clip_multipart_objects",
"duplicate_x", "duplicate_y", "gcode_arcs", "multiply_x", "multiply_y",
"support_material_tool", "acceleration", "adjust_overhang_flow",
"standby_temperature", "scale", "rotate", "duplicate", "duplicate_grid",
"start_perimeters_at_concave_points", "start_perimeters_at_non_overhang", "randomize_start",
"seal_position", "vibration_limit", "bed_size",
"print_center", "g0", "threads", "pressure_advance", "wipe_tower_per_color_wipe",
"serial_port", "serial_speed",
// Introduced in some PrusaSlicer 2.3.1 alpha, later renamed or removed.
"fuzzy_skin_perimeter_mode", "fuzzy_skin_shape",
// Introduced in PrusaSlicer 2.3.0-alpha2, later replaced by automatic calculation based on extrusion width.
"wall_add_middle_threshold", "wall_split_middle_threshold",
};
void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value) void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value)
{ {
// handle legacy options // handle legacy options
@ -4125,32 +4133,17 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
} }
}*/ }*/
// Ignore the following obsolete configuration keys:
static std::set<std::string> ignore = {
"duplicate_x", "duplicate_y", "gcode_arcs", "multiply_x", "multiply_y",
"support_material_tool", "acceleration", "adjust_overhang_flow",
"standby_temperature", "scale", "rotate", "duplicate", "duplicate_grid",
"start_perimeters_at_concave_points", "start_perimeters_at_non_overhang", "randomize_start",
"seal_position", "vibration_limit", "bed_size",
"print_center", "g0", "threads", "pressure_advance", "wipe_tower_per_color_wipe",
"serial_port", "serial_speed",
// Introduced in some PrusaSlicer 2.3.1 alpha, later renamed or removed.
"fuzzy_skin_perimeter_mode", "fuzzy_skin_shape",
// Introduced in PrusaSlicer 2.3.0-alpha2, later replaced by automatic calculation based on extrusion width.
"wall_add_middle_threshold", "wall_split_middle_threshold",
};
// In PrusaSlicer 2.3.0-alpha0 the "monotonous" infill was introduced, which was later renamed to "monotonic". // In PrusaSlicer 2.3.0-alpha0 the "monotonous" infill was introduced, which was later renamed to "monotonic".
if (value == "monotonous" && (opt_key == "top_fill_pattern" || opt_key == "bottom_fill_pattern" || opt_key == "fill_pattern")) if (value == "monotonous" && (opt_key == "top_fill_pattern" || opt_key == "bottom_fill_pattern" || opt_key == "fill_pattern"))
value = "monotonic"; value = "monotonic";
if (ignore.find(opt_key) != ignore.end()) { if (PrintConfigDef_ignore.find(opt_key) != PrintConfigDef_ignore.end()) {
opt_key = ""; opt_key = {};
return; return;
} }
if (! print_config_def.has(opt_key)) { if (! print_config_def.has(opt_key)) {
opt_key = ""; opt_key = {};
return; return;
} }
} }

View File

@ -487,7 +487,6 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionFloat, brim_separation)) ((ConfigOptionFloat, brim_separation))
((ConfigOptionEnum<BrimType>, brim_type)) ((ConfigOptionEnum<BrimType>, brim_type))
((ConfigOptionFloat, brim_width)) ((ConfigOptionFloat, brim_width))
((ConfigOptionBool, clip_multipart_objects))
((ConfigOptionBool, dont_support_bridges)) ((ConfigOptionBool, dont_support_bridges))
((ConfigOptionFloat, elefant_foot_compensation)) ((ConfigOptionFloat, elefant_foot_compensation))
((ConfigOptionFloatOrPercent, extrusion_width)) ((ConfigOptionFloatOrPercent, extrusion_width))

View File

@ -620,8 +620,7 @@ bool PrintObject::invalidate_state_by_config_options(
|| opt_key == "slicing_mode") { || opt_key == "slicing_mode") {
steps.emplace_back(posSlice); steps.emplace_back(posSlice);
} else if ( } else if (
opt_key == "clip_multipart_objects" opt_key == "elefant_foot_compensation"
|| opt_key == "elefant_foot_compensation"
|| opt_key == "support_material_contact_distance" || opt_key == "support_material_contact_distance"
|| opt_key == "xy_size_compensation") { || opt_key == "xy_size_compensation") {
steps.emplace_back(posSlice); steps.emplace_back(posSlice);

View File

@ -237,9 +237,6 @@ static std::vector<std::vector<ExPolygons>> slices_to_regions(
const PrintObjectRegions &print_object_regions, const PrintObjectRegions &print_object_regions,
const std::vector<float> &zs, const std::vector<float> &zs,
std::vector<VolumeSlices> &&volume_slices, std::vector<VolumeSlices> &&volume_slices,
// If clipping is disabled, then ExPolygons produced by different volumes will never be merged, thus they will be allowed to overlap.
// It is up to the model designer to handle these overlaps.
const bool clip_multipart_objects,
const std::function<void()> &throw_on_cancel_callback) const std::function<void()> &throw_on_cancel_callback)
{ {
model_volumes_sort_by_id(model_volumes); model_volumes_sort_by_id(model_volumes);
@ -308,7 +305,7 @@ static std::vector<std::vector<ExPolygons>> slices_to_regions(
} }
tbb::parallel_for( tbb::parallel_for(
tbb::blocked_range<size_t>(0, zs_complex.size()), tbb::blocked_range<size_t>(0, zs_complex.size()),
[&slices_by_region, &print_object_regions, &zs_complex, &layer_ranges_regions_to_slices, clip_multipart_objects, &throw_on_cancel_callback] [&slices_by_region, &print_object_regions, &zs_complex, &layer_ranges_regions_to_slices, &throw_on_cancel_callback]
(const tbb::blocked_range<size_t> &range) { (const tbb::blocked_range<size_t> &range) {
float z = zs_complex[range.begin()].second; float z = zs_complex[range.begin()].second;
auto it_layer_range = layer_range_first(print_object_regions.layer_ranges, z); auto it_layer_range = layer_range_first(print_object_regions.layer_ranges, z);
@ -359,7 +356,7 @@ static std::vector<std::vector<ExPolygons>> slices_to_regions(
if (next_region_same_modifier) if (next_region_same_modifier)
// To be used in the following iteration. // To be used in the following iteration.
temp_slices[idx_region + 1].expolygons = std::move(source); temp_slices[idx_region + 1].expolygons = std::move(source);
} else if ((region.model_volume->is_model_part() && clip_multipart_objects) || region.model_volume->is_negative_volume()) { } else if (region.model_volume->is_model_part() || region.model_volume->is_negative_volume()) {
// Clip every non-zero region preceding it. // Clip every non-zero region preceding it.
for (int idx_region2 = 0; idx_region2 < idx_region; ++ idx_region2) for (int idx_region2 = 0; idx_region2 < idx_region; ++ idx_region2)
if (! temp_slices[idx_region2].expolygons.empty()) { if (! temp_slices[idx_region2].expolygons.empty()) {
@ -388,10 +385,7 @@ static std::vector<std::vector<ExPolygons>> slices_to_regions(
merged = true; merged = true;
} }
} }
// Don't unite the regions if ! clip_multipart_objects. In that case it is user's responsibility if (merged)
// to handle region overlaps. Indeed, one may intentionally let the regions overlap to produce crossing perimeters
// for example.
if (merged && clip_multipart_objects)
expolygons = closing_ex(expolygons, float(scale_(EPSILON))); expolygons = closing_ex(expolygons, float(scale_(EPSILON)));
slices_by_region[temp_slices[i].region_id][z_idx] = std::move(expolygons); slices_by_region[temp_slices[i].region_id][z_idx] = std::move(expolygons);
i = j; i = j;
@ -696,7 +690,6 @@ void PrintObject::slice_volumes()
slice_volumes_inner( slice_volumes_inner(
print->config(), this->config(), this->trafo_centered(), print->config(), this->config(), this->trafo_centered(),
this->model_object()->volumes, m_shared_regions->layer_ranges, slice_zs, throw_on_cancel_callback), this->model_object()->volumes, m_shared_regions->layer_ranges, slice_zs, throw_on_cancel_callback),
m_config.clip_multipart_objects,
throw_on_cancel_callback); throw_on_cancel_callback);
for (size_t region_id = 0; region_id < region_slices.size(); ++ region_id) { for (size_t region_id = 0; region_id < region_slices.size(); ++ region_id) {

View File

@ -269,7 +269,8 @@ void set_current_thread_qos()
#ifdef __APPLE__ #ifdef __APPLE__
// OSX specific: Set Quality of Service to "user initiated", so that the threads will be scheduled to high performance // OSX specific: Set Quality of Service to "user initiated", so that the threads will be scheduled to high performance
// cores if available. // cores if available.
pthread_set_qos_class_self_np(QOS_CLASS_USER_INITIATED, 0); // With QOS_CLASS_USER_INITIATED the worker threads drop priority once slicer loses user focus.
pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, 0);
#endif // __APPLE__ #endif // __APPLE__
} }

View File

@ -1315,10 +1315,12 @@ PageUpdate::PageUpdate(ConfigWizard *parent)
box_presets->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->preset_update = event.IsChecked(); }); box_presets->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->preset_update = event.IsChecked(); });
} }
namespace DownloaderUtils namespace DownloaderUtils
{ {
namespace {
#ifdef _WIN32 #ifdef _WIN32
wxString get_downloads_path() wxString get_downloads_path()
{ {
wxString ret; wxString ret;
@ -1330,7 +1332,6 @@ namespace DownloaderUtils
CoTaskMemFree(path); CoTaskMemFree(path);
return ret; return ret;
} }
#elif __APPLE__ #elif __APPLE__
wxString get_downloads_path() wxString get_downloads_path()
{ {
@ -1348,9 +1349,8 @@ namespace DownloaderUtils
} }
return wxString(); return wxString();
} }
#endif #endif
}
Worker::Worker(wxWindow* parent) Worker::Worker(wxWindow* parent)
: wxBoxSizer(wxHORIZONTAL) : wxBoxSizer(wxHORIZONTAL)
, m_parent(parent) , m_parent(parent)
@ -1432,16 +1432,16 @@ PageDownloader::PageDownloader(ConfigWizard* parent)
))); )));
#endif #endif
box_allow_downloads->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { this->downloader->allow(event.IsChecked()); }); box_allow_downloads->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { this->m_downloader->allow(event.IsChecked()); });
downloader = new DownloaderUtils::Worker(this); m_downloader = new DownloaderUtils::Worker(this);
append(downloader); append(m_downloader);
downloader->allow(box_allow_value); m_downloader->allow(box_allow_value);
} }
bool PageDownloader::on_finish_downloader() const bool PageDownloader::on_finish_downloader() const
{ {
return downloader->on_finish(); return m_downloader->on_finish();
} }
bool DownloaderUtils::Worker::perform_register(const std::string& path_override/* = {}*/) bool DownloaderUtils::Worker::perform_register(const std::string& path_override/* = {}*/)
@ -3035,9 +3035,11 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
#ifdef __linux__ #ifdef __linux__
// Desktop integration on Linux // Desktop integration on Linux
BOOST_LOG_TRIVIAL(debug) << "ConfigWizard::priv::apply_config integrate_desktop" << page_welcome->integrate_desktop() << " perform_registration_linux " << page_downloader->downloader->get_perform_registration_linux(); BOOST_LOG_TRIVIAL(debug) << "ConfigWizard::priv::apply_config integrate_desktop" << page_welcome->integrate_desktop() << " perform_registration_linux " << page_downloader->m_downloader->get_perform_registration_linux();
if (page_welcome->integrate_desktop() || page_downloader->downloader->get_perform_registration_linux()) if (page_welcome->integrate_desktop())
DesktopIntegrationDialog::perform_desktop_integration(page_downloader->downloader->get_perform_registration_linux()); DesktopIntegrationDialog::perform_desktop_integration();
if (page_downloader->m_downloader->get_perform_registration_linux())
DesktopIntegrationDialog::perform_downloader_desktop_integration();
#endif #endif
// Decide whether to create snapshot based on run_reason and the reset profile checkbox // Decide whether to create snapshot based on run_reason and the reset profile checkbox
@ -3175,6 +3177,7 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
// apply materials in app_config // apply materials in app_config
for (const std::string& section_name : {AppConfig::SECTION_FILAMENTS, AppConfig::SECTION_MATERIALS}) for (const std::string& section_name : {AppConfig::SECTION_FILAMENTS, AppConfig::SECTION_MATERIALS})
if (appconfig_new.has_section(section_name))
app_config->set_section(section_name, appconfig_new.get_section(section_name)); app_config->set_section(section_name, appconfig_new.get_section(section_name));
app_config->set_vendors(appconfig_new); app_config->set_vendors(appconfig_new);

View File

@ -4,6 +4,8 @@
#include <memory> #include <memory>
#include <wx/dialog.h> #include <wx/dialog.h>
#include <wx/sizer.h>
#include <wx/textctrl.h>
#include "GUI_Utils.hpp" #include "GUI_Utils.hpp"
@ -14,6 +16,36 @@ class PresetUpdater;
namespace GUI { namespace GUI {
namespace DownloaderUtils {
class Worker : public wxBoxSizer
{
wxWindow* m_parent{ nullptr };
wxTextCtrl* m_input_path{ nullptr };
bool downloader_checked{ false };
#ifdef __linux__
bool perform_registration_linux{ false };
#endif // __linux__
void deregister();
public:
Worker(wxWindow* parent);
~Worker() {}
void allow(bool allow_) { downloader_checked = allow_; }
bool is_checked() const { return downloader_checked; }
wxString path_name() const { return m_input_path ? m_input_path->GetValue() : wxString(); }
void set_path_name(wxString name);
void set_path_name(const std::string& name);
bool on_finish();
bool perform_register(const std::string& path_override = {});
#ifdef __linux__
bool get_perform_registration_linux() { return perform_registration_linux; }
#endif // __linux__
};
}
class ConfigWizard: public DPIDialog class ConfigWizard: public DPIDialog
{ {

View File

@ -10,12 +10,10 @@
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <wx/sizer.h>
#include <wx/panel.h> #include <wx/panel.h>
#include <wx/button.h> #include <wx/button.h>
#include <wx/choice.h> #include <wx/choice.h>
#include <wx/spinctrl.h> #include <wx/spinctrl.h>
#include <wx/textctrl.h>
#include <wx/listbox.h> #include <wx/listbox.h>
#include <wx/checklst.h> #include <wx/checklst.h>
#include <wx/radiobut.h> #include <wx/radiobut.h>
@ -418,44 +416,10 @@ struct PageUpdate: ConfigWizardPage
PageUpdate(ConfigWizard *parent); PageUpdate(ConfigWizard *parent);
}; };
namespace DownloaderUtils {
wxString get_downloads_path();
class Worker : public wxBoxSizer
{
wxWindow* m_parent {nullptr};
wxTextCtrl* m_input_path {nullptr};
bool downloader_checked {false};
#ifdef __linux__
bool perform_registration_linux { false };
#endif // __linux__
void deregister();
public:
Worker(wxWindow* parent);
~Worker(){}
void allow(bool allow_) { downloader_checked = allow_; }
bool is_checked() const { return downloader_checked; }
wxString path_name() const { return m_input_path ? m_input_path->GetValue() : wxString(); }
void set_path_name(wxString name);
void set_path_name(const std::string& name);
bool on_finish();
bool perform_register(const std::string& path_override = {});
#ifdef __linux__
bool get_perform_registration_linux() { return perform_registration_linux; }
#endif // __linux__
};
}
struct PageDownloader : ConfigWizardPage struct PageDownloader : ConfigWizardPage
{ {
DownloaderUtils::Worker* downloader{ nullptr }; DownloaderUtils::Worker* m_downloader { nullptr };
PageDownloader(ConfigWizard* parent); PageDownloader(ConfigWizard* parent);

View File

@ -218,9 +218,9 @@ bool DesktopIntegrationDialog::integration_possible()
{ {
return true; return true;
} }
void DesktopIntegrationDialog::perform_desktop_integration(bool perform_downloader) void DesktopIntegrationDialog::perform_desktop_integration()
{ {
BOOST_LOG_TRIVIAL(debug) << "performing desktop integration. With downloader integration: " << perform_downloader; BOOST_LOG_TRIVIAL(debug) << "performing desktop integration.";
// Path to appimage // Path to appimage
const char *appimage_env = std::getenv("APPIMAGE"); const char *appimage_env = std::getenv("APPIMAGE");
std::string excutable_path; std::string excutable_path;
@ -423,38 +423,6 @@ void DesktopIntegrationDialog::perform_desktop_integration(bool perform_download
show_error(nullptr, _L("Performing desktop integration failed - could not create Gcodeviewer desktop file. PrusaSlicer desktop file was probably created successfully.")); show_error(nullptr, _L("Performing desktop integration failed - could not create Gcodeviewer desktop file. PrusaSlicer desktop file was probably created successfully."));
} }
} }
if (perform_downloader)
{
std::string desktop_file_downloader = GUI::format(
"[Desktop Entry]\n"
"Name=PrusaSlicer URL Protocol%1%\n"
"Exec=\"%3%\" --single-instance %%u\n"
"Icon=PrusaSlicer%4%\n"
"Terminal=false\n"
"Type=Application\n"
"MimeType=x-scheme-handler/prusaslicer;\n"
"StartupNotify=false\n"
, name_suffix, version_suffix, excutable_path, version_suffix);
// desktop file for downloader as part of main app
std::string desktop_path = GUI::format("%1%/applications/PrusaSlicerURLProtocol%2%.desktop", target_dir_desktop, version_suffix);
if (create_desktop_file(desktop_path, desktop_file_downloader)) {
// save path to desktop file
app_config->set("desktop_integration_URL_path", desktop_path);
// finish registration on mime type
std::string command = GUI::format("xdg-mime default PrusaSlicerURLProtocol%1%.desktop x-scheme-handler/prusaslicer", version_suffix);
BOOST_LOG_TRIVIAL(debug) << "system command: " << command;
int r = system(command.c_str());
BOOST_LOG_TRIVIAL(debug) << "system result: " << r;
} else {
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could not create URL Protocol desktop file";
show_error(nullptr, _L("Performing desktop integration failed - could not create URL Protocol desktop file."));
return;
}
}
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::DesktopIntegrationSuccess); wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::DesktopIntegrationSuccess);
} }
void DesktopIntegrationDialog::undo_desktop_intgration() void DesktopIntegrationDialog::undo_desktop_intgration()
@ -487,15 +455,162 @@ void DesktopIntegrationDialog::undo_desktop_intgration()
std::remove(path.c_str()); std::remove(path.c_str());
} }
} }
// URL Protocol
path = std::string(app_config->get("desktop_integration_URL_path"));
if (!path.empty()) {
BOOST_LOG_TRIVIAL(debug) << "removing " << path;
std::remove(path.c_str());
}
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::UndoDesktopIntegrationSuccess); wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::UndoDesktopIntegrationSuccess);
} }
void DesktopIntegrationDialog::perform_downloader_desktop_integration()
{
BOOST_LOG_TRIVIAL(debug) << "performing downloader desktop integration.";
// Path to appimage
const char* appimage_env = std::getenv("APPIMAGE");
std::string excutable_path;
if (appimage_env) {
try {
excutable_path = boost::filesystem::canonical(boost::filesystem::path(appimage_env)).string();
}
catch (std::exception&) {
BOOST_LOG_TRIVIAL(error) << "Performing downloader desktop integration failed - boost::filesystem::canonical did not return appimage path.";
show_error(nullptr, _L("Performing downloader desktop integration failed - boost::filesystem::canonical did not return appimage path."));
return;
}
}
else {
// not appimage - find executable
excutable_path = boost::dll::program_location().string();
//excutable_path = wxStandardPaths::Get().GetExecutablePath().string();
BOOST_LOG_TRIVIAL(debug) << "non-appimage path to executable: " << excutable_path;
if (excutable_path.empty())
{
BOOST_LOG_TRIVIAL(error) << "Performing downloader desktop integration failed - no executable found.";
show_error(nullptr, _L("Performing downloader desktop integration failed - Could not find executable."));
return;
}
}
// Escape ' characters in appimage, other special symbols will be esacaped in desktop file by 'excutable_path'
//boost::replace_all(excutable_path, "'", "'\\''");
excutable_path = escape_string(excutable_path);
// Find directories icons and applications
// $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored.
// If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used.
// $XDG_DATA_DIRS defines the preference-ordered set of base directories to search for data files in addition to the $XDG_DATA_HOME base directory.
// The directories in $XDG_DATA_DIRS should be seperated with a colon ':'.
// If $XDG_DATA_DIRS is either not set or empty, a value equal to /usr/local/share/:/usr/share/ should be used.
std::vector<std::string>target_candidates;
resolve_path_from_var("XDG_DATA_HOME", target_candidates);
resolve_path_from_var("XDG_DATA_DIRS", target_candidates);
AppConfig* app_config = wxGetApp().app_config;
// suffix string to create different desktop file for alpha, beta.
std::string version_suffix;
std::string name_suffix;
std::string version(SLIC3R_VERSION);
if (version.find("alpha") != std::string::npos)
{
version_suffix = "-alpha";
name_suffix = " - alpha";
}
else if (version.find("beta") != std::string::npos)
{
version_suffix = "-beta";
name_suffix = " - beta";
}
// theme path to icon destination
std::string icon_theme_path;
std::string icon_theme_dirs;
if (platform_flavor() == PlatformFlavor::LinuxOnChromium) {
icon_theme_path = "hicolor/96x96/apps/";
icon_theme_dirs = "/hicolor/96x96/apps";
}
std::string target_dir_desktop;
// desktop file
// iterate thru target_candidates to find applications folder
std::string desktop_file_downloader = GUI::format(
"[Desktop Entry]\n"
"Name=PrusaSlicer URL Protocol%1%\n"
"Exec=\"%2%\" --single-instance %%u\n"
"Terminal=false\n"
"Type=Application\n"
"MimeType=x-scheme-handler/prusaslicer;\n"
"StartupNotify=false\n"
"NoDisplay=true\n"
, name_suffix, excutable_path);
// desktop file for downloader as part of main app
std::string desktop_path = GUI::format("%1%/applications/PrusaSlicerURLProtocol%2%.desktop", target_dir_desktop, version_suffix);
if (create_desktop_file(desktop_path, desktop_file_downloader)) {
// save path to desktop file
app_config->set("desktop_integration_URL_path", desktop_path);
// finish registration on mime type
std::string command = GUI::format("xdg-mime default PrusaSlicerURLProtocol%1%.desktop x-scheme-handler/prusaslicer", version_suffix);
BOOST_LOG_TRIVIAL(debug) << "system command: " << command;
int r = system(command.c_str());
BOOST_LOG_TRIVIAL(debug) << "system result: " << r;
}
bool candidate_found = false;
for (size_t i = 0; i < target_candidates.size(); ++i) {
if (contains_path_dir(target_candidates[i], "applications")) {
target_dir_desktop = target_candidates[i];
// Write slicer desktop file
std::string path = GUI::format("%1%/applications/PrusaSlicerURLProtocol%2%.desktop", target_dir_desktop, version_suffix);
if (create_desktop_file(path, desktop_file_downloader)) {
app_config->set("desktop_integration_URL_path", path);
candidate_found = true;
BOOST_LOG_TRIVIAL(debug) << "PrusaSlicerURLProtocol.desktop file installation success.";
break;
}
else {
// write failed - try another path
BOOST_LOG_TRIVIAL(debug) << "Attempt to PrusaSlicerURLProtocol.desktop file installation failed. failed path: " << target_candidates[i];
target_dir_desktop.clear();
}
}
}
// if all failed - try creating default home folder
if (!candidate_found) {
// create $HOME/.local/share
create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/applications");
// create desktop file
target_dir_desktop = GUI::format("%1%/.local/share", wxFileName::GetHomeDir());
std::string path = GUI::format("%1%/applications/PrusaSlicerURLProtocol%2%.desktop", target_dir_desktop, version_suffix);
if (contains_path_dir(target_dir_desktop, "applications")) {
if (!create_desktop_file(path, desktop_file_downloader)) {
// Desktop file not written - end desktop integration
BOOST_LOG_TRIVIAL(error) << "Performing downloader desktop integration failed - could not create desktop file.";
return;
}
app_config->set("desktop_integration_URL_path", path);
}
else {
// Desktop file not written - end desktop integration
BOOST_LOG_TRIVIAL(error) << "Performing downloader desktop integration failed because the application directory was not found.";
return;
}
}
assert(!target_dir_desktop.empty());
if (target_dir_desktop.empty()) {
// Desktop file not written - end desktop integration
BOOST_LOG_TRIVIAL(error) << "Performing downloader desktop integration failed because the application directory was not found.";
show_error(nullptr, _L("Performing downloader desktop integration failed because the application directory was not found."));
return;
}
// finish registration on mime type
std::string command = GUI::format("xdg-mime default PrusaSlicerURLProtocol%1%.desktop x-scheme-handler/prusaslicer", version_suffix);
BOOST_LOG_TRIVIAL(debug) << "system command: " << command;
int r = system(command.c_str());
BOOST_LOG_TRIVIAL(debug) << "system result: " << r;
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::DesktopIntegrationSuccess);
}
void DesktopIntegrationDialog::undo_downloader_registration() void DesktopIntegrationDialog::undo_downloader_registration()
{ {
const AppConfig *app_config = wxGetApp().app_config; const AppConfig *app_config = wxGetApp().app_config;
@ -532,7 +647,7 @@ DesktopIntegrationDialog::DesktopIntegrationDialog(wxWindow *parent)
wxButton *btn_perform = new wxButton(this, wxID_ANY, _L("Perform")); wxButton *btn_perform = new wxButton(this, wxID_ANY, _L("Perform"));
btn_szr->Add(btn_perform, 0, wxALL, 10); btn_szr->Add(btn_perform, 0, wxALL, 10);
btn_perform->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { DesktopIntegrationDialog::perform_desktop_integration(false); EndModal(wxID_ANY); }); btn_perform->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { DesktopIntegrationDialog::perform_desktop_integration(); EndModal(wxID_ANY); });
if (can_undo){ if (can_undo){
wxButton *btn_undo = new wxButton(this, wxID_ANY, _L("Undo")); wxButton *btn_undo = new wxButton(this, wxID_ANY, _L("Undo"));

View File

@ -29,10 +29,11 @@ public:
// if perform_downloader: // if perform_downloader:
// Creates Destktop files for PrusaSlicer downloader feature // Creates Destktop files for PrusaSlicer downloader feature
// Regiters PrusaSlicer to start on prusaslicer:// URL // Regiters PrusaSlicer to start on prusaslicer:// URL
static void perform_desktop_integration(bool perform_downloader); static void perform_desktop_integration();
// Deletes Desktop files and icons for both PrusaSlicer and GcodeViewer at paths stored in App Config. // Deletes Desktop files and icons for both PrusaSlicer and GcodeViewer at paths stored in App Config.
static void undo_desktop_intgration(); static void undo_desktop_intgration();
static void perform_downloader_desktop_integration();
static void undo_downloader_registration(); static void undo_downloader_registration();
private: private:

View File

@ -137,13 +137,30 @@ void FileGet::priv::get_perform()
std::string extension = boost::filesystem::extension(dest_path); std::string extension = boost::filesystem::extension(dest_path);
std::string just_filename = m_filename.substr(0, m_filename.size() - extension.size()); std::string just_filename = m_filename.substr(0, m_filename.size() - extension.size());
std::string final_filename = just_filename; std::string final_filename = just_filename;
// Find unsed filename
try {
size_t version = 0; size_t version = 0;
while (boost::filesystem::exists(m_dest_folder / (final_filename + extension)) || boost::filesystem::exists(m_dest_folder / (final_filename + extension + "." + std::to_string(get_current_pid()) + ".download"))) while (boost::filesystem::exists(m_dest_folder / (final_filename + extension)) || boost::filesystem::exists(m_dest_folder / (final_filename + extension + "." + std::to_string(get_current_pid()) + ".download")))
{ {
++version; ++version;
final_filename = just_filename + "(" + std::to_string(version) + ")"; if (version > 999) {
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR);
evt->SetString(GUI::format_wxstr(L"Failed to find suitable filename. Last name: %1%." , (m_dest_folder / (final_filename + extension)).string()));
evt->SetInt(m_id);
m_evt_handler->QueueEvent(evt);
return;
} }
final_filename = GUI::format("%1%(%2%)", just_filename, std::to_string(version));
}
} catch (const boost::filesystem::filesystem_error& e)
{
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR);
evt->SetString(e.what());
evt->SetInt(m_id);
m_evt_handler->QueueEvent(evt);
return;
}
m_filename = final_filename + extension; m_filename = final_filename + extension;
m_tmp_path = m_dest_folder / (m_filename + "." + std::to_string(get_current_pid()) + ".download"); m_tmp_path = m_dest_folder / (m_filename + "." + std::to_string(get_current_pid()) + ".download");

View File

@ -172,16 +172,20 @@ FileArchiveDialog::FileArchiveDialog(wxWindow* parent_window, mz_zip_archive* ar
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMAXIMIZE_BOX) wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMAXIMIZE_BOX)
, m_selected_paths (selected_paths) , m_selected_paths (selected_paths)
{ {
#ifdef _WIN32
wxGetApp().UpdateDarkUI(this);
#else
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
#endif
int em = em_unit(); int em = em_unit();
wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
m_avc = new ArchiveViewCtrl(this, wxSize(45 * em, 30 * em));
m_avc = new ArchiveViewCtrl(this, wxSize(60 * em, 30 * em)); wxDataViewColumn* toggle_column = m_avc->AppendToggleColumn(L"\u2714", 0, wxDATAVIEW_CELL_ACTIVATABLE, 6 * em);
m_avc->AppendToggleColumn(L"\u2714", 0, wxDATAVIEW_CELL_ACTIVATABLE, 6 * em);
m_avc->AppendTextColumn("filename", 1); m_avc->AppendTextColumn("filename", 1);
std::vector<std::shared_ptr<ArchiveViewNode>> stack; std::vector<std::shared_ptr<ArchiveViewNode>> stack;
std::function<void(std::vector<std::shared_ptr<ArchiveViewNode> >&, size_t)> reduce_stack = [] (std::vector<std::shared_ptr<ArchiveViewNode>>& stack, size_t size) { std::function<void(std::vector<std::shared_ptr<ArchiveViewNode> >&, size_t)> reduce_stack = [] (std::vector<std::shared_ptr<ArchiveViewNode>>& stack, size_t size) {
@ -233,40 +237,51 @@ FileArchiveDialog::FileArchiveDialog(wxWindow* parent_window, mz_zip_archive* ar
} }
// sorting files will help adjust_stack function to not create multiple same folders // sorting files will help adjust_stack function to not create multiple same folders
std::sort(filtered_entries.begin(), filtered_entries.end(), [](const boost::filesystem::path& p1, const boost::filesystem::path& p2){ return p1.string() > p2.string(); }); std::sort(filtered_entries.begin(), filtered_entries.end(), [](const boost::filesystem::path& p1, const boost::filesystem::path& p2){ return p1.string() > p2.string(); });
size_t entry_count = 0;
size_t depth = 1;
for (const boost::filesystem::path& path : filtered_entries) for (const boost::filesystem::path& path : filtered_entries)
{ {
std::shared_ptr<ArchiveViewNode> parent(nullptr); std::shared_ptr<ArchiveViewNode> parent(nullptr);
adjust_stack(path, stack); depth = std::max(depth, adjust_stack(path, stack));
if (!stack.empty()) if (!stack.empty())
parent = stack.back(); parent = stack.back();
if (std::regex_match(path.extension().string(), pattern_drop)) { // this leaves out non-compatible files if (std::regex_match(path.extension().string(), pattern_drop)) { // this leaves out non-compatible files
m_avc->get_model()->AddFile(parent, GUI::format_wxstr(path.filename().string()), false)->set_fullpath(/*std::move(path)*/path); // filename string to wstring? m_avc->get_model()->AddFile(parent, GUI::format_wxstr(path.filename().string()), false)->set_fullpath(/*std::move(path)*/path); // filename string to wstring?
entry_count++;
} }
} }
if (entry_count == 1)
on_all_button();
toggle_column->SetWidth((4 + depth) * em);
wxBoxSizer* btn_sizer = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer* btn_sizer = new wxBoxSizer(wxHORIZONTAL);
wxButton* btn_all = new wxButton(this, wxID_ANY, "All"); wxButton* btn_all = new wxButton(this, wxID_ANY, _L("All"));
btn_all->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { on_all_button(); }); btn_all->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { on_all_button(); });
btn_sizer->Add(btn_all, 0, wxLeft); btn_sizer->Add(btn_all, 0);
wxButton* btn_none = new wxButton(this, wxID_ANY, "None"); wxButton* btn_none = new wxButton(this, wxID_ANY, _L("None"));
btn_none->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { on_none_button(); }); btn_none->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { on_none_button(); });
btn_sizer->Add(btn_none, 0, wxLeft); btn_sizer->Add(btn_none, 0, wxLEFT, em);
btn_sizer->AddStretchSpacer(); btn_sizer->AddStretchSpacer();
wxButton* btn_run = new wxButton(this, wxID_OK, "Open"); wxButton* btn_run = new wxButton(this, wxID_OK, _L("Open"));
btn_run->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { on_open_button(); }); btn_run->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { on_open_button(); });
btn_sizer->Add(btn_run, 0, wxRIGHT); btn_sizer->Add(btn_run, 0, wxRIGHT, em);
wxButton* cancel_btn = new wxButton(this, wxID_CANCEL, "Cancel"); wxButton* cancel_btn = new wxButton(this, wxID_CANCEL, _L("Cancel"));
cancel_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { this->EndModal(wxID_CANCEL); }); cancel_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { this->EndModal(wxID_CANCEL); });
btn_sizer->Add(cancel_btn, 0, wxRIGHT); btn_sizer->Add(cancel_btn, 0, wxRIGHT, em);
topSizer->Add(m_avc, 1, wxEXPAND | wxALL, 10); topSizer->Add(m_avc, 1, wxEXPAND | wxALL, 10);
topSizer->Add(btn_sizer, 0, wxEXPAND | wxALL, 10); topSizer->Add(btn_sizer, 0, wxEXPAND | wxALL, 10);
this->SetMinSize(wxSize(80 * em, 30 * em));
this->SetSizer(topSizer); this->SetSizer(topSizer);
SetMinSize(wxSize(40 * em, 30 * em));
for (const wxString& id : {_L("All"), _L("None"), _L("Open"), _L("Cancel") })
wxGetApp().UpdateDarkUI(static_cast<wxButton*>(FindWindowByLabel(id, this)));
} }
void FileArchiveDialog::on_dpi_changed(const wxRect& suggested_rect) void FileArchiveDialog::on_dpi_changed(const wxRect& suggested_rect)
@ -277,9 +292,8 @@ void FileArchiveDialog::on_dpi_changed(const wxRect& suggested_rect)
//for (auto btn : { m_save_btn, m_transfer_btn, m_discard_btn }) //for (auto btn : { m_save_btn, m_transfer_btn, m_discard_btn })
// if (btn) btn->msw_rescale(); // if (btn) btn->msw_rescale();
const wxSize& size = wxSize(70 * em, 30 * em); const wxSize& size = wxSize(45 * em, 40 * em);
SetMinSize(size); SetSize(size);
//m_tree->Rescale(em); //m_tree->Rescale(em);
Fit(); Fit();

View File

@ -3947,8 +3947,8 @@ void GLCanvas3D::update_sequential_clearance()
// the results are then cached for following displacements // the results are then cached for following displacements
if (m_sequential_print_clearance_first_displacement) { if (m_sequential_print_clearance_first_displacement) {
m_sequential_print_clearance.m_hull_2d_cache.clear(); m_sequential_print_clearance.m_hull_2d_cache.clear();
float shrink_factor = static_cast<float>(scale_(0.5 * fff_print()->config().extruder_clearance_radius.value - EPSILON)); const float shrink_factor = static_cast<float>(scale_(0.5 * fff_print()->config().extruder_clearance_radius.value - EPSILON));
double mitter_limit = scale_(0.1); const double mitter_limit = scale_(0.1);
m_sequential_print_clearance.m_hull_2d_cache.reserve(m_model->objects.size()); m_sequential_print_clearance.m_hull_2d_cache.reserve(m_model->objects.size());
for (size_t i = 0; i < m_model->objects.size(); ++i) { for (size_t i = 0; i < m_model->objects.size(); ++i) {
ModelObject* model_object = m_model->objects[i]; ModelObject* model_object = m_model->objects[i];
@ -3956,7 +3956,7 @@ void GLCanvas3D::update_sequential_clearance()
#if ENABLE_WORLD_COORDINATE #if ENABLE_WORLD_COORDINATE
Geometry::Transformation trafo = model_instance0->get_transformation(); Geometry::Transformation trafo = model_instance0->get_transformation();
trafo.set_offset({ 0.0, 0.0, model_instance0->get_offset().z() }); trafo.set_offset({ 0.0, 0.0, model_instance0->get_offset().z() });
Polygon hull_2d = offset(model_object->convex_hull_2d(trafo.get_matrix()), const Polygon hull_2d = offset(model_object->convex_hull_2d(trafo.get_matrix()),
// Shrink the extruder_clearance_radius a tiny bit, so that if the object arrangement algorithm placed the objects // Shrink the extruder_clearance_radius a tiny bit, so that if the object arrangement algorithm placed the objects
// exactly by satisfying the extruder_clearance_radius, this test will not trigger collision. // exactly by satisfying the extruder_clearance_radius, this test will not trigger collision.
shrink_factor, shrink_factor,
@ -3984,13 +3984,8 @@ void GLCanvas3D::update_sequential_clearance()
polygons.reserve(instances_count); polygons.reserve(instances_count);
for (size_t i = 0; i < instance_transforms.size(); ++i) { for (size_t i = 0; i < instance_transforms.size(); ++i) {
const auto& instances = instance_transforms[i]; const auto& instances = instance_transforms[i];
double rotation_z0 = instances.front()->get_rotation().z();
for (const auto& instance : instances) { for (const auto& instance : instances) {
Geometry::Transformation transformation; const Transform3d& trafo = instance->get_matrix();
const Vec3d& offset = instance->get_offset();
transformation.set_offset({ offset.x(), offset.y(), 0.0 });
transformation.set_rotation(Z, instance->get_rotation().z() - rotation_z0);
const Transform3d& trafo = transformation.get_matrix();
const Pointf3s& hull_2d = m_sequential_print_clearance.m_hull_2d_cache[i]; const Pointf3s& hull_2d = m_sequential_print_clearance.m_hull_2d_cache[i];
Points inst_pts; Points inst_pts;
inst_pts.reserve(hull_2d.size()); inst_pts.reserve(hull_2d.size());
@ -7060,7 +7055,7 @@ void GLCanvas3D::GizmoHighlighter::init(GLGizmosManager* manager, GLGizmosManage
{ {
if (m_timer.IsRunning()) if (m_timer.IsRunning())
invalidate(); invalidate();
if (!gizmo || !canvas) if (gizmo == GLGizmosManager::EType::Undefined || !canvas)
return; return;
m_timer.Start(300, false); m_timer.Start(300, false);

View File

@ -79,7 +79,6 @@
#include "DesktopIntegrationDialog.hpp" #include "DesktopIntegrationDialog.hpp"
#include "SendSystemInfoDialog.hpp" #include "SendSystemInfoDialog.hpp"
#include "Downloader.hpp" #include "Downloader.hpp"
#include "ConfigWizard_private.hpp"
#include "BitmapCache.hpp" #include "BitmapCache.hpp"
#include "Notebook.hpp" #include "Notebook.hpp"
@ -2890,6 +2889,7 @@ void GUI_App::MacOpenURL(const wxString& url)
{ {
if (app_config && !app_config->get_bool("downloader_url_registered")) if (app_config && !app_config->get_bool("downloader_url_registered"))
{ {
notification_manager()->push_notification(NotificationType::URLNotRegistered);
BOOST_LOG_TRIVIAL(error) << "Recieved command to open URL, but it is not allowed in app configuration. URL: " << url; BOOST_LOG_TRIVIAL(error) << "Recieved command to open URL, but it is not allowed in app configuration. URL: " << url;
return; return;
} }
@ -3081,11 +3081,11 @@ void GUI_App::show_downloader_registration_dialog()
), SLIC3R_APP_NAME, SLIC3R_VERSION) ), SLIC3R_APP_NAME, SLIC3R_VERSION)
, true, wxYES_NO); , true, wxYES_NO);
if (msg.ShowModal() == wxID_YES) { if (msg.ShowModal() == wxID_YES) {
auto downloader = new DownloaderUtils::Worker(nullptr); auto downloader_worker = new DownloaderUtils::Worker(nullptr);
downloader->perform_register(app_config->get("url_downloader_dest")); downloader_worker->perform_register(app_config->get("url_downloader_dest"));
#ifdef __linux__ #ifdef __linux__
if (downloader->get_perform_registration_linux()) if (downloader_worker->get_perform_registration_linux())
DesktopIntegrationDialog::perform_desktop_integration(true); DesktopIntegrationDialog::perform_downloader_desktop_integration();
#endif // __linux__ #endif // __linux__
} else { } else {
app_config->set("downloader_url_registered", "0"); app_config->set("downloader_url_registered", "0");

View File

@ -306,7 +306,7 @@ public:
Plater* plater(); Plater* plater();
const Plater* plater() const; const Plater* plater() const;
Model& model(); Model& model();
NotificationManager * notification_manager(); NotificationManager* notification_manager();
GalleryDialog * gallery_dialog(); GalleryDialog * gallery_dialog();
Downloader* downloader(); Downloader* downloader();

View File

@ -448,14 +448,21 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
Selection& selection = canvas->get_selection(); Selection& selection = canvas->get_selection();
#if ENABLE_WORLD_COORDINATE #if ENABLE_WORLD_COORDINATE
if (selection.is_single_volume_or_modifier()) if (selection.is_single_volume_or_modifier()) {
GLVolume* vol = const_cast<GLVolume*>(selection.get_first_volume());
Geometry::Transformation trafo = vol->get_volume_transformation();
trafo.reset_rotation();
vol->set_volume_transformation(trafo);
}
#else #else
if (selection.is_single_volume() || selection.is_single_modifier()) if (selection.is_single_volume() || selection.is_single_modifier())
#endif // ENABLE_WORLD_COORDINATE
const_cast<GLVolume*>(selection.get_first_volume())->set_volume_rotation(Vec3d::Zero()); const_cast<GLVolume*>(selection.get_first_volume())->set_volume_rotation(Vec3d::Zero());
#endif // ENABLE_WORLD_COORDINATE
else if (selection.is_single_full_instance()) { else if (selection.is_single_full_instance()) {
Geometry::Transformation trafo = selection.get_first_volume()->get_instance_transformation();
trafo.reset_rotation();
for (unsigned int idx : selection.get_volume_idxs()) { for (unsigned int idx : selection.get_volume_idxs()) {
const_cast<GLVolume*>(selection.get_volume(idx))->set_instance_rotation(Vec3d::Zero()); const_cast<GLVolume*>(selection.get_volume(idx))->set_instance_transformation(trafo);
} }
} }
else else
@ -484,24 +491,22 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
Selection& selection = canvas->get_selection(); Selection& selection = canvas->get_selection();
if (selection.is_single_volume_or_modifier()) { if (selection.is_single_volume_or_modifier()) {
const bool is_left_handed = selection.get_first_volume()->get_volume_transformation().is_left_handed(); GLVolume* vol = const_cast<GLVolume*>(selection.get_first_volume());
const_cast<GLVolume*>(selection.get_first_volume())->set_volume_scaling_factor(Vec3d::Ones()); Geometry::Transformation trafo = vol->get_volume_transformation();
if (is_left_handed) trafo.reset_scaling_factor();
const_cast<GLVolume*>(selection.get_first_volume())->set_volume_mirror({ -1.0 , 1.0, 1.0 }); vol->set_volume_transformation(trafo);
} }
else if (selection.is_single_full_instance()) { else if (selection.is_single_full_instance()) {
const bool is_left_handed = selection.get_first_volume()->get_instance_transformation().is_left_handed(); Geometry::Transformation trafo = selection.get_first_volume()->get_instance_transformation();
trafo.reset_scaling_factor();
for (unsigned int idx : selection.get_volume_idxs()) { for (unsigned int idx : selection.get_volume_idxs()) {
const_cast<GLVolume*>(selection.get_volume(idx))->set_instance_scaling_factor(Vec3d::Ones()); const_cast<GLVolume*>(selection.get_volume(idx))->set_instance_transformation(trafo);
if (is_left_handed)
const_cast<GLVolume*>(selection.get_volume(idx))->set_instance_mirror({ -1.0 , 1.0, 1.0 });
} }
} }
else else
return; return;
canvas->do_scale(L("Reset scale")); canvas->do_scale(L("Reset scale"));
UpdateAndShow(true); UpdateAndShow(true);
#else #else
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset scale")); Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset scale"));
@ -740,7 +745,6 @@ void ObjectManipulation::update_settings_value(const Selection& selection)
m_new_rotate_label_string = L("Rotate (relative)"); m_new_rotate_label_string = L("Rotate (relative)");
m_new_position = Vec3d::Zero(); m_new_position = Vec3d::Zero();
m_new_rotation = Vec3d::Zero(); m_new_rotation = Vec3d::Zero();
m_new_scale = Vec3d(100.0, 100.0, 100.0);
m_new_size = selection.get_bounding_box_in_current_reference_system().first.size(); m_new_size = selection.get_bounding_box_in_current_reference_system().first.size();
#else #else
m_new_rotation = volume->get_instance_rotation() * (180.0 / M_PI); m_new_rotation = volume->get_instance_rotation() * (180.0 / M_PI);
@ -927,93 +931,48 @@ void ObjectManipulation::update_if_dirty()
#if ENABLE_WORLD_COORDINATE
void ObjectManipulation::update_reset_buttons_visibility() void ObjectManipulation::update_reset_buttons_visibility()
{ {
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
if (!canvas) if (!canvas)
return; return;
const Selection& selection = canvas->get_selection();
bool show_drop_to_bed = false;
bool show_rotation = false; bool show_rotation = false;
bool show_scale = false; bool show_scale = false;
bool show_drop_to_bed = false; bool show_mirror = false;
#if ENABLE_WORLD_COORDINATE
bool show_skew = false; bool show_skew = false;
bool show_mirror_warning = false;
const Selection& selection = canvas->get_selection();
if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) { if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) {
const double min_z = selection.is_single_full_instance() ? selection.get_scaled_instance_bounding_box().min.z() : const double min_z = selection.is_single_full_instance() ? selection.get_scaled_instance_bounding_box().min.z() :
get_volume_min_z(*selection.get_first_volume()); get_volume_min_z(*selection.get_first_volume());
show_drop_to_bed = std::abs(min_z) > EPSILON; show_drop_to_bed = std::abs(min_z) > EPSILON;
const GLVolume* volume = selection.get_first_volume(); const GLVolume* volume = selection.get_first_volume();
Geometry::Transformation trafo; const Geometry::Transformation trafo = selection.is_single_full_instance() ? volume->get_instance_transformation() : volume->get_volume_transformation();
#else
if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) {
const GLVolume* volume = selection.get_first_volume();
Vec3d rotation;
Vec3d scale;
double min_z = 0.0;
#endif // ENABLE_WORLD_COORDINATE
if (selection.is_single_full_instance()) { const Geometry::TransformationSVD trafo_svd(trafo);
#if ENABLE_WORLD_COORDINATE show_rotation = trafo_svd.rotation;
trafo = volume->get_instance_transformation(); show_scale = trafo_svd.scale;
const Selection::IndicesList& idxs = selection.get_volume_idxs(); show_mirror = trafo_svd.mirror;
for (unsigned int id : idxs) { show_skew = trafo_svd.skew;
const Geometry::Transformation world_trafo(selection.get_volume(id)->world_matrix());
show_skew |= world_trafo.has_skew();
show_mirror_warning |= world_trafo.get_matrix().matrix().determinant() < 0.0;
}
#else
rotation = volume->get_instance_rotation();
scale = volume->get_instance_scaling_factor();
min_z = selection.get_scaled_instance_bounding_box().min.z();
#endif // ENABLE_WORLD_COORDINATE
}
else {
#if ENABLE_WORLD_COORDINATE
Geometry::Transformation trafo = volume->get_volume_transformation();
const Geometry::Transformation world_trafo(volume->world_matrix());
show_skew |= world_trafo.has_skew();
show_mirror_warning |= world_trafo.get_matrix().matrix().determinant() < 0.0;
#else
rotation = volume->get_volume_rotation();
scale = volume->get_volume_scaling_factor();
min_z = get_volume_min_z(*volume);
#endif // ENABLE_WORLD_COORDINATE
}
#if ENABLE_WORLD_COORDINATE
const Transform3d rotation = trafo.get_rotation_matrix();
const Transform3d scale = trafo.get_scaling_factor_matrix();
show_rotation = show_mirror_warning ? !trafo.get_matrix().matrix().block<3, 3>(0, 0).isDiagonal() : !rotation.isApprox(Transform3d::Identity());
show_scale = !scale.isApprox(Transform3d::Identity());
#else
show_rotation = !rotation.isApprox(Vec3d::Zero());
show_scale = !scale.isApprox(Vec3d::Ones());
show_drop_to_bed = std::abs(min_z) > SINKING_Z_THRESHOLD;
#endif // ENABLE_WORLD_COORDINATE
} }
#if ENABLE_WORLD_COORDINATE wxGetApp().CallAfter([this, show_drop_to_bed, show_rotation, show_scale, show_mirror, show_skew] {
wxGetApp().CallAfter([this, show_rotation, show_scale, show_drop_to_bed, show_skew, show_mirror_warning] {
#else
wxGetApp().CallAfter([this, show_rotation, show_scale, show_drop_to_bed] {
#endif // ENABLE_WORLD_COORDINATE
// There is a case (under OSX), when this function is called after the Manipulation panel is hidden // There is a case (under OSX), when this function is called after the Manipulation panel is hidden
// So, let check if Manipulation panel is still shown for this moment // So, let check if Manipulation panel is still shown for this moment
if (!this->IsShown()) if (!this->IsShown())
return; return;
m_drop_to_bed_button->Show(show_drop_to_bed);
m_reset_rotation_button->Show(show_rotation); m_reset_rotation_button->Show(show_rotation);
m_reset_scale_button->Show(show_scale); m_reset_scale_button->Show(show_scale);
m_drop_to_bed_button->Show(show_drop_to_bed); m_mirror_warning_bitmap->SetBitmap(show_mirror ? m_manifold_warning_bmp.bmp() : wxNullBitmap);
#if ENABLE_WORLD_COORDINATE m_mirror_warning_bitmap->SetMinSize(show_mirror ? m_manifold_warning_bmp.GetSize() : wxSize(0, 0));
m_mirror_warning_bitmap->SetToolTip(show_mirror ? _L("Left handed") : "");
m_reset_skew_button->Show(show_skew); m_reset_skew_button->Show(show_skew);
m_skew_label->Show(show_skew); m_skew_label->Show(show_skew);
m_mirror_warning_bitmap->SetBitmap(show_mirror_warning ? m_manifold_warning_bmp.bmp() : wxNullBitmap);
m_mirror_warning_bitmap->SetMinSize(show_mirror_warning ? m_manifold_warning_bmp.GetSize() : wxSize(0, 0));
m_mirror_warning_bitmap->SetToolTip(show_mirror_warning ? _L("Left handed") : "");
#endif // ENABLE_WORLD_COORDINATE
// Because of CallAfter we need to layout sidebar after Show/hide of reset buttons one more time // Because of CallAfter we need to layout sidebar after Show/hide of reset buttons one more time
Sidebar& panel = wxGetApp().sidebar(); Sidebar& panel = wxGetApp().sidebar();
@ -1024,23 +983,75 @@ void ObjectManipulation::update_reset_buttons_visibility()
} }
}); });
} }
#else
void ObjectManipulation::update_reset_buttons_visibility()
{
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
if (!canvas)
return;
const Selection& selection = canvas->get_selection();
bool show_rotation = false;
bool show_scale = false;
bool show_drop_to_bed = false;
if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) {
const GLVolume* volume = selection.get_first_volume();
Vec3d rotation;
Vec3d scale;
double min_z = 0.0;
if (selection.is_single_full_instance()) {
rotation = volume->get_instance_rotation();
scale = volume->get_instance_scaling_factor();
min_z = selection.get_scaled_instance_bounding_box().min.z();
}
else {
rotation = volume->get_volume_rotation();
scale = volume->get_volume_scaling_factor();
min_z = get_volume_min_z(*volume);
}
show_rotation = !rotation.isApprox(Vec3d::Zero());
show_scale = !scale.isApprox(Vec3d::Ones());
show_drop_to_bed = std::abs(min_z) > SINKING_Z_THRESHOLD;
}
wxGetApp().CallAfter([this, show_rotation, show_scale, show_drop_to_bed] {
// There is a case (under OSX), when this function is called after the Manipulation panel is hidden
// So, let check if Manipulation panel is still shown for this moment
if (!this->IsShown())
return;
m_reset_rotation_button->Show(show_rotation);
m_reset_scale_button->Show(show_scale);
m_drop_to_bed_button->Show(show_drop_to_bed);
// Because of CallAfter we need to layout sidebar after Show/hide of reset buttons one more time
Sidebar& panel = wxGetApp().sidebar();
if (!panel.IsFrozen()) {
panel.Freeze();
panel.Layout();
panel.Thaw();
}
});
}
#endif // ENABLE_WORLD_COORDINATE
void ObjectManipulation::update_mirror_buttons_visibility() void ObjectManipulation::update_mirror_buttons_visibility()
{ {
#if ENABLE_WORLD_COORDINATE
const bool can_mirror = wxGetApp().plater()->can_mirror();
for (ScalableButton* button : m_mirror_buttons) {
button->Enable(can_mirror);
}
#else
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
Selection& selection = canvas->get_selection(); Selection& selection = canvas->get_selection();
#if ENABLE_WORLD_COORDINATE
if (is_local_coordinates()) {
if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) {
#else
std::array<MirrorButtonState, 3> new_states = { mbHidden, mbHidden, mbHidden }; std::array<MirrorButtonState, 3> new_states = { mbHidden, mbHidden, mbHidden };
if (!m_world_coordinates) { if (!m_world_coordinates) {
if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) {
#endif // ENABLE_WORLD_COORDINATE
const GLVolume* volume = selection.get_first_volume(); const GLVolume* volume = selection.get_first_volume();
Vec3d mirror; Vec3d mirror;
@ -1049,19 +1060,10 @@ void ObjectManipulation::update_mirror_buttons_visibility()
else else
mirror = volume->get_volume_mirror(); mirror = volume->get_volume_mirror();
#if !ENABLE_WORLD_COORDINATE
for (unsigned char i=0; i<3; ++i) for (unsigned char i=0; i<3; ++i)
new_states[i] = (mirror[i] < 0. ? mbActive : mbShown); new_states[i] = (mirror[i] < 0. ? mbActive : mbShown);
#endif // !ENABLE_WORLD_COORDINATE
} }
} }
#if ENABLE_WORLD_COORDINATE
const bool can_mirror = wxGetApp().plater()->can_mirror();
for (ScalableButton* button : m_mirror_buttons) {
button->Enable(can_mirror);
}
#else
else { else {
// the mirroring buttons should be hidden in world coordinates, // the mirroring buttons should be hidden in world coordinates,
// unless we make it actually mirror in world coords. // unless we make it actually mirror in world coords.

View File

@ -198,16 +198,23 @@ void GLGizmoBase::render_grabbers(const BoundingBoxf3& box) const
} }
void GLGizmoBase::render_grabbers(float size) const void GLGizmoBase::render_grabbers(float size) const
{
render_grabbers(0, m_grabbers.size() - 1, size, false);
}
void GLGizmoBase::render_grabbers(size_t first, size_t last, float size, bool force_hover) const
{ {
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
if (shader == nullptr) if (shader == nullptr)
return; return;
shader->start_using(); shader->start_using();
shader->set_uniform("emission_factor", 0.1f); shader->set_uniform("emission_factor", 0.1f);
for (int i = 0; i < (int)m_grabbers.size(); ++i) { glsafe(::glDisable(GL_CULL_FACE));
for (size_t i = first; i <= last; ++i) {
if (m_grabbers[i].enabled) if (m_grabbers[i].enabled)
m_grabbers[i].render(m_hover_id == i, size); m_grabbers[i].render(force_hover ? true : m_hover_id == (int)i, size);
} }
glsafe(::glEnable(GL_CULL_FACE));
shader->stop_using(); shader->stop_using();
} }

View File

@ -219,6 +219,7 @@ protected:
void render_grabbers(const BoundingBoxf3& box) const; void render_grabbers(const BoundingBoxf3& box) const;
void render_grabbers(float size) const; void render_grabbers(float size) const;
void render_grabbers(size_t first, size_t last, float size, bool force_hover) const;
std::string format(float value, unsigned int decimals) const; std::string format(float value, unsigned int decimals) const;

View File

@ -244,7 +244,7 @@ std::string GLGizmoCut3D::get_tooltip() const
if (m_hover_id == Z || (m_dragging && m_hover_id == CutPlane)) { if (m_hover_id == Z || (m_dragging && m_hover_id == CutPlane)) {
double koef = m_imperial_units ? ObjectManipulation::mm_to_in : 1.0; double koef = m_imperial_units ? ObjectManipulation::mm_to_in : 1.0;
std::string unit_str = " " + (m_imperial_units ? _u8L("inch") : _u8L("mm")); std::string unit_str = " " + (m_imperial_units ? _u8L("inch") : _u8L("mm"));
const BoundingBoxf3 tbb = transformed_bounding_box(m_plane_center); const BoundingBoxf3& tbb = m_transformed_bounding_box;
if (tbb.max.z() >= 0.0) { if (tbb.max.z() >= 0.0) {
double top = (tbb.min.z() <= 0.0 ? tbb.max.z() : tbb.size().z()) * koef; double top = (tbb.min.z() <= 0.0 ? tbb.max.z() : tbb.size().z()) * koef;
tooltip += format(top, 2) + " " + unit_str + " (" + _u8L("Top part") + ")"; tooltip += format(top, 2) + " " + unit_str + " (" + _u8L("Top part") + ")";
@ -401,7 +401,7 @@ bool GLGizmoCut3D::is_looking_forward() const
void GLGizmoCut3D::update_clipper() void GLGizmoCut3D::update_clipper()
{ {
BoundingBoxf3 box = bounding_box(); BoundingBoxf3 box = m_bounding_box;
// update cut_normal // update cut_normal
Vec3d beg, end = beg = m_plane_center; Vec3d beg, end = beg = m_plane_center;
@ -549,7 +549,7 @@ bool GLGizmoCut3D::render_slider_double_input(const std::string& label, float& v
return !is_approx(old_val, value); return !is_approx(old_val, value);
}; };
const BoundingBoxf3 bbox = bounding_box(); const BoundingBoxf3 bbox = m_bounding_box;
const float mean_size = float((bbox.size().x() + bbox.size().y() + bbox.size().z()) / 9.0) * (m_imperial_units ? f_mm_to_in : 1.f); const float mean_size = float((bbox.size().x() + bbox.size().y() + bbox.size().z()) / 9.0) * (m_imperial_units ? f_mm_to_in : 1.f);
ImGuiWrapper::text(label); ImGuiWrapper::text(label);
@ -795,7 +795,7 @@ void GLGizmoCut3D::render_cut_plane_grabbers()
const Transform3d view_matrix = wxGetApp().plater()->get_camera().get_view_matrix() * translation_transform(m_plane_center) * m_rotation_m; const Transform3d view_matrix = wxGetApp().plater()->get_camera().get_view_matrix() * translation_transform(m_plane_center) * m_rotation_m;
const double mean_size = get_grabber_mean_size(bounding_box()); const double mean_size = get_grabber_mean_size(m_bounding_box);
double size; double size;
const bool dragging_by_cut_plane = m_dragging && m_hover_id == CutPlane; const bool dragging_by_cut_plane = m_dragging && m_hover_id == CutPlane;
@ -1033,7 +1033,7 @@ void GLGizmoCut3D::update_raycasters_for_picking_transform()
else if (!cut_line_processing()){ else if (!cut_line_processing()){
const Transform3d trafo = translation_transform(m_plane_center) * m_rotation_m; const Transform3d trafo = translation_transform(m_plane_center) * m_rotation_m;
const BoundingBoxf3 box = bounding_box(); const BoundingBoxf3 box = m_bounding_box;
const double size = get_half_size(get_grabber_mean_size(box)); const double size = get_half_size(get_grabber_mean_size(box));
Vec3d scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); Vec3d scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size);
@ -1127,14 +1127,14 @@ void GLGizmoCut3D::dragging_grabber_z(const GLGizmoBase::UpdateData &data)
Vec3d starting_vec = m_rotation_m * Vec3d::UnitZ(); Vec3d starting_vec = m_rotation_m * Vec3d::UnitZ();
if (starting_vec.norm() != 0.0) { if (starting_vec.norm() != 0.0) {
Vec3d mouse_dir = data.mouse_ray.unit_vector(); const Vec3d mouse_dir = data.mouse_ray.unit_vector();
// finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position
// use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form
// in our case plane normal and ray direction are the same (orthogonal view) // in our case plane normal and ray direction are the same (orthogonal view)
// when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal
Vec3d inters = data.mouse_ray.a + (starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; const Vec3d inters = data.mouse_ray.a + (starting_drag_position - data.mouse_ray.a).dot(mouse_dir) * mouse_dir;
// vector from the starting position to the found intersection // vector from the starting position to the found intersection
Vec3d inters_vec = inters - starting_drag_position; const Vec3d inters_vec = inters - starting_drag_position;
starting_vec.normalize(); starting_vec.normalize();
// finds projection of the vector along the staring direction // finds projection of the vector along the staring direction
@ -1183,7 +1183,11 @@ void GLGizmoCut3D::dragging_grabber_xy(const GLGizmoBase::UpdateData &data)
Vec3d rotation = Vec3d::Zero(); Vec3d rotation = Vec3d::Zero();
rotation[m_hover_id] = theta; rotation[m_hover_id] = theta;
m_rotation_m = m_start_dragging_m * rotation_transform(rotation);
const Transform3d rotation_tmp = m_start_dragging_m * rotation_transform(rotation);
if (m_rotation_m.rotation() != rotation_tmp.rotation())
m_transformed_bounding_box = transformed_bounding_box(m_plane_center);
m_rotation_m = rotation_tmp;
m_angle = theta; m_angle = theta;
while (m_angle > two_pi) while (m_angle > two_pi)
@ -1245,9 +1249,13 @@ void GLGizmoCut3D::on_stop_dragging()
void GLGizmoCut3D::set_center_pos(const Vec3d& center_pos, bool force/* = false*/) void GLGizmoCut3D::set_center_pos(const Vec3d& center_pos, bool force/* = false*/)
{ {
if (m_plane_center == center_pos)
return;
bool can_set_center_pos = force; bool can_set_center_pos = force;
BoundingBoxf3 tbb;
if (!can_set_center_pos) { if (!can_set_center_pos) {
const BoundingBoxf3 tbb = transformed_bounding_box(center_pos); tbb = transformed_bounding_box(center_pos);
if (tbb.max.z() > -1. && tbb.min.z() < 1.) if (tbb.max.z() > -1. && tbb.min.z() < 1.)
can_set_center_pos = true; can_set_center_pos = true;
else { else {
@ -1260,6 +1268,7 @@ void GLGizmoCut3D::set_center_pos(const Vec3d& center_pos, bool force/* = false*
} }
if (can_set_center_pos) { if (can_set_center_pos) {
m_transformed_bounding_box = tbb;
m_plane_center = center_pos; m_plane_center = center_pos;
m_center_offset = m_plane_center - m_bb_center; m_center_offset = m_plane_center - m_bb_center;
} }
@ -1279,7 +1288,7 @@ BoundingBoxf3 GLGizmoCut3D::bounding_box() const
return ret; return ret;
} }
BoundingBoxf3 GLGizmoCut3D::transformed_bounding_box(const Vec3d& plane_center, bool revert_move /*= false*/) const BoundingBoxf3 GLGizmoCut3D::transformed_bounding_box(const Vec3d& plane_center) const
{ {
// #ysFIXME !!! // #ysFIXME !!!
BoundingBoxf3 ret; BoundingBoxf3 ret;
@ -1299,10 +1308,7 @@ BoundingBoxf3 GLGizmoCut3D::transformed_bounding_box(const Vec3d& plane_center,
Vec3d cut_center_offset = plane_center - instance_offset; Vec3d cut_center_offset = plane_center - instance_offset;
cut_center_offset[Z] -= sel_info->get_sla_shift(); cut_center_offset[Z] -= sel_info->get_sla_shift();
const auto move = translation_transform(-cut_center_offset); const auto cut_matrix = Transform3d::Identity() * m_rotation_m.inverse() * translation_transform(-cut_center_offset);
const auto move2 = translation_transform(plane_center);
const auto cut_matrix = (revert_move ? move2 : Transform3d::Identity()) * m_rotation_m.inverse() * move;
const Selection& selection = m_parent.get_selection(); const Selection& selection = m_parent.get_selection();
const Selection::IndicesList& idxs = selection.get_volume_idxs(); const Selection::IndicesList& idxs = selection.get_volume_idxs();
@ -1335,6 +1341,8 @@ bool GLGizmoCut3D::update_bb()
const BoundingBoxf3 box = bounding_box(); const BoundingBoxf3 box = bounding_box();
if (m_max_pos != box.max || m_min_pos != box.min) { if (m_max_pos != box.max || m_min_pos != box.min) {
m_bounding_box = box;
invalidate_cut_plane(); invalidate_cut_plane();
m_max_pos = box.max; m_max_pos = box.max;
@ -1388,7 +1396,7 @@ void GLGizmoCut3D::init_picking_models()
} }
if (!m_plane.model.is_initialized() && !m_hide_cut_plane && !m_connectors_editing) { if (!m_plane.model.is_initialized() && !m_hide_cut_plane && !m_connectors_editing) {
const double cp_width = 0.02 * get_grabber_mean_size(bounding_box()); const double cp_width = 0.02 * get_grabber_mean_size(m_bounding_box);
indexed_triangle_set its = its_make_frustum_dowel((double)m_cut_plane_radius_koef * m_radius, cp_width, m_cut_plane_as_circle ? 180 : 4); indexed_triangle_set its = its_make_frustum_dowel((double)m_cut_plane_radius_koef * m_radius, cp_width, m_cut_plane_as_circle ? 180 : 4);
m_plane.model.init_from(its); m_plane.model.init_from(its);
m_plane.mesh_raycaster = std::make_unique<MeshRaycaster>(std::make_shared<const TriangleMesh>(std::move(its))); m_plane.mesh_raycaster = std::make_unique<MeshRaycaster>(std::make_shared<const TriangleMesh>(std::move(its)));
@ -1631,9 +1639,8 @@ void GLGizmoCut3D::render_build_size()
{ {
double koef = m_imperial_units ? ObjectManipulation::mm_to_in : 1.0; double koef = m_imperial_units ? ObjectManipulation::mm_to_in : 1.0;
wxString unit_str = " " + (m_imperial_units ? _L("in") : _L("mm")); wxString unit_str = " " + (m_imperial_units ? _L("in") : _L("mm"));
const BoundingBoxf3 tbb = transformed_bounding_box(m_plane_center);
Vec3d tbb_sz = tbb.size(); Vec3d tbb_sz = m_transformed_bounding_box.size();
wxString size = "X: " + double_to_string(tbb_sz.x() * koef, 2) + unit_str + wxString size = "X: " + double_to_string(tbb_sz.x() * koef, 2) + unit_str +
", Y: " + double_to_string(tbb_sz.y() * koef, 2) + unit_str + ", Y: " + double_to_string(tbb_sz.y() * koef, 2) + unit_str +
", Z: " + double_to_string(tbb_sz.z() * koef, 2) + unit_str; ", Z: " + double_to_string(tbb_sz.z() * koef, 2) + unit_str;
@ -1646,7 +1653,7 @@ void GLGizmoCut3D::render_build_size()
void GLGizmoCut3D::reset_cut_plane() void GLGizmoCut3D::reset_cut_plane()
{ {
set_center(bounding_box().center()); set_center(m_bb_center);
m_rotation_m = Transform3d::Identity(); m_rotation_m = Transform3d::Identity();
m_angle_arc.reset(); m_angle_arc.reset();
update_clipper(); update_clipper();
@ -1746,7 +1753,7 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors)
const bool has_connectors = !connectors.empty(); const bool has_connectors = !connectors.empty();
const bool is_cut_plane_init = m_rotation_m.isApprox(Transform3d::Identity()) && bounding_box().center() == m_plane_center; const bool is_cut_plane_init = m_rotation_m.isApprox(Transform3d::Identity()) && m_bb_center == m_plane_center;
m_imgui->disabled_begin(is_cut_plane_init); m_imgui->disabled_begin(is_cut_plane_init);
if (render_reset_button("cut_plane", _u8L("Reset cutting plane"))) if (render_reset_button("cut_plane", _u8L("Reset cutting plane")))
reset_cut_plane(); reset_cut_plane();
@ -1838,10 +1845,9 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors)
add_vertical_scaled_interval(0.75f); add_vertical_scaled_interval(0.75f);
m_imgui->disabled_begin(has_connectors); m_imgui->disabled_begin(has_connectors);
add_horizontal_shift(m_imgui->scaled(/*1*/.2f));
ImGuiWrapper::text(_L("Cut to") + ":"); ImGuiWrapper::text(_L("Cut to") + ":");
ImGui::SameLine(); add_horizontal_scaled_interval(1.2f);
if (m_imgui->radio_button(_L("Objects"), !m_keep_as_parts)) if (m_imgui->radio_button(_L("Objects"), !m_keep_as_parts))
m_keep_as_parts = false; m_keep_as_parts = false;
ImGui::SameLine(); ImGui::SameLine();
@ -1990,38 +1996,6 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit)
render_debug_input_window(x); render_debug_input_window(x);
} }
// get volume transformation regarding to the "border". Border is related from the size of connectors
Transform3d GLGizmoCut3D::get_volume_transformation(const ModelVolume* volume) const
{
bool is_prizm_dowel = m_connector_type == CutConnectorType::Dowel && m_connector_style == size_t(CutConnectorStyle::Prizm);
#if ENABLE_WORLD_COORDINATE
const Transform3d connector_trafo = is_prizm_dowel ?
Geometry::translation_transform(-m_connector_depth_ratio * Vec3d::UnitZ()) * m_rotation_m * Geometry::scale_transform({ 0.5 * m_connector_size, 0.5 * m_connector_size, 2 * m_connector_depth_ratio }) :
m_rotation_m * Geometry::scale_transform({ 0.5 * m_connector_size, 0.5 * m_connector_size, m_connector_depth_ratio });
#else
const Transform3d connector_trafo = assemble_transform(
is_prizm_dowel ? Vec3d(0.0, 0.0, -m_connector_depth_ratio) : Vec3d::Zero(),
Transformation(m_rotation_m).get_rotation(),
Vec3d(0.5*m_connector_size, 0.5*m_connector_size, is_prizm_dowel ? 2 * m_connector_depth_ratio : m_connector_depth_ratio),
Vec3d::Ones());
#endif // ENABLE_WORLD_COORDINATE
const Vec3d connector_bb = m_connector_mesh.transformed_bounding_box(connector_trafo).size();
const Vec3d bb = volume->mesh().bounding_box().size();
// calculate an unused border - part of the the volume, where we can't put connectors
const Vec3d border_scale(connector_bb.x() / bb.x(), connector_bb.y() / bb.y(), connector_bb.z() / bb.z());
const Transform3d vol_matrix = volume->get_matrix();
const Vec3d vol_trans = vol_matrix.translation();
// offset of the volume will be changed after scaling, so calculate the needed offset and set it to a volume_trafo
const Vec3d offset(vol_trans.x() * border_scale.x(), vol_trans.y() * border_scale.y(), vol_trans.z() * border_scale.z());
// scale and translate volume to suppress to put connectors too close to the border
return translation_transform(offset) * scale_transform(Vec3d::Ones() - border_scale) * vol_matrix;
}
bool GLGizmoCut3D::is_outside_of_cut_contour(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos) bool GLGizmoCut3D::is_outside_of_cut_contour(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos)
{ {
// check if connector pos is out of clipping plane // check if connector pos is out of clipping plane
@ -2072,7 +2046,7 @@ bool GLGizmoCut3D::is_conflict_for_connector(size_t idx, const CutConnectors& co
const BoundingBoxf3 cur_tbb = m_shapes[cur_connector.attribs].model.get_bounding_box().transformed(matrix); const BoundingBoxf3 cur_tbb = m_shapes[cur_connector.attribs].model.get_bounding_box().transformed(matrix);
// check if connector's bounding box is inside the object's bounding box // check if connector's bounding box is inside the object's bounding box
if (!bounding_box().contains(cur_tbb)) { if (!m_bounding_box.contains(cur_tbb)) {
m_info_stats.outside_bb++; m_info_stats.outside_bb++;
return true; return true;
} }

View File

@ -43,6 +43,9 @@ class GLGizmoCut3D : public GLGizmoBase
Vec3d m_bb_center{ Vec3d::Zero() }; Vec3d m_bb_center{ Vec3d::Zero() };
Vec3d m_center_offset{ Vec3d::Zero() }; Vec3d m_center_offset{ Vec3d::Zero() };
BoundingBoxf3 m_bounding_box;
BoundingBoxf3 m_transformed_bounding_box;
// values from RotationGizmo // values from RotationGizmo
double m_radius{ 0.0 }; double m_radius{ 0.0 };
double m_grabber_radius{ 0.0 }; double m_grabber_radius{ 0.0 };
@ -193,7 +196,7 @@ public:
void invalidate_cut_plane(); void invalidate_cut_plane();
BoundingBoxf3 bounding_box() const; BoundingBoxf3 bounding_box() const;
BoundingBoxf3 transformed_bounding_box(const Vec3d& plane_center, bool revert_move = false) const; BoundingBoxf3 transformed_bounding_box(const Vec3d& plane_center) const;
protected: protected:
bool on_init() override; bool on_init() override;
@ -263,7 +266,6 @@ private:
void render_connect_mode_radio_button(CutConnectorMode mode); void render_connect_mode_radio_button(CutConnectorMode mode);
bool render_reset_button(const std::string& label_id, const std::string& tooltip) const; bool render_reset_button(const std::string& label_id, const std::string& tooltip) const;
bool render_connect_type_radio_button(CutConnectorType type); bool render_connect_type_radio_button(CutConnectorType type);
Transform3d get_volume_transformation(const ModelVolume* volume) const;
bool is_outside_of_cut_contour(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos); bool is_outside_of_cut_contour(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos);
bool is_conflict_for_connector(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos); bool is_conflict_for_connector(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos);
void render_connectors(); void render_connectors();

View File

@ -519,6 +519,13 @@ bool GLGizmoFdmSupports::has_backend_supports()
void GLGizmoFdmSupports::auto_generate() void GLGizmoFdmSupports::auto_generate()
{ {
std::string err = wxGetApp().plater()->fff_print().validate();
if (!err.empty()) {
MessageDialog dlg(GUI::wxGetApp().plater(), _L("Automatic painting requires valid print setup. \n") + from_u8(err), _L("Warning"), wxOK);
dlg.ShowModal();
return;
}
ModelObject *mo = m_c->selection_info()->model_object(); ModelObject *mo = m_c->selection_info()->model_object();
bool not_painted = std::all_of(mo->volumes.begin(), mo->volumes.end(), [](const ModelVolume* vol){ bool not_painted = std::all_of(mo->volumes.begin(), mo->volumes.end(), [](const ModelVolume* vol){
return vol->type() != ModelVolumeType::MODEL_PART || vol->supported_facets.empty(); return vol->type() != ModelVolumeType::MODEL_PART || vol->supported_facets.empty();

View File

@ -3,7 +3,7 @@
#include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" #include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp"
#include "libslic3r/Geometry/ConvexHull.hpp" #include "libslic3r/Geometry/ConvexHull.hpp"
@ -38,6 +38,7 @@ bool GLGizmoFlatten::on_mouse(const wxMouseEvent &mouse_event)
// Rotate the object so the normal points downward: // Rotate the object so the normal points downward:
selection.flattening_rotate(m_planes[m_hover_id].normal); selection.flattening_rotate(m_planes[m_hover_id].normal);
m_parent.do_rotate(L("Gizmo-Place on Face")); m_parent.do_rotate(L("Gizmo-Place on Face"));
wxGetApp().obj_manipul()->set_dirty();
} }
return true; return true;
} }

View File

@ -339,7 +339,7 @@ double GLGizmoMove3D::calc_projection(const UpdateData& data) const
// use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form
// in our case plane normal and ray direction are the same (orthogonal view) // in our case plane normal and ray direction are the same (orthogonal view)
// when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal
const Vec3d inters = data.mouse_ray.a + (m_starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; const Vec3d inters = data.mouse_ray.a + (m_starting_drag_position - data.mouse_ray.a).dot(mouse_dir) * mouse_dir;
// vector from the starting position to the found intersection // vector from the starting position to the found intersection
const Vec3d inters_vec = inters - m_starting_drag_position; const Vec3d inters_vec = inters - m_starting_drag_position;

View File

@ -238,7 +238,13 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection)
selection.get_bounding_box_in_reference_system(ECoordinatesType::Local) : selection.get_bounding_box_in_current_reference_system(); selection.get_bounding_box_in_reference_system(ECoordinatesType::Local) : selection.get_bounding_box_in_current_reference_system();
m_bounding_box = box; m_bounding_box = box;
m_center = box_trafo.translation(); m_center = box_trafo.translation();
m_orient_matrix = box_trafo; m_orient_matrix = Geometry::translation_transform(m_center);
if (!wxGetApp().obj_manipul()->is_world_coordinates()) {
const GLVolume& v = *selection.get_first_volume();
m_orient_matrix = m_orient_matrix * v.get_instance_transformation().get_rotation_matrix();
if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates())
m_orient_matrix = m_orient_matrix * v.get_volume_transformation().get_rotation_matrix();
}
m_radius = Offset + m_bounding_box.radius(); m_radius = Offset + m_bounding_box.radius();
m_snap_coarse_in_radius = m_radius / 3.0f; m_snap_coarse_in_radius = m_radius / 3.0f;

View File

@ -218,15 +218,7 @@ void GLGizmoScale3D::on_render()
m_bounding_box = box; m_bounding_box = box;
m_center = box_trafo.translation(); m_center = box_trafo.translation();
m_grabbers_transform = box_trafo; m_grabbers_transform = box_trafo;
m_instance_center = Vec3d::Zero(); m_instance_center = (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) ? selection.get_first_volume()->get_instance_offset() : m_center;
if (selection.is_single_full_instance() && !wxGetApp().obj_manipul()->is_world_coordinates())
m_instance_center = selection.get_first_volume()->get_instance_offset();
else if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_instance_coordinates())
m_instance_center = m_center;
else if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates())
m_instance_center = m_center;
else
m_instance_center = selection.is_single_full_instance() ? selection.get_first_volume()->get_instance_offset() : m_center;
// x axis // x axis
const Vec3d box_half_size = 0.5 * m_bounding_box.size(); const Vec3d box_half_size = 0.5 * m_bounding_box.size();
@ -264,9 +256,8 @@ void GLGizmoScale3D::on_render()
#endif // ENABLE_GL_CORE_PROFILE #endif // ENABLE_GL_CORE_PROFILE
glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f));
const Transform3d base_matrix = local_transform(selection);
for (int i = 0; i < 10; ++i) { for (int i = 0; i < 10; ++i) {
m_grabbers[i].matrix = base_matrix; m_grabbers[i].matrix = m_grabbers_transform;
} }
const float grabber_mean_size = (float)((m_bounding_box.size().x() + m_bounding_box.size().y() + m_bounding_box.size().z()) / 3.0); const float grabber_mean_size = (float)((m_bounding_box.size().x() + m_bounding_box.size().y() + m_bounding_box.size().z()) / 3.0);
@ -281,7 +272,7 @@ void GLGizmoScale3D::on_render()
if (shader != nullptr) { if (shader != nullptr) {
shader->start_using(); shader->start_using();
const Camera& camera = wxGetApp().plater()->get_camera(); const Camera& camera = wxGetApp().plater()->get_camera();
shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix); shader->set_uniform("view_model_matrix", camera.get_view_matrix() * m_grabbers_transform);
shader->set_uniform("projection_matrix", camera.get_projection_matrix()); shader->set_uniform("projection_matrix", camera.get_projection_matrix());
#if ENABLE_GL_CORE_PROFILE #if ENABLE_GL_CORE_PROFILE
const std::array<int, 4>& viewport = camera.get_viewport(); const std::array<int, 4>& viewport = camera.get_viewport();
@ -315,7 +306,7 @@ void GLGizmoScale3D::on_render()
if (shader != nullptr) { if (shader != nullptr) {
shader->start_using(); shader->start_using();
const Camera& camera = wxGetApp().plater()->get_camera(); const Camera& camera = wxGetApp().plater()->get_camera();
shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix); shader->set_uniform("view_model_matrix", camera.get_view_matrix() * m_grabbers_transform);
shader->set_uniform("projection_matrix", camera.get_projection_matrix()); shader->set_uniform("projection_matrix", camera.get_projection_matrix());
#if ENABLE_GL_CORE_PROFILE #if ENABLE_GL_CORE_PROFILE
const std::array<int, 4>& viewport = camera.get_viewport(); const std::array<int, 4>& viewport = camera.get_viewport();
@ -332,8 +323,7 @@ void GLGizmoScale3D::on_render()
if (shader != nullptr) { if (shader != nullptr) {
shader->start_using(); shader->start_using();
shader->set_uniform("emission_factor", 0.1f); shader->set_uniform("emission_factor", 0.1f);
m_grabbers[0].render(true, grabber_mean_size); render_grabbers(0, 1, grabber_mean_size, true);
m_grabbers[1].render(true, grabber_mean_size);
shader->stop_using(); shader->stop_using();
} }
} }
@ -347,7 +337,7 @@ void GLGizmoScale3D::on_render()
if (shader != nullptr) { if (shader != nullptr) {
shader->start_using(); shader->start_using();
const Camera& camera = wxGetApp().plater()->get_camera(); const Camera& camera = wxGetApp().plater()->get_camera();
shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix); shader->set_uniform("view_model_matrix", camera.get_view_matrix() * m_grabbers_transform);
shader->set_uniform("projection_matrix", camera.get_projection_matrix()); shader->set_uniform("projection_matrix", camera.get_projection_matrix());
#if ENABLE_GL_CORE_PROFILE #if ENABLE_GL_CORE_PROFILE
const std::array<int, 4>& viewport = camera.get_viewport(); const std::array<int, 4>& viewport = camera.get_viewport();
@ -364,8 +354,7 @@ void GLGizmoScale3D::on_render()
if (shader != nullptr) { if (shader != nullptr) {
shader->start_using(); shader->start_using();
shader->set_uniform("emission_factor", 0.1f); shader->set_uniform("emission_factor", 0.1f);
m_grabbers[2].render(true, grabber_mean_size); render_grabbers(2, 3, grabber_mean_size, true);
m_grabbers[3].render(true, grabber_mean_size);
shader->stop_using(); shader->stop_using();
} }
} }
@ -379,7 +368,7 @@ void GLGizmoScale3D::on_render()
if (shader != nullptr) { if (shader != nullptr) {
shader->start_using(); shader->start_using();
const Camera& camera = wxGetApp().plater()->get_camera(); const Camera& camera = wxGetApp().plater()->get_camera();
shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix); shader->set_uniform("view_model_matrix", camera.get_view_matrix() * m_grabbers_transform);
shader->set_uniform("projection_matrix", camera.get_projection_matrix()); shader->set_uniform("projection_matrix", camera.get_projection_matrix());
#if ENABLE_GL_CORE_PROFILE #if ENABLE_GL_CORE_PROFILE
const std::array<int, 4>& viewport = camera.get_viewport(); const std::array<int, 4>& viewport = camera.get_viewport();
@ -396,8 +385,7 @@ void GLGizmoScale3D::on_render()
if (shader != nullptr) { if (shader != nullptr) {
shader->start_using(); shader->start_using();
shader->set_uniform("emission_factor", 0.1f); shader->set_uniform("emission_factor", 0.1f);
m_grabbers[4].render(true, grabber_mean_size); render_grabbers(4, 5, grabber_mean_size, true);
m_grabbers[5].render(true, grabber_mean_size);
shader->stop_using(); shader->stop_using();
} }
} }
@ -411,7 +399,7 @@ void GLGizmoScale3D::on_render()
if (shader != nullptr) { if (shader != nullptr) {
shader->start_using(); shader->start_using();
const Camera& camera = wxGetApp().plater()->get_camera(); const Camera& camera = wxGetApp().plater()->get_camera();
shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix); shader->set_uniform("view_model_matrix", camera.get_view_matrix() * m_grabbers_transform);
shader->set_uniform("projection_matrix", camera.get_projection_matrix()); shader->set_uniform("projection_matrix", camera.get_projection_matrix());
#if ENABLE_GL_CORE_PROFILE #if ENABLE_GL_CORE_PROFILE
const std::array<int, 4>& viewport = camera.get_viewport(); const std::array<int, 4>& viewport = camera.get_viewport();
@ -431,9 +419,7 @@ void GLGizmoScale3D::on_render()
if (shader != nullptr) { if (shader != nullptr) {
shader->start_using(); shader->start_using();
shader->set_uniform("emission_factor", 0.1f); shader->set_uniform("emission_factor", 0.1f);
for (int i = 6; i < 10; ++i) { render_grabbers(6, 9, grabber_mean_size, true);
m_grabbers[i].render(true, grabber_mean_size);
}
shader->stop_using(); shader->stop_using();
} }
} }
@ -757,7 +743,7 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data)
double ratio = calc_ratio(data); double ratio = calc_ratio(data);
if (ratio > 0.0) { if (ratio > 0.0) {
Vec3d curr_scale = m_scale; Vec3d curr_scale = m_scale;
Vec3d starting_scale = m_starting.scale; const Vec3d starting_scale = m_starting.scale;
const Selection& selection = m_parent.get_selection(); const Selection& selection = m_parent.get_selection();
const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type();
@ -770,13 +756,6 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data)
if (m_hover_id == 2 * axis) if (m_hover_id == 2 * axis)
local_offset *= -1.0; local_offset *= -1.0;
Vec3d center_offset = m_starting.instance_center - m_starting.center; // world coordinates (== Vec3d::Zero() for single volume selection)
if (selection.is_single_full_instance() && coordinates_type == ECoordinatesType::Local)
// from world coordinates to instance coordinates
center_offset = selection.get_first_volume()->get_instance_transformation().get_rotation_matrix().inverse() * center_offset;
local_offset += (ratio - 1.0) * center_offset(axis);
switch (axis) switch (axis)
{ {
case X: { m_offset = local_offset * Vec3d::UnitX(); break; } case X: { m_offset = local_offset * Vec3d::UnitX(); break; }
@ -785,10 +764,6 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data)
default: { m_offset = Vec3d::Zero(); break; } default: { m_offset = Vec3d::Zero(); break; }
} }
if (selection.is_single_full_instance() && coordinates_type == ECoordinatesType::Local)
// from instance coordinates to world coordinates
m_offset = selection.get_first_volume()->get_instance_transformation().get_rotation_matrix() * m_offset;
if (selection.is_single_volume_or_modifier()) { if (selection.is_single_volume_or_modifier()) {
if (coordinates_type == ECoordinatesType::Instance) if (coordinates_type == ECoordinatesType::Instance)
m_offset = selection.get_first_volume()->get_instance_transformation().get_scaling_factor_matrix().inverse() * m_offset; m_offset = selection.get_first_volume()->get_instance_transformation().get_scaling_factor_matrix().inverse() * m_offset;
@ -849,18 +824,6 @@ void GLGizmoScale3D::do_scale_uniform(const UpdateData & data)
if (m_hover_id == 6 || m_hover_id == 7) if (m_hover_id == 6 || m_hover_id == 7)
m_offset.y() *= -1.0; m_offset.y() *= -1.0;
Vec3d center_offset = m_starting.instance_center - m_starting.center; // world coordinates (== Vec3d::Zero() for single volume selection)
if (selection.is_single_full_instance() && coordinates_type == ECoordinatesType::Local)
// from world coordinates to instance coordinates
center_offset = selection.get_first_volume()->get_instance_transformation().get_rotation_matrix().inverse() * center_offset;
m_offset += (ratio - 1.0) * center_offset;
if (selection.is_single_full_instance() && coordinates_type == ECoordinatesType::Local)
// from instance coordinates to world coordinates
m_offset = selection.get_first_volume()->get_instance_transformation().get_rotation_matrix() * m_offset;
if (selection.is_single_volume_or_modifier()) { if (selection.is_single_volume_or_modifier()) {
if (coordinates_type == ECoordinatesType::Instance) if (coordinates_type == ECoordinatesType::Instance)
m_offset = selection.get_first_volume()->get_instance_transformation().get_scaling_factor_matrix().inverse() * m_offset; m_offset = selection.get_first_volume()->get_instance_transformation().get_scaling_factor_matrix().inverse() * m_offset;
@ -904,7 +867,7 @@ double GLGizmoScale3D::calc_ratio(const UpdateData& data) const
// use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form
// in our case plane normal and ray direction are the same (orthogonal view) // in our case plane normal and ray direction are the same (orthogonal view)
// when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal
const Vec3d inters = data.mouse_ray.a + (m_starting.drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; const Vec3d inters = data.mouse_ray.a + (m_starting.drag_position - data.mouse_ray.a).dot(mouse_dir) * mouse_dir;
// vector from the starting position to the found intersection // vector from the starting position to the found intersection
const Vec3d inters_vec = inters - m_starting.drag_position; const Vec3d inters_vec = inters - m_starting.drag_position;
@ -920,20 +883,5 @@ double GLGizmoScale3D::calc_ratio(const UpdateData& data) const
return ratio; return ratio;
} }
#if ENABLE_WORLD_COORDINATE
Transform3d GLGizmoScale3D::local_transform(const Selection& selection) const
{
Transform3d ret = Geometry::translation_transform(m_center);
if (!wxGetApp().obj_manipul()->is_world_coordinates()) {
const GLVolume& v = *selection.get_first_volume();
Transform3d orient_matrix = v.get_instance_transformation().get_rotation_matrix();
if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates())
orient_matrix = orient_matrix * v.get_volume_transformation().get_rotation_matrix();
ret = ret * orient_matrix;
}
return ret;
}
#endif // ENABLE_WORLD_COORDINATE
} // namespace GUI } // namespace GUI
} // namespace Slic3r } // namespace Slic3r

View File

@ -102,9 +102,6 @@ private:
void do_scale_uniform(const UpdateData& data); void do_scale_uniform(const UpdateData& data);
double calc_ratio(const UpdateData& data) const; double calc_ratio(const UpdateData& data) const;
#if ENABLE_WORLD_COORDINATE
Transform3d local_transform(const Selection& selection) const;
#endif // ENABLE_WORLD_COORDINATE
}; };

View File

@ -740,6 +740,7 @@ void GLGizmosManager::render_arrow(const GLCanvas3D& parent, EType highlighted_t
const float icons_size_x = 2.0f * m_layout.scaled_icons_size() * inv_cnv_w; const float icons_size_x = 2.0f * m_layout.scaled_icons_size() * inv_cnv_w;
const float icons_size_y = 2.0f * m_layout.scaled_icons_size() * inv_cnv_h; const float icons_size_y = 2.0f * m_layout.scaled_icons_size() * inv_cnv_h;
const float stride_y = 2.0f * m_layout.scaled_stride_y() * inv_cnv_h; const float stride_y = 2.0f * m_layout.scaled_stride_y() * inv_cnv_h;
top_y -= stride_y;
for (size_t idx : selectable_idxs) { for (size_t idx : selectable_idxs) {
if (idx == highlighted_type) { if (idx == highlighted_type) {

View File

@ -120,7 +120,9 @@ enum class NotificationType
// Short meesage to fill space between start and finish of export // Short meesage to fill space between start and finish of export
ExportOngoing, ExportOngoing,
// Progressbar of download from prusaslicer:// url // Progressbar of download from prusaslicer:// url
URLDownload URLDownload,
// MacOS specific - PS comes forward even when downloader is not allowed
URLNotRegistered,
}; };
class NotificationManager class NotificationManager
@ -916,6 +918,16 @@ private:
{NotificationType::UndoDesktopIntegrationFail, NotificationLevel::WarningNotificationLevel, 10, {NotificationType::UndoDesktopIntegrationFail, NotificationLevel::WarningNotificationLevel, 10,
_u8L("Undo desktop integration failed.") }, _u8L("Undo desktop integration failed.") },
{NotificationType::ExportOngoing, NotificationLevel::RegularNotificationLevel, 0, _u8L("Exporting.") }, {NotificationType::ExportOngoing, NotificationLevel::RegularNotificationLevel, 0, _u8L("Exporting.") },
{NotificationType::URLNotRegistered
, NotificationLevel::RegularNotificationLevel
, 10
, _u8L("PrusaSlicer recieved a download request from Printables.com, but it's not allowed. You can allow it")
, _u8L("here.")
, [](wxEvtHandler* evnthndlr) {
wxGetApp().open_preferences("downloader_url_registered", "Other");
return true;
} },
//{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New version is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr) { //{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New version is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr) {
// wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }}, // wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }},
//{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New vesion of PrusaSlicer is available.", _u8L("Download page.") }, //{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New vesion of PrusaSlicer is available.", _u8L("Download page.") },

View File

@ -1038,25 +1038,50 @@ void ogStaticText::SetText(const wxString& value, bool wrap/* = true*/)
void ogStaticText::SetPathEnd(const std::string& link) void ogStaticText::SetPathEnd(const std::string& link)
{ {
#ifndef __linux__
Bind(wxEVT_ENTER_WINDOW, [this, link](wxMouseEvent& event) {
SetToolTip(OptionsGroup::get_url(get_app_config()->get("suppress_hyperlinks") != "1" ? link : std::string()));
FocusText(true);
event.Skip();
});
Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& event) { FocusText(false); event.Skip(); });
Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent& event) { Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent& event) {
if (HasCapture()) if (HasCapture())
return; return;
this->CaptureMouse(); this->CaptureMouse();
event.Skip(); event.Skip();
} ); });
Bind(wxEVT_LEFT_UP, [link, this](wxMouseEvent& event) { Bind(wxEVT_LEFT_UP, [link, this](wxMouseEvent& event) {
if (!HasCapture()) if (!HasCapture())
return; return;
ReleaseMouse(); ReleaseMouse();
OptionsGroup::launch_browser(link); OptionsGroup::launch_browser(link);
event.Skip(); event.Skip();
} ); });
Bind(wxEVT_ENTER_WINDOW, [this, link](wxMouseEvent& event) {
#else
// Workaround: On Linux wxStaticText doesn't receive wxEVT_ENTER(LEAVE)_WINDOW events,
// so implement this behaviour trough wxEVT_MOTION events for this control and it's parent
Bind(wxEVT_MOTION, [link, this](wxMouseEvent& event) {
SetToolTip(OptionsGroup::get_url(!get_app_config()->get_bool("suppress_hyperlinks") ? link : std::string())); SetToolTip(OptionsGroup::get_url(!get_app_config()->get_bool("suppress_hyperlinks") ? link : std::string()));
FocusText(true); FocusText(true);
event.Skip(); event.Skip();
}); });
Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& event) { FocusText(false); event.Skip(); }); GetParent()->Bind(wxEVT_MOTION, [this](wxMouseEvent& event) {
FocusText(false);
event.Skip();
});
// On Linux a mouse capturing causes a totally application freeze
Bind(wxEVT_LEFT_UP, [link, this](wxMouseEvent& event) {
OptionsGroup::launch_browser(link);
event.Skip();
});
#endif
} }
void ogStaticText::FocusText(bool focus) void ogStaticText::FocusText(bool focus)
@ -1066,6 +1091,9 @@ void ogStaticText::FocusText(bool focus)
SetFont(focus ? Slic3r::GUI::wxGetApp().link_font() : SetFont(focus ? Slic3r::GUI::wxGetApp().link_font() :
Slic3r::GUI::wxGetApp().normal_font()); Slic3r::GUI::wxGetApp().normal_font());
#ifdef __linux__
this->GetContainingSizer()->Layout();
#endif
Refresh(); Refresh();
} }

View File

@ -10,7 +10,7 @@
#include "ButtonsDescription.hpp" #include "ButtonsDescription.hpp"
#include "OG_CustomCtrl.hpp" #include "OG_CustomCtrl.hpp"
#include "GLCanvas3D.hpp" #include "GLCanvas3D.hpp"
#include "ConfigWizard_private.hpp" #include "ConfigWizard.hpp"
#include <boost/dll/runtime_symbol_info.hpp> #include <boost/dll/runtime_symbol_info.hpp>
@ -712,7 +712,7 @@ void PreferencesDialog::accept(wxEvent&)
return; return;
#ifdef __linux__ #ifdef __linux__
if( downloader->get_perform_registration_linux()) if( downloader->get_perform_registration_linux())
DesktopIntegrationDialog::perform_desktop_integration(true); DesktopIntegrationDialog::perform_downloader_desktop_integration();
#endif // __linux__ #endif // __linux__
} }

View File

@ -59,7 +59,7 @@ class PreferencesDialog : public DPIDialog
wxColourPickerCtrl* m_mode_advanced { nullptr }; wxColourPickerCtrl* m_mode_advanced { nullptr };
wxColourPickerCtrl* m_mode_expert { nullptr }; wxColourPickerCtrl* m_mode_expert { nullptr };
DownloaderUtils::Worker* downloader{ nullptr }; DownloaderUtils::Worker* downloader { nullptr };
wxBookCtrlBase* tabs {nullptr}; wxBookCtrlBase* tabs {nullptr};

View File

@ -836,108 +836,57 @@ const std::pair<BoundingBoxf3, Transform3d>& Selection::get_bounding_box_in_curr
std::pair<BoundingBoxf3, Transform3d> Selection::get_bounding_box_in_reference_system(ECoordinatesType type) const std::pair<BoundingBoxf3, Transform3d> Selection::get_bounding_box_in_reference_system(ECoordinatesType type) const
{ {
BoundingBoxf3 original_box; //
// trafo to current reference system
//
Transform3d trafo; Transform3d trafo;
//
// calculate box aligned to current reference system
//
switch (type) switch (type)
{ {
case ECoordinatesType::World: case ECoordinatesType::World: { trafo = Transform3d::Identity(); break; }
{ case ECoordinatesType::Instance: { trafo = get_first_volume()->get_instance_transformation().get_matrix(); break; }
original_box = get_bounding_box(); case ECoordinatesType::Local: { trafo = get_first_volume()->world_matrix(); break; }
trafo = Transform3d::Identity();
break;
}
case ECoordinatesType::Instance: {
for (unsigned int id : m_list) {
const GLVolume& v = *get_volume(id);
original_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix()));
}
trafo = get_first_volume()->get_instance_transformation().get_matrix();
break;
}
case ECoordinatesType::Local: {
assert(is_single_volume_or_modifier() || is_single_volume_instance());
const GLVolume& v = *get_first_volume();
original_box = v.bounding_box();
trafo = v.world_matrix();
break;
}
} }
// //
// calculate box size in world coordinates // trafo basis in world coordinates
// //
auto point_to_Vec4d = [](const Vec3d& p) { return Vec4d(p.x(), p.y(), p.z(), 1.0); }; Geometry::Transformation t(trafo);
auto Vec4d_to_Vec3d = [](const Vec4d& v) { return Vec3d(v.x(), v.y(), v.z()); }; t.reset_scaling_factor();
const Transform3d basis_trafo = t.get_matrix_no_offset();
auto apply_transform = [](const std::vector<Vec4d>& original, const Transform3d& trafo, bool normalize) { std::vector<Vec3d> axes = { Vec3d::UnitX(), Vec3d::UnitY(), Vec3d::UnitZ() };
std::vector<Vec4d> transformed(original.size()); for (size_t i = 0; i < axes.size(); ++i) {
for (size_t i = 0; i < original.size(); ++i) { axes[i] = basis_trafo * axes[i];
transformed[i] = trafo * original[i];
if (normalize)
transformed[i].normalize();
}
return transformed;
};
auto calc_box_size = [point_to_Vec4d, Vec4d_to_Vec3d, apply_transform](const BoundingBoxf3& box, const Transform3d& trafo) {
Geometry::Transformation transformation(trafo);
// box aligned to current reference system
std::vector<Vec4d> homo_vertices = {
point_to_Vec4d({ box.min.x(), box.min.y(), box.min.z() }),
point_to_Vec4d({ box.max.x(), box.min.y(), box.min.z() }),
point_to_Vec4d({ box.max.x(), box.max.y(), box.min.z() }),
point_to_Vec4d({ box.min.x(), box.max.y(), box.min.z() }),
point_to_Vec4d({ box.min.x(), box.min.y(), box.max.z() }),
point_to_Vec4d({ box.max.x(), box.min.y(), box.max.z() }),
point_to_Vec4d({ box.max.x(), box.max.y(), box.max.z() }),
point_to_Vec4d({ box.min.x(), box.max.y(), box.max.z() })
};
// box vertices in world coordinates
std::vector<Vec4d> transformed_homo_vertices = apply_transform(homo_vertices, trafo, false);
// project back to current reference system
const std::vector<Vec4d> homo_axes = { Vec4d::UnitX(), Vec4d::UnitY(), Vec4d::UnitZ() };
std::vector<Vec4d> transformed_homo_axes = apply_transform(homo_axes, Geometry::Transformation(trafo).get_matrix_no_scaling_factor(), true);
std::vector<Vec3d> transformed_axes(transformed_homo_axes.size());
for (size_t i = 0; i < transformed_homo_axes.size(); ++i) {
transformed_axes[i] = Vec4d_to_Vec3d(transformed_homo_axes[i]);
} }
//
// calculate bounding box aligned to trafo basis
//
Vec3d min = { DBL_MAX, DBL_MAX, DBL_MAX }; Vec3d min = { DBL_MAX, DBL_MAX, DBL_MAX };
Vec3d max = { -DBL_MAX, -DBL_MAX, -DBL_MAX }; Vec3d max = { -DBL_MAX, -DBL_MAX, -DBL_MAX };
for (unsigned int id : m_list) {
for (const Vec4d& v_homo : transformed_homo_vertices) { const GLVolume& vol = *get_volume(id);
const Vec3d v = Vec4d_to_Vec3d(v_homo); const Transform3d vol_world_rafo = vol.world_matrix();
const TriangleMesh* mesh = vol.convex_hull();
if (mesh == nullptr)
mesh = &m_model->objects[vol.object_idx()]->volumes[vol.volume_idx()]->mesh();
assert(mesh != nullptr);
for (const stl_vertex& v : mesh->its.vertices) {
const Vec3d world_v = vol_world_rafo * v.cast<double>();
for (int i = 0; i < 3; ++i) { for (int i = 0; i < 3; ++i) {
const double dot_i = v.dot(transformed_axes[i]); const double i_comp = world_v.dot(axes[i]);
min(i) = std::min(min(i), dot_i); min(i) = std::min(min(i), i_comp);
max(i) = std::max(max(i), dot_i); max(i) = std::max(max(i), i_comp);
} }
} }
}
// return size const Vec3d box_size = max - min;
const Vec3d size = max - min;
return size;
};
const Vec3d box_size = calc_box_size(original_box, trafo);
const std::vector<Vec4d> box_center = { point_to_Vec4d(original_box.center()) };
std::vector<Vec4d> transformed_box_center = apply_transform(box_center, trafo, false);
//
// return box centered at 0, 0, 0
//
const Vec3d half_box_size = 0.5 * box_size; const Vec3d half_box_size = 0.5 * box_size;
BoundingBoxf3 out_box(-half_box_size, half_box_size); BoundingBoxf3 out_box(-half_box_size, half_box_size);
Geometry::Transformation out_trafo(trafo); Geometry::Transformation out_trafo(trafo);
out_trafo.set_offset(Vec4d_to_Vec3d(transformed_box_center[0])); const Vec3d center = 0.5 * (min + max);
out_trafo.set_offset(basis_trafo * center);
return { out_box, out_trafo.get_matrix_no_scaling_factor() }; return { out_box, out_trafo.get_matrix_no_scaling_factor() };
} }
#endif // ENABLE_WORLD_COORDINATE #endif // ENABLE_WORLD_COORDINATE
@ -1049,7 +998,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_
assert(transformation_type.relative() || (transformation_type.absolute() && transformation_type.local())); assert(transformation_type.relative() || (transformation_type.absolute() && transformation_type.local()));
const Transform3d rotation_matrix = Geometry::rotation_transform(rotation); Transform3d rotation_matrix = Geometry::rotation_transform(rotation);
for (unsigned int i : m_list) { for (unsigned int i : m_list) {
GLVolume& v = *(*m_volumes)[i]; GLVolume& v = *(*m_volumes)[i];
@ -1074,31 +1023,50 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_
transform_volume_relative(v, volume_data, transformation_type, rotation_matrix, m_cache.dragging_center); transform_volume_relative(v, volume_data, transformation_type, rotation_matrix, m_cache.dragging_center);
} }
else { else {
if (transformation_type.local() && transformation_type.absolute()) { if (transformation_type.instance()) {
const Geometry::Transformation& vol_trafo = volume_data.get_volume_transform(); const Geometry::Transformation& vol_trafo = volume_data.get_volume_transform();
Matrix3d vol_rotation, vol_scale; const Geometry::Transformation world_trafo = inst_trafo * vol_trafo;
vol_trafo.get_matrix().computeRotationScaling(&vol_rotation, &vol_scale); // ensure proper sign of rotation for mirrored objects
const Transform3d trafo = vol_trafo.get_rotation_matrix() * rotation_matrix; if (world_trafo.is_left_handed() && !rotation.normalized().isApprox(Vec3d::UnitX()))
v.set_volume_transformation(vol_trafo.get_offset_matrix() * trafo * Transform3d(vol_scale)); rotation_matrix = rotation_matrix.inverse();
// ensure that the volume rotates as a rigid body
const Geometry::TransformationSVD world_svd(world_trafo);
if (world_svd.anisotropic_scale) {
const Transform3d vol_scale_matrix = vol_trafo.get_scaling_factor_matrix();
rotation_matrix = vol_scale_matrix.inverse() * rotation_matrix * vol_scale_matrix;
}
const Transform3d vol_rotation_matrix = vol_trafo.get_rotation_matrix();
rotation_matrix = vol_rotation_matrix.inverse() * rotation_matrix * vol_rotation_matrix;
v.set_volume_transformation(vol_trafo.get_matrix() * rotation_matrix);
}
else {
if (transformation_type.local()) {
const Geometry::Transformation& vol_trafo = volume_data.get_volume_transform();
const Geometry::Transformation world_trafo = inst_trafo * vol_trafo;
// ensure proper sign of rotation for mirrored objects
if (world_trafo.is_left_handed() && !rotation.normalized().isApprox(Vec3d::UnitX()))
rotation_matrix = rotation_matrix.inverse();
// ensure that the volume rotates as a rigid body
const Geometry::TransformationSVD svd(world_trafo);
if (svd.anisotropic_scale) {
const Transform3d vol_scale_matrix = vol_trafo.get_scaling_factor_matrix();
rotation_matrix = vol_scale_matrix.inverse() * rotation_matrix * vol_scale_matrix;
}
} }
else
transform_volume_relative(v, volume_data, transformation_type, rotation_matrix, m_cache.dragging_center); transform_volume_relative(v, volume_data, transformation_type, rotation_matrix, m_cache.dragging_center);
} }
} }
} }
}
#if !DISABLE_INSTANCES_SYNCH #if !DISABLE_INSTANCES_SYNCH
if (m_mode == Instance) { if (m_mode == Instance) {
int rot_axis_max = 0; int rot_axis_max = 0;
rotation.cwiseAbs().maxCoeff(&rot_axis_max); rotation.cwiseAbs().maxCoeff(&rot_axis_max);
SyncRotationType synch; synchronize_unselected_instances((transformation_type.world() && rot_axis_max == 2) ? SyncRotationType::NONE : SyncRotationType::GENERAL);
if (transformation_type.world() && rot_axis_max == 2)
synch = SyncRotationType::NONE;
else if (transformation_type.instance())
synch = SyncRotationType::FULL;
else
synch = SyncRotationType::GENERAL;
synchronize_unselected_instances(synch);
} }
else if (m_mode == Volume) else if (m_mode == Volume)
synchronize_unselected_volumes(); synchronize_unselected_volumes();
@ -1466,17 +1434,12 @@ void Selection::scale_and_translate(const Vec3d& scale, const Vec3d& translation
if (!m_valid) if (!m_valid)
return; return;
for (unsigned int i : m_list) {
GLVolume& v = *(*m_volumes)[i];
const VolumeCache& volume_data = m_cache.volumes_data[i];
const Geometry::Transformation& inst_trafo = volume_data.get_instance_transform();
Vec3d relative_scale = scale; Vec3d relative_scale = scale;
if (transformation_type.absolute()) { if (transformation_type.absolute()) {
// converts to relative scale
if (m_mode == Instance) { if (m_mode == Instance) {
if (is_single_full_instance()) { if (is_single_full_instance()) {
BoundingBoxf3 current_box = m_box.get_bounding_box(); BoundingBoxf3 current_box = get_bounding_box_in_current_reference_system().first;
BoundingBoxf3 original_box; BoundingBoxf3 original_box;
if (transformation_type.world()) if (transformation_type.world())
original_box = get_full_unscaled_instance_bounding_box(); original_box = get_full_unscaled_instance_bounding_box();
@ -1484,12 +1447,15 @@ void Selection::scale_and_translate(const Vec3d& scale, const Vec3d& translation
original_box = get_full_unscaled_instance_local_bounding_box(); original_box = get_full_unscaled_instance_local_bounding_box();
relative_scale = original_box.size().cwiseProduct(scale).cwiseQuotient(current_box.size()); relative_scale = original_box.size().cwiseProduct(scale).cwiseQuotient(current_box.size());
}
}
transformation_type.set_relative(); transformation_type.set_relative();
} }
}
else { for (unsigned int i : m_list) {
} GLVolume& v = *(*m_volumes)[i];
} const VolumeCache& volume_data = m_cache.volumes_data[i];
const Geometry::Transformation& inst_trafo = volume_data.get_instance_transform();
if (m_mode == Instance) { if (m_mode == Instance) {
if (transformation_type.instance()) { if (transformation_type.instance()) {
@ -1529,7 +1495,9 @@ void Selection::scale_and_translate(const Vec3d& scale, const Vec3d& translation
#if !DISABLE_INSTANCES_SYNCH #if !DISABLE_INSTANCES_SYNCH
if (m_mode == Instance) if (m_mode == Instance)
synchronize_unselected_instances(SyncRotationType::NONE); // even if there is no rotation, we pass SyncRotationType::GENERAL to force
// synchronize_unselected_instances() to apply the scale to the other instances
synchronize_unselected_instances(SyncRotationType::GENERAL);
else if (m_mode == Volume) else if (m_mode == Volume)
synchronize_unselected_volumes(); synchronize_unselected_volumes();
#endif // !DISABLE_INSTANCES_SYNCH #endif // !DISABLE_INSTANCES_SYNCH
@ -2196,7 +2164,6 @@ void Selection::update_type()
unsigned int volumes_count = (unsigned int)model_object->volumes.size(); unsigned int volumes_count = (unsigned int)model_object->volumes.size();
unsigned int instances_count = (unsigned int)model_object->instances.size(); unsigned int instances_count = (unsigned int)model_object->instances.size();
if (volumes_count * instances_count == 1) { if (volumes_count * instances_count == 1) {
const ModelVolume* model_volume = model_object->volumes[first->volume_idx()];
m_type = SingleFullObject; m_type = SingleFullObject;
// ensures the correct mode is selected // ensures the correct mode is selected
m_mode = Instance; m_mode = Instance;
@ -2834,7 +2801,7 @@ void Selection::render_debug_window() const
} }
static int current_method_idx = 0; static int current_method_idx = 0;
ImGui::Combo("Decomposition method", &current_method_idx, "computeRotationScaling\0computeScalingRotation\0"); ImGui::Combo("Decomposition method", &current_method_idx, "computeRotationScaling\0computeScalingRotation\0SVD\0");
const GLVolume& v = *get_volume(current_vol_idx); const GLVolume& v = *get_volume(current_vol_idx);
@ -2854,12 +2821,13 @@ void Selection::render_debug_window() const
ImGui::EndGroup(); ImGui::EndGroup();
}; };
auto add_matrices_set = [add_matrix](const std::string& name, const Transform3d& m, size_t method) { auto add_matrices_set = [&imgui, add_matrix](const std::string& name, const Transform3d& m, size_t method) {
static unsigned int counter = 0; static unsigned int counter = 0;
++counter; ++counter;
if (ImGui::CollapsingHeader(name.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) { if (ImGui::CollapsingHeader(name.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) {
add_matrix("Full", m, 4); add_matrix("Full", m, 4);
if (method == 0 || method == 1) {
Matrix3d rotation; Matrix3d rotation;
Matrix3d scale; Matrix3d scale;
if (method == 0) if (method == 0)
@ -2872,6 +2840,38 @@ void Selection::render_debug_window() const
ImGui::SameLine(); ImGui::SameLine();
add_matrix("Scale component", Transform3d(scale), 3); add_matrix("Scale component", Transform3d(scale), 3);
} }
else {
const Geometry::TransformationSVD svd(m);
ImGui::SameLine();
add_matrix("U", Transform3d(svd.u), 3);
ImGui::SameLine();
add_matrix("S", Transform3d(svd.s), 3);
ImGui::SameLine();
add_matrix("V", Transform3d(svd.v), 3);
ImGui::Dummy(ImVec2(0.0f, 0.0f));
float spacing = 0.0f;
if (svd.rotation) {
ImGui::SameLine(0.0f, spacing);
imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, svd.rotation_90_degrees ? "Rotation 90 degs" : "Rotation");
spacing = 10.0f;
}
if (svd.scale) {
ImGui::SameLine(0.0f, spacing);
imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, svd.anisotropic_scale ? "Anisotropic scale" : "Isotropic scale");
spacing = 10.0f;
}
if (svd.mirror) {
ImGui::SameLine(0.0f, spacing);
imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, "Mirror");
spacing = 10.0f;
}
if (svd.skew) {
ImGui::SameLine(0.0f, spacing);
imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, "Skew");
}
}
}
}; };
add_matrices_set("World", v.world_matrix(), current_method_idx); add_matrices_set("World", v.world_matrix(), current_method_idx);
@ -2968,10 +2968,10 @@ static void verify_instances_rotation_synchronized(const Model &model, const GLV
assert(idx_volume_first != -1); // object without instances? assert(idx_volume_first != -1); // object without instances?
if (idx_volume_first == -1) if (idx_volume_first == -1)
continue; continue;
const Transform3d::ConstLinearPart &rotation0 = volumes[idx_volume_first]->get_instance_transformation().get_matrix().linear(); const Transform3d::ConstLinearPart& rotation0 = volumes[idx_volume_first]->get_instance_transformation().get_matrix().linear();
for (int i = idx_volume_first + 1; i < (int)volumes.size(); ++i) for (int i = idx_volume_first + 1; i < (int)volumes.size(); ++i)
if (volumes[i]->object_idx() == idx_object) { if (volumes[i]->object_idx() == idx_object) {
const Transform3d::ConstLinearPart &rotation = volumes[i]->get_instance_transformation().get_matrix().linear(); const Transform3d::ConstLinearPart& rotation = volumes[i]->get_instance_transformation().get_matrix().linear();
assert(is_rotation_xy_synchronized(rotation, rotation0)); assert(is_rotation_xy_synchronized(rotation, rotation0));
} }
} }
@ -2994,7 +2994,6 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_
const int object_idx = volume_i->object_idx(); const int object_idx = volume_i->object_idx();
const int instance_idx = volume_i->instance_idx(); const int instance_idx = volume_i->instance_idx();
const Transform3d& curr_inst_trafo_i = volume_i->get_instance_transformation().get_matrix(); const Transform3d& curr_inst_trafo_i = volume_i->get_instance_transformation().get_matrix();
const bool curr_inst_left_handed = is_left_handed(curr_inst_trafo_i);
const Transform3d& old_inst_trafo_i = m_cache.volumes_data[i].get_instance_transform().get_matrix(); const Transform3d& old_inst_trafo_i = m_cache.volumes_data[i].get_instance_transform().get_matrix();
bool mirrored = is_left_handed(curr_inst_trafo_i) != is_left_handed(old_inst_trafo_i); bool mirrored = is_left_handed(curr_inst_trafo_i) != is_left_handed(old_inst_trafo_i);
// bool mirrored = curr_inst_trafo_i.linear().determinant() * old_inst_trafo_i.linear().determinant() < 0; // bool mirrored = curr_inst_trafo_i.linear().determinant() * old_inst_trafo_i.linear().determinant() < 0;

View File

@ -499,10 +499,6 @@ public:
NONE = 0, NONE = 0,
// Synchronize after rotation by an axis not parallel with Z. // Synchronize after rotation by an axis not parallel with Z.
GENERAL = 1, GENERAL = 1,
#if ENABLE_WORLD_COORDINATE
// Fully synchronize rotation.
FULL = 2,
#endif // ENABLE_WORLD_COORDINATE
}; };
void synchronize_unselected_instances(SyncRotationType sync_rotation_type); void synchronize_unselected_instances(SyncRotationType sync_rotation_type);
void synchronize_unselected_volumes(); void synchronize_unselected_volumes();

View File

@ -1641,9 +1641,6 @@ void TabPrint::build()
optgroup->append_single_option_line("xy_size_compensation"); optgroup->append_single_option_line("xy_size_compensation");
optgroup->append_single_option_line("elefant_foot_compensation", "elephant-foot-compensation_114487"); optgroup->append_single_option_line("elefant_foot_compensation", "elephant-foot-compensation_114487");
optgroup = page->new_optgroup(L("Other"));
optgroup->append_single_option_line("clip_multipart_objects");
optgroup = page->new_optgroup(L("Arachne perimeter generator")); optgroup = page->new_optgroup(L("Arachne perimeter generator"));
optgroup->append_single_option_line("wall_transition_angle"); optgroup->append_single_option_line("wall_transition_angle");
optgroup->append_single_option_line("wall_transition_filter_deviation"); optgroup->append_single_option_line("wall_transition_filter_deviation");
@ -1732,9 +1729,7 @@ void TabPrint::update_description_lines()
if (m_post_process_explanation) { if (m_post_process_explanation) {
m_post_process_explanation->SetText( m_post_process_explanation->SetText(
_L("Post processing scripts shall modify G-code file in place.")); _L("Post processing scripts shall modify G-code file in place."));
#ifndef __linux__
m_post_process_explanation->SetPathEnd("post-processing-scripts_283913"); m_post_process_explanation->SetPathEnd("post-processing-scripts_283913");
#endif // __linux__
} }
// upadte G-code substitutions from the current configuration // upadte G-code substitutions from the current configuration
{ {

View File

@ -734,11 +734,14 @@ bool PrusaLink::get_storage(wxArrayString& output) const
const auto path = section.second.get_optional<std::string>("path"); const auto path = section.second.get_optional<std::string>("path");
const auto space = section.second.get_optional<std::string>("free_space"); const auto space = section.second.get_optional<std::string>("free_space");
const auto read_only = section.second.get_optional<bool>("read_only"); const auto read_only = section.second.get_optional<bool>("read_only");
const auto ro = section.second.get_optional<bool>("ro"); // In PrusaLink 0.7.0RC2 "read_only" value is stored under "ro".
const auto available = section.second.get_optional<bool>("available"); const auto available = section.second.get_optional<bool>("available");
if (path && (!available || *available)) { if (path && (!available || *available)) {
StorageInfo si; StorageInfo si;
si.name = boost::nowide::widen(*path); si.name = boost::nowide::widen(*path);
si.read_only = read_only ? *read_only : false; // If read_only is missing, assume it is NOT read only. // If read_only is missing, assume it is NOT read only.
// si.read_only = read_only ? *read_only : false; // version without "ro"
si.read_only = (read_only ? *read_only : (ro ? *ro : false));
si.free_space = space ? std::stoll(*space) : 1; // If free_space is missing, assume there is free space. si.free_space = space ? std::stoll(*space) : 1; // If free_space is missing, assume there is free space.
storage.emplace_back(std::move(si)); storage.emplace_back(std::move(si));
} }