From b5b42a570a9268f4237b0cc3b1814b5c1266bc3f Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 27 Apr 2022 17:18:28 +0200 Subject: [PATCH 01/11] Force utf8 encoding for wxString in all Unix-like platforms --- deps/wxWidgets/wxWidgets.cmake | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/deps/wxWidgets/wxWidgets.cmake b/deps/wxWidgets/wxWidgets.cmake index 1ca6735cc3..2939425682 100644 --- a/deps/wxWidgets/wxWidgets.cmake +++ b/deps/wxWidgets/wxWidgets.cmake @@ -9,6 +9,11 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux") set(_wx_toolkit "-DwxBUILD_TOOLKIT=gtk${_gtk_ver}") endif() +set(_unicode_utf8 OFF) +if (UNIX) # wxWidgets will not use char as the underlying type for wxString unless its forced to. + set (_unicode_utf8 ON) +endif() + prusaslicer_add_cmake_project(wxWidgets # GIT_REPOSITORY "https://github.com/prusa3d/wxWidgets" # GIT_TAG tm_cross_compile #${_wx_git_tag} @@ -23,6 +28,7 @@ prusaslicer_add_cmake_project(wxWidgets -DwxUSE_MEDIACTRL=OFF -DwxUSE_DETECT_SM=OFF -DwxUSE_UNICODE=ON + -DwxUSE_UNICODE_UTF8=${_unicode_utf8} -DwxUSE_OPENGL=ON -DwxUSE_LIBPNG=sys -DwxUSE_ZLIB=sys From 7f7d229178b5bdb9b485fc0daa2701cbd6cd2362 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 2 May 2022 08:50:27 +0200 Subject: [PATCH 02/11] Follow-up of 3ce2d3a700ef215b37faef273f54be5619b9d642 - Fixed export of used filament data to gcode for multimaterial prints --- src/libslic3r/GCode/GCodeProcessor.cpp | 52 +++++++++++++------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index f4cf12064d..92e0a698e3 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -3661,42 +3661,44 @@ void GCodeProcessor::post_process() return std::tuple(!ret.empty(), (extra_lines_count == 0) ? extra_lines_count : extra_lines_count - 1); }; - struct FilamentData - { - double mm{ 0.0 }; - double cm3{ 0.0 }; - double g{ 0.0 }; - double cost{ 0.0 }; - }; + std::vector filament_mm(m_result.extruders_count, 0.0); + std::vector filament_cm3(m_result.extruders_count, 0.0); + std::vector filament_g(m_result.extruders_count, 0.0); + std::vector filament_cost(m_result.extruders_count, 0.0); + + double filament_total_g = 0.0; + double filament_total_cost = 0.0; - FilamentData filament_data; - for (const auto& [role, used] : m_result.print_statistics.used_filaments_per_role) { - filament_data.mm += used.first; - filament_data.g += used.second; - } for (const auto& [id, volume] : m_result.print_statistics.volumes_per_extruder) { - filament_data.cm3 += volume; - filament_data.cost += volume * double(m_result.filament_densities[id]) * double(m_result.filament_cost[id]) * 0.000001; + filament_mm[id] = volume / (static_cast(M_PI) * sqr(0.5 * m_result.filament_diameters[id])); + filament_cm3[id] = volume * 0.001; + filament_g[id] = filament_cm3[id] * double(m_result.filament_densities[id]); + filament_cost[id] = filament_g[id] * double(m_result.filament_cost[id]) * 0.001; + filament_total_g += filament_g[id]; + filament_total_cost += filament_cost[id]; } - auto process_used_filament = [&filament_data](std::string& gcode_line) { - auto process_tag = [](std::string& gcode_line, const std::string& tag, double value) { + auto process_used_filament = [&](std::string& gcode_line) { + auto process_tag = [](std::string& gcode_line, const std::string& tag, const std::vector& values) { if (boost::algorithm::istarts_with(gcode_line, tag)) { - char buf[128]; - sprintf(buf, "%s %.2lf\n", tag.c_str(), value); - gcode_line = buf; + gcode_line = tag; + char buf[1024]; + for (size_t i = 0; i < values.size(); ++i) { + sprintf(buf, i == values.size() - 1 ? " %.2lf\n" : " %.2lf,", values[i]); + gcode_line += buf; + } return true; } return false; }; bool ret = false; - ret |= process_tag(gcode_line, "; filament used [mm] =", filament_data.mm * 1000.0); - ret |= process_tag(gcode_line, "; filament used [g] =", filament_data.g); - ret |= process_tag(gcode_line, "; total filament used [g] =", filament_data.g); - ret |= process_tag(gcode_line, "; filament used [cm3] =", filament_data.cm3 / 1000.0); - ret |= process_tag(gcode_line, "; filament cost =", filament_data.cost); - ret |= process_tag(gcode_line, "; total filament cost =", filament_data.cost); + ret |= process_tag(gcode_line, "; filament used [mm] =", filament_mm); + ret |= process_tag(gcode_line, "; filament used [g] =", filament_g); + ret |= process_tag(gcode_line, "; total filament used [g] =", { filament_total_g }); + ret |= process_tag(gcode_line, "; filament used [cm3] =", filament_cm3); + ret |= process_tag(gcode_line, "; filament cost =", filament_cost); + ret |= process_tag(gcode_line, "; total filament cost =", { filament_total_cost }); return ret; }; From 3687bc28d551f612c82c5a8dad9257d4f3a11f27 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 28 Apr 2022 15:59:13 +0200 Subject: [PATCH 03/11] Some reduction of Perl dependencies on ClipperLib, ported some ClipperLib polyline clipping tests to C++. --- lib/Slic3r/ExPolygon.pm | 5 - lib/Slic3r/Geometry.pm | 37 -------- lib/Slic3r/Geometry/Clipper.pm | 4 +- lib/Slic3r/Print/Object.pm | 2 +- t/geometry.t | 40 +------- t/polyclip.t | 121 ------------------------- tests/libslic3r/test_clipper_utils.cpp | 78 ++++++++++++++++ xs/xsp/Clipper.xsp | 20 ---- xs/xsp/Surface.xsp | 10 -- 9 files changed, 82 insertions(+), 235 deletions(-) delete mode 100644 t/polyclip.t diff --git a/lib/Slic3r/ExPolygon.pm b/lib/Slic3r/ExPolygon.pm index 6adb650c21..6337cb9a1a 100644 --- a/lib/Slic3r/ExPolygon.pm +++ b/lib/Slic3r/ExPolygon.pm @@ -12,11 +12,6 @@ sub offset { return Slic3r::Geometry::Clipper::offset(\@$self, @_); } -sub offset_ex { - my $self = shift; - return Slic3r::Geometry::Clipper::offset_ex(\@$self, @_); -} - sub noncollapsing_offset_ex { my $self = shift; my ($distance, @params) = @_; diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm index 286a73e2de..ca262fc766 100644 --- a/lib/Slic3r/Geometry.pm +++ b/lib/Slic3r/Geometry.pm @@ -14,10 +14,8 @@ our @EXPORT_OK = qw( dot line_intersection normalize - point_in_segment polyline_lines polygon_is_convex - polygon_segment_having_point scale unscale scaled_epsilon @@ -45,30 +43,6 @@ sub scaled_epsilon () { epsilon / &Slic3r::SCALING_FACTOR } sub scale ($) { $_[0] / &Slic3r::SCALING_FACTOR } sub unscale ($) { $_[0] * &Slic3r::SCALING_FACTOR } -# used by geometry.t, polygon_segment_having_point -sub point_in_segment { - my ($point, $line) = @_; - - my ($x, $y) = @$point; - my $line_p = $line->pp; - my @line_x = sort { $a <=> $b } $line_p->[A][X], $line_p->[B][X]; - my @line_y = sort { $a <=> $b } $line_p->[A][Y], $line_p->[B][Y]; - - # check whether the point is in the segment bounding box - return 0 unless $x >= ($line_x[0] - epsilon) && $x <= ($line_x[1] + epsilon) - && $y >= ($line_y[0] - epsilon) && $y <= ($line_y[1] + epsilon); - - # if line is vertical, check whether point's X is the same as the line - if ($line_p->[A][X] == $line_p->[B][X]) { - return abs($x - $line_p->[A][X]) < epsilon ? 1 : 0; - } - - # calculate the Y in line at X of the point - my $y3 = $line_p->[A][Y] + ($line_p->[B][Y] - $line_p->[A][Y]) - * ($x - $line_p->[A][X]) / ($line_p->[B][X] - $line_p->[A][X]); - return abs($y3 - $y) < epsilon ? 1 : 0; -} - # used by geometry.t sub polyline_lines { my ($polyline) = @_; @@ -76,17 +50,6 @@ sub polyline_lines { return map Slic3r::Line->new(@points[$_, $_+1]), 0 .. $#points-1; } -# given a $polygon, return the (first) segment having $point -# used by geometry.t -sub polygon_segment_having_point { - my ($polygon, $point) = @_; - - foreach my $line (@{ $polygon->lines }) { - return $line if point_in_segment($point, $line); - } - return undef; -} - # polygon must be simple (non complex) and ccw sub polygon_is_convex { my ($points) = @_; diff --git a/lib/Slic3r/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm index b7a7da7727..cfcb622fdb 100644 --- a/lib/Slic3r/Geometry/Clipper.pm +++ b/lib/Slic3r/Geometry/Clipper.pm @@ -6,9 +6,9 @@ require Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw( offset - offset_ex offset2_ex + offset2_ex diff_ex diff union_ex intersection_ex JT_ROUND JT_MITER JT_SQUARE - intersection intersection_pl diff_pl union); + intersection diff_pl union); 1; diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 0385f88b88..f03a97ea3a 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -7,7 +7,7 @@ use List::Util qw(min max sum first); use Slic3r::Flow ':roles'; use Slic3r::Geometry qw(scale epsilon); use Slic3r::Geometry::Clipper qw(diff diff_ex intersection intersection_ex union union_ex - offset offset_ex offset2_ex JT_MITER); + offset offset2_ex JT_MITER); use Slic3r::Print::State ':steps'; use Slic3r::Surface ':types'; diff --git a/t/geometry.t b/t/geometry.t index bb72b22e17..874dab9877 100644 --- a/t/geometry.t +++ b/t/geometry.t @@ -2,7 +2,7 @@ use Test::More; use strict; use warnings; -plan tests => 30; +plan tests => 27; BEGIN { use FindBin; @@ -39,44 +39,6 @@ isnt Slic3r::Geometry::line_intersection($line1, $line2, 1), undef, 'line_inters #========================================================== -{ - my $polygon = Slic3r::Polygon->new( - [45919000, 515273900], [14726100, 461246400], [14726100, 348753500], [33988700, 315389800], - [43749700, 343843000], [45422300, 352251500], [52362100, 362637800], [62748400, 369577600], - [75000000, 372014700], [87251500, 369577600], [97637800, 362637800], [104577600, 352251500], - [107014700, 340000000], [104577600, 327748400], [97637800, 317362100], [87251500, 310422300], - [82789200, 309534700], [69846100, 294726100], [254081000, 294726100], [285273900, 348753500], - [285273900, 461246400], [254081000, 515273900], - ); - - # this points belongs to $polyline - # note: it's actually a vertex, while we should better check an intermediate point - my $point = Slic3r::Point->new(104577600, 327748400); - - local $Slic3r::Geometry::epsilon = 1E-5; - is_deeply Slic3r::Geometry::polygon_segment_having_point($polygon, $point)->pp, - [ [107014700, 340000000], [104577600, 327748400] ], - 'polygon_segment_having_point'; -} - -#========================================================== - -{ - my $point = Slic3r::Point->new(736310778.185108, 5017423926.8924); - my $line = Slic3r::Line->new([627484000, 3695776000], [750000000, 3720147000]); - is Slic3r::Geometry::point_in_segment($point, $line), 0, 'point_in_segment'; -} - -#========================================================== - -{ - my $point = Slic3r::Point->new(736310778.185108, 5017423926.8924); - my $line = Slic3r::Line->new([627484000, 3695776000], [750000000, 3720147000]); - is Slic3r::Geometry::point_in_segment($point, $line), 0, 'point_in_segment'; -} - -#========================================================== - my $polygons = [ Slic3r::Polygon->new( # contour, ccw [45919000, 515273900], [14726100, 461246400], [14726100, 348753500], [33988700, 315389800], diff --git a/t/polyclip.t b/t/polyclip.t deleted file mode 100644 index 0808c7be99..0000000000 --- a/t/polyclip.t +++ /dev/null @@ -1,121 +0,0 @@ -use Test::More; -use strict; -use warnings; - -plan tests => 18; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use Slic3r; -use Slic3r::Geometry::Clipper qw(intersection_pl); - -#========================================================== - -is Slic3r::Geometry::point_in_segment(Slic3r::Point->new(10, 10), Slic3r::Line->new([5, 10], [20, 10])), 1, 'point in horizontal segment'; -is Slic3r::Geometry::point_in_segment(Slic3r::Point->new(30, 10), Slic3r::Line->new([5, 10], [20, 10])), 0, 'point not in horizontal segment'; -is Slic3r::Geometry::point_in_segment(Slic3r::Point->new(10, 10), Slic3r::Line->new([10, 5], [10, 20])), 1, 'point in vertical segment'; -is Slic3r::Geometry::point_in_segment(Slic3r::Point->new(10, 30), Slic3r::Line->new([10, 5], [10, 20])), 0, 'point not in vertical segment'; -is Slic3r::Geometry::point_in_segment(Slic3r::Point->new(15, 15), Slic3r::Line->new([10, 10], [20, 20])), 1, 'point in diagonal segment'; -is Slic3r::Geometry::point_in_segment(Slic3r::Point->new(20, 15), Slic3r::Line->new([10, 10], [20, 20])), 0, 'point not in diagonal segment'; - -#========================================================== - -my $square = Slic3r::Polygon->new( # ccw - [100, 100], - [200, 100], - [200, 200], - [100, 200], -); - -#========================================================== - -{ - my $hole_in_square = [ # cw - [140, 140], - [140, 160], - [160, 160], - [160, 140], - ]; - my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); - #is $expolygon->contains_point(Slic3r::Point->new(100, 100)), 1, 'corner point is recognized'; - #is $expolygon->contains_point(Slic3r::Point->new(100, 180)), 1, 'point on contour is recognized'; - #is $expolygon->contains_point(Slic3r::Point->new(140, 150)), 1, 'point on hole contour is recognized'; - #is $expolygon->contains_point(Slic3r::Point->new(140, 140)), 1, 'point on hole corner is recognized'; - { - my $intersection = intersection_pl([Slic3r::Polyline->new([150,180], [150,150])], \@$expolygon); - is $intersection->[0]->length, Slic3r::Line->new([150, 180], [150, 160])->length, - 'line is clipped to square with hole'; - } - { - my $intersection = intersection_pl([Slic3r::Polyline->new([150,150], [150,120])], \@$expolygon); - is $intersection->[0]->length, Slic3r::Line->new([150, 140], [150, 120])->length, - 'line is clipped to square with hole'; - } - { - my $intersection = intersection_pl([Slic3r::Polyline->new([120,180], [180,180])], \@$expolygon); - is $intersection->[0]->length, Slic3r::Line->new([120,180], [180,180])->length, - 'line is clipped to square with hole'; - } - { - my $intersection = intersection_pl([Slic3r::Polyline->new([50, 150], [300, 150])], \@$expolygon); - is $intersection->[0]->length, Slic3r::Line->new([100, 150], [140, 150])->length, - 'line is clipped to square with hole'; - is $intersection->[1]->length, Slic3r::Line->new([160, 150], [200, 150])->length, - 'line is clipped to square with hole'; - } - { - my $intersection = intersection_pl([Slic3r::Polyline->new([300, 150], [50, 150])], \@$expolygon); - is $intersection->[0]->length, Slic3r::Line->new([200, 150], [160, 150])->length, - 'reverse line is clipped to square with hole'; - is $intersection->[1]->length, Slic3r::Line->new([140, 150], [100, 150])->length, - 'reverse line is clipped to square with hole'; - } - { - my $intersection = intersection_pl([Slic3r::Polyline->new([100,180], [200,180])], \@$expolygon); - is $intersection->[0]->length, Slic3r::Line->new([100,180], [200,180])->length, - 'tangent line is clipped to square with hole'; - } -} - -#========================================================== - -{ - my $large_circle = Slic3r::Polygon->new_scale( # ccw - [151.8639,288.1192], [133.2778,284.6011], [115.0091,279.6997], [98.2859,270.8606], [82.2734,260.7933], - [68.8974,247.4181], [56.5622,233.0777], [47.7228,216.3558], [40.1617,199.0172], [36.6431,180.4328], - [34.932,165.2312], [37.5567,165.1101], [41.0547,142.9903], [36.9056,141.4295], [40.199,124.1277], - [47.7776,106.7972], [56.6335,90.084], [68.9831,75.7557], [82.3712,62.3948], [98.395,52.3429], - [115.1281,43.5199], [133.4004,38.6374], [151.9884,35.1378], [170.8905,35.8571], [189.6847,37.991], - [207.5349,44.2488], [224.8662,51.8273], [240.0786,63.067], [254.407,75.4169], [265.6311,90.6406], - [275.6832,106.6636], [281.9225,124.52], [286.8064,142.795], [287.5061,161.696], [286.7874,180.5972], - [281.8856,198.8664], [275.6283,216.7169], [265.5604,232.7294], [254.3211,247.942], [239.9802,260.2776], - [224.757,271.5022], [207.4179,279.0635], [189.5605,285.3035], [170.7649,287.4188], - ); - ok $large_circle->is_counter_clockwise, "contour is counter-clockwise"; - - my $small_circle = Slic3r::Polygon->new_scale( # cw - [158.227,215.9007], [164.5136,215.9007], [175.15,214.5007], [184.5576,210.6044], [190.2268,207.8743], - [199.1462,201.0306], [209.0146,188.346], [213.5135,177.4829], [214.6979,168.4866], [216.1025,162.3325], - [214.6463,151.2703], [213.2471,145.1399], [209.0146,134.9203], [199.1462,122.2357], [189.8944,115.1366], - [181.2504,111.5567], [175.5684,108.8205], [164.5136,107.3655], [158.2269,107.3655], [147.5907,108.7656], - [138.183,112.6616], [132.5135,115.3919], [123.5943,122.2357], [113.7259,134.92], [109.2269,145.7834], - [108.0426,154.7799], [106.638,160.9339], [108.0941,171.9957], [109.4933,178.1264], [113.7259,188.3463], - [123.5943,201.0306], [132.8461,208.1296], [141.4901,211.7094], [147.172,214.4458], - ); - ok $small_circle->is_clockwise, "hole is clockwise"; - - my $expolygon = Slic3r::ExPolygon->new($large_circle, $small_circle); - my $line = Slic3r::Polyline->new_scale([152.742,288.086671142818], [152.742,34.166466971035]); - - my $intersection = intersection_pl([$line], \@$expolygon); - is $intersection->[0]->length, Slic3r::Line->new([152742000, 288086661], [152742000, 215178843])->length, - 'line is clipped to square with hole'; - is $intersection->[1]->length, Slic3r::Line->new([152742000, 108087507], [152742000, 35166477])->length, - 'line is clipped to square with hole'; -} - -#========================================================== diff --git a/tests/libslic3r/test_clipper_utils.cpp b/tests/libslic3r/test_clipper_utils.cpp index 1b2b45eca7..b357d8ca83 100644 --- a/tests/libslic3r/test_clipper_utils.cpp +++ b/tests/libslic3r/test_clipper_utils.cpp @@ -188,6 +188,46 @@ SCENARIO("Various Clipper operations - t/clipper.t", "[ClipperUtils]") { REQUIRE(intersection.front().area() == Approx(match.area())); } } + + ExPolygons expolygons { ExPolygon { square, hole_in_square } }; + WHEN("Clipping line 1") { + Polylines intersection = intersection_pl({ Polyline { { 15, 18 }, { 15, 15 } } }, expolygons); + THEN("line is clipped to square with hole") { + REQUIRE((Vec2f(15, 18) - Vec2f(15, 16)).norm() == Approx(intersection.front().length())); + } + } + WHEN("Clipping line 2") { + Polylines intersection = intersection_pl({ Polyline { { 15, 15 }, { 15, 12 } } }, expolygons); + THEN("line is clipped to square with hole") { + REQUIRE((Vec2f(15, 14) - Vec2f(15, 12)).norm() == Approx(intersection.front().length())); + } + } + WHEN("Clipping line 3") { + Polylines intersection = intersection_pl({ Polyline { { 12, 18 }, { 18, 18 } } }, expolygons); + THEN("line is clipped to square with hole") { + REQUIRE((Vec2f(18, 18) - Vec2f(12, 18)).norm() == Approx(intersection.front().length())); + } + } + WHEN("Clipping line 4") { + Polylines intersection = intersection_pl({ Polyline { { 5, 15 }, { 30, 15 } } }, expolygons); + THEN("line is clipped to square with hole") { + REQUIRE((Vec2f(14, 15) - Vec2f(10, 15)).norm() == Approx(intersection.front().length())); + REQUIRE((Vec2f(20, 15) - Vec2f(16, 15)).norm() == Approx(intersection[1].length())); + } + } + WHEN("Clipping line 5") { + Polylines intersection = intersection_pl({ Polyline { { 30, 15 }, { 5, 15 } } }, expolygons); + THEN("reverse line is clipped to square with hole") { + REQUIRE((Vec2f(20, 15) - Vec2f(16, 15)).norm() == Approx(intersection.front().length())); + REQUIRE((Vec2f(14, 15) - Vec2f(10, 15)).norm() == Approx(intersection[1].length())); + } + } + WHEN("Clipping line 6") { + Polylines intersection = intersection_pl({ Polyline { { 10, 18 }, { 20, 18 } } }, expolygons); + THEN("tangent line is clipped to square with hole") { + REQUIRE((Vec2f(20, 18) - Vec2f(10, 18)).norm() == Approx(intersection.front().length())); + } + } } GIVEN("square with hole 2") { // CCW oriented contour @@ -223,6 +263,44 @@ SCENARIO("Various Clipper operations - t/clipper.t", "[ClipperUtils]") { } } } + GIVEN("circle") { + Slic3r::ExPolygon circle_with_hole { Polygon::new_scale({ + { 151.8639,288.1192 }, {133.2778,284.6011}, { 115.0091,279.6997 }, { 98.2859,270.8606 }, { 82.2734,260.7933 }, + { 68.8974,247.4181 }, { 56.5622,233.0777 }, { 47.7228,216.3558 }, { 40.1617,199.0172 }, { 36.6431,180.4328 }, + { 34.932,165.2312 }, { 37.5567,165.1101 }, { 41.0547,142.9903 }, { 36.9056,141.4295 }, { 40.199,124.1277 }, + { 47.7776,106.7972 }, { 56.6335,90.084 }, { 68.9831,75.7557 }, { 82.3712,62.3948 }, { 98.395,52.3429 }, + { 115.1281,43.5199 }, { 133.4004,38.6374 }, { 151.9884,35.1378 }, { 170.8905,35.8571 }, { 189.6847,37.991 }, + { 207.5349,44.2488 }, { 224.8662,51.8273 }, { 240.0786,63.067 }, { 254.407,75.4169 }, { 265.6311,90.6406 }, + { 275.6832,106.6636 }, { 281.9225,124.52 }, { 286.8064,142.795 }, { 287.5061,161.696 }, { 286.7874,180.5972 }, + { 281.8856,198.8664 }, { 275.6283,216.7169 }, { 265.5604,232.7294 }, { 254.3211,247.942 }, { 239.9802,260.2776 }, + { 224.757,271.5022 }, { 207.4179,279.0635 }, { 189.5605,285.3035 }, { 170.7649,287.4188 } + }) }; + circle_with_hole.holes = { Polygon::new_scale({ + { 158.227,215.9007 }, { 164.5136,215.9007 }, { 175.15,214.5007 }, { 184.5576,210.6044 }, { 190.2268,207.8743 }, + { 199.1462,201.0306 }, { 209.0146,188.346 }, { 213.5135,177.4829 }, { 214.6979,168.4866 }, { 216.1025,162.3325 }, + { 214.6463,151.2703 }, { 213.2471,145.1399 }, { 209.0146,134.9203 }, { 199.1462,122.2357 }, { 189.8944,115.1366 }, + { 181.2504,111.5567 }, { 175.5684,108.8205 }, { 164.5136,107.3655 }, { 158.2269,107.3655 }, { 147.5907,108.7656 }, + { 138.183,112.6616 }, { 132.5135,115.3919 }, { 123.5943,122.2357 }, { 113.7259,134.92 }, { 109.2269,145.7834 }, + { 108.0426,154.7799 }, { 106.638,160.9339 }, { 108.0941,171.9957 }, { 109.4933,178.1264 }, { 113.7259,188.3463 }, + { 123.5943,201.0306 }, { 132.8461,208.1296 }, { 141.4901,211.7094 }, { 147.172,214.4458 } + }) }; + THEN("contour is counter-clockwise") { + REQUIRE(circle_with_hole.contour.is_counter_clockwise()); + } + THEN("hole is counter-clockwise") { + REQUIRE(circle_with_hole.holes.size() == 1); + REQUIRE(circle_with_hole.holes.front().is_clockwise()); + } + + WHEN("clipping a line") { + auto line = Polyline::new_scale({ { 152.742,288.086671142818 }, { 152.742,34.166466971035 } }); + Polylines intersection = intersection_pl({ line }, { circle_with_hole }); + THEN("clipped to two pieces") { + REQUIRE(intersection.front().length() == Approx((Vec2d(152742000, 215178843) - Vec2d(152742000, 288086661)).norm())); + REQUIRE(intersection[1].length() == Approx((Vec2d(152742000, 35166477) - Vec2d(152742000, 108087507)).norm())); + } + } + } } template diff --git a/xs/xsp/Clipper.xsp b/xs/xsp/Clipper.xsp index eae3afeffb..18f8dec072 100644 --- a/xs/xsp/Clipper.xsp +++ b/xs/xsp/Clipper.xsp @@ -30,17 +30,6 @@ offset(polygons, delta, joinType = Slic3r::ClipperLib::jtMiter, miterLimit = 3) OUTPUT: RETVAL -ExPolygons -offset_ex(polygons, delta, joinType = Slic3r::ClipperLib::jtMiter, miterLimit = 3) - Polygons polygons - const float delta - Slic3r::ClipperLib::JoinType joinType - double miterLimit - CODE: - RETVAL = offset_ex(polygons, delta, joinType, miterLimit); - OUTPUT: - RETVAL - ExPolygons offset2_ex(polygons, delta1, delta2, joinType = Slic3r::ClipperLib::jtMiter, miterLimit = 3) Polygons polygons @@ -102,15 +91,6 @@ intersection_ex(subject, clip, safety_offset = false) OUTPUT: RETVAL -Polylines -intersection_pl(subject, clip) - Polylines subject - Polygons clip - CODE: - RETVAL = intersection_pl(subject, clip); - OUTPUT: - RETVAL - Polygons union(subject, safety_offset = false) Polygons subject diff --git a/xs/xsp/Surface.xsp b/xs/xsp/Surface.xsp index 49d988333c..8804b851bb 100644 --- a/xs/xsp/Surface.xsp +++ b/xs/xsp/Surface.xsp @@ -82,16 +82,6 @@ Surface::polygons() OUTPUT: RETVAL -Surfaces -Surface::offset(delta, joinType = ClipperLib::jtMiter, miterLimit = 3) - const float delta - Slic3r::ClipperLib::JoinType joinType - double miterLimit - CODE: - surfaces_append(RETVAL, offset_ex(THIS->expolygon, delta, joinType, miterLimit), *THIS); - OUTPUT: - RETVAL - %} }; From d88e9634f511714dc03072164b4f340efda0331d Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 28 Apr 2022 17:02:33 +0200 Subject: [PATCH 04/11] Little more reduce of Perl bindings --- lib/Slic3r.pm | 1 - lib/Slic3r/ExPolygon.pm | 8 ------- lib/Slic3r/Geometry/Clipper.pm | 5 ++--- lib/Slic3r/Line.pm | 6 ------ lib/Slic3r/Print/Object.pm | 3 --- t/perimeters.t | 2 +- xs/xsp/Clipper.xsp | 39 ---------------------------------- xs/xsp/Polygon.xsp | 6 ------ 8 files changed, 3 insertions(+), 67 deletions(-) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 94f0b5658f..d9274bea93 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -42,7 +42,6 @@ use Slic3r::ExtrusionLoop; use Slic3r::ExtrusionPath; use Slic3r::Flow; use Slic3r::GCode::Reader; -use Slic3r::Geometry::Clipper; use Slic3r::Layer; use Slic3r::Line; use Slic3r::Model; diff --git a/lib/Slic3r/ExPolygon.pm b/lib/Slic3r/ExPolygon.pm index 6337cb9a1a..4ab1bc8ab4 100644 --- a/lib/Slic3r/ExPolygon.pm +++ b/lib/Slic3r/ExPolygon.pm @@ -4,14 +4,6 @@ use warnings; # an ExPolygon is a polygon with holes -use List::Util qw(first); -use Slic3r::Geometry::Clipper qw(union_ex diff_pl); - -sub offset { - my $self = shift; - return Slic3r::Geometry::Clipper::offset(\@$self, @_); -} - sub noncollapsing_offset_ex { my $self = shift; my ($distance, @params) = @_; diff --git a/lib/Slic3r/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm index cfcb622fdb..c1fa81d9f3 100644 --- a/lib/Slic3r/Geometry/Clipper.pm +++ b/lib/Slic3r/Geometry/Clipper.pm @@ -7,8 +7,7 @@ our @ISA = qw(Exporter); our @EXPORT_OK = qw( offset offset2_ex - diff_ex diff union_ex intersection_ex - JT_ROUND JT_MITER JT_SQUARE - intersection diff_pl union); + diff_ex diff union_ex + union); 1; diff --git a/lib/Slic3r/Line.pm b/lib/Slic3r/Line.pm index bf53520d2d..c06f9a1fa3 100644 --- a/lib/Slic3r/Line.pm +++ b/lib/Slic3r/Line.pm @@ -5,12 +5,6 @@ use warnings; # a line is a two-points line use parent 'Slic3r::Polyline'; -sub intersection { - my $self = shift; - my ($line, $require_crossing) = @_; - return Slic3r::Geometry::line_intersection($self, $line, $require_crossing); -} - sub grow { my $self = shift; return Slic3r::Polyline->new(@$self)->grow(@_); diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index f03a97ea3a..64480e08fe 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -5,9 +5,6 @@ use warnings; use List::Util qw(min max sum first); use Slic3r::Flow ':roles'; -use Slic3r::Geometry qw(scale epsilon); -use Slic3r::Geometry::Clipper qw(diff diff_ex intersection intersection_ex union union_ex - offset offset2_ex JT_MITER); use Slic3r::Print::State ':steps'; use Slic3r::Surface ':types'; diff --git a/t/perimeters.t b/t/perimeters.t index adc2a6cec7..c4aef6e7e7 100644 --- a/t/perimeters.t +++ b/t/perimeters.t @@ -14,7 +14,7 @@ use List::Util qw(first); use Slic3r; use Slic3r::Flow ':roles'; use Slic3r::Geometry qw(PI scale unscale); -use Slic3r::Geometry::Clipper qw(union_ex diff union offset); +use Slic3r::Geometry::Clipper qw(union_ex diff); use Slic3r::Surface ':types'; use Slic3r::Test; diff --git a/xs/xsp/Clipper.xsp b/xs/xsp/Clipper.xsp index 18f8dec072..5f9f359067 100644 --- a/xs/xsp/Clipper.xsp +++ b/xs/xsp/Clipper.xsp @@ -9,16 +9,6 @@ %{ -IV -_constant() - ALIAS: - JT_MITER = jtMiter - JT_ROUND = jtRound - JT_SQUARE = jtSquare - CODE: - RETVAL = ix; - OUTPUT: RETVAL - Polygons offset(polygons, delta, joinType = Slic3r::ClipperLib::jtMiter, miterLimit = 3) Polygons polygons @@ -62,35 +52,6 @@ diff_ex(subject, clip, safety_offset = false) OUTPUT: RETVAL -Polylines -diff_pl(subject, clip) - Polylines subject - Polygons clip - CODE: - RETVAL = diff_pl(subject, clip); - OUTPUT: - RETVAL - -Polygons -intersection(subject, clip, safety_offset = false) - Polygons subject - Polygons clip - bool safety_offset - CODE: - RETVAL = intersection(subject, clip, safety_offset ? ApplySafetyOffset::Yes : ApplySafetyOffset::No); - OUTPUT: - RETVAL - -ExPolygons -intersection_ex(subject, clip, safety_offset = false) - Polygons subject - Polygons clip - bool safety_offset - CODE: - RETVAL = intersection_ex(subject, clip, safety_offset ? ApplySafetyOffset::Yes : ApplySafetyOffset::No); - OUTPUT: - RETVAL - Polygons union(subject, safety_offset = false) Polygons subject diff --git a/xs/xsp/Polygon.xsp b/xs/xsp/Polygon.xsp index 6b6d525240..984eb16a91 100644 --- a/xs/xsp/Polygon.xsp +++ b/xs/xsp/Polygon.xsp @@ -41,12 +41,6 @@ Clone bounding_box(); Clone point_projection(Point* point) %code{% RETVAL = THIS->point_projection(*point); %}; - Clone intersection(Line* line) - %code{% - Point p; - (void)THIS->intersection(*line, &p); - RETVAL = p; - %}; Clone first_intersection(Line* line) %code{% Point p; From 33b2478b6907c7e888cc11ecf6dfbdf818437993 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 2 May 2022 14:34:39 +0200 Subject: [PATCH 05/11] Ported Infill unit tests from Perl to C++. --- src/libslic3r/Config.hpp | 2 + src/libslic3r/ExtrusionEntity.hpp | 4 +- src/libslic3r/GCodeReader.hpp | 4 + t/fill.t | 318 ------------- tests/fff_print/test_data.cpp | 13 + tests/fff_print/test_extrusion_entity.cpp | 58 +++ tests/fff_print/test_fill.cpp | 516 +++++++++++++--------- xs/CMakeLists.txt | 3 - xs/lib/Slic3r/XS.pm | 24 - xs/main.xs.in | 1 - xs/src/perlglue.cpp | 2 - xs/xsp/ExtrusionSimulator.xsp | 50 --- xs/xsp/Filler.xsp | 65 --- xs/xsp/GCodeSender.xsp | 24 - xs/xsp/my.map | 8 - xs/xsp/typemap.xspt | 6 - 16 files changed, 387 insertions(+), 711 deletions(-) delete mode 100644 t/fill.t delete mode 100644 xs/xsp/ExtrusionSimulator.xsp delete mode 100644 xs/xsp/Filler.xsp delete mode 100644 xs/xsp/GCodeSender.xsp diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index a087dd8540..bfd307de3d 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include "libslic3r.h" #include "clonable_ptr.hpp" @@ -1989,6 +1990,7 @@ public: struct SetDeserializeItem { SetDeserializeItem(const char *opt_key, const char *opt_value, bool append = false) : opt_key(opt_key), opt_value(opt_value), append(append) {} SetDeserializeItem(const std::string &opt_key, const std::string &opt_value, bool append = false) : opt_key(opt_key), opt_value(opt_value), append(append) {} + SetDeserializeItem(const std::string &opt_key, const std::string_view opt_value, bool append = false) : opt_key(opt_key), opt_value(opt_value), append(append) {} SetDeserializeItem(const char *opt_key, const bool value, bool append = false) : opt_key(opt_key), opt_value(value ? "1" : "0"), append(append) {} SetDeserializeItem(const std::string &opt_key, const bool value, bool append = false) : opt_key(opt_key), opt_value(value ? "1" : "0"), append(append) {} SetDeserializeItem(const char *opt_key, const int value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {} diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp index 5e6a51d20a..2e9e467890 100644 --- a/src/libslic3r/ExtrusionEntity.hpp +++ b/src/libslic3r/ExtrusionEntity.hpp @@ -324,10 +324,10 @@ inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &&polylines, E polylines.clear(); } -inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines &polylines, ExtrusionRole role, double mm3_per_mm, float width, float height) +inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, const Polylines &polylines, ExtrusionRole role, double mm3_per_mm, float width, float height) { dst.reserve(dst.size() + polylines.size()); - for (Polyline &polyline : polylines) + for (const Polyline &polyline : polylines) if (polyline.is_valid()) { ExtrusionPath *extrusion_path = new ExtrusionPath(role, mm3_per_mm, width, height); dst.push_back(extrusion_path); diff --git a/src/libslic3r/GCodeReader.hpp b/src/libslic3r/GCodeReader.hpp index 0ab268139f..40a0992292 100644 --- a/src/libslic3r/GCodeReader.hpp +++ b/src/libslic3r/GCodeReader.hpp @@ -35,6 +35,8 @@ public: float new_Z(const GCodeReader &reader) const { return this->has(Z) ? this->z() : reader.z(); } float new_E(const GCodeReader &reader) const { return this->has(E) ? this->e() : reader.e(); } float new_F(const GCodeReader &reader) const { return this->has(F) ? this->f() : reader.f(); } + Point new_XY_scaled(const GCodeReader &reader) const + { return Point::new_scale(this->new_X(reader), this->new_Y(reader)); } float dist_X(const GCodeReader &reader) const { return this->has(X) ? (this->x() - reader.x()) : 0; } float dist_Y(const GCodeReader &reader) const { return this->has(Y) ? (this->y() - reader.y()) : 0; } float dist_Z(const GCodeReader &reader) const { return this->has(Z) ? (this->z() - reader.z()) : 0; } @@ -134,6 +136,8 @@ public: float e() const { return m_position[E]; } float& f() { return m_position[F]; } float f() const { return m_position[F]; } + Point xy_scaled() const { return Point::new_scale(this->x(), this->y()); } + // Returns 0 for gcfNoExtrusion. char extrusion_axis() const { return m_extrusion_axis; } diff --git a/t/fill.t b/t/fill.t deleted file mode 100644 index 88cc35801f..0000000000 --- a/t/fill.t +++ /dev/null @@ -1,318 +0,0 @@ -use Test::More; -use strict; -use warnings; - -#plan tests => 43; -# Test of a 100% coverage is off. -plan tests => 19; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use List::Util qw(first sum); -use Slic3r; -use Slic3r::Geometry qw(X Y scale unscale convex_hull); -use Slic3r::Geometry::Clipper qw(union diff diff_ex offset offset2_ex); -use Slic3r::Surface qw(:types); -use Slic3r::Test; - -sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } - -{ - my $expolygon = Slic3r::ExPolygon->new([ scale_points [0,0], [50,0], [50,50], [0,50] ]); - my $filler = Slic3r::Filler->new_from_type('rectilinear'); - $filler->set_bounding_box($expolygon->bounding_box); - $filler->set_angle(0); - my $surface = Slic3r::Surface->new( - surface_type => S_TYPE_TOP, - expolygon => $expolygon, - ); - my $flow = Slic3r::Flow->new( - width => 0.69, - height => 0.4, - nozzle_diameter => 0.50, - ); - $filler->set_spacing($flow->spacing); - foreach my $angle (0, 45) { - $surface->expolygon->rotate(Slic3r::Geometry::deg2rad($angle), [0,0]); - my $paths = $filler->fill_surface($surface, layer_height => 0.4, density => 0.4); - is scalar @$paths, 1, 'one continuous path'; - } -} - -SKIP: -{ - skip "The FillRectilinear2 does not fill the surface completely", 1; - - my $test = sub { - my ($expolygon, $flow_spacing, $angle, $density) = @_; - - my $filler = Slic3r::Filler->new_from_type('rectilinear'); - $filler->set_bounding_box($expolygon->bounding_box); - $filler->set_angle($angle // 0); - # Adjust line spacing to fill the region. - $filler->set_dont_adjust(0); - $filler->set_link_max_length(scale(1.2*$flow_spacing)); - my $surface = Slic3r::Surface->new( - surface_type => S_TYPE_BOTTOM, - expolygon => $expolygon, - ); - my $flow = Slic3r::Flow->new( - width => $flow_spacing, - height => 0.4, - nozzle_diameter => $flow_spacing, - ); - $filler->set_spacing($flow->spacing); - my $paths = $filler->fill_surface( - $surface, - layer_height => $flow->height, - density => $density // 1, - ); - - # check whether any part was left uncovered - my @grown_paths = map @{Slic3r::Polyline->new(@$_)->grow(scale $filler->spacing/2)}, @$paths; - my $uncovered = diff_ex([ @$expolygon ], [ @grown_paths ], 1); - - # ignore very small dots - my $uncovered_filtered = [ grep $_->area > (scale $flow_spacing)**2, @$uncovered ]; - - is scalar(@$uncovered_filtered), 0, 'solid surface is fully filled'; - - if (0 && @$uncovered_filtered) { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output("uncovered.svg", - no_arrows => 1, - expolygons => [ $expolygon ], - blue_expolygons => [ @$uncovered ], - red_expolygons => [ @$uncovered_filtered ], - polylines => [ @$paths ], - ); - exit; - } - }; - - my $expolygon = Slic3r::ExPolygon->new([ - [6883102, 9598327.01296997], - [6883102, 20327272.01297], - [3116896, 20327272.01297], - [3116896, 9598327.01296997], - ]); - $test->($expolygon, 0.55); - - for (1..20) { - $expolygon->scale(1.05); - $test->($expolygon, 0.55); - } - - $expolygon = Slic3r::ExPolygon->new( - [[59515297,5422499],[59531249,5578697],[59695801,6123186],[59965713,6630228],[60328214,7070685],[60773285,7434379],[61274561,7702115],[61819378,7866770],[62390306,7924789],[62958700,7866744],[63503012,7702244],[64007365,7434357],[64449960,7070398],[64809327,6634999],[65082143,6123325],[65245005,5584454],[65266967,5422499],[66267307,5422499],[66269190,8310081],[66275379,17810072],[66277259,20697500],[65267237,20697500],[65245004,20533538],[65082082,19994444],[64811462,19488579],[64450624,19048208],[64012101,18686514],[63503122,18415781],[62959151,18251378],[62453416,18198442],[62390147,18197355],[62200087,18200576],[61813519,18252990],[61274433,18415918],[60768598,18686517],[60327567,19047892],[59963609,19493297],[59695865,19994587],[59531222,20539379],[59515153,20697500],[58502480,20697500],[58502480,5422499]] - ); - $test->($expolygon, 0.524341649025257); - - $expolygon = Slic3r::ExPolygon->new([ scale_points [0,0], [98,0], [98,10], [0,10] ]); - $test->($expolygon, 0.5, 45, 0.99); # non-solid infill -} - -{ - my $collection = Slic3r::Polyline::Collection->new( - Slic3r::Polyline->new([0,15], [0,18], [0,20]), - Slic3r::Polyline->new([0,10], [0,8], [0,5]), - ); - is_deeply - [ map $_->[Y], map @$_, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ], - [20, 18, 15, 10, 8, 5], - 'chained path'; -} - -{ - my $collection = Slic3r::Polyline::Collection->new( - Slic3r::Polyline->new([4,0], [10,0], [15,0]), - Slic3r::Polyline->new([10,5], [15,5], [20,5]), - ); - is_deeply - [ map $_->[X], map @$_, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ], - [reverse 4, 10, 15, 10, 15, 20], - 'chained path'; -} - -{ - my $collection = Slic3r::ExtrusionPath::Collection->new( - map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), - Slic3r::Polyline->new([0,15], [0,18], [0,20]), - Slic3r::Polyline->new([0,10], [0,8], [0,5]), - ); - is_deeply - [ map $_->[Y], map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ], - [20, 18, 15, 10, 8, 5], - 'chained path'; -} - -{ - my $collection = Slic3r::ExtrusionPath::Collection->new( - map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), - Slic3r::Polyline->new([15,0], [10,0], [4,0]), - Slic3r::Polyline->new([10,5], [15,5], [20,5]), - ); - is_deeply - [ map $_->[X], map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ], - [reverse 4, 10, 15, 10, 15, 20], - 'chained path'; -} - -for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) { - my $config = Slic3r::Config::new_from_defaults; - $config->set('nozzle_diameter', [0.4,0.4,0.4,0.4]); - $config->set('fill_pattern', $pattern); - $config->set('top_fill_pattern', $pattern); - $config->set('bottom_fill_pattern', $pattern); - $config->set('perimeters', 1); - $config->set('skirts', 0); - $config->set('fill_density', 20); - $config->set('layer_height', 0.05); - $config->set('perimeter_extruder', 1); - $config->set('infill_extruder', 2); - my $print = Slic3r::Test::init_print('20mm_cube', config => $config, scale => 2); - ok my $gcode = Slic3r::Test::gcode($print), "successful $pattern infill generation"; - my $tool = undef; - my @perimeter_points = my @infill_points = (); - Slic3r::GCode::Reader->new->parse($gcode, sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd =~ /^T(\d+)/) { - $tool = $1; - } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { - if ($tool == $config->perimeter_extruder-1) { - push @perimeter_points, Slic3r::Point->new_scale($args->{X}, $args->{Y}); - } elsif ($tool == $config->infill_extruder-1) { - push @infill_points, Slic3r::Point->new_scale($args->{X}, $args->{Y}); - } - } - }); - my $convex_hull = convex_hull(\@perimeter_points); - ok !(defined first { !$convex_hull->contains_point($_) } @infill_points), "infill does not exceed perimeters ($pattern)"; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('nozzle_diameter', [0.4,0.4,0.4,0.4]); - $config->set('infill_only_where_needed', 1); - $config->set('bottom_solid_layers', 0); - $config->set('infill_extruder', 2); - $config->set('infill_extrusion_width', 0.5); - $config->set('wipe_into_infill', 0); - $config->set('fill_density', 40); - $config->set('cooling', [ 0 ]); # for preventing speeds from being altered - $config->set('first_layer_speed', '100%'); # for preventing speeds from being altered - - my $test = sub { - my $print = Slic3r::Test::init_print('pyramid', config => $config); - - my $tool = undef; - my @infill_extrusions = (); # array of polylines - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd =~ /^T(\d+)/) { - $tool = $1; - } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { - if ($tool == $config->infill_extruder-1) { - push @infill_extrusions, Slic3r::Line->new_scale( - [ $self->X, $self->Y ], - [ $info->{new_X}, $info->{new_Y} ], - ); - } - } - }); - return 0 if !@infill_extrusions; # prevent calling convex_hull() with no points - - my $convex_hull = convex_hull([ map $_->pp, map @$_, @infill_extrusions ]); - return unscale unscale sum(map $_->area, @{offset([$convex_hull], scale(+$config->infill_extrusion_width/2))}); - }; - - my $tolerance = 5; # mm^2 - - $config->set('solid_infill_below_area', 0); - ok $test->() < $tolerance, - 'no infill is generated when using infill_only_where_needed on a pyramid'; - - $config->set('solid_infill_below_area', 70); - ok abs($test->() - $config->solid_infill_below_area) < $tolerance, - 'infill is only generated under the forced solid shells'; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('skirts', 0); - $config->set('perimeters', 1); - $config->set('fill_density', 0); - $config->set('top_solid_layers', 0); - $config->set('bottom_solid_layers', 0); - $config->set('solid_infill_below_area', 20000000); - $config->set('solid_infill_every_layers', 2); - $config->set('perimeter_speed', 99); - $config->set('external_perimeter_speed', 99); - $config->set('cooling', [ 0 ]); - $config->set('first_layer_speed', '100%'); - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my %layers_with_extrusion = (); - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd eq 'G1' && $info->{dist_XY} > 0 && $info->{extruding}) { - if (($args->{F} // $self->F) != $config->perimeter_speed*60) { - $layers_with_extrusion{$self->Z} = ($args->{F} // $self->F); - } - } - }); - - ok !%layers_with_extrusion, - "solid_infill_below_area and solid_infill_every_layers are ignored when fill_density is 0"; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('skirts', 0); - $config->set('perimeters', 3); - $config->set('fill_density', 0); - $config->set('layer_height', 0.2); - $config->set('first_layer_height', 0.2); - $config->set('nozzle_diameter', [0.35,0.35,0.35,0.35]); - $config->set('infill_extruder', 2); - $config->set('solid_infill_extruder', 2); - $config->set('infill_extrusion_width', 0.52); - $config->set('solid_infill_extrusion_width', 0.52); - $config->set('first_layer_extrusion_width', 0); - - my $print = Slic3r::Test::init_print('A', config => $config); - my %infill = (); # Z => [ Line, Line ... ] - my $tool = undef; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd =~ /^T(\d+)/) { - $tool = $1; - } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { - if ($tool == $config->infill_extruder-1) { - my $z = 1 * $self->Z; - $infill{$z} ||= []; - push @{$infill{$z}}, Slic3r::Line->new_scale( - [ $self->X, $self->Y ], - [ $info->{new_X}, $info->{new_Y} ], - ); - } - } - }); - my $grow_d = scale($config->infill_extrusion_width)/2; - my $layer0_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.2} } ]); - my $layer1_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.4} } ]); - my $diff = diff($layer0_infill, $layer1_infill); - $diff = offset2_ex($diff, -$grow_d, +$grow_d); - $diff = [ grep { $_->area > 2*(($grow_d*2)**2) } @$diff ]; - is scalar(@$diff), 0, 'no missing parts in solid shell when fill_density is 0'; -} - -__END__ diff --git a/tests/fff_print/test_data.cpp b/tests/fff_print/test_data.cpp index f7077007db..6be45d2380 100644 --- a/tests/fff_print/test_data.cpp +++ b/tests/fff_print/test_data.cpp @@ -187,6 +187,19 @@ TriangleMesh mesh(TestMesh m) return mesh; } +TriangleMesh mesh(TestMesh min, Vec3d translate, Vec3d scale) +{ + TriangleMesh m = mesh(min); + m.translate(translate.cast()); + m.scale(scale.cast()); + return m; +} + +TriangleMesh mesh(TestMesh m, Vec3d translate, double scale) +{ + return mesh(m, translate, Vec3d(scale, scale, scale)); +} + static bool verbose_gcode() { const char *v = std::getenv("SLIC3R_TESTS_GCODE"); diff --git a/tests/fff_print/test_extrusion_entity.cpp b/tests/fff_print/test_extrusion_entity.cpp index 9bef52a5c3..7e0ca822fc 100644 --- a/tests/fff_print/test_extrusion_entity.cpp +++ b/tests/fff_print/test_extrusion_entity.cpp @@ -5,6 +5,7 @@ #include "libslic3r/ExtrusionEntityCollection.hpp" #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/Point.hpp" +#include "libslic3r/ShortestPath.hpp" #include "libslic3r/libslic3r.h" #include "test_data.hpp" @@ -83,3 +84,60 @@ SCENARIO("ExtrusionEntityCollection: Polygon flattening", "[ExtrusionEntity]") { } } } + +TEST_CASE("ExtrusionEntityCollection: Chained path", "[ExtrusionEntity]") { + struct Test { + Polylines unchained; + Polylines chained; + Point initial_point; + }; + std::vector tests { + { + { + { {0,15}, {0,18}, {0,20} }, + { {0,10}, {0,8}, {0,5} } + }, + { + { {0,20}, {0,18}, {0,15} }, + { {0,10}, {0,8}, {0,5} } + }, + { 0,30 } + }, + { + { + { {4,0}, {10,0}, {15,0} }, + { {10,5}, {15,5}, {20,5} } + }, + { + { {20,5}, {15,5}, {10,5} }, + { {15,0}, {10,0}, {4,0} } + }, + { 30,0 } + }, + { + { + { {15,0}, {10,0}, {4,0} }, + { {10,5}, {15,5}, {20,5} } + }, + { + { {20,5}, {15,5}, {10,5} }, + { {15,0}, {10,0}, {4,0} } + }, + { 30,0 } + }, + }; + for (const Test &test : tests) { + Polylines chained = chain_polylines(test.unchained, &test.initial_point); + REQUIRE(chained == test.chained); + ExtrusionEntityCollection unchained_extrusions; + extrusion_entities_append_paths(unchained_extrusions.entities, test.unchained, + erInternalInfill, 0., 0.4f, 0.3f); + ExtrusionEntityCollection chained_extrusions = unchained_extrusions.chained_path_from(test.initial_point); + REQUIRE(chained_extrusions.entities.size() == test.chained.size()); + for (size_t i = 0; i < chained_extrusions.entities.size(); ++ i) { + const Points &p1 = test.chained[i].points; + const Points &p2 = dynamic_cast(chained_extrusions.entities[i])->polyline.points; + REQUIRE(p1 == p2); + } + } +} \ No newline at end of file diff --git a/tests/fff_print/test_fill.cpp b/tests/fff_print/test_fill.cpp index 8e311282e0..9f30bf8be7 100644 --- a/tests/fff_print/test_fill.cpp +++ b/tests/fff_print/test_fill.cpp @@ -7,6 +7,7 @@ #include "libslic3r/Fill/Fill.hpp" #include "libslic3r/Flow.hpp" #include "libslic3r/Geometry.hpp" +#include "libslic3r/Geometry/ConvexHull.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/SVG.hpp" #include "libslic3r/libslic3r.h" @@ -14,6 +15,7 @@ #include "test_data.hpp" using namespace Slic3r; +using namespace std::literals; bool test_if_solid_surface_filled(const ExPolygon& expolygon, double flow_spacing, double angle = 0, double density = 1.0); @@ -43,7 +45,7 @@ TEST_CASE("Fill: Pattern Path Length", "[Fill]") { SECTION("Square") { Slic3r::Points test_set; test_set.reserve(4); - std::vector points {Vec2d(0,0), Vec2d(100,0), Vec2d(100,100), Vec2d(0,100)}; + std::vector points { {0,0}, {100,0}, {100,100}, {0,100} }; for (size_t i = 0; i < 4; ++i) { std::transform(points.cbegin()+i, points.cend(), std::back_inserter(test_set), [] (const Vec2d& a) -> Point { return Point::new_scale(a.x(), a.y()); } ); std::transform(points.cbegin(), points.cbegin()+i, std::back_inserter(test_set), [] (const Vec2d& a) -> Point { return Point::new_scale(a.x(), a.y()); } ); @@ -118,24 +120,26 @@ TEST_CASE("Fill: Pattern Path Length", "[Fill]") { REQUIRE(std::abs(paths[0].length() - static_cast(scale_(3*100 + 2*50))) - SCALED_EPSILON > 0); // path has expected length } - SECTION("Rotated Square") { - Slic3r::Points square { Point::new_scale(0,0), Point::new_scale(50,0), Point::new_scale(50,50), Point::new_scale(0,50)}; - Slic3r::ExPolygon expolygon(square); + SECTION("Rotated Square produces one continuous path") { + Slic3r::ExPolygon expolygon(Polygon::new_scale({ {0, 0}, {50, 0}, {50, 50}, {0, 50} })); std::unique_ptr filler(Slic3r::Fill::new_from_type("rectilinear")); - filler->bounding_box = get_extents(expolygon.contour); + filler->bounding_box = get_extents(expolygon); filler->angle = 0; Surface surface(stTop, expolygon); - auto flow = Slic3r::Flow(0.69f, 0.4f, 0.50f); + // width, height, nozzle_dmr + auto flow = Slic3r::Flow(0.69f, 0.4f, 0.5f); FillParams fill_params; - fill_params.density = 1.0; - filler->spacing = flow.spacing(); - - for (auto angle : { 0.0, 45.0}) { - surface.expolygon.rotate(angle, Point(0,0)); - Polylines paths = filler->fill_surface(&surface, fill_params); - REQUIRE(paths.size() == 1); + for (auto density : { 0.4, 1.0 }) { + fill_params.density = density; + filler->spacing = flow.spacing(); + for (auto angle : { 0.0, 45.0}) { + surface.expolygon.rotate(angle, Point(0,0)); + Polylines paths = filler->fill_surface(&surface, fill_params); + // one continuous path + REQUIRE(paths.size() == 1); + } } } @@ -190,202 +194,226 @@ TEST_CASE("Fill: Pattern Path Length", "[Fill]") { } } +SCENARIO("Infill does not exceed perimeters", "[Fill]") +{ + auto test = [](const std::string_view pattern) { + DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "nozzle_diameter", "0.4, 0.4, 0.4, 0.4" }, + { "fill_pattern", pattern }, + { "top_fill_pattern", pattern }, + { "bottom_fill_pattern", pattern }, + { "perimeters", 1 }, + { "skirts", 0 }, + { "fill_density", 0.2 }, + { "layer_height", 0.05 }, + { "perimeter_extruder", 1 }, + { "infill_extruder", 2 } + }); + + WHEN("40mm cube sliced") { + std::string gcode = Slic3r::Test::slice({ mesh(Slic3r::Test::TestMesh::cube_20x20x20, Vec3d::Zero(), 2.0) }, config); + THEN("gcode not empty") { + REQUIRE(! gcode.empty()); + } + THEN("infill does not exceed perimeters") { + GCodeReader parser; + const int perimeter_extruder = config.opt_int("perimeter_extruder"); + const int infill_extruder = config.opt_int("infill_extruder"); + int tool = -1; + Points perimeter_points; + Points infill_points; + parser.parse_buffer(gcode, [&tool, &perimeter_points, &infill_points, perimeter_extruder, infill_extruder] + (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + // if the command is a T command, set the the current tool + if (boost::starts_with(line.cmd(), "T")) { + tool = atoi(line.cmd().data() + 1) + 1; + } else if (line.cmd() == "G1" && line.extruding(self) && line.dist_XY(self) > 0) { + if (tool == perimeter_extruder) + perimeter_points.emplace_back(line.new_XY_scaled(self)); + else if (tool == infill_extruder) + infill_points.emplace_back(line.new_XY_scaled(self)); + } + }); + auto convex_hull = Geometry::convex_hull(perimeter_points); + int num_inside = std::count_if(infill_points.begin(), infill_points.end(), [&convex_hull](const Point &pt){ return convex_hull.contains(pt); }); + REQUIRE(num_inside == infill_points.size()); + } + } + }; + + GIVEN("Rectilinear") { test("rectilinear"sv); } + GIVEN("Honeycomb") { test("honeycomb"sv); } + GIVEN("HilbertCurve") { test("hilbertcurve"sv); } + GIVEN("Concentric") { test("concentric"sv); } +} + +SCENARIO("Infill only where needed", "[Fill]") +{ + DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "nozzle_diameter", "0.4, 0.4, 0.4, 0.4" }, + { "infill_only_where_needed", true }, + { "bottom_solid_layers", 0 }, + { "infill_extruder", 2 }, + { "infill_extrusion_width", 0.5 }, + { "wipe_into_infill", false }, + { "fill_density", 0.4 }, + // for preventing speeds from being altered + { "cooling", "0, 0, 0, 0" }, + // for preventing speeds from being altered + { "first_layer_speed", "100%" } + }); + + auto test = [&config]() -> double { + std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::pyramid }, config); + THEN("gcode not empty") { + REQUIRE(! gcode.empty()); + } + + GCodeReader parser; + int tool = -1; + const int infill_extruder = config.opt_int("infill_extruder"); + Points infill_points; + parser.parse_buffer(gcode, [&tool, &infill_points, infill_extruder](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + // if the command is a T command, set the the current tool + if (boost::starts_with(line.cmd(), "T")) { + tool = atoi(line.cmd().data() + 1) + 1; + } else if (line.cmd() == "G1" && line.extruding(self) && line.dist_XY(self) > 0) { + if (tool == infill_extruder) { + infill_points.emplace_back(self.xy_scaled()); + infill_points.emplace_back(line.new_XY_scaled(self)); + } + } + }); + // prevent calling convex_hull() with no points + THEN("infill not empty") { + REQUIRE(! infill_points.empty()); + } + + auto opt_width = config.opt("infill_extrusion_width"); + REQUIRE(! opt_width->percent); + Polygons convex_hull = expand(Geometry::convex_hull(infill_points), scaled(opt_width->value / 2)); + return SCALING_FACTOR * SCALING_FACTOR * std::accumulate(convex_hull.begin(), convex_hull.end(), 0., [](double acc, const Polygon &poly){ return acc + poly.area(); }); + }; + + double tolerance = 5; // mm^2 + + GIVEN("solid_infill_below_area == 0") { + config.opt_float("solid_infill_below_area") = 0; + WHEN("pyramid is sliced ") { + auto area = test(); + THEN("no infill is generated when using infill_only_where_needed on a pyramid") { + REQUIRE(area < tolerance); + } + } + } + GIVEN("solid_infill_below_area == 70") { + config.opt_float("solid_infill_below_area") = 70; + WHEN("pyramid is sliced ") { + auto area = test(); + THEN("infill is only generated under the forced solid shells") { + REQUIRE(std::abs(area - 70) < tolerance); + } + } + } +} + +SCENARIO("Infill density zero", "[Fill]") +{ + WHEN("20mm cube is sliced") { + DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "skirts", 0 }, + { "perimeters", 1 }, + { "fill_density", 0 }, + { "top_solid_layers", 0 }, + { "bottom_solid_layers", 0 }, + { "solid_infill_below_area", 20000000 }, + { "solid_infill_every_layers", 2 }, + { "perimeter_speed", 99 }, + { "external_perimeter_speed", 99 }, + { "cooling", "0" }, + { "first_layer_speed", "100%" } + }); + + std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config); + THEN("gcode not empty") { + REQUIRE(! gcode.empty()); + } + + THEN("solid_infill_below_area and solid_infill_every_layers are ignored when fill_density is 0") { + GCodeReader parser; + const double perimeter_speed = config.opt_float("perimeter_speed"); + std::map layers_with_extrusion; + parser.parse_buffer(gcode, [&layers_with_extrusion, perimeter_speed](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + if (line.cmd() == "G1" && line.extruding(self) && line.dist_XY(self) > 0) { + double f = line.new_F(self); + if (std::abs(f - perimeter_speed * 60.) > 0.01) + // It is a perimeter. + layers_with_extrusion[self.z()] = f; + } + }); + REQUIRE(layers_with_extrusion.empty()); + } + } + + WHEN("A is sliced") { + DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "skirts", 0 }, + { "perimeters", 3 }, + { "fill_density", 0 }, + { "layer_height", 0.2 }, + { "first_layer_height", 0.2 }, + { "nozzle_diameter", "0.35,0.35,0.35,0.35" }, + { "infill_extruder", 2 }, + { "solid_infill_extruder", 2 }, + { "infill_extrusion_width", 0.52 }, + { "solid_infill_extrusion_width", 0.52 }, + { "first_layer_extrusion_width", 0 } + }); + + std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::A }, config); + THEN("gcode not empty") { + REQUIRE(! gcode.empty()); + } + + THEN("no missing parts in solid shell when fill_density is 0") { + GCodeReader parser; + int tool = -1; + const int infill_extruder = config.opt_int("infill_extruder"); + std::map infill; + parser.parse_buffer(gcode, [&tool, &infill, infill_extruder](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + if (boost::starts_with(line.cmd(), "T")) { + tool = atoi(line.cmd().data() + 1) + 1; + } else if (line.cmd() == "G1" && line.extruding(self) && line.dist_XY(self) > 0) { + if (tool == infill_extruder) + infill[scaled(self.z())].emplace_back(self.xy_scaled(), line.new_XY_scaled(self)); + } + }); + auto opt_width = config.opt("infill_extrusion_width"); + REQUIRE(! opt_width->percent); + auto grow_d = scaled(opt_width->value / 2); + auto inflate_lines = [grow_d](const Lines &lines) { + Polygons out; + for (const Line &line : lines) + append(out, offset(Polyline{ line.a, line.b }, grow_d, Slic3r::ClipperLib::jtSquare, 3.)); + return union_(out); + }; + Polygons layer0_infill = inflate_lines(infill[scaled(0.2)]); + Polygons layer1_infill = inflate_lines(infill[scaled(0.4)]); + ExPolygons poly = opening_ex(diff_ex(layer0_infill, layer1_infill), grow_d); + const double threshold = 2. * sqr(grow_d * 2.); + int missing_parts = std::count_if(poly.begin(), poly.end(), [threshold](const ExPolygon &poly){ return poly.area() > threshold; }); + REQUIRE(missing_parts == 0); + } + } +} + /* -{ - my $collection = Slic3r::Polyline::Collection->new( - Slic3r::Polyline->new([0,15], [0,18], [0,20]), - Slic3r::Polyline->new([0,10], [0,8], [0,5]), - ); - is_deeply - [ map $_->[Y], map @$_, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ], - [20, 18, 15, 10, 8, 5], - 'chained path'; -} - -{ - my $collection = Slic3r::Polyline::Collection->new( - Slic3r::Polyline->new([4,0], [10,0], [15,0]), - Slic3r::Polyline->new([10,5], [15,5], [20,5]), - ); - is_deeply - [ map $_->[X], map @$_, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ], - [reverse 4, 10, 15, 10, 15, 20], - 'chained path'; -} - -{ - my $collection = Slic3r::ExtrusionPath::Collection->new( - map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), - Slic3r::Polyline->new([0,15], [0,18], [0,20]), - Slic3r::Polyline->new([0,10], [0,8], [0,5]), - ); - is_deeply - [ map $_->[Y], map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ], - [20, 18, 15, 10, 8, 5], - 'chained path'; -} - -{ - my $collection = Slic3r::ExtrusionPath::Collection->new( - map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), - Slic3r::Polyline->new([15,0], [10,0], [4,0]), - Slic3r::Polyline->new([10,5], [15,5], [20,5]), - ); - is_deeply - [ map $_->[X], map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ], - [reverse 4, 10, 15, 10, 15, 20], - 'chained path'; -} - -for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) { - my $config = Slic3r::Config->new_from_defaults; - $config->set('fill_pattern', $pattern); - $config->set('external_fill_pattern', $pattern); - $config->set('perimeters', 1); - $config->set('skirts', 0); - $config->set('fill_density', 20); - $config->set('layer_height', 0.05); - $config->set('perimeter_extruder', 1); - $config->set('infill_extruder', 2); - my $print = Slic3r::Test::init_print('20mm_cube', config => $config, scale => 2); - ok my $gcode = Slic3r::Test::gcode($print), "successful $pattern infill generation"; - my $tool = undef; - my @perimeter_points = my @infill_points = (); - Slic3r::GCode::Reader->new->parse($gcode, sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd =~ /^T(\d+)/) { - $tool = $1; - } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { - if ($tool == $config->perimeter_extruder-1) { - push @perimeter_points, Slic3r::Point->new_scale($args->{X}, $args->{Y}); - } elsif ($tool == $config->infill_extruder-1) { - push @infill_points, Slic3r::Point->new_scale($args->{X}, $args->{Y}); - } - } - }); - my $convex_hull = convex_hull(\@perimeter_points); - ok !(defined first { !$convex_hull->contains_point($_) } @infill_points), "infill does not exceed perimeters ($pattern)"; -} - -{ - my $config = Slic3r::Config->new_from_defaults; - $config->set('infill_only_where_needed', 1); - $config->set('bottom_solid_layers', 0); - $config->set('infill_extruder', 2); - $config->set('infill_extrusion_width', 0.5); - $config->set('fill_density', 40); - $config->set('cooling', 0); # for preventing speeds from being altered - $config->set('first_layer_speed', '100%'); # for preventing speeds from being altered - - my $test = sub { - my $print = Slic3r::Test::init_print('pyramid', config => $config); - - my $tool = undef; - my @infill_extrusions = (); # array of polylines - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd =~ /^T(\d+)/) { - $tool = $1; - } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { - if ($tool == $config->infill_extruder-1) { - push @infill_extrusions, Slic3r::Line->new_scale( - [ $self->X, $self->Y ], - [ $info->{new_X}, $info->{new_Y} ], - ); - } - } - }); - return 0 if !@infill_extrusions; # prevent calling convex_hull() with no points - - my $convex_hull = convex_hull([ map $_->pp, map @$_, @infill_extrusions ]); - return unscale unscale sum(map $_->area, @{offset([$convex_hull], scale(+$config->infill_extrusion_width/2))}); - }; - - my $tolerance = 5; # mm^2 - - $config->set('solid_infill_below_area', 0); - ok $test->() < $tolerance, - 'no infill is generated when using infill_only_where_needed on a pyramid'; - - $config->set('solid_infill_below_area', 70); - ok abs($test->() - $config->solid_infill_below_area) < $tolerance, - 'infill is only generated under the forced solid shells'; -} - -{ - my $config = Slic3r::Config->new_from_defaults; - $config->set('skirts', 0); - $config->set('perimeters', 1); - $config->set('fill_density', 0); - $config->set('top_solid_layers', 0); - $config->set('bottom_solid_layers', 0); - $config->set('solid_infill_below_area', 20000000); - $config->set('solid_infill_every_layers', 2); - $config->set('perimeter_speed', 99); - $config->set('external_perimeter_speed', 99); - $config->set('cooling', 0); - $config->set('first_layer_speed', '100%'); - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my %layers_with_extrusion = (); - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd eq 'G1' && $info->{dist_XY} > 0 && $info->{extruding}) { - if (($args->{F} // $self->F) != $config->perimeter_speed*60) { - $layers_with_extrusion{$self->Z} = ($args->{F} // $self->F); - } - } - }); - - ok !%layers_with_extrusion, - "solid_infill_below_area and solid_infill_every_layers are ignored when fill_density is 0"; -} - -{ - my $config = Slic3r::Config->new_from_defaults; - $config->set('skirts', 0); - $config->set('perimeters', 3); - $config->set('fill_density', 0); - $config->set('layer_height', 0.2); - $config->set('first_layer_height', 0.2); - $config->set('nozzle_diameter', [0.35]); - $config->set('infill_extruder', 2); - $config->set('solid_infill_extruder', 2); - $config->set('infill_extrusion_width', 0.52); - $config->set('solid_infill_extrusion_width', 0.52); - $config->set('first_layer_extrusion_width', 0); - - my $print = Slic3r::Test::init_print('A', config => $config); - my %infill = (); # Z => [ Line, Line ... ] - my $tool = undef; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd =~ /^T(\d+)/) { - $tool = $1; - } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { - if ($tool == $config->infill_extruder-1) { - my $z = 1 * $self->Z; - $infill{$z} ||= []; - push @{$infill{$z}}, Slic3r::Line->new_scale( - [ $self->X, $self->Y ], - [ $info->{new_X}, $info->{new_Y} ], - ); - } - } - }); - my $grow_d = scale($config->infill_extrusion_width)/2; - my $layer0_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.2} } ]); - my $layer1_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.4} } ]); - my $diff = diff($layer0_infill, $layer1_infill); - $diff = offset2_ex($diff, -$grow_d, +$grow_d); - $diff = [ grep { $_->area > 2*(($grow_d*2)**2) } @$diff ]; - is scalar(@$diff), 0, 'no missing parts in solid shell when fill_density is 0'; -} - { # GH: #2697 my $config = Slic3r::Config->new_from_defaults; @@ -427,6 +455,78 @@ for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) { my @holes = map @{$_->holes}, @$covered; ok sum(map unscale unscale $_->area*-1, @holes) < 1, 'no gaps between top solid infill and perimeters'; } + +{ + skip "The FillRectilinear2 does not fill the surface completely", 1; + + my $test = sub { + my ($expolygon, $flow_spacing, $angle, $density) = @_; + + my $filler = Slic3r::Filler->new_from_type('rectilinear'); + $filler->set_bounding_box($expolygon->bounding_box); + $filler->set_angle($angle // 0); + # Adjust line spacing to fill the region. + $filler->set_dont_adjust(0); + $filler->set_link_max_length(scale(1.2*$flow_spacing)); + my $surface = Slic3r::Surface->new( + surface_type => S_TYPE_BOTTOM, + expolygon => $expolygon, + ); + my $flow = Slic3r::Flow->new( + width => $flow_spacing, + height => 0.4, + nozzle_diameter => $flow_spacing, + ); + $filler->set_spacing($flow->spacing); + my $paths = $filler->fill_surface( + $surface, + layer_height => $flow->height, + density => $density // 1, + ); + + # check whether any part was left uncovered + my @grown_paths = map @{Slic3r::Polyline->new(@$_)->grow(scale $filler->spacing/2)}, @$paths; + my $uncovered = diff_ex([ @$expolygon ], [ @grown_paths ], 1); + + # ignore very small dots + my $uncovered_filtered = [ grep $_->area > (scale $flow_spacing)**2, @$uncovered ]; + + is scalar(@$uncovered_filtered), 0, 'solid surface is fully filled'; + + if (0 && @$uncovered_filtered) { + require "Slic3r/SVG.pm"; + Slic3r::SVG::output("uncovered.svg", + no_arrows => 1, + expolygons => [ $expolygon ], + blue_expolygons => [ @$uncovered ], + red_expolygons => [ @$uncovered_filtered ], + polylines => [ @$paths ], + ); + exit; + } + }; + + my $expolygon = Slic3r::ExPolygon->new([ + [6883102, 9598327.01296997], + [6883102, 20327272.01297], + [3116896, 20327272.01297], + [3116896, 9598327.01296997], + ]); + $test->($expolygon, 0.55); + + for (1..20) { + $expolygon->scale(1.05); + $test->($expolygon, 0.55); + } + + $expolygon = Slic3r::ExPolygon->new( + [[59515297,5422499],[59531249,5578697],[59695801,6123186],[59965713,6630228],[60328214,7070685],[60773285,7434379],[61274561,7702115],[61819378,7866770],[62390306,7924789],[62958700,7866744],[63503012,7702244],[64007365,7434357],[64449960,7070398],[64809327,6634999],[65082143,6123325],[65245005,5584454],[65266967,5422499],[66267307,5422499],[66269190,8310081],[66275379,17810072],[66277259,20697500],[65267237,20697500],[65245004,20533538],[65082082,19994444],[64811462,19488579],[64450624,19048208],[64012101,18686514],[63503122,18415781],[62959151,18251378],[62453416,18198442],[62390147,18197355],[62200087,18200576],[61813519,18252990],[61274433,18415918],[60768598,18686517],[60327567,19047892],[59963609,19493297],[59695865,19994587],[59531222,20539379],[59515153,20697500],[58502480,20697500],[58502480,5422499]] + ); + $test->($expolygon, 0.524341649025257); + + $expolygon = Slic3r::ExPolygon->new([ scale_points [0,0], [98,0], [98,10], [0,10] ]); + $test->($expolygon, 0.5, 45, 0.99); # non-solid infill +} */ bool test_if_solid_surface_filled(const ExPolygon& expolygon, double flow_spacing, double angle, double density) diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 06fc98322f..022ba2c013 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -53,11 +53,8 @@ set(XS_XSP_FILES ${XSP_DIR}/ExtrusionLoop.xsp ${XSP_DIR}/ExtrusionMultiPath.xsp ${XSP_DIR}/ExtrusionPath.xsp - ${XSP_DIR}/ExtrusionSimulator.xsp - ${XSP_DIR}/Filler.xsp ${XSP_DIR}/Flow.xsp ${XSP_DIR}/GCode.xsp - # ${XSP_DIR}/GCodeSender.xsp ${XSP_DIR}/Geometry.xsp ${XSP_DIR}/Layer.xsp ${XSP_DIR}/Line.xsp diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm index 6d3bf35cf2..83bc0ee70e 100644 --- a/xs/lib/Slic3r/XS.pm +++ b/xs/lib/Slic3r/XS.pm @@ -133,23 +133,6 @@ sub clone { ); } -package Slic3r::ExtrusionSimulator; - -sub new { - my ($class, %args) = @_; - return $class->_new(); -} - -package Slic3r::Filler; - -sub fill_surface { - my ($self, $surface, %args) = @_; - $self->set_density($args{density}) if defined($args{density}); - $self->set_dont_adjust($args{dont_adjust}) if defined($args{dont_adjust}); - $self->set_complete($args{complete}) if defined($args{complete}); - return $self->_fill_surface($surface); -} - package Slic3r::Flow; sub new { @@ -255,19 +238,12 @@ for my $class (qw( Slic3r::ExtrusionMultiPath Slic3r::ExtrusionPath Slic3r::ExtrusionPath::Collection - Slic3r::ExtrusionSimulator - Slic3r::Filler Slic3r::Flow Slic3r::GCode Slic3r::GCode::PlaceholderParser Slic3r::Geometry::BoundingBox Slic3r::Geometry::BoundingBoxf Slic3r::Geometry::BoundingBoxf3 - Slic3r::GUI::_3DScene::GLShader - Slic3r::GUI::_3DScene::GLVolume - Slic3r::GUI::Preset - Slic3r::GUI::PresetCollection - Slic3r::GUI::Tab Slic3r::Layer Slic3r::Layer::Region Slic3r::Layer::Support diff --git a/xs/main.xs.in b/xs/main.xs.in index c10f432d83..d8db108be5 100644 --- a/xs/main.xs.in +++ b/xs/main.xs.in @@ -2,7 +2,6 @@ #include #include #include -// #include #ifdef __cplusplus /* extern "C" { */ diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index 20288243e9..2d996120dd 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -9,8 +9,6 @@ REGISTER_CLASS(ExtrusionMultiPath, "ExtrusionMultiPath"); REGISTER_CLASS(ExtrusionPath, "ExtrusionPath"); REGISTER_CLASS(ExtrusionLoop, "ExtrusionLoop"); REGISTER_CLASS(ExtrusionEntityCollection, "ExtrusionPath::Collection"); -REGISTER_CLASS(ExtrusionSimulator, "ExtrusionSimulator"); -REGISTER_CLASS(Filler, "Filler"); REGISTER_CLASS(Flow, "Flow"); REGISTER_CLASS(CoolingBuffer, "GCode::CoolingBuffer"); REGISTER_CLASS(GCode, "GCode"); diff --git a/xs/xsp/ExtrusionSimulator.xsp b/xs/xsp/ExtrusionSimulator.xsp deleted file mode 100644 index 9395913b41..0000000000 --- a/xs/xsp/ExtrusionSimulator.xsp +++ /dev/null @@ -1,50 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/ExtrusionSimulator.hpp" -%} - -%name{Slic3r::ExtrusionSimulator} class ExtrusionSimulator { - ~ExtrusionSimulator(); - %name{_new} ExtrusionSimulator(); - - Clone clone() - %code{% RETVAL = THIS; %}; - - void set_image_size(Point *image_size) - %code{% THIS->set_image_size(*image_size); %}; - void set_viewport(BoundingBox *viewport) - %code{% THIS->set_viewport(*viewport); %}; - void set_bounding_box(BoundingBox *bbox) - %code{% THIS->set_bounding_box(*bbox); %}; - - void reset_accumulator(); - void extrude_to_accumulator(ExtrusionPath *path, Point *shift, ExtrusionSimulationType simulationType) - %code{% THIS->extrude_to_accumulator(*path, *shift, simulationType); %}; - void evaluate_accumulator(ExtrusionSimulationType simulationType); - void* image_ptr() - %code{% RETVAL = const_cast(const_cast(THIS)->image_ptr()); %}; - -%{ - -%} -}; - -%package{Slic3r::ExtrusionSimulator}; -%{ - -IV -_constant() - ALIAS: - EXTRSIM_SIMPLE = ExtrusionSimulationSimple - EXTRSIM_DONT_SPREAD = ExtrusionSimulationDontSpread - EXTRSIM_SPREAD_NFULL = ExtrisopmSimulationSpreadNotOverfilled - EXTRSIM_SPREAD_FULL = ExtrusionSimulationSpreadFull - EXTRSIM_SPREAD_EXCESS = ExtrusionSimulationSpreadExcess - PROTOTYPE: - CODE: - RETVAL = ix; - OUTPUT: RETVAL - -%} diff --git a/xs/xsp/Filler.xsp b/xs/xsp/Filler.xsp deleted file mode 100644 index 2ea2b34d5a..0000000000 --- a/xs/xsp/Filler.xsp +++ /dev/null @@ -1,65 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/Fill/Fill.hpp" -#include "libslic3r/ExtrusionEntity.hpp" -#include "libslic3r/ExtrusionEntityCollection.hpp" -%} - -%name{Slic3r::Filler} class Filler { - ~Filler(); - - void set_bounding_box(BoundingBox *bbox) - %code{% THIS->fill->set_bounding_box(*bbox); %}; - void set_spacing(coordf_t spacing) - %code{% THIS->fill->spacing = spacing; %}; - coordf_t spacing() - %code{% RETVAL = THIS->fill->spacing; %}; - void set_layer_id(size_t layer_id) - %code{% THIS->fill->layer_id = layer_id; %}; - void set_z(coordf_t z) - %code{% THIS->fill->z = z; %}; - void set_angle(float angle) - %code{% THIS->fill->angle = angle; %}; - void set_link_max_length(coordf_t len) - %code{% THIS->fill->link_max_length = len; %}; - void set_loop_clipping(coordf_t clipping) - %code{% THIS->fill->loop_clipping = clipping; %}; - - bool use_bridge_flow() - %code{% RETVAL = THIS->fill->use_bridge_flow(); %}; - bool no_sort() - %code{% RETVAL = THIS->fill->no_sort(); %}; - - void set_density(float density) - %code{% THIS->params.density = density; %}; - void set_dont_adjust(bool dont_adjust) - %code{% THIS->params.dont_adjust = dont_adjust; %}; - - PolylineCollection* _fill_surface(Surface *surface) - %code{% - PolylineCollection *pc = NULL; - if (THIS->fill != NULL) { - pc = new PolylineCollection(); - pc->polylines = THIS->fill->fill_surface(surface, THIS->params); - } - RETVAL = pc; - %}; - -%{ - -Filler* -new_from_type(CLASS, type) - char* CLASS; - std::string type; - CODE: - Filler *filler = new Filler(); - filler->fill = Fill::new_from_type(type); - RETVAL = filler; - OUTPUT: - RETVAL - -%} - -}; diff --git a/xs/xsp/GCodeSender.xsp b/xs/xsp/GCodeSender.xsp deleted file mode 100644 index f99244a1ff..0000000000 --- a/xs/xsp/GCodeSender.xsp +++ /dev/null @@ -1,24 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/GCodeSender.hpp" -%} - -%name{Slic3r::GCode::Sender} class GCodeSender { - GCodeSender(); - ~GCodeSender(); - - bool connect(std::string port, unsigned int baud_rate); - void disconnect(); - bool is_connected(); - bool wait_connected(unsigned int timeout = 3); - int queue_size(); - void send(std::string s, bool priority = false); - void pause_queue(); - void resume_queue(); - void purge_queue(bool priority = false); - std::vector purge_log(); - std::string getT(); - std::string getB(); -}; diff --git a/xs/xsp/my.map b/xs/xsp/my.map index ca26750dc5..a660041bc8 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -118,14 +118,6 @@ ExtrusionLoop* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T -ExtrusionSimulator* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -Filler* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - Flow* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index f9e61c6a03..cc71094459 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -58,9 +58,6 @@ %typemap{ExPolygonCollection*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; -%typemap{Filler*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; %typemap{Flow*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; @@ -88,9 +85,6 @@ %typemap{ExtrusionLoop*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; -%typemap{ExtrusionSimulator*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; %typemap{TriangleMesh*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; From 1bf4d61e36fe07015bfdceaaf03d4cf6cf5c2b80 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 2 May 2022 14:36:23 +0200 Subject: [PATCH 06/11] Fix for #8261 - view mode gets reset when exiting the configuration wizard early ConfigWizard::PageMode:: Code for initialization of values is moved to constructor from on_activate(). On Linux wxRadioButton values have to be set otherwise first value in group will be selected by default. --- src/slic3r/GUI/ConfigWizard.cpp | 25 +++++++++---------------- src/slic3r/GUI/ConfigWizard_private.hpp | 2 -- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 4ff0898820..86768dfc56 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -1281,17 +1281,6 @@ PageMode::PageMode(ConfigWizard *parent) radio_advanced = new wxRadioButton(this, wxID_ANY, _L("Advanced mode")); radio_expert = new wxRadioButton(this, wxID_ANY, _L("Expert mode")); - append(radio_simple); - append(radio_advanced); - append(radio_expert); - - append_text("\n" + _L("The size of the object can be specified in inches")); - check_inch = new wxCheckBox(this, wxID_ANY, _L("Use inches")); - append(check_inch); -} - -void PageMode::on_activate() -{ std::string mode { "simple" }; wxGetApp().app_config->get("", "view_mode", mode); @@ -1299,7 +1288,16 @@ void PageMode::on_activate() else if (mode == "expert") { radio_expert->SetValue(true); } else { radio_simple->SetValue(true); } + append(radio_simple); + append(radio_advanced); + append(radio_expert); + + append_text("\n" + _L("The size of the object can be specified in inches")); + check_inch = new wxCheckBox(this, wxID_ANY, _L("Use inches")); check_inch->SetValue(wxGetApp().app_config->get("use_inches") == "1"); + append(check_inch); + + on_activate(); } void PageMode::serialize_mode(AppConfig *app_config) const @@ -1310,11 +1308,6 @@ void PageMode::serialize_mode(AppConfig *app_config) const if (radio_advanced->GetValue()) { mode = "advanced"; } if (radio_expert->GetValue()) { mode = "expert"; } - // If "Mode" page wasn't selected (no one radiobutton is checked), - // we shouldn't to update a view_mode value in app_config - if (mode.empty()) - return; - app_config->set("view_mode", mode); app_config->set("use_inches", check_inch->GetValue() ? "1" : "0"); } diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp index c822a2be81..4de8381ffe 100644 --- a/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/src/slic3r/GUI/ConfigWizard_private.hpp @@ -426,8 +426,6 @@ struct PageMode: ConfigWizardPage PageMode(ConfigWizard *parent); void serialize_mode(AppConfig *app_config) const; - - virtual void on_activate(); }; struct PageVendors: ConfigWizardPage From 881a5dbf3725e0c9b4467cadc9c21815aec99623 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 2 May 2022 15:10:24 +0200 Subject: [PATCH 07/11] Fix for #8228 - Model errors icon does not go away after proper model reloading --- src/slic3r/GUI/Plater.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index d542c18e7f..0456936e44 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3854,6 +3854,9 @@ void Plater::priv::reload_from_disk() old_model_object->sort_volumes(wxGetApp().app_config->get("order_volumes") == "1"); sla::reproject_points_and_holes(old_model_object); + + // Fix warning icon in object list + wxGetApp().obj_list()->update_item_error_icon(obj_idx, vol_idx); } } #else From c4b6381acfcde002a6fc0cc7ecc3578fcfecaebe Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 3 May 2022 09:13:53 +0200 Subject: [PATCH 08/11] Fix for #8254 - Change of Print/Filament/Printer Settings not detected --- src/slic3r/GUI/MainFrame.cpp | 11 ++++++++++- src/slic3r/GUI/PrintHostDialogs.cpp | 7 +++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 570877716f..cde7a05a22 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -706,6 +706,10 @@ void MainFrame::init_tabpanel() // before the MainFrame is fully set up. tab->OnActivate(); m_last_selected_tab = m_tabpanel->GetSelection(); +#ifdef _MSW_DARK_MODE + if (wxGetApp().tabs_as_menu()) + tab->SetFocus(); +#endif } else select_tab(size_t(0)); // select Plater @@ -2018,8 +2022,13 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) m_plater->SetFocus(); Layout(); } - else + else { select(false); +#ifdef _MSW_DARK_MODE + if (wxGetApp().tabs_as_menu() && tab == 0) + m_plater->SetFocus(); +#endif + } // When we run application in ESettingsLayout::New or ESettingsLayout::Dlg mode, tabpanel is hidden from the very beginning // and as a result Tab::update_changed_tree_ui() function couldn't update m_is_nonsys_values values, diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp index b53fe5c474..a09d158821 100644 --- a/src/slic3r/GUI/PrintHostDialogs.cpp +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -490,8 +490,11 @@ bool PrintHostQueueDialog::load_user_data(int udt, std::vector& vector) auto* app_config = wxGetApp().app_config; auto hasget = [app_config](const std::string& name, std::vector& vector)->bool { if (app_config->has(name)) { - vector.push_back(std::stoi(app_config->get(name))); - return true; + std::string val = app_config->get(name); + if (!val.empty() || val[0]!='\0') { + vector.push_back(std::stoi(val)); + return true; + } } return false; }; From 1805f3c370db81444eec8ccce1c5162c73bcecf6 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 3 May 2022 09:19:01 +0200 Subject: [PATCH 09/11] Tech ENABLE_NEW_RECTANGLE_SELECTION - Fixed object selection while a gizmo is open --- src/slic3r/GUI/GLCanvas3D.cpp | 15298 ++++++++++++++++---------------- 1 file changed, 7649 insertions(+), 7649 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 14dfe32205..8a8ccfe1c1 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1,7649 +1,7649 @@ -#include "libslic3r/libslic3r.h" -#include "GLCanvas3D.hpp" - -#include - -#include "libslic3r/BuildVolume.hpp" -#include "libslic3r/ClipperUtils.hpp" -#include "libslic3r/PrintConfig.hpp" -#include "libslic3r/GCode/ThumbnailData.hpp" -#include "libslic3r/Geometry/ConvexHull.hpp" -#include "libslic3r/ExtrusionEntity.hpp" -#include "libslic3r/Layer.hpp" -#include "libslic3r/Utils.hpp" -#include "libslic3r/Technologies.hpp" -#include "libslic3r/Tesselate.hpp" -#include "libslic3r/PresetBundle.hpp" -#include "3DBed.hpp" -#include "3DScene.hpp" -#include "BackgroundSlicingProcess.hpp" -#include "GLShader.hpp" -#include "GUI.hpp" -#include "Tab.hpp" -#include "GUI_Preview.hpp" -#include "OpenGLManager.hpp" -#include "Plater.hpp" -#include "MainFrame.hpp" -#include "GUI_App.hpp" -#include "GUI_ObjectList.hpp" -#include "GUI_ObjectManipulation.hpp" -#include "Mouse3DController.hpp" -#include "I18N.hpp" -#include "NotificationManager.hpp" -#include "format.hpp" - -#include "slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp" -#include "slic3r/Utils/UndoRedo.hpp" - -#if ENABLE_RETINA_GL -#include "slic3r/Utils/RetinaHelper.hpp" -#endif - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -// Print now includes tbb, and tbb includes Windows. This breaks compilation of wxWidgets if included before wx. -#include "libslic3r/Print.hpp" -#include "libslic3r/SLAPrint.hpp" - -#include "wxExtensions.hpp" - -#include -#include - -#include -#include - -#include -#include -#include -#include -#include "DoubleSlider.hpp" - -#include - -static constexpr const float TRACKBALLSIZE = 0.8f; - -#if ENABLE_LEGACY_OPENGL_REMOVAL -static const Slic3r::ColorRGBA DEFAULT_BG_DARK_COLOR = { 0.478f, 0.478f, 0.478f, 1.0f }; -static const Slic3r::ColorRGBA DEFAULT_BG_LIGHT_COLOR = { 0.753f, 0.753f, 0.753f, 1.0f }; -static const Slic3r::ColorRGBA ERROR_BG_DARK_COLOR = { 0.478f, 0.192f, 0.039f, 1.0f }; -static const Slic3r::ColorRGBA ERROR_BG_LIGHT_COLOR = { 0.753f, 0.192f, 0.039f, 1.0f }; -#else -static const Slic3r::ColorRGB DEFAULT_BG_DARK_COLOR = { 0.478f, 0.478f, 0.478f }; -static const Slic3r::ColorRGB DEFAULT_BG_LIGHT_COLOR = { 0.753f, 0.753f, 0.753f }; -static const Slic3r::ColorRGB ERROR_BG_DARK_COLOR = { 0.478f, 0.192f, 0.039f }; -static const Slic3r::ColorRGB ERROR_BG_LIGHT_COLOR = { 0.753f, 0.192f, 0.039f }; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - -// Number of floats -static constexpr const size_t MAX_VERTEX_BUFFER_SIZE = 131072 * 6; // 3.15MB -// Reserve size in number of floats. -#if !ENABLE_LEGACY_OPENGL_REMOVAL -static constexpr const size_t VERTEX_BUFFER_RESERVE_SIZE = 131072 * 2; // 1.05MB -// Reserve size in number of floats, maximum sum of all preallocated buffers. -//static constexpr const size_t VERTEX_BUFFER_RESERVE_SIZE_SUM_MAX = 1024 * 1024 * 128 / 4; // 128MB -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - -namespace Slic3r { -namespace GUI { - -#ifdef __WXGTK3__ -// wxGTK3 seems to simulate OSX behavior in regard to HiDPI scaling support. -RetinaHelper::RetinaHelper(wxWindow* window) : m_window(window), m_self(nullptr) {} -RetinaHelper::~RetinaHelper() {} -float RetinaHelper::get_scale_factor() { return float(m_window->GetContentScaleFactor()); } -#endif // __WXGTK3__ - -// Fixed the collision between BuildVolume::Type::Convex and macro Convex defined inside /usr/include/X11/X.h that is included by WxWidgets 3.0. -#if defined(__linux__) && defined(Convex) -#undef Convex -#endif - -GLCanvas3D::LayersEditing::~LayersEditing() -{ - if (m_z_texture_id != 0) { - glsafe(::glDeleteTextures(1, &m_z_texture_id)); - m_z_texture_id = 0; - } - delete m_slicing_parameters; -} - -const float GLCanvas3D::LayersEditing::THICKNESS_BAR_WIDTH = 70.0f; - -void GLCanvas3D::LayersEditing::init() -{ - glsafe(::glGenTextures(1, (GLuint*)&m_z_texture_id)); - glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1)); - glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); -} - -void GLCanvas3D::LayersEditing::set_config(const DynamicPrintConfig* config) -{ - m_config = config; - delete m_slicing_parameters; - m_slicing_parameters = nullptr; - m_layers_texture.valid = false; -} - -void GLCanvas3D::LayersEditing::select_object(const Model &model, int object_id) -{ - const ModelObject *model_object_new = (object_id >= 0) ? model.objects[object_id] : nullptr; - // Maximum height of an object changes when the object gets rotated or scaled. - // Changing maximum height of an object will invalidate the layer heigth editing profile. - // m_model_object->bounding_box() is cached, therefore it is cheap even if this method is called frequently. - const float new_max_z = (model_object_new == nullptr) ? 0.0f : static_cast(model_object_new->bounding_box().max.z()); - if (m_model_object != model_object_new || this->last_object_id != object_id || m_object_max_z != new_max_z || - (model_object_new != nullptr && m_model_object->id() != model_object_new->id())) { - m_layer_height_profile.clear(); - m_layer_height_profile_modified = false; - delete m_slicing_parameters; - m_slicing_parameters = nullptr; - m_layers_texture.valid = false; - this->last_object_id = object_id; - m_model_object = model_object_new; - m_object_max_z = new_max_z; - } -} - -bool GLCanvas3D::LayersEditing::is_allowed() const -{ - return wxGetApp().get_shader("variable_layer_height") != nullptr && m_z_texture_id > 0; -} - -bool GLCanvas3D::LayersEditing::is_enabled() const -{ - return m_enabled; -} - -void GLCanvas3D::LayersEditing::set_enabled(bool enabled) -{ - m_enabled = is_allowed() && enabled; -} - -float GLCanvas3D::LayersEditing::s_overlay_window_width; - -void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) -{ - if (!m_enabled) - return; - - const Size& cnv_size = canvas.get_canvas_size(); - - ImGuiWrapper& imgui = *wxGetApp().imgui(); - imgui.set_next_window_pos(static_cast(cnv_size.get_width()) - imgui.get_style_scaling() * THICKNESS_BAR_WIDTH, - static_cast(cnv_size.get_height()), ImGuiCond_Always, 1.0f, 1.0f); - - imgui.begin(_L("Variable layer height"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Left mouse button:")); - ImGui::SameLine(); - imgui.text(_L("Add detail")); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Right mouse button:")); - ImGui::SameLine(); - imgui.text(_L("Remove detail")); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Shift + Left mouse button:")); - ImGui::SameLine(); - imgui.text(_L("Reset to base")); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Shift + Right mouse button:")); - ImGui::SameLine(); - imgui.text(_L("Smoothing")); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Mouse wheel:")); - ImGui::SameLine(); - imgui.text(_L("Increase/decrease edit area")); - - ImGui::Separator(); - if (imgui.button(_L("Adaptive"))) - wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), Event(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, m_adaptive_quality)); - - ImGui::SameLine(); - float text_align = ImGui::GetCursorPosX(); - ImGui::AlignTextToFramePadding(); - imgui.text(_L("Quality / Speed")); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::TextUnformatted(_L("Higher print quality versus higher print speed.").ToUTF8()); - ImGui::EndTooltip(); - } - - ImGui::SameLine(); - float widget_align = ImGui::GetCursorPosX(); - ImGui::PushItemWidth(imgui.get_style_scaling() * 120.0f); - m_adaptive_quality = std::clamp(m_adaptive_quality, 0.0f, 1.f); - imgui.slider_float("", &m_adaptive_quality, 0.0f, 1.f, "%.2f"); - - ImGui::Separator(); - if (imgui.button(_L("Smooth"))) - wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), HeightProfileSmoothEvent(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, m_smooth_params)); - - ImGui::SameLine(); - ImGui::SetCursorPosX(text_align); - ImGui::AlignTextToFramePadding(); - imgui.text(_L("Radius")); - ImGui::SameLine(); - ImGui::SetCursorPosX(widget_align); - ImGui::PushItemWidth(imgui.get_style_scaling() * 120.0f); - int radius = (int)m_smooth_params.radius; - if (ImGui::SliderInt("##1", &radius, 1, 10)) { - radius = std::clamp(radius, 1, 10); - m_smooth_params.radius = (unsigned int)radius; - } - - ImGui::SetCursorPosX(text_align); - ImGui::AlignTextToFramePadding(); - imgui.text(_L("Keep min")); - ImGui::SameLine(); - if (ImGui::GetCursorPosX() < widget_align) // because of line lenght after localization - ImGui::SetCursorPosX(widget_align); - - ImGui::PushItemWidth(imgui.get_style_scaling() * 120.0f); - imgui.checkbox("##2", m_smooth_params.keep_min); - - ImGui::Separator(); - if (imgui.button(_L("Reset"))) - wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), SimpleEvent(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE)); - - GLCanvas3D::LayersEditing::s_overlay_window_width = ImGui::GetWindowSize().x /*+ (float)m_layers_texture.width/4*/; - imgui.end(); - -#if ENABLE_GL_SHADERS_ATTRIBUTES - render_active_object_annotations(canvas); - render_profile(canvas); -#else - const Rect& bar_rect = get_bar_rect_viewport(canvas); -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_profile.dirty = m_profile.old_bar_rect != bar_rect; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - render_active_object_annotations(canvas, bar_rect); - render_profile(bar_rect); -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_profile.old_bar_rect = bar_rect; - m_profile.dirty = false; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -#endif // ENABLE_GL_SHADERS_ATTRIBUTES -} - -float GLCanvas3D::LayersEditing::get_cursor_z_relative(const GLCanvas3D& canvas) -{ - const Vec2d mouse_pos = canvas.get_local_mouse_position(); - const Rect& rect = get_bar_rect_screen(canvas); - float x = (float)mouse_pos.x(); - float y = (float)mouse_pos.y(); - float t = rect.get_top(); - float b = rect.get_bottom(); - - return (rect.get_left() <= x && x <= rect.get_right() && t <= y && y <= b) ? - // Inside the bar. - (b - y - 1.0f) / (b - t - 1.0f) : - // Outside the bar. - -1000.0f; -} - -bool GLCanvas3D::LayersEditing::bar_rect_contains(const GLCanvas3D& canvas, float x, float y) -{ - const Rect& rect = get_bar_rect_screen(canvas); - return rect.get_left() <= x && x <= rect.get_right() && rect.get_top() <= y && y <= rect.get_bottom(); -} - -Rect GLCanvas3D::LayersEditing::get_bar_rect_screen(const GLCanvas3D& canvas) -{ - const Size& cnv_size = canvas.get_canvas_size(); - float w = (float)cnv_size.get_width(); - float h = (float)cnv_size.get_height(); - - return { w - thickness_bar_width(canvas), 0.0f, w, h }; -} - -#if !ENABLE_GL_SHADERS_ATTRIBUTES -Rect GLCanvas3D::LayersEditing::get_bar_rect_viewport(const GLCanvas3D& canvas) -{ - const Size& cnv_size = canvas.get_canvas_size(); - float half_w = 0.5f * (float)cnv_size.get_width(); - float half_h = 0.5f * (float)cnv_size.get_height(); - float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); - return { (half_w - thickness_bar_width(canvas)) * inv_zoom, half_h * inv_zoom, half_w * inv_zoom, -half_h * inv_zoom }; -} -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - -bool GLCanvas3D::LayersEditing::is_initialized() const -{ - return wxGetApp().get_shader("variable_layer_height") != nullptr; -} - -std::string GLCanvas3D::LayersEditing::get_tooltip(const GLCanvas3D& canvas) const -{ - std::string ret; - if (m_enabled && m_layer_height_profile.size() >= 4) { - float z = get_cursor_z_relative(canvas); - if (z != -1000.0f) { - z *= m_object_max_z; - - float h = 0.0f; - for (size_t i = m_layer_height_profile.size() - 2; i >= 2; i -= 2) { - const float zi = static_cast(m_layer_height_profile[i]); - const float zi_1 = static_cast(m_layer_height_profile[i - 2]); - if (zi_1 <= z && z <= zi) { - float dz = zi - zi_1; - h = (dz != 0.0f) ? static_cast(lerp(m_layer_height_profile[i - 1], m_layer_height_profile[i + 1], (z - zi_1) / dz)) : - static_cast(m_layer_height_profile[i + 1]); - break; - } - } - if (h > 0.0f) - ret = std::to_string(h); - } - } - return ret; -} - -#if ENABLE_GL_SHADERS_ATTRIBUTES -void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3D& canvas) -#else -void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3D& canvas, const Rect& bar_rect) -#endif // ENABLE_GL_SHADERS_ATTRIBUTES -{ -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Size cnv_size = canvas.get_canvas_size(); - const float cnv_width = (float)cnv_size.get_width(); - const float cnv_height = (float)cnv_size.get_height(); - if (cnv_width == 0.0f || cnv_height == 0.0f) - return; - - const float cnv_inv_width = 1.0f / cnv_width; -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - GLShaderProgram* shader = wxGetApp().get_shader("variable_layer_height"); - if (shader == nullptr) - return; - - shader->start_using(); - - shader->set_uniform("z_to_texture_row", float(m_layers_texture.cells - 1) / (float(m_layers_texture.width) * m_object_max_z)); - shader->set_uniform("z_texture_row_to_normalized", 1.0f / (float)m_layers_texture.height); - shader->set_uniform("z_cursor", m_object_max_z * this->get_cursor_z_relative(canvas)); - shader->set_uniform("z_cursor_band_width", band_width); - shader->set_uniform("object_max_z", m_object_max_z); -#if ENABLE_GL_SHADERS_ATTRIBUTES - shader->set_uniform("view_model_matrix", Transform3d::Identity()); - shader->set_uniform("projection_matrix", Transform3d::Identity()); - shader->set_uniform("normal_matrix", (Matrix3d)Eigen::Matrix3d::Identity()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); - glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id)); - - // Render the color bar -#if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_GL_SHADERS_ATTRIBUTES - if (!m_profile.background.is_initialized() || m_profile.old_canvas_width != cnv_width) { - m_profile.old_canvas_width = cnv_width; -#else - if (!m_profile.background.is_initialized() || m_profile.dirty) { -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - m_profile.background.reset(); - - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P2T2 }; - init_data.reserve_vertices(4); - init_data.reserve_indices(6); - - // vertices -#if ENABLE_GL_SHADERS_ATTRIBUTES - const float l = 1.0f - 2.0f * THICKNESS_BAR_WIDTH * cnv_inv_width; - const float r = 1.0f; - const float t = 1.0f; - const float b = -1.0f; -#else - const float l = bar_rect.get_left(); - const float r = bar_rect.get_right(); - const float t = bar_rect.get_top(); - const float b = bar_rect.get_bottom(); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - init_data.add_vertex(Vec2f(l, b), Vec2f(0.0f, 0.0f)); - init_data.add_vertex(Vec2f(r, b), Vec2f(1.0f, 0.0f)); - init_data.add_vertex(Vec2f(r, t), Vec2f(1.0f, 1.0f)); - init_data.add_vertex(Vec2f(l, t), Vec2f(0.0f, 1.0f)); - - // indices - init_data.add_triangle(0, 1, 2); - init_data.add_triangle(2, 3, 0); - - m_profile.background.init_from(std::move(init_data)); - } - - m_profile.background.render(); -#else - const float l = bar_rect.get_left(); - const float r = bar_rect.get_right(); - const float t = bar_rect.get_top(); - const float b = bar_rect.get_bottom(); - - ::glBegin(GL_QUADS); - ::glNormal3f(0.0f, 0.0f, 1.0f); - ::glTexCoord2f(0.0f, 0.0f); ::glVertex2f(l, b); - ::glTexCoord2f(1.0f, 0.0f); ::glVertex2f(r, b); - ::glTexCoord2f(1.0f, 1.0f); ::glVertex2f(r, t); - ::glTexCoord2f(0.0f, 1.0f); ::glVertex2f(l, t); - glsafe(::glEnd()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); - - shader->stop_using(); -} - -#if ENABLE_GL_SHADERS_ATTRIBUTES -void GLCanvas3D::LayersEditing::render_profile(const GLCanvas3D& canvas) -#else -void GLCanvas3D::LayersEditing::render_profile(const Rect& bar_rect) -#endif // ENABLE_GL_SHADERS_ATTRIBUTES -{ - //FIXME show some kind of legend. - - if (!m_slicing_parameters) - return; - -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Size cnv_size = canvas.get_canvas_size(); - const float cnv_width = (float)cnv_size.get_width(); - const float cnv_height = (float)cnv_size.get_height(); - if (cnv_width == 0.0f || cnv_height == 0.0f) - return; - - // Make the vertical bar a bit wider so the layer height curve does not touch the edge of the bar region. - const float scale_x = THICKNESS_BAR_WIDTH / float(1.12 * m_slicing_parameters->max_layer_height); - const float scale_y = cnv_height / m_object_max_z; - - const float cnv_inv_width = 1.0f / cnv_width; - const float cnv_inv_height = 1.0f / cnv_height; -#else - // Make the vertical bar a bit wider so the layer height curve does not touch the edge of the bar region. - const float scale_x = bar_rect.get_width() / float(1.12 * m_slicing_parameters->max_layer_height); - const float scale_y = bar_rect.get_height() / m_object_max_z; -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - -#if ENABLE_LEGACY_OPENGL_REMOVAL - // Baseline -#if ENABLE_GL_SHADERS_ATTRIBUTES - if (!m_profile.baseline.is_initialized() || m_profile.old_layer_height_profile != m_layer_height_profile) { -#else - if (!m_profile.baseline.is_initialized() || m_profile.dirty) { -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - m_profile.baseline.reset(); - - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P2 }; - init_data.color = ColorRGBA::BLACK(); - init_data.reserve_vertices(2); - init_data.reserve_indices(2); - - // vertices -#if ENABLE_GL_SHADERS_ATTRIBUTES - const float axis_x = 2.0f * ((cnv_width - THICKNESS_BAR_WIDTH + float(m_slicing_parameters->layer_height) * scale_x) * cnv_inv_width - 0.5f); - init_data.add_vertex(Vec2f(axis_x, -1.0f)); - init_data.add_vertex(Vec2f(axis_x, 1.0f)); -#else - const float x = bar_rect.get_left() + float(m_slicing_parameters->layer_height) * scale_x; - init_data.add_vertex(Vec2f(x, bar_rect.get_bottom())); - init_data.add_vertex(Vec2f(x, bar_rect.get_top())); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - // indices - init_data.add_line(0, 1); - - m_profile.baseline.init_from(std::move(init_data)); - } - -#if ENABLE_GL_SHADERS_ATTRIBUTES - if (!m_profile.profile.is_initialized() || m_profile.old_layer_height_profile != m_layer_height_profile) { -#else - if (!m_profile.profile.is_initialized() || m_profile.dirty || m_profile.old_layer_height_profile != m_layer_height_profile) { -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - m_profile.old_layer_height_profile = m_layer_height_profile; - m_profile.profile.reset(); - - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::LineStrip, GLModel::Geometry::EVertexLayout::P2 }; - init_data.color = ColorRGBA::BLUE(); - init_data.reserve_vertices(m_layer_height_profile.size() / 2); - init_data.reserve_indices(m_layer_height_profile.size() / 2); - - // vertices + indices - for (unsigned int i = 0; i < (unsigned int)m_layer_height_profile.size(); i += 2) { -#if ENABLE_GL_SHADERS_ATTRIBUTES - init_data.add_vertex(Vec2f(2.0f * ((cnv_width - THICKNESS_BAR_WIDTH + float(m_layer_height_profile[i + 1]) * scale_x) * cnv_inv_width - 0.5f), - 2.0f * (float(m_layer_height_profile[i]) * scale_y * cnv_inv_height - 0.5))); -#else - init_data.add_vertex(Vec2f(bar_rect.get_left() + float(m_layer_height_profile[i + 1]) * scale_x, - bar_rect.get_bottom() + float(m_layer_height_profile[i]) * scale_y)); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - init_data.add_index(i / 2); - } - - m_profile.profile.init_from(std::move(init_data)); - } - - GLShaderProgram* shader = wxGetApp().get_shader("flat"); - if (shader != nullptr) { - shader->start_using(); -#if ENABLE_GL_SHADERS_ATTRIBUTES - shader->set_uniform("view_model_matrix", Transform3d::Identity()); - shader->set_uniform("projection_matrix", Transform3d::Identity()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - m_profile.baseline.render(); - m_profile.profile.render(); - shader->stop_using(); - } -#else - const float x = bar_rect.get_left() + float(m_slicing_parameters->layer_height) * scale_x; - - // Baseline - glsafe(::glColor3f(0.0f, 0.0f, 0.0f)); - ::glBegin(GL_LINE_STRIP); - ::glVertex2f(x, bar_rect.get_bottom()); - ::glVertex2f(x, bar_rect.get_top()); - glsafe(::glEnd()); - - // Curve - glsafe(::glColor3f(0.0f, 0.0f, 1.0f)); - ::glBegin(GL_LINE_STRIP); - for (unsigned int i = 0; i < m_layer_height_profile.size(); i += 2) - ::glVertex2f(bar_rect.get_left() + (float)m_layer_height_profile[i + 1] * scale_x, bar_rect.get_bottom() + (float)m_layer_height_profile[i] * scale_y); - glsafe(::glEnd()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} - -void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const GLVolumeCollection& volumes) -{ - assert(this->is_allowed()); - assert(this->last_object_id != -1); - - GLShaderProgram* current_shader = wxGetApp().get_current_shader(); - ScopeGuard guard([current_shader]() { if (current_shader != nullptr) current_shader->start_using(); }); - if (current_shader != nullptr) - current_shader->stop_using(); - - GLShaderProgram* shader = wxGetApp().get_shader("variable_layer_height"); - if (shader == nullptr) - return; - - shader->start_using(); - - generate_layer_height_texture(); - - // Uniforms were resolved, go ahead using the layer editing shader. - shader->set_uniform("z_to_texture_row", float(m_layers_texture.cells - 1) / (float(m_layers_texture.width) * float(m_object_max_z))); - shader->set_uniform("z_texture_row_to_normalized", 1.0f / float(m_layers_texture.height)); - shader->set_uniform("z_cursor", float(m_object_max_z) * float(this->get_cursor_z_relative(canvas))); - shader->set_uniform("z_cursor_band_width", float(this->band_width)); - -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - // Initialize the layer height texture mapping. - const GLsizei w = (GLsizei)m_layers_texture.width; - const GLsizei h = (GLsizei)m_layers_texture.height; - const GLsizei half_w = w / 2; - const GLsizei half_h = h / 2; - glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); - glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id)); - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); - glsafe(::glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, half_w, half_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); - glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data())); - glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, half_w, half_h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data() + m_layers_texture.width * m_layers_texture.height * 4)); - for (GLVolume* glvolume : volumes.volumes) { - // Render the object using the layer editing shader and texture. - if (!glvolume->is_active || glvolume->composite_id.object_id != this->last_object_id || glvolume->is_modifier) - continue; - - shader->set_uniform("volume_world_matrix", glvolume->world_matrix()); - shader->set_uniform("object_max_z", 0.0f); -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Transform3d view_model_matrix = camera.get_view_matrix() * glvolume->world_matrix(); - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - glvolume->render(); - } - // Revert back to the previous shader. - glBindTexture(GL_TEXTURE_2D, 0); -} - -void GLCanvas3D::LayersEditing::adjust_layer_height_profile() -{ - this->update_slicing_parameters(); - PrintObject::update_layer_height_profile(*m_model_object, *m_slicing_parameters, m_layer_height_profile); - Slic3r::adjust_layer_height_profile(*m_slicing_parameters, m_layer_height_profile, this->last_z, this->strength, this->band_width, this->last_action); - m_layer_height_profile_modified = true; - m_layers_texture.valid = false; -} - -void GLCanvas3D::LayersEditing::reset_layer_height_profile(GLCanvas3D& canvas) -{ - const_cast(m_model_object)->layer_height_profile.clear(); - m_layer_height_profile.clear(); - m_layers_texture.valid = false; - canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); - wxGetApp().obj_list()->update_info_items(last_object_id); -} - -void GLCanvas3D::LayersEditing::adaptive_layer_height_profile(GLCanvas3D& canvas, float quality_factor) -{ - this->update_slicing_parameters(); - m_layer_height_profile = layer_height_profile_adaptive(*m_slicing_parameters, *m_model_object, quality_factor); - const_cast(m_model_object)->layer_height_profile.set(m_layer_height_profile); - m_layers_texture.valid = false; - canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); - wxGetApp().obj_list()->update_info_items(last_object_id); -} - -void GLCanvas3D::LayersEditing::smooth_layer_height_profile(GLCanvas3D& canvas, const HeightProfileSmoothingParams& smoothing_params) -{ - this->update_slicing_parameters(); - m_layer_height_profile = smooth_height_profile(m_layer_height_profile, *m_slicing_parameters, smoothing_params); - const_cast(m_model_object)->layer_height_profile.set(m_layer_height_profile); - m_layers_texture.valid = false; - canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); - wxGetApp().obj_list()->update_info_items(last_object_id); -} - -void GLCanvas3D::LayersEditing::generate_layer_height_texture() -{ - this->update_slicing_parameters(); - // Always try to update the layer height profile. - bool update = ! m_layers_texture.valid; - if (PrintObject::update_layer_height_profile(*m_model_object, *m_slicing_parameters, m_layer_height_profile)) { - // Initialized to the default value. - m_layer_height_profile_modified = false; - update = true; - } - // Update if the layer height profile was changed, or when the texture is not valid. - if (! update && ! m_layers_texture.data.empty() && m_layers_texture.cells > 0) - // Texture is valid, don't update. - return; - - if (m_layers_texture.data.empty()) { - m_layers_texture.width = 1024; - m_layers_texture.height = 1024; - m_layers_texture.levels = 2; - m_layers_texture.data.assign(m_layers_texture.width * m_layers_texture.height * 5, 0); - } - - bool level_of_detail_2nd_level = true; - m_layers_texture.cells = Slic3r::generate_layer_height_texture( - *m_slicing_parameters, - Slic3r::generate_object_layers(*m_slicing_parameters, m_layer_height_profile), - m_layers_texture.data.data(), m_layers_texture.height, m_layers_texture.width, level_of_detail_2nd_level); - m_layers_texture.valid = true; -} - -void GLCanvas3D::LayersEditing::accept_changes(GLCanvas3D& canvas) -{ - if (last_object_id >= 0) { - if (m_layer_height_profile_modified) { - wxGetApp().plater()->take_snapshot(_L("Variable layer height - Manual edit")); - const_cast(m_model_object)->layer_height_profile.set(m_layer_height_profile); - canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); - wxGetApp().obj_list()->update_info_items(last_object_id); - } - } - m_layer_height_profile_modified = false; -} - -void GLCanvas3D::LayersEditing::update_slicing_parameters() -{ - if (m_slicing_parameters == nullptr) { - m_slicing_parameters = new SlicingParameters(); - *m_slicing_parameters = PrintObject::slicing_parameters(*m_config, *m_model_object, m_object_max_z); - } -} - -float GLCanvas3D::LayersEditing::thickness_bar_width(const GLCanvas3D &canvas) -{ - return -#if ENABLE_RETINA_GL - canvas.get_canvas_size().get_scale_factor() -#else - canvas.get_wxglcanvas()->GetContentScaleFactor() -#endif - * THICKNESS_BAR_WIDTH; -} - - -const Point GLCanvas3D::Mouse::Drag::Invalid_2D_Point(INT_MAX, INT_MAX); -const Vec3d GLCanvas3D::Mouse::Drag::Invalid_3D_Point(DBL_MAX, DBL_MAX, DBL_MAX); -const int GLCanvas3D::Mouse::Drag::MoveThresholdPx = 5; - -GLCanvas3D::Mouse::Drag::Drag() - : start_position_2D(Invalid_2D_Point) - , start_position_3D(Invalid_3D_Point) - , move_volume_idx(-1) - , move_requires_threshold(false) - , move_start_threshold_position_2D(Invalid_2D_Point) -{ -} - -GLCanvas3D::Mouse::Mouse() - : dragging(false) - , position(DBL_MAX, DBL_MAX) - , scene_position(DBL_MAX, DBL_MAX, DBL_MAX) - , ignore_left_up(false) -{ -} - -void GLCanvas3D::Labels::render(const std::vector& sorted_instances) const -{ - if (!m_enabled || !is_shown()) - return; - - const Camera& camera = wxGetApp().plater()->get_camera(); - const Model* model = m_canvas.get_model(); - if (model == nullptr) - return; - - Transform3d world_to_eye = camera.get_view_matrix(); - Transform3d world_to_screen = camera.get_projection_matrix() * world_to_eye; - const std::array& viewport = camera.get_viewport(); - - struct Owner - { - int obj_idx; - int inst_idx; - size_t model_instance_id; - BoundingBoxf3 world_box; - double eye_center_z; - std::string title; - std::string label; - std::string print_order; - bool selected; - }; - - // collect owners world bounding boxes and data from volumes - std::vector owners; - const GLVolumeCollection& volumes = m_canvas.get_volumes(); - for (const GLVolume* volume : volumes.volumes) { - int obj_idx = volume->object_idx(); - if (0 <= obj_idx && obj_idx < (int)model->objects.size()) { - int inst_idx = volume->instance_idx(); - std::vector::iterator it = std::find_if(owners.begin(), owners.end(), [obj_idx, inst_idx](const Owner& owner) { - return (owner.obj_idx == obj_idx) && (owner.inst_idx == inst_idx); - }); - if (it != owners.end()) { - it->world_box.merge(volume->transformed_bounding_box()); - it->selected &= volume->selected; - } else { - const ModelObject* model_object = model->objects[obj_idx]; - Owner owner; - owner.obj_idx = obj_idx; - owner.inst_idx = inst_idx; - owner.model_instance_id = model_object->instances[inst_idx]->id().id; - owner.world_box = volume->transformed_bounding_box(); - owner.title = "object" + std::to_string(obj_idx) + "_inst##" + std::to_string(inst_idx); - owner.label = model_object->name; - if (model_object->instances.size() > 1) - owner.label += " (" + std::to_string(inst_idx + 1) + ")"; - owner.selected = volume->selected; - owners.emplace_back(owner); - } - } - } - - // updates print order strings - if (sorted_instances.size() > 1) { - for (size_t i = 0; i < sorted_instances.size(); ++i) { - size_t id = sorted_instances[i]->id().id; - std::vector::iterator it = std::find_if(owners.begin(), owners.end(), [id](const Owner& owner) { - return owner.model_instance_id == id; - }); - if (it != owners.end()) - it->print_order = std::string((_(L("Seq."))).ToUTF8()) + "#: " + std::to_string(i + 1); - } - } - - // calculate eye bounding boxes center zs - for (Owner& owner : owners) { - owner.eye_center_z = (world_to_eye * owner.world_box.center())(2); - } - - // sort owners by center eye zs and selection - std::sort(owners.begin(), owners.end(), [](const Owner& owner1, const Owner& owner2) { - if (!owner1.selected && owner2.selected) - return true; - else if (owner1.selected && !owner2.selected) - return false; - else - return (owner1.eye_center_z < owner2.eye_center_z); - }); - - ImGuiWrapper& imgui = *wxGetApp().imgui(); - - // render info windows - for (const Owner& owner : owners) { - Vec3d screen_box_center = world_to_screen * owner.world_box.center(); - float x = 0.0f; - float y = 0.0f; - if (camera.get_type() == Camera::EType::Perspective) { - x = (0.5f + 0.001f * 0.5f * (float)screen_box_center(0)) * viewport[2]; - y = (0.5f - 0.001f * 0.5f * (float)screen_box_center(1)) * viewport[3]; - } else { - x = (0.5f + 0.5f * (float)screen_box_center(0)) * viewport[2]; - y = (0.5f - 0.5f * (float)screen_box_center(1)) * viewport[3]; - } - - if (x < 0.0f || viewport[2] < x || y < 0.0f || viewport[3] < y) - continue; - - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, owner.selected ? 3.0f : 1.5f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - ImGui::PushStyleColor(ImGuiCol_Border, owner.selected ? ImVec4(0.757f, 0.404f, 0.216f, 1.0f) : ImVec4(0.75f, 0.75f, 0.75f, 1.0f)); - imgui.set_next_window_pos(x, y, ImGuiCond_Always, 0.5f, 0.5f); - imgui.begin(owner.title, ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); - ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); - float win_w = ImGui::GetWindowWidth(); - float label_len = imgui.calc_text_size(owner.label).x; - ImGui::SetCursorPosX(0.5f * (win_w - label_len)); - ImGui::AlignTextToFramePadding(); - imgui.text(owner.label); - - if (!owner.print_order.empty()) { - ImGui::Separator(); - float po_len = imgui.calc_text_size(owner.print_order).x; - ImGui::SetCursorPosX(0.5f * (win_w - po_len)); - ImGui::AlignTextToFramePadding(); - imgui.text(owner.print_order); - } - - // force re-render while the windows gets to its final size (it takes several frames) - if (ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x) - imgui.set_requires_extra_frame(); - - imgui.end(); - ImGui::PopStyleColor(); - ImGui::PopStyleVar(2); - } -} - -void GLCanvas3D::Tooltip::set_text(const std::string& text) -{ - // If the mouse is inside an ImGUI dialog, then the tooltip is suppressed. - m_text = m_in_imgui ? std::string() : text; -} - -void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas) -{ - static ImVec2 size(0.0f, 0.0f); - - auto validate_position = [](const Vec2d& position, const GLCanvas3D& canvas, const ImVec2& wnd_size) { - auto calc_cursor_height = []() { - float ret = 16.0f; -#ifdef _WIN32 - // see: https://forums.codeguru.com/showthread.php?449040-get-the-system-current-cursor-size - // this code is not perfect because it returns a maximum height equal to 31 even if the cursor bitmap shown on screen is bigger - // but at least it gives the same result as wxWidgets in the settings tabs - ICONINFO ii; - if (::GetIconInfo((HICON)GetCursor(), &ii) != 0) { - BITMAP bitmap; - ::GetObject(ii.hbmMask, sizeof(BITMAP), &bitmap); - int width = bitmap.bmWidth; - int height = (ii.hbmColor == nullptr) ? bitmap.bmHeight / 2 : bitmap.bmHeight; - HDC dc = ::CreateCompatibleDC(nullptr); - if (dc != nullptr) { - if (::SelectObject(dc, ii.hbmMask) != nullptr) { - for (int i = 0; i < width; ++i) { - for (int j = 0; j < height; ++j) { - if (::GetPixel(dc, i, j) != RGB(255, 255, 255)) { - if (ret < float(j)) - ret = float(j); - } - } - } - ::DeleteDC(dc); - } - } - ::DeleteObject(ii.hbmColor); - ::DeleteObject(ii.hbmMask); - } -#endif // _WIN32 - return ret; - }; - - const Size cnv_size = canvas.get_canvas_size(); - const float x = std::clamp(float(position.x()), 0.0f, float(cnv_size.get_width()) - wnd_size.x); - const float y = std::clamp(float(position.y()) + calc_cursor_height(), 0.0f, float(cnv_size.get_height()) - wnd_size.y); - return Vec2f(x, y); - }; - - if (m_text.empty()) { - m_start_time = std::chrono::steady_clock::now(); - return; - } - - // draw the tooltip as hidden until the delay is expired - // use a value of alpha slightly different from 0.0f because newer imgui does not calculate properly the window size if alpha == 0.0f - const float alpha = (std::chrono::duration_cast(std::chrono::steady_clock::now() - m_start_time).count() < 500) ? 0.01f : 1.0f; - - const Vec2f position = validate_position(mouse_position, canvas, size); - - ImGuiWrapper& imgui = *wxGetApp().imgui(); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha); - imgui.set_next_window_pos(position.x(), position.y(), ImGuiCond_Always, 0.0f, 0.0f); - - imgui.begin(wxString("canvas_tooltip"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoFocusOnAppearing); - ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); - ImGui::TextUnformatted(m_text.c_str()); - - // force re-render while the windows gets to its final size (it may take several frames) or while hidden - if (alpha < 1.0f || ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x) - imgui.set_requires_extra_frame(); - - size = ImGui::GetWindowSize(); - - imgui.end(); - ImGui::PopStyleVar(2); -} - -void GLCanvas3D::SequentialPrintClearance::set_polygons(const Polygons& polygons) -{ - m_perimeter.reset(); - m_fill.reset(); - if (polygons.empty()) - return; - -#if !ENABLE_LEGACY_OPENGL_REMOVAL - size_t triangles_count = 0; - for (const Polygon& poly : polygons) { - triangles_count += poly.points.size() - 2; - } - const size_t vertices_count = 3 * triangles_count; -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - - if (m_render_fill) { - GLModel::Geometry fill_data; -#if ENABLE_LEGACY_OPENGL_REMOVAL - fill_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; - fill_data.color = { 0.3333f, 0.0f, 0.0f, 0.5f }; - - // vertices + indices - const ExPolygons polygons_union = union_ex(polygons); - unsigned int vertices_counter = 0; - for (const ExPolygon& poly : polygons_union) { - const std::vector triangulation = triangulate_expolygon_3d(poly); - fill_data.reserve_vertices(fill_data.vertices_count() + triangulation.size()); - fill_data.reserve_indices(fill_data.indices_count() + triangulation.size()); - for (const Vec3d& v : triangulation) { - fill_data.add_vertex((Vec3f)(v.cast() + 0.0125f * Vec3f::UnitZ())); // add a small positive z to avoid z-fighting - ++vertices_counter; - if (vertices_counter % 3 == 0) - fill_data.add_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1); - } - } - - m_fill.init_from(std::move(fill_data)); -#else - GLModel::Geometry::Entity entity; - entity.type = GLModel::EPrimitiveType::Triangles; - entity.color = { 0.3333f, 0.0f, 0.0f, 0.5f }; - entity.positions.reserve(vertices_count); - entity.normals.reserve(vertices_count); - entity.indices.reserve(vertices_count); - - const ExPolygons polygons_union = union_ex(polygons); - for (const ExPolygon& poly : polygons_union) { - const std::vector triangulation = triangulate_expolygon_3d(poly); - for (const Vec3d& v : triangulation) { - entity.positions.emplace_back(v.cast() + Vec3f(0.0f, 0.0f, 0.0125f)); // add a small positive z to avoid z-fighting - entity.normals.emplace_back(Vec3f::UnitZ()); - const size_t positions_count = entity.positions.size(); - if (positions_count % 3 == 0) { - entity.indices.emplace_back(positions_count - 3); - entity.indices.emplace_back(positions_count - 2); - entity.indices.emplace_back(positions_count - 1); - } - } - } - - fill_data.entities.emplace_back(entity); - m_fill.init_from(fill_data); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_perimeter.init_from(polygons, 0.025f); // add a small positive z to avoid z-fighting -#else - GLModel::Geometry perimeter_data; - for (const Polygon& poly : polygons) { - GLModel::Geometry::Entity ent; - ent.type = GLModel::EPrimitiveType::LineLoop; - ent.positions.reserve(poly.points.size()); - ent.indices.reserve(poly.points.size()); - unsigned int id_count = 0; - for (const Point& p : poly.points) { - ent.positions.emplace_back(unscale(p.x()), unscale(p.y()), 0.025f); // add a small positive z to avoid z-fighting - ent.normals.emplace_back(Vec3f::UnitZ()); - ent.indices.emplace_back(id_count++); - } - - perimeter_data.entities.emplace_back(ent); - } - - m_perimeter.init_from(perimeter_data); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} - -void GLCanvas3D::SequentialPrintClearance::render() -{ - const ColorRGBA FILL_COLOR = { 1.0f, 0.0f, 0.0f, 0.5f }; - const ColorRGBA NO_FILL_COLOR = { 1.0f, 1.0f, 1.0f, 0.75f }; - -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLShaderProgram* shader = wxGetApp().get_shader("flat"); -#else - GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - if (shader == nullptr) - return; - - shader->start_using(); - -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - shader->set_uniform("view_model_matrix", camera.get_view_matrix()); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glDisable(GL_CULL_FACE)); - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_perimeter.set_color(m_render_fill ? FILL_COLOR : NO_FILL_COLOR); -#else - m_perimeter.set_color(-1, m_render_fill ? FILL_COLOR : NO_FILL_COLOR); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - m_perimeter.render(); - m_fill.render(); - - glsafe(::glDisable(GL_BLEND)); - glsafe(::glEnable(GL_CULL_FACE)); - glsafe(::glDisable(GL_DEPTH_TEST)); - - shader->stop_using(); -} - -wxDEFINE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, RBtnEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_SELECT_ALL, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_QUESTION_MARK, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event); -wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_ROTATED, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_SCALED, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_FORCE_UPDATE, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_WIPETOWER_MOVED, Vec3dEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3dEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, Event); -wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_GEOMETRY, Vec3dsEvent<2>); -wxDEFINE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_STARTED, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_MOVE_SLIDERS, wxKeyEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_JUMP_TO, wxKeyEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_COLLAPSE_SIDEBAR, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, Event); -wxDEFINE_EVENT(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, HeightProfileSmoothEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_RELOAD_FROM_DISK, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_RENDER_TIMER, wxTimerEvent/*RenderTimerEvent*/); -wxDEFINE_EVENT(EVT_GLCANVAS_TOOLBAR_HIGHLIGHTER_TIMER, wxTimerEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_GIZMO_HIGHLIGHTER_TIMER, wxTimerEvent); - -const double GLCanvas3D::DefaultCameraZoomToBoxMarginFactor = 1.25; - -void GLCanvas3D::load_arrange_settings() -{ - std::string dist_fff_str = - wxGetApp().app_config->get("arrange", "min_object_distance_fff"); - - std::string dist_fff_seq_print_str = - wxGetApp().app_config->get("arrange", "min_object_distance_fff_seq_print"); - - std::string dist_sla_str = - wxGetApp().app_config->get("arrange", "min_object_distance_sla"); - - std::string en_rot_fff_str = - wxGetApp().app_config->get("arrange", "enable_rotation_fff"); - - std::string en_rot_fff_seqp_str = - wxGetApp().app_config->get("arrange", "enable_rotation_fff_seq_print"); - - std::string en_rot_sla_str = - wxGetApp().app_config->get("arrange", "enable_rotation_sla"); - - if (!dist_fff_str.empty()) - m_arrange_settings_fff.distance = std::stof(dist_fff_str); - - if (!dist_fff_seq_print_str.empty()) - m_arrange_settings_fff_seq_print.distance = std::stof(dist_fff_seq_print_str); - - if (!dist_sla_str.empty()) - m_arrange_settings_sla.distance = std::stof(dist_sla_str); - - if (!en_rot_fff_str.empty()) - m_arrange_settings_fff.enable_rotation = (en_rot_fff_str == "1" || en_rot_fff_str == "yes"); - - if (!en_rot_fff_seqp_str.empty()) - m_arrange_settings_fff_seq_print.enable_rotation = (en_rot_fff_seqp_str == "1" || en_rot_fff_seqp_str == "yes"); - - if (!en_rot_sla_str.empty()) - m_arrange_settings_sla.enable_rotation = (en_rot_sla_str == "1" || en_rot_sla_str == "yes"); -} - -PrinterTechnology GLCanvas3D::current_printer_technology() const -{ - return m_process->current_printer_technology(); -} - -GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed) - : m_canvas(canvas) - , m_context(nullptr) - , m_bed(bed) -#if ENABLE_RETINA_GL - , m_retina_helper(nullptr) -#endif - , m_in_render(false) - , m_main_toolbar(GLToolbar::Normal, "Main") - , m_undoredo_toolbar(GLToolbar::Normal, "Undo_Redo") - , m_gizmos(*this) - , m_use_clipping_planes(false) - , m_sidebar_field("") - , m_extra_frame_requested(false) - , m_config(nullptr) - , m_process(nullptr) - , m_model(nullptr) - , m_dirty(true) - , m_initialized(false) - , m_apply_zoom_to_volumes_filter(false) - , m_picking_enabled(false) - , m_moving_enabled(false) - , m_dynamic_background_enabled(false) - , m_multisample_allowed(false) - , m_moving(false) - , m_tab_down(false) - , m_cursor_type(Standard) - , m_reload_delayed(false) -#if ENABLE_RENDER_PICKING_PASS - , m_show_picking_texture(false) -#endif // ENABLE_RENDER_PICKING_PASS - , m_render_sla_auxiliaries(true) - , m_labels(*this) - , m_slope(m_volumes) -{ - if (m_canvas != nullptr) { - m_timer.SetOwner(m_canvas); - m_render_timer.SetOwner(m_canvas); -#if ENABLE_RETINA_GL - m_retina_helper.reset(new RetinaHelper(canvas)); -#endif // ENABLE_RETINA_GL - } - - load_arrange_settings(); - - m_selection.set_volumes(&m_volumes.volumes); -} - -GLCanvas3D::~GLCanvas3D() -{ - reset_volumes(); -} - -void GLCanvas3D::post_event(wxEvent &&event) -{ - event.SetEventObject(m_canvas); - wxPostEvent(m_canvas, event); -} - -bool GLCanvas3D::init() -{ - if (m_initialized) - return true; - - if (m_canvas == nullptr || m_context == nullptr) - return false; - - glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 1.0f)); - glsafe(::glClearDepth(1.0f)); - - glsafe(::glDepthFunc(GL_LESS)); - - glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glEnable(GL_CULL_FACE)); - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - -#if !ENABLE_LEGACY_OPENGL_REMOVAL - // Set antialiasing / multisampling - glsafe(::glDisable(GL_LINE_SMOOTH)); - glsafe(::glDisable(GL_POLYGON_SMOOTH)); - - // ambient lighting - GLfloat ambient[4] = { 0.3f, 0.3f, 0.3f, 1.0f }; - glsafe(::glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient)); - - glsafe(::glEnable(GL_LIGHT0)); - glsafe(::glEnable(GL_LIGHT1)); - - // light from camera - GLfloat specular_cam[4] = { 0.3f, 0.3f, 0.3f, 1.0f }; - glsafe(::glLightfv(GL_LIGHT1, GL_SPECULAR, specular_cam)); - GLfloat diffuse_cam[4] = { 0.2f, 0.2f, 0.2f, 1.0f }; - glsafe(::glLightfv(GL_LIGHT1, GL_DIFFUSE, diffuse_cam)); - - // light from above - GLfloat specular_top[4] = { 0.2f, 0.2f, 0.2f, 1.0f }; - glsafe(::glLightfv(GL_LIGHT0, GL_SPECULAR, specular_top)); - GLfloat diffuse_top[4] = { 0.5f, 0.5f, 0.5f, 1.0f }; - glsafe(::glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse_top)); - - // Enables Smooth Color Shading; try GL_FLAT for (lack of) fun. - glsafe(::glShadeModel(GL_SMOOTH)); - - // A handy trick -- have surface material mirror the color. - glsafe(::glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)); - glsafe(::glEnable(GL_COLOR_MATERIAL)); -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - - if (m_multisample_allowed) - glsafe(::glEnable(GL_MULTISAMPLE)); - - if (m_main_toolbar.is_enabled()) - m_layers_editing.init(); - -#if !ENABLE_LEGACY_OPENGL_REMOVAL - // on linux the gl context is not valid until the canvas is not shown on screen - // we defer the geometry finalization of volumes until the first call to render() - m_volumes.finalize_geometry(true); -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - - if (m_gizmos.is_enabled() && !m_gizmos.init()) - std::cout << "Unable to initialize gizmos: please, check that all the required textures are available" << std::endl; - - if (!_init_toolbars()) - return false; - - if (m_selection.is_enabled() && !m_selection.init()) - return false; - - m_initialized = true; - - return true; -} - -void GLCanvas3D::set_as_dirty() -{ - m_dirty = true; -} - -unsigned int GLCanvas3D::get_volumes_count() const -{ - return (unsigned int)m_volumes.volumes.size(); -} - -void GLCanvas3D::reset_volumes() -{ - if (!m_initialized) - return; - - if (m_volumes.empty()) - return; - - _set_current(); - - m_selection.clear(); - m_volumes.clear(); - m_dirty = true; - - _set_warning_notification(EWarning::ObjectOutside, false); -} - -ModelInstanceEPrintVolumeState GLCanvas3D::check_volumes_outside_state() const -{ - ModelInstanceEPrintVolumeState state = ModelInstanceEPrintVolumeState::ModelInstancePVS_Inside; - if (m_initialized) - m_volumes.check_outside_state(m_bed.build_volume(), &state); - return state; -} - -void GLCanvas3D::toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo, int instance_idx) -{ -#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - if (current_printer_technology() != ptSLA) - return; -#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - - m_render_sla_auxiliaries = visible; - - for (GLVolume* vol : m_volumes.volumes) { -#if !ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - if (vol->composite_id.object_id == 1000) - continue; // the wipe tower -#endif // !ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo) - && (instance_idx == -1 || vol->composite_id.instance_id == instance_idx) - && vol->composite_id.volume_id < 0) - vol->is_active = visible; - } -} - -void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject* mo, int instance_idx, const ModelVolume* mv) -{ - for (GLVolume* vol : m_volumes.volumes) { -#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - if (vol->is_wipe_tower) - vol->is_active = (visible && mo == nullptr); -#else - if (vol->composite_id.object_id == 1000) { // wipe tower - vol->is_active = (visible && mo == nullptr); - } -#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - else { - if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo) - && (instance_idx == -1 || vol->composite_id.instance_id == instance_idx) - && (mv == nullptr || m_model->objects[vol->composite_id.object_id]->volumes[vol->composite_id.volume_id] == mv)) { - vol->is_active = visible; - - if (instance_idx == -1) { - vol->force_native_color = false; - vol->force_neutral_color = false; - } else { - const GLGizmosManager& gm = get_gizmos_manager(); - auto gizmo_type = gm.get_current_type(); - if ( (gizmo_type == GLGizmosManager::FdmSupports - || gizmo_type == GLGizmosManager::Seam) - && ! vol->is_modifier) - vol->force_neutral_color = true; - else if (gizmo_type == GLGizmosManager::MmuSegmentation) - vol->is_active = false; - else - vol->force_native_color = true; - } - } - } - } - - if (visible && !mo) - toggle_sla_auxiliaries_visibility(true, mo, instance_idx); - - if (!mo && !visible && !m_model->objects.empty() && (m_model->objects.size() > 1 || m_model->objects.front()->instances.size() > 1)) - _set_warning_notification(EWarning::SomethingNotShown, true); - - if (!mo && visible) - _set_warning_notification(EWarning::SomethingNotShown, false); -} - -void GLCanvas3D::update_instance_printable_state_for_object(const size_t obj_idx) -{ - ModelObject* model_object = m_model->objects[obj_idx]; - for (int inst_idx = 0; inst_idx < (int)model_object->instances.size(); ++inst_idx) { - ModelInstance* instance = model_object->instances[inst_idx]; - - for (GLVolume* volume : m_volumes.volumes) { - if (volume->object_idx() == (int)obj_idx && volume->instance_idx() == inst_idx) - volume->printable = instance->printable; - } - } -} - -void GLCanvas3D::update_instance_printable_state_for_objects(const std::vector& object_idxs) -{ - for (size_t obj_idx : object_idxs) - update_instance_printable_state_for_object(obj_idx); -} - -void GLCanvas3D::set_config(const DynamicPrintConfig* config) -{ - m_config = config; - m_layers_editing.set_config(config); -} - -void GLCanvas3D::set_process(BackgroundSlicingProcess *process) -{ - m_process = process; -} - -void GLCanvas3D::set_model(Model* model) -{ - m_model = model; - m_selection.set_model(m_model); -} - -void GLCanvas3D::bed_shape_changed() -{ - refresh_camera_scene_box(); - wxGetApp().plater()->get_camera().requires_zoom_to_bed = true; - m_dirty = true; -} - -void GLCanvas3D::refresh_camera_scene_box() -{ - wxGetApp().plater()->get_camera().set_scene_box(scene_bounding_box()); -} - -BoundingBoxf3 GLCanvas3D::volumes_bounding_box() const -{ - BoundingBoxf3 bb; - for (const GLVolume* volume : m_volumes.volumes) { - if (!m_apply_zoom_to_volumes_filter || ((volume != nullptr) && volume->zoom_to_volumes)) - bb.merge(volume->transformed_bounding_box()); - } - return bb; -} - -BoundingBoxf3 GLCanvas3D::scene_bounding_box() const -{ - BoundingBoxf3 bb = volumes_bounding_box(); - bb.merge(m_bed.extended_bounding_box()); - double h = m_bed.build_volume().max_print_height(); - //FIXME why -h? - bb.min.z() = std::min(bb.min.z(), -h); - bb.max.z() = std::max(bb.max.z(), h); - return bb; -} - -bool GLCanvas3D::is_layers_editing_enabled() const -{ - return m_layers_editing.is_enabled(); -} - -bool GLCanvas3D::is_layers_editing_allowed() const -{ - return m_layers_editing.is_allowed(); -} - -void GLCanvas3D::reset_layer_height_profile() -{ - wxGetApp().plater()->take_snapshot(_L("Variable layer height - Reset")); - m_layers_editing.reset_layer_height_profile(*this); - m_layers_editing.state = LayersEditing::Completed; - m_dirty = true; -} - -void GLCanvas3D::adaptive_layer_height_profile(float quality_factor) -{ - wxGetApp().plater()->take_snapshot(_L("Variable layer height - Adaptive")); - m_layers_editing.adaptive_layer_height_profile(*this, quality_factor); - m_layers_editing.state = LayersEditing::Completed; - m_dirty = true; -} - -void GLCanvas3D::smooth_layer_height_profile(const HeightProfileSmoothingParams& smoothing_params) -{ - wxGetApp().plater()->take_snapshot(_L("Variable layer height - Smooth all")); - m_layers_editing.smooth_layer_height_profile(*this, smoothing_params); - m_layers_editing.state = LayersEditing::Completed; - m_dirty = true; -} - -bool GLCanvas3D::is_reload_delayed() const -{ - return m_reload_delayed; -} - -void GLCanvas3D::enable_layers_editing(bool enable) -{ - m_layers_editing.set_enabled(enable); - set_as_dirty(); -} - -void GLCanvas3D::enable_legend_texture(bool enable) -{ - m_gcode_viewer.enable_legend(enable); -} - -void GLCanvas3D::enable_picking(bool enable) -{ - m_picking_enabled = enable; - m_selection.set_mode(Selection::Instance); -} - -void GLCanvas3D::enable_moving(bool enable) -{ - m_moving_enabled = enable; -} - -void GLCanvas3D::enable_gizmos(bool enable) -{ - m_gizmos.set_enabled(enable); -} - -void GLCanvas3D::enable_selection(bool enable) -{ - m_selection.set_enabled(enable); -} - -void GLCanvas3D::enable_main_toolbar(bool enable) -{ - m_main_toolbar.set_enabled(enable); -} - -void GLCanvas3D::enable_undoredo_toolbar(bool enable) -{ - m_undoredo_toolbar.set_enabled(enable); -} - -void GLCanvas3D::enable_dynamic_background(bool enable) -{ - m_dynamic_background_enabled = enable; -} - -void GLCanvas3D::allow_multisample(bool allow) -{ - m_multisample_allowed = allow; -} - -void GLCanvas3D::zoom_to_bed() -{ - BoundingBoxf3 box = m_bed.build_volume().bounding_volume(); - box.min.z() = 0.0; - box.max.z() = 0.0; - _zoom_to_box(box); -} - -void GLCanvas3D::zoom_to_volumes() -{ - m_apply_zoom_to_volumes_filter = true; - _zoom_to_box(volumes_bounding_box()); - m_apply_zoom_to_volumes_filter = false; -} - -void GLCanvas3D::zoom_to_selection() -{ - if (!m_selection.is_empty()) - _zoom_to_box(m_selection.get_bounding_box()); -} - -void GLCanvas3D::zoom_to_gcode() -{ - _zoom_to_box(m_gcode_viewer.get_paths_bounding_box(), 1.05); -} - -void GLCanvas3D::select_view(const std::string& direction) -{ - wxGetApp().plater()->get_camera().select_view(direction); - if (m_canvas != nullptr) - m_canvas->Refresh(); -} - -void GLCanvas3D::update_volumes_colors_by_extruder() -{ - if (m_config != nullptr) - m_volumes.update_colors_by_extruder(m_config); -} - -void GLCanvas3D::render() -{ - if (m_in_render) { - // if called recursively, return - m_dirty = true; - return; - } - - m_in_render = true; - Slic3r::ScopeGuard in_render_guard([this]() { m_in_render = false; }); - (void)in_render_guard; - - if (m_canvas == nullptr) - return; - - // ensures this canvas is current and initialized - if (!_is_shown_on_screen() || !_set_current() || !wxGetApp().init_opengl()) - return; - - if (!is_initialized() && !init()) - return; - - if (!m_main_toolbar.is_enabled()) - m_gcode_viewer.init(); - - if (! m_bed.build_volume().valid()) { - // this happens at startup when no data is still saved under <>\AppData\Roaming\Slic3rPE - post_event(SimpleEvent(EVT_GLCANVAS_UPDATE_BED_SHAPE)); - return; - } - -#if ENABLE_ENVIRONMENT_MAP - if (wxGetApp().is_editor()) - wxGetApp().plater()->init_environment_texture(); -#endif // ENABLE_ENVIRONMENT_MAP - -#if ENABLE_GLMODEL_STATISTICS - GLModel::reset_statistics_counters(); -#endif // ENABLE_GLMODEL_STATISTICS - - const Size& cnv_size = get_canvas_size(); - // Probably due to different order of events on Linux/GTK2, when one switched from 3D scene - // to preview, this was called before canvas had its final size. It reported zero width - // and the viewport was set incorrectly, leading to tripping glAsserts further down - // the road (in apply_projection). That's why the minimum size is forced to 10. - Camera& camera = wxGetApp().plater()->get_camera(); - camera.apply_viewport(0, 0, std::max(10u, (unsigned int)cnv_size.get_width()), std::max(10u, (unsigned int)cnv_size.get_height())); - - if (camera.requires_zoom_to_bed) { - zoom_to_bed(); - _resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height()); - camera.requires_zoom_to_bed = false; - } - -#if !ENABLE_LEGACY_OPENGL_REMOVAL - camera.apply_view_matrix(); -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - camera.apply_projection(_max_bounding_box(true, true)); - -#if !ENABLE_LEGACY_OPENGL_REMOVAL - GLfloat position_cam[4] = { 1.0f, 0.0f, 1.0f, 0.0f }; - glsafe(::glLightfv(GL_LIGHT1, GL_POSITION, position_cam)); - GLfloat position_top[4] = { -0.5f, -0.5f, 1.0f, 0.0f }; - glsafe(::glLightfv(GL_LIGHT0, GL_POSITION, position_top)); -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - - wxGetApp().imgui()->new_frame(); - - if (m_picking_enabled) { -#if ENABLE_NEW_RECTANGLE_SELECTION - if (m_rectangle_selection.is_dragging() && !m_rectangle_selection.is_empty()) -#else - if (m_rectangle_selection.is_dragging()) -#endif // ENABLE_NEW_RECTANGLE_SELECTION - // picking pass using rectangle selection - _rectangular_selection_picking_pass(); - else if (!m_volumes.empty()) - // regular picking pass - _picking_pass(); - } - -#if ENABLE_RENDER_PICKING_PASS - if (!m_picking_enabled || !m_show_picking_texture) { -#endif // ENABLE_RENDER_PICKING_PASS - - const bool is_looking_downward = camera.is_looking_downward(); - - // draw scene - glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); - _render_background(); - - _render_objects(GLVolumeCollection::ERenderType::Opaque); - if (!m_main_toolbar.is_enabled()) - _render_gcode(); - _render_sla_slices(); - _render_selection(); - if (is_looking_downward) -#if ENABLE_GL_SHADERS_ATTRIBUTES - _render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), false, true); -#else - _render_bed(false, true); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - _render_objects(GLVolumeCollection::ERenderType::Transparent); - - _render_sequential_clearance(); -#if ENABLE_RENDER_SELECTION_CENTER - _render_selection_center(); -#endif // ENABLE_RENDER_SELECTION_CENTER -#if ENABLE_SHOW_TOOLPATHS_COG - if (!m_main_toolbar.is_enabled()) - _render_gcode_cog(); -#endif // ENABLE_SHOW_TOOLPATHS_COG - - // we need to set the mouse's scene position here because the depth buffer - // could be invalidated by the following gizmo render methods - // this position is used later into on_mouse() to drag the objects - if (m_picking_enabled) - m_mouse.scene_position = _mouse_to_3d(m_mouse.position.cast()); - - // sidebar hints need to be rendered before the gizmos because the depth buffer - // could be invalidated by the following gizmo render methods - _render_selection_sidebar_hints(); - _render_current_gizmo(); - if (!is_looking_downward) -#if ENABLE_GL_SHADERS_ATTRIBUTES - _render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), true, true); -#else - _render_bed(true, true); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES -#if ENABLE_RENDER_PICKING_PASS - } -#endif // ENABLE_RENDER_PICKING_PASS - -#if ENABLE_SHOW_CAMERA_TARGET - _render_camera_target(); -#endif // ENABLE_SHOW_CAMERA_TARGET - - if (m_picking_enabled && m_rectangle_selection.is_dragging()) - m_rectangle_selection.render(*this); - - // draw overlays - _render_overlays(); - - if (wxGetApp().plater()->is_render_statistic_dialog_visible()) { - ImGuiWrapper& imgui = *wxGetApp().imgui(); - imgui.begin(std::string("Render statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - imgui.text("FPS (SwapBuffers() calls per second):"); - ImGui::SameLine(); - imgui.text(std::to_string(m_render_stats.get_fps_and_reset_if_needed())); - ImGui::Separator(); - imgui.text("Compressed textures:"); - ImGui::SameLine(); - imgui.text(OpenGLManager::are_compressed_textures_supported() ? "supported" : "not supported"); - imgui.text("Max texture size:"); - ImGui::SameLine(); - imgui.text(std::to_string(OpenGLManager::get_gl_info().get_max_tex_size())); - imgui.end(); - } - -#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW - if (wxGetApp().is_editor() && wxGetApp().plater()->is_view3D_shown()) - wxGetApp().plater()->render_project_state_debug_window(); -#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW - -#if ENABLE_CAMERA_STATISTICS - camera.debug_render(); -#endif // ENABLE_CAMERA_STATISTICS -#if ENABLE_GLMODEL_STATISTICS - GLModel::render_statistics(); -#endif // ENABLE_GLMODEL_STATISTICS - - std::string tooltip; - - // Negative coordinate means out of the window, likely because the window was deactivated. - // In that case the tooltip should be hidden. - if (m_mouse.position.x() >= 0. && m_mouse.position.y() >= 0.) { - if (tooltip.empty()) - tooltip = m_layers_editing.get_tooltip(*this); - - if (tooltip.empty()) - tooltip = m_gizmos.get_tooltip(); - - if (tooltip.empty()) - tooltip = m_main_toolbar.get_tooltip(); - - if (tooltip.empty()) - tooltip = m_undoredo_toolbar.get_tooltip(); - - if (tooltip.empty()) - tooltip = wxGetApp().plater()->get_collapse_toolbar().get_tooltip(); - - if (tooltip.empty()) - tooltip = wxGetApp().plater()->get_view_toolbar().get_tooltip(); - } - - set_tooltip(tooltip); - - if (m_tooltip_enabled) - m_tooltip.render(m_mouse.position, *this); - - wxGetApp().plater()->get_mouse3d_controller().render_settings_dialog(*this); - - wxGetApp().plater()->get_notification_manager()->render_notifications(*this, get_overlay_window_width()); - - wxGetApp().imgui()->render(); - - m_canvas->SwapBuffers(); - m_render_stats.increment_fps_counter(); -} - -void GLCanvas3D::render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, Camera::EType camera_type) -{ - render_thumbnail(thumbnail_data, w, h, thumbnail_params, m_volumes, camera_type); -} - -void GLCanvas3D::render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type) -{ - switch (OpenGLManager::get_framebuffers_type()) - { - case OpenGLManager::EFramebufferType::Arb: { _render_thumbnail_framebuffer(thumbnail_data, w, h, thumbnail_params, volumes, camera_type); break; } - case OpenGLManager::EFramebufferType::Ext: { _render_thumbnail_framebuffer_ext(thumbnail_data, w, h, thumbnail_params, volumes, camera_type); break; } - default: { _render_thumbnail_legacy(thumbnail_data, w, h, thumbnail_params, volumes, camera_type); break; } - } -} - -void GLCanvas3D::select_all() -{ - m_selection.add_all(); - m_dirty = true; -} - -void GLCanvas3D::deselect_all() -{ - m_selection.remove_all(); - wxGetApp().obj_manipul()->set_dirty(); - m_gizmos.reset_all_states(); - m_gizmos.update_data(); - post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); -} - -void GLCanvas3D::delete_selected() -{ - m_selection.erase(); -} - -void GLCanvas3D::ensure_on_bed(unsigned int object_idx, bool allow_negative_z) -{ - if (allow_negative_z) - return; - - typedef std::map, double> InstancesToZMap; - InstancesToZMap instances_min_z; - - for (GLVolume* volume : m_volumes.volumes) { - if (volume->object_idx() == (int)object_idx && !volume->is_modifier) { - double min_z = volume->transformed_convex_hull_bounding_box().min.z(); - std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); - InstancesToZMap::iterator it = instances_min_z.find(instance); - if (it == instances_min_z.end()) - it = instances_min_z.insert(InstancesToZMap::value_type(instance, DBL_MAX)).first; - - it->second = std::min(it->second, min_z); - } - } - - for (GLVolume* volume : m_volumes.volumes) { - std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); - InstancesToZMap::iterator it = instances_min_z.find(instance); - if (it != instances_min_z.end()) - volume->set_instance_offset(Z, volume->get_instance_offset(Z) - it->second); - } -} - - -const std::vector& GLCanvas3D::get_gcode_layers_zs() const -{ - return m_gcode_viewer.get_layers_zs(); -} - -std::vector GLCanvas3D::get_volumes_print_zs(bool active_only) const -{ - return m_volumes.get_current_print_zs(active_only); -} - -void GLCanvas3D::set_gcode_options_visibility_from_flags(unsigned int flags) -{ - m_gcode_viewer.set_options_visibility_from_flags(flags); -} - -void GLCanvas3D::set_toolpath_role_visibility_flags(unsigned int flags) -{ - m_gcode_viewer.set_toolpath_role_visibility_flags(flags); -} - -void GLCanvas3D::set_toolpath_view_type(GCodeViewer::EViewType type) -{ - m_gcode_viewer.set_view_type(type); -} - -void GLCanvas3D::set_volumes_z_range(const std::array& range) -{ - m_volumes.set_range(range[0] - 1e-6, range[1] + 1e-6); -} - -void GLCanvas3D::set_toolpaths_z_range(const std::array& range) -{ - if (m_gcode_viewer.has_data()) - m_gcode_viewer.set_layers_z_range(range); -} - -std::vector GLCanvas3D::load_object(const ModelObject& model_object, int obj_idx, std::vector instance_idxs) -{ - if (instance_idxs.empty()) { - for (unsigned int i = 0; i < model_object.instances.size(); ++i) { - instance_idxs.emplace_back(i); - } - } -#if ENABLE_LEGACY_OPENGL_REMOVAL - return m_volumes.load_object(&model_object, obj_idx, instance_idxs); -#else - return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_initialized); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} - -std::vector GLCanvas3D::load_object(const Model& model, int obj_idx) -{ - if (0 <= obj_idx && obj_idx < (int)model.objects.size()) { - const ModelObject* model_object = model.objects[obj_idx]; - if (model_object != nullptr) - return load_object(*model_object, obj_idx, std::vector()); - } - - return std::vector(); -} - -void GLCanvas3D::mirror_selection(Axis axis) -{ - m_selection.mirror(axis); - do_mirror(L("Mirror Object")); - wxGetApp().obj_manipul()->set_dirty(); -} - -// Reload the 3D scene of -// 1) Model / ModelObjects / ModelInstances / ModelVolumes -// 2) Print bed -// 3) SLA support meshes for their respective ModelObjects / ModelInstances -// 4) Wipe tower preview -// 5) Out of bed collision status & message overlay (texture) -void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_refresh) -{ - if (m_canvas == nullptr || m_config == nullptr || m_model == nullptr) - return; - - if (!m_initialized) - return; - - _set_current(); - - m_hover_volume_idxs.clear(); - - struct ModelVolumeState { - ModelVolumeState(const GLVolume* volume) : - model_volume(nullptr), geometry_id(volume->geometry_id), volume_idx(-1) {} - ModelVolumeState(const ModelVolume* model_volume, const ObjectID& instance_id, const GLVolume::CompositeID& composite_id) : - model_volume(model_volume), geometry_id(std::make_pair(model_volume->id().id, instance_id.id)), composite_id(composite_id), volume_idx(-1) {} - ModelVolumeState(const ObjectID& volume_id, const ObjectID& instance_id) : - model_volume(nullptr), geometry_id(std::make_pair(volume_id.id, instance_id.id)), volume_idx(-1) {} - bool new_geometry() const { return this->volume_idx == size_t(-1); } - const ModelVolume* model_volume; - // ObjectID of ModelVolume + ObjectID of ModelInstance - // or timestamp of an SLAPrintObjectStep + ObjectID of ModelInstance - std::pair geometry_id; - GLVolume::CompositeID composite_id; - // Volume index in the new GLVolume vector. - size_t volume_idx; - }; - std::vector model_volume_state; - std::vector aux_volume_state; - - struct GLVolumeState { - GLVolumeState() : - volume_idx(size_t(-1)) {} - GLVolumeState(const GLVolume* volume, unsigned int volume_idx) : - composite_id(volume->composite_id), volume_idx(volume_idx) {} - GLVolumeState(const GLVolume::CompositeID &composite_id) : - composite_id(composite_id), volume_idx(size_t(-1)) {} - - GLVolume::CompositeID composite_id; - // Volume index in the old GLVolume vector. - size_t volume_idx; - }; - - // SLA steps to pull the preview meshes for. - typedef std::array SLASteps; - SLASteps sla_steps = { slaposDrillHoles, slaposSupportTree, slaposPad }; - struct SLASupportState { - std::array::value> step; - }; - // State of the sla_steps for all SLAPrintObjects. - std::vector sla_support_state; - - std::vector instance_ids_selected; - std::vector map_glvolume_old_to_new(m_volumes.volumes.size(), size_t(-1)); - std::vector deleted_volumes; - std::vector glvolumes_new; - glvolumes_new.reserve(m_volumes.volumes.size()); - auto model_volume_state_lower = [](const ModelVolumeState& m1, const ModelVolumeState& m2) { return m1.geometry_id < m2.geometry_id; }; - - m_reload_delayed = !m_canvas->IsShown() && !refresh_immediately && !force_full_scene_refresh; - - PrinterTechnology printer_technology = current_printer_technology(); - int volume_idx_wipe_tower_old = -1; - - // Release invalidated volumes to conserve GPU memory in case of delayed refresh (see m_reload_delayed). - // First initialize model_volumes_new_sorted & model_instances_new_sorted. - for (int object_idx = 0; object_idx < (int)m_model->objects.size(); ++object_idx) { - const ModelObject* model_object = m_model->objects[object_idx]; - for (int instance_idx = 0; instance_idx < (int)model_object->instances.size(); ++instance_idx) { - const ModelInstance* model_instance = model_object->instances[instance_idx]; - for (int volume_idx = 0; volume_idx < (int)model_object->volumes.size(); ++volume_idx) { - const ModelVolume* model_volume = model_object->volumes[volume_idx]; - model_volume_state.emplace_back(model_volume, model_instance->id(), GLVolume::CompositeID(object_idx, volume_idx, instance_idx)); - } - } - } - if (printer_technology == ptSLA) { - const SLAPrint* sla_print = this->sla_print(); -#ifndef NDEBUG - // Verify that the SLAPrint object is synchronized with m_model. - check_model_ids_equal(*m_model, sla_print->model()); -#endif /* NDEBUG */ - sla_support_state.reserve(sla_print->objects().size()); - for (const SLAPrintObject* print_object : sla_print->objects()) { - SLASupportState state; - for (size_t istep = 0; istep < sla_steps.size(); ++istep) { - state.step[istep] = print_object->step_state_with_timestamp(sla_steps[istep]); - if (state.step[istep].state == PrintStateBase::DONE) { - if (!print_object->has_mesh(sla_steps[istep])) - // Consider the DONE step without a valid mesh as invalid for the purpose - // of mesh visualization. - state.step[istep].state = PrintStateBase::INVALID; - else if (sla_steps[istep] != slaposDrillHoles) - for (const ModelInstance* model_instance : print_object->model_object()->instances) - // Only the instances, which are currently printable, will have the SLA support structures kept. - // The instances outside the print bed will have the GLVolumes of their support structures released. - if (model_instance->is_printable()) - aux_volume_state.emplace_back(state.step[istep].timestamp, model_instance->id()); - } - } - sla_support_state.emplace_back(state); - } - } - std::sort(model_volume_state.begin(), model_volume_state.end(), model_volume_state_lower); - std::sort(aux_volume_state.begin(), aux_volume_state.end(), model_volume_state_lower); - // Release all ModelVolume based GLVolumes not found in the current Model. Find the GLVolume of a hollowed mesh. - for (size_t volume_id = 0; volume_id < m_volumes.volumes.size(); ++volume_id) { - GLVolume* volume = m_volumes.volumes[volume_id]; - ModelVolumeState key(volume); - ModelVolumeState* mvs = nullptr; - if (volume->volume_idx() < 0) { - auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower); - if (it != aux_volume_state.end() && it->geometry_id == key.geometry_id) - // This can be an SLA support structure that should not be rendered (in case someone used undo - // to revert to before it was generated). We only reuse the volume if that's not the case. - if (m_model->objects[volume->composite_id.object_id]->sla_points_status != sla::PointsStatus::NoPoints) - mvs = &(*it); - } - else { - auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower); - if (it != model_volume_state.end() && it->geometry_id == key.geometry_id) - mvs = &(*it); - } - // Emplace instance ID of the volume. Both the aux volumes and model volumes share the same instance ID. - // The wipe tower has its own wipe_tower_instance_id(). - if (m_selection.contains_volume(volume_id)) - instance_ids_selected.emplace_back(volume->geometry_id.second); - if (mvs == nullptr || force_full_scene_refresh) { - // This GLVolume will be released. - if (volume->is_wipe_tower) { - // There is only one wipe tower. - assert(volume_idx_wipe_tower_old == -1); - volume_idx_wipe_tower_old = (int)volume_id; - } - if (!m_reload_delayed) { - deleted_volumes.emplace_back(volume, volume_id); - delete volume; - } - } - else { - // This GLVolume will be reused. - volume->set_sla_shift_z(0.0); - map_glvolume_old_to_new[volume_id] = glvolumes_new.size(); - mvs->volume_idx = glvolumes_new.size(); - glvolumes_new.emplace_back(volume); - // Update color of the volume based on the current extruder. - if (mvs->model_volume != nullptr) { - int extruder_id = mvs->model_volume->extruder_id(); - if (extruder_id != -1) - volume->extruder_id = extruder_id; - - volume->is_modifier = !mvs->model_volume->is_model_part(); - volume->set_color(color_from_model_volume(*mvs->model_volume)); - // force update of render_color alpha channel - volume->set_render_color(volume->color.is_transparent()); - - // updates volumes transformations - volume->set_instance_transformation(mvs->model_volume->get_object()->instances[mvs->composite_id.instance_id]->get_transformation()); - volume->set_volume_transformation(mvs->model_volume->get_transformation()); - - // updates volumes convex hull - if (mvs->model_volume->is_model_part() && ! volume->convex_hull()) - // Model volume was likely changed from modifier or support blocker / enforcer to a model part. - // Only model parts require convex hulls. - volume->set_convex_hull(mvs->model_volume->get_convex_hull_shared_ptr()); - } - } - } - sort_remove_duplicates(instance_ids_selected); - auto deleted_volumes_lower = [](const GLVolumeState &v1, const GLVolumeState &v2) { return v1.composite_id < v2.composite_id; }; - std::sort(deleted_volumes.begin(), deleted_volumes.end(), deleted_volumes_lower); - - if (m_reload_delayed) - return; - - bool update_object_list = false; - if (m_volumes.volumes != glvolumes_new) - update_object_list = true; - m_volumes.volumes = std::move(glvolumes_new); - for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++ obj_idx) { - const ModelObject &model_object = *m_model->objects[obj_idx]; - for (int volume_idx = 0; volume_idx < (int)model_object.volumes.size(); ++ volume_idx) { - const ModelVolume &model_volume = *model_object.volumes[volume_idx]; - for (int instance_idx = 0; instance_idx < (int)model_object.instances.size(); ++ instance_idx) { - const ModelInstance &model_instance = *model_object.instances[instance_idx]; - ModelVolumeState key(model_volume.id(), model_instance.id()); - auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower); - assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id); - if (it->new_geometry()) { - // New volume. - auto it_old_volume = std::lower_bound(deleted_volumes.begin(), deleted_volumes.end(), GLVolumeState(it->composite_id), deleted_volumes_lower); - if (it_old_volume != deleted_volumes.end() && it_old_volume->composite_id == it->composite_id) - // If a volume changed its ObjectID, but it reuses a GLVolume's CompositeID, maintain its selection. - map_glvolume_old_to_new[it_old_volume->volume_idx] = m_volumes.volumes.size(); - // Note the index of the loaded volume, so that we can reload the main model GLVolume with the hollowed mesh - // later in this function. - it->volume_idx = m_volumes.volumes.size(); -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx); -#else - m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, m_initialized); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - m_volumes.volumes.back()->geometry_id = key.geometry_id; - update_object_list = true; - } else { - // Recycling an old GLVolume. - GLVolume &existing_volume = *m_volumes.volumes[it->volume_idx]; - assert(existing_volume.geometry_id == key.geometry_id); - // Update the Object/Volume/Instance indices into the current Model. - if (existing_volume.composite_id != it->composite_id) { - existing_volume.composite_id = it->composite_id; - update_object_list = true; - } - } - } - } - } - if (printer_technology == ptSLA) { - size_t idx = 0; - const SLAPrint *sla_print = this->sla_print(); - std::vector shift_zs(m_model->objects.size(), 0); - double relative_correction_z = sla_print->relative_correction().z(); - if (relative_correction_z <= EPSILON) - relative_correction_z = 1.; - for (const SLAPrintObject *print_object : sla_print->objects()) { - SLASupportState &state = sla_support_state[idx ++]; - const ModelObject *model_object = print_object->model_object(); - // Find an index of the ModelObject - int object_idx; - // There may be new SLA volumes added to the scene for this print_object. - // Find the object index of this print_object in the Model::objects list. - auto it = std::find(sla_print->model().objects.begin(), sla_print->model().objects.end(), model_object); - assert(it != sla_print->model().objects.end()); - object_idx = it - sla_print->model().objects.begin(); - // Cache the Z offset to be applied to all volumes with this object_idx. - shift_zs[object_idx] = print_object->get_current_elevation() / relative_correction_z; - // Collect indices of this print_object's instances, for which the SLA support meshes are to be added to the scene. - // pairs of - std::vector> instances[std::tuple_size::value]; - for (size_t print_instance_idx = 0; print_instance_idx < print_object->instances().size(); ++ print_instance_idx) { - const SLAPrintObject::Instance &instance = print_object->instances()[print_instance_idx]; - // Find index of ModelInstance corresponding to this SLAPrintObject::Instance. - auto it = std::find_if(model_object->instances.begin(), model_object->instances.end(), - [&instance](const ModelInstance *mi) { return mi->id() == instance.instance_id; }); - assert(it != model_object->instances.end()); - int instance_idx = it - model_object->instances.begin(); - for (size_t istep = 0; istep < sla_steps.size(); ++ istep) - if (sla_steps[istep] == slaposDrillHoles) { - // Hollowing is a special case, where the mesh from the backend is being loaded into the 1st volume of an instance, - // not into its own GLVolume. - // There shall always be such a GLVolume allocated. - ModelVolumeState key(model_object->volumes.front()->id(), instance.instance_id); - auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower); - assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id); - assert(!it->new_geometry()); - GLVolume &volume = *m_volumes.volumes[it->volume_idx]; - if (! volume.offsets.empty() && state.step[istep].timestamp != volume.offsets.front()) { - // The backend either produced a new hollowed mesh, or it invalidated the one that the front end has seen. -#if ENABLE_LEGACY_OPENGL_REMOVAL - volume.model.reset(); -#else - volume.indexed_vertex_array.release_geometry(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - if (state.step[istep].state == PrintStateBase::DONE) { - TriangleMesh mesh = print_object->get_mesh(slaposDrillHoles); - assert(! mesh.empty()); - - // sla_trafo does not contain volume trafo. To get a mesh to create - // a new volume from, we have to apply vol trafo inverse separately. - const ModelObject& mo = *m_model->objects[volume.object_idx()]; - Transform3d trafo = sla_print->sla_trafo(mo) - * mo.volumes.front()->get_transformation().get_matrix(); - mesh.transform(trafo.inverse()); -#if ENABLE_SMOOTH_NORMALS -#if ENABLE_LEGACY_OPENGL_REMOVAL - volume.model.init_from(mesh, true); -#else - volume.indexed_vertex_array.load_mesh(mesh, true); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -#else -#if ENABLE_LEGACY_OPENGL_REMOVAL - volume.model.init_from(mesh); -#else - volume.indexed_vertex_array.load_mesh(mesh); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -#endif // ENABLE_SMOOTH_NORMALS - } - else { - // Reload the original volume. -#if ENABLE_SMOOTH_NORMALS -#if ENABLE_LEGACY_OPENGL_REMOVAL - volume.model.init_from(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh(), true); -#else - volume.indexed_vertex_array.load_mesh(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh(), true); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -#else -#if ENABLE_LEGACY_OPENGL_REMOVAL - volume.model.init_from(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh()); -#else - volume.indexed_vertex_array.load_mesh(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -#endif // ENABLE_SMOOTH_NORMALS - } -#if !ENABLE_LEGACY_OPENGL_REMOVAL - volume.finalize_geometry(true); -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - } - //FIXME it is an ugly hack to write the timestamp into the "offsets" field to not have to add another member variable - // to the GLVolume. We should refactor GLVolume significantly, so that the GLVolume will not contain member variables - // of various concenrs (model vs. 3D print path). - volume.offsets = { state.step[istep].timestamp }; - } - else if (state.step[istep].state == PrintStateBase::DONE) { - // Check whether there is an existing auxiliary volume to be updated, or a new auxiliary volume to be created. - ModelVolumeState key(state.step[istep].timestamp, instance.instance_id.id); - auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower); - assert(it != aux_volume_state.end() && it->geometry_id == key.geometry_id); - if (it->new_geometry()) { - // This can be an SLA support structure that should not be rendered (in case someone used undo - // to revert to before it was generated). If that's the case, we should not generate anything. - if (model_object->sla_points_status != sla::PointsStatus::NoPoints) - instances[istep].emplace_back(std::pair(instance_idx, print_instance_idx)); - else - shift_zs[object_idx] = 0.; - } - else { - // Recycling an old GLVolume. Update the Object/Instance indices into the current Model. - m_volumes.volumes[it->volume_idx]->composite_id = GLVolume::CompositeID(object_idx, m_volumes.volumes[it->volume_idx]->volume_idx(), instance_idx); - m_volumes.volumes[it->volume_idx]->set_instance_transformation(model_object->instances[instance_idx]->get_transformation()); - } - } - } - - for (size_t istep = 0; istep < sla_steps.size(); ++istep) - if (!instances[istep].empty()) -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp); -#else - m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp, m_initialized); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - - // Shift-up all volumes of the object so that it has the right elevation with respect to the print bed - for (GLVolume* volume : m_volumes.volumes) - if (volume->object_idx() < (int)m_model->objects.size() && m_model->objects[volume->object_idx()]->instances[volume->instance_idx()]->is_printable()) - volume->set_sla_shift_z(shift_zs[volume->object_idx()]); - } - - if (printer_technology == ptFFF && m_config->has("nozzle_diameter")) { - // Should the wipe tower be visualized ? - unsigned int extruders_count = (unsigned int)dynamic_cast(m_config->option("nozzle_diameter"))->values.size(); - - bool wt = dynamic_cast(m_config->option("wipe_tower"))->value; - bool co = dynamic_cast(m_config->option("complete_objects"))->value; - - if (extruders_count > 1 && wt && !co) { - // Height of a print (Show at least a slab) - double height = std::max(m_model->bounding_box().max(2), 10.0); - - float x = dynamic_cast(m_config->option("wipe_tower_x"))->value; - float y = dynamic_cast(m_config->option("wipe_tower_y"))->value; - float w = dynamic_cast(m_config->option("wipe_tower_width"))->value; - float a = dynamic_cast(m_config->option("wipe_tower_rotation_angle"))->value; - - const Print *print = m_process->fff_print(); - - float depth = print->wipe_tower_data(extruders_count).depth; - float brim_width = print->wipe_tower_data(extruders_count).brim_width; - -#if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( - x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower), - brim_width); -#else - int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( - 1000, x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower), - brim_width); -#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL -#else -#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( - x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower), - brim_width, m_initialized); -#else - int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( - 1000, x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower), - brim_width, m_initialized); -#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - if (volume_idx_wipe_tower_old != -1) - map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new; - } - } - - update_volumes_colors_by_extruder(); - // Update selection indices based on the old/new GLVolumeCollection. - if (m_selection.get_mode() == Selection::Instance) - m_selection.instances_changed(instance_ids_selected); - else - m_selection.volumes_changed(map_glvolume_old_to_new); - - m_gizmos.update_data(); - m_gizmos.refresh_on_off_state(); - - // Update the toolbar - if (update_object_list) - post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); - - // checks for geometry outside the print volume to render it accordingly - if (!m_volumes.empty()) { - ModelInstanceEPrintVolumeState state; - const bool contained_min_one = m_volumes.check_outside_state(m_bed.build_volume(), &state); - const bool partlyOut = (state == ModelInstanceEPrintVolumeState::ModelInstancePVS_Partly_Outside); - const bool fullyOut = (state == ModelInstanceEPrintVolumeState::ModelInstancePVS_Fully_Outside); - - _set_warning_notification(EWarning::ObjectClashed, partlyOut); - _set_warning_notification(EWarning::ObjectOutside, fullyOut); - if (printer_technology != ptSLA || !contained_min_one) - _set_warning_notification(EWarning::SlaSupportsOutside, false); - - post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, - contained_min_one && !m_model->objects.empty() && !partlyOut)); - } - else { - _set_warning_notification(EWarning::ObjectOutside, false); - _set_warning_notification(EWarning::ObjectClashed, false); - _set_warning_notification(EWarning::SlaSupportsOutside, false); - post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, false)); - } - - refresh_camera_scene_box(); - - if (m_selection.is_empty()) { - // If no object is selected, deactivate the active gizmo, if any - // Otherwise it may be shown after cleaning the scene (if it was active while the objects were deleted) - m_gizmos.reset_all_states(); - - // If no object is selected, reset the objects manipulator on the sidebar - // to force a reset of its cache - auto manip = wxGetApp().obj_manipul(); - if (manip != nullptr) - manip->set_dirty(); - } - - // and force this canvas to be redrawn. - m_dirty = true; -} - -#if !ENABLE_LEGACY_OPENGL_REMOVAL -static void reserve_new_volume_finalize_old_volume(GLVolume& vol_new, GLVolume& vol_old, bool gl_initialized, size_t prealloc_size = VERTEX_BUFFER_RESERVE_SIZE) -{ - // Assign the large pre-allocated buffers to the new GLVolume. - vol_new.indexed_vertex_array = std::move(vol_old.indexed_vertex_array); - // Copy the content back to the old GLVolume. - vol_old.indexed_vertex_array = vol_new.indexed_vertex_array; - // Clear the buffers, but keep them pre-allocated. - vol_new.indexed_vertex_array.clear(); - // Just make sure that clear did not clear the reserved memory. - // Reserving number of vertices (3x position + 3x color) - vol_new.indexed_vertex_array.reserve(prealloc_size / 6); - // Finalize the old geometry, possibly move data to the graphics card. - vol_old.finalize_geometry(gl_initialized); -} -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - -void GLCanvas3D::load_gcode_preview(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors) -{ -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_gcode_viewer.load(gcode_result, *this->fff_print()); -#else - m_gcode_viewer.load(gcode_result, *this->fff_print(), m_initialized); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - if (wxGetApp().is_editor()) { - m_gcode_viewer.update_shells_color_by_extruder(m_config); - _set_warning_notification_if_needed(EWarning::ToolpathOutside); - } - - m_gcode_viewer.refresh(gcode_result, str_tool_colors); - set_as_dirty(); - request_extra_frame(); -} - -#if ENABLE_PREVIEW_LAYOUT -void GLCanvas3D::refresh_gcode_preview_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) -{ - m_gcode_viewer.refresh_render_paths(keep_sequential_current_first, keep_sequential_current_last); - set_as_dirty(); - request_extra_frame(); -} -#else -void GLCanvas3D::refresh_gcode_preview_render_paths() -{ - m_gcode_viewer.refresh_render_paths(); - set_as_dirty(); - request_extra_frame(); -} -#endif // ENABLE_PREVIEW_LAYOUT - -void GLCanvas3D::load_sla_preview() -{ - const SLAPrint* print = sla_print(); - if (m_canvas != nullptr && print != nullptr) { - _set_current(); - // Release OpenGL data before generating new data. - reset_volumes(); - _load_sla_shells(); - _update_sla_shells_outside_state(); - _set_warning_notification_if_needed(EWarning::SlaSupportsOutside); - } -} - -void GLCanvas3D::load_preview(const std::vector& str_tool_colors, const std::vector& color_print_values) -{ - const Print *print = this->fff_print(); - if (print == nullptr) - return; - - _set_current(); - - // Release OpenGL data before generating new data. - this->reset_volumes(); - - const BuildVolume &build_volume = m_bed.build_volume(); - _load_print_toolpaths(build_volume); - _load_wipe_tower_toolpaths(build_volume, str_tool_colors); - for (const PrintObject* object : print->objects()) - _load_print_object_toolpaths(*object, build_volume, str_tool_colors, color_print_values); - - _set_warning_notification_if_needed(EWarning::ToolpathOutside); -} - -void GLCanvas3D::bind_event_handlers() -{ - if (m_canvas != nullptr) { - m_canvas->Bind(wxEVT_SIZE, &GLCanvas3D::on_size, this); - m_canvas->Bind(wxEVT_IDLE, &GLCanvas3D::on_idle, this); - m_canvas->Bind(wxEVT_CHAR, &GLCanvas3D::on_char, this); - m_canvas->Bind(wxEVT_KEY_DOWN, &GLCanvas3D::on_key, this); - m_canvas->Bind(wxEVT_KEY_UP, &GLCanvas3D::on_key, this); - m_canvas->Bind(wxEVT_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this); - m_canvas->Bind(wxEVT_TIMER, &GLCanvas3D::on_timer, this); - m_canvas->Bind(EVT_GLCANVAS_RENDER_TIMER, &GLCanvas3D::on_render_timer, this); - m_toolbar_highlighter.set_timer_owner(m_canvas, 0); - m_canvas->Bind(EVT_GLCANVAS_TOOLBAR_HIGHLIGHTER_TIMER, [this](wxTimerEvent&) { m_toolbar_highlighter.blink(); }); - m_gizmo_highlighter.set_timer_owner(m_canvas, 0); - m_canvas->Bind(EVT_GLCANVAS_GIZMO_HIGHLIGHTER_TIMER, [this](wxTimerEvent&) { m_gizmo_highlighter.blink(); }); - m_canvas->Bind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this); - m_canvas->Bind(wxEVT_LEFT_UP, &GLCanvas3D::on_mouse, this); - m_canvas->Bind(wxEVT_MIDDLE_DOWN, &GLCanvas3D::on_mouse, this); - m_canvas->Bind(wxEVT_MIDDLE_UP, &GLCanvas3D::on_mouse, this); - m_canvas->Bind(wxEVT_RIGHT_DOWN, &GLCanvas3D::on_mouse, this); - m_canvas->Bind(wxEVT_RIGHT_UP, &GLCanvas3D::on_mouse, this); - m_canvas->Bind(wxEVT_MOTION, &GLCanvas3D::on_mouse, this); - m_canvas->Bind(wxEVT_ENTER_WINDOW, &GLCanvas3D::on_mouse, this); - m_canvas->Bind(wxEVT_LEAVE_WINDOW, &GLCanvas3D::on_mouse, this); - m_canvas->Bind(wxEVT_LEFT_DCLICK, &GLCanvas3D::on_mouse, this); - m_canvas->Bind(wxEVT_MIDDLE_DCLICK, &GLCanvas3D::on_mouse, this); - m_canvas->Bind(wxEVT_RIGHT_DCLICK, &GLCanvas3D::on_mouse, this); - m_canvas->Bind(wxEVT_PAINT, &GLCanvas3D::on_paint, this); - m_canvas->Bind(wxEVT_SET_FOCUS, &GLCanvas3D::on_set_focus, this); - - m_event_handlers_bound = true; - } -} - -void GLCanvas3D::unbind_event_handlers() -{ - if (m_canvas != nullptr && m_event_handlers_bound) { - m_canvas->Unbind(wxEVT_SIZE, &GLCanvas3D::on_size, this); - m_canvas->Unbind(wxEVT_IDLE, &GLCanvas3D::on_idle, this); - m_canvas->Unbind(wxEVT_CHAR, &GLCanvas3D::on_char, this); - m_canvas->Unbind(wxEVT_KEY_DOWN, &GLCanvas3D::on_key, this); - m_canvas->Unbind(wxEVT_KEY_UP, &GLCanvas3D::on_key, this); - m_canvas->Unbind(wxEVT_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this); - m_canvas->Unbind(wxEVT_TIMER, &GLCanvas3D::on_timer, this); - m_canvas->Unbind(EVT_GLCANVAS_RENDER_TIMER, &GLCanvas3D::on_render_timer, this); - m_canvas->Unbind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this); - m_canvas->Unbind(wxEVT_LEFT_UP, &GLCanvas3D::on_mouse, this); - m_canvas->Unbind(wxEVT_MIDDLE_DOWN, &GLCanvas3D::on_mouse, this); - m_canvas->Unbind(wxEVT_MIDDLE_UP, &GLCanvas3D::on_mouse, this); - m_canvas->Unbind(wxEVT_RIGHT_DOWN, &GLCanvas3D::on_mouse, this); - m_canvas->Unbind(wxEVT_RIGHT_UP, &GLCanvas3D::on_mouse, this); - m_canvas->Unbind(wxEVT_MOTION, &GLCanvas3D::on_mouse, this); - m_canvas->Unbind(wxEVT_ENTER_WINDOW, &GLCanvas3D::on_mouse, this); - m_canvas->Unbind(wxEVT_LEAVE_WINDOW, &GLCanvas3D::on_mouse, this); - m_canvas->Unbind(wxEVT_LEFT_DCLICK, &GLCanvas3D::on_mouse, this); - m_canvas->Unbind(wxEVT_MIDDLE_DCLICK, &GLCanvas3D::on_mouse, this); - m_canvas->Unbind(wxEVT_RIGHT_DCLICK, &GLCanvas3D::on_mouse, this); - m_canvas->Unbind(wxEVT_PAINT, &GLCanvas3D::on_paint, this); - m_canvas->Unbind(wxEVT_SET_FOCUS, &GLCanvas3D::on_set_focus, this); - - m_event_handlers_bound = false; - } -} - -void GLCanvas3D::on_size(wxSizeEvent& evt) -{ - m_dirty = true; -} - -void GLCanvas3D::on_idle(wxIdleEvent& evt) -{ - if (!m_initialized) - return; - - m_dirty |= m_main_toolbar.update_items_state(); - m_dirty |= m_undoredo_toolbar.update_items_state(); - m_dirty |= wxGetApp().plater()->get_view_toolbar().update_items_state(); - m_dirty |= wxGetApp().plater()->get_collapse_toolbar().update_items_state(); - bool mouse3d_controller_applied = wxGetApp().plater()->get_mouse3d_controller().apply(wxGetApp().plater()->get_camera()); - m_dirty |= mouse3d_controller_applied; - m_dirty |= wxGetApp().plater()->get_notification_manager()->update_notifications(*this); - auto gizmo = wxGetApp().plater()->canvas3D()->get_gizmos_manager().get_current(); - if (gizmo != nullptr) m_dirty |= gizmo->update_items_state(); - // ImGuiWrapper::m_requires_extra_frame may have been set by a render made outside of the OnIdle mechanism - bool imgui_requires_extra_frame = wxGetApp().imgui()->requires_extra_frame(); - m_dirty |= imgui_requires_extra_frame; - - if (!m_dirty) - return; - - // this needs to be done here. - // during the render launched by the refresh the value may be set again - wxGetApp().imgui()->reset_requires_extra_frame(); - - _refresh_if_shown_on_screen(); - - if (m_extra_frame_requested || mouse3d_controller_applied || imgui_requires_extra_frame || wxGetApp().imgui()->requires_extra_frame()) { - m_extra_frame_requested = false; - evt.RequestMore(); - } - else - m_dirty = false; -} - -void GLCanvas3D::on_char(wxKeyEvent& evt) -{ - if (!m_initialized) - return; - - // see include/wx/defs.h enum wxKeyCode - int keyCode = evt.GetKeyCode(); - int ctrlMask = wxMOD_CONTROL; - int shiftMask = wxMOD_SHIFT; - - auto imgui = wxGetApp().imgui(); - if (imgui->update_key_data(evt)) { - render(); - return; - } - - if (keyCode == WXK_ESCAPE && (_deactivate_undo_redo_toolbar_items() || _deactivate_search_toolbar_item() || _deactivate_arrange_menu())) - return; - - if (m_gizmos.on_char(evt)) - return; - - if ((evt.GetModifiers() & ctrlMask) != 0) { - // CTRL is pressed - switch (keyCode) { -#ifdef __APPLE__ - case 'a': - case 'A': -#else /* __APPLE__ */ - case WXK_CONTROL_A: -#endif /* __APPLE__ */ - post_event(SimpleEvent(EVT_GLCANVAS_SELECT_ALL)); - break; -#ifdef __APPLE__ - case 'c': - case 'C': -#else /* __APPLE__ */ - case WXK_CONTROL_C: -#endif /* __APPLE__ */ - post_event(SimpleEvent(EVT_GLTOOLBAR_COPY)); - break; -#ifdef __APPLE__ - case 'm': - case 'M': -#else /* __APPLE__ */ - case WXK_CONTROL_M: -#endif /* __APPLE__ */ - { -#ifdef _WIN32 - if (wxGetApp().app_config->get("use_legacy_3DConnexion") == "1") { -#endif //_WIN32 -#ifdef __APPLE__ - // On OSX use Cmd+Shift+M to "Show/Hide 3Dconnexion devices settings dialog" - if ((evt.GetModifiers() & shiftMask) != 0) { -#endif // __APPLE__ - Mouse3DController& controller = wxGetApp().plater()->get_mouse3d_controller(); - controller.show_settings_dialog(!controller.is_settings_dialog_shown()); - m_dirty = true; -#ifdef __APPLE__ - } - else - // and Cmd+M to minimize application - wxGetApp().mainframe->Iconize(); -#endif // __APPLE__ -#ifdef _WIN32 - } -#endif //_WIN32 - break; - } -#ifdef __APPLE__ - case 'v': - case 'V': -#else /* __APPLE__ */ - case WXK_CONTROL_V: -#endif /* __APPLE__ */ - post_event(SimpleEvent(EVT_GLTOOLBAR_PASTE)); - break; - - -#ifdef __APPLE__ - case 'f': - case 'F': -#else /* __APPLE__ */ - case WXK_CONTROL_F: -#endif /* __APPLE__ */ - _activate_search_toolbar_item(); - break; - - -#ifdef __APPLE__ - case 'y': - case 'Y': -#else /* __APPLE__ */ - case WXK_CONTROL_Y: -#endif /* __APPLE__ */ - post_event(SimpleEvent(EVT_GLCANVAS_REDO)); - break; -#ifdef __APPLE__ - case 'z': - case 'Z': -#else /* __APPLE__ */ - case WXK_CONTROL_Z: -#endif /* __APPLE__ */ - post_event(SimpleEvent(EVT_GLCANVAS_UNDO)); - break; - - case WXK_BACK: - case WXK_DELETE: - post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); break; - default: evt.Skip(); - } - } else { - switch (keyCode) - { - case WXK_BACK: - case WXK_DELETE: { post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE)); break; } - case WXK_ESCAPE: { deselect_all(); break; } - case WXK_F5: { - if ((wxGetApp().is_editor() && !wxGetApp().plater()->model().objects.empty()) || - (wxGetApp().is_gcode_viewer() && !wxGetApp().plater()->get_last_loaded_gcode().empty())) - post_event(SimpleEvent(EVT_GLCANVAS_RELOAD_FROM_DISK)); - break; - } - case '0': { select_view("iso"); break; } - case '1': { select_view("top"); break; } - case '2': { select_view("bottom"); break; } - case '3': { select_view("front"); break; } - case '4': { select_view("rear"); break; } - case '5': { select_view("left"); break; } - case '6': { select_view("right"); break; } - case '+': { - if (dynamic_cast(m_canvas->GetParent()) != nullptr) - post_event(wxKeyEvent(EVT_GLCANVAS_EDIT_COLOR_CHANGE, evt)); - else - post_event(Event(EVT_GLCANVAS_INCREASE_INSTANCES, +1)); - break; - } - case '-': { - if (dynamic_cast(m_canvas->GetParent()) != nullptr) - post_event(wxKeyEvent(EVT_GLCANVAS_EDIT_COLOR_CHANGE, evt)); - else - post_event(Event(EVT_GLCANVAS_INCREASE_INSTANCES, -1)); - break; - } - case '?': { post_event(SimpleEvent(EVT_GLCANVAS_QUESTION_MARK)); break; } - case 'A': - case 'a': { post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE)); break; } - case 'B': - case 'b': { zoom_to_bed(); break; } - case 'C': - case 'c': { m_gcode_viewer.toggle_gcode_window_visibility(); m_dirty = true; request_extra_frame(); break; } - case 'E': - case 'e': { m_labels.show(!m_labels.is_shown()); m_dirty = true; break; } - case 'G': - case 'g': { - if ((evt.GetModifiers() & shiftMask) != 0) { - if (dynamic_cast(m_canvas->GetParent()) != nullptr) - post_event(wxKeyEvent(EVT_GLCANVAS_JUMP_TO, evt)); - } - break; - } - case 'I': - case 'i': { _update_camera_zoom(1.0); break; } - case 'K': - case 'k': { wxGetApp().plater()->get_camera().select_next_type(); m_dirty = true; break; } - case 'L': - case 'l': { - if (!m_main_toolbar.is_enabled()) { -#if ENABLE_PREVIEW_LAYOUT - show_legend(!is_legend_shown()); -#else - m_gcode_viewer.enable_legend(!m_gcode_viewer.is_legend_enabled()); - m_dirty = true; - wxGetApp().plater()->update_preview_bottom_toolbar(); -#endif // ENABLE_PREVIEW_LAYOUT - } - break; - } - case 'O': - case 'o': { _update_camera_zoom(-1.0); break; } -#if ENABLE_RENDER_PICKING_PASS - case 'T': - case 't': { - m_show_picking_texture = !m_show_picking_texture; - m_dirty = true; - break; - } -#endif // ENABLE_RENDER_PICKING_PASS - case 'Z': - case 'z': { - if (!m_selection.is_empty()) - zoom_to_selection(); - else { - if (!m_volumes.empty()) - zoom_to_volumes(); - else - _zoom_to_box(m_gcode_viewer.get_paths_bounding_box()); - } - break; - } - default: { evt.Skip(); break; } - } - } -} - -class TranslationProcessor -{ - using UpAction = std::function; - using DownAction = std::function; - - UpAction m_up_action{ nullptr }; - DownAction m_down_action{ nullptr }; - - bool m_running{ false }; - Vec3d m_direction{ Vec3d::UnitX() }; - -public: - TranslationProcessor(UpAction up_action, DownAction down_action) - : m_up_action(up_action), m_down_action(down_action) - { - } - - void process(wxKeyEvent& evt) - { - const int keyCode = evt.GetKeyCode(); - wxEventType type = evt.GetEventType(); - if (type == wxEVT_KEY_UP) { - switch (keyCode) - { - case WXK_NUMPAD_LEFT: case WXK_LEFT: - case WXK_NUMPAD_RIGHT: case WXK_RIGHT: - case WXK_NUMPAD_UP: case WXK_UP: - case WXK_NUMPAD_DOWN: case WXK_DOWN: - { - m_running = false; - m_up_action(); - break; - } - default: { break; } - } - } - else if (type == wxEVT_KEY_DOWN) { - bool apply = false; - - switch (keyCode) - { - case WXK_SHIFT: - { - if (m_running) - apply = true; - - break; - } - case WXK_NUMPAD_LEFT: - case WXK_LEFT: - { - m_direction = -Vec3d::UnitX(); - apply = true; - break; - } - case WXK_NUMPAD_RIGHT: - case WXK_RIGHT: - { - m_direction = Vec3d::UnitX(); - apply = true; - break; - } - case WXK_NUMPAD_UP: - case WXK_UP: - { - m_direction = Vec3d::UnitY(); - apply = true; - break; - } - case WXK_NUMPAD_DOWN: - case WXK_DOWN: - { - m_direction = -Vec3d::UnitY(); - apply = true; - break; - } - default: { break; } - } - - if (apply) { - m_running = true; - m_down_action(m_direction, evt.ShiftDown(), evt.CmdDown()); - } - } - } -}; - -void GLCanvas3D::on_key(wxKeyEvent& evt) -{ - static TranslationProcessor translationProcessor( - [this]() { - do_move(L("Gizmo-Move")); - m_gizmos.update_data(); - - wxGetApp().obj_manipul()->set_dirty(); - // Let the plater know that the dragging finished, so a delayed refresh - // of the scene with the background processing data should be performed. - post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); - // updates camera target constraints - refresh_camera_scene_box(); - m_dirty = true; - }, - [this](const Vec3d& direction, bool slow, bool camera_space) { - m_selection.setup_cache(); - double multiplier = slow ? 1.0 : 10.0; - - Vec3d displacement; - if (camera_space) { - Eigen::Matrix inv_view_3x3 = wxGetApp().plater()->get_camera().get_view_matrix().inverse().matrix().block(0, 0, 3, 3); - displacement = multiplier * (inv_view_3x3 * direction); - displacement.z() = 0.0; - } - else - displacement = multiplier * direction; - - m_selection.translate(displacement); - m_dirty = true; - } - ); - - const int keyCode = evt.GetKeyCode(); - - auto imgui = wxGetApp().imgui(); - if (imgui->update_key_data(evt)) { - render(); - } - else { - if (!m_gizmos.on_key(evt)) { - if (evt.GetEventType() == wxEVT_KEY_UP) { - if (evt.ShiftDown() && evt.ControlDown() && keyCode == WXK_SPACE) { - wxGetApp().plater()->toggle_render_statistic_dialog(); - m_dirty = true; - } - if (m_tab_down && keyCode == WXK_TAB && !evt.HasAnyModifiers()) { - // Enable switching between 3D and Preview with Tab - // m_canvas->HandleAsNavigationKey(evt); // XXX: Doesn't work in some cases / on Linux - post_event(SimpleEvent(EVT_GLCANVAS_TAB)); - } - else if (keyCode == WXK_TAB && evt.ShiftDown() && ! wxGetApp().is_gcode_viewer()) { - // Collapse side-panel with Shift+Tab - post_event(SimpleEvent(EVT_GLCANVAS_COLLAPSE_SIDEBAR)); - } - else if (keyCode == WXK_SHIFT) { - translationProcessor.process(evt); - - if (m_picking_enabled && m_rectangle_selection.is_dragging()) { - _update_selection_from_hover(); - m_rectangle_selection.stop_dragging(); - m_mouse.ignore_left_up = true; -#if !ENABLE_NEW_RECTANGLE_SELECTION - m_dirty = true; -#endif // !ENABLE_NEW_RECTANGLE_SELECTION - } -#if ENABLE_NEW_RECTANGLE_SELECTION - m_dirty = true; -#endif // ENABLE_NEW_RECTANGLE_SELECTION -// set_cursor(Standard); - } - else if (keyCode == WXK_ALT) { - if (m_picking_enabled && m_rectangle_selection.is_dragging()) { - _update_selection_from_hover(); - m_rectangle_selection.stop_dragging(); - m_mouse.ignore_left_up = true; - m_dirty = true; - } -// set_cursor(Standard); - } - else if (keyCode == WXK_CONTROL) { -#if ENABLE_NEW_CAMERA_MOVEMENTS - if (m_mouse.dragging) { - // if the user releases CTRL while rotating the 3D scene - // prevent from moving the selected volume - m_mouse.drag.move_volume_idx = -1; - m_mouse.set_start_position_3D_as_invalid(); - } -#endif // ENABLE_NEW_CAMERA_MOVEMENTS - m_dirty = true; - } - else if (m_gizmos.is_enabled() && !m_selection.is_empty()) { - translationProcessor.process(evt); - - switch (keyCode) - { - case WXK_NUMPAD_PAGEUP: case WXK_PAGEUP: - case WXK_NUMPAD_PAGEDOWN: case WXK_PAGEDOWN: - { - do_rotate(L("Gizmo-Rotate")); - m_gizmos.update_data(); - - wxGetApp().obj_manipul()->set_dirty(); - // Let the plater know that the dragging finished, so a delayed refresh - // of the scene with the background processing data should be performed. - post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); - // updates camera target constraints - refresh_camera_scene_box(); - m_dirty = true; - - break; - } - default: { break; } - } - } - } - else if (evt.GetEventType() == wxEVT_KEY_DOWN) { - m_tab_down = keyCode == WXK_TAB && !evt.HasAnyModifiers(); - if (keyCode == WXK_SHIFT) { - translationProcessor.process(evt); - - if (m_picking_enabled && (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports)) { - m_mouse.ignore_left_up = false; -// set_cursor(Cross); - } -#if ENABLE_NEW_RECTANGLE_SELECTION - m_dirty = true; -#endif // ENABLE_NEW_RECTANGLE_SELECTION - } - else if (keyCode == WXK_ALT) { - if (m_picking_enabled && (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports)) { - m_mouse.ignore_left_up = false; -// set_cursor(Cross); - } - } - else if (keyCode == WXK_CONTROL) - m_dirty = true; - else if (m_gizmos.is_enabled() && !m_selection.is_empty()) { - auto do_rotate = [this](double angle_z_rad) { - m_selection.setup_cache(); - m_selection.rotate(Vec3d(0.0, 0.0, angle_z_rad), TransformationType(TransformationType::World_Relative_Joint)); - m_dirty = true; -// wxGetApp().obj_manipul()->set_dirty(); - }; - - translationProcessor.process(evt); - - switch (keyCode) - { - case WXK_NUMPAD_PAGEUP: case WXK_PAGEUP: { do_rotate(0.25 * M_PI); break; } - case WXK_NUMPAD_PAGEDOWN: case WXK_PAGEDOWN: { do_rotate(-0.25 * M_PI); break; } - default: { break; } - } - } - else if (!m_gizmos.is_enabled()) { - // DoubleSlider navigation in Preview - if (keyCode == WXK_LEFT || - keyCode == WXK_RIGHT || - keyCode == WXK_UP || - keyCode == WXK_DOWN) { - if (dynamic_cast(m_canvas->GetParent()) != nullptr) - post_event(wxKeyEvent(EVT_GLCANVAS_MOVE_SLIDERS, evt)); - } - } - } - } - } - - if (keyCode != WXK_TAB - && keyCode != WXK_LEFT - && keyCode != WXK_UP - && keyCode != WXK_RIGHT - && keyCode != WXK_DOWN) { - evt.Skip(); // Needed to have EVT_CHAR generated as well - } -} - -void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt) -{ -#ifdef WIN32 - // Try to filter out spurious mouse wheel events comming from 3D mouse. - if (wxGetApp().plater()->get_mouse3d_controller().process_mouse_wheel()) - return; -#endif - - if (!m_initialized) - return; - - // Ignore the wheel events if the middle button is pressed. - if (evt.MiddleIsDown()) - return; - -#if ENABLE_RETINA_GL - const float scale = m_retina_helper->get_scale_factor(); - evt.SetX(evt.GetX() * scale); - evt.SetY(evt.GetY() * scale); -#endif - - if (wxGetApp().imgui()->update_mouse_data(evt)) { - m_dirty = true; - return; - } - -#ifdef __WXMSW__ - // For some reason the Idle event is not being generated after the mouse scroll event in case of scrolling with the two fingers on the touch pad, - // if the event is not allowed to be passed further. - // https://github.com/prusa3d/PrusaSlicer/issues/2750 - // evt.Skip() used to trigger the needed screen refresh, but it does no more. wxWakeUpIdle() seem to work now. - wxWakeUpIdle(); -#endif /* __WXMSW__ */ - - // Performs layers editing updates, if enabled - if (is_layers_editing_enabled()) { - int object_idx_selected = m_selection.get_object_idx(); - if (object_idx_selected != -1) { - // A volume is selected. Test, whether hovering over a layer thickness bar. - if (m_layers_editing.bar_rect_contains(*this, (float)evt.GetX(), (float)evt.GetY())) { - // Adjust the width of the selection. - m_layers_editing.band_width = std::max(std::min(m_layers_editing.band_width * (1.0f + 0.1f * (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta()), 10.0f), 1.5f); - if (m_canvas != nullptr) - m_canvas->Refresh(); - - return; - } - } - } - - // If the Search window or Undo/Redo list is opened, - // update them according to the event - if (m_main_toolbar.is_item_pressed("search") || - m_undoredo_toolbar.is_item_pressed("undo") || - m_undoredo_toolbar.is_item_pressed("redo")) { - m_mouse_wheel = int((double)evt.GetWheelRotation() / (double)evt.GetWheelDelta()); - return; - } - - // Inform gizmos about the event so they have the opportunity to react. - if (m_gizmos.on_mouse_wheel(evt)) - return; - - // Calculate the zoom delta and apply it to the current zoom factor - double direction_factor = (wxGetApp().app_config->get("reverse_mouse_wheel_zoom") == "1") ? -1.0 : 1.0; - _update_camera_zoom(direction_factor * (double)evt.GetWheelRotation() / (double)evt.GetWheelDelta()); -} - -void GLCanvas3D::on_timer(wxTimerEvent& evt) -{ - if (m_layers_editing.state == LayersEditing::Editing) - _perform_layer_editing_action(); -} - -void GLCanvas3D::on_render_timer(wxTimerEvent& evt) -{ - // no need to wake up idle - // right after this event, idle event is fired - // m_dirty = true; - // wxWakeUpIdle(); -} - - -void GLCanvas3D::schedule_extra_frame(int miliseconds) -{ - // Schedule idle event right now - if (miliseconds == 0) - { - // We want to wakeup idle evnt but most likely this is call inside render cycle so we need to wait - if (m_in_render) - miliseconds = 33; - else { - m_dirty = true; - wxWakeUpIdle(); - return; - } - } - int remaining_time = m_render_timer.GetInterval(); - // Timer is not running - if (!m_render_timer.IsRunning()) { - m_render_timer.StartOnce(miliseconds); - // Timer is running - restart only if new period is shorter than remaning period - } else { - if (miliseconds + 20 < remaining_time) { - m_render_timer.Stop(); - m_render_timer.StartOnce(miliseconds); - } - } -} - -#ifndef NDEBUG -// #define SLIC3R_DEBUG_MOUSE_EVENTS -#endif - -#ifdef SLIC3R_DEBUG_MOUSE_EVENTS -std::string format_mouse_event_debug_message(const wxMouseEvent &evt) -{ - static int idx = 0; - char buf[2048]; - std::string out; - sprintf(buf, "Mouse Event %d - ", idx ++); - out = buf; - - if (evt.Entering()) - out += "Entering "; - if (evt.Leaving()) - out += "Leaving "; - if (evt.Dragging()) - out += "Dragging "; - if (evt.Moving()) - out += "Moving "; - if (evt.Magnify()) - out += "Magnify "; - if (evt.LeftDown()) - out += "LeftDown "; - if (evt.LeftUp()) - out += "LeftUp "; - if (evt.LeftDClick()) - out += "LeftDClick "; - if (evt.MiddleDown()) - out += "MiddleDown "; - if (evt.MiddleUp()) - out += "MiddleUp "; - if (evt.MiddleDClick()) - out += "MiddleDClick "; - if (evt.RightDown()) - out += "RightDown "; - if (evt.RightUp()) - out += "RightUp "; - if (evt.RightDClick()) - out += "RightDClick "; - - sprintf(buf, "(%d, %d)", evt.GetX(), evt.GetY()); - out += buf; - return out; -} -#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */ - -void GLCanvas3D::on_mouse(wxMouseEvent& evt) -{ - if (!m_initialized || !_set_current()) - return; - -#if ENABLE_RETINA_GL - const float scale = m_retina_helper->get_scale_factor(); - evt.SetX(evt.GetX() * scale); - evt.SetY(evt.GetY() * scale); -#endif - - Point pos(evt.GetX(), evt.GetY()); - - ImGuiWrapper* imgui = wxGetApp().imgui(); - if (m_tooltip.is_in_imgui() && evt.LeftUp()) - // ignore left up events coming from imgui windows and not processed by them - m_mouse.ignore_left_up = true; - m_tooltip.set_in_imgui(false); - if (imgui->update_mouse_data(evt)) { - m_mouse.position = evt.Leaving() ? Vec2d(-1.0, -1.0) : pos.cast(); - m_tooltip.set_in_imgui(true); - render(); -#ifdef SLIC3R_DEBUG_MOUSE_EVENTS - printf((format_mouse_event_debug_message(evt) + " - Consumed by ImGUI\n").c_str()); -#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */ - m_dirty = true; - // do not return if dragging or tooltip not empty to allow for tooltip update - // also, do not return if the mouse is moving and also is inside MM gizmo to allow update seed fill selection - if (!m_mouse.dragging && m_tooltip.is_empty() && (m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation || !evt.Moving())) - return; - } - -#ifdef __WXMSW__ - bool on_enter_workaround = false; - if (! evt.Entering() && ! evt.Leaving() && m_mouse.position.x() == -1.0) { - // Workaround for SPE-832: There seems to be a mouse event sent to the window before evt.Entering() - m_mouse.position = pos.cast(); - render(); -#ifdef SLIC3R_DEBUG_MOUSE_EVENTS - printf((format_mouse_event_debug_message(evt) + " - OnEnter workaround\n").c_str()); -#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */ - on_enter_workaround = true; - } else -#endif /* __WXMSW__ */ - { -#ifdef SLIC3R_DEBUG_MOUSE_EVENTS - printf((format_mouse_event_debug_message(evt) + " - other\n").c_str()); -#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */ - } - - if (m_main_toolbar.on_mouse(evt, *this)) { - if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) - mouse_up_cleanup(); - m_mouse.set_start_position_3D_as_invalid(); -#if ENABLE_OBJECT_MANIPULATOR_FOCUS - handle_sidebar_focus_event("", false); -#endif // ENABLE_OBJECT_MANIPULATOR_FOCUS - return; - } - - if (m_undoredo_toolbar.on_mouse(evt, *this)) { - if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) - mouse_up_cleanup(); - m_mouse.set_start_position_3D_as_invalid(); -#if ENABLE_OBJECT_MANIPULATOR_FOCUS - handle_sidebar_focus_event("", false); -#endif // ENABLE_OBJECT_MANIPULATOR_FOCUS - return; - } - - if (wxGetApp().plater()->get_collapse_toolbar().on_mouse(evt, *this)) { - if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) - mouse_up_cleanup(); - m_mouse.set_start_position_3D_as_invalid(); -#if ENABLE_OBJECT_MANIPULATOR_FOCUS - handle_sidebar_focus_event("", false); -#endif // ENABLE_OBJECT_MANIPULATOR_FOCUS - return; - } - - if (wxGetApp().plater()->get_view_toolbar().on_mouse(evt, *this)) { - if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) - mouse_up_cleanup(); - m_mouse.set_start_position_3D_as_invalid(); -#if ENABLE_OBJECT_MANIPULATOR_FOCUS - handle_sidebar_focus_event("", false); -#endif // ENABLE_OBJECT_MANIPULATOR_FOCUS - return; - } - - for (GLVolume* volume : m_volumes.volumes) { - volume->force_sinking_contours = false; - } - - auto show_sinking_contours = [this]() { - const Selection::IndicesList& idxs = m_selection.get_volume_idxs(); - for (unsigned int idx : idxs) { - m_volumes.volumes[idx]->force_sinking_contours = true; - } - m_dirty = true; - }; - - if (m_gizmos.on_mouse(evt)) { - if (wxWindow::FindFocus() != m_canvas) - // Grab keyboard focus for input in gizmo dialogs. - m_canvas->SetFocus(); - - if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) - mouse_up_cleanup(); - - m_mouse.set_start_position_3D_as_invalid(); - m_mouse.position = pos.cast(); - - // It should be detection of volume change - // Not only detection of some modifiers !!! - if (evt.Dragging()) { - GLGizmosManager::EType c = m_gizmos.get_current_type(); - if (current_printer_technology() == ptFFF && - fff_print()->config().complete_objects){ - if (c == GLGizmosManager::EType::Move || - c == GLGizmosManager::EType::Scale || - c == GLGizmosManager::EType::Rotate ) - update_sequential_clearance(); - } else { - if (c == GLGizmosManager::EType::Move || - c == GLGizmosManager::EType::Scale || - c == GLGizmosManager::EType::Rotate) - show_sinking_contours(); - } - } - -#if ENABLE_OBJECT_MANIPULATOR_FOCUS - handle_sidebar_focus_event("", false); -#endif // ENABLE_OBJECT_MANIPULATOR_FOCUS - return; - } - - bool any_gizmo_active = m_gizmos.get_current() != nullptr; - - int selected_object_idx = m_selection.get_object_idx(); - int layer_editing_object_idx = is_layers_editing_enabled() ? selected_object_idx : -1; - m_layers_editing.select_object(*m_model, layer_editing_object_idx); - - if (m_mouse.drag.move_requires_threshold && m_mouse.is_move_start_threshold_position_2D_defined() && m_mouse.is_move_threshold_met(pos)) { - m_mouse.drag.move_requires_threshold = false; - m_mouse.set_move_start_threshold_position_2D_as_invalid(); - } - -#if ENABLE_OBJECT_MANIPULATOR_FOCUS - if (evt.ButtonDown()) { - std::string curr_sidebar_field = m_sidebar_field; - handle_sidebar_focus_event("", false); - if (boost::algorithm::istarts_with(curr_sidebar_field, "layer")) - // restore visibility of layers hints after left clicking on the 3D scene - m_sidebar_field = curr_sidebar_field; - if (wxWindow::FindFocus() != m_canvas) - // Grab keyboard focus on any mouse click event. - m_canvas->SetFocus(); - } -#else - if (evt.ButtonDown() && wxWindow::FindFocus() != m_canvas) - // Grab keyboard focus on any mouse click event. - m_canvas->SetFocus(); -#endif // ENABLE_OBJECT_MANIPULATOR_FOCUS - - if (evt.Entering()) { -//#if defined(__WXMSW__) || defined(__linux__) -// // On Windows and Linux needs focus in order to catch key events -#if !ENABLE_OBJECT_MANIPULATOR_FOCUS - // Set focus in order to remove it from sidebar fields -#endif // !ENABLE_OBJECT_MANIPULATOR_FOCUS - if (m_canvas != nullptr) { -#if !ENABLE_OBJECT_MANIPULATOR_FOCUS - // Only set focus, if the top level window of this canvas is active. - auto p = dynamic_cast(evt.GetEventObject()); - while (p->GetParent()) - p = p->GetParent(); - auto *top_level_wnd = dynamic_cast(p); - if (top_level_wnd && top_level_wnd->IsActive()) - m_canvas->SetFocus(); -#endif // !ENABLE_OBJECT_MANIPULATOR_FOCUS - m_mouse.position = pos.cast(); - m_tooltip_enabled = false; - // 1) forces a frame render to ensure that m_hover_volume_idxs is updated even when the user right clicks while - // the context menu is shown, ensuring it to disappear if the mouse is outside any volume and to - // change the volume hover state if any is under the mouse - // 2) when switching between 3d view and preview the size of the canvas changes if the side panels are visible, - // so forces a resize to avoid multiple renders with different sizes (seen as flickering) - _refresh_if_shown_on_screen(); - m_tooltip_enabled = true; - } - m_mouse.set_start_position_2D_as_invalid(); -//#endif - } - else if (evt.Leaving()) { - _deactivate_undo_redo_toolbar_items(); - - // to remove hover on objects when the mouse goes out of this canvas - m_mouse.position = Vec2d(-1.0, -1.0); - m_dirty = true; - } - else if (evt.LeftDown() || evt.RightDown() || evt.MiddleDown()) { - if (_deactivate_undo_redo_toolbar_items() || _deactivate_search_toolbar_item() || _deactivate_arrange_menu()) - return; - - // If user pressed left or right button we first check whether this happened - // on a volume or not. - m_layers_editing.state = LayersEditing::Unknown; - if (layer_editing_object_idx != -1 && m_layers_editing.bar_rect_contains(*this, pos(0), pos(1))) { - // A volume is selected and the mouse is inside the layer thickness bar. - // Start editing the layer height. - m_layers_editing.state = LayersEditing::Editing; - _perform_layer_editing_action(&evt); - } -#if !ENABLE_NEW_RECTANGLE_SELECTION - else if (evt.LeftDown() && (evt.ShiftDown() || evt.AltDown()) && m_picking_enabled) { - if (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports - && m_gizmos.get_current_type() != GLGizmosManager::FdmSupports - && m_gizmos.get_current_type() != GLGizmosManager::Seam - && m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation) { - m_rectangle_selection.start_dragging(m_mouse.position, evt.ShiftDown() ? GLSelectionRectangle::Select : GLSelectionRectangle::Deselect); - m_dirty = true; - } - } -#endif // !ENABLE_NEW_RECTANGLE_SELECTION - else { -#if ENABLE_NEW_RECTANGLE_SELECTION - const bool rectangle_selection_dragging = m_rectangle_selection.is_dragging(); - if (evt.LeftDown() && (evt.ShiftDown() || evt.AltDown()) && m_picking_enabled) { - if (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports && - m_gizmos.get_current_type() != GLGizmosManager::FdmSupports && - m_gizmos.get_current_type() != GLGizmosManager::Seam && - m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation) { - m_rectangle_selection.start_dragging(m_mouse.position, evt.ShiftDown() ? GLSelectionRectangle::EState::Select : GLSelectionRectangle::EState::Deselect); - m_dirty = true; - } - } -#endif // ENABLE_NEW_RECTANGLE_SELECTION - - // Select volume in this 3D canvas. - // Don't deselect a volume if layer editing is enabled or any gizmo is active. We want the object to stay selected - // during the scene manipulation. - -#if ENABLE_NEW_RECTANGLE_SELECTION - if (m_picking_enabled && !any_gizmo_active && (!m_hover_volume_idxs.empty() || !is_layers_editing_enabled()) && !rectangle_selection_dragging) { -#else - if (m_picking_enabled && (!any_gizmo_active || !evt.CmdDown()) && (!m_hover_volume_idxs.empty() || !is_layers_editing_enabled())) { -#endif // ENABLE_NEW_RECTANGLE_SELECTION - if (evt.LeftDown() && !m_hover_volume_idxs.empty()) { - int volume_idx = get_first_hover_volume_idx(); - bool already_selected = m_selection.contains_volume(volume_idx); -#if ENABLE_NEW_RECTANGLE_SELECTION - bool shift_down = evt.ShiftDown(); -#else - bool ctrl_down = evt.CmdDown(); -#endif // ENABLE_NEW_RECTANGLE_SELECTION - - Selection::IndicesList curr_idxs = m_selection.get_volume_idxs(); - -#if ENABLE_NEW_RECTANGLE_SELECTION - if (already_selected && shift_down) - m_selection.remove(volume_idx); - else { - m_selection.add(volume_idx, !shift_down, true); -#else - if (already_selected && ctrl_down) - m_selection.remove(volume_idx); - else { - m_selection.add(volume_idx, !ctrl_down, true); -#endif // ENABLE_NEW_RECTANGLE_SELECTION - m_mouse.drag.move_requires_threshold = !already_selected; - if (already_selected) - m_mouse.set_move_start_threshold_position_2D_as_invalid(); - else - m_mouse.drag.move_start_threshold_position_2D = pos; - } - - // propagate event through callback - if (curr_idxs != m_selection.get_volume_idxs()) { - if (m_selection.is_empty()) - m_gizmos.reset_all_states(); - else - m_gizmos.refresh_on_off_state(); - - m_gizmos.update_data(); - post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); - m_dirty = true; - } - } - } - -#if ENABLE_NEW_RECTANGLE_SELECTION - if (!m_hover_volume_idxs.empty() && !m_rectangle_selection.is_dragging()) { -#else - if (!m_hover_volume_idxs.empty()) { -#endif // ENABLE_NEW_RECTANGLE_SELECTION - if (evt.LeftDown() && m_moving_enabled && m_mouse.drag.move_volume_idx == -1) { - // Only accept the initial position, if it is inside the volume bounding box. - const int volume_idx = get_first_hover_volume_idx(); - BoundingBoxf3 volume_bbox = m_volumes.volumes[volume_idx]->transformed_bounding_box(); - volume_bbox.offset(1.0); - if ((!any_gizmo_active || !evt.CmdDown()) && volume_bbox.contains(m_mouse.scene_position)) { - m_volumes.volumes[volume_idx]->hover = GLVolume::HS_None; - // The dragging operation is initiated. - m_mouse.drag.move_volume_idx = volume_idx; -#if ENABLE_NEW_CAMERA_MOVEMENTS - m_selection.setup_cache(); - if (!evt.CmdDown()) -#endif // ENABLE_NEW_CAMERA_MOVEMENTS - m_mouse.drag.start_position_3D = m_mouse.scene_position; - m_sequential_print_clearance_first_displacement = true; - m_moving = true; - } - } - } - } - } -#if ENABLE_NEW_CAMERA_MOVEMENTS - else if (evt.Dragging() && evt.LeftIsDown() && !evt.CmdDown() && m_layers_editing.state == LayersEditing::Unknown && - m_mouse.drag.move_volume_idx != -1 && m_mouse.is_start_position_3D_defined()) { -#else - else if (evt.Dragging() && evt.LeftIsDown() && m_layers_editing.state == LayersEditing::Unknown && m_mouse.drag.move_volume_idx != -1) { -#endif // ENABLE_NEW_CAMERA_MOVEMENTS - if (!m_mouse.drag.move_requires_threshold) { - m_mouse.dragging = true; - Vec3d cur_pos = m_mouse.drag.start_position_3D; - // we do not want to translate objects if the user just clicked on an object while pressing shift to remove it from the selection and then drag - if (m_selection.contains_volume(get_first_hover_volume_idx())) { - const Camera& camera = wxGetApp().plater()->get_camera(); - if (std::abs(camera.get_dir_forward().z()) < EPSILON) { - // side view -> move selected volumes orthogonally to camera view direction - const Linef3 ray = mouse_ray(pos); - const Vec3d dir = ray.unit_vector(); - // 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 - // 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 - const Vec3d inters = ray.a + (m_mouse.drag.start_position_3D - ray.a).dot(dir) / dir.squaredNorm() * dir; - // vector from the starting position to the found intersection - const Vec3d inters_vec = inters - m_mouse.drag.start_position_3D; - - const Vec3d camera_right = camera.get_dir_right(); - const Vec3d camera_up = camera.get_dir_up(); - - // finds projection of the vector along the camera axes - const double projection_x = inters_vec.dot(camera_right); - const double projection_z = inters_vec.dot(camera_up); - - // apply offset - cur_pos = m_mouse.drag.start_position_3D + projection_x * camera_right + projection_z * camera_up; - } - else { - // Generic view - // Get new position at the same Z of the initial click point. - cur_pos = mouse_ray(pos).intersect_plane(m_mouse.drag.start_position_3D.z()); - } - } - - m_selection.translate(cur_pos - m_mouse.drag.start_position_3D); - if (current_printer_technology() == ptFFF && fff_print()->config().complete_objects) - update_sequential_clearance(); - wxGetApp().obj_manipul()->set_dirty(); - m_dirty = true; - } - } - else if (evt.Dragging() && evt.LeftIsDown() && m_picking_enabled && m_rectangle_selection.is_dragging()) { -#if ENABLE_NEW_RECTANGLE_SELECTION - // keeps the mouse position updated while dragging the selection rectangle - m_mouse.position = pos.cast(); - m_rectangle_selection.dragging(m_mouse.position); -#else - m_rectangle_selection.dragging(pos.cast()); -#endif // ENABLE_NEW_RECTANGLE_SELECTION - m_dirty = true; - } - else if (evt.Dragging()) { - m_mouse.dragging = true; - - if (m_layers_editing.state != LayersEditing::Unknown && layer_editing_object_idx != -1) { - if (m_layers_editing.state == LayersEditing::Editing) { - _perform_layer_editing_action(&evt); - m_mouse.position = pos.cast(); - } - } - // do not process the dragging if the left mouse was set down in another canvas -#if ENABLE_NEW_CAMERA_MOVEMENTS - else if (evt.LeftIsDown()) { - // if dragging over blank area with left button, rotate - if ((any_gizmo_active || evt.CmdDown() || m_hover_volume_idxs.empty()) && m_mouse.is_start_position_3D_defined()) { -#else - // if dragging over blank area with left button, rotate - else if (evt.LeftIsDown()) { - if ((any_gizmo_active || m_hover_volume_idxs.empty()) && m_mouse.is_start_position_3D_defined()) { -#endif // ENABLE_NEW_CAMERA_MOVEMENTS - const Vec3d rot = (Vec3d(pos.x(), pos.y(), 0.0) - m_mouse.drag.start_position_3D) * (PI * TRACKBALLSIZE / 180.0); - if (wxGetApp().app_config->get("use_free_camera") == "1") - // Virtual track ball (similar to the 3DConnexion mouse). - wxGetApp().plater()->get_camera().rotate_local_around_target(Vec3d(rot.y(), rot.x(), 0.0)); - else { - // Forces camera right vector to be parallel to XY plane in case it has been misaligned using the 3D mouse free rotation. - // It is cheaper to call this function right away instead of testing wxGetApp().plater()->get_mouse3d_controller().connected(), - // which checks an atomics (flushes CPU caches). - // See GH issue #3816. - Camera& camera = wxGetApp().plater()->get_camera(); - camera.recover_from_free_camera(); - camera.rotate_on_sphere(rot.x(), rot.y(), current_printer_technology() != ptSLA); - } - - m_dirty = true; - } - m_mouse.drag.start_position_3D = Vec3d((double)pos.x(), (double)pos.y(), 0.0); - } - else if (evt.MiddleIsDown() || evt.RightIsDown()) { - // If dragging over blank area with right/middle button, pan. - if (m_mouse.is_start_position_2D_defined()) { - // get point in model space at Z = 0 - float z = 0.0f; - const Vec3d cur_pos = _mouse_to_3d(pos, &z); - const Vec3d orig = _mouse_to_3d(m_mouse.drag.start_position_2D, &z); - Camera& camera = wxGetApp().plater()->get_camera(); - if (wxGetApp().app_config->get("use_free_camera") != "1") - // Forces camera right vector to be parallel to XY plane in case it has been misaligned using the 3D mouse free rotation. - // It is cheaper to call this function right away instead of testing wxGetApp().plater()->get_mouse3d_controller().connected(), - // which checks an atomics (flushes CPU caches). - // See GH issue #3816. - camera.recover_from_free_camera(); - - camera.set_target(camera.get_target() + orig - cur_pos); - m_dirty = true; - } - - m_mouse.drag.start_position_2D = pos; - } - } - else if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) { - if (m_layers_editing.state != LayersEditing::Unknown) { - m_layers_editing.state = LayersEditing::Unknown; - _stop_timer(); - m_layers_editing.accept_changes(*this); - } - else if (m_mouse.drag.move_volume_idx != -1 && m_mouse.dragging) { - do_move(L("Move Object")); - wxGetApp().obj_manipul()->set_dirty(); - // Let the plater know that the dragging finished, so a delayed refresh - // of the scene with the background processing data should be performed. - post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); - } - else if (evt.LeftUp() && m_picking_enabled && m_rectangle_selection.is_dragging()) { - if (evt.ShiftDown() || evt.AltDown()) - _update_selection_from_hover(); - - m_rectangle_selection.stop_dragging(); - } - else if (evt.LeftUp() && !m_mouse.ignore_left_up && !m_mouse.dragging && m_hover_volume_idxs.empty() && !is_layers_editing_enabled()) { - // deselect and propagate event through callback - if (!evt.ShiftDown() && (!any_gizmo_active || !evt.CmdDown()) && m_picking_enabled) - deselect_all(); - } - else if (evt.RightUp()) { - m_mouse.position = pos.cast(); - // forces a frame render to ensure that m_hover_volume_idxs is updated even when the user right clicks while - // the context menu is already shown - render(); - if (!m_hover_volume_idxs.empty()) { - // if right clicking on volume, propagate event through callback (shows context menu) - int volume_idx = get_first_hover_volume_idx(); - if (!m_volumes.volumes[volume_idx]->is_wipe_tower // no context menu for the wipe tower - && m_gizmos.get_current_type() != GLGizmosManager::SlaSupports) // disable context menu when the gizmo is open - { - // forces the selection of the volume - /* m_selection.add(volume_idx); // #et_FIXME_if_needed - * To avoid extra "Add-Selection" snapshots, - * call add() with check_for_already_contained=true - * */ - m_selection.add(volume_idx, true, true); - m_gizmos.refresh_on_off_state(); - post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); - m_gizmos.update_data(); - wxGetApp().obj_manipul()->set_dirty(); - // forces a frame render to update the view before the context menu is shown - render(); - } - } - Vec2d logical_pos = pos.cast(); -#if ENABLE_RETINA_GL - const float factor = m_retina_helper->get_scale_factor(); - logical_pos = logical_pos.cwiseQuotient(Vec2d(factor, factor)); -#endif // ENABLE_RETINA_GL - if (!m_mouse.dragging) { - // do not post the event if the user is panning the scene - // or if right click was done over the wipe tower - const bool post_right_click_event = m_hover_volume_idxs.empty() || !m_volumes.volumes[get_first_hover_volume_idx()]->is_wipe_tower; - if (post_right_click_event) - post_event(RBtnEvent(EVT_GLCANVAS_RIGHT_CLICK, { logical_pos, m_hover_volume_idxs.empty() })); - } - } - - mouse_up_cleanup(); - } - else if (evt.Moving()) { - m_mouse.position = pos.cast(); - - // updates gizmos overlay - if (m_selection.is_empty()) - m_gizmos.reset_all_states(); - - m_dirty = true; - } - else - evt.Skip(); - - if (m_moving) - show_sinking_contours(); - -#ifdef __WXMSW__ - if (on_enter_workaround) - m_mouse.position = Vec2d(-1., -1.); -#endif /* __WXMSW__ */ -} - -void GLCanvas3D::on_paint(wxPaintEvent& evt) -{ - if (m_initialized) - m_dirty = true; - else - // Call render directly, so it gets initialized immediately, not from On Idle handler. - this->render(); -} - -void GLCanvas3D::on_set_focus(wxFocusEvent& evt) -{ - m_tooltip_enabled = false; - _refresh_if_shown_on_screen(); - m_tooltip_enabled = true; -} - -Size GLCanvas3D::get_canvas_size() const -{ - int w = 0; - int h = 0; - - if (m_canvas != nullptr) - m_canvas->GetSize(&w, &h); - -#if ENABLE_RETINA_GL - const float factor = m_retina_helper->get_scale_factor(); - w *= factor; - h *= factor; -#else - const float factor = 1.0f; -#endif - - return Size(w, h, factor); -} - -Vec2d GLCanvas3D::get_local_mouse_position() const -{ - if (m_canvas == nullptr) - return Vec2d::Zero(); - - wxPoint mouse_pos = m_canvas->ScreenToClient(wxGetMousePosition()); - const double factor = -#if ENABLE_RETINA_GL - m_retina_helper->get_scale_factor(); -#else - 1.0; -#endif - return Vec2d(factor * mouse_pos.x, factor * mouse_pos.y); -} - -void GLCanvas3D::set_tooltip(const std::string& tooltip) -{ - if (m_canvas != nullptr) - m_tooltip.set_text(tooltip); -} - -void GLCanvas3D::do_move(const std::string& snapshot_type) -{ - if (m_model == nullptr) - return; - - if (!snapshot_type.empty()) - wxGetApp().plater()->take_snapshot(_(snapshot_type)); - - std::set> done; // keeps track of modified instances - bool object_moved = false; - Vec3d wipe_tower_origin = Vec3d::Zero(); - - Selection::EMode selection_mode = m_selection.get_mode(); - - for (const GLVolume* v : m_volumes.volumes) { - int object_idx = v->object_idx(); - int instance_idx = v->instance_idx(); - int volume_idx = v->volume_idx(); - - std::pair done_id(object_idx, instance_idx); - - if (0 <= object_idx && object_idx < (int)m_model->objects.size()) { - done.insert(done_id); - - // Move instances/volumes - ModelObject* model_object = m_model->objects[object_idx]; - if (model_object != nullptr) { - if (selection_mode == Selection::Instance) - model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); - else if (selection_mode == Selection::Volume) - model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); - - object_moved = true; - model_object->invalidate_bounding_box(); - } - } -#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - else if (v->is_wipe_tower) - // Move a wipe tower proxy. - wipe_tower_origin = v->get_volume_offset(); -#else - else if (object_idx == 1000) - // Move a wipe tower proxy. - wipe_tower_origin = v->get_volume_offset(); -#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - } - - // Fixes flying instances - for (const std::pair& i : done) { - ModelObject* m = m_model->objects[i.first]; - const double shift_z = m->get_instance_min_z(i.second); - if (current_printer_technology() == ptSLA || shift_z > SINKING_Z_THRESHOLD) { - const Vec3d shift(0.0, 0.0, -shift_z); - m_selection.translate(i.first, i.second, shift); - m->translate_instance(i.second, shift); - } - wxGetApp().obj_list()->update_info_items(static_cast(i.first)); - } - - // if the selection is not valid to allow for layer editing after the move, we need to turn off the tool if it is running - // similar to void Plater::priv::selection_changed() - if (!wxGetApp().plater()->can_layers_editing() && is_layers_editing_enabled()) - post_event(SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); - - if (object_moved) - post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_MOVED)); - - if (wipe_tower_origin != Vec3d::Zero()) - post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_MOVED, std::move(wipe_tower_origin))); - - reset_sequential_print_clearance(); - - m_dirty = true; -} - -void GLCanvas3D::do_rotate(const std::string& snapshot_type) -{ - if (m_model == nullptr) - return; - - if (!snapshot_type.empty()) - wxGetApp().plater()->take_snapshot(_(snapshot_type)); - - // stores current min_z of instances - std::map, double> min_zs; - for (int i = 0; i < static_cast(m_model->objects.size()); ++i) { - const ModelObject* obj = m_model->objects[i]; - for (int j = 0; j < static_cast(obj->instances.size()); ++j) { - if (snapshot_type.empty() && m_selection.get_object_idx() == i) { - // This means we are flattening this object. In that case pretend - // that it is not sinking (even if it is), so it is placed on bed - // later on (whatever is sinking will be left sinking). - min_zs[{ i, j }] = SINKING_Z_THRESHOLD; - } else - min_zs[{ i, j }] = obj->instance_bounding_box(j).min.z(); - - } - } - - std::set> done; // keeps track of modified instances - - Selection::EMode selection_mode = m_selection.get_mode(); - - for (const GLVolume* v : m_volumes.volumes) { -#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - if (v->is_wipe_tower) { -#else - int object_idx = v->object_idx(); - if (object_idx == 1000) { // the wipe tower -#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - Vec3d offset = v->get_volume_offset(); - post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3d(offset(0), offset(1), v->get_volume_rotation()(2)))); - } -#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - int object_idx = v->object_idx(); -#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - if (object_idx < 0 || (int)m_model->objects.size() <= object_idx) - continue; - - int instance_idx = v->instance_idx(); - int volume_idx = v->volume_idx(); - - done.insert(std::pair(object_idx, instance_idx)); - - // Rotate instances/volumes. - ModelObject* model_object = m_model->objects[object_idx]; - if (model_object != nullptr) { - if (selection_mode == Selection::Instance) { - model_object->instances[instance_idx]->set_rotation(v->get_instance_rotation()); - model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); - } - else if (selection_mode == Selection::Volume) { - model_object->volumes[volume_idx]->set_rotation(v->get_volume_rotation()); - model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); - } - model_object->invalidate_bounding_box(); - } - } - - // Fixes sinking/flying instances - for (const std::pair& i : done) { - ModelObject* m = m_model->objects[i.first]; - const double shift_z = m->get_instance_min_z(i.second); - // leave sinking instances as sinking - if (min_zs.find({ i.first, i.second })->second >= SINKING_Z_THRESHOLD || shift_z > SINKING_Z_THRESHOLD) { - const Vec3d shift(0.0, 0.0, -shift_z); - m_selection.translate(i.first, i.second, shift); - m->translate_instance(i.second, shift); - } - - wxGetApp().obj_list()->update_info_items(static_cast(i.first)); - } - - if (!done.empty()) - post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED)); - - m_dirty = true; -} - -void GLCanvas3D::do_scale(const std::string& snapshot_type) -{ - if (m_model == nullptr) - return; - - if (!snapshot_type.empty()) - wxGetApp().plater()->take_snapshot(_(snapshot_type)); - - // stores current min_z of instances - std::map, double> min_zs; - if (!snapshot_type.empty()) { - for (int i = 0; i < static_cast(m_model->objects.size()); ++i) { - const ModelObject* obj = m_model->objects[i]; - for (int j = 0; j < static_cast(obj->instances.size()); ++j) { - min_zs[{ i, j }] = obj->instance_bounding_box(j).min.z(); - } - } - } - - std::set> done; // keeps track of modified instances - - Selection::EMode selection_mode = m_selection.get_mode(); - - for (const GLVolume* v : m_volumes.volumes) { - int object_idx = v->object_idx(); - if (object_idx < 0 || (int)m_model->objects.size() <= object_idx) - continue; - - int instance_idx = v->instance_idx(); - int volume_idx = v->volume_idx(); - - done.insert(std::pair(object_idx, instance_idx)); - - // Rotate instances/volumes - ModelObject* model_object = m_model->objects[object_idx]; - if (model_object != nullptr) { - if (selection_mode == Selection::Instance) { - model_object->instances[instance_idx]->set_scaling_factor(v->get_instance_scaling_factor()); - model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); - } - else if (selection_mode == Selection::Volume) { - model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); - model_object->volumes[volume_idx]->set_scaling_factor(v->get_volume_scaling_factor()); - model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); - } - model_object->invalidate_bounding_box(); - } - } - - // Fixes sinking/flying instances - for (const std::pair& i : done) { - ModelObject* m = m_model->objects[i.first]; - double shift_z = m->get_instance_min_z(i.second); - // leave sinking instances as sinking - if (min_zs.empty() || min_zs.find({ i.first, i.second })->second >= SINKING_Z_THRESHOLD || shift_z > SINKING_Z_THRESHOLD) { - Vec3d shift(0.0, 0.0, -shift_z); - m_selection.translate(i.first, i.second, shift); - m->translate_instance(i.second, shift); - } - wxGetApp().obj_list()->update_info_items(static_cast(i.first)); - } - - if (!done.empty()) - post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_SCALED)); - - m_dirty = true; -} - -void GLCanvas3D::do_mirror(const std::string& snapshot_type) -{ - if (m_model == nullptr) - return; - - if (!snapshot_type.empty()) - wxGetApp().plater()->take_snapshot(_(snapshot_type)); - - // stores current min_z of instances - std::map, double> min_zs; - if (!snapshot_type.empty()) { - for (int i = 0; i < static_cast(m_model->objects.size()); ++i) { - const ModelObject* obj = m_model->objects[i]; - for (int j = 0; j < static_cast(obj->instances.size()); ++j) { - min_zs[{ i, j }] = obj->instance_bounding_box(j).min.z(); - } - } - } - - std::set> done; // keeps track of modified instances - - Selection::EMode selection_mode = m_selection.get_mode(); - - for (const GLVolume* v : m_volumes.volumes) { - int object_idx = v->object_idx(); - if (object_idx < 0 || (int)m_model->objects.size() <= object_idx) - continue; - - int instance_idx = v->instance_idx(); - int volume_idx = v->volume_idx(); - - done.insert(std::pair(object_idx, instance_idx)); - - // Mirror instances/volumes - ModelObject* model_object = m_model->objects[object_idx]; - if (model_object != nullptr) { - if (selection_mode == Selection::Instance) - model_object->instances[instance_idx]->set_mirror(v->get_instance_mirror()); - else if (selection_mode == Selection::Volume) - model_object->volumes[volume_idx]->set_mirror(v->get_volume_mirror()); - - model_object->invalidate_bounding_box(); - } - } - - // Fixes sinking/flying instances - for (const std::pair& i : done) { - ModelObject* m = m_model->objects[i.first]; - double shift_z = m->get_instance_min_z(i.second); - // leave sinking instances as sinking - if (min_zs.empty() || min_zs.find({ i.first, i.second })->second >= SINKING_Z_THRESHOLD || shift_z > SINKING_Z_THRESHOLD) { - Vec3d shift(0.0, 0.0, -shift_z); - m_selection.translate(i.first, i.second, shift); - m->translate_instance(i.second, shift); - } - wxGetApp().obj_list()->update_info_items(static_cast(i.first)); - } - - post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); - - m_dirty = true; -} - -void GLCanvas3D::update_gizmos_on_off_state() -{ - set_as_dirty(); - m_gizmos.update_data(); - m_gizmos.refresh_on_off_state(); -} - -void GLCanvas3D::handle_sidebar_focus_event(const std::string& opt_key, bool focus_on) -{ - m_sidebar_field = focus_on ? opt_key : ""; - if (!m_sidebar_field.empty()) - m_gizmos.reset_all_states(); - - m_dirty = true; -} - -void GLCanvas3D::handle_layers_data_focus_event(const t_layer_height_range range, const EditorType type) -{ - std::string field = "layer_" + std::to_string(type) + "_" + std::to_string(range.first) + "_" + std::to_string(range.second); - handle_sidebar_focus_event(field, true); -} - -void GLCanvas3D::update_ui_from_settings() -{ - m_dirty = true; - -#if __APPLE__ - // Update OpenGL scaling on OSX after the user toggled the "use_retina_opengl" settings in Preferences dialog. - const float orig_scaling = m_retina_helper->get_scale_factor(); - - const bool use_retina = wxGetApp().app_config->get("use_retina_opengl") == "1"; - BOOST_LOG_TRIVIAL(debug) << "GLCanvas3D: Use Retina OpenGL: " << use_retina; - m_retina_helper->set_use_retina(use_retina); - const float new_scaling = m_retina_helper->get_scale_factor(); - - if (new_scaling != orig_scaling) { - BOOST_LOG_TRIVIAL(debug) << "GLCanvas3D: Scaling factor: " << new_scaling; - - Camera& camera = wxGetApp().plater()->get_camera(); - camera.set_zoom(camera.get_zoom() * new_scaling / orig_scaling); - _refresh_if_shown_on_screen(); - } -#endif // ENABLE_RETINA_GL - - if (wxGetApp().is_editor()) - wxGetApp().plater()->enable_collapse_toolbar(wxGetApp().app_config->get("show_collapse_button") == "1"); -} - -GLCanvas3D::WipeTowerInfo GLCanvas3D::get_wipe_tower_info() const -{ - WipeTowerInfo wti; - - for (const GLVolume* vol : m_volumes.volumes) { - if (vol->is_wipe_tower) { - wti.m_pos = Vec2d(m_config->opt_float("wipe_tower_x"), - m_config->opt_float("wipe_tower_y")); - wti.m_rotation = (M_PI/180.) * m_config->opt_float("wipe_tower_rotation_angle"); - const BoundingBoxf3& bb = vol->bounding_box(); - wti.m_bb = BoundingBoxf{to_2d(bb.min), to_2d(bb.max)}; - break; - } - } - - return wti; -} - -Linef3 GLCanvas3D::mouse_ray(const Point& mouse_pos) -{ - float z0 = 0.0f; - float z1 = 1.0f; - return Linef3(_mouse_to_3d(mouse_pos, &z0), _mouse_to_3d(mouse_pos, &z1)); -} - -double GLCanvas3D::get_size_proportional_to_max_bed_size(double factor) const -{ - const BoundingBoxf& bbox = m_bed.build_volume().bounding_volume2d(); - return factor * std::max(bbox.size()[0], bbox.size()[1]); -} - -void GLCanvas3D::set_cursor(ECursorType type) -{ - if ((m_canvas != nullptr) && (m_cursor_type != type)) - { - switch (type) - { - case Standard: { m_canvas->SetCursor(*wxSTANDARD_CURSOR); break; } - case Cross: { m_canvas->SetCursor(*wxCROSS_CURSOR); break; } - } - - m_cursor_type = type; - } -} - -void GLCanvas3D::msw_rescale() -{ -#if ENABLE_PREVIEW_LAYOUT - m_gcode_viewer.invalidate_legend(); -#endif // ENABLE_PREVIEW_LAYOUT -} - -void GLCanvas3D::update_tooltip_for_settings_item_in_main_toolbar() -{ - std::string new_tooltip = _u8L("Switch to Settings") + - "\n" + "[" + GUI::shortkey_ctrl_prefix() + "2] - " + _u8L("Print Settings Tab") + - "\n" + "[" + GUI::shortkey_ctrl_prefix() + "3] - " + (current_printer_technology() == ptFFF ? _u8L("Filament Settings Tab") : _u8L("Material Settings Tab")) + - "\n" + "[" + GUI::shortkey_ctrl_prefix() + "4] - " + _u8L("Printer Settings Tab") ; - - m_main_toolbar.set_tooltip(get_main_toolbar_item_id("settings"), new_tooltip); -} - -bool GLCanvas3D::has_toolpaths_to_export() const -{ - return m_gcode_viewer.can_export_toolpaths(); -} - -void GLCanvas3D::export_toolpaths_to_obj(const char* filename) const -{ - m_gcode_viewer.export_toolpaths_to_obj(filename); -} - -void GLCanvas3D::mouse_up_cleanup() -{ - m_moving = false; - m_mouse.drag.move_volume_idx = -1; - m_mouse.set_start_position_3D_as_invalid(); - m_mouse.set_start_position_2D_as_invalid(); - m_mouse.dragging = false; - m_mouse.ignore_left_up = false; - m_dirty = true; - - if (m_canvas->HasCapture()) - m_canvas->ReleaseMouse(); -} - -void GLCanvas3D::update_sequential_clearance() -{ - if (current_printer_technology() != ptFFF || !fff_print()->config().complete_objects) - return; - - if (m_layers_editing.is_enabled() || m_gizmos.is_dragging()) - return; - - // collects instance transformations from volumes - // first define temporary cache - unsigned int instances_count = 0; - std::vector>> instance_transforms; - for (size_t obj = 0; obj < m_model->objects.size(); ++obj) { - instance_transforms.emplace_back(std::vector>()); - const ModelObject* model_object = m_model->objects[obj]; - for (size_t i = 0; i < model_object->instances.size(); ++i) { - instance_transforms[obj].emplace_back(std::optional()); - ++instances_count; - } - } - - if (instances_count == 1) - return; - - // second fill temporary cache with data from volumes - for (const GLVolume* v : m_volumes.volumes) { - if (v->is_modifier || v->is_wipe_tower) - continue; - - auto& transform = instance_transforms[v->object_idx()][v->instance_idx()]; - if (!transform.has_value()) - transform = v->get_instance_transformation(); - } - - // calculates objects 2d hulls (see also: Print::sequential_print_horizontal_clearance_valid()) - // this is done only the first time this method is called while moving the mouse, - // the results are then cached for following displacements - if (m_sequential_print_clearance_first_displacement) { - m_sequential_print_clearance.m_hull_2d_cache.clear(); - float shrink_factor = static_cast(scale_(0.5 * fff_print()->config().extruder_clearance_radius.value - EPSILON)); - double mitter_limit = scale_(0.1); - m_sequential_print_clearance.m_hull_2d_cache.reserve(m_model->objects.size()); - for (size_t i = 0; i < m_model->objects.size(); ++i) { - ModelObject* model_object = m_model->objects[i]; - ModelInstance* model_instance0 = model_object->instances.front(); - Polygon hull_2d = offset(model_object->convex_hull_2d(Geometry::assemble_transform({ 0.0, 0.0, model_instance0->get_offset().z() }, model_instance0->get_rotation(), - model_instance0->get_scaling_factor(), model_instance0->get_mirror())), - // 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. - shrink_factor, - jtRound, mitter_limit).front(); - - Pointf3s& cache_hull_2d = m_sequential_print_clearance.m_hull_2d_cache.emplace_back(Pointf3s()); - cache_hull_2d.reserve(hull_2d.points.size()); - for (const Point& p : hull_2d.points) { - cache_hull_2d.emplace_back(unscale(p.x()), unscale(p.y()), 0.0); - } - } - m_sequential_print_clearance_first_displacement = false; - } - - // calculates instances 2d hulls (see also: Print::sequential_print_horizontal_clearance_valid()) - Polygons polygons; - polygons.reserve(instances_count); - for (size_t i = 0; i < instance_transforms.size(); ++i) { - const auto& instances = instance_transforms[i]; - double rotation_z0 = instances.front()->get_rotation().z(); - for (const auto& instance : instances) { - Geometry::Transformation transformation; - 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]; - Points inst_pts; - inst_pts.reserve(hull_2d.size()); - for (size_t j = 0; j < hull_2d.size(); ++j) { - const Vec3d p = trafo * hull_2d[j]; - inst_pts.emplace_back(scaled(p.x()), scaled(p.y())); - } - polygons.emplace_back(Geometry::convex_hull(std::move(inst_pts))); - } - } - - // sends instances 2d hulls to be rendered - set_sequential_print_clearance_visible(true); - set_sequential_print_clearance_render_fill(false); - set_sequential_print_clearance_polygons(polygons); -} - -bool GLCanvas3D::is_object_sinking(int object_idx) const -{ - for (const GLVolume* v : m_volumes.volumes) { - if (v->object_idx() == object_idx && (v->is_sinking() || (!v->is_modifier && v->is_below_printbed()))) - return true; - } - return false; -} - -bool GLCanvas3D::_is_shown_on_screen() const -{ - return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false; -} - -// Getter for the const char*[] -static bool string_getter(const bool is_undo, int idx, const char** out_text) -{ - return wxGetApp().plater()->undo_redo_string_getter(is_undo, idx, out_text); -} - -bool GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x) -{ - bool action_taken = false; - - ImGuiWrapper* imgui = wxGetApp().imgui(); - -#if ENABLE_GL_SHADERS_ATTRIBUTES - imgui->set_next_window_pos(pos_x, m_undoredo_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); -#else - const float x = pos_x * (float)wxGetApp().plater()->get_camera().get_zoom() + 0.5f * (float)get_canvas_size().get_width(); - imgui->set_next_window_pos(x, m_undoredo_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - std::string title = is_undo ? L("Undo History") : L("Redo History"); - imgui->begin(_(title), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - - int hovered = m_imgui_undo_redo_hovered_pos; - int selected = -1; - float em = static_cast(wxGetApp().em_unit()); -#if ENABLE_RETINA_GL - em *= m_retina_helper->get_scale_factor(); -#endif - - if (imgui->undo_redo_list(ImVec2(18 * em, 26 * em), is_undo, &string_getter, hovered, selected, m_mouse_wheel)) - m_imgui_undo_redo_hovered_pos = hovered; - else - m_imgui_undo_redo_hovered_pos = -1; - - if (selected >= 0) { - is_undo ? wxGetApp().plater()->undo_to(selected) : wxGetApp().plater()->redo_to(selected); - action_taken = true; - } - - imgui->text(wxString::Format(is_undo ? _L_PLURAL("Undo %1$d Action", "Undo %1$d Actions", hovered + 1) : _L_PLURAL("Redo %1$d Action", "Redo %1$d Actions", hovered + 1), hovered + 1)); - - imgui->end(); - - return action_taken; -} - -// Getter for the const char*[] for the search list -static bool search_string_getter(int idx, const char** label, const char** tooltip) -{ - return wxGetApp().plater()->search_string_getter(idx, label, tooltip); -} - -bool GLCanvas3D::_render_search_list(float pos_x) -{ - bool action_taken = false; - ImGuiWrapper* imgui = wxGetApp().imgui(); - -#if ENABLE_GL_SHADERS_ATTRIBUTES - imgui->set_next_window_pos(pos_x, m_main_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); -#else - const float x = /*pos_x * (float)wxGetApp().plater()->get_camera().get_zoom() + */0.5f * (float)get_canvas_size().get_width(); - imgui->set_next_window_pos(x, m_main_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - std::string title = L("Search"); - imgui->begin(_(title), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - - int selected = -1; - bool edited = false; - float em = static_cast(wxGetApp().em_unit()); -#if ENABLE_RETINA_GL - em *= m_retina_helper->get_scale_factor(); -#endif // ENABLE_RETINA_GL - - Sidebar& sidebar = wxGetApp().sidebar(); - - std::string& search_line = sidebar.get_search_line(); - char *s = new char[255]; - strcpy(s, search_line.empty() ? _u8L("Enter a search term").c_str() : search_line.c_str()); - - imgui->search_list(ImVec2(45 * em, 30 * em), &search_string_getter, s, - sidebar.get_searcher().view_params, - selected, edited, m_mouse_wheel, wxGetApp().is_localized()); - - search_line = s; - delete [] s; - if (search_line == _u8L("Enter a search term")) - search_line.clear(); - - if (edited) - sidebar.search(); - - if (selected >= 0) { - // selected == 9999 means that Esc kye was pressed - /*// revert commit https://github.com/prusa3d/PrusaSlicer/commit/91897589928789b261ca0dc735ffd46f2b0b99f2 - if (selected == 9999) - action_taken = true; - else - sidebar.jump_to_option(selected);*/ - if (selected != 9999) { - imgui->end(); // end imgui before the jump to option - sidebar.jump_to_option(selected); - return true; - } - action_taken = true; - } - - imgui->end(); - - return action_taken; -} - -bool GLCanvas3D::_render_arrange_menu(float pos_x) -{ - ImGuiWrapper *imgui = wxGetApp().imgui(); - -#if ENABLE_GL_SHADERS_ATTRIBUTES - imgui->set_next_window_pos(pos_x, m_main_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); -#else - auto canvas_w = float(get_canvas_size().get_width()); - const float x = pos_x * float(wxGetApp().plater()->get_camera().get_zoom()) + 0.5f * canvas_w; - imgui->set_next_window_pos(x, m_main_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - imgui->begin(_L("Arrange options"), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); - - ArrangeSettings settings = get_arrange_settings(); - ArrangeSettings &settings_out = get_arrange_settings(); - - auto &appcfg = wxGetApp().app_config; - PrinterTechnology ptech = current_printer_technology(); - - bool settings_changed = false; - float dist_min = 0.f; - std::string dist_key = "min_object_distance", rot_key = "enable_rotation"; - std::string postfix; - - if (ptech == ptSLA) { - dist_min = 0.f; - postfix = "_sla"; - } else if (ptech == ptFFF) { - auto co_opt = m_config->option("complete_objects"); - if (co_opt && co_opt->value) { - dist_min = float(min_object_distance(*m_config)); - postfix = "_fff_seq_print"; - } else { - dist_min = 0.f; - postfix = "_fff"; - } - } - - dist_key += postfix; - rot_key += postfix; - - imgui->text(GUI::format_wxstr(_L("Press %1%left mouse button to enter the exact value"), shortkey_ctrl_prefix())); - - if (imgui->slider_float(_L("Spacing"), &settings.distance, dist_min, 100.0f, "%5.2f") || dist_min > settings.distance) { - settings.distance = std::max(dist_min, settings.distance); - settings_out.distance = settings.distance; - appcfg->set("arrange", dist_key.c_str(), float_to_string_decimal_point(settings_out.distance)); - settings_changed = true; - } - - if (imgui->checkbox(_L("Enable rotations (slow)"), settings.enable_rotation)) { - settings_out.enable_rotation = settings.enable_rotation; - appcfg->set("arrange", rot_key.c_str(), settings_out.enable_rotation? "1" : "0"); - settings_changed = true; - } - - ImGui::Separator(); - - if (imgui->button(_L("Reset"))) { - settings_out = ArrangeSettings{}; - settings_out.distance = std::max(dist_min, settings_out.distance); - appcfg->set("arrange", dist_key.c_str(), float_to_string_decimal_point(settings_out.distance)); - appcfg->set("arrange", rot_key.c_str(), settings_out.enable_rotation? "1" : "0"); - settings_changed = true; - } - - ImGui::SameLine(); - - if (imgui->button(_L("Arrange"))) { - wxGetApp().plater()->arrange(); - } - - imgui->end(); - - return settings_changed; -} - -#define ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT 0 -#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT -static void debug_output_thumbnail(const ThumbnailData& thumbnail_data) -{ - // debug export of generated image - wxImage image(thumbnail_data.width, thumbnail_data.height); - image.InitAlpha(); - - for (unsigned int r = 0; r < thumbnail_data.height; ++r) - { - unsigned int rr = (thumbnail_data.height - 1 - r) * thumbnail_data.width; - for (unsigned int c = 0; c < thumbnail_data.width; ++c) - { - unsigned char* px = (unsigned char*)thumbnail_data.pixels.data() + 4 * (rr + c); - image.SetRGB((int)c, (int)r, px[0], px[1], px[2]); - image.SetAlpha((int)c, (int)r, px[3]); - } - } - - image.SaveFile("C:/prusa/test/test.png", wxBITMAP_TYPE_PNG); -} -#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT - -void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type) -{ - auto is_visible = [](const GLVolume& v) { - bool ret = v.printable; - ret &= (!v.shader_outside_printer_detection_enabled || !v.is_outside); - return ret; - }; - - GLVolumePtrs visible_volumes; - - for (GLVolume* vol : volumes.volumes) { - if (!vol->is_modifier && !vol->is_wipe_tower && (!thumbnail_params.parts_only || vol->composite_id.volume_id >= 0)) { - if (!thumbnail_params.printable_only || is_visible(*vol)) - visible_volumes.emplace_back(vol); - } - } - - BoundingBoxf3 volumes_box; - if (!visible_volumes.empty()) { - for (const GLVolume* vol : visible_volumes) { - volumes_box.merge(vol->transformed_bounding_box()); - } - } - else - // This happens for empty projects - volumes_box = m_bed.extended_bounding_box(); - - Camera camera; - camera.set_type(camera_type); - camera.set_scene_box(scene_bounding_box()); - camera.apply_viewport(0, 0, thumbnail_data.width, thumbnail_data.height); - camera.zoom_to_box(volumes_box); -#if !ENABLE_LEGACY_OPENGL_REMOVAL - camera.apply_view_matrix(); -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Transform3d& view_matrix = camera.get_view_matrix(); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - double near_z = -1.0; - double far_z = -1.0; - - if (thumbnail_params.show_bed) { - // extends the near and far z of the frustrum to avoid the bed being clipped - - // box in eye space -#if ENABLE_GL_SHADERS_ATTRIBUTES - const BoundingBoxf3 t_bed_box = m_bed.extended_bounding_box().transformed(view_matrix); -#else - const BoundingBoxf3 t_bed_box = m_bed.extended_bounding_box().transformed(camera.get_view_matrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - near_z = -t_bed_box.max.z(); - far_z = -t_bed_box.min.z(); - } - - camera.apply_projection(volumes_box, near_z, far_z); - - GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); - if (shader == nullptr) - return; - - if (thumbnail_params.transparent_background) - glsafe(::glClearColor(0.0f, 0.0f, 0.0f, 0.0f)); - - glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); - glsafe(::glEnable(GL_DEPTH_TEST)); - - shader->start_using(); - shader->set_uniform("emission_factor", 0.0f); - -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Transform3d& projection_matrix = camera.get_projection_matrix(); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - for (GLVolume* vol : visible_volumes) { -#if ENABLE_LEGACY_OPENGL_REMOVAL - vol->model.set_color((vol->printable && !vol->is_outside) ? (current_printer_technology() == ptSLA ? vol->color : ColorRGBA::ORANGE()) : ColorRGBA::GRAY()); -#else - shader->set_uniform("uniform_color", (vol->printable && !vol->is_outside) ? (current_printer_technology() == ptSLA ? vol->color : ColorRGBA::ORANGE()) : ColorRGBA::GRAY()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - // the volume may have been deactivated by an active gizmo - const bool is_active = vol->is_active; - vol->is_active = true; -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Transform3d matrix = view_matrix * vol->world_matrix(); - shader->set_uniform("view_model_matrix", matrix); - shader->set_uniform("projection_matrix", projection_matrix); - shader->set_uniform("normal_matrix", (Matrix3d)matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - vol->render(); - vol->is_active = is_active; - } - - shader->stop_using(); - - glsafe(::glDisable(GL_DEPTH_TEST)); - - if (thumbnail_params.show_bed) -#if ENABLE_GL_SHADERS_ATTRIBUTES - _render_bed(view_matrix, projection_matrix, !camera.is_looking_downward(), false); -#else - _render_bed(!camera.is_looking_downward(), false); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - // restore background color - if (thumbnail_params.transparent_background) - glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 1.0f)); -} - -void GLCanvas3D::_render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type) -{ - thumbnail_data.set(w, h); - if (!thumbnail_data.is_valid()) - return; - - bool multisample = m_multisample_allowed; - if (multisample) - glsafe(::glEnable(GL_MULTISAMPLE)); - - GLint max_samples; - glsafe(::glGetIntegerv(GL_MAX_SAMPLES, &max_samples)); - GLsizei num_samples = max_samples / 2; - - GLuint render_fbo; - glsafe(::glGenFramebuffers(1, &render_fbo)); - glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, render_fbo)); - - GLuint render_tex = 0; - GLuint render_tex_buffer = 0; - if (multisample) { - // use renderbuffer instead of texture to avoid the need to use glTexImage2DMultisample which is available only since OpenGL 3.2 - glsafe(::glGenRenderbuffers(1, &render_tex_buffer)); - glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, render_tex_buffer)); - glsafe(::glRenderbufferStorageMultisample(GL_RENDERBUFFER, num_samples, GL_RGBA8, w, h)); - glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, render_tex_buffer)); - } - else { - glsafe(::glGenTextures(1, &render_tex)); - glsafe(::glBindTexture(GL_TEXTURE_2D, render_tex)); - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); - glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, render_tex, 0)); - } - - GLuint render_depth; - glsafe(::glGenRenderbuffers(1, &render_depth)); - glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, render_depth)); - if (multisample) - glsafe(::glRenderbufferStorageMultisample(GL_RENDERBUFFER, num_samples, GL_DEPTH_COMPONENT24, w, h)); - else - glsafe(::glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, w, h)); - - glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, render_depth)); - - GLenum drawBufs[] = { GL_COLOR_ATTACHMENT0 }; - glsafe(::glDrawBuffers(1, drawBufs)); - - if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) { - _render_thumbnail_internal(thumbnail_data, thumbnail_params, volumes, camera_type); - - if (multisample) { - GLuint resolve_fbo; - glsafe(::glGenFramebuffers(1, &resolve_fbo)); - glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, resolve_fbo)); - - GLuint resolve_tex; - glsafe(::glGenTextures(1, &resolve_tex)); - glsafe(::glBindTexture(GL_TEXTURE_2D, resolve_tex)); - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); - glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, resolve_tex, 0)); - - glsafe(::glDrawBuffers(1, drawBufs)); - - if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) { - glsafe(::glBindFramebuffer(GL_READ_FRAMEBUFFER, render_fbo)); - glsafe(::glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolve_fbo)); - glsafe(::glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_LINEAR)); - - glsafe(::glBindFramebuffer(GL_READ_FRAMEBUFFER, resolve_fbo)); - glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); - } - - glsafe(::glDeleteTextures(1, &resolve_tex)); - glsafe(::glDeleteFramebuffers(1, &resolve_fbo)); - } - else - glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); - -#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT - debug_output_thumbnail(thumbnail_data); -#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT - } - - glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, 0)); - glsafe(::glDeleteRenderbuffers(1, &render_depth)); - if (render_tex_buffer != 0) - glsafe(::glDeleteRenderbuffers(1, &render_tex_buffer)); - if (render_tex != 0) - glsafe(::glDeleteTextures(1, &render_tex)); - glsafe(::glDeleteFramebuffers(1, &render_fbo)); - - if (multisample) - glsafe(::glDisable(GL_MULTISAMPLE)); -} - -void GLCanvas3D::_render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type) -{ - thumbnail_data.set(w, h); - if (!thumbnail_data.is_valid()) - return; - - bool multisample = m_multisample_allowed; - if (multisample) - glsafe(::glEnable(GL_MULTISAMPLE)); - - GLint max_samples; - glsafe(::glGetIntegerv(GL_MAX_SAMPLES_EXT, &max_samples)); - GLsizei num_samples = max_samples / 2; - - GLuint render_fbo; - glsafe(::glGenFramebuffersEXT(1, &render_fbo)); - glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, render_fbo)); - - GLuint render_tex = 0; - GLuint render_tex_buffer = 0; - if (multisample) { - // use renderbuffer instead of texture to avoid the need to use glTexImage2DMultisample which is available only since OpenGL 3.2 - glsafe(::glGenRenderbuffersEXT(1, &render_tex_buffer)); - glsafe(::glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, render_tex_buffer)); - glsafe(::glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, num_samples, GL_RGBA8, w, h)); - glsafe(::glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, render_tex_buffer)); - } - else { - glsafe(::glGenTextures(1, &render_tex)); - glsafe(::glBindTexture(GL_TEXTURE_2D, render_tex)); - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); - glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, render_tex, 0)); - } - - GLuint render_depth; - glsafe(::glGenRenderbuffersEXT(1, &render_depth)); - glsafe(::glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, render_depth)); - if (multisample) - glsafe(::glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, num_samples, GL_DEPTH_COMPONENT24, w, h)); - else - glsafe(::glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, w, h)); - - glsafe(::glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, render_depth)); - - GLenum drawBufs[] = { GL_COLOR_ATTACHMENT0 }; - glsafe(::glDrawBuffers(1, drawBufs)); - - if (::glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT) { - _render_thumbnail_internal(thumbnail_data, thumbnail_params, volumes, camera_type); - - if (multisample) { - GLuint resolve_fbo; - glsafe(::glGenFramebuffersEXT(1, &resolve_fbo)); - glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, resolve_fbo)); - - GLuint resolve_tex; - glsafe(::glGenTextures(1, &resolve_tex)); - glsafe(::glBindTexture(GL_TEXTURE_2D, resolve_tex)); - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); - glsafe(::glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, resolve_tex, 0)); - - glsafe(::glDrawBuffers(1, drawBufs)); - - if (::glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT) { - glsafe(::glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, render_fbo)); - glsafe(::glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, resolve_fbo)); - glsafe(::glBlitFramebufferEXT(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_LINEAR)); - - glsafe(::glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, resolve_fbo)); - glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); - } - - glsafe(::glDeleteTextures(1, &resolve_tex)); - glsafe(::glDeleteFramebuffersEXT(1, &resolve_fbo)); - } - else - glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); - -#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT - debug_output_thumbnail(thumbnail_data); -#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT - } - - glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0)); - glsafe(::glDeleteRenderbuffersEXT(1, &render_depth)); - if (render_tex_buffer != 0) - glsafe(::glDeleteRenderbuffersEXT(1, &render_tex_buffer)); - if (render_tex != 0) - glsafe(::glDeleteTextures(1, &render_tex)); - glsafe(::glDeleteFramebuffersEXT(1, &render_fbo)); - - if (multisample) - glsafe(::glDisable(GL_MULTISAMPLE)); -} - -void GLCanvas3D::_render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type) -{ - // check that thumbnail size does not exceed the default framebuffer size - const Size& cnv_size = get_canvas_size(); - unsigned int cnv_w = (unsigned int)cnv_size.get_width(); - unsigned int cnv_h = (unsigned int)cnv_size.get_height(); - if (w > cnv_w || h > cnv_h) { - float ratio = std::min((float)cnv_w / (float)w, (float)cnv_h / (float)h); - w = (unsigned int)(ratio * (float)w); - h = (unsigned int)(ratio * (float)h); - } - - thumbnail_data.set(w, h); - if (!thumbnail_data.is_valid()) - return; - - _render_thumbnail_internal(thumbnail_data, thumbnail_params, volumes, camera_type); - - glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); -#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT - debug_output_thumbnail(thumbnail_data); -#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT - - // restore the default framebuffer size to avoid flickering on the 3D scene - wxGetApp().plater()->get_camera().apply_viewport(0, 0, cnv_size.get_width(), cnv_size.get_height()); -} - -bool GLCanvas3D::_init_toolbars() -{ - if (!_init_main_toolbar()) - return false; - - if (!_init_undoredo_toolbar()) - return false; - - if (!_init_view_toolbar()) - return false; - - if (!_init_collapse_toolbar()) - return false; - - return true; -} - -bool GLCanvas3D::_init_main_toolbar() -{ - if (!m_main_toolbar.is_enabled()) - return true; - - BackgroundTexture::Metadata background_data; - background_data.filename = "toolbar_background.png"; - background_data.left = 16; - background_data.top = 16; - background_data.right = 16; - background_data.bottom = 16; - - if (!m_main_toolbar.init(background_data)) { - // unable to init the toolbar texture, disable it - m_main_toolbar.set_enabled(false); - return true; - } - // init arrow -#if ENABLE_GL_SHADERS_ATTRIBUTES - if (!m_main_toolbar.init_arrow("toolbar_arrow_2.svg")) -#else - BackgroundTexture::Metadata arrow_data; - arrow_data.filename = "toolbar_arrow.svg"; - arrow_data.left = 0; - arrow_data.top = 0; - arrow_data.right = 0; - arrow_data.bottom = 0; - if (!m_main_toolbar.init_arrow(arrow_data)) -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - BOOST_LOG_TRIVIAL(error) << "Main toolbar failed to load arrow texture."; - - // m_gizmos is created at constructor, thus we can init arrow here. -#if ENABLE_GL_SHADERS_ATTRIBUTES - if (!m_gizmos.init_arrow("toolbar_arrow_2.svg")) -#else - if (!m_gizmos.init_arrow(arrow_data)) -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - BOOST_LOG_TRIVIAL(error) << "Gizmos manager failed to load arrow texture."; - -// m_main_toolbar.set_layout_type(GLToolbar::Layout::Vertical); - m_main_toolbar.set_layout_type(GLToolbar::Layout::Horizontal); - m_main_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Right); - m_main_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top); - m_main_toolbar.set_border(5.0f); - m_main_toolbar.set_separator_size(5); - m_main_toolbar.set_gap_size(4); - - GLToolbarItem::Data item; - - item.name = "add"; - item.icon_filename = "add.svg"; - item.tooltip = _utf8(L("Add...")) + " [" + GUI::shortkey_ctrl_prefix() + "I]"; - item.sprite_id = 0; - item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ADD)); }; - if (!m_main_toolbar.add_item(item)) - return false; - - item.name = "delete"; - item.icon_filename = "remove.svg"; - item.tooltip = _utf8(L("Delete")) + " [Del]"; - item.sprite_id = 1; - item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE)); }; - item.enabling_callback = []()->bool { return wxGetApp().plater()->can_delete(); }; - if (!m_main_toolbar.add_item(item)) - return false; - - item.name = "deleteall"; - item.icon_filename = "delete_all.svg"; - item.tooltip = _utf8(L("Delete all")) + " [" + GUI::shortkey_ctrl_prefix() + "Del]"; - item.sprite_id = 2; - item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); }; - item.enabling_callback = []()->bool { return wxGetApp().plater()->can_delete_all(); }; - if (!m_main_toolbar.add_item(item)) - return false; - - item.name = "arrange"; - item.icon_filename = "arrange.svg"; - item.tooltip = _utf8(L("Arrange")) + " [A]\n" + _utf8(L("Arrange selection")) + " [Shift+A]\n" + _utf8(L("Click right mouse button to show arrangement options")); - item.sprite_id = 3; - item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ARRANGE)); }; - item.enabling_callback = []()->bool { return wxGetApp().plater()->can_arrange(); }; - item.right.toggable = true; - item.right.render_callback = [this](float left, float right, float, float) { - if (m_canvas != nullptr) - _render_arrange_menu(0.5f * (left + right)); - }; - if (!m_main_toolbar.add_item(item)) - return false; - - item.right.toggable = false; - item.right.render_callback = GLToolbarItem::Default_Render_Callback; - - if (!m_main_toolbar.add_separator()) - return false; - - item.name = "copy"; - item.icon_filename = "copy.svg"; - item.tooltip = _utf8(L("Copy")) + " [" + GUI::shortkey_ctrl_prefix() + "C]"; - item.sprite_id = 4; - item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_COPY)); }; - item.enabling_callback = []()->bool { return wxGetApp().plater()->can_copy_to_clipboard(); }; - if (!m_main_toolbar.add_item(item)) - return false; - - item.name = "paste"; - item.icon_filename = "paste.svg"; - item.tooltip = _utf8(L("Paste")) + " [" + GUI::shortkey_ctrl_prefix() + "V]"; - item.sprite_id = 5; - item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_PASTE)); }; - item.enabling_callback = []()->bool { return wxGetApp().plater()->can_paste_from_clipboard(); }; - if (!m_main_toolbar.add_item(item)) - return false; - - if (!m_main_toolbar.add_separator()) - return false; - - item.name = "more"; - item.icon_filename = "instance_add.svg"; - item.tooltip = _utf8(L("Add instance")) + " [+]"; - item.sprite_id = 6; - item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_MORE)); }; - item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; - item.enabling_callback = []()->bool { return wxGetApp().plater()->can_increase_instances(); }; - - if (!m_main_toolbar.add_item(item)) - return false; - - item.name = "fewer"; - item.icon_filename = "instance_remove.svg"; - item.tooltip = _utf8(L("Remove instance")) + " [-]"; - item.sprite_id = 7; - item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_FEWER)); }; - item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; - item.enabling_callback = []()->bool { return wxGetApp().plater()->can_decrease_instances(); }; - if (!m_main_toolbar.add_item(item)) - return false; - - if (!m_main_toolbar.add_separator()) - return false; - - item.name = "splitobjects"; - item.icon_filename = "split_objects.svg"; - item.tooltip = _utf8(L("Split to objects")); - item.sprite_id = 8; - item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_OBJECTS)); }; - item.visibility_callback = GLToolbarItem::Default_Visibility_Callback; - item.enabling_callback = []()->bool { return wxGetApp().plater()->can_split_to_objects(); }; - if (!m_main_toolbar.add_item(item)) - return false; - - item.name = "splitvolumes"; - item.icon_filename = "split_parts.svg"; - item.tooltip = _utf8(L("Split to parts")); - item.sprite_id = 9; - item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_VOLUMES)); }; - item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; - item.enabling_callback = []()->bool { return wxGetApp().plater()->can_split_to_volumes(); }; - if (!m_main_toolbar.add_item(item)) - return false; - - if (!m_main_toolbar.add_separator()) - return false; - - item.name = "settings"; - item.icon_filename = "settings.svg"; - item.tooltip = _u8L("Switch to Settings") + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "2] - " + _u8L("Print Settings Tab") + - "\n" + "[" + GUI::shortkey_ctrl_prefix() + "3] - " + (current_printer_technology() == ptFFF ? _u8L("Filament Settings Tab") : _u8L("Material Settings Tab")) + - "\n" + "[" + GUI::shortkey_ctrl_prefix() + "4] - " + _u8L("Printer Settings Tab") ; - item.sprite_id = 10; - item.enabling_callback = GLToolbarItem::Default_Enabling_Callback; - item.visibility_callback = []() { return (wxGetApp().app_config->get("new_settings_layout_mode") == "1" || - wxGetApp().app_config->get("dlg_settings_layout_mode") == "1"); }; - item.left.action_callback = []() { wxGetApp().mainframe->select_tab(); }; - if (!m_main_toolbar.add_item(item)) - return false; - - /* - if (!m_main_toolbar.add_separator()) - return false; - */ - - item.name = "search"; - item.icon_filename = "search_.svg"; - item.tooltip = _utf8(L("Search")) + " [" + GUI::shortkey_ctrl_prefix() + "F]"; - item.sprite_id = 11; - item.left.toggable = true; - item.left.render_callback = [this](float left, float right, float, float) { - if (m_canvas != nullptr) { - if (_render_search_list(0.5f * (left + right))) - _deactivate_search_toolbar_item(); - } - }; - item.left.action_callback = GLToolbarItem::Default_Action_Callback; - item.visibility_callback = GLToolbarItem::Default_Visibility_Callback; - item.enabling_callback = GLToolbarItem::Default_Enabling_Callback; - if (!m_main_toolbar.add_item(item)) - return false; - - if (!m_main_toolbar.add_separator()) - return false; - - item.name = "layersediting"; - item.icon_filename = "layers_white.svg"; - item.tooltip = _utf8(L("Variable layer height")); - item.sprite_id = 12; - item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); }; - item.visibility_callback = [this]()->bool { - bool res = current_printer_technology() == ptFFF; - // turns off if changing printer technology - if (!res && m_main_toolbar.is_item_visible("layersediting") && m_main_toolbar.is_item_pressed("layersediting")) - force_main_toolbar_left_action(get_main_toolbar_item_id("layersediting")); - - return res; - }; - item.enabling_callback = []()->bool { return wxGetApp().plater()->can_layers_editing(); }; - item.left.render_callback = GLToolbarItem::Default_Render_Callback; - if (!m_main_toolbar.add_item(item)) - return false; - - return true; -} - -bool GLCanvas3D::_init_undoredo_toolbar() -{ - if (!m_undoredo_toolbar.is_enabled()) - return true; - - BackgroundTexture::Metadata background_data; - background_data.filename = "toolbar_background.png"; - background_data.left = 16; - background_data.top = 16; - background_data.right = 16; - background_data.bottom = 16; - - if (!m_undoredo_toolbar.init(background_data)) { - // unable to init the toolbar texture, disable it - m_undoredo_toolbar.set_enabled(false); - return true; - } - - // init arrow -#if ENABLE_GL_SHADERS_ATTRIBUTES - if (!m_undoredo_toolbar.init_arrow("toolbar_arrow_2.svg")) -#else - BackgroundTexture::Metadata arrow_data; - arrow_data.filename = "toolbar_arrow.svg"; - arrow_data.left = 0; - arrow_data.top = 0; - arrow_data.right = 0; - arrow_data.bottom = 0; - if (!m_undoredo_toolbar.init_arrow(arrow_data)) -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - BOOST_LOG_TRIVIAL(error) << "Undo/Redo toolbar failed to load arrow texture."; - -// m_undoredo_toolbar.set_layout_type(GLToolbar::Layout::Vertical); - m_undoredo_toolbar.set_layout_type(GLToolbar::Layout::Horizontal); - m_undoredo_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Left); - m_undoredo_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top); - m_undoredo_toolbar.set_border(5.0f); - m_undoredo_toolbar.set_separator_size(5); - m_undoredo_toolbar.set_gap_size(4); - - GLToolbarItem::Data item; - - item.name = "undo"; - item.icon_filename = "undo_toolbar.svg"; - item.tooltip = _utf8(L("Undo")) + " [" + GUI::shortkey_ctrl_prefix() + "Z]\n" + _utf8(L("Click right mouse button to open/close History")); - item.sprite_id = 0; - item.left.action_callback = [this]() { post_event(SimpleEvent(EVT_GLCANVAS_UNDO)); }; - item.right.toggable = true; - item.right.action_callback = [this]() { m_imgui_undo_redo_hovered_pos = -1; }; - item.right.render_callback = [this](float left, float right, float, float) { - if (m_canvas != nullptr) { - if (_render_undo_redo_stack(true, 0.5f * (left + right))) - _deactivate_undo_redo_toolbar_items(); - } - }; - item.enabling_callback = [this]()->bool { - bool can_undo = wxGetApp().plater()->can_undo(); - int id = m_undoredo_toolbar.get_item_id("undo"); - - std::string curr_additional_tooltip; - m_undoredo_toolbar.get_additional_tooltip(id, curr_additional_tooltip); - - std::string new_additional_tooltip; - if (can_undo) { - std::string action; - wxGetApp().plater()->undo_redo_topmost_string_getter(true, action); - new_additional_tooltip = (boost::format(_utf8(L("Next Undo action: %1%"))) % action).str(); - } - - if (new_additional_tooltip != curr_additional_tooltip) { - m_undoredo_toolbar.set_additional_tooltip(id, new_additional_tooltip); - set_tooltip(""); - } - return can_undo; - }; - - if (!m_undoredo_toolbar.add_item(item)) - return false; - - item.name = "redo"; - item.icon_filename = "redo_toolbar.svg"; - item.tooltip = _utf8(L("Redo")) + " [" + GUI::shortkey_ctrl_prefix() + "Y]\n" + _utf8(L("Click right mouse button to open/close History")); - item.sprite_id = 1; - item.left.action_callback = [this]() { post_event(SimpleEvent(EVT_GLCANVAS_REDO)); }; - item.right.action_callback = [this]() { m_imgui_undo_redo_hovered_pos = -1; }; - item.right.render_callback = [this](float left, float right, float, float) { - if (m_canvas != nullptr) { - if (_render_undo_redo_stack(false, 0.5f * (left + right))) - _deactivate_undo_redo_toolbar_items(); - } - }; - item.enabling_callback = [this]()->bool { - bool can_redo = wxGetApp().plater()->can_redo(); - int id = m_undoredo_toolbar.get_item_id("redo"); - - std::string curr_additional_tooltip; - m_undoredo_toolbar.get_additional_tooltip(id, curr_additional_tooltip); - - std::string new_additional_tooltip; - if (can_redo) { - std::string action; - wxGetApp().plater()->undo_redo_topmost_string_getter(false, action); - new_additional_tooltip = (boost::format(_utf8(L("Next Redo action: %1%"))) % action).str(); - } - - if (new_additional_tooltip != curr_additional_tooltip) { - m_undoredo_toolbar.set_additional_tooltip(id, new_additional_tooltip); - set_tooltip(""); - } - return can_redo; - }; - - if (!m_undoredo_toolbar.add_item(item)) - return false; - /* - if (!m_undoredo_toolbar.add_separator()) - return false; - */ - return true; -} - -bool GLCanvas3D::_init_view_toolbar() -{ - return wxGetApp().plater()->init_view_toolbar(); -} - -bool GLCanvas3D::_init_collapse_toolbar() -{ - return wxGetApp().plater()->init_collapse_toolbar(); -} - -bool GLCanvas3D::_set_current() -{ - return m_context != nullptr && m_canvas->SetCurrent(*m_context); -} - -void GLCanvas3D::_resize(unsigned int w, unsigned int h) -{ - if (m_canvas == nullptr && m_context == nullptr) - return; - - const std::array new_size = { w, h }; - if (m_old_size == new_size) - return; - - m_old_size = new_size; - - auto *imgui = wxGetApp().imgui(); - imgui->set_display_size(static_cast(w), static_cast(h)); - const float font_size = 1.5f * wxGetApp().em_unit(); -#if ENABLE_RETINA_GL - imgui->set_scaling(font_size, 1.0f, m_retina_helper->get_scale_factor()); -#else - imgui->set_scaling(font_size, m_canvas->GetContentScaleFactor(), 1.0f); -#endif - - this->request_extra_frame(); - - // ensures that this canvas is current - _set_current(); -} - -BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_bed_model) const -{ - BoundingBoxf3 bb = volumes_bounding_box(); - - // The following is a workaround for gizmos not being taken in account when calculating the tight camera frustrum - // A better solution would ask the gizmo manager for the bounding box of the current active gizmo, if any - if (include_gizmos && m_gizmos.is_running()) { - const BoundingBoxf3 sel_bb = m_selection.get_bounding_box(); - const Vec3d sel_bb_center = sel_bb.center(); - const Vec3d extend_by = sel_bb.max_size() * Vec3d::Ones(); - bb.merge(BoundingBoxf3(sel_bb_center - extend_by, sel_bb_center + extend_by)); - } - - const BoundingBoxf3 bed_bb = include_bed_model ? m_bed.extended_bounding_box() : m_bed.build_volume().bounding_volume(); - bb.merge(bed_bb); - - if (!m_main_toolbar.is_enabled()) - bb.merge(m_gcode_viewer.get_max_bounding_box()); - - // clamp max bb size with respect to bed bb size - if (!m_picking_enabled) { - static const double max_scale_factor = 2.0; - const Vec3d bb_size = bb.size(); - const Vec3d bed_bb_size = m_bed.build_volume().bounding_volume().size(); - if (bb_size.x() > max_scale_factor * bed_bb_size.x() || - bb_size.y() > max_scale_factor * bed_bb_size.y() || - bb_size.z() > max_scale_factor * bed_bb_size.z()) { - const Vec3d bed_bb_center = bed_bb.center(); - const Vec3d extend_by = max_scale_factor * bed_bb_size; - bb = BoundingBoxf3(bed_bb_center - extend_by, bed_bb_center + extend_by); - } - } - - return bb; -} - -void GLCanvas3D::_zoom_to_box(const BoundingBoxf3& box, double margin_factor) -{ - wxGetApp().plater()->get_camera().zoom_to_box(box, margin_factor); - m_dirty = true; -} - -void GLCanvas3D::_update_camera_zoom(double zoom) -{ - wxGetApp().plater()->get_camera().update_zoom(zoom); - m_dirty = true; -} - -void GLCanvas3D::_refresh_if_shown_on_screen() -{ - if (_is_shown_on_screen()) { - const Size& cnv_size = get_canvas_size(); - _resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height()); - - // Because of performance problems on macOS, where PaintEvents are not delivered - // frequently enough, we call render() here directly when we can. - render(); - } -} - -void GLCanvas3D::_picking_pass() -{ - if (m_picking_enabled && !m_mouse.dragging && m_mouse.position != Vec2d(DBL_MAX, DBL_MAX)) { - m_hover_volume_idxs.clear(); - - // Render the object for picking. - // FIXME This cannot possibly work in a multi - sampled context as the color gets mangled by the anti - aliasing. - // Better to use software ray - casting on a bounding - box hierarchy. - - if (m_multisample_allowed) - // This flag is often ignored by NVIDIA drivers if rendering into a screen buffer. - glsafe(::glDisable(GL_MULTISAMPLE)); - - glsafe(::glDisable(GL_BLEND)); - glsafe(::glEnable(GL_DEPTH_TEST)); - - glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - m_camera_clipping_plane = m_gizmos.get_clipping_plane(); - if (m_camera_clipping_plane.is_active()) { - ::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_camera_clipping_plane.get_data().data()); - ::glEnable(GL_CLIP_PLANE0); - } -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - _render_volumes_for_picking(); -#if !ENABLE_GL_SHADERS_ATTRIBUTES - if (m_camera_clipping_plane.is_active()) - ::glDisable(GL_CLIP_PLANE0); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - _render_bed_for_picking(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward()); -#else - _render_bed_for_picking(!wxGetApp().plater()->get_camera().is_looking_downward()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - m_gizmos.render_current_gizmo_for_picking_pass(); - - if (m_multisample_allowed) - glsafe(::glEnable(GL_MULTISAMPLE)); - - int volume_id = -1; - int gizmo_id = -1; - - std::array color = { 0, 0, 0, 0 }; - const Size& cnv_size = get_canvas_size(); - bool inside = 0 <= m_mouse.position(0) && m_mouse.position(0) < cnv_size.get_width() && 0 <= m_mouse.position(1) && m_mouse.position(1) < cnv_size.get_height(); - if (inside) { - glsafe(::glReadPixels(m_mouse.position(0), cnv_size.get_height() - m_mouse.position.y() - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color.data())); - if (picking_checksum_alpha_channel(color[0], color[1], color[2]) == color[3]) { - // Only non-interpolated colors are valid, those have their lowest three bits zeroed. - // we reserve color = (0,0,0) for occluders (as the printbed) - // volumes' id are shifted by 1 - // see: _render_volumes_for_picking() - unsigned int id = picking_encode(color[0], color[1], color[2]); - volume_id = id - 1; - // gizmos' id are instead properly encoded by the color - gizmo_id = id; - } - } - if (0 <= volume_id && volume_id < (int)m_volumes.volumes.size()) { - // do not add the volume id if any gizmo is active and CTRL is pressed - if (m_gizmos.get_current_type() == GLGizmosManager::EType::Undefined || !wxGetKeyState(WXK_CONTROL)) - m_hover_volume_idxs.emplace_back(volume_id); - m_gizmos.set_hover_id(-1); - } - else - m_gizmos.set_hover_id(inside && (unsigned int)gizmo_id <= GLGizmoBase::BASE_ID ? ((int)GLGizmoBase::BASE_ID - gizmo_id) : -1); - - _update_volumes_hover_state(); - } -} - -void GLCanvas3D::_rectangular_selection_picking_pass() -{ - m_gizmos.set_hover_id(-1); - - std::set idxs; - - if (m_picking_enabled) { - if (m_multisample_allowed) - // This flag is often ignored by NVIDIA drivers if rendering into a screen buffer. - glsafe(::glDisable(GL_MULTISAMPLE)); - - glsafe(::glDisable(GL_BLEND)); - glsafe(::glEnable(GL_DEPTH_TEST)); - - glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); - - _render_volumes_for_picking(); -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - _render_bed_for_picking(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward()); -#else - _render_bed_for_picking(!wxGetApp().plater()->get_camera().is_looking_downward()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - if (m_multisample_allowed) - glsafe(::glEnable(GL_MULTISAMPLE)); - - int width = std::max((int)m_rectangle_selection.get_width(), 1); - int height = std::max((int)m_rectangle_selection.get_height(), 1); - int px_count = width * height; - - int left = (int)m_rectangle_selection.get_left(); - int top = get_canvas_size().get_height() - (int)m_rectangle_selection.get_top(); - if (left >= 0 && top >= 0) { -#define USE_PARALLEL 1 -#if USE_PARALLEL - struct Pixel - { - std::array data; - // Only non-interpolated colors are valid, those have their lowest three bits zeroed. - bool valid() const { return picking_checksum_alpha_channel(data[0], data[1], data[2]) == data[3]; } - // we reserve color = (0,0,0) for occluders (as the printbed) - // volumes' id are shifted by 1 - // see: _render_volumes_for_picking() - int id() const { return data[0] + (data[1] << 8) + (data[2] << 16) - 1; } - }; - - std::vector frame(px_count); - glsafe(::glReadPixels(left, top, width, height, GL_RGBA, GL_UNSIGNED_BYTE, (void*)frame.data())); - - tbb::spin_mutex mutex; - tbb::parallel_for(tbb::blocked_range(0, frame.size(), (size_t)width), - [this, &frame, &idxs, &mutex](const tbb::blocked_range& range) { - for (size_t i = range.begin(); i < range.end(); ++i) - if (frame[i].valid()) { - int volume_id = frame[i].id(); - if (0 <= volume_id && volume_id < (int)m_volumes.volumes.size()) { - mutex.lock(); - idxs.insert(volume_id); - mutex.unlock(); - } - } - }); -#else - std::vector frame(4 * px_count); - glsafe(::glReadPixels(left, top, width, height, GL_RGBA, GL_UNSIGNED_BYTE, (void*)frame.data())); - - for (int i = 0; i < px_count; ++i) - { - int px_id = 4 * i; - int volume_id = frame[px_id] + (frame[px_id + 1] << 8) + (frame[px_id + 2] << 16); - if (0 <= volume_id && volume_id < (int)m_volumes.volumes.size()) - idxs.insert(volume_id); - } -#endif // USE_PARALLEL - } - } - - m_hover_volume_idxs.assign(idxs.begin(), idxs.end()); - _update_volumes_hover_state(); -} - -void GLCanvas3D::_render_background() -{ - bool use_error_color = false; - if (wxGetApp().is_editor()) { - use_error_color = m_dynamic_background_enabled && - (current_printer_technology() != ptSLA || !m_volumes.empty()); - - if (!m_volumes.empty()) - use_error_color &= _is_any_volume_outside(); - else - use_error_color &= m_gcode_viewer.has_data() && !m_gcode_viewer.is_contained_in_bed(); - } - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPushMatrix()); - glsafe(::glLoadIdentity()); - glsafe(::glMatrixMode(GL_PROJECTION)); - glsafe(::glPushMatrix()); - glsafe(::glLoadIdentity()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - - // Draws a bottom to top gradient over the complete screen. - glsafe(::glDisable(GL_DEPTH_TEST)); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - const ColorRGBA bottom_color = use_error_color ? ERROR_BG_DARK_COLOR : DEFAULT_BG_DARK_COLOR; - - if (!m_background.is_initialized()) { - m_background.reset(); - - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P2T2 }; - init_data.reserve_vertices(4); - init_data.reserve_indices(6); - - // vertices - init_data.add_vertex(Vec2f(-1.0f, -1.0f), Vec2f(0.0f, 0.0f)); - init_data.add_vertex(Vec2f(1.0f, -1.0f), Vec2f(1.0f, 0.0f)); - init_data.add_vertex(Vec2f(1.0f, 1.0f), Vec2f(1.0f, 1.0f)); - init_data.add_vertex(Vec2f(-1.0f, 1.0f), Vec2f(0.0f, 1.0f)); - - // indices - init_data.add_triangle(0, 1, 2); - init_data.add_triangle(2, 3, 0); - - m_background.init_from(std::move(init_data)); - } - - GLShaderProgram* shader = wxGetApp().get_shader("background"); - if (shader != nullptr) { - shader->start_using(); - shader->set_uniform("top_color", use_error_color ? ERROR_BG_LIGHT_COLOR : DEFAULT_BG_LIGHT_COLOR); - shader->set_uniform("bottom_color", bottom_color); - m_background.render(); - shader->stop_using(); - } -#else - ::glBegin(GL_QUADS); - ::glColor3fv(use_error_color ? ERROR_BG_DARK_COLOR.data(): DEFAULT_BG_DARK_COLOR.data()); - ::glVertex2f(-1.0f, -1.0f); - ::glVertex2f(1.0f, -1.0f); - - ::glColor3fv(use_error_color ? ERROR_BG_LIGHT_COLOR.data() : DEFAULT_BG_LIGHT_COLOR.data()); - ::glVertex2f(1.0f, 1.0f); - ::glVertex2f(-1.0f, 1.0f); - glsafe(::glEnd()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - glsafe(::glEnable(GL_DEPTH_TEST)); - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); - glsafe(::glMatrixMode(GL_MODELVIEW)); - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES -} - -#if ENABLE_GL_SHADERS_ATTRIBUTES -void GLCanvas3D::_render_bed(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_axes) -#else -void GLCanvas3D::_render_bed(bool bottom, bool show_axes) -#endif // ENABLE_GL_SHADERS_ATTRIBUTES -{ - float scale_factor = 1.0; -#if ENABLE_RETINA_GL - scale_factor = m_retina_helper->get_scale_factor(); -#endif // ENABLE_RETINA_GL - - bool show_texture = ! bottom || - (m_gizmos.get_current_type() != GLGizmosManager::FdmSupports - && m_gizmos.get_current_type() != GLGizmosManager::SlaSupports - && m_gizmos.get_current_type() != GLGizmosManager::Hollow - && m_gizmos.get_current_type() != GLGizmosManager::Seam - && m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation); - -#if ENABLE_GL_SHADERS_ATTRIBUTES - m_bed.render(*this, view_matrix, projection_matrix, bottom, scale_factor, show_axes, show_texture); -#else - m_bed.render(*this, bottom, scale_factor, show_axes, show_texture); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES -} - -#if ENABLE_GL_SHADERS_ATTRIBUTES -void GLCanvas3D::_render_bed_for_picking(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom) -#else -void GLCanvas3D::_render_bed_for_picking(bool bottom) -#endif // ENABLE_GL_SHADERS_ATTRIBUTES -{ - float scale_factor = 1.0; -#if ENABLE_RETINA_GL - scale_factor = m_retina_helper->get_scale_factor(); -#endif // ENABLE_RETINA_GL - -#if ENABLE_GL_SHADERS_ATTRIBUTES - m_bed.render_for_picking(*this, view_matrix, projection_matrix, bottom, scale_factor); -#else - m_bed.render_for_picking(*this, bottom, scale_factor); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES -} - -void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type) -{ - if (m_volumes.empty()) - return; - - glsafe(::glEnable(GL_DEPTH_TEST)); - - m_camera_clipping_plane = m_gizmos.get_clipping_plane(); - - if (m_picking_enabled) - // Update the layer editing selection to the first object selected, update the current object maximum Z. - m_layers_editing.select_object(*m_model, this->is_layers_editing_enabled() ? m_selection.get_object_idx() : -1); - - if (const BuildVolume &build_volume = m_bed.build_volume(); build_volume.valid()) { - switch (build_volume.type()) { - case BuildVolume::Type::Rectangle: { - const BoundingBox3Base bed_bb = build_volume.bounding_volume().inflated(BuildVolume::SceneEpsilon); - m_volumes.set_print_volume({ 0, // circle - { float(bed_bb.min.x()), float(bed_bb.min.y()), float(bed_bb.max.x()), float(bed_bb.max.y()) }, - { 0.0f, float(build_volume.max_print_height()) } }); - break; - } - case BuildVolume::Type::Circle: { - m_volumes.set_print_volume({ 1, // rectangle - { unscaled(build_volume.circle().center.x()), unscaled(build_volume.circle().center.y()), unscaled(build_volume.circle().radius + BuildVolume::SceneEpsilon), 0.0f }, - { 0.0f, float(build_volume.max_print_height() + BuildVolume::SceneEpsilon) } }); - break; - } - default: - case BuildVolume::Type::Convex: - case BuildVolume::Type::Custom: { - m_volumes.set_print_volume({ static_cast(type), - { -FLT_MAX, -FLT_MAX, FLT_MAX, FLT_MAX }, - { -FLT_MAX, FLT_MAX } } - ); - } - } - if (m_requires_check_outside_state) { - m_volumes.check_outside_state(build_volume, nullptr); - m_requires_check_outside_state = false; - } - } - - if (m_use_clipping_planes) - m_volumes.set_z_range(-m_clipping_planes[0].get_data()[3], m_clipping_planes[1].get_data()[3]); - else - m_volumes.set_z_range(-FLT_MAX, FLT_MAX); - - m_volumes.set_clipping_plane(m_camera_clipping_plane.get_data()); - m_volumes.set_show_sinking_contours(! m_gizmos.is_hiding_instances()); -#if ENABLE_SHOW_NON_MANIFOLD_EDGES - m_volumes.set_show_non_manifold_edges(!m_gizmos.is_hiding_instances() && m_gizmos.get_current_type() != GLGizmosManager::Simplify); -#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES - - GLShaderProgram* shader = wxGetApp().get_shader("gouraud"); - if (shader != nullptr) { - shader->start_using(); - - switch (type) - { - default: - case GLVolumeCollection::ERenderType::Opaque: - { - if (m_picking_enabled && !m_gizmos.is_dragging() && m_layers_editing.is_enabled() && (m_layers_editing.last_object_id != -1) && (m_layers_editing.object_max_z() > 0.0f)) { - int object_id = m_layers_editing.last_object_id; -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - m_volumes.render(type, false, camera.get_view_matrix(), camera.get_projection_matrix(), [object_id](const GLVolume& volume) { - // Which volume to paint without the layer height profile shader? - return volume.is_active && (volume.is_modifier || volume.composite_id.object_id != object_id); - }); -#else - m_volumes.render(type, false, wxGetApp().plater()->get_camera().get_view_matrix(), [object_id](const GLVolume& volume) { - // Which volume to paint without the layer height profile shader? - return volume.is_active && (volume.is_modifier || volume.composite_id.object_id != object_id); - }); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - // Let LayersEditing handle rendering of the active object using the layer height profile shader. - m_layers_editing.render_volumes(*this, m_volumes); - } - else { - // do not cull backfaces to show broken geometry, if any -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - m_volumes.render(type, m_picking_enabled, camera.get_view_matrix(), camera.get_projection_matrix(), [this](const GLVolume& volume) { - return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0); - }); -#else - m_volumes.render(type, m_picking_enabled, wxGetApp().plater()->get_camera().get_view_matrix(), [this](const GLVolume& volume) { - return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0); - }); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - } - - // In case a painting gizmo is open, it should render the painted triangles - // before transparent objects are rendered. Otherwise they would not be - // visible when inside modifier meshes etc. - { - GLGizmosManager& gm = get_gizmos_manager(); -// GLGizmosManager::EType type = gm.get_current_type(); - if (dynamic_cast(gm.get_current())) { - shader->stop_using(); - gm.render_painter_gizmo(); - shader->start_using(); - } - } - break; - } - case GLVolumeCollection::ERenderType::Transparent: - { -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - m_volumes.render(type, false, camera.get_view_matrix(), camera.get_projection_matrix()); -#else - m_volumes.render(type, false, wxGetApp().plater()->get_camera().get_view_matrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - break; - } - } - shader->stop_using(); - } - - m_camera_clipping_plane = ClippingPlane::ClipsNothing(); -} - -void GLCanvas3D::_render_gcode() -{ - m_gcode_viewer.render(); -} - -#if ENABLE_SHOW_TOOLPATHS_COG -void GLCanvas3D::_render_gcode_cog() -{ - m_gcode_viewer.render_cog(); -} -#endif // ENABLE_SHOW_TOOLPATHS_COG - -void GLCanvas3D::_render_selection() -{ - float scale_factor = 1.0; -#if ENABLE_RETINA_GL - scale_factor = m_retina_helper->get_scale_factor(); -#endif // ENABLE_RETINA_GL - - if (!m_gizmos.is_running()) - m_selection.render(scale_factor); -} - -void GLCanvas3D::_render_sequential_clearance() -{ - if (m_layers_editing.is_enabled() || m_gizmos.is_dragging()) - return; - - switch (m_gizmos.get_current_type()) - { - case GLGizmosManager::EType::Flatten: - case GLGizmosManager::EType::Cut: - case GLGizmosManager::EType::Hollow: - case GLGizmosManager::EType::SlaSupports: - case GLGizmosManager::EType::FdmSupports: - case GLGizmosManager::EType::Seam: { return; } - default: { break; } - } - - m_sequential_print_clearance.render(); -} - -#if ENABLE_RENDER_SELECTION_CENTER -void GLCanvas3D::_render_selection_center() -{ - m_selection.render_center(m_gizmos.is_dragging()); -} -#endif // ENABLE_RENDER_SELECTION_CENTER - -void GLCanvas3D::_check_and_update_toolbar_icon_scale() -{ - // Don't update a toolbar scale, when we are on a Preview - if (wxGetApp().plater()->is_preview_shown()) - return; - - float scale = wxGetApp().toolbar_icon_scale(); - Size cnv_size = get_canvas_size(); - - float size = GLToolbar::Default_Icons_Size * scale; - - // Set current size for all top toolbars. It will be used for next calculations - GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); -#if ENABLE_RETINA_GL - const float sc = m_retina_helper->get_scale_factor() * scale; - m_main_toolbar.set_scale(sc); - m_undoredo_toolbar.set_scale(sc); - collapse_toolbar.set_scale(sc); - size *= m_retina_helper->get_scale_factor(); -#else - m_main_toolbar.set_icons_size(size); - m_undoredo_toolbar.set_icons_size(size); - collapse_toolbar.set_icons_size(size); -#endif // ENABLE_RETINA_GL - - float top_tb_width = m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar.get_width(); - int items_cnt = m_main_toolbar.get_visible_items_cnt() + m_undoredo_toolbar.get_visible_items_cnt() + collapse_toolbar.get_visible_items_cnt(); - float noitems_width = top_tb_width - size * items_cnt; // width of separators and borders in top toolbars - - // calculate scale needed for items in all top toolbars - float new_h_scale = (cnv_size.get_width() - noitems_width) / (items_cnt * GLToolbar::Default_Icons_Size); - - items_cnt = m_gizmos.get_selectable_icons_cnt() + 3; // +3 means a place for top and view toolbars and separators in gizmos toolbar - - // calculate scale needed for items in the gizmos toolbar - float new_v_scale = cnv_size.get_height() / (items_cnt * GLGizmosManager::Default_Icons_Size); - - // set minimum scale as a auto scale for the toolbars - float new_scale = std::min(new_h_scale, new_v_scale); -#if ENABLE_RETINA_GL - new_scale /= m_retina_helper->get_scale_factor(); -#endif - if (fabs(new_scale - scale) > 0.01) // scale is changed by 1% and more - wxGetApp().set_auto_toolbar_icon_scale(new_scale); -} - -void GLCanvas3D::_render_overlays() -{ - glsafe(::glDisable(GL_DEPTH_TEST)); -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPushMatrix()); - glsafe(::glLoadIdentity()); - // ensure that the textures are renderered inside the frustrum - const Camera& camera = wxGetApp().plater()->get_camera(); - glsafe(::glTranslated(0.0, 0.0, -(camera.get_near_z() + 0.005))); - // ensure that the overlay fits the frustrum near z plane - double gui_scale = camera.get_gui_scale(); - glsafe(::glScaled(gui_scale, gui_scale, 1.0)); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - - _check_and_update_toolbar_icon_scale(); - - _render_gizmos_overlay(); - - // main toolbar and undoredo toolbar need to be both updated before rendering because both their sizes are needed - // to correctly place them -#if ENABLE_RETINA_GL - const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(/*true*/); - m_main_toolbar.set_scale(scale); - m_undoredo_toolbar.set_scale(scale); - wxGetApp().plater()->get_collapse_toolbar().set_scale(scale); -#else - const float size = int(GLToolbar::Default_Icons_Size * wxGetApp().toolbar_icon_scale(/*true*/)); - m_main_toolbar.set_icons_size(size); - m_undoredo_toolbar.set_icons_size(size); - wxGetApp().plater()->get_collapse_toolbar().set_icons_size(size); -#endif // ENABLE_RETINA_GL - - _render_main_toolbar(); - _render_undoredo_toolbar(); - _render_collapse_toolbar(); - _render_view_toolbar(); - - if (m_layers_editing.last_object_id >= 0 && m_layers_editing.object_max_z() > 0.0f) - m_layers_editing.render_overlay(*this); - - const ConfigOptionBool* opt = dynamic_cast(m_config->option("complete_objects")); - bool sequential_print = opt != nullptr && opt->value; - std::vector sorted_instances; - if (sequential_print) { - for (ModelObject* model_object : m_model->objects) - for (ModelInstance* model_instance : model_object->instances) { - sorted_instances.emplace_back(model_instance); - } - } - m_labels.render(sorted_instances); - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES -} - -void GLCanvas3D::_render_volumes_for_picking() const -{ -#if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_GL_SHADERS_ATTRIBUTES - GLShaderProgram* shader = wxGetApp().get_shader("flat_clip"); -#else - GLShaderProgram* shader = wxGetApp().get_shader("flat"); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - if (shader == nullptr) - return; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - // do not cull backfaces to show broken geometry, if any - glsafe(::glDisable(GL_CULL_FACE)); - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - - const Transform3d& view_matrix = wxGetApp().plater()->get_camera().get_view_matrix(); - for (size_t type = 0; type < 2; ++ type) { - GLVolumeWithIdAndZList to_render = volumes_to_render(m_volumes.volumes, (type == 0) ? GLVolumeCollection::ERenderType::Opaque : GLVolumeCollection::ERenderType::Transparent, view_matrix); - for (const GLVolumeWithIdAndZ& volume : to_render) - if (!volume.first->disabled && (volume.first->composite_id.volume_id >= 0 || m_render_sla_auxiliaries)) { - // Object picking mode. Render the object with a color encoding the object index. - // we reserve color = (0,0,0) for occluders (as the printbed) - // so we shift volumes' id by 1 to get the proper color - const unsigned int id = 1 + volume.second.first; -#if ENABLE_LEGACY_OPENGL_REMOVAL - volume.first->model.set_color(picking_decode(id)); - shader->start_using(); -#else - glsafe(::glColor4fv(picking_decode(id).data())); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - shader->set_uniform("view_model_matrix", camera.get_view_matrix() * volume.first->world_matrix()); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - shader->set_uniform("volume_world_matrix", volume.first->world_matrix()); - shader->set_uniform("z_range", m_volumes.get_z_range()); - shader->set_uniform("clipping_plane", m_volumes.get_clipping_plane()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - volume.first->render(); -#if ENABLE_LEGACY_OPENGL_REMOVAL - shader->stop_using(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - } - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - - glsafe(::glEnable(GL_CULL_FACE)); -} - -void GLCanvas3D::_render_current_gizmo() const -{ - m_gizmos.render_current_gizmo(); -} - -void GLCanvas3D::_render_gizmos_overlay() -{ -#if ENABLE_RETINA_GL -// m_gizmos.set_overlay_scale(m_retina_helper->get_scale_factor()); - const float scale = m_retina_helper->get_scale_factor()*wxGetApp().toolbar_icon_scale(); - m_gizmos.set_overlay_scale(scale); //! #ys_FIXME_experiment -#else -// m_gizmos.set_overlay_scale(m_canvas->GetContentScaleFactor()); -// m_gizmos.set_overlay_scale(wxGetApp().em_unit()*0.1f); - const float size = int(GLGizmosManager::Default_Icons_Size * wxGetApp().toolbar_icon_scale()); - m_gizmos.set_overlay_icon_size(size); //! #ys_FIXME_experiment -#endif /* __WXMSW__ */ - - m_gizmos.render_overlay(); - - if (m_gizmo_highlighter.m_render_arrow) - m_gizmos.render_arrow(*this, m_gizmo_highlighter.m_gizmo_type); -} - -void GLCanvas3D::_render_main_toolbar() -{ - if (!m_main_toolbar.is_enabled()) - return; - - const Size cnv_size = get_canvas_size(); -#if ENABLE_GL_SHADERS_ATTRIBUTES - const float top = 0.5f * (float)cnv_size.get_height(); -#else - const float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); - const float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); - const float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f; -#if ENABLE_GL_SHADERS_ATTRIBUTES - const float left = -0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar_width); -#else - const float left = -0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar_width) * inv_zoom; -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - m_main_toolbar.set_position(top, left); - m_main_toolbar.render(*this); - if (m_toolbar_highlighter.m_render_arrow) - m_main_toolbar.render_arrow(*this, m_toolbar_highlighter.m_toolbar_item); -} - -void GLCanvas3D::_render_undoredo_toolbar() -{ - if (!m_undoredo_toolbar.is_enabled()) - return; - - const Size cnv_size = get_canvas_size(); -#if ENABLE_GL_SHADERS_ATTRIBUTES - const float top = 0.5f * (float)cnv_size.get_height(); -#else - float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); - - const float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); - const float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f; -#if ENABLE_GL_SHADERS_ATTRIBUTES - const float left = m_main_toolbar.get_width() - 0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar_width); -#else - const float left = (m_main_toolbar.get_width() - 0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar_width)) * inv_zoom; -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - m_undoredo_toolbar.set_position(top, left); - m_undoredo_toolbar.render(*this); - if (m_toolbar_highlighter.m_render_arrow) - m_undoredo_toolbar.render_arrow(*this, m_toolbar_highlighter.m_toolbar_item); -} - -void GLCanvas3D::_render_collapse_toolbar() const -{ - GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); - - const Size cnv_size = get_canvas_size(); - const float band = m_layers_editing.is_enabled() ? (wxGetApp().imgui()->get_style_scaling() * LayersEditing::THICKNESS_BAR_WIDTH) : 0.0; -#if ENABLE_GL_SHADERS_ATTRIBUTES - const float top = 0.5f * (float)cnv_size.get_height(); - const float left = 0.5f * (float)cnv_size.get_width() - collapse_toolbar.get_width() - band; -#else - const float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); - - const float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; - const float left = (0.5f * (float)cnv_size.get_width() - (float)collapse_toolbar.get_width() - band) * inv_zoom; -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - collapse_toolbar.set_position(top, left); - collapse_toolbar.render(*this); -} - -void GLCanvas3D::_render_view_toolbar() const -{ - GLToolbar& view_toolbar = wxGetApp().plater()->get_view_toolbar(); - -#if ENABLE_RETINA_GL - const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(); -#if __APPLE__ - view_toolbar.set_scale(scale); -#else // if GTK3 - const float size = int(GLGizmosManager::Default_Icons_Size * scale); - view_toolbar.set_icons_size(size); -#endif // __APPLE__ -#else - const float size = int(GLGizmosManager::Default_Icons_Size * wxGetApp().toolbar_icon_scale()); - view_toolbar.set_icons_size(size); -#endif // ENABLE_RETINA_GL - - const Size cnv_size = get_canvas_size(); -#if ENABLE_GL_SHADERS_ATTRIBUTES - // places the toolbar on the bottom-left corner of the 3d scene - float top = -0.5f * (float)cnv_size.get_height() + view_toolbar.get_height(); - float left = -0.5f * (float)cnv_size.get_width(); -#else - float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); - - // places the toolbar on the bottom-left corner of the 3d scene - float top = (-0.5f * (float)cnv_size.get_height() + view_toolbar.get_height()) * inv_zoom; - float left = -0.5f * (float)cnv_size.get_width() * inv_zoom; -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - view_toolbar.set_position(top, left); - view_toolbar.render(*this); -} - -#if ENABLE_SHOW_CAMERA_TARGET -void GLCanvas3D::_render_camera_target() -{ - static const float half_length = 5.0f; - - glsafe(::glDisable(GL_DEPTH_TEST)); - glsafe(::glLineWidth(2.0f)); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - const Vec3f& target = wxGetApp().plater()->get_camera().get_target().cast(); - m_camera_target.target = target.cast(); - - for (int i = 0; i < 3; ++i) { - if (!m_camera_target.axis[i].is_initialized()) { - m_camera_target.axis[i].reset(); - - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; - init_data.color = (i == X) ? ColorRGBA::X() : ((i == Y) ? ColorRGBA::Y() : ColorRGBA::Z()); - init_data.reserve_vertices(2); - init_data.reserve_indices(2); - - // vertices - if (i == X) { - init_data.add_vertex(Vec3f(-half_length, 0.0f, 0.0f)); - init_data.add_vertex(Vec3f(+half_length, 0.0f, 0.0f)); - } - else if (i == Y) { - init_data.add_vertex(Vec3f(0.0f, -half_length, 0.0f)); - init_data.add_vertex(Vec3f(0.0f, +half_length, 0.0f)); - } - else { - init_data.add_vertex(Vec3f(0.0f, 0.0f, -half_length)); - init_data.add_vertex(Vec3f(0.0f, 0.0f, +half_length)); - } - - // indices - init_data.add_line(0, 1); - - m_camera_target.axis[i].init_from(std::move(init_data)); - } - } - - GLShaderProgram* shader = wxGetApp().get_shader("flat"); - if (shader != nullptr) { - shader->start_using(); -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - shader->set_uniform("view_model_matrix", camera.get_view_matrix() * Geometry::assemble_transform(m_camera_target.target)); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - for (int i = 0; i < 3; ++i) { - m_camera_target.axis[i].render(); - } - shader->stop_using(); - } -#else - ::glBegin(GL_LINES); - const Vec3d& target = wxGetApp().plater()->get_camera().get_target(); - // draw line for x axis - ::glColor3f(1.0f, 0.0f, 0.0f); - ::glVertex3d(target.x() - half_length, target.y(), target.z()); - ::glVertex3d(target.x() + half_length, target.y(), target.z()); - // draw line for y axis - ::glColor3f(0.0f, 1.0f, 0.0f); - ::glVertex3d(target.x(), target.y() - half_length, target.z()); - ::glVertex3d(target.x(), target.y() + half_length, target.z()); - // draw line for z axis - ::glColor3f(0.0f, 0.0f, 1.0f); - ::glVertex3d(target.x(), target.y(), target.z() - half_length); - ::glVertex3d(target.x(), target.y(), target.z() + half_length); - glsafe(::glEnd()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} -#endif // ENABLE_SHOW_CAMERA_TARGET - -void GLCanvas3D::_render_sla_slices() -{ - if (!m_use_clipping_planes || current_printer_technology() != ptSLA) - return; - - const SLAPrint* print = this->sla_print(); - const PrintObjects& print_objects = print->objects(); - if (print_objects.empty()) - // nothing to render, return - return; - - double clip_min_z = -m_clipping_planes[0].get_data()[3]; - double clip_max_z = m_clipping_planes[1].get_data()[3]; - for (unsigned int i = 0; i < (unsigned int)print_objects.size(); ++i) { - const SLAPrintObject* obj = print_objects[i]; - - if (!obj->is_step_done(slaposSliceSupports)) - continue; - -#if ENABLE_LEGACY_OPENGL_REMOVAL - SlaCap::ObjectIdToModelsMap::iterator it_caps_bottom = m_sla_caps[0].triangles.find(i); - SlaCap::ObjectIdToModelsMap::iterator it_caps_top = m_sla_caps[1].triangles.find(i); -#else - SlaCap::ObjectIdToTrianglesMap::iterator it_caps_bottom = m_sla_caps[0].triangles.find(i); - SlaCap::ObjectIdToTrianglesMap::iterator it_caps_top = m_sla_caps[1].triangles.find(i); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - { - if (it_caps_bottom == m_sla_caps[0].triangles.end()) - it_caps_bottom = m_sla_caps[0].triangles.emplace(i, SlaCap::Triangles()).first; - if (!m_sla_caps[0].matches(clip_min_z)) { - m_sla_caps[0].z = clip_min_z; -#if ENABLE_LEGACY_OPENGL_REMOVAL - it_caps_bottom->second.object.reset(); - it_caps_bottom->second.supports.reset(); -#else - it_caps_bottom->second.object.clear(); - it_caps_bottom->second.supports.clear(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - if (it_caps_top == m_sla_caps[1].triangles.end()) - it_caps_top = m_sla_caps[1].triangles.emplace(i, SlaCap::Triangles()).first; - if (!m_sla_caps[1].matches(clip_max_z)) { - m_sla_caps[1].z = clip_max_z; -#if ENABLE_LEGACY_OPENGL_REMOVAL - it_caps_top->second.object.reset(); - it_caps_top->second.supports.reset(); -#else - it_caps_top->second.object.clear(); - it_caps_top->second.supports.clear(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - } -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLModel& bottom_obj_triangles = it_caps_bottom->second.object; - GLModel& bottom_sup_triangles = it_caps_bottom->second.supports; - GLModel& top_obj_triangles = it_caps_top->second.object; - GLModel& top_sup_triangles = it_caps_top->second.supports; -#else - Pointf3s &bottom_obj_triangles = it_caps_bottom->second.object; - Pointf3s &bottom_sup_triangles = it_caps_bottom->second.supports; - Pointf3s &top_obj_triangles = it_caps_top->second.object; - Pointf3s &top_sup_triangles = it_caps_top->second.supports; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - -#if ENABLE_LEGACY_OPENGL_REMOVAL - auto init_model = [](GLModel& model, const Pointf3s& triangles, const ColorRGBA& color) { - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; - init_data.reserve_vertices(triangles.size()); - init_data.reserve_indices(triangles.size() / 3); - init_data.color = color; - - unsigned int vertices_count = 0; - for (const Vec3d& v : triangles) { - init_data.add_vertex((Vec3f)v.cast()); - ++vertices_count; - if (vertices_count % 3 == 0) - init_data.add_triangle(vertices_count - 3, vertices_count - 2, vertices_count - 1); - } - - if (!init_data.is_empty()) - model.init_from(std::move(init_data)); - }; - - if ((!bottom_obj_triangles.is_initialized() || !bottom_sup_triangles.is_initialized() || - !top_obj_triangles.is_initialized() || !top_sup_triangles.is_initialized()) && !obj->get_slice_index().empty()) { -#else - if ((bottom_obj_triangles.empty() || bottom_sup_triangles.empty() || top_obj_triangles.empty() || top_sup_triangles.empty()) && - !obj->get_slice_index().empty()) { -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - double layer_height = print->default_object_config().layer_height.value; - double initial_layer_height = print->material_config().initial_layer_height.value; - bool left_handed = obj->is_left_handed(); - - coord_t key_zero = obj->get_slice_index().front().print_level(); - // Slice at the center of the slab starting at clip_min_z will be rendered for the lower plane. - coord_t key_low = coord_t((clip_min_z - initial_layer_height + layer_height) / SCALING_FACTOR) + key_zero; - // Slice at the center of the slab ending at clip_max_z will be rendered for the upper plane. - coord_t key_high = coord_t((clip_max_z - initial_layer_height) / SCALING_FACTOR) + key_zero; - - const SliceRecord& slice_low = obj->closest_slice_to_print_level(key_low, coord_t(SCALED_EPSILON)); - const SliceRecord& slice_high = obj->closest_slice_to_print_level(key_high, coord_t(SCALED_EPSILON)); - - // Offset to avoid OpenGL Z fighting between the object's horizontal surfaces and the triangluated surfaces of the cuts. - const double plane_shift_z = 0.002; - - if (slice_low.is_valid()) { - const ExPolygons& obj_bottom = slice_low.get_slice(soModel); - const ExPolygons& sup_bottom = slice_low.get_slice(soSupport); -#if ENABLE_LEGACY_OPENGL_REMOVAL - // calculate model bottom cap - if (!bottom_obj_triangles.is_initialized() && !obj_bottom.empty()) - init_model(bottom_obj_triangles, triangulate_expolygons_3d(obj_bottom, clip_min_z - plane_shift_z, !left_handed), { 1.0f, 0.37f, 0.0f, 1.0f }); - // calculate support bottom cap - if (!bottom_sup_triangles.is_initialized() && !sup_bottom.empty()) - init_model(bottom_sup_triangles, triangulate_expolygons_3d(sup_bottom, clip_min_z - plane_shift_z, !left_handed), { 1.0f, 0.0f, 0.37f, 1.0f }); -#else - // calculate model bottom cap - if (bottom_obj_triangles.empty() && !obj_bottom.empty()) - bottom_obj_triangles = triangulate_expolygons_3d(obj_bottom, clip_min_z - plane_shift_z, ! left_handed); - // calculate support bottom cap - if (bottom_sup_triangles.empty() && !sup_bottom.empty()) - bottom_sup_triangles = triangulate_expolygons_3d(sup_bottom, clip_min_z - plane_shift_z, !left_handed); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - - if (slice_high.is_valid()) { - const ExPolygons& obj_top = slice_high.get_slice(soModel); - const ExPolygons& sup_top = slice_high.get_slice(soSupport); -#if ENABLE_LEGACY_OPENGL_REMOVAL - // calculate model top cap - if (!top_obj_triangles.is_initialized() && !obj_top.empty()) - init_model(top_obj_triangles, triangulate_expolygons_3d(obj_top, clip_max_z + plane_shift_z, left_handed), { 1.0f, 0.37f, 0.0f, 1.0f }); - // calculate support top cap - if (!top_sup_triangles.is_initialized() && !sup_top.empty()) - init_model(top_sup_triangles, triangulate_expolygons_3d(sup_top, clip_max_z + plane_shift_z, left_handed), { 1.0f, 0.0f, 0.37f, 1.0f }); -#else - // calculate model top cap - if (top_obj_triangles.empty() && !obj_top.empty()) - top_obj_triangles = triangulate_expolygons_3d(obj_top, clip_max_z + plane_shift_z, left_handed); - // calculate support top cap - if (top_sup_triangles.empty() && !sup_top.empty()) - top_sup_triangles = triangulate_expolygons_3d(sup_top, clip_max_z + plane_shift_z, left_handed); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLShaderProgram* shader = wxGetApp().get_shader("flat"); - if (shader != nullptr) { - shader->start_using(); - - for (const SLAPrintObject::Instance& inst : obj->instances()) { -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - const Transform3d view_model_matrix = camera.get_view_matrix() * - Geometry::assemble_transform(Vec3d(unscale(inst.shift.x()), unscale(inst.shift.y()), 0.0), - inst.rotation * Vec3d::UnitZ(), Vec3d::Ones(), - obj->is_left_handed() ? Vec3d(-1.0f, 1.0f, 1.0f) : Vec3d::Ones()); - - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#else - glsafe(::glPushMatrix()); - glsafe(::glTranslated(unscale(inst.shift.x()), unscale(inst.shift.y()), 0.0)); - glsafe(::glRotatef(Geometry::rad2deg(inst.rotation), 0.0f, 0.0f, 1.0f)); - if (obj->is_left_handed()) - // The polygons are mirrored by X. - glsafe(::glScalef(-1.0f, 1.0f, 1.0f)); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - bottom_obj_triangles.render(); - top_obj_triangles.render(); - bottom_sup_triangles.render(); - top_sup_triangles.render(); - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - } - - shader->stop_using(); - } -#else - if (!bottom_obj_triangles.empty() || !top_obj_triangles.empty() || !bottom_sup_triangles.empty() || !top_sup_triangles.empty()) { - for (const SLAPrintObject::Instance& inst : obj->instances()) { - glsafe(::glPushMatrix()); - glsafe(::glTranslated(unscale(inst.shift.x()), unscale(inst.shift.y()), 0.0)); - glsafe(::glRotatef(Geometry::rad2deg(inst.rotation), 0.0f, 0.0f, 1.0f)); - if (obj->is_left_handed()) - // The polygons are mirrored by X. - glsafe(::glScalef(-1.0f, 1.0f, 1.0f)); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glColor3f(1.0f, 0.37f, 0.0f)); - if (!bottom_obj_triangles.empty()) { - glsafe(::glVertexPointer(3, GL_DOUBLE, 0, (GLdouble*)bottom_obj_triangles.front().data())); - glsafe(::glDrawArrays(GL_TRIANGLES, 0, bottom_obj_triangles.size())); - } - if (! top_obj_triangles.empty()) { - glsafe(::glVertexPointer(3, GL_DOUBLE, 0, (GLdouble*)top_obj_triangles.front().data())); - glsafe(::glDrawArrays(GL_TRIANGLES, 0, top_obj_triangles.size())); - } - glsafe(::glColor3f(1.0f, 0.0f, 0.37f)); - if (! bottom_sup_triangles.empty()) { - glsafe(::glVertexPointer(3, GL_DOUBLE, 0, (GLdouble*)bottom_sup_triangles.front().data())); - glsafe(::glDrawArrays(GL_TRIANGLES, 0, bottom_sup_triangles.size())); - } - if (! top_sup_triangles.empty()) { - glsafe(::glVertexPointer(3, GL_DOUBLE, 0, (GLdouble*)top_sup_triangles.front().data())); - glsafe(::glDrawArrays(GL_TRIANGLES, 0, top_sup_triangles.size())); - } - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glPopMatrix()); - } - } -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } -} - -void GLCanvas3D::_render_selection_sidebar_hints() -{ - m_selection.render_sidebar_hints(m_sidebar_field); -} - -void GLCanvas3D::_update_volumes_hover_state() -{ - for (GLVolume* v : m_volumes.volumes) { - v->hover = GLVolume::HS_None; - } - - if (m_hover_volume_idxs.empty()) - return; - - bool ctrl_pressed = wxGetKeyState(WXK_CONTROL); // additive select/deselect - bool shift_pressed = wxGetKeyState(WXK_SHIFT); // select by rectangle - bool alt_pressed = wxGetKeyState(WXK_ALT); // deselect by rectangle - - if (alt_pressed && (shift_pressed || ctrl_pressed)) { - // illegal combinations of keys - m_hover_volume_idxs.clear(); - return; - } - -#if !ENABLE_NEW_RECTANGLE_SELECTION - bool selection_modifiers_only = m_selection.is_empty() || m_selection.is_any_modifier(); -#endif // !ENABLE_NEW_RECTANGLE_SELECTION - - bool hover_modifiers_only = true; - for (int i : m_hover_volume_idxs) { - if (!m_volumes.volumes[i]->is_modifier) { - hover_modifiers_only = false; - break; - } - } - - std::set> hover_instances; - for (int i : m_hover_volume_idxs) { - const GLVolume& v = *m_volumes.volumes[i]; - hover_instances.insert(std::make_pair(v.object_idx(), v.instance_idx())); - } - - bool hover_from_single_instance = hover_instances.size() == 1; - - if (hover_modifiers_only && !hover_from_single_instance) { - // do not allow to select volumes from different instances - m_hover_volume_idxs.clear(); - return; - } - - for (int i : m_hover_volume_idxs) { - GLVolume& volume = *m_volumes.volumes[i]; - if (volume.hover != GLVolume::HS_None) - continue; - -#if ENABLE_NEW_RECTANGLE_SELECTION - bool deselect = volume.selected && ((shift_pressed && m_rectangle_selection.is_empty()) || (alt_pressed && !m_rectangle_selection.is_empty())); - bool select = !volume.selected && (m_rectangle_selection.is_empty() || (shift_pressed && !m_rectangle_selection.is_empty())); -#else - bool deselect = volume.selected && ((ctrl_pressed && !shift_pressed) || alt_pressed); - // (volume->is_modifier && !selection_modifiers_only && !is_ctrl_pressed) -> allows hovering on selected modifiers belonging to selection of type Instance - bool select = (!volume.selected || (volume.is_modifier && !selection_modifiers_only && !ctrl_pressed)) && !alt_pressed; -#endif // ENABLE_NEW_RECTANGLE_SELECTION - - if (select || deselect) { - bool as_volume = - volume.is_modifier && hover_from_single_instance && !ctrl_pressed && - ( - (!deselect) || - (deselect && !m_selection.is_single_full_instance() && (volume.object_idx() == m_selection.get_object_idx()) && (volume.instance_idx() == m_selection.get_instance_idx())) - ); - - if (as_volume) - volume.hover = deselect ? GLVolume::HS_Deselect : GLVolume::HS_Select; - else { - int object_idx = volume.object_idx(); - int instance_idx = volume.instance_idx(); - - for (GLVolume* v : m_volumes.volumes) { - if (v->object_idx() == object_idx && v->instance_idx() == instance_idx) - v->hover = deselect ? GLVolume::HS_Deselect : GLVolume::HS_Select; - } - } - } - else if (volume.selected) - volume.hover = GLVolume::HS_Hover; - } -} - -void GLCanvas3D::_perform_layer_editing_action(wxMouseEvent* evt) -{ - int object_idx_selected = m_layers_editing.last_object_id; - if (object_idx_selected == -1) - return; - - // A volume is selected. Test, whether hovering over a layer thickness bar. - if (evt != nullptr) { - const Rect& rect = LayersEditing::get_bar_rect_screen(*this); - float b = rect.get_bottom(); - m_layers_editing.last_z = m_layers_editing.object_max_z() * (b - evt->GetY() - 1.0f) / (b - rect.get_top()); - m_layers_editing.last_action = - evt->ShiftDown() ? (evt->RightIsDown() ? LAYER_HEIGHT_EDIT_ACTION_SMOOTH : LAYER_HEIGHT_EDIT_ACTION_REDUCE) : - (evt->RightIsDown() ? LAYER_HEIGHT_EDIT_ACTION_INCREASE : LAYER_HEIGHT_EDIT_ACTION_DECREASE); - } - - m_layers_editing.adjust_layer_height_profile(); - _refresh_if_shown_on_screen(); - - // Automatic action on mouse down with the same coordinate. - _start_timer(); -} - -Vec3d GLCanvas3D::_mouse_to_3d(const Point& mouse_pos, float* z) -{ - if (m_canvas == nullptr) - return Vec3d(DBL_MAX, DBL_MAX, DBL_MAX); - - const Camera& camera = wxGetApp().plater()->get_camera(); - const Matrix4d modelview = camera.get_view_matrix().matrix(); - const Matrix4d projection = camera.get_projection_matrix().matrix(); - const Vec4i viewport(camera.get_viewport().data()); - - const int y = viewport[3] - mouse_pos.y(); - float mouse_z; - if (z == nullptr) - glsafe(::glReadPixels(mouse_pos.x(), y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, (void*)&mouse_z)); - else - mouse_z = *z; - - Vec3d out; - igl::unproject(Vec3d(mouse_pos.x(), y, mouse_z), modelview, projection, viewport, out); - return out; -} - -Vec3d GLCanvas3D::_mouse_to_bed_3d(const Point& mouse_pos) -{ - return mouse_ray(mouse_pos).intersect_plane(0.0); -} - -void GLCanvas3D::_start_timer() -{ - m_timer.Start(100, wxTIMER_CONTINUOUS); -} - -void GLCanvas3D::_stop_timer() -{ - m_timer.Stop(); -} - -void GLCanvas3D::_load_print_toolpaths(const BuildVolume &build_volume) -{ - const Print *print = this->fff_print(); - if (print == nullptr) - return; - - if (! print->is_step_done(psSkirtBrim)) - return; - - if (!print->has_skirt() && !print->has_brim()) - return; - - const ColorRGBA color = ColorRGBA::GREENISH(); - - // number of skirt layers - size_t total_layer_count = 0; - for (const PrintObject* print_object : print->objects()) { - total_layer_count = std::max(total_layer_count, print_object->total_layer_count()); - } - size_t skirt_height = print->has_infinite_skirt() ? total_layer_count : std::min(print->config().skirt_height.value, total_layer_count); - if (skirt_height == 0 && print->has_brim()) - skirt_height = 1; - - // Get first skirt_height layers. - //FIXME This code is fishy. It may not work for multiple objects with different layering due to variable layer height feature. - // This is not critical as this is just an initial preview. - const PrintObject* highest_object = *std::max_element(print->objects().begin(), print->objects().end(), [](auto l, auto r){ return l->layers().size() < r->layers().size(); }); - std::vector print_zs; - print_zs.reserve(skirt_height * 2); - for (size_t i = 0; i < std::min(skirt_height, highest_object->layers().size()); ++ i) - print_zs.emplace_back(float(highest_object->layers()[i]->print_z)); - // Only add skirt for the raft layers. - for (size_t i = 0; i < std::min(skirt_height, std::min(highest_object->slicing_parameters().raft_layers(), highest_object->support_layers().size())); ++ i) - print_zs.emplace_back(float(highest_object->support_layers()[i]->print_z)); - sort_remove_duplicates(print_zs); - skirt_height = std::min(skirt_height, print_zs.size()); - print_zs.erase(print_zs.begin() + skirt_height, print_zs.end()); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLVolume* volume = m_volumes.new_toolpath_volume(color); - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; -#else - GLVolume *volume = m_volumes.new_toolpath_volume(color, VERTEX_BUFFER_RESERVE_SIZE); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - for (size_t i = 0; i < skirt_height; ++ i) { - volume->print_zs.emplace_back(print_zs[i]); -#if ENABLE_LEGACY_OPENGL_REMOVAL - volume->offsets.emplace_back(init_data.indices_count()); - if (i == 0) - _3DScene::extrusionentity_to_verts(print->brim(), print_zs[i], Point(0, 0), init_data); - _3DScene::extrusionentity_to_verts(print->skirt(), print_zs[i], Point(0, 0), init_data); -#else - volume->offsets.emplace_back(volume->indexed_vertex_array.quad_indices.size()); - volume->offsets.emplace_back(volume->indexed_vertex_array.triangle_indices.size()); - if (i == 0) - _3DScene::extrusionentity_to_verts(print->brim(), print_zs[i], Point(0, 0), *volume); - _3DScene::extrusionentity_to_verts(print->skirt(), print_zs[i], Point(0, 0), *volume); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - // Ensure that no volume grows over the limits. If the volume is too large, allocate a new one. -#if ENABLE_LEGACY_OPENGL_REMOVAL - if (init_data.vertices_size_bytes() > MAX_VERTEX_BUFFER_SIZE) { - volume->model.init_from(std::move(init_data)); -#else - if (volume->indexed_vertex_array.vertices_and_normals_interleaved.size() > MAX_VERTEX_BUFFER_SIZE) { -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - GLVolume &vol = *volume; - volume = m_volumes.new_toolpath_volume(vol.color); -#if !ENABLE_LEGACY_OPENGL_REMOVAL - reserve_new_volume_finalize_old_volume(*volume, vol, m_initialized); -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - } - } -#if ENABLE_LEGACY_OPENGL_REMOVAL - volume->model.init_from(std::move(init_data)); - volume->is_outside = !contains(build_volume, volume->model); -#else - volume->is_outside = ! build_volume.all_paths_inside_vertices_and_normals_interleaved(volume->indexed_vertex_array.vertices_and_normals_interleaved, volume->indexed_vertex_array.bounding_box()); - volume->indexed_vertex_array.finalize_geometry(m_initialized); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} - -void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, const BuildVolume& build_volume, const std::vector& str_tool_colors, const std::vector& color_print_values) -{ - std::vector tool_colors; - decode_colors(str_tool_colors, tool_colors); - - struct Ctxt - { - const PrintInstances *shifted_copies; - std::vector layers; - bool has_perimeters; - bool has_infill; - bool has_support; - const std::vector* tool_colors; - bool is_single_material_print; - int extruders_cnt; - const std::vector* color_print_values; - - static ColorRGBA color_perimeters() { return ColorRGBA::YELLOW(); } - static ColorRGBA color_infill() { return ColorRGBA::REDISH(); } - static ColorRGBA color_support() { return ColorRGBA::GREENISH(); } - static ColorRGBA color_pause_or_custom_code() { return ColorRGBA::GRAY(); } - - // For cloring by a tool, return a parsed color. - bool color_by_tool() const { return tool_colors != nullptr; } - size_t number_tools() const { return color_by_tool() ? tool_colors->size() : 0; } - const ColorRGBA& color_tool(size_t tool) const { return (*tool_colors)[tool]; } - - // For coloring by a color_print(M600), return a parsed color. - bool color_by_color_print() const { return color_print_values!=nullptr; } - const size_t color_print_color_idx_by_layer_idx(const size_t layer_idx) const { - const CustomGCode::Item value{layers[layer_idx]->print_z + EPSILON, CustomGCode::Custom, 0, ""}; - auto it = std::lower_bound(color_print_values->begin(), color_print_values->end(), value); - return (it - color_print_values->begin()) % number_tools(); - } - - const size_t color_print_color_idx_by_layer_idx_and_extruder(const size_t layer_idx, const int extruder) const - { - const coordf_t print_z = layers[layer_idx]->print_z; - - auto it = std::find_if(color_print_values->begin(), color_print_values->end(), - [print_z](const CustomGCode::Item& code) - { return fabs(code.print_z - print_z) < EPSILON; }); - if (it != color_print_values->end()) { - CustomGCode::Type type = it->type; - // pause print or custom Gcode - if (type == CustomGCode::PausePrint || - (type != CustomGCode::ColorChange && type != CustomGCode::ToolChange)) - return number_tools()-1; // last color item is a gray color for pause print or custom G-code - - // change tool (extruder) - if (type == CustomGCode::ToolChange) - return get_color_idx_for_tool_change(it, extruder); - // change color for current extruder - if (type == CustomGCode::ColorChange) { - int color_idx = get_color_idx_for_color_change(it, extruder); - if (color_idx >= 0) - return color_idx; - } - } - - const CustomGCode::Item value{print_z + EPSILON, CustomGCode::Custom, 0, ""}; - it = std::lower_bound(color_print_values->begin(), color_print_values->end(), value); - while (it != color_print_values->begin()) { - --it; - // change color for current extruder - if (it->type == CustomGCode::ColorChange) { - int color_idx = get_color_idx_for_color_change(it, extruder); - if (color_idx >= 0) - return color_idx; - } - // change tool (extruder) - if (it->type == CustomGCode::ToolChange) - return get_color_idx_for_tool_change(it, extruder); - } - - return std::min(extruders_cnt - 1, std::max(extruder - 1, 0));; - } - - private: - int get_m600_color_idx(std::vector::const_iterator it) const - { - int shift = 0; - while (it != color_print_values->begin()) { - --it; - if (it->type == CustomGCode::ColorChange) - shift++; - } - return extruders_cnt + shift; - } - - int get_color_idx_for_tool_change(std::vector::const_iterator it, const int extruder) const - { - const int current_extruder = it->extruder == 0 ? extruder : it->extruder; - if (number_tools() == size_t(extruders_cnt + 1)) // there is no one "M600" - return std::min(extruders_cnt - 1, std::max(current_extruder - 1, 0)); - - auto it_n = it; - while (it_n != color_print_values->begin()) { - --it_n; - if (it_n->type == CustomGCode::ColorChange && it_n->extruder == current_extruder) - return get_m600_color_idx(it_n); - } - - return std::min(extruders_cnt - 1, std::max(current_extruder - 1, 0)); - } - - int get_color_idx_for_color_change(std::vector::const_iterator it, const int extruder) const - { - if (extruders_cnt == 1) - return get_m600_color_idx(it); - - auto it_n = it; - bool is_tool_change = false; - while (it_n != color_print_values->begin()) { - --it_n; - if (it_n->type == CustomGCode::ToolChange) { - is_tool_change = true; - if (it_n->extruder == it->extruder || (it_n->extruder == 0 && it->extruder == extruder)) - return get_m600_color_idx(it); - break; - } - } - if (!is_tool_change && it->extruder == extruder) - return get_m600_color_idx(it); - - return -1; - } - - } ctxt; - - ctxt.has_perimeters = print_object.is_step_done(posPerimeters); - ctxt.has_infill = print_object.is_step_done(posInfill); - ctxt.has_support = print_object.is_step_done(posSupportMaterial); - ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors; - ctxt.color_print_values = color_print_values.empty() ? nullptr : &color_print_values; - ctxt.is_single_material_print = this->fff_print()->extruders().size()==1; - ctxt.extruders_cnt = wxGetApp().extruders_edited_cnt(); - - ctxt.shifted_copies = &print_object.instances(); - - // order layers by print_z - { - size_t nlayers = 0; - if (ctxt.has_perimeters || ctxt.has_infill) - nlayers = print_object.layers().size(); - if (ctxt.has_support) - nlayers += print_object.support_layers().size(); - ctxt.layers.reserve(nlayers); - } - if (ctxt.has_perimeters || ctxt.has_infill) - for (const Layer *layer : print_object.layers()) - ctxt.layers.emplace_back(layer); - if (ctxt.has_support) - for (const Layer *layer : print_object.support_layers()) - ctxt.layers.emplace_back(layer); - std::sort(ctxt.layers.begin(), ctxt.layers.end(), [](const Layer *l1, const Layer *l2) { return l1->print_z < l2->print_z; }); - - // Maximum size of an allocation block: 32MB / sizeof(float) - BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - start" << m_volumes.log_memory_info() << log_memory_info(); - - const bool is_selected_separate_extruder = m_selected_extruder > 0 && ctxt.color_by_color_print(); - - //FIXME Improve the heuristics for a grain size. - size_t grain_size = std::max(ctxt.layers.size() / 16, size_t(1)); - tbb::spin_mutex new_volume_mutex; - auto new_volume = [this, &new_volume_mutex](const ColorRGBA& color) { - // Allocate the volume before locking. - GLVolume *volume = new GLVolume(color); - volume->is_extrusion_path = true; -#if ENABLE_LEGACY_OPENGL_REMOVAL - // to prevent sending data to gpu (in the main thread) while - // editing the model geometry - volume->model.disable_render(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - tbb::spin_mutex::scoped_lock lock; - // Lock by ROII, so if the emplace_back() fails, the lock will be released. - lock.acquire(new_volume_mutex); - m_volumes.volumes.emplace_back(volume); - lock.release(); - return volume; - }; - const size_t volumes_cnt_initial = m_volumes.volumes.size(); - tbb::parallel_for( - tbb::blocked_range(0, ctxt.layers.size(), grain_size), - [&ctxt, &new_volume, is_selected_separate_extruder, this](const tbb::blocked_range& range) { - GLVolumePtrs vols; -#if ENABLE_LEGACY_OPENGL_REMOVAL - std::vector geometries; - auto select_geometry = [&ctxt, &geometries](size_t layer_idx, int extruder, int feature) -> GLModel::Geometry& { - return geometries[ctxt.color_by_color_print() ? - ctxt.color_print_color_idx_by_layer_idx_and_extruder(layer_idx, extruder) : - ctxt.color_by_tool() ? - std::min(ctxt.number_tools() - 1, std::max(extruder - 1, 0)) : - feature - ]; - }; -#else - auto volume = [&ctxt, &vols](size_t layer_idx, int extruder, int feature) -> GLVolume& { - return *vols[ctxt.color_by_color_print() ? - ctxt.color_print_color_idx_by_layer_idx_and_extruder(layer_idx, extruder) : - ctxt.color_by_tool() ? - std::min(ctxt.number_tools() - 1, std::max(extruder - 1, 0)) : - feature - ]; - }; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - if (ctxt.color_by_color_print() || ctxt.color_by_tool()) { - for (size_t i = 0; i < ctxt.number_tools(); ++i) { - vols.emplace_back(new_volume(ctxt.color_tool(i))); -#if ENABLE_LEGACY_OPENGL_REMOVAL - geometries.emplace_back(GLModel::Geometry()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - } - else { - vols = { new_volume(ctxt.color_perimeters()), new_volume(ctxt.color_infill()), new_volume(ctxt.color_support()) }; -#if ENABLE_LEGACY_OPENGL_REMOVAL - geometries = { GLModel::Geometry(), GLModel::Geometry(), GLModel::Geometry() }; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - assert(vols.size() == geometries.size()); - for (GLModel::Geometry& g : geometries) { - g.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; - } -#else - for (GLVolume *vol : vols) - // Reserving number of vertices (3x position + 3x color) - vol->indexed_vertex_array.reserve(VERTEX_BUFFER_RESERVE_SIZE / 6); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { - const Layer *layer = ctxt.layers[idx_layer]; - - if (is_selected_separate_extruder) { - bool at_least_one_has_correct_extruder = false; - for (const LayerRegion* layerm : layer->regions()) { - if (layerm->slices.surfaces.empty()) - continue; - const PrintRegionConfig& cfg = layerm->region().config(); - if (cfg.perimeter_extruder.value == m_selected_extruder || - cfg.infill_extruder.value == m_selected_extruder || - cfg.solid_infill_extruder.value == m_selected_extruder ) { - at_least_one_has_correct_extruder = true; - break; - } - } - if (!at_least_one_has_correct_extruder) - continue; - } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - for (size_t i = 0; i < vols.size(); ++i) { - GLVolume* vol = vols[i]; - if (vol->print_zs.empty() || vol->print_zs.back() != layer->print_z) { - vol->print_zs.emplace_back(layer->print_z); - vol->offsets.emplace_back(geometries[i].indices_count()); - } - } -#else - for (GLVolume* vol : vols) - if (vol->print_zs.empty() || vol->print_zs.back() != layer->print_z) { - vol->print_zs.emplace_back(layer->print_z); - vol->offsets.emplace_back(vol->indexed_vertex_array.quad_indices.size()); - vol->offsets.emplace_back(vol->indexed_vertex_array.triangle_indices.size()); - } -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - for (const PrintInstance &instance : *ctxt.shifted_copies) { - const Point © = instance.shift; - for (const LayerRegion *layerm : layer->regions()) { - if (is_selected_separate_extruder) { - const PrintRegionConfig& cfg = layerm->region().config(); - if (cfg.perimeter_extruder.value != m_selected_extruder || - cfg.infill_extruder.value != m_selected_extruder || - cfg.solid_infill_extruder.value != m_selected_extruder) - continue; - } - if (ctxt.has_perimeters) -#if ENABLE_LEGACY_OPENGL_REMOVAL - _3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy, - select_geometry(idx_layer, layerm->region().config().perimeter_extruder.value, 0)); -#else - _3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy, - volume(idx_layer, layerm->region().config().perimeter_extruder.value, 0)); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - if (ctxt.has_infill) { - for (const ExtrusionEntity *ee : layerm->fills.entities) { - // fill represents infill extrusions of a single island. - const auto *fill = dynamic_cast(ee); - if (! fill->entities.empty()) -#if ENABLE_LEGACY_OPENGL_REMOVAL - _3DScene::extrusionentity_to_verts(*fill, float(layer->print_z), copy, - select_geometry(idx_layer, is_solid_infill(fill->entities.front()->role()) ? - layerm->region().config().solid_infill_extruder : - layerm->region().config().infill_extruder, 1)); -#else - _3DScene::extrusionentity_to_verts(*fill, float(layer->print_z), copy, - volume(idx_layer, - is_solid_infill(fill->entities.front()->role()) ? - layerm->region().config().solid_infill_extruder : - layerm->region().config().infill_extruder, - 1)); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - } - } - if (ctxt.has_support) { - const SupportLayer *support_layer = dynamic_cast(layer); - if (support_layer) { - for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities) -#if ENABLE_LEGACY_OPENGL_REMOVAL - _3DScene::extrusionentity_to_verts(extrusion_entity, float(layer->print_z), copy, - select_geometry(idx_layer, (extrusion_entity->role() == erSupportMaterial) ? - support_layer->object()->config().support_material_extruder : - support_layer->object()->config().support_material_interface_extruder, 2)); -#else - _3DScene::extrusionentity_to_verts(extrusion_entity, float(layer->print_z), copy, - volume(idx_layer, - (extrusion_entity->role() == erSupportMaterial) ? - support_layer->object()->config().support_material_extruder : - support_layer->object()->config().support_material_interface_extruder, - 2)); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - } - } - // Ensure that no volume grows over the limits. If the volume is too large, allocate a new one. - for (size_t i = 0; i < vols.size(); ++i) { - GLVolume &vol = *vols[i]; -#if ENABLE_LEGACY_OPENGL_REMOVAL - if (geometries[i].vertices_size_bytes() > MAX_VERTEX_BUFFER_SIZE) { - vol.model.init_from(std::move(geometries[i])); -#else - if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() > MAX_VERTEX_BUFFER_SIZE) { -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - vols[i] = new_volume(vol.color); -#if !ENABLE_LEGACY_OPENGL_REMOVAL - reserve_new_volume_finalize_old_volume(*vols[i], vol, false); -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - } - } - } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - for (size_t i = 0; i < vols.size(); ++i) { - if (!geometries[i].is_empty()) - vols[i]->model.init_from(std::move(geometries[i])); - } -#else - for (GLVolume *vol : vols) - // Ideally one would call vol->indexed_vertex_array.finalize() here to move the buffers to the OpenGL driver, - // but this code runs in parallel and the OpenGL driver is not thread safe. - vol->indexed_vertex_array.shrink_to_fit(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - }); - - BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - finalizing results" << m_volumes.log_memory_info() << log_memory_info(); - // Remove empty volumes from the newly added volumes. - { - for (auto ptr_it = m_volumes.volumes.begin() + volumes_cnt_initial; ptr_it != m_volumes.volumes.end(); ++ptr_it) - if ((*ptr_it)->empty()) { - delete *ptr_it; - *ptr_it = nullptr; - } - m_volumes.volumes.erase(std::remove(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), nullptr), m_volumes.volumes.end()); - } - for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) { - GLVolume* v = m_volumes.volumes[i]; -#if ENABLE_LEGACY_OPENGL_REMOVAL - v->is_outside = !contains(build_volume, v->model); - // We are done editinig the model, now it can be sent to gpu - v->model.enable_render(); -#else - v->is_outside = ! build_volume.all_paths_inside_vertices_and_normals_interleaved(v->indexed_vertex_array.vertices_and_normals_interleaved, v->indexed_vertex_array.bounding_box()); - v->indexed_vertex_array.finalize_geometry(m_initialized); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - - BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end" << m_volumes.log_memory_info() << log_memory_info(); -} - -void GLCanvas3D::_load_wipe_tower_toolpaths(const BuildVolume& build_volume, const std::vector& str_tool_colors) -{ - const Print *print = this->fff_print(); - if (print == nullptr || print->wipe_tower_data().tool_changes.empty()) - return; - - if (!print->is_step_done(psWipeTower)) - return; - - std::vector tool_colors; - decode_colors(str_tool_colors, tool_colors); - - struct Ctxt - { - const Print *print; - const std::vector *tool_colors; - Vec2f wipe_tower_pos; - float wipe_tower_angle; - - static ColorRGBA color_support() { return ColorRGBA::GREENISH(); } - - // For cloring by a tool, return a parsed color. - bool color_by_tool() const { return tool_colors != nullptr; } - size_t number_tools() const { return this->color_by_tool() ? tool_colors->size() : 0; } - const ColorRGBA& color_tool(size_t tool) const { return (*tool_colors)[tool]; } - int volume_idx(int tool, int feature) const { - return this->color_by_tool() ? std::min(this->number_tools() - 1, std::max(tool, 0)) : feature; - } - - const std::vector& tool_change(size_t idx) { - const auto &tool_changes = print->wipe_tower_data().tool_changes; - return priming.empty() ? - ((idx == tool_changes.size()) ? final : tool_changes[idx]) : - ((idx == 0) ? priming : (idx == tool_changes.size() + 1) ? final : tool_changes[idx - 1]); - } - std::vector priming; - std::vector final; - } ctxt; - - ctxt.print = print; - ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors; - if (print->wipe_tower_data().priming && print->config().single_extruder_multi_material_priming) - for (int i=0; i<(int)print->wipe_tower_data().priming.get()->size(); ++i) - ctxt.priming.emplace_back(print->wipe_tower_data().priming.get()->at(i)); - if (print->wipe_tower_data().final_purge) - ctxt.final.emplace_back(*print->wipe_tower_data().final_purge.get()); - - ctxt.wipe_tower_angle = ctxt.print->config().wipe_tower_rotation_angle.value/180.f * PI; - ctxt.wipe_tower_pos = Vec2f(ctxt.print->config().wipe_tower_x.value, ctxt.print->config().wipe_tower_y.value); - - BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - start" << m_volumes.log_memory_info() << log_memory_info(); - - //FIXME Improve the heuristics for a grain size. - size_t n_items = print->wipe_tower_data().tool_changes.size() + (ctxt.priming.empty() ? 0 : 1); - size_t grain_size = std::max(n_items / 128, size_t(1)); - tbb::spin_mutex new_volume_mutex; - auto new_volume = [this, &new_volume_mutex](const ColorRGBA& color) { - auto *volume = new GLVolume(color); - volume->is_extrusion_path = true; -#if ENABLE_LEGACY_OPENGL_REMOVAL - // to prevent sending data to gpu (in the main thread) while - // editing the model geometry - volume->model.disable_render(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - tbb::spin_mutex::scoped_lock lock; - lock.acquire(new_volume_mutex); - m_volumes.volumes.emplace_back(volume); - lock.release(); - return volume; - }; - const size_t volumes_cnt_initial = m_volumes.volumes.size(); - std::vector volumes_per_thread(n_items); - tbb::parallel_for( - tbb::blocked_range(0, n_items, grain_size), - [&ctxt, &new_volume](const tbb::blocked_range& range) { - // Bounding box of this slab of a wipe tower. - GLVolumePtrs vols; -#if ENABLE_LEGACY_OPENGL_REMOVAL - std::vector geometries; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - if (ctxt.color_by_tool()) { - for (size_t i = 0; i < ctxt.number_tools(); ++i) { - vols.emplace_back(new_volume(ctxt.color_tool(i))); -#if ENABLE_LEGACY_OPENGL_REMOVAL - geometries.emplace_back(GLModel::Geometry()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - } - else { - vols = { new_volume(ctxt.color_support()) }; -#if ENABLE_LEGACY_OPENGL_REMOVAL - geometries = { GLModel::Geometry() }; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - assert(vols.size() == geometries.size()); - for (GLModel::Geometry& g : geometries) { - g.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; - } -#else - for (GLVolume *volume : vols) - // Reserving number of vertices (3x position + 3x color) - volume->indexed_vertex_array.reserve(VERTEX_BUFFER_RESERVE_SIZE / 6); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) { - const std::vector &layer = ctxt.tool_change(idx_layer); - for (size_t i = 0; i < vols.size(); ++i) { - GLVolume &vol = *vols[i]; - if (vol.print_zs.empty() || vol.print_zs.back() != layer.front().print_z) { - vol.print_zs.emplace_back(layer.front().print_z); -#if ENABLE_LEGACY_OPENGL_REMOVAL - vol.offsets.emplace_back(geometries[i].indices_count()); -#else - vol.offsets.emplace_back(vol.indexed_vertex_array.quad_indices.size()); - vol.offsets.emplace_back(vol.indexed_vertex_array.triangle_indices.size()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - } - for (const WipeTower::ToolChangeResult &extrusions : layer) { - for (size_t i = 1; i < extrusions.extrusions.size();) { - const WipeTower::Extrusion &e = extrusions.extrusions[i]; - if (e.width == 0.) { - ++i; - continue; - } - size_t j = i + 1; - if (ctxt.color_by_tool()) - for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].tool == e.tool && extrusions.extrusions[j].width > 0.f; ++j); - else - for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].width > 0.f; ++j); - size_t n_lines = j - i; - Lines lines; - std::vector widths; - std::vector heights; - lines.reserve(n_lines); - widths.reserve(n_lines); - heights.assign(n_lines, extrusions.layer_height); - WipeTower::Extrusion e_prev = extrusions.extrusions[i-1]; - - if (!extrusions.priming) { // wipe tower extrusions describe the wipe tower at the origin with no rotation - e_prev.pos = Eigen::Rotation2Df(ctxt.wipe_tower_angle) * e_prev.pos; - e_prev.pos += ctxt.wipe_tower_pos; - } - - for (; i < j; ++i) { - WipeTower::Extrusion e = extrusions.extrusions[i]; - assert(e.width > 0.f); - if (!extrusions.priming) { - e.pos = Eigen::Rotation2Df(ctxt.wipe_tower_angle) * e.pos; - e.pos += ctxt.wipe_tower_pos; - } - - lines.emplace_back(Point::new_scale(e_prev.pos.x(), e_prev.pos.y()), Point::new_scale(e.pos.x(), e.pos.y())); - widths.emplace_back(e.width); - - e_prev = e; - } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - _3DScene::thick_lines_to_verts(lines, widths, heights, lines.front().a == lines.back().b, extrusions.print_z, - geometries[ctxt.volume_idx(e.tool, 0)]); -#else - _3DScene::thick_lines_to_verts(lines, widths, heights, lines.front().a == lines.back().b, extrusions.print_z, - *vols[ctxt.volume_idx(e.tool, 0)]); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - } - } - for (size_t i = 0; i < vols.size(); ++i) { - GLVolume &vol = *vols[i]; -#if ENABLE_LEGACY_OPENGL_REMOVAL - if (geometries[i].vertices_size_bytes() > MAX_VERTEX_BUFFER_SIZE) { - vol.model.init_from(std::move(geometries[i])); -#else - if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() > MAX_VERTEX_BUFFER_SIZE) { -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - vols[i] = new_volume(vol.color); -#if !ENABLE_LEGACY_OPENGL_REMOVAL - reserve_new_volume_finalize_old_volume(*vols[i], vol, false); -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - } - } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - for (size_t i = 0; i < vols.size(); ++i) { - if (!geometries[i].is_empty()) - vols[i]->model.init_from(std::move(geometries[i])); - } -#else - for (GLVolume *vol : vols) - vol->indexed_vertex_array.shrink_to_fit(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - }); - - BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - finalizing results" << m_volumes.log_memory_info() << log_memory_info(); - // Remove empty volumes from the newly added volumes. - { - for (auto ptr_it = m_volumes.volumes.begin() + volumes_cnt_initial; ptr_it != m_volumes.volumes.end(); ++ptr_it) - if ((*ptr_it)->empty()) { - delete *ptr_it; - *ptr_it = nullptr; - } - m_volumes.volumes.erase(std::remove(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), nullptr), m_volumes.volumes.end()); - } - for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) { - GLVolume* v = m_volumes.volumes[i]; -#if ENABLE_LEGACY_OPENGL_REMOVAL - v->is_outside = !contains(build_volume, v->model); - // We are done editinig the model, now it can be sent to gpu - v->model.enable_render(); -#else - v->is_outside = ! build_volume.all_paths_inside_vertices_and_normals_interleaved(v->indexed_vertex_array.vertices_and_normals_interleaved, v->indexed_vertex_array.bounding_box()); - v->indexed_vertex_array.finalize_geometry(m_initialized); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - - BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end" << m_volumes.log_memory_info() << log_memory_info(); -} - -// While it looks like we can call -// this->reload_scene(true, true) -// the two functions are quite different: -// 1) This function only loads objects, for which the step slaposSliceSupports already finished. Therefore objects outside of the print bed never load. -// 2) This function loads object mesh with the relative scaling correction (the "relative_correction" parameter) was applied, -// therefore the mesh may be slightly larger or smaller than the mesh shown in the 3D scene. -void GLCanvas3D::_load_sla_shells() -{ - const SLAPrint* print = this->sla_print(); - if (print->objects().empty()) - // nothing to render, return - return; - - auto add_volume = [this](const SLAPrintObject &object, int volume_id, const SLAPrintObject::Instance& instance, - const TriangleMesh& mesh, const ColorRGBA& color, bool outside_printer_detection_enabled) { - m_volumes.volumes.emplace_back(new GLVolume(color)); - GLVolume& v = *m_volumes.volumes.back(); -#if ENABLE_SMOOTH_NORMALS -#if ENABLE_LEGACY_OPENGL_REMOVAL - v.model.init_from(mesh, true); -#else - v.indexed_vertex_array.load_mesh(mesh, true); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -#else -#if ENABLE_LEGACY_OPENGL_REMOVAL - v.model.init_from(mesh); -#else - v.indexed_vertex_array.load_mesh(mesh); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -#endif // ENABLE_SMOOTH_NORMALS -#if !ENABLE_LEGACY_OPENGL_REMOVAL - v.indexed_vertex_array.finalize_geometry(m_initialized); -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - v.shader_outside_printer_detection_enabled = outside_printer_detection_enabled; - v.composite_id.volume_id = volume_id; - v.set_instance_offset(unscale(instance.shift.x(), instance.shift.y(), 0.0)); - v.set_instance_rotation({ 0.0, 0.0, (double)instance.rotation }); - v.set_instance_mirror(X, object.is_left_handed() ? -1. : 1.); - v.set_convex_hull(mesh.convex_hull_3d()); - }; - - // adds objects' volumes - for (const SLAPrintObject* obj : print->objects()) - if (obj->is_step_done(slaposSliceSupports)) { - unsigned int initial_volumes_count = (unsigned int)m_volumes.volumes.size(); - for (const SLAPrintObject::Instance& instance : obj->instances()) { - add_volume(*obj, 0, instance, obj->get_mesh_to_print(), GLVolume::MODEL_COLOR[0], true); - // Set the extruder_id and volume_id to achieve the same color as in the 3D scene when - // through the update_volumes_colors_by_extruder() call. - m_volumes.volumes.back()->extruder_id = obj->model_object()->volumes.front()->extruder_id(); - if (obj->is_step_done(slaposSupportTree) && obj->has_mesh(slaposSupportTree)) - add_volume(*obj, -int(slaposSupportTree), instance, obj->support_mesh(), GLVolume::SLA_SUPPORT_COLOR, true); - if (obj->is_step_done(slaposPad) && obj->has_mesh(slaposPad)) - add_volume(*obj, -int(slaposPad), instance, obj->pad_mesh(), GLVolume::SLA_PAD_COLOR, false); - } - double shift_z = obj->get_current_elevation(); - for (unsigned int i = initial_volumes_count; i < m_volumes.volumes.size(); ++ i) { - // apply shift z - m_volumes.volumes[i]->set_sla_shift_z(shift_z); - } - } - - update_volumes_colors_by_extruder(); -} - -void GLCanvas3D::_update_sla_shells_outside_state() -{ - check_volumes_outside_state(); -} - -void GLCanvas3D::_set_warning_notification_if_needed(EWarning warning) -{ - _set_current(); - bool show = false; - if (!m_volumes.empty()) - show = _is_any_volume_outside(); - else { - if (wxGetApp().is_editor()) { - if (current_printer_technology() != ptSLA) - show = m_gcode_viewer.has_data() && !m_gcode_viewer.is_contained_in_bed(); - } - } - - _set_warning_notification(warning, show); -} - -void GLCanvas3D::_set_warning_notification(EWarning warning, bool state) -{ - enum ErrorType{ - PLATER_WARNING, - PLATER_ERROR, - SLICING_ERROR - }; - std::string text; - ErrorType error = ErrorType::PLATER_WARNING; - switch (warning) { - case EWarning::ObjectOutside: text = _u8L("An object outside the print area was detected."); break; - case EWarning::ToolpathOutside: text = _u8L("A toolpath outside the print area was detected."); error = ErrorType::SLICING_ERROR; break; - case EWarning::SlaSupportsOutside: text = _u8L("SLA supports outside the print area were detected."); error = ErrorType::PLATER_ERROR; break; - case EWarning::SomethingNotShown: text = _u8L("Some objects are not visible during editing."); break; - case EWarning::ObjectClashed: - text = _u8L("An object outside the print area was detected.\n" - "Resolve the current problem to continue slicing."); - error = ErrorType::PLATER_ERROR; - break; - } - auto& notification_manager = *wxGetApp().plater()->get_notification_manager(); - switch (error) - { - case PLATER_WARNING: - if (state) - notification_manager.push_plater_warning_notification(text); - else - notification_manager.close_plater_warning_notification(text); - break; - case PLATER_ERROR: - if (state) - notification_manager.push_plater_error_notification(text); - else - notification_manager.close_plater_error_notification(text); - break; - case SLICING_ERROR: - if (state) - notification_manager.push_slicing_error_notification(text); - else - notification_manager.close_slicing_error_notification(text); - break; - default: - break; - } -} - -bool GLCanvas3D::_is_any_volume_outside() const -{ - for (const GLVolume* volume : m_volumes.volumes) { - if (volume != nullptr && volume->is_outside) - return true; - } - - return false; -} - -void GLCanvas3D::_update_selection_from_hover() -{ - bool ctrl_pressed = wxGetKeyState(WXK_CONTROL); - - if (m_hover_volume_idxs.empty()) { - if (!ctrl_pressed && m_rectangle_selection.get_state() == GLSelectionRectangle::EState::Select) - m_selection.remove_all(); - - return; - } - - GLSelectionRectangle::EState state = m_rectangle_selection.get_state(); - - bool hover_modifiers_only = true; - for (int i : m_hover_volume_idxs) { - if (!m_volumes.volumes[i]->is_modifier) { - hover_modifiers_only = false; - break; - } - } - - bool selection_changed = false; -#if ENABLE_NEW_RECTANGLE_SELECTION - if (!m_rectangle_selection.is_empty()) { -#endif // ENABLE_NEW_RECTANGLE_SELECTION - if (state == GLSelectionRectangle::EState::Select) { - bool contains_all = true; - for (int i : m_hover_volume_idxs) { - if (!m_selection.contains_volume((unsigned int)i)) { - contains_all = false; - break; - } - } - - // the selection is going to be modified (Add) - if (!contains_all) { - wxGetApp().plater()->take_snapshot(_L("Selection-Add from rectangle"), UndoRedo::SnapshotType::Selection); - selection_changed = true; - } - } - else { - bool contains_any = false; - for (int i : m_hover_volume_idxs) { - if (m_selection.contains_volume((unsigned int)i)) { - contains_any = true; - break; - } - } - - // the selection is going to be modified (Remove) - if (contains_any) { - wxGetApp().plater()->take_snapshot(_L("Selection-Remove from rectangle"), UndoRedo::SnapshotType::Selection); - selection_changed = true; - } - } -#if ENABLE_NEW_RECTANGLE_SELECTION - } -#endif // ENABLE_NEW_RECTANGLE_SELECTION - - if (!selection_changed) - return; - - Plater::SuppressSnapshots suppress(wxGetApp().plater()); - - if (state == GLSelectionRectangle::EState::Select && !ctrl_pressed) - m_selection.clear(); - - for (int i : m_hover_volume_idxs) { - if (state == GLSelectionRectangle::EState::Select) { - if (hover_modifiers_only) { - const GLVolume& v = *m_volumes.volumes[i]; - m_selection.add_volume(v.object_idx(), v.volume_idx(), v.instance_idx(), false); - } - else - m_selection.add(i, false); - } - else - m_selection.remove(i); - } - - if (m_selection.is_empty()) - m_gizmos.reset_all_states(); - else - m_gizmos.refresh_on_off_state(); - - m_gizmos.update_data(); - post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); - m_dirty = true; -} - -bool GLCanvas3D::_deactivate_undo_redo_toolbar_items() -{ - if (m_undoredo_toolbar.is_item_pressed("undo")) { - m_undoredo_toolbar.force_right_action(m_undoredo_toolbar.get_item_id("undo"), *this); - return true; - } - else if (m_undoredo_toolbar.is_item_pressed("redo")) { - m_undoredo_toolbar.force_right_action(m_undoredo_toolbar.get_item_id("redo"), *this); - return true; - } - - return false; -} - -bool GLCanvas3D::is_search_pressed() const -{ - return m_main_toolbar.is_item_pressed("search"); -} - -bool GLCanvas3D::_deactivate_arrange_menu() -{ - if (m_main_toolbar.is_item_pressed("arrange")) { - m_main_toolbar.force_right_action(m_main_toolbar.get_item_id("arrange"), *this); - return true; - } - - return false; -} - -bool GLCanvas3D::_deactivate_search_toolbar_item() -{ - if (is_search_pressed()) { - m_main_toolbar.force_left_action(m_main_toolbar.get_item_id("search"), *this); - return true; - } - - return false; -} - -bool GLCanvas3D::_activate_search_toolbar_item() -{ - if (!m_main_toolbar.is_item_pressed("search")) { - m_main_toolbar.force_left_action(m_main_toolbar.get_item_id("search"), *this); - return true; - } - - return false; -} - -bool GLCanvas3D::_deactivate_collapse_toolbar_items() -{ - GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); - if (collapse_toolbar.is_item_pressed("print")) { - collapse_toolbar.force_left_action(collapse_toolbar.get_item_id("print"), *this); - return true; - } - - return false; -} - -void GLCanvas3D::highlight_toolbar_item(const std::string& item_name) -{ - GLToolbarItem* item = m_main_toolbar.get_item(item_name); - if (!item) - item = m_undoredo_toolbar.get_item(item_name); - if (!item || !item->is_visible()) - return; - m_toolbar_highlighter.init(item, this); -} - -void GLCanvas3D::highlight_gizmo(const std::string& gizmo_name) -{ - GLGizmosManager::EType gizmo = m_gizmos.get_gizmo_from_name(gizmo_name); - if(gizmo == GLGizmosManager::EType::Undefined) - return; - m_gizmo_highlighter.init(&m_gizmos, gizmo, this); -} - -const Print* GLCanvas3D::fff_print() const -{ - return (m_process == nullptr) ? nullptr : m_process->fff_print(); -} - -const SLAPrint* GLCanvas3D::sla_print() const -{ - return (m_process == nullptr) ? nullptr : m_process->sla_print(); -} - -void GLCanvas3D::WipeTowerInfo::apply_wipe_tower() const -{ - DynamicPrintConfig cfg; - cfg.opt("wipe_tower_x", true)->value = m_pos(X); - cfg.opt("wipe_tower_y", true)->value = m_pos(Y); - cfg.opt("wipe_tower_rotation_angle", true)->value = (180./M_PI) * m_rotation; - wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg); -} - -void GLCanvas3D::RenderTimer::Notify() -{ - wxPostEvent((wxEvtHandler*)GetOwner(), RenderTimerEvent( EVT_GLCANVAS_RENDER_TIMER, *this)); -} - -void GLCanvas3D::ToolbarHighlighterTimer::Notify() -{ - wxPostEvent((wxEvtHandler*)GetOwner(), ToolbarHighlighterTimerEvent(EVT_GLCANVAS_TOOLBAR_HIGHLIGHTER_TIMER, *this)); -} - -void GLCanvas3D::GizmoHighlighterTimer::Notify() -{ - wxPostEvent((wxEvtHandler*)GetOwner(), GizmoHighlighterTimerEvent(EVT_GLCANVAS_GIZMO_HIGHLIGHTER_TIMER, *this)); -} - -void GLCanvas3D::ToolbarHighlighter::set_timer_owner(wxEvtHandler* owner, int timerid/* = wxID_ANY*/) -{ - m_timer.SetOwner(owner, timerid); -} - -void GLCanvas3D::ToolbarHighlighter::init(GLToolbarItem* toolbar_item, GLCanvas3D* canvas) -{ - if (m_timer.IsRunning()) - invalidate(); - if (!toolbar_item || !canvas) - return; - - m_timer.Start(300, false); - - m_toolbar_item = toolbar_item; - m_canvas = canvas; -} - -void GLCanvas3D::ToolbarHighlighter::invalidate() -{ - m_timer.Stop(); - - if (m_toolbar_item) { - m_toolbar_item->set_highlight(GLToolbarItem::EHighlightState::NotHighlighted); - } - m_toolbar_item = nullptr; - m_blink_counter = 0; - m_render_arrow = false; -} - -void GLCanvas3D::ToolbarHighlighter::blink() -{ - if (m_toolbar_item) { - char state = m_toolbar_item->get_highlight(); - if (state != (char)GLToolbarItem::EHighlightState::HighlightedShown) - m_toolbar_item->set_highlight(GLToolbarItem::EHighlightState::HighlightedShown); - else - m_toolbar_item->set_highlight(GLToolbarItem::EHighlightState::HighlightedHidden); - - m_render_arrow = !m_render_arrow; - m_canvas->set_as_dirty(); - } - else - invalidate(); - - if ((++m_blink_counter) >= 11) - invalidate(); -} - -void GLCanvas3D::GizmoHighlighter::set_timer_owner(wxEvtHandler* owner, int timerid/* = wxID_ANY*/) -{ - m_timer.SetOwner(owner, timerid); -} - -void GLCanvas3D::GizmoHighlighter::init(GLGizmosManager* manager, GLGizmosManager::EType gizmo, GLCanvas3D* canvas) -{ - if (m_timer.IsRunning()) - invalidate(); - if (!gizmo || !canvas) - return; - - m_timer.Start(300, false); - - m_gizmo_manager = manager; - m_gizmo_type = gizmo; - m_canvas = canvas; -} - -void GLCanvas3D::GizmoHighlighter::invalidate() -{ - m_timer.Stop(); - - if (m_gizmo_manager) { - m_gizmo_manager->set_highlight(GLGizmosManager::EType::Undefined, false); - } - m_gizmo_manager = nullptr; - m_gizmo_type = GLGizmosManager::EType::Undefined; - m_blink_counter = 0; - m_render_arrow = false; -} - -void GLCanvas3D::GizmoHighlighter::blink() -{ - if (m_gizmo_manager) { - if (m_blink_counter % 2 == 0) - m_gizmo_manager->set_highlight(m_gizmo_type, true); - else - m_gizmo_manager->set_highlight(m_gizmo_type, false); - - m_render_arrow = !m_render_arrow; - m_canvas->set_as_dirty(); - } - else - invalidate(); - - if ((++m_blink_counter) >= 11) - invalidate(); -} - -} // namespace GUI -} // namespace Slic3r +#include "libslic3r/libslic3r.h" +#include "GLCanvas3D.hpp" + +#include + +#include "libslic3r/BuildVolume.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/GCode/ThumbnailData.hpp" +#include "libslic3r/Geometry/ConvexHull.hpp" +#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/Layer.hpp" +#include "libslic3r/Utils.hpp" +#include "libslic3r/Technologies.hpp" +#include "libslic3r/Tesselate.hpp" +#include "libslic3r/PresetBundle.hpp" +#include "3DBed.hpp" +#include "3DScene.hpp" +#include "BackgroundSlicingProcess.hpp" +#include "GLShader.hpp" +#include "GUI.hpp" +#include "Tab.hpp" +#include "GUI_Preview.hpp" +#include "OpenGLManager.hpp" +#include "Plater.hpp" +#include "MainFrame.hpp" +#include "GUI_App.hpp" +#include "GUI_ObjectList.hpp" +#include "GUI_ObjectManipulation.hpp" +#include "Mouse3DController.hpp" +#include "I18N.hpp" +#include "NotificationManager.hpp" +#include "format.hpp" + +#include "slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp" +#include "slic3r/Utils/UndoRedo.hpp" + +#if ENABLE_RETINA_GL +#include "slic3r/Utils/RetinaHelper.hpp" +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +// Print now includes tbb, and tbb includes Windows. This breaks compilation of wxWidgets if included before wx. +#include "libslic3r/Print.hpp" +#include "libslic3r/SLAPrint.hpp" + +#include "wxExtensions.hpp" + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include "DoubleSlider.hpp" + +#include + +static constexpr const float TRACKBALLSIZE = 0.8f; + +#if ENABLE_LEGACY_OPENGL_REMOVAL +static const Slic3r::ColorRGBA DEFAULT_BG_DARK_COLOR = { 0.478f, 0.478f, 0.478f, 1.0f }; +static const Slic3r::ColorRGBA DEFAULT_BG_LIGHT_COLOR = { 0.753f, 0.753f, 0.753f, 1.0f }; +static const Slic3r::ColorRGBA ERROR_BG_DARK_COLOR = { 0.478f, 0.192f, 0.039f, 1.0f }; +static const Slic3r::ColorRGBA ERROR_BG_LIGHT_COLOR = { 0.753f, 0.192f, 0.039f, 1.0f }; +#else +static const Slic3r::ColorRGB DEFAULT_BG_DARK_COLOR = { 0.478f, 0.478f, 0.478f }; +static const Slic3r::ColorRGB DEFAULT_BG_LIGHT_COLOR = { 0.753f, 0.753f, 0.753f }; +static const Slic3r::ColorRGB ERROR_BG_DARK_COLOR = { 0.478f, 0.192f, 0.039f }; +static const Slic3r::ColorRGB ERROR_BG_LIGHT_COLOR = { 0.753f, 0.192f, 0.039f }; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + +// Number of floats +static constexpr const size_t MAX_VERTEX_BUFFER_SIZE = 131072 * 6; // 3.15MB +// Reserve size in number of floats. +#if !ENABLE_LEGACY_OPENGL_REMOVAL +static constexpr const size_t VERTEX_BUFFER_RESERVE_SIZE = 131072 * 2; // 1.05MB +// Reserve size in number of floats, maximum sum of all preallocated buffers. +//static constexpr const size_t VERTEX_BUFFER_RESERVE_SIZE_SUM_MAX = 1024 * 1024 * 128 / 4; // 128MB +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + +namespace Slic3r { +namespace GUI { + +#ifdef __WXGTK3__ +// wxGTK3 seems to simulate OSX behavior in regard to HiDPI scaling support. +RetinaHelper::RetinaHelper(wxWindow* window) : m_window(window), m_self(nullptr) {} +RetinaHelper::~RetinaHelper() {} +float RetinaHelper::get_scale_factor() { return float(m_window->GetContentScaleFactor()); } +#endif // __WXGTK3__ + +// Fixed the collision between BuildVolume::Type::Convex and macro Convex defined inside /usr/include/X11/X.h that is included by WxWidgets 3.0. +#if defined(__linux__) && defined(Convex) +#undef Convex +#endif + +GLCanvas3D::LayersEditing::~LayersEditing() +{ + if (m_z_texture_id != 0) { + glsafe(::glDeleteTextures(1, &m_z_texture_id)); + m_z_texture_id = 0; + } + delete m_slicing_parameters; +} + +const float GLCanvas3D::LayersEditing::THICKNESS_BAR_WIDTH = 70.0f; + +void GLCanvas3D::LayersEditing::init() +{ + glsafe(::glGenTextures(1, (GLuint*)&m_z_texture_id)); + glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1)); + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); +} + +void GLCanvas3D::LayersEditing::set_config(const DynamicPrintConfig* config) +{ + m_config = config; + delete m_slicing_parameters; + m_slicing_parameters = nullptr; + m_layers_texture.valid = false; +} + +void GLCanvas3D::LayersEditing::select_object(const Model &model, int object_id) +{ + const ModelObject *model_object_new = (object_id >= 0) ? model.objects[object_id] : nullptr; + // Maximum height of an object changes when the object gets rotated or scaled. + // Changing maximum height of an object will invalidate the layer heigth editing profile. + // m_model_object->bounding_box() is cached, therefore it is cheap even if this method is called frequently. + const float new_max_z = (model_object_new == nullptr) ? 0.0f : static_cast(model_object_new->bounding_box().max.z()); + if (m_model_object != model_object_new || this->last_object_id != object_id || m_object_max_z != new_max_z || + (model_object_new != nullptr && m_model_object->id() != model_object_new->id())) { + m_layer_height_profile.clear(); + m_layer_height_profile_modified = false; + delete m_slicing_parameters; + m_slicing_parameters = nullptr; + m_layers_texture.valid = false; + this->last_object_id = object_id; + m_model_object = model_object_new; + m_object_max_z = new_max_z; + } +} + +bool GLCanvas3D::LayersEditing::is_allowed() const +{ + return wxGetApp().get_shader("variable_layer_height") != nullptr && m_z_texture_id > 0; +} + +bool GLCanvas3D::LayersEditing::is_enabled() const +{ + return m_enabled; +} + +void GLCanvas3D::LayersEditing::set_enabled(bool enabled) +{ + m_enabled = is_allowed() && enabled; +} + +float GLCanvas3D::LayersEditing::s_overlay_window_width; + +void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) +{ + if (!m_enabled) + return; + + const Size& cnv_size = canvas.get_canvas_size(); + + ImGuiWrapper& imgui = *wxGetApp().imgui(); + imgui.set_next_window_pos(static_cast(cnv_size.get_width()) - imgui.get_style_scaling() * THICKNESS_BAR_WIDTH, + static_cast(cnv_size.get_height()), ImGuiCond_Always, 1.0f, 1.0f); + + imgui.begin(_L("Variable layer height"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse); + + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Left mouse button:")); + ImGui::SameLine(); + imgui.text(_L("Add detail")); + + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Right mouse button:")); + ImGui::SameLine(); + imgui.text(_L("Remove detail")); + + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Shift + Left mouse button:")); + ImGui::SameLine(); + imgui.text(_L("Reset to base")); + + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Shift + Right mouse button:")); + ImGui::SameLine(); + imgui.text(_L("Smoothing")); + + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Mouse wheel:")); + ImGui::SameLine(); + imgui.text(_L("Increase/decrease edit area")); + + ImGui::Separator(); + if (imgui.button(_L("Adaptive"))) + wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), Event(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, m_adaptive_quality)); + + ImGui::SameLine(); + float text_align = ImGui::GetCursorPosX(); + ImGui::AlignTextToFramePadding(); + imgui.text(_L("Quality / Speed")); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::TextUnformatted(_L("Higher print quality versus higher print speed.").ToUTF8()); + ImGui::EndTooltip(); + } + + ImGui::SameLine(); + float widget_align = ImGui::GetCursorPosX(); + ImGui::PushItemWidth(imgui.get_style_scaling() * 120.0f); + m_adaptive_quality = std::clamp(m_adaptive_quality, 0.0f, 1.f); + imgui.slider_float("", &m_adaptive_quality, 0.0f, 1.f, "%.2f"); + + ImGui::Separator(); + if (imgui.button(_L("Smooth"))) + wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), HeightProfileSmoothEvent(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, m_smooth_params)); + + ImGui::SameLine(); + ImGui::SetCursorPosX(text_align); + ImGui::AlignTextToFramePadding(); + imgui.text(_L("Radius")); + ImGui::SameLine(); + ImGui::SetCursorPosX(widget_align); + ImGui::PushItemWidth(imgui.get_style_scaling() * 120.0f); + int radius = (int)m_smooth_params.radius; + if (ImGui::SliderInt("##1", &radius, 1, 10)) { + radius = std::clamp(radius, 1, 10); + m_smooth_params.radius = (unsigned int)radius; + } + + ImGui::SetCursorPosX(text_align); + ImGui::AlignTextToFramePadding(); + imgui.text(_L("Keep min")); + ImGui::SameLine(); + if (ImGui::GetCursorPosX() < widget_align) // because of line lenght after localization + ImGui::SetCursorPosX(widget_align); + + ImGui::PushItemWidth(imgui.get_style_scaling() * 120.0f); + imgui.checkbox("##2", m_smooth_params.keep_min); + + ImGui::Separator(); + if (imgui.button(_L("Reset"))) + wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), SimpleEvent(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE)); + + GLCanvas3D::LayersEditing::s_overlay_window_width = ImGui::GetWindowSize().x /*+ (float)m_layers_texture.width/4*/; + imgui.end(); + +#if ENABLE_GL_SHADERS_ATTRIBUTES + render_active_object_annotations(canvas); + render_profile(canvas); +#else + const Rect& bar_rect = get_bar_rect_viewport(canvas); +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_profile.dirty = m_profile.old_bar_rect != bar_rect; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + render_active_object_annotations(canvas, bar_rect); + render_profile(bar_rect); +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_profile.old_bar_rect = bar_rect; + m_profile.dirty = false; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +#endif // ENABLE_GL_SHADERS_ATTRIBUTES +} + +float GLCanvas3D::LayersEditing::get_cursor_z_relative(const GLCanvas3D& canvas) +{ + const Vec2d mouse_pos = canvas.get_local_mouse_position(); + const Rect& rect = get_bar_rect_screen(canvas); + float x = (float)mouse_pos.x(); + float y = (float)mouse_pos.y(); + float t = rect.get_top(); + float b = rect.get_bottom(); + + return (rect.get_left() <= x && x <= rect.get_right() && t <= y && y <= b) ? + // Inside the bar. + (b - y - 1.0f) / (b - t - 1.0f) : + // Outside the bar. + -1000.0f; +} + +bool GLCanvas3D::LayersEditing::bar_rect_contains(const GLCanvas3D& canvas, float x, float y) +{ + const Rect& rect = get_bar_rect_screen(canvas); + return rect.get_left() <= x && x <= rect.get_right() && rect.get_top() <= y && y <= rect.get_bottom(); +} + +Rect GLCanvas3D::LayersEditing::get_bar_rect_screen(const GLCanvas3D& canvas) +{ + const Size& cnv_size = canvas.get_canvas_size(); + float w = (float)cnv_size.get_width(); + float h = (float)cnv_size.get_height(); + + return { w - thickness_bar_width(canvas), 0.0f, w, h }; +} + +#if !ENABLE_GL_SHADERS_ATTRIBUTES +Rect GLCanvas3D::LayersEditing::get_bar_rect_viewport(const GLCanvas3D& canvas) +{ + const Size& cnv_size = canvas.get_canvas_size(); + float half_w = 0.5f * (float)cnv_size.get_width(); + float half_h = 0.5f * (float)cnv_size.get_height(); + float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); + return { (half_w - thickness_bar_width(canvas)) * inv_zoom, half_h * inv_zoom, half_w * inv_zoom, -half_h * inv_zoom }; +} +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + +bool GLCanvas3D::LayersEditing::is_initialized() const +{ + return wxGetApp().get_shader("variable_layer_height") != nullptr; +} + +std::string GLCanvas3D::LayersEditing::get_tooltip(const GLCanvas3D& canvas) const +{ + std::string ret; + if (m_enabled && m_layer_height_profile.size() >= 4) { + float z = get_cursor_z_relative(canvas); + if (z != -1000.0f) { + z *= m_object_max_z; + + float h = 0.0f; + for (size_t i = m_layer_height_profile.size() - 2; i >= 2; i -= 2) { + const float zi = static_cast(m_layer_height_profile[i]); + const float zi_1 = static_cast(m_layer_height_profile[i - 2]); + if (zi_1 <= z && z <= zi) { + float dz = zi - zi_1; + h = (dz != 0.0f) ? static_cast(lerp(m_layer_height_profile[i - 1], m_layer_height_profile[i + 1], (z - zi_1) / dz)) : + static_cast(m_layer_height_profile[i + 1]); + break; + } + } + if (h > 0.0f) + ret = std::to_string(h); + } + } + return ret; +} + +#if ENABLE_GL_SHADERS_ATTRIBUTES +void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3D& canvas) +#else +void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3D& canvas, const Rect& bar_rect) +#endif // ENABLE_GL_SHADERS_ATTRIBUTES +{ +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Size cnv_size = canvas.get_canvas_size(); + const float cnv_width = (float)cnv_size.get_width(); + const float cnv_height = (float)cnv_size.get_height(); + if (cnv_width == 0.0f || cnv_height == 0.0f) + return; + + const float cnv_inv_width = 1.0f / cnv_width; +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + GLShaderProgram* shader = wxGetApp().get_shader("variable_layer_height"); + if (shader == nullptr) + return; + + shader->start_using(); + + shader->set_uniform("z_to_texture_row", float(m_layers_texture.cells - 1) / (float(m_layers_texture.width) * m_object_max_z)); + shader->set_uniform("z_texture_row_to_normalized", 1.0f / (float)m_layers_texture.height); + shader->set_uniform("z_cursor", m_object_max_z * this->get_cursor_z_relative(canvas)); + shader->set_uniform("z_cursor_band_width", band_width); + shader->set_uniform("object_max_z", m_object_max_z); +#if ENABLE_GL_SHADERS_ATTRIBUTES + shader->set_uniform("view_model_matrix", Transform3d::Identity()); + shader->set_uniform("projection_matrix", Transform3d::Identity()); + shader->set_uniform("normal_matrix", (Matrix3d)Eigen::Matrix3d::Identity()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); + glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id)); + + // Render the color bar +#if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_GL_SHADERS_ATTRIBUTES + if (!m_profile.background.is_initialized() || m_profile.old_canvas_width != cnv_width) { + m_profile.old_canvas_width = cnv_width; +#else + if (!m_profile.background.is_initialized() || m_profile.dirty) { +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_profile.background.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P2T2 }; + init_data.reserve_vertices(4); + init_data.reserve_indices(6); + + // vertices +#if ENABLE_GL_SHADERS_ATTRIBUTES + const float l = 1.0f - 2.0f * THICKNESS_BAR_WIDTH * cnv_inv_width; + const float r = 1.0f; + const float t = 1.0f; + const float b = -1.0f; +#else + const float l = bar_rect.get_left(); + const float r = bar_rect.get_right(); + const float t = bar_rect.get_top(); + const float b = bar_rect.get_bottom(); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + init_data.add_vertex(Vec2f(l, b), Vec2f(0.0f, 0.0f)); + init_data.add_vertex(Vec2f(r, b), Vec2f(1.0f, 0.0f)); + init_data.add_vertex(Vec2f(r, t), Vec2f(1.0f, 1.0f)); + init_data.add_vertex(Vec2f(l, t), Vec2f(0.0f, 1.0f)); + + // indices + init_data.add_triangle(0, 1, 2); + init_data.add_triangle(2, 3, 0); + + m_profile.background.init_from(std::move(init_data)); + } + + m_profile.background.render(); +#else + const float l = bar_rect.get_left(); + const float r = bar_rect.get_right(); + const float t = bar_rect.get_top(); + const float b = bar_rect.get_bottom(); + + ::glBegin(GL_QUADS); + ::glNormal3f(0.0f, 0.0f, 1.0f); + ::glTexCoord2f(0.0f, 0.0f); ::glVertex2f(l, b); + ::glTexCoord2f(1.0f, 0.0f); ::glVertex2f(r, b); + ::glTexCoord2f(1.0f, 1.0f); ::glVertex2f(r, t); + ::glTexCoord2f(0.0f, 1.0f); ::glVertex2f(l, t); + glsafe(::glEnd()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); + + shader->stop_using(); +} + +#if ENABLE_GL_SHADERS_ATTRIBUTES +void GLCanvas3D::LayersEditing::render_profile(const GLCanvas3D& canvas) +#else +void GLCanvas3D::LayersEditing::render_profile(const Rect& bar_rect) +#endif // ENABLE_GL_SHADERS_ATTRIBUTES +{ + //FIXME show some kind of legend. + + if (!m_slicing_parameters) + return; + +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Size cnv_size = canvas.get_canvas_size(); + const float cnv_width = (float)cnv_size.get_width(); + const float cnv_height = (float)cnv_size.get_height(); + if (cnv_width == 0.0f || cnv_height == 0.0f) + return; + + // Make the vertical bar a bit wider so the layer height curve does not touch the edge of the bar region. + const float scale_x = THICKNESS_BAR_WIDTH / float(1.12 * m_slicing_parameters->max_layer_height); + const float scale_y = cnv_height / m_object_max_z; + + const float cnv_inv_width = 1.0f / cnv_width; + const float cnv_inv_height = 1.0f / cnv_height; +#else + // Make the vertical bar a bit wider so the layer height curve does not touch the edge of the bar region. + const float scale_x = bar_rect.get_width() / float(1.12 * m_slicing_parameters->max_layer_height); + const float scale_y = bar_rect.get_height() / m_object_max_z; +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + +#if ENABLE_LEGACY_OPENGL_REMOVAL + // Baseline +#if ENABLE_GL_SHADERS_ATTRIBUTES + if (!m_profile.baseline.is_initialized() || m_profile.old_layer_height_profile != m_layer_height_profile) { +#else + if (!m_profile.baseline.is_initialized() || m_profile.dirty) { +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_profile.baseline.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P2 }; + init_data.color = ColorRGBA::BLACK(); + init_data.reserve_vertices(2); + init_data.reserve_indices(2); + + // vertices +#if ENABLE_GL_SHADERS_ATTRIBUTES + const float axis_x = 2.0f * ((cnv_width - THICKNESS_BAR_WIDTH + float(m_slicing_parameters->layer_height) * scale_x) * cnv_inv_width - 0.5f); + init_data.add_vertex(Vec2f(axis_x, -1.0f)); + init_data.add_vertex(Vec2f(axis_x, 1.0f)); +#else + const float x = bar_rect.get_left() + float(m_slicing_parameters->layer_height) * scale_x; + init_data.add_vertex(Vec2f(x, bar_rect.get_bottom())); + init_data.add_vertex(Vec2f(x, bar_rect.get_top())); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + // indices + init_data.add_line(0, 1); + + m_profile.baseline.init_from(std::move(init_data)); + } + +#if ENABLE_GL_SHADERS_ATTRIBUTES + if (!m_profile.profile.is_initialized() || m_profile.old_layer_height_profile != m_layer_height_profile) { +#else + if (!m_profile.profile.is_initialized() || m_profile.dirty || m_profile.old_layer_height_profile != m_layer_height_profile) { +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_profile.old_layer_height_profile = m_layer_height_profile; + m_profile.profile.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::LineStrip, GLModel::Geometry::EVertexLayout::P2 }; + init_data.color = ColorRGBA::BLUE(); + init_data.reserve_vertices(m_layer_height_profile.size() / 2); + init_data.reserve_indices(m_layer_height_profile.size() / 2); + + // vertices + indices + for (unsigned int i = 0; i < (unsigned int)m_layer_height_profile.size(); i += 2) { +#if ENABLE_GL_SHADERS_ATTRIBUTES + init_data.add_vertex(Vec2f(2.0f * ((cnv_width - THICKNESS_BAR_WIDTH + float(m_layer_height_profile[i + 1]) * scale_x) * cnv_inv_width - 0.5f), + 2.0f * (float(m_layer_height_profile[i]) * scale_y * cnv_inv_height - 0.5))); +#else + init_data.add_vertex(Vec2f(bar_rect.get_left() + float(m_layer_height_profile[i + 1]) * scale_x, + bar_rect.get_bottom() + float(m_layer_height_profile[i]) * scale_y)); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + init_data.add_index(i / 2); + } + + m_profile.profile.init_from(std::move(init_data)); + } + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); +#if ENABLE_GL_SHADERS_ATTRIBUTES + shader->set_uniform("view_model_matrix", Transform3d::Identity()); + shader->set_uniform("projection_matrix", Transform3d::Identity()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_profile.baseline.render(); + m_profile.profile.render(); + shader->stop_using(); + } +#else + const float x = bar_rect.get_left() + float(m_slicing_parameters->layer_height) * scale_x; + + // Baseline + glsafe(::glColor3f(0.0f, 0.0f, 0.0f)); + ::glBegin(GL_LINE_STRIP); + ::glVertex2f(x, bar_rect.get_bottom()); + ::glVertex2f(x, bar_rect.get_top()); + glsafe(::glEnd()); + + // Curve + glsafe(::glColor3f(0.0f, 0.0f, 1.0f)); + ::glBegin(GL_LINE_STRIP); + for (unsigned int i = 0; i < m_layer_height_profile.size(); i += 2) + ::glVertex2f(bar_rect.get_left() + (float)m_layer_height_profile[i + 1] * scale_x, bar_rect.get_bottom() + (float)m_layer_height_profile[i] * scale_y); + glsafe(::glEnd()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} + +void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const GLVolumeCollection& volumes) +{ + assert(this->is_allowed()); + assert(this->last_object_id != -1); + + GLShaderProgram* current_shader = wxGetApp().get_current_shader(); + ScopeGuard guard([current_shader]() { if (current_shader != nullptr) current_shader->start_using(); }); + if (current_shader != nullptr) + current_shader->stop_using(); + + GLShaderProgram* shader = wxGetApp().get_shader("variable_layer_height"); + if (shader == nullptr) + return; + + shader->start_using(); + + generate_layer_height_texture(); + + // Uniforms were resolved, go ahead using the layer editing shader. + shader->set_uniform("z_to_texture_row", float(m_layers_texture.cells - 1) / (float(m_layers_texture.width) * float(m_object_max_z))); + shader->set_uniform("z_texture_row_to_normalized", 1.0f / float(m_layers_texture.height)); + shader->set_uniform("z_cursor", float(m_object_max_z) * float(this->get_cursor_z_relative(canvas))); + shader->set_uniform("z_cursor_band_width", float(this->band_width)); + +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + // Initialize the layer height texture mapping. + const GLsizei w = (GLsizei)m_layers_texture.width; + const GLsizei h = (GLsizei)m_layers_texture.height; + const GLsizei half_w = w / 2; + const GLsizei half_h = h / 2; + glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); + glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id)); + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); + glsafe(::glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, half_w, half_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); + glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data())); + glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, half_w, half_h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data() + m_layers_texture.width * m_layers_texture.height * 4)); + for (GLVolume* glvolume : volumes.volumes) { + // Render the object using the layer editing shader and texture. + if (!glvolume->is_active || glvolume->composite_id.object_id != this->last_object_id || glvolume->is_modifier) + continue; + + shader->set_uniform("volume_world_matrix", glvolume->world_matrix()); + shader->set_uniform("object_max_z", 0.0f); +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d view_model_matrix = camera.get_view_matrix() * glvolume->world_matrix(); + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + glvolume->render(); + } + // Revert back to the previous shader. + glBindTexture(GL_TEXTURE_2D, 0); +} + +void GLCanvas3D::LayersEditing::adjust_layer_height_profile() +{ + this->update_slicing_parameters(); + PrintObject::update_layer_height_profile(*m_model_object, *m_slicing_parameters, m_layer_height_profile); + Slic3r::adjust_layer_height_profile(*m_slicing_parameters, m_layer_height_profile, this->last_z, this->strength, this->band_width, this->last_action); + m_layer_height_profile_modified = true; + m_layers_texture.valid = false; +} + +void GLCanvas3D::LayersEditing::reset_layer_height_profile(GLCanvas3D& canvas) +{ + const_cast(m_model_object)->layer_height_profile.clear(); + m_layer_height_profile.clear(); + m_layers_texture.valid = false; + canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + wxGetApp().obj_list()->update_info_items(last_object_id); +} + +void GLCanvas3D::LayersEditing::adaptive_layer_height_profile(GLCanvas3D& canvas, float quality_factor) +{ + this->update_slicing_parameters(); + m_layer_height_profile = layer_height_profile_adaptive(*m_slicing_parameters, *m_model_object, quality_factor); + const_cast(m_model_object)->layer_height_profile.set(m_layer_height_profile); + m_layers_texture.valid = false; + canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + wxGetApp().obj_list()->update_info_items(last_object_id); +} + +void GLCanvas3D::LayersEditing::smooth_layer_height_profile(GLCanvas3D& canvas, const HeightProfileSmoothingParams& smoothing_params) +{ + this->update_slicing_parameters(); + m_layer_height_profile = smooth_height_profile(m_layer_height_profile, *m_slicing_parameters, smoothing_params); + const_cast(m_model_object)->layer_height_profile.set(m_layer_height_profile); + m_layers_texture.valid = false; + canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + wxGetApp().obj_list()->update_info_items(last_object_id); +} + +void GLCanvas3D::LayersEditing::generate_layer_height_texture() +{ + this->update_slicing_parameters(); + // Always try to update the layer height profile. + bool update = ! m_layers_texture.valid; + if (PrintObject::update_layer_height_profile(*m_model_object, *m_slicing_parameters, m_layer_height_profile)) { + // Initialized to the default value. + m_layer_height_profile_modified = false; + update = true; + } + // Update if the layer height profile was changed, or when the texture is not valid. + if (! update && ! m_layers_texture.data.empty() && m_layers_texture.cells > 0) + // Texture is valid, don't update. + return; + + if (m_layers_texture.data.empty()) { + m_layers_texture.width = 1024; + m_layers_texture.height = 1024; + m_layers_texture.levels = 2; + m_layers_texture.data.assign(m_layers_texture.width * m_layers_texture.height * 5, 0); + } + + bool level_of_detail_2nd_level = true; + m_layers_texture.cells = Slic3r::generate_layer_height_texture( + *m_slicing_parameters, + Slic3r::generate_object_layers(*m_slicing_parameters, m_layer_height_profile), + m_layers_texture.data.data(), m_layers_texture.height, m_layers_texture.width, level_of_detail_2nd_level); + m_layers_texture.valid = true; +} + +void GLCanvas3D::LayersEditing::accept_changes(GLCanvas3D& canvas) +{ + if (last_object_id >= 0) { + if (m_layer_height_profile_modified) { + wxGetApp().plater()->take_snapshot(_L("Variable layer height - Manual edit")); + const_cast(m_model_object)->layer_height_profile.set(m_layer_height_profile); + canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + wxGetApp().obj_list()->update_info_items(last_object_id); + } + } + m_layer_height_profile_modified = false; +} + +void GLCanvas3D::LayersEditing::update_slicing_parameters() +{ + if (m_slicing_parameters == nullptr) { + m_slicing_parameters = new SlicingParameters(); + *m_slicing_parameters = PrintObject::slicing_parameters(*m_config, *m_model_object, m_object_max_z); + } +} + +float GLCanvas3D::LayersEditing::thickness_bar_width(const GLCanvas3D &canvas) +{ + return +#if ENABLE_RETINA_GL + canvas.get_canvas_size().get_scale_factor() +#else + canvas.get_wxglcanvas()->GetContentScaleFactor() +#endif + * THICKNESS_BAR_WIDTH; +} + + +const Point GLCanvas3D::Mouse::Drag::Invalid_2D_Point(INT_MAX, INT_MAX); +const Vec3d GLCanvas3D::Mouse::Drag::Invalid_3D_Point(DBL_MAX, DBL_MAX, DBL_MAX); +const int GLCanvas3D::Mouse::Drag::MoveThresholdPx = 5; + +GLCanvas3D::Mouse::Drag::Drag() + : start_position_2D(Invalid_2D_Point) + , start_position_3D(Invalid_3D_Point) + , move_volume_idx(-1) + , move_requires_threshold(false) + , move_start_threshold_position_2D(Invalid_2D_Point) +{ +} + +GLCanvas3D::Mouse::Mouse() + : dragging(false) + , position(DBL_MAX, DBL_MAX) + , scene_position(DBL_MAX, DBL_MAX, DBL_MAX) + , ignore_left_up(false) +{ +} + +void GLCanvas3D::Labels::render(const std::vector& sorted_instances) const +{ + if (!m_enabled || !is_shown()) + return; + + const Camera& camera = wxGetApp().plater()->get_camera(); + const Model* model = m_canvas.get_model(); + if (model == nullptr) + return; + + Transform3d world_to_eye = camera.get_view_matrix(); + Transform3d world_to_screen = camera.get_projection_matrix() * world_to_eye; + const std::array& viewport = camera.get_viewport(); + + struct Owner + { + int obj_idx; + int inst_idx; + size_t model_instance_id; + BoundingBoxf3 world_box; + double eye_center_z; + std::string title; + std::string label; + std::string print_order; + bool selected; + }; + + // collect owners world bounding boxes and data from volumes + std::vector owners; + const GLVolumeCollection& volumes = m_canvas.get_volumes(); + for (const GLVolume* volume : volumes.volumes) { + int obj_idx = volume->object_idx(); + if (0 <= obj_idx && obj_idx < (int)model->objects.size()) { + int inst_idx = volume->instance_idx(); + std::vector::iterator it = std::find_if(owners.begin(), owners.end(), [obj_idx, inst_idx](const Owner& owner) { + return (owner.obj_idx == obj_idx) && (owner.inst_idx == inst_idx); + }); + if (it != owners.end()) { + it->world_box.merge(volume->transformed_bounding_box()); + it->selected &= volume->selected; + } else { + const ModelObject* model_object = model->objects[obj_idx]; + Owner owner; + owner.obj_idx = obj_idx; + owner.inst_idx = inst_idx; + owner.model_instance_id = model_object->instances[inst_idx]->id().id; + owner.world_box = volume->transformed_bounding_box(); + owner.title = "object" + std::to_string(obj_idx) + "_inst##" + std::to_string(inst_idx); + owner.label = model_object->name; + if (model_object->instances.size() > 1) + owner.label += " (" + std::to_string(inst_idx + 1) + ")"; + owner.selected = volume->selected; + owners.emplace_back(owner); + } + } + } + + // updates print order strings + if (sorted_instances.size() > 1) { + for (size_t i = 0; i < sorted_instances.size(); ++i) { + size_t id = sorted_instances[i]->id().id; + std::vector::iterator it = std::find_if(owners.begin(), owners.end(), [id](const Owner& owner) { + return owner.model_instance_id == id; + }); + if (it != owners.end()) + it->print_order = std::string((_(L("Seq."))).ToUTF8()) + "#: " + std::to_string(i + 1); + } + } + + // calculate eye bounding boxes center zs + for (Owner& owner : owners) { + owner.eye_center_z = (world_to_eye * owner.world_box.center())(2); + } + + // sort owners by center eye zs and selection + std::sort(owners.begin(), owners.end(), [](const Owner& owner1, const Owner& owner2) { + if (!owner1.selected && owner2.selected) + return true; + else if (owner1.selected && !owner2.selected) + return false; + else + return (owner1.eye_center_z < owner2.eye_center_z); + }); + + ImGuiWrapper& imgui = *wxGetApp().imgui(); + + // render info windows + for (const Owner& owner : owners) { + Vec3d screen_box_center = world_to_screen * owner.world_box.center(); + float x = 0.0f; + float y = 0.0f; + if (camera.get_type() == Camera::EType::Perspective) { + x = (0.5f + 0.001f * 0.5f * (float)screen_box_center(0)) * viewport[2]; + y = (0.5f - 0.001f * 0.5f * (float)screen_box_center(1)) * viewport[3]; + } else { + x = (0.5f + 0.5f * (float)screen_box_center(0)) * viewport[2]; + y = (0.5f - 0.5f * (float)screen_box_center(1)) * viewport[3]; + } + + if (x < 0.0f || viewport[2] < x || y < 0.0f || viewport[3] < y) + continue; + + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, owner.selected ? 3.0f : 1.5f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleColor(ImGuiCol_Border, owner.selected ? ImVec4(0.757f, 0.404f, 0.216f, 1.0f) : ImVec4(0.75f, 0.75f, 0.75f, 1.0f)); + imgui.set_next_window_pos(x, y, ImGuiCond_Always, 0.5f, 0.5f); + imgui.begin(owner.title, ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); + ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); + float win_w = ImGui::GetWindowWidth(); + float label_len = imgui.calc_text_size(owner.label).x; + ImGui::SetCursorPosX(0.5f * (win_w - label_len)); + ImGui::AlignTextToFramePadding(); + imgui.text(owner.label); + + if (!owner.print_order.empty()) { + ImGui::Separator(); + float po_len = imgui.calc_text_size(owner.print_order).x; + ImGui::SetCursorPosX(0.5f * (win_w - po_len)); + ImGui::AlignTextToFramePadding(); + imgui.text(owner.print_order); + } + + // force re-render while the windows gets to its final size (it takes several frames) + if (ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x) + imgui.set_requires_extra_frame(); + + imgui.end(); + ImGui::PopStyleColor(); + ImGui::PopStyleVar(2); + } +} + +void GLCanvas3D::Tooltip::set_text(const std::string& text) +{ + // If the mouse is inside an ImGUI dialog, then the tooltip is suppressed. + m_text = m_in_imgui ? std::string() : text; +} + +void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas) +{ + static ImVec2 size(0.0f, 0.0f); + + auto validate_position = [](const Vec2d& position, const GLCanvas3D& canvas, const ImVec2& wnd_size) { + auto calc_cursor_height = []() { + float ret = 16.0f; +#ifdef _WIN32 + // see: https://forums.codeguru.com/showthread.php?449040-get-the-system-current-cursor-size + // this code is not perfect because it returns a maximum height equal to 31 even if the cursor bitmap shown on screen is bigger + // but at least it gives the same result as wxWidgets in the settings tabs + ICONINFO ii; + if (::GetIconInfo((HICON)GetCursor(), &ii) != 0) { + BITMAP bitmap; + ::GetObject(ii.hbmMask, sizeof(BITMAP), &bitmap); + int width = bitmap.bmWidth; + int height = (ii.hbmColor == nullptr) ? bitmap.bmHeight / 2 : bitmap.bmHeight; + HDC dc = ::CreateCompatibleDC(nullptr); + if (dc != nullptr) { + if (::SelectObject(dc, ii.hbmMask) != nullptr) { + for (int i = 0; i < width; ++i) { + for (int j = 0; j < height; ++j) { + if (::GetPixel(dc, i, j) != RGB(255, 255, 255)) { + if (ret < float(j)) + ret = float(j); + } + } + } + ::DeleteDC(dc); + } + } + ::DeleteObject(ii.hbmColor); + ::DeleteObject(ii.hbmMask); + } +#endif // _WIN32 + return ret; + }; + + const Size cnv_size = canvas.get_canvas_size(); + const float x = std::clamp(float(position.x()), 0.0f, float(cnv_size.get_width()) - wnd_size.x); + const float y = std::clamp(float(position.y()) + calc_cursor_height(), 0.0f, float(cnv_size.get_height()) - wnd_size.y); + return Vec2f(x, y); + }; + + if (m_text.empty()) { + m_start_time = std::chrono::steady_clock::now(); + return; + } + + // draw the tooltip as hidden until the delay is expired + // use a value of alpha slightly different from 0.0f because newer imgui does not calculate properly the window size if alpha == 0.0f + const float alpha = (std::chrono::duration_cast(std::chrono::steady_clock::now() - m_start_time).count() < 500) ? 0.01f : 1.0f; + + const Vec2f position = validate_position(mouse_position, canvas, size); + + ImGuiWrapper& imgui = *wxGetApp().imgui(); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha); + imgui.set_next_window_pos(position.x(), position.y(), ImGuiCond_Always, 0.0f, 0.0f); + + imgui.begin(wxString("canvas_tooltip"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoFocusOnAppearing); + ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); + ImGui::TextUnformatted(m_text.c_str()); + + // force re-render while the windows gets to its final size (it may take several frames) or while hidden + if (alpha < 1.0f || ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x) + imgui.set_requires_extra_frame(); + + size = ImGui::GetWindowSize(); + + imgui.end(); + ImGui::PopStyleVar(2); +} + +void GLCanvas3D::SequentialPrintClearance::set_polygons(const Polygons& polygons) +{ + m_perimeter.reset(); + m_fill.reset(); + if (polygons.empty()) + return; + +#if !ENABLE_LEGACY_OPENGL_REMOVAL + size_t triangles_count = 0; + for (const Polygon& poly : polygons) { + triangles_count += poly.points.size() - 2; + } + const size_t vertices_count = 3 * triangles_count; +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + + if (m_render_fill) { + GLModel::Geometry fill_data; +#if ENABLE_LEGACY_OPENGL_REMOVAL + fill_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; + fill_data.color = { 0.3333f, 0.0f, 0.0f, 0.5f }; + + // vertices + indices + const ExPolygons polygons_union = union_ex(polygons); + unsigned int vertices_counter = 0; + for (const ExPolygon& poly : polygons_union) { + const std::vector triangulation = triangulate_expolygon_3d(poly); + fill_data.reserve_vertices(fill_data.vertices_count() + triangulation.size()); + fill_data.reserve_indices(fill_data.indices_count() + triangulation.size()); + for (const Vec3d& v : triangulation) { + fill_data.add_vertex((Vec3f)(v.cast() + 0.0125f * Vec3f::UnitZ())); // add a small positive z to avoid z-fighting + ++vertices_counter; + if (vertices_counter % 3 == 0) + fill_data.add_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1); + } + } + + m_fill.init_from(std::move(fill_data)); +#else + GLModel::Geometry::Entity entity; + entity.type = GLModel::EPrimitiveType::Triangles; + entity.color = { 0.3333f, 0.0f, 0.0f, 0.5f }; + entity.positions.reserve(vertices_count); + entity.normals.reserve(vertices_count); + entity.indices.reserve(vertices_count); + + const ExPolygons polygons_union = union_ex(polygons); + for (const ExPolygon& poly : polygons_union) { + const std::vector triangulation = triangulate_expolygon_3d(poly); + for (const Vec3d& v : triangulation) { + entity.positions.emplace_back(v.cast() + Vec3f(0.0f, 0.0f, 0.0125f)); // add a small positive z to avoid z-fighting + entity.normals.emplace_back(Vec3f::UnitZ()); + const size_t positions_count = entity.positions.size(); + if (positions_count % 3 == 0) { + entity.indices.emplace_back(positions_count - 3); + entity.indices.emplace_back(positions_count - 2); + entity.indices.emplace_back(positions_count - 1); + } + } + } + + fill_data.entities.emplace_back(entity); + m_fill.init_from(fill_data); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_perimeter.init_from(polygons, 0.025f); // add a small positive z to avoid z-fighting +#else + GLModel::Geometry perimeter_data; + for (const Polygon& poly : polygons) { + GLModel::Geometry::Entity ent; + ent.type = GLModel::EPrimitiveType::LineLoop; + ent.positions.reserve(poly.points.size()); + ent.indices.reserve(poly.points.size()); + unsigned int id_count = 0; + for (const Point& p : poly.points) { + ent.positions.emplace_back(unscale(p.x()), unscale(p.y()), 0.025f); // add a small positive z to avoid z-fighting + ent.normals.emplace_back(Vec3f::UnitZ()); + ent.indices.emplace_back(id_count++); + } + + perimeter_data.entities.emplace_back(ent); + } + + m_perimeter.init_from(perimeter_data); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} + +void GLCanvas3D::SequentialPrintClearance::render() +{ + const ColorRGBA FILL_COLOR = { 1.0f, 0.0f, 0.0f, 0.5f }; + const ColorRGBA NO_FILL_COLOR = { 1.0f, 1.0f, 1.0f, 0.75f }; + +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader("flat"); +#else + GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + if (shader == nullptr) + return; + + shader->start_using(); + +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glDisable(GL_CULL_FACE)); + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_perimeter.set_color(m_render_fill ? FILL_COLOR : NO_FILL_COLOR); +#else + m_perimeter.set_color(-1, m_render_fill ? FILL_COLOR : NO_FILL_COLOR); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + m_perimeter.render(); + m_fill.render(); + + glsafe(::glDisable(GL_BLEND)); + glsafe(::glEnable(GL_CULL_FACE)); + glsafe(::glDisable(GL_DEPTH_TEST)); + + shader->stop_using(); +} + +wxDEFINE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, RBtnEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_SELECT_ALL, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_QUESTION_MARK, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event); +wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_ROTATED, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_SCALED, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_FORCE_UPDATE, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_WIPETOWER_MOVED, Vec3dEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3dEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, Event); +wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_GEOMETRY, Vec3dsEvent<2>); +wxDEFINE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_STARTED, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_MOVE_SLIDERS, wxKeyEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_JUMP_TO, wxKeyEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_COLLAPSE_SIDEBAR, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, Event); +wxDEFINE_EVENT(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, HeightProfileSmoothEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_RELOAD_FROM_DISK, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_RENDER_TIMER, wxTimerEvent/*RenderTimerEvent*/); +wxDEFINE_EVENT(EVT_GLCANVAS_TOOLBAR_HIGHLIGHTER_TIMER, wxTimerEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_GIZMO_HIGHLIGHTER_TIMER, wxTimerEvent); + +const double GLCanvas3D::DefaultCameraZoomToBoxMarginFactor = 1.25; + +void GLCanvas3D::load_arrange_settings() +{ + std::string dist_fff_str = + wxGetApp().app_config->get("arrange", "min_object_distance_fff"); + + std::string dist_fff_seq_print_str = + wxGetApp().app_config->get("arrange", "min_object_distance_fff_seq_print"); + + std::string dist_sla_str = + wxGetApp().app_config->get("arrange", "min_object_distance_sla"); + + std::string en_rot_fff_str = + wxGetApp().app_config->get("arrange", "enable_rotation_fff"); + + std::string en_rot_fff_seqp_str = + wxGetApp().app_config->get("arrange", "enable_rotation_fff_seq_print"); + + std::string en_rot_sla_str = + wxGetApp().app_config->get("arrange", "enable_rotation_sla"); + + if (!dist_fff_str.empty()) + m_arrange_settings_fff.distance = std::stof(dist_fff_str); + + if (!dist_fff_seq_print_str.empty()) + m_arrange_settings_fff_seq_print.distance = std::stof(dist_fff_seq_print_str); + + if (!dist_sla_str.empty()) + m_arrange_settings_sla.distance = std::stof(dist_sla_str); + + if (!en_rot_fff_str.empty()) + m_arrange_settings_fff.enable_rotation = (en_rot_fff_str == "1" || en_rot_fff_str == "yes"); + + if (!en_rot_fff_seqp_str.empty()) + m_arrange_settings_fff_seq_print.enable_rotation = (en_rot_fff_seqp_str == "1" || en_rot_fff_seqp_str == "yes"); + + if (!en_rot_sla_str.empty()) + m_arrange_settings_sla.enable_rotation = (en_rot_sla_str == "1" || en_rot_sla_str == "yes"); +} + +PrinterTechnology GLCanvas3D::current_printer_technology() const +{ + return m_process->current_printer_technology(); +} + +GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed) + : m_canvas(canvas) + , m_context(nullptr) + , m_bed(bed) +#if ENABLE_RETINA_GL + , m_retina_helper(nullptr) +#endif + , m_in_render(false) + , m_main_toolbar(GLToolbar::Normal, "Main") + , m_undoredo_toolbar(GLToolbar::Normal, "Undo_Redo") + , m_gizmos(*this) + , m_use_clipping_planes(false) + , m_sidebar_field("") + , m_extra_frame_requested(false) + , m_config(nullptr) + , m_process(nullptr) + , m_model(nullptr) + , m_dirty(true) + , m_initialized(false) + , m_apply_zoom_to_volumes_filter(false) + , m_picking_enabled(false) + , m_moving_enabled(false) + , m_dynamic_background_enabled(false) + , m_multisample_allowed(false) + , m_moving(false) + , m_tab_down(false) + , m_cursor_type(Standard) + , m_reload_delayed(false) +#if ENABLE_RENDER_PICKING_PASS + , m_show_picking_texture(false) +#endif // ENABLE_RENDER_PICKING_PASS + , m_render_sla_auxiliaries(true) + , m_labels(*this) + , m_slope(m_volumes) +{ + if (m_canvas != nullptr) { + m_timer.SetOwner(m_canvas); + m_render_timer.SetOwner(m_canvas); +#if ENABLE_RETINA_GL + m_retina_helper.reset(new RetinaHelper(canvas)); +#endif // ENABLE_RETINA_GL + } + + load_arrange_settings(); + + m_selection.set_volumes(&m_volumes.volumes); +} + +GLCanvas3D::~GLCanvas3D() +{ + reset_volumes(); +} + +void GLCanvas3D::post_event(wxEvent &&event) +{ + event.SetEventObject(m_canvas); + wxPostEvent(m_canvas, event); +} + +bool GLCanvas3D::init() +{ + if (m_initialized) + return true; + + if (m_canvas == nullptr || m_context == nullptr) + return false; + + glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 1.0f)); + glsafe(::glClearDepth(1.0f)); + + glsafe(::glDepthFunc(GL_LESS)); + + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glEnable(GL_CULL_FACE)); + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + +#if !ENABLE_LEGACY_OPENGL_REMOVAL + // Set antialiasing / multisampling + glsafe(::glDisable(GL_LINE_SMOOTH)); + glsafe(::glDisable(GL_POLYGON_SMOOTH)); + + // ambient lighting + GLfloat ambient[4] = { 0.3f, 0.3f, 0.3f, 1.0f }; + glsafe(::glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient)); + + glsafe(::glEnable(GL_LIGHT0)); + glsafe(::glEnable(GL_LIGHT1)); + + // light from camera + GLfloat specular_cam[4] = { 0.3f, 0.3f, 0.3f, 1.0f }; + glsafe(::glLightfv(GL_LIGHT1, GL_SPECULAR, specular_cam)); + GLfloat diffuse_cam[4] = { 0.2f, 0.2f, 0.2f, 1.0f }; + glsafe(::glLightfv(GL_LIGHT1, GL_DIFFUSE, diffuse_cam)); + + // light from above + GLfloat specular_top[4] = { 0.2f, 0.2f, 0.2f, 1.0f }; + glsafe(::glLightfv(GL_LIGHT0, GL_SPECULAR, specular_top)); + GLfloat diffuse_top[4] = { 0.5f, 0.5f, 0.5f, 1.0f }; + glsafe(::glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse_top)); + + // Enables Smooth Color Shading; try GL_FLAT for (lack of) fun. + glsafe(::glShadeModel(GL_SMOOTH)); + + // A handy trick -- have surface material mirror the color. + glsafe(::glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)); + glsafe(::glEnable(GL_COLOR_MATERIAL)); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + + if (m_multisample_allowed) + glsafe(::glEnable(GL_MULTISAMPLE)); + + if (m_main_toolbar.is_enabled()) + m_layers_editing.init(); + +#if !ENABLE_LEGACY_OPENGL_REMOVAL + // on linux the gl context is not valid until the canvas is not shown on screen + // we defer the geometry finalization of volumes until the first call to render() + m_volumes.finalize_geometry(true); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + + if (m_gizmos.is_enabled() && !m_gizmos.init()) + std::cout << "Unable to initialize gizmos: please, check that all the required textures are available" << std::endl; + + if (!_init_toolbars()) + return false; + + if (m_selection.is_enabled() && !m_selection.init()) + return false; + + m_initialized = true; + + return true; +} + +void GLCanvas3D::set_as_dirty() +{ + m_dirty = true; +} + +unsigned int GLCanvas3D::get_volumes_count() const +{ + return (unsigned int)m_volumes.volumes.size(); +} + +void GLCanvas3D::reset_volumes() +{ + if (!m_initialized) + return; + + if (m_volumes.empty()) + return; + + _set_current(); + + m_selection.clear(); + m_volumes.clear(); + m_dirty = true; + + _set_warning_notification(EWarning::ObjectOutside, false); +} + +ModelInstanceEPrintVolumeState GLCanvas3D::check_volumes_outside_state() const +{ + ModelInstanceEPrintVolumeState state = ModelInstanceEPrintVolumeState::ModelInstancePVS_Inside; + if (m_initialized) + m_volumes.check_outside_state(m_bed.build_volume(), &state); + return state; +} + +void GLCanvas3D::toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo, int instance_idx) +{ +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + if (current_printer_technology() != ptSLA) + return; +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + + m_render_sla_auxiliaries = visible; + + for (GLVolume* vol : m_volumes.volumes) { +#if !ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + if (vol->composite_id.object_id == 1000) + continue; // the wipe tower +#endif // !ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo) + && (instance_idx == -1 || vol->composite_id.instance_id == instance_idx) + && vol->composite_id.volume_id < 0) + vol->is_active = visible; + } +} + +void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject* mo, int instance_idx, const ModelVolume* mv) +{ + for (GLVolume* vol : m_volumes.volumes) { +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + if (vol->is_wipe_tower) + vol->is_active = (visible && mo == nullptr); +#else + if (vol->composite_id.object_id == 1000) { // wipe tower + vol->is_active = (visible && mo == nullptr); + } +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + else { + if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo) + && (instance_idx == -1 || vol->composite_id.instance_id == instance_idx) + && (mv == nullptr || m_model->objects[vol->composite_id.object_id]->volumes[vol->composite_id.volume_id] == mv)) { + vol->is_active = visible; + + if (instance_idx == -1) { + vol->force_native_color = false; + vol->force_neutral_color = false; + } else { + const GLGizmosManager& gm = get_gizmos_manager(); + auto gizmo_type = gm.get_current_type(); + if ( (gizmo_type == GLGizmosManager::FdmSupports + || gizmo_type == GLGizmosManager::Seam) + && ! vol->is_modifier) + vol->force_neutral_color = true; + else if (gizmo_type == GLGizmosManager::MmuSegmentation) + vol->is_active = false; + else + vol->force_native_color = true; + } + } + } + } + + if (visible && !mo) + toggle_sla_auxiliaries_visibility(true, mo, instance_idx); + + if (!mo && !visible && !m_model->objects.empty() && (m_model->objects.size() > 1 || m_model->objects.front()->instances.size() > 1)) + _set_warning_notification(EWarning::SomethingNotShown, true); + + if (!mo && visible) + _set_warning_notification(EWarning::SomethingNotShown, false); +} + +void GLCanvas3D::update_instance_printable_state_for_object(const size_t obj_idx) +{ + ModelObject* model_object = m_model->objects[obj_idx]; + for (int inst_idx = 0; inst_idx < (int)model_object->instances.size(); ++inst_idx) { + ModelInstance* instance = model_object->instances[inst_idx]; + + for (GLVolume* volume : m_volumes.volumes) { + if (volume->object_idx() == (int)obj_idx && volume->instance_idx() == inst_idx) + volume->printable = instance->printable; + } + } +} + +void GLCanvas3D::update_instance_printable_state_for_objects(const std::vector& object_idxs) +{ + for (size_t obj_idx : object_idxs) + update_instance_printable_state_for_object(obj_idx); +} + +void GLCanvas3D::set_config(const DynamicPrintConfig* config) +{ + m_config = config; + m_layers_editing.set_config(config); +} + +void GLCanvas3D::set_process(BackgroundSlicingProcess *process) +{ + m_process = process; +} + +void GLCanvas3D::set_model(Model* model) +{ + m_model = model; + m_selection.set_model(m_model); +} + +void GLCanvas3D::bed_shape_changed() +{ + refresh_camera_scene_box(); + wxGetApp().plater()->get_camera().requires_zoom_to_bed = true; + m_dirty = true; +} + +void GLCanvas3D::refresh_camera_scene_box() +{ + wxGetApp().plater()->get_camera().set_scene_box(scene_bounding_box()); +} + +BoundingBoxf3 GLCanvas3D::volumes_bounding_box() const +{ + BoundingBoxf3 bb; + for (const GLVolume* volume : m_volumes.volumes) { + if (!m_apply_zoom_to_volumes_filter || ((volume != nullptr) && volume->zoom_to_volumes)) + bb.merge(volume->transformed_bounding_box()); + } + return bb; +} + +BoundingBoxf3 GLCanvas3D::scene_bounding_box() const +{ + BoundingBoxf3 bb = volumes_bounding_box(); + bb.merge(m_bed.extended_bounding_box()); + double h = m_bed.build_volume().max_print_height(); + //FIXME why -h? + bb.min.z() = std::min(bb.min.z(), -h); + bb.max.z() = std::max(bb.max.z(), h); + return bb; +} + +bool GLCanvas3D::is_layers_editing_enabled() const +{ + return m_layers_editing.is_enabled(); +} + +bool GLCanvas3D::is_layers_editing_allowed() const +{ + return m_layers_editing.is_allowed(); +} + +void GLCanvas3D::reset_layer_height_profile() +{ + wxGetApp().plater()->take_snapshot(_L("Variable layer height - Reset")); + m_layers_editing.reset_layer_height_profile(*this); + m_layers_editing.state = LayersEditing::Completed; + m_dirty = true; +} + +void GLCanvas3D::adaptive_layer_height_profile(float quality_factor) +{ + wxGetApp().plater()->take_snapshot(_L("Variable layer height - Adaptive")); + m_layers_editing.adaptive_layer_height_profile(*this, quality_factor); + m_layers_editing.state = LayersEditing::Completed; + m_dirty = true; +} + +void GLCanvas3D::smooth_layer_height_profile(const HeightProfileSmoothingParams& smoothing_params) +{ + wxGetApp().plater()->take_snapshot(_L("Variable layer height - Smooth all")); + m_layers_editing.smooth_layer_height_profile(*this, smoothing_params); + m_layers_editing.state = LayersEditing::Completed; + m_dirty = true; +} + +bool GLCanvas3D::is_reload_delayed() const +{ + return m_reload_delayed; +} + +void GLCanvas3D::enable_layers_editing(bool enable) +{ + m_layers_editing.set_enabled(enable); + set_as_dirty(); +} + +void GLCanvas3D::enable_legend_texture(bool enable) +{ + m_gcode_viewer.enable_legend(enable); +} + +void GLCanvas3D::enable_picking(bool enable) +{ + m_picking_enabled = enable; + m_selection.set_mode(Selection::Instance); +} + +void GLCanvas3D::enable_moving(bool enable) +{ + m_moving_enabled = enable; +} + +void GLCanvas3D::enable_gizmos(bool enable) +{ + m_gizmos.set_enabled(enable); +} + +void GLCanvas3D::enable_selection(bool enable) +{ + m_selection.set_enabled(enable); +} + +void GLCanvas3D::enable_main_toolbar(bool enable) +{ + m_main_toolbar.set_enabled(enable); +} + +void GLCanvas3D::enable_undoredo_toolbar(bool enable) +{ + m_undoredo_toolbar.set_enabled(enable); +} + +void GLCanvas3D::enable_dynamic_background(bool enable) +{ + m_dynamic_background_enabled = enable; +} + +void GLCanvas3D::allow_multisample(bool allow) +{ + m_multisample_allowed = allow; +} + +void GLCanvas3D::zoom_to_bed() +{ + BoundingBoxf3 box = m_bed.build_volume().bounding_volume(); + box.min.z() = 0.0; + box.max.z() = 0.0; + _zoom_to_box(box); +} + +void GLCanvas3D::zoom_to_volumes() +{ + m_apply_zoom_to_volumes_filter = true; + _zoom_to_box(volumes_bounding_box()); + m_apply_zoom_to_volumes_filter = false; +} + +void GLCanvas3D::zoom_to_selection() +{ + if (!m_selection.is_empty()) + _zoom_to_box(m_selection.get_bounding_box()); +} + +void GLCanvas3D::zoom_to_gcode() +{ + _zoom_to_box(m_gcode_viewer.get_paths_bounding_box(), 1.05); +} + +void GLCanvas3D::select_view(const std::string& direction) +{ + wxGetApp().plater()->get_camera().select_view(direction); + if (m_canvas != nullptr) + m_canvas->Refresh(); +} + +void GLCanvas3D::update_volumes_colors_by_extruder() +{ + if (m_config != nullptr) + m_volumes.update_colors_by_extruder(m_config); +} + +void GLCanvas3D::render() +{ + if (m_in_render) { + // if called recursively, return + m_dirty = true; + return; + } + + m_in_render = true; + Slic3r::ScopeGuard in_render_guard([this]() { m_in_render = false; }); + (void)in_render_guard; + + if (m_canvas == nullptr) + return; + + // ensures this canvas is current and initialized + if (!_is_shown_on_screen() || !_set_current() || !wxGetApp().init_opengl()) + return; + + if (!is_initialized() && !init()) + return; + + if (!m_main_toolbar.is_enabled()) + m_gcode_viewer.init(); + + if (! m_bed.build_volume().valid()) { + // this happens at startup when no data is still saved under <>\AppData\Roaming\Slic3rPE + post_event(SimpleEvent(EVT_GLCANVAS_UPDATE_BED_SHAPE)); + return; + } + +#if ENABLE_ENVIRONMENT_MAP + if (wxGetApp().is_editor()) + wxGetApp().plater()->init_environment_texture(); +#endif // ENABLE_ENVIRONMENT_MAP + +#if ENABLE_GLMODEL_STATISTICS + GLModel::reset_statistics_counters(); +#endif // ENABLE_GLMODEL_STATISTICS + + const Size& cnv_size = get_canvas_size(); + // Probably due to different order of events on Linux/GTK2, when one switched from 3D scene + // to preview, this was called before canvas had its final size. It reported zero width + // and the viewport was set incorrectly, leading to tripping glAsserts further down + // the road (in apply_projection). That's why the minimum size is forced to 10. + Camera& camera = wxGetApp().plater()->get_camera(); + camera.apply_viewport(0, 0, std::max(10u, (unsigned int)cnv_size.get_width()), std::max(10u, (unsigned int)cnv_size.get_height())); + + if (camera.requires_zoom_to_bed) { + zoom_to_bed(); + _resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height()); + camera.requires_zoom_to_bed = false; + } + +#if !ENABLE_LEGACY_OPENGL_REMOVAL + camera.apply_view_matrix(); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + camera.apply_projection(_max_bounding_box(true, true)); + +#if !ENABLE_LEGACY_OPENGL_REMOVAL + GLfloat position_cam[4] = { 1.0f, 0.0f, 1.0f, 0.0f }; + glsafe(::glLightfv(GL_LIGHT1, GL_POSITION, position_cam)); + GLfloat position_top[4] = { -0.5f, -0.5f, 1.0f, 0.0f }; + glsafe(::glLightfv(GL_LIGHT0, GL_POSITION, position_top)); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + + wxGetApp().imgui()->new_frame(); + + if (m_picking_enabled) { +#if ENABLE_NEW_RECTANGLE_SELECTION + if (m_rectangle_selection.is_dragging() && !m_rectangle_selection.is_empty()) +#else + if (m_rectangle_selection.is_dragging()) +#endif // ENABLE_NEW_RECTANGLE_SELECTION + // picking pass using rectangle selection + _rectangular_selection_picking_pass(); + else if (!m_volumes.empty()) + // regular picking pass + _picking_pass(); + } + +#if ENABLE_RENDER_PICKING_PASS + if (!m_picking_enabled || !m_show_picking_texture) { +#endif // ENABLE_RENDER_PICKING_PASS + + const bool is_looking_downward = camera.is_looking_downward(); + + // draw scene + glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); + _render_background(); + + _render_objects(GLVolumeCollection::ERenderType::Opaque); + if (!m_main_toolbar.is_enabled()) + _render_gcode(); + _render_sla_slices(); + _render_selection(); + if (is_looking_downward) +#if ENABLE_GL_SHADERS_ATTRIBUTES + _render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), false, true); +#else + _render_bed(false, true); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + _render_objects(GLVolumeCollection::ERenderType::Transparent); + + _render_sequential_clearance(); +#if ENABLE_RENDER_SELECTION_CENTER + _render_selection_center(); +#endif // ENABLE_RENDER_SELECTION_CENTER +#if ENABLE_SHOW_TOOLPATHS_COG + if (!m_main_toolbar.is_enabled()) + _render_gcode_cog(); +#endif // ENABLE_SHOW_TOOLPATHS_COG + + // we need to set the mouse's scene position here because the depth buffer + // could be invalidated by the following gizmo render methods + // this position is used later into on_mouse() to drag the objects + if (m_picking_enabled) + m_mouse.scene_position = _mouse_to_3d(m_mouse.position.cast()); + + // sidebar hints need to be rendered before the gizmos because the depth buffer + // could be invalidated by the following gizmo render methods + _render_selection_sidebar_hints(); + _render_current_gizmo(); + if (!is_looking_downward) +#if ENABLE_GL_SHADERS_ATTRIBUTES + _render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), true, true); +#else + _render_bed(true, true); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES +#if ENABLE_RENDER_PICKING_PASS + } +#endif // ENABLE_RENDER_PICKING_PASS + +#if ENABLE_SHOW_CAMERA_TARGET + _render_camera_target(); +#endif // ENABLE_SHOW_CAMERA_TARGET + + if (m_picking_enabled && m_rectangle_selection.is_dragging()) + m_rectangle_selection.render(*this); + + // draw overlays + _render_overlays(); + + if (wxGetApp().plater()->is_render_statistic_dialog_visible()) { + ImGuiWrapper& imgui = *wxGetApp().imgui(); + imgui.begin(std::string("Render statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + imgui.text("FPS (SwapBuffers() calls per second):"); + ImGui::SameLine(); + imgui.text(std::to_string(m_render_stats.get_fps_and_reset_if_needed())); + ImGui::Separator(); + imgui.text("Compressed textures:"); + ImGui::SameLine(); + imgui.text(OpenGLManager::are_compressed_textures_supported() ? "supported" : "not supported"); + imgui.text("Max texture size:"); + ImGui::SameLine(); + imgui.text(std::to_string(OpenGLManager::get_gl_info().get_max_tex_size())); + imgui.end(); + } + +#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW + if (wxGetApp().is_editor() && wxGetApp().plater()->is_view3D_shown()) + wxGetApp().plater()->render_project_state_debug_window(); +#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW + +#if ENABLE_CAMERA_STATISTICS + camera.debug_render(); +#endif // ENABLE_CAMERA_STATISTICS +#if ENABLE_GLMODEL_STATISTICS + GLModel::render_statistics(); +#endif // ENABLE_GLMODEL_STATISTICS + + std::string tooltip; + + // Negative coordinate means out of the window, likely because the window was deactivated. + // In that case the tooltip should be hidden. + if (m_mouse.position.x() >= 0. && m_mouse.position.y() >= 0.) { + if (tooltip.empty()) + tooltip = m_layers_editing.get_tooltip(*this); + + if (tooltip.empty()) + tooltip = m_gizmos.get_tooltip(); + + if (tooltip.empty()) + tooltip = m_main_toolbar.get_tooltip(); + + if (tooltip.empty()) + tooltip = m_undoredo_toolbar.get_tooltip(); + + if (tooltip.empty()) + tooltip = wxGetApp().plater()->get_collapse_toolbar().get_tooltip(); + + if (tooltip.empty()) + tooltip = wxGetApp().plater()->get_view_toolbar().get_tooltip(); + } + + set_tooltip(tooltip); + + if (m_tooltip_enabled) + m_tooltip.render(m_mouse.position, *this); + + wxGetApp().plater()->get_mouse3d_controller().render_settings_dialog(*this); + + wxGetApp().plater()->get_notification_manager()->render_notifications(*this, get_overlay_window_width()); + + wxGetApp().imgui()->render(); + + m_canvas->SwapBuffers(); + m_render_stats.increment_fps_counter(); +} + +void GLCanvas3D::render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, Camera::EType camera_type) +{ + render_thumbnail(thumbnail_data, w, h, thumbnail_params, m_volumes, camera_type); +} + +void GLCanvas3D::render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type) +{ + switch (OpenGLManager::get_framebuffers_type()) + { + case OpenGLManager::EFramebufferType::Arb: { _render_thumbnail_framebuffer(thumbnail_data, w, h, thumbnail_params, volumes, camera_type); break; } + case OpenGLManager::EFramebufferType::Ext: { _render_thumbnail_framebuffer_ext(thumbnail_data, w, h, thumbnail_params, volumes, camera_type); break; } + default: { _render_thumbnail_legacy(thumbnail_data, w, h, thumbnail_params, volumes, camera_type); break; } + } +} + +void GLCanvas3D::select_all() +{ + m_selection.add_all(); + m_dirty = true; +} + +void GLCanvas3D::deselect_all() +{ + m_selection.remove_all(); + wxGetApp().obj_manipul()->set_dirty(); + m_gizmos.reset_all_states(); + m_gizmos.update_data(); + post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); +} + +void GLCanvas3D::delete_selected() +{ + m_selection.erase(); +} + +void GLCanvas3D::ensure_on_bed(unsigned int object_idx, bool allow_negative_z) +{ + if (allow_negative_z) + return; + + typedef std::map, double> InstancesToZMap; + InstancesToZMap instances_min_z; + + for (GLVolume* volume : m_volumes.volumes) { + if (volume->object_idx() == (int)object_idx && !volume->is_modifier) { + double min_z = volume->transformed_convex_hull_bounding_box().min.z(); + std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); + InstancesToZMap::iterator it = instances_min_z.find(instance); + if (it == instances_min_z.end()) + it = instances_min_z.insert(InstancesToZMap::value_type(instance, DBL_MAX)).first; + + it->second = std::min(it->second, min_z); + } + } + + for (GLVolume* volume : m_volumes.volumes) { + std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); + InstancesToZMap::iterator it = instances_min_z.find(instance); + if (it != instances_min_z.end()) + volume->set_instance_offset(Z, volume->get_instance_offset(Z) - it->second); + } +} + + +const std::vector& GLCanvas3D::get_gcode_layers_zs() const +{ + return m_gcode_viewer.get_layers_zs(); +} + +std::vector GLCanvas3D::get_volumes_print_zs(bool active_only) const +{ + return m_volumes.get_current_print_zs(active_only); +} + +void GLCanvas3D::set_gcode_options_visibility_from_flags(unsigned int flags) +{ + m_gcode_viewer.set_options_visibility_from_flags(flags); +} + +void GLCanvas3D::set_toolpath_role_visibility_flags(unsigned int flags) +{ + m_gcode_viewer.set_toolpath_role_visibility_flags(flags); +} + +void GLCanvas3D::set_toolpath_view_type(GCodeViewer::EViewType type) +{ + m_gcode_viewer.set_view_type(type); +} + +void GLCanvas3D::set_volumes_z_range(const std::array& range) +{ + m_volumes.set_range(range[0] - 1e-6, range[1] + 1e-6); +} + +void GLCanvas3D::set_toolpaths_z_range(const std::array& range) +{ + if (m_gcode_viewer.has_data()) + m_gcode_viewer.set_layers_z_range(range); +} + +std::vector GLCanvas3D::load_object(const ModelObject& model_object, int obj_idx, std::vector instance_idxs) +{ + if (instance_idxs.empty()) { + for (unsigned int i = 0; i < model_object.instances.size(); ++i) { + instance_idxs.emplace_back(i); + } + } +#if ENABLE_LEGACY_OPENGL_REMOVAL + return m_volumes.load_object(&model_object, obj_idx, instance_idxs); +#else + return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_initialized); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} + +std::vector GLCanvas3D::load_object(const Model& model, int obj_idx) +{ + if (0 <= obj_idx && obj_idx < (int)model.objects.size()) { + const ModelObject* model_object = model.objects[obj_idx]; + if (model_object != nullptr) + return load_object(*model_object, obj_idx, std::vector()); + } + + return std::vector(); +} + +void GLCanvas3D::mirror_selection(Axis axis) +{ + m_selection.mirror(axis); + do_mirror(L("Mirror Object")); + wxGetApp().obj_manipul()->set_dirty(); +} + +// Reload the 3D scene of +// 1) Model / ModelObjects / ModelInstances / ModelVolumes +// 2) Print bed +// 3) SLA support meshes for their respective ModelObjects / ModelInstances +// 4) Wipe tower preview +// 5) Out of bed collision status & message overlay (texture) +void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_refresh) +{ + if (m_canvas == nullptr || m_config == nullptr || m_model == nullptr) + return; + + if (!m_initialized) + return; + + _set_current(); + + m_hover_volume_idxs.clear(); + + struct ModelVolumeState { + ModelVolumeState(const GLVolume* volume) : + model_volume(nullptr), geometry_id(volume->geometry_id), volume_idx(-1) {} + ModelVolumeState(const ModelVolume* model_volume, const ObjectID& instance_id, const GLVolume::CompositeID& composite_id) : + model_volume(model_volume), geometry_id(std::make_pair(model_volume->id().id, instance_id.id)), composite_id(composite_id), volume_idx(-1) {} + ModelVolumeState(const ObjectID& volume_id, const ObjectID& instance_id) : + model_volume(nullptr), geometry_id(std::make_pair(volume_id.id, instance_id.id)), volume_idx(-1) {} + bool new_geometry() const { return this->volume_idx == size_t(-1); } + const ModelVolume* model_volume; + // ObjectID of ModelVolume + ObjectID of ModelInstance + // or timestamp of an SLAPrintObjectStep + ObjectID of ModelInstance + std::pair geometry_id; + GLVolume::CompositeID composite_id; + // Volume index in the new GLVolume vector. + size_t volume_idx; + }; + std::vector model_volume_state; + std::vector aux_volume_state; + + struct GLVolumeState { + GLVolumeState() : + volume_idx(size_t(-1)) {} + GLVolumeState(const GLVolume* volume, unsigned int volume_idx) : + composite_id(volume->composite_id), volume_idx(volume_idx) {} + GLVolumeState(const GLVolume::CompositeID &composite_id) : + composite_id(composite_id), volume_idx(size_t(-1)) {} + + GLVolume::CompositeID composite_id; + // Volume index in the old GLVolume vector. + size_t volume_idx; + }; + + // SLA steps to pull the preview meshes for. + typedef std::array SLASteps; + SLASteps sla_steps = { slaposDrillHoles, slaposSupportTree, slaposPad }; + struct SLASupportState { + std::array::value> step; + }; + // State of the sla_steps for all SLAPrintObjects. + std::vector sla_support_state; + + std::vector instance_ids_selected; + std::vector map_glvolume_old_to_new(m_volumes.volumes.size(), size_t(-1)); + std::vector deleted_volumes; + std::vector glvolumes_new; + glvolumes_new.reserve(m_volumes.volumes.size()); + auto model_volume_state_lower = [](const ModelVolumeState& m1, const ModelVolumeState& m2) { return m1.geometry_id < m2.geometry_id; }; + + m_reload_delayed = !m_canvas->IsShown() && !refresh_immediately && !force_full_scene_refresh; + + PrinterTechnology printer_technology = current_printer_technology(); + int volume_idx_wipe_tower_old = -1; + + // Release invalidated volumes to conserve GPU memory in case of delayed refresh (see m_reload_delayed). + // First initialize model_volumes_new_sorted & model_instances_new_sorted. + for (int object_idx = 0; object_idx < (int)m_model->objects.size(); ++object_idx) { + const ModelObject* model_object = m_model->objects[object_idx]; + for (int instance_idx = 0; instance_idx < (int)model_object->instances.size(); ++instance_idx) { + const ModelInstance* model_instance = model_object->instances[instance_idx]; + for (int volume_idx = 0; volume_idx < (int)model_object->volumes.size(); ++volume_idx) { + const ModelVolume* model_volume = model_object->volumes[volume_idx]; + model_volume_state.emplace_back(model_volume, model_instance->id(), GLVolume::CompositeID(object_idx, volume_idx, instance_idx)); + } + } + } + if (printer_technology == ptSLA) { + const SLAPrint* sla_print = this->sla_print(); +#ifndef NDEBUG + // Verify that the SLAPrint object is synchronized with m_model. + check_model_ids_equal(*m_model, sla_print->model()); +#endif /* NDEBUG */ + sla_support_state.reserve(sla_print->objects().size()); + for (const SLAPrintObject* print_object : sla_print->objects()) { + SLASupportState state; + for (size_t istep = 0; istep < sla_steps.size(); ++istep) { + state.step[istep] = print_object->step_state_with_timestamp(sla_steps[istep]); + if (state.step[istep].state == PrintStateBase::DONE) { + if (!print_object->has_mesh(sla_steps[istep])) + // Consider the DONE step without a valid mesh as invalid for the purpose + // of mesh visualization. + state.step[istep].state = PrintStateBase::INVALID; + else if (sla_steps[istep] != slaposDrillHoles) + for (const ModelInstance* model_instance : print_object->model_object()->instances) + // Only the instances, which are currently printable, will have the SLA support structures kept. + // The instances outside the print bed will have the GLVolumes of their support structures released. + if (model_instance->is_printable()) + aux_volume_state.emplace_back(state.step[istep].timestamp, model_instance->id()); + } + } + sla_support_state.emplace_back(state); + } + } + std::sort(model_volume_state.begin(), model_volume_state.end(), model_volume_state_lower); + std::sort(aux_volume_state.begin(), aux_volume_state.end(), model_volume_state_lower); + // Release all ModelVolume based GLVolumes not found in the current Model. Find the GLVolume of a hollowed mesh. + for (size_t volume_id = 0; volume_id < m_volumes.volumes.size(); ++volume_id) { + GLVolume* volume = m_volumes.volumes[volume_id]; + ModelVolumeState key(volume); + ModelVolumeState* mvs = nullptr; + if (volume->volume_idx() < 0) { + auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower); + if (it != aux_volume_state.end() && it->geometry_id == key.geometry_id) + // This can be an SLA support structure that should not be rendered (in case someone used undo + // to revert to before it was generated). We only reuse the volume if that's not the case. + if (m_model->objects[volume->composite_id.object_id]->sla_points_status != sla::PointsStatus::NoPoints) + mvs = &(*it); + } + else { + auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower); + if (it != model_volume_state.end() && it->geometry_id == key.geometry_id) + mvs = &(*it); + } + // Emplace instance ID of the volume. Both the aux volumes and model volumes share the same instance ID. + // The wipe tower has its own wipe_tower_instance_id(). + if (m_selection.contains_volume(volume_id)) + instance_ids_selected.emplace_back(volume->geometry_id.second); + if (mvs == nullptr || force_full_scene_refresh) { + // This GLVolume will be released. + if (volume->is_wipe_tower) { + // There is only one wipe tower. + assert(volume_idx_wipe_tower_old == -1); + volume_idx_wipe_tower_old = (int)volume_id; + } + if (!m_reload_delayed) { + deleted_volumes.emplace_back(volume, volume_id); + delete volume; + } + } + else { + // This GLVolume will be reused. + volume->set_sla_shift_z(0.0); + map_glvolume_old_to_new[volume_id] = glvolumes_new.size(); + mvs->volume_idx = glvolumes_new.size(); + glvolumes_new.emplace_back(volume); + // Update color of the volume based on the current extruder. + if (mvs->model_volume != nullptr) { + int extruder_id = mvs->model_volume->extruder_id(); + if (extruder_id != -1) + volume->extruder_id = extruder_id; + + volume->is_modifier = !mvs->model_volume->is_model_part(); + volume->set_color(color_from_model_volume(*mvs->model_volume)); + // force update of render_color alpha channel + volume->set_render_color(volume->color.is_transparent()); + + // updates volumes transformations + volume->set_instance_transformation(mvs->model_volume->get_object()->instances[mvs->composite_id.instance_id]->get_transformation()); + volume->set_volume_transformation(mvs->model_volume->get_transformation()); + + // updates volumes convex hull + if (mvs->model_volume->is_model_part() && ! volume->convex_hull()) + // Model volume was likely changed from modifier or support blocker / enforcer to a model part. + // Only model parts require convex hulls. + volume->set_convex_hull(mvs->model_volume->get_convex_hull_shared_ptr()); + } + } + } + sort_remove_duplicates(instance_ids_selected); + auto deleted_volumes_lower = [](const GLVolumeState &v1, const GLVolumeState &v2) { return v1.composite_id < v2.composite_id; }; + std::sort(deleted_volumes.begin(), deleted_volumes.end(), deleted_volumes_lower); + + if (m_reload_delayed) + return; + + bool update_object_list = false; + if (m_volumes.volumes != glvolumes_new) + update_object_list = true; + m_volumes.volumes = std::move(glvolumes_new); + for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++ obj_idx) { + const ModelObject &model_object = *m_model->objects[obj_idx]; + for (int volume_idx = 0; volume_idx < (int)model_object.volumes.size(); ++ volume_idx) { + const ModelVolume &model_volume = *model_object.volumes[volume_idx]; + for (int instance_idx = 0; instance_idx < (int)model_object.instances.size(); ++ instance_idx) { + const ModelInstance &model_instance = *model_object.instances[instance_idx]; + ModelVolumeState key(model_volume.id(), model_instance.id()); + auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower); + assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id); + if (it->new_geometry()) { + // New volume. + auto it_old_volume = std::lower_bound(deleted_volumes.begin(), deleted_volumes.end(), GLVolumeState(it->composite_id), deleted_volumes_lower); + if (it_old_volume != deleted_volumes.end() && it_old_volume->composite_id == it->composite_id) + // If a volume changed its ObjectID, but it reuses a GLVolume's CompositeID, maintain its selection. + map_glvolume_old_to_new[it_old_volume->volume_idx] = m_volumes.volumes.size(); + // Note the index of the loaded volume, so that we can reload the main model GLVolume with the hollowed mesh + // later in this function. + it->volume_idx = m_volumes.volumes.size(); +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx); +#else + m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, m_initialized); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + m_volumes.volumes.back()->geometry_id = key.geometry_id; + update_object_list = true; + } else { + // Recycling an old GLVolume. + GLVolume &existing_volume = *m_volumes.volumes[it->volume_idx]; + assert(existing_volume.geometry_id == key.geometry_id); + // Update the Object/Volume/Instance indices into the current Model. + if (existing_volume.composite_id != it->composite_id) { + existing_volume.composite_id = it->composite_id; + update_object_list = true; + } + } + } + } + } + if (printer_technology == ptSLA) { + size_t idx = 0; + const SLAPrint *sla_print = this->sla_print(); + std::vector shift_zs(m_model->objects.size(), 0); + double relative_correction_z = sla_print->relative_correction().z(); + if (relative_correction_z <= EPSILON) + relative_correction_z = 1.; + for (const SLAPrintObject *print_object : sla_print->objects()) { + SLASupportState &state = sla_support_state[idx ++]; + const ModelObject *model_object = print_object->model_object(); + // Find an index of the ModelObject + int object_idx; + // There may be new SLA volumes added to the scene for this print_object. + // Find the object index of this print_object in the Model::objects list. + auto it = std::find(sla_print->model().objects.begin(), sla_print->model().objects.end(), model_object); + assert(it != sla_print->model().objects.end()); + object_idx = it - sla_print->model().objects.begin(); + // Cache the Z offset to be applied to all volumes with this object_idx. + shift_zs[object_idx] = print_object->get_current_elevation() / relative_correction_z; + // Collect indices of this print_object's instances, for which the SLA support meshes are to be added to the scene. + // pairs of + std::vector> instances[std::tuple_size::value]; + for (size_t print_instance_idx = 0; print_instance_idx < print_object->instances().size(); ++ print_instance_idx) { + const SLAPrintObject::Instance &instance = print_object->instances()[print_instance_idx]; + // Find index of ModelInstance corresponding to this SLAPrintObject::Instance. + auto it = std::find_if(model_object->instances.begin(), model_object->instances.end(), + [&instance](const ModelInstance *mi) { return mi->id() == instance.instance_id; }); + assert(it != model_object->instances.end()); + int instance_idx = it - model_object->instances.begin(); + for (size_t istep = 0; istep < sla_steps.size(); ++ istep) + if (sla_steps[istep] == slaposDrillHoles) { + // Hollowing is a special case, where the mesh from the backend is being loaded into the 1st volume of an instance, + // not into its own GLVolume. + // There shall always be such a GLVolume allocated. + ModelVolumeState key(model_object->volumes.front()->id(), instance.instance_id); + auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower); + assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id); + assert(!it->new_geometry()); + GLVolume &volume = *m_volumes.volumes[it->volume_idx]; + if (! volume.offsets.empty() && state.step[istep].timestamp != volume.offsets.front()) { + // The backend either produced a new hollowed mesh, or it invalidated the one that the front end has seen. +#if ENABLE_LEGACY_OPENGL_REMOVAL + volume.model.reset(); +#else + volume.indexed_vertex_array.release_geometry(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + if (state.step[istep].state == PrintStateBase::DONE) { + TriangleMesh mesh = print_object->get_mesh(slaposDrillHoles); + assert(! mesh.empty()); + + // sla_trafo does not contain volume trafo. To get a mesh to create + // a new volume from, we have to apply vol trafo inverse separately. + const ModelObject& mo = *m_model->objects[volume.object_idx()]; + Transform3d trafo = sla_print->sla_trafo(mo) + * mo.volumes.front()->get_transformation().get_matrix(); + mesh.transform(trafo.inverse()); +#if ENABLE_SMOOTH_NORMALS +#if ENABLE_LEGACY_OPENGL_REMOVAL + volume.model.init_from(mesh, true); +#else + volume.indexed_vertex_array.load_mesh(mesh, true); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +#else +#if ENABLE_LEGACY_OPENGL_REMOVAL + volume.model.init_from(mesh); +#else + volume.indexed_vertex_array.load_mesh(mesh); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +#endif // ENABLE_SMOOTH_NORMALS + } + else { + // Reload the original volume. +#if ENABLE_SMOOTH_NORMALS +#if ENABLE_LEGACY_OPENGL_REMOVAL + volume.model.init_from(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh(), true); +#else + volume.indexed_vertex_array.load_mesh(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh(), true); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +#else +#if ENABLE_LEGACY_OPENGL_REMOVAL + volume.model.init_from(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh()); +#else + volume.indexed_vertex_array.load_mesh(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +#endif // ENABLE_SMOOTH_NORMALS + } +#if !ENABLE_LEGACY_OPENGL_REMOVAL + volume.finalize_geometry(true); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + } + //FIXME it is an ugly hack to write the timestamp into the "offsets" field to not have to add another member variable + // to the GLVolume. We should refactor GLVolume significantly, so that the GLVolume will not contain member variables + // of various concenrs (model vs. 3D print path). + volume.offsets = { state.step[istep].timestamp }; + } + else if (state.step[istep].state == PrintStateBase::DONE) { + // Check whether there is an existing auxiliary volume to be updated, or a new auxiliary volume to be created. + ModelVolumeState key(state.step[istep].timestamp, instance.instance_id.id); + auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower); + assert(it != aux_volume_state.end() && it->geometry_id == key.geometry_id); + if (it->new_geometry()) { + // This can be an SLA support structure that should not be rendered (in case someone used undo + // to revert to before it was generated). If that's the case, we should not generate anything. + if (model_object->sla_points_status != sla::PointsStatus::NoPoints) + instances[istep].emplace_back(std::pair(instance_idx, print_instance_idx)); + else + shift_zs[object_idx] = 0.; + } + else { + // Recycling an old GLVolume. Update the Object/Instance indices into the current Model. + m_volumes.volumes[it->volume_idx]->composite_id = GLVolume::CompositeID(object_idx, m_volumes.volumes[it->volume_idx]->volume_idx(), instance_idx); + m_volumes.volumes[it->volume_idx]->set_instance_transformation(model_object->instances[instance_idx]->get_transformation()); + } + } + } + + for (size_t istep = 0; istep < sla_steps.size(); ++istep) + if (!instances[istep].empty()) +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp); +#else + m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp, m_initialized); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + + // Shift-up all volumes of the object so that it has the right elevation with respect to the print bed + for (GLVolume* volume : m_volumes.volumes) + if (volume->object_idx() < (int)m_model->objects.size() && m_model->objects[volume->object_idx()]->instances[volume->instance_idx()]->is_printable()) + volume->set_sla_shift_z(shift_zs[volume->object_idx()]); + } + + if (printer_technology == ptFFF && m_config->has("nozzle_diameter")) { + // Should the wipe tower be visualized ? + unsigned int extruders_count = (unsigned int)dynamic_cast(m_config->option("nozzle_diameter"))->values.size(); + + bool wt = dynamic_cast(m_config->option("wipe_tower"))->value; + bool co = dynamic_cast(m_config->option("complete_objects"))->value; + + if (extruders_count > 1 && wt && !co) { + // Height of a print (Show at least a slab) + double height = std::max(m_model->bounding_box().max(2), 10.0); + + float x = dynamic_cast(m_config->option("wipe_tower_x"))->value; + float y = dynamic_cast(m_config->option("wipe_tower_y"))->value; + float w = dynamic_cast(m_config->option("wipe_tower_width"))->value; + float a = dynamic_cast(m_config->option("wipe_tower_rotation_angle"))->value; + + const Print *print = m_process->fff_print(); + + float depth = print->wipe_tower_data(extruders_count).depth; + float brim_width = print->wipe_tower_data(extruders_count).brim_width; + +#if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( + x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower), + brim_width); +#else + int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( + 1000, x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower), + brim_width); +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL +#else +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( + x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower), + brim_width, m_initialized); +#else + int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( + 1000, x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower), + brim_width, m_initialized); +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + if (volume_idx_wipe_tower_old != -1) + map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new; + } + } + + update_volumes_colors_by_extruder(); + // Update selection indices based on the old/new GLVolumeCollection. + if (m_selection.get_mode() == Selection::Instance) + m_selection.instances_changed(instance_ids_selected); + else + m_selection.volumes_changed(map_glvolume_old_to_new); + + m_gizmos.update_data(); + m_gizmos.refresh_on_off_state(); + + // Update the toolbar + if (update_object_list) + post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); + + // checks for geometry outside the print volume to render it accordingly + if (!m_volumes.empty()) { + ModelInstanceEPrintVolumeState state; + const bool contained_min_one = m_volumes.check_outside_state(m_bed.build_volume(), &state); + const bool partlyOut = (state == ModelInstanceEPrintVolumeState::ModelInstancePVS_Partly_Outside); + const bool fullyOut = (state == ModelInstanceEPrintVolumeState::ModelInstancePVS_Fully_Outside); + + _set_warning_notification(EWarning::ObjectClashed, partlyOut); + _set_warning_notification(EWarning::ObjectOutside, fullyOut); + if (printer_technology != ptSLA || !contained_min_one) + _set_warning_notification(EWarning::SlaSupportsOutside, false); + + post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, + contained_min_one && !m_model->objects.empty() && !partlyOut)); + } + else { + _set_warning_notification(EWarning::ObjectOutside, false); + _set_warning_notification(EWarning::ObjectClashed, false); + _set_warning_notification(EWarning::SlaSupportsOutside, false); + post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, false)); + } + + refresh_camera_scene_box(); + + if (m_selection.is_empty()) { + // If no object is selected, deactivate the active gizmo, if any + // Otherwise it may be shown after cleaning the scene (if it was active while the objects were deleted) + m_gizmos.reset_all_states(); + + // If no object is selected, reset the objects manipulator on the sidebar + // to force a reset of its cache + auto manip = wxGetApp().obj_manipul(); + if (manip != nullptr) + manip->set_dirty(); + } + + // and force this canvas to be redrawn. + m_dirty = true; +} + +#if !ENABLE_LEGACY_OPENGL_REMOVAL +static void reserve_new_volume_finalize_old_volume(GLVolume& vol_new, GLVolume& vol_old, bool gl_initialized, size_t prealloc_size = VERTEX_BUFFER_RESERVE_SIZE) +{ + // Assign the large pre-allocated buffers to the new GLVolume. + vol_new.indexed_vertex_array = std::move(vol_old.indexed_vertex_array); + // Copy the content back to the old GLVolume. + vol_old.indexed_vertex_array = vol_new.indexed_vertex_array; + // Clear the buffers, but keep them pre-allocated. + vol_new.indexed_vertex_array.clear(); + // Just make sure that clear did not clear the reserved memory. + // Reserving number of vertices (3x position + 3x color) + vol_new.indexed_vertex_array.reserve(prealloc_size / 6); + // Finalize the old geometry, possibly move data to the graphics card. + vol_old.finalize_geometry(gl_initialized); +} +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + +void GLCanvas3D::load_gcode_preview(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors) +{ +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_gcode_viewer.load(gcode_result, *this->fff_print()); +#else + m_gcode_viewer.load(gcode_result, *this->fff_print(), m_initialized); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + if (wxGetApp().is_editor()) { + m_gcode_viewer.update_shells_color_by_extruder(m_config); + _set_warning_notification_if_needed(EWarning::ToolpathOutside); + } + + m_gcode_viewer.refresh(gcode_result, str_tool_colors); + set_as_dirty(); + request_extra_frame(); +} + +#if ENABLE_PREVIEW_LAYOUT +void GLCanvas3D::refresh_gcode_preview_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) +{ + m_gcode_viewer.refresh_render_paths(keep_sequential_current_first, keep_sequential_current_last); + set_as_dirty(); + request_extra_frame(); +} +#else +void GLCanvas3D::refresh_gcode_preview_render_paths() +{ + m_gcode_viewer.refresh_render_paths(); + set_as_dirty(); + request_extra_frame(); +} +#endif // ENABLE_PREVIEW_LAYOUT + +void GLCanvas3D::load_sla_preview() +{ + const SLAPrint* print = sla_print(); + if (m_canvas != nullptr && print != nullptr) { + _set_current(); + // Release OpenGL data before generating new data. + reset_volumes(); + _load_sla_shells(); + _update_sla_shells_outside_state(); + _set_warning_notification_if_needed(EWarning::SlaSupportsOutside); + } +} + +void GLCanvas3D::load_preview(const std::vector& str_tool_colors, const std::vector& color_print_values) +{ + const Print *print = this->fff_print(); + if (print == nullptr) + return; + + _set_current(); + + // Release OpenGL data before generating new data. + this->reset_volumes(); + + const BuildVolume &build_volume = m_bed.build_volume(); + _load_print_toolpaths(build_volume); + _load_wipe_tower_toolpaths(build_volume, str_tool_colors); + for (const PrintObject* object : print->objects()) + _load_print_object_toolpaths(*object, build_volume, str_tool_colors, color_print_values); + + _set_warning_notification_if_needed(EWarning::ToolpathOutside); +} + +void GLCanvas3D::bind_event_handlers() +{ + if (m_canvas != nullptr) { + m_canvas->Bind(wxEVT_SIZE, &GLCanvas3D::on_size, this); + m_canvas->Bind(wxEVT_IDLE, &GLCanvas3D::on_idle, this); + m_canvas->Bind(wxEVT_CHAR, &GLCanvas3D::on_char, this); + m_canvas->Bind(wxEVT_KEY_DOWN, &GLCanvas3D::on_key, this); + m_canvas->Bind(wxEVT_KEY_UP, &GLCanvas3D::on_key, this); + m_canvas->Bind(wxEVT_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this); + m_canvas->Bind(wxEVT_TIMER, &GLCanvas3D::on_timer, this); + m_canvas->Bind(EVT_GLCANVAS_RENDER_TIMER, &GLCanvas3D::on_render_timer, this); + m_toolbar_highlighter.set_timer_owner(m_canvas, 0); + m_canvas->Bind(EVT_GLCANVAS_TOOLBAR_HIGHLIGHTER_TIMER, [this](wxTimerEvent&) { m_toolbar_highlighter.blink(); }); + m_gizmo_highlighter.set_timer_owner(m_canvas, 0); + m_canvas->Bind(EVT_GLCANVAS_GIZMO_HIGHLIGHTER_TIMER, [this](wxTimerEvent&) { m_gizmo_highlighter.blink(); }); + m_canvas->Bind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_LEFT_UP, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_MIDDLE_DOWN, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_MIDDLE_UP, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_RIGHT_DOWN, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_RIGHT_UP, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_MOTION, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_ENTER_WINDOW, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_LEAVE_WINDOW, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_LEFT_DCLICK, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_MIDDLE_DCLICK, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_RIGHT_DCLICK, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_PAINT, &GLCanvas3D::on_paint, this); + m_canvas->Bind(wxEVT_SET_FOCUS, &GLCanvas3D::on_set_focus, this); + + m_event_handlers_bound = true; + } +} + +void GLCanvas3D::unbind_event_handlers() +{ + if (m_canvas != nullptr && m_event_handlers_bound) { + m_canvas->Unbind(wxEVT_SIZE, &GLCanvas3D::on_size, this); + m_canvas->Unbind(wxEVT_IDLE, &GLCanvas3D::on_idle, this); + m_canvas->Unbind(wxEVT_CHAR, &GLCanvas3D::on_char, this); + m_canvas->Unbind(wxEVT_KEY_DOWN, &GLCanvas3D::on_key, this); + m_canvas->Unbind(wxEVT_KEY_UP, &GLCanvas3D::on_key, this); + m_canvas->Unbind(wxEVT_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this); + m_canvas->Unbind(wxEVT_TIMER, &GLCanvas3D::on_timer, this); + m_canvas->Unbind(EVT_GLCANVAS_RENDER_TIMER, &GLCanvas3D::on_render_timer, this); + m_canvas->Unbind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_LEFT_UP, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_MIDDLE_DOWN, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_MIDDLE_UP, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_RIGHT_DOWN, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_RIGHT_UP, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_MOTION, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_ENTER_WINDOW, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_LEAVE_WINDOW, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_LEFT_DCLICK, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_MIDDLE_DCLICK, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_RIGHT_DCLICK, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_PAINT, &GLCanvas3D::on_paint, this); + m_canvas->Unbind(wxEVT_SET_FOCUS, &GLCanvas3D::on_set_focus, this); + + m_event_handlers_bound = false; + } +} + +void GLCanvas3D::on_size(wxSizeEvent& evt) +{ + m_dirty = true; +} + +void GLCanvas3D::on_idle(wxIdleEvent& evt) +{ + if (!m_initialized) + return; + + m_dirty |= m_main_toolbar.update_items_state(); + m_dirty |= m_undoredo_toolbar.update_items_state(); + m_dirty |= wxGetApp().plater()->get_view_toolbar().update_items_state(); + m_dirty |= wxGetApp().plater()->get_collapse_toolbar().update_items_state(); + bool mouse3d_controller_applied = wxGetApp().plater()->get_mouse3d_controller().apply(wxGetApp().plater()->get_camera()); + m_dirty |= mouse3d_controller_applied; + m_dirty |= wxGetApp().plater()->get_notification_manager()->update_notifications(*this); + auto gizmo = wxGetApp().plater()->canvas3D()->get_gizmos_manager().get_current(); + if (gizmo != nullptr) m_dirty |= gizmo->update_items_state(); + // ImGuiWrapper::m_requires_extra_frame may have been set by a render made outside of the OnIdle mechanism + bool imgui_requires_extra_frame = wxGetApp().imgui()->requires_extra_frame(); + m_dirty |= imgui_requires_extra_frame; + + if (!m_dirty) + return; + + // this needs to be done here. + // during the render launched by the refresh the value may be set again + wxGetApp().imgui()->reset_requires_extra_frame(); + + _refresh_if_shown_on_screen(); + + if (m_extra_frame_requested || mouse3d_controller_applied || imgui_requires_extra_frame || wxGetApp().imgui()->requires_extra_frame()) { + m_extra_frame_requested = false; + evt.RequestMore(); + } + else + m_dirty = false; +} + +void GLCanvas3D::on_char(wxKeyEvent& evt) +{ + if (!m_initialized) + return; + + // see include/wx/defs.h enum wxKeyCode + int keyCode = evt.GetKeyCode(); + int ctrlMask = wxMOD_CONTROL; + int shiftMask = wxMOD_SHIFT; + + auto imgui = wxGetApp().imgui(); + if (imgui->update_key_data(evt)) { + render(); + return; + } + + if (keyCode == WXK_ESCAPE && (_deactivate_undo_redo_toolbar_items() || _deactivate_search_toolbar_item() || _deactivate_arrange_menu())) + return; + + if (m_gizmos.on_char(evt)) + return; + + if ((evt.GetModifiers() & ctrlMask) != 0) { + // CTRL is pressed + switch (keyCode) { +#ifdef __APPLE__ + case 'a': + case 'A': +#else /* __APPLE__ */ + case WXK_CONTROL_A: +#endif /* __APPLE__ */ + post_event(SimpleEvent(EVT_GLCANVAS_SELECT_ALL)); + break; +#ifdef __APPLE__ + case 'c': + case 'C': +#else /* __APPLE__ */ + case WXK_CONTROL_C: +#endif /* __APPLE__ */ + post_event(SimpleEvent(EVT_GLTOOLBAR_COPY)); + break; +#ifdef __APPLE__ + case 'm': + case 'M': +#else /* __APPLE__ */ + case WXK_CONTROL_M: +#endif /* __APPLE__ */ + { +#ifdef _WIN32 + if (wxGetApp().app_config->get("use_legacy_3DConnexion") == "1") { +#endif //_WIN32 +#ifdef __APPLE__ + // On OSX use Cmd+Shift+M to "Show/Hide 3Dconnexion devices settings dialog" + if ((evt.GetModifiers() & shiftMask) != 0) { +#endif // __APPLE__ + Mouse3DController& controller = wxGetApp().plater()->get_mouse3d_controller(); + controller.show_settings_dialog(!controller.is_settings_dialog_shown()); + m_dirty = true; +#ifdef __APPLE__ + } + else + // and Cmd+M to minimize application + wxGetApp().mainframe->Iconize(); +#endif // __APPLE__ +#ifdef _WIN32 + } +#endif //_WIN32 + break; + } +#ifdef __APPLE__ + case 'v': + case 'V': +#else /* __APPLE__ */ + case WXK_CONTROL_V: +#endif /* __APPLE__ */ + post_event(SimpleEvent(EVT_GLTOOLBAR_PASTE)); + break; + + +#ifdef __APPLE__ + case 'f': + case 'F': +#else /* __APPLE__ */ + case WXK_CONTROL_F: +#endif /* __APPLE__ */ + _activate_search_toolbar_item(); + break; + + +#ifdef __APPLE__ + case 'y': + case 'Y': +#else /* __APPLE__ */ + case WXK_CONTROL_Y: +#endif /* __APPLE__ */ + post_event(SimpleEvent(EVT_GLCANVAS_REDO)); + break; +#ifdef __APPLE__ + case 'z': + case 'Z': +#else /* __APPLE__ */ + case WXK_CONTROL_Z: +#endif /* __APPLE__ */ + post_event(SimpleEvent(EVT_GLCANVAS_UNDO)); + break; + + case WXK_BACK: + case WXK_DELETE: + post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); break; + default: evt.Skip(); + } + } else { + switch (keyCode) + { + case WXK_BACK: + case WXK_DELETE: { post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE)); break; } + case WXK_ESCAPE: { deselect_all(); break; } + case WXK_F5: { + if ((wxGetApp().is_editor() && !wxGetApp().plater()->model().objects.empty()) || + (wxGetApp().is_gcode_viewer() && !wxGetApp().plater()->get_last_loaded_gcode().empty())) + post_event(SimpleEvent(EVT_GLCANVAS_RELOAD_FROM_DISK)); + break; + } + case '0': { select_view("iso"); break; } + case '1': { select_view("top"); break; } + case '2': { select_view("bottom"); break; } + case '3': { select_view("front"); break; } + case '4': { select_view("rear"); break; } + case '5': { select_view("left"); break; } + case '6': { select_view("right"); break; } + case '+': { + if (dynamic_cast(m_canvas->GetParent()) != nullptr) + post_event(wxKeyEvent(EVT_GLCANVAS_EDIT_COLOR_CHANGE, evt)); + else + post_event(Event(EVT_GLCANVAS_INCREASE_INSTANCES, +1)); + break; + } + case '-': { + if (dynamic_cast(m_canvas->GetParent()) != nullptr) + post_event(wxKeyEvent(EVT_GLCANVAS_EDIT_COLOR_CHANGE, evt)); + else + post_event(Event(EVT_GLCANVAS_INCREASE_INSTANCES, -1)); + break; + } + case '?': { post_event(SimpleEvent(EVT_GLCANVAS_QUESTION_MARK)); break; } + case 'A': + case 'a': { post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE)); break; } + case 'B': + case 'b': { zoom_to_bed(); break; } + case 'C': + case 'c': { m_gcode_viewer.toggle_gcode_window_visibility(); m_dirty = true; request_extra_frame(); break; } + case 'E': + case 'e': { m_labels.show(!m_labels.is_shown()); m_dirty = true; break; } + case 'G': + case 'g': { + if ((evt.GetModifiers() & shiftMask) != 0) { + if (dynamic_cast(m_canvas->GetParent()) != nullptr) + post_event(wxKeyEvent(EVT_GLCANVAS_JUMP_TO, evt)); + } + break; + } + case 'I': + case 'i': { _update_camera_zoom(1.0); break; } + case 'K': + case 'k': { wxGetApp().plater()->get_camera().select_next_type(); m_dirty = true; break; } + case 'L': + case 'l': { + if (!m_main_toolbar.is_enabled()) { +#if ENABLE_PREVIEW_LAYOUT + show_legend(!is_legend_shown()); +#else + m_gcode_viewer.enable_legend(!m_gcode_viewer.is_legend_enabled()); + m_dirty = true; + wxGetApp().plater()->update_preview_bottom_toolbar(); +#endif // ENABLE_PREVIEW_LAYOUT + } + break; + } + case 'O': + case 'o': { _update_camera_zoom(-1.0); break; } +#if ENABLE_RENDER_PICKING_PASS + case 'T': + case 't': { + m_show_picking_texture = !m_show_picking_texture; + m_dirty = true; + break; + } +#endif // ENABLE_RENDER_PICKING_PASS + case 'Z': + case 'z': { + if (!m_selection.is_empty()) + zoom_to_selection(); + else { + if (!m_volumes.empty()) + zoom_to_volumes(); + else + _zoom_to_box(m_gcode_viewer.get_paths_bounding_box()); + } + break; + } + default: { evt.Skip(); break; } + } + } +} + +class TranslationProcessor +{ + using UpAction = std::function; + using DownAction = std::function; + + UpAction m_up_action{ nullptr }; + DownAction m_down_action{ nullptr }; + + bool m_running{ false }; + Vec3d m_direction{ Vec3d::UnitX() }; + +public: + TranslationProcessor(UpAction up_action, DownAction down_action) + : m_up_action(up_action), m_down_action(down_action) + { + } + + void process(wxKeyEvent& evt) + { + const int keyCode = evt.GetKeyCode(); + wxEventType type = evt.GetEventType(); + if (type == wxEVT_KEY_UP) { + switch (keyCode) + { + case WXK_NUMPAD_LEFT: case WXK_LEFT: + case WXK_NUMPAD_RIGHT: case WXK_RIGHT: + case WXK_NUMPAD_UP: case WXK_UP: + case WXK_NUMPAD_DOWN: case WXK_DOWN: + { + m_running = false; + m_up_action(); + break; + } + default: { break; } + } + } + else if (type == wxEVT_KEY_DOWN) { + bool apply = false; + + switch (keyCode) + { + case WXK_SHIFT: + { + if (m_running) + apply = true; + + break; + } + case WXK_NUMPAD_LEFT: + case WXK_LEFT: + { + m_direction = -Vec3d::UnitX(); + apply = true; + break; + } + case WXK_NUMPAD_RIGHT: + case WXK_RIGHT: + { + m_direction = Vec3d::UnitX(); + apply = true; + break; + } + case WXK_NUMPAD_UP: + case WXK_UP: + { + m_direction = Vec3d::UnitY(); + apply = true; + break; + } + case WXK_NUMPAD_DOWN: + case WXK_DOWN: + { + m_direction = -Vec3d::UnitY(); + apply = true; + break; + } + default: { break; } + } + + if (apply) { + m_running = true; + m_down_action(m_direction, evt.ShiftDown(), evt.CmdDown()); + } + } + } +}; + +void GLCanvas3D::on_key(wxKeyEvent& evt) +{ + static TranslationProcessor translationProcessor( + [this]() { + do_move(L("Gizmo-Move")); + m_gizmos.update_data(); + + wxGetApp().obj_manipul()->set_dirty(); + // Let the plater know that the dragging finished, so a delayed refresh + // of the scene with the background processing data should be performed. + post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); + // updates camera target constraints + refresh_camera_scene_box(); + m_dirty = true; + }, + [this](const Vec3d& direction, bool slow, bool camera_space) { + m_selection.setup_cache(); + double multiplier = slow ? 1.0 : 10.0; + + Vec3d displacement; + if (camera_space) { + Eigen::Matrix inv_view_3x3 = wxGetApp().plater()->get_camera().get_view_matrix().inverse().matrix().block(0, 0, 3, 3); + displacement = multiplier * (inv_view_3x3 * direction); + displacement.z() = 0.0; + } + else + displacement = multiplier * direction; + + m_selection.translate(displacement); + m_dirty = true; + } + ); + + const int keyCode = evt.GetKeyCode(); + + auto imgui = wxGetApp().imgui(); + if (imgui->update_key_data(evt)) { + render(); + } + else { + if (!m_gizmos.on_key(evt)) { + if (evt.GetEventType() == wxEVT_KEY_UP) { + if (evt.ShiftDown() && evt.ControlDown() && keyCode == WXK_SPACE) { + wxGetApp().plater()->toggle_render_statistic_dialog(); + m_dirty = true; + } + if (m_tab_down && keyCode == WXK_TAB && !evt.HasAnyModifiers()) { + // Enable switching between 3D and Preview with Tab + // m_canvas->HandleAsNavigationKey(evt); // XXX: Doesn't work in some cases / on Linux + post_event(SimpleEvent(EVT_GLCANVAS_TAB)); + } + else if (keyCode == WXK_TAB && evt.ShiftDown() && ! wxGetApp().is_gcode_viewer()) { + // Collapse side-panel with Shift+Tab + post_event(SimpleEvent(EVT_GLCANVAS_COLLAPSE_SIDEBAR)); + } + else if (keyCode == WXK_SHIFT) { + translationProcessor.process(evt); + + if (m_picking_enabled && m_rectangle_selection.is_dragging()) { + _update_selection_from_hover(); + m_rectangle_selection.stop_dragging(); + m_mouse.ignore_left_up = true; +#if !ENABLE_NEW_RECTANGLE_SELECTION + m_dirty = true; +#endif // !ENABLE_NEW_RECTANGLE_SELECTION + } +#if ENABLE_NEW_RECTANGLE_SELECTION + m_dirty = true; +#endif // ENABLE_NEW_RECTANGLE_SELECTION +// set_cursor(Standard); + } + else if (keyCode == WXK_ALT) { + if (m_picking_enabled && m_rectangle_selection.is_dragging()) { + _update_selection_from_hover(); + m_rectangle_selection.stop_dragging(); + m_mouse.ignore_left_up = true; + m_dirty = true; + } +// set_cursor(Standard); + } + else if (keyCode == WXK_CONTROL) { +#if ENABLE_NEW_CAMERA_MOVEMENTS + if (m_mouse.dragging) { + // if the user releases CTRL while rotating the 3D scene + // prevent from moving the selected volume + m_mouse.drag.move_volume_idx = -1; + m_mouse.set_start_position_3D_as_invalid(); + } +#endif // ENABLE_NEW_CAMERA_MOVEMENTS + m_dirty = true; + } + else if (m_gizmos.is_enabled() && !m_selection.is_empty()) { + translationProcessor.process(evt); + + switch (keyCode) + { + case WXK_NUMPAD_PAGEUP: case WXK_PAGEUP: + case WXK_NUMPAD_PAGEDOWN: case WXK_PAGEDOWN: + { + do_rotate(L("Gizmo-Rotate")); + m_gizmos.update_data(); + + wxGetApp().obj_manipul()->set_dirty(); + // Let the plater know that the dragging finished, so a delayed refresh + // of the scene with the background processing data should be performed. + post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); + // updates camera target constraints + refresh_camera_scene_box(); + m_dirty = true; + + break; + } + default: { break; } + } + } + } + else if (evt.GetEventType() == wxEVT_KEY_DOWN) { + m_tab_down = keyCode == WXK_TAB && !evt.HasAnyModifiers(); + if (keyCode == WXK_SHIFT) { + translationProcessor.process(evt); + + if (m_picking_enabled && (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports)) { + m_mouse.ignore_left_up = false; +// set_cursor(Cross); + } +#if ENABLE_NEW_RECTANGLE_SELECTION + m_dirty = true; +#endif // ENABLE_NEW_RECTANGLE_SELECTION + } + else if (keyCode == WXK_ALT) { + if (m_picking_enabled && (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports)) { + m_mouse.ignore_left_up = false; +// set_cursor(Cross); + } + } + else if (keyCode == WXK_CONTROL) + m_dirty = true; + else if (m_gizmos.is_enabled() && !m_selection.is_empty()) { + auto do_rotate = [this](double angle_z_rad) { + m_selection.setup_cache(); + m_selection.rotate(Vec3d(0.0, 0.0, angle_z_rad), TransformationType(TransformationType::World_Relative_Joint)); + m_dirty = true; +// wxGetApp().obj_manipul()->set_dirty(); + }; + + translationProcessor.process(evt); + + switch (keyCode) + { + case WXK_NUMPAD_PAGEUP: case WXK_PAGEUP: { do_rotate(0.25 * M_PI); break; } + case WXK_NUMPAD_PAGEDOWN: case WXK_PAGEDOWN: { do_rotate(-0.25 * M_PI); break; } + default: { break; } + } + } + else if (!m_gizmos.is_enabled()) { + // DoubleSlider navigation in Preview + if (keyCode == WXK_LEFT || + keyCode == WXK_RIGHT || + keyCode == WXK_UP || + keyCode == WXK_DOWN) { + if (dynamic_cast(m_canvas->GetParent()) != nullptr) + post_event(wxKeyEvent(EVT_GLCANVAS_MOVE_SLIDERS, evt)); + } + } + } + } + } + + if (keyCode != WXK_TAB + && keyCode != WXK_LEFT + && keyCode != WXK_UP + && keyCode != WXK_RIGHT + && keyCode != WXK_DOWN) { + evt.Skip(); // Needed to have EVT_CHAR generated as well + } +} + +void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt) +{ +#ifdef WIN32 + // Try to filter out spurious mouse wheel events comming from 3D mouse. + if (wxGetApp().plater()->get_mouse3d_controller().process_mouse_wheel()) + return; +#endif + + if (!m_initialized) + return; + + // Ignore the wheel events if the middle button is pressed. + if (evt.MiddleIsDown()) + return; + +#if ENABLE_RETINA_GL + const float scale = m_retina_helper->get_scale_factor(); + evt.SetX(evt.GetX() * scale); + evt.SetY(evt.GetY() * scale); +#endif + + if (wxGetApp().imgui()->update_mouse_data(evt)) { + m_dirty = true; + return; + } + +#ifdef __WXMSW__ + // For some reason the Idle event is not being generated after the mouse scroll event in case of scrolling with the two fingers on the touch pad, + // if the event is not allowed to be passed further. + // https://github.com/prusa3d/PrusaSlicer/issues/2750 + // evt.Skip() used to trigger the needed screen refresh, but it does no more. wxWakeUpIdle() seem to work now. + wxWakeUpIdle(); +#endif /* __WXMSW__ */ + + // Performs layers editing updates, if enabled + if (is_layers_editing_enabled()) { + int object_idx_selected = m_selection.get_object_idx(); + if (object_idx_selected != -1) { + // A volume is selected. Test, whether hovering over a layer thickness bar. + if (m_layers_editing.bar_rect_contains(*this, (float)evt.GetX(), (float)evt.GetY())) { + // Adjust the width of the selection. + m_layers_editing.band_width = std::max(std::min(m_layers_editing.band_width * (1.0f + 0.1f * (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta()), 10.0f), 1.5f); + if (m_canvas != nullptr) + m_canvas->Refresh(); + + return; + } + } + } + + // If the Search window or Undo/Redo list is opened, + // update them according to the event + if (m_main_toolbar.is_item_pressed("search") || + m_undoredo_toolbar.is_item_pressed("undo") || + m_undoredo_toolbar.is_item_pressed("redo")) { + m_mouse_wheel = int((double)evt.GetWheelRotation() / (double)evt.GetWheelDelta()); + return; + } + + // Inform gizmos about the event so they have the opportunity to react. + if (m_gizmos.on_mouse_wheel(evt)) + return; + + // Calculate the zoom delta and apply it to the current zoom factor + double direction_factor = (wxGetApp().app_config->get("reverse_mouse_wheel_zoom") == "1") ? -1.0 : 1.0; + _update_camera_zoom(direction_factor * (double)evt.GetWheelRotation() / (double)evt.GetWheelDelta()); +} + +void GLCanvas3D::on_timer(wxTimerEvent& evt) +{ + if (m_layers_editing.state == LayersEditing::Editing) + _perform_layer_editing_action(); +} + +void GLCanvas3D::on_render_timer(wxTimerEvent& evt) +{ + // no need to wake up idle + // right after this event, idle event is fired + // m_dirty = true; + // wxWakeUpIdle(); +} + + +void GLCanvas3D::schedule_extra_frame(int miliseconds) +{ + // Schedule idle event right now + if (miliseconds == 0) + { + // We want to wakeup idle evnt but most likely this is call inside render cycle so we need to wait + if (m_in_render) + miliseconds = 33; + else { + m_dirty = true; + wxWakeUpIdle(); + return; + } + } + int remaining_time = m_render_timer.GetInterval(); + // Timer is not running + if (!m_render_timer.IsRunning()) { + m_render_timer.StartOnce(miliseconds); + // Timer is running - restart only if new period is shorter than remaning period + } else { + if (miliseconds + 20 < remaining_time) { + m_render_timer.Stop(); + m_render_timer.StartOnce(miliseconds); + } + } +} + +#ifndef NDEBUG +// #define SLIC3R_DEBUG_MOUSE_EVENTS +#endif + +#ifdef SLIC3R_DEBUG_MOUSE_EVENTS +std::string format_mouse_event_debug_message(const wxMouseEvent &evt) +{ + static int idx = 0; + char buf[2048]; + std::string out; + sprintf(buf, "Mouse Event %d - ", idx ++); + out = buf; + + if (evt.Entering()) + out += "Entering "; + if (evt.Leaving()) + out += "Leaving "; + if (evt.Dragging()) + out += "Dragging "; + if (evt.Moving()) + out += "Moving "; + if (evt.Magnify()) + out += "Magnify "; + if (evt.LeftDown()) + out += "LeftDown "; + if (evt.LeftUp()) + out += "LeftUp "; + if (evt.LeftDClick()) + out += "LeftDClick "; + if (evt.MiddleDown()) + out += "MiddleDown "; + if (evt.MiddleUp()) + out += "MiddleUp "; + if (evt.MiddleDClick()) + out += "MiddleDClick "; + if (evt.RightDown()) + out += "RightDown "; + if (evt.RightUp()) + out += "RightUp "; + if (evt.RightDClick()) + out += "RightDClick "; + + sprintf(buf, "(%d, %d)", evt.GetX(), evt.GetY()); + out += buf; + return out; +} +#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */ + +void GLCanvas3D::on_mouse(wxMouseEvent& evt) +{ + if (!m_initialized || !_set_current()) + return; + +#if ENABLE_RETINA_GL + const float scale = m_retina_helper->get_scale_factor(); + evt.SetX(evt.GetX() * scale); + evt.SetY(evt.GetY() * scale); +#endif + + Point pos(evt.GetX(), evt.GetY()); + + ImGuiWrapper* imgui = wxGetApp().imgui(); + if (m_tooltip.is_in_imgui() && evt.LeftUp()) + // ignore left up events coming from imgui windows and not processed by them + m_mouse.ignore_left_up = true; + m_tooltip.set_in_imgui(false); + if (imgui->update_mouse_data(evt)) { + m_mouse.position = evt.Leaving() ? Vec2d(-1.0, -1.0) : pos.cast(); + m_tooltip.set_in_imgui(true); + render(); +#ifdef SLIC3R_DEBUG_MOUSE_EVENTS + printf((format_mouse_event_debug_message(evt) + " - Consumed by ImGUI\n").c_str()); +#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */ + m_dirty = true; + // do not return if dragging or tooltip not empty to allow for tooltip update + // also, do not return if the mouse is moving and also is inside MM gizmo to allow update seed fill selection + if (!m_mouse.dragging && m_tooltip.is_empty() && (m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation || !evt.Moving())) + return; + } + +#ifdef __WXMSW__ + bool on_enter_workaround = false; + if (! evt.Entering() && ! evt.Leaving() && m_mouse.position.x() == -1.0) { + // Workaround for SPE-832: There seems to be a mouse event sent to the window before evt.Entering() + m_mouse.position = pos.cast(); + render(); +#ifdef SLIC3R_DEBUG_MOUSE_EVENTS + printf((format_mouse_event_debug_message(evt) + " - OnEnter workaround\n").c_str()); +#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */ + on_enter_workaround = true; + } else +#endif /* __WXMSW__ */ + { +#ifdef SLIC3R_DEBUG_MOUSE_EVENTS + printf((format_mouse_event_debug_message(evt) + " - other\n").c_str()); +#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */ + } + + if (m_main_toolbar.on_mouse(evt, *this)) { + if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) + mouse_up_cleanup(); + m_mouse.set_start_position_3D_as_invalid(); +#if ENABLE_OBJECT_MANIPULATOR_FOCUS + handle_sidebar_focus_event("", false); +#endif // ENABLE_OBJECT_MANIPULATOR_FOCUS + return; + } + + if (m_undoredo_toolbar.on_mouse(evt, *this)) { + if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) + mouse_up_cleanup(); + m_mouse.set_start_position_3D_as_invalid(); +#if ENABLE_OBJECT_MANIPULATOR_FOCUS + handle_sidebar_focus_event("", false); +#endif // ENABLE_OBJECT_MANIPULATOR_FOCUS + return; + } + + if (wxGetApp().plater()->get_collapse_toolbar().on_mouse(evt, *this)) { + if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) + mouse_up_cleanup(); + m_mouse.set_start_position_3D_as_invalid(); +#if ENABLE_OBJECT_MANIPULATOR_FOCUS + handle_sidebar_focus_event("", false); +#endif // ENABLE_OBJECT_MANIPULATOR_FOCUS + return; + } + + if (wxGetApp().plater()->get_view_toolbar().on_mouse(evt, *this)) { + if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) + mouse_up_cleanup(); + m_mouse.set_start_position_3D_as_invalid(); +#if ENABLE_OBJECT_MANIPULATOR_FOCUS + handle_sidebar_focus_event("", false); +#endif // ENABLE_OBJECT_MANIPULATOR_FOCUS + return; + } + + for (GLVolume* volume : m_volumes.volumes) { + volume->force_sinking_contours = false; + } + + auto show_sinking_contours = [this]() { + const Selection::IndicesList& idxs = m_selection.get_volume_idxs(); + for (unsigned int idx : idxs) { + m_volumes.volumes[idx]->force_sinking_contours = true; + } + m_dirty = true; + }; + + if (m_gizmos.on_mouse(evt)) { + if (wxWindow::FindFocus() != m_canvas) + // Grab keyboard focus for input in gizmo dialogs. + m_canvas->SetFocus(); + + if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) + mouse_up_cleanup(); + + m_mouse.set_start_position_3D_as_invalid(); + m_mouse.position = pos.cast(); + + // It should be detection of volume change + // Not only detection of some modifiers !!! + if (evt.Dragging()) { + GLGizmosManager::EType c = m_gizmos.get_current_type(); + if (current_printer_technology() == ptFFF && + fff_print()->config().complete_objects){ + if (c == GLGizmosManager::EType::Move || + c == GLGizmosManager::EType::Scale || + c == GLGizmosManager::EType::Rotate ) + update_sequential_clearance(); + } else { + if (c == GLGizmosManager::EType::Move || + c == GLGizmosManager::EType::Scale || + c == GLGizmosManager::EType::Rotate) + show_sinking_contours(); + } + } + +#if ENABLE_OBJECT_MANIPULATOR_FOCUS + handle_sidebar_focus_event("", false); +#endif // ENABLE_OBJECT_MANIPULATOR_FOCUS + return; + } + + bool any_gizmo_active = m_gizmos.get_current() != nullptr; + + int selected_object_idx = m_selection.get_object_idx(); + int layer_editing_object_idx = is_layers_editing_enabled() ? selected_object_idx : -1; + m_layers_editing.select_object(*m_model, layer_editing_object_idx); + + if (m_mouse.drag.move_requires_threshold && m_mouse.is_move_start_threshold_position_2D_defined() && m_mouse.is_move_threshold_met(pos)) { + m_mouse.drag.move_requires_threshold = false; + m_mouse.set_move_start_threshold_position_2D_as_invalid(); + } + +#if ENABLE_OBJECT_MANIPULATOR_FOCUS + if (evt.ButtonDown()) { + std::string curr_sidebar_field = m_sidebar_field; + handle_sidebar_focus_event("", false); + if (boost::algorithm::istarts_with(curr_sidebar_field, "layer")) + // restore visibility of layers hints after left clicking on the 3D scene + m_sidebar_field = curr_sidebar_field; + if (wxWindow::FindFocus() != m_canvas) + // Grab keyboard focus on any mouse click event. + m_canvas->SetFocus(); + } +#else + if (evt.ButtonDown() && wxWindow::FindFocus() != m_canvas) + // Grab keyboard focus on any mouse click event. + m_canvas->SetFocus(); +#endif // ENABLE_OBJECT_MANIPULATOR_FOCUS + + if (evt.Entering()) { +//#if defined(__WXMSW__) || defined(__linux__) +// // On Windows and Linux needs focus in order to catch key events +#if !ENABLE_OBJECT_MANIPULATOR_FOCUS + // Set focus in order to remove it from sidebar fields +#endif // !ENABLE_OBJECT_MANIPULATOR_FOCUS + if (m_canvas != nullptr) { +#if !ENABLE_OBJECT_MANIPULATOR_FOCUS + // Only set focus, if the top level window of this canvas is active. + auto p = dynamic_cast(evt.GetEventObject()); + while (p->GetParent()) + p = p->GetParent(); + auto *top_level_wnd = dynamic_cast(p); + if (top_level_wnd && top_level_wnd->IsActive()) + m_canvas->SetFocus(); +#endif // !ENABLE_OBJECT_MANIPULATOR_FOCUS + m_mouse.position = pos.cast(); + m_tooltip_enabled = false; + // 1) forces a frame render to ensure that m_hover_volume_idxs is updated even when the user right clicks while + // the context menu is shown, ensuring it to disappear if the mouse is outside any volume and to + // change the volume hover state if any is under the mouse + // 2) when switching between 3d view and preview the size of the canvas changes if the side panels are visible, + // so forces a resize to avoid multiple renders with different sizes (seen as flickering) + _refresh_if_shown_on_screen(); + m_tooltip_enabled = true; + } + m_mouse.set_start_position_2D_as_invalid(); +//#endif + } + else if (evt.Leaving()) { + _deactivate_undo_redo_toolbar_items(); + + // to remove hover on objects when the mouse goes out of this canvas + m_mouse.position = Vec2d(-1.0, -1.0); + m_dirty = true; + } + else if (evt.LeftDown() || evt.RightDown() || evt.MiddleDown()) { + if (_deactivate_undo_redo_toolbar_items() || _deactivate_search_toolbar_item() || _deactivate_arrange_menu()) + return; + + // If user pressed left or right button we first check whether this happened + // on a volume or not. + m_layers_editing.state = LayersEditing::Unknown; + if (layer_editing_object_idx != -1 && m_layers_editing.bar_rect_contains(*this, pos(0), pos(1))) { + // A volume is selected and the mouse is inside the layer thickness bar. + // Start editing the layer height. + m_layers_editing.state = LayersEditing::Editing; + _perform_layer_editing_action(&evt); + } +#if !ENABLE_NEW_RECTANGLE_SELECTION + else if (evt.LeftDown() && (evt.ShiftDown() || evt.AltDown()) && m_picking_enabled) { + if (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports + && m_gizmos.get_current_type() != GLGizmosManager::FdmSupports + && m_gizmos.get_current_type() != GLGizmosManager::Seam + && m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation) { + m_rectangle_selection.start_dragging(m_mouse.position, evt.ShiftDown() ? GLSelectionRectangle::Select : GLSelectionRectangle::Deselect); + m_dirty = true; + } + } +#endif // !ENABLE_NEW_RECTANGLE_SELECTION + else { +#if ENABLE_NEW_RECTANGLE_SELECTION + const bool rectangle_selection_dragging = m_rectangle_selection.is_dragging(); + if (evt.LeftDown() && (evt.ShiftDown() || evt.AltDown()) && m_picking_enabled) { + if (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports && + m_gizmos.get_current_type() != GLGizmosManager::FdmSupports && + m_gizmos.get_current_type() != GLGizmosManager::Seam && + m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation) { + m_rectangle_selection.start_dragging(m_mouse.position, evt.ShiftDown() ? GLSelectionRectangle::EState::Select : GLSelectionRectangle::EState::Deselect); + m_dirty = true; + } + } +#endif // ENABLE_NEW_RECTANGLE_SELECTION + + // Select volume in this 3D canvas. + // Don't deselect a volume if layer editing is enabled or any gizmo is active. We want the object to stay selected + // during the scene manipulation. + +#if ENABLE_NEW_RECTANGLE_SELECTION + if (m_picking_enabled && (!any_gizmo_active || !evt.CmdDown()) && (!m_hover_volume_idxs.empty() || !is_layers_editing_enabled()) && !rectangle_selection_dragging) { +#else + if (m_picking_enabled && (!any_gizmo_active || !evt.CmdDown()) && (!m_hover_volume_idxs.empty() || !is_layers_editing_enabled())) { +#endif // ENABLE_NEW_RECTANGLE_SELECTION + if (evt.LeftDown() && !m_hover_volume_idxs.empty()) { + int volume_idx = get_first_hover_volume_idx(); + bool already_selected = m_selection.contains_volume(volume_idx); +#if ENABLE_NEW_RECTANGLE_SELECTION + bool shift_down = evt.ShiftDown(); +#else + bool ctrl_down = evt.CmdDown(); +#endif // ENABLE_NEW_RECTANGLE_SELECTION + + Selection::IndicesList curr_idxs = m_selection.get_volume_idxs(); + +#if ENABLE_NEW_RECTANGLE_SELECTION + if (already_selected && shift_down) + m_selection.remove(volume_idx); + else { + m_selection.add(volume_idx, !shift_down, true); +#else + if (already_selected && ctrl_down) + m_selection.remove(volume_idx); + else { + m_selection.add(volume_idx, !ctrl_down, true); +#endif // ENABLE_NEW_RECTANGLE_SELECTION + m_mouse.drag.move_requires_threshold = !already_selected; + if (already_selected) + m_mouse.set_move_start_threshold_position_2D_as_invalid(); + else + m_mouse.drag.move_start_threshold_position_2D = pos; + } + + // propagate event through callback + if (curr_idxs != m_selection.get_volume_idxs()) { + if (m_selection.is_empty()) + m_gizmos.reset_all_states(); + else + m_gizmos.refresh_on_off_state(); + + m_gizmos.update_data(); + post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); + m_dirty = true; + } + } + } + +#if ENABLE_NEW_RECTANGLE_SELECTION + if (!m_hover_volume_idxs.empty() && !m_rectangle_selection.is_dragging()) { +#else + if (!m_hover_volume_idxs.empty()) { +#endif // ENABLE_NEW_RECTANGLE_SELECTION + if (evt.LeftDown() && m_moving_enabled && m_mouse.drag.move_volume_idx == -1) { + // Only accept the initial position, if it is inside the volume bounding box. + const int volume_idx = get_first_hover_volume_idx(); + BoundingBoxf3 volume_bbox = m_volumes.volumes[volume_idx]->transformed_bounding_box(); + volume_bbox.offset(1.0); + if ((!any_gizmo_active || !evt.CmdDown()) && volume_bbox.contains(m_mouse.scene_position)) { + m_volumes.volumes[volume_idx]->hover = GLVolume::HS_None; + // The dragging operation is initiated. + m_mouse.drag.move_volume_idx = volume_idx; +#if ENABLE_NEW_CAMERA_MOVEMENTS + m_selection.setup_cache(); + if (!evt.CmdDown()) +#endif // ENABLE_NEW_CAMERA_MOVEMENTS + m_mouse.drag.start_position_3D = m_mouse.scene_position; + m_sequential_print_clearance_first_displacement = true; + m_moving = true; + } + } + } + } + } +#if ENABLE_NEW_CAMERA_MOVEMENTS + else if (evt.Dragging() && evt.LeftIsDown() && !evt.CmdDown() && m_layers_editing.state == LayersEditing::Unknown && + m_mouse.drag.move_volume_idx != -1 && m_mouse.is_start_position_3D_defined()) { +#else + else if (evt.Dragging() && evt.LeftIsDown() && m_layers_editing.state == LayersEditing::Unknown && m_mouse.drag.move_volume_idx != -1) { +#endif // ENABLE_NEW_CAMERA_MOVEMENTS + if (!m_mouse.drag.move_requires_threshold) { + m_mouse.dragging = true; + Vec3d cur_pos = m_mouse.drag.start_position_3D; + // we do not want to translate objects if the user just clicked on an object while pressing shift to remove it from the selection and then drag + if (m_selection.contains_volume(get_first_hover_volume_idx())) { + const Camera& camera = wxGetApp().plater()->get_camera(); + if (std::abs(camera.get_dir_forward().z()) < EPSILON) { + // side view -> move selected volumes orthogonally to camera view direction + const Linef3 ray = mouse_ray(pos); + const Vec3d dir = ray.unit_vector(); + // 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 + // 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 + const Vec3d inters = ray.a + (m_mouse.drag.start_position_3D - ray.a).dot(dir) / dir.squaredNorm() * dir; + // vector from the starting position to the found intersection + const Vec3d inters_vec = inters - m_mouse.drag.start_position_3D; + + const Vec3d camera_right = camera.get_dir_right(); + const Vec3d camera_up = camera.get_dir_up(); + + // finds projection of the vector along the camera axes + const double projection_x = inters_vec.dot(camera_right); + const double projection_z = inters_vec.dot(camera_up); + + // apply offset + cur_pos = m_mouse.drag.start_position_3D + projection_x * camera_right + projection_z * camera_up; + } + else { + // Generic view + // Get new position at the same Z of the initial click point. + cur_pos = mouse_ray(pos).intersect_plane(m_mouse.drag.start_position_3D.z()); + } + } + + m_selection.translate(cur_pos - m_mouse.drag.start_position_3D); + if (current_printer_technology() == ptFFF && fff_print()->config().complete_objects) + update_sequential_clearance(); + wxGetApp().obj_manipul()->set_dirty(); + m_dirty = true; + } + } + else if (evt.Dragging() && evt.LeftIsDown() && m_picking_enabled && m_rectangle_selection.is_dragging()) { +#if ENABLE_NEW_RECTANGLE_SELECTION + // keeps the mouse position updated while dragging the selection rectangle + m_mouse.position = pos.cast(); + m_rectangle_selection.dragging(m_mouse.position); +#else + m_rectangle_selection.dragging(pos.cast()); +#endif // ENABLE_NEW_RECTANGLE_SELECTION + m_dirty = true; + } + else if (evt.Dragging()) { + m_mouse.dragging = true; + + if (m_layers_editing.state != LayersEditing::Unknown && layer_editing_object_idx != -1) { + if (m_layers_editing.state == LayersEditing::Editing) { + _perform_layer_editing_action(&evt); + m_mouse.position = pos.cast(); + } + } + // do not process the dragging if the left mouse was set down in another canvas +#if ENABLE_NEW_CAMERA_MOVEMENTS + else if (evt.LeftIsDown()) { + // if dragging over blank area with left button, rotate + if ((any_gizmo_active || evt.CmdDown() || m_hover_volume_idxs.empty()) && m_mouse.is_start_position_3D_defined()) { +#else + // if dragging over blank area with left button, rotate + else if (evt.LeftIsDown()) { + if ((any_gizmo_active || m_hover_volume_idxs.empty()) && m_mouse.is_start_position_3D_defined()) { +#endif // ENABLE_NEW_CAMERA_MOVEMENTS + const Vec3d rot = (Vec3d(pos.x(), pos.y(), 0.0) - m_mouse.drag.start_position_3D) * (PI * TRACKBALLSIZE / 180.0); + if (wxGetApp().app_config->get("use_free_camera") == "1") + // Virtual track ball (similar to the 3DConnexion mouse). + wxGetApp().plater()->get_camera().rotate_local_around_target(Vec3d(rot.y(), rot.x(), 0.0)); + else { + // Forces camera right vector to be parallel to XY plane in case it has been misaligned using the 3D mouse free rotation. + // It is cheaper to call this function right away instead of testing wxGetApp().plater()->get_mouse3d_controller().connected(), + // which checks an atomics (flushes CPU caches). + // See GH issue #3816. + Camera& camera = wxGetApp().plater()->get_camera(); + camera.recover_from_free_camera(); + camera.rotate_on_sphere(rot.x(), rot.y(), current_printer_technology() != ptSLA); + } + + m_dirty = true; + } + m_mouse.drag.start_position_3D = Vec3d((double)pos.x(), (double)pos.y(), 0.0); + } + else if (evt.MiddleIsDown() || evt.RightIsDown()) { + // If dragging over blank area with right/middle button, pan. + if (m_mouse.is_start_position_2D_defined()) { + // get point in model space at Z = 0 + float z = 0.0f; + const Vec3d cur_pos = _mouse_to_3d(pos, &z); + const Vec3d orig = _mouse_to_3d(m_mouse.drag.start_position_2D, &z); + Camera& camera = wxGetApp().plater()->get_camera(); + if (wxGetApp().app_config->get("use_free_camera") != "1") + // Forces camera right vector to be parallel to XY plane in case it has been misaligned using the 3D mouse free rotation. + // It is cheaper to call this function right away instead of testing wxGetApp().plater()->get_mouse3d_controller().connected(), + // which checks an atomics (flushes CPU caches). + // See GH issue #3816. + camera.recover_from_free_camera(); + + camera.set_target(camera.get_target() + orig - cur_pos); + m_dirty = true; + } + + m_mouse.drag.start_position_2D = pos; + } + } + else if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) { + if (m_layers_editing.state != LayersEditing::Unknown) { + m_layers_editing.state = LayersEditing::Unknown; + _stop_timer(); + m_layers_editing.accept_changes(*this); + } + else if (m_mouse.drag.move_volume_idx != -1 && m_mouse.dragging) { + do_move(L("Move Object")); + wxGetApp().obj_manipul()->set_dirty(); + // Let the plater know that the dragging finished, so a delayed refresh + // of the scene with the background processing data should be performed. + post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); + } + else if (evt.LeftUp() && m_picking_enabled && m_rectangle_selection.is_dragging()) { + if (evt.ShiftDown() || evt.AltDown()) + _update_selection_from_hover(); + + m_rectangle_selection.stop_dragging(); + } + else if (evt.LeftUp() && !m_mouse.ignore_left_up && !m_mouse.dragging && m_hover_volume_idxs.empty() && !is_layers_editing_enabled()) { + // deselect and propagate event through callback + if (!evt.ShiftDown() && (!any_gizmo_active || !evt.CmdDown()) && m_picking_enabled) + deselect_all(); + } + else if (evt.RightUp()) { + m_mouse.position = pos.cast(); + // forces a frame render to ensure that m_hover_volume_idxs is updated even when the user right clicks while + // the context menu is already shown + render(); + if (!m_hover_volume_idxs.empty()) { + // if right clicking on volume, propagate event through callback (shows context menu) + int volume_idx = get_first_hover_volume_idx(); + if (!m_volumes.volumes[volume_idx]->is_wipe_tower // no context menu for the wipe tower + && m_gizmos.get_current_type() != GLGizmosManager::SlaSupports) // disable context menu when the gizmo is open + { + // forces the selection of the volume + /* m_selection.add(volume_idx); // #et_FIXME_if_needed + * To avoid extra "Add-Selection" snapshots, + * call add() with check_for_already_contained=true + * */ + m_selection.add(volume_idx, true, true); + m_gizmos.refresh_on_off_state(); + post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); + m_gizmos.update_data(); + wxGetApp().obj_manipul()->set_dirty(); + // forces a frame render to update the view before the context menu is shown + render(); + } + } + Vec2d logical_pos = pos.cast(); +#if ENABLE_RETINA_GL + const float factor = m_retina_helper->get_scale_factor(); + logical_pos = logical_pos.cwiseQuotient(Vec2d(factor, factor)); +#endif // ENABLE_RETINA_GL + if (!m_mouse.dragging) { + // do not post the event if the user is panning the scene + // or if right click was done over the wipe tower + const bool post_right_click_event = m_hover_volume_idxs.empty() || !m_volumes.volumes[get_first_hover_volume_idx()]->is_wipe_tower; + if (post_right_click_event) + post_event(RBtnEvent(EVT_GLCANVAS_RIGHT_CLICK, { logical_pos, m_hover_volume_idxs.empty() })); + } + } + + mouse_up_cleanup(); + } + else if (evt.Moving()) { + m_mouse.position = pos.cast(); + + // updates gizmos overlay + if (m_selection.is_empty()) + m_gizmos.reset_all_states(); + + m_dirty = true; + } + else + evt.Skip(); + + if (m_moving) + show_sinking_contours(); + +#ifdef __WXMSW__ + if (on_enter_workaround) + m_mouse.position = Vec2d(-1., -1.); +#endif /* __WXMSW__ */ +} + +void GLCanvas3D::on_paint(wxPaintEvent& evt) +{ + if (m_initialized) + m_dirty = true; + else + // Call render directly, so it gets initialized immediately, not from On Idle handler. + this->render(); +} + +void GLCanvas3D::on_set_focus(wxFocusEvent& evt) +{ + m_tooltip_enabled = false; + _refresh_if_shown_on_screen(); + m_tooltip_enabled = true; +} + +Size GLCanvas3D::get_canvas_size() const +{ + int w = 0; + int h = 0; + + if (m_canvas != nullptr) + m_canvas->GetSize(&w, &h); + +#if ENABLE_RETINA_GL + const float factor = m_retina_helper->get_scale_factor(); + w *= factor; + h *= factor; +#else + const float factor = 1.0f; +#endif + + return Size(w, h, factor); +} + +Vec2d GLCanvas3D::get_local_mouse_position() const +{ + if (m_canvas == nullptr) + return Vec2d::Zero(); + + wxPoint mouse_pos = m_canvas->ScreenToClient(wxGetMousePosition()); + const double factor = +#if ENABLE_RETINA_GL + m_retina_helper->get_scale_factor(); +#else + 1.0; +#endif + return Vec2d(factor * mouse_pos.x, factor * mouse_pos.y); +} + +void GLCanvas3D::set_tooltip(const std::string& tooltip) +{ + if (m_canvas != nullptr) + m_tooltip.set_text(tooltip); +} + +void GLCanvas3D::do_move(const std::string& snapshot_type) +{ + if (m_model == nullptr) + return; + + if (!snapshot_type.empty()) + wxGetApp().plater()->take_snapshot(_(snapshot_type)); + + std::set> done; // keeps track of modified instances + bool object_moved = false; + Vec3d wipe_tower_origin = Vec3d::Zero(); + + Selection::EMode selection_mode = m_selection.get_mode(); + + for (const GLVolume* v : m_volumes.volumes) { + int object_idx = v->object_idx(); + int instance_idx = v->instance_idx(); + int volume_idx = v->volume_idx(); + + std::pair done_id(object_idx, instance_idx); + + if (0 <= object_idx && object_idx < (int)m_model->objects.size()) { + done.insert(done_id); + + // Move instances/volumes + ModelObject* model_object = m_model->objects[object_idx]; + if (model_object != nullptr) { + if (selection_mode == Selection::Instance) + model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); + else if (selection_mode == Selection::Volume) + model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); + + object_moved = true; + model_object->invalidate_bounding_box(); + } + } +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + else if (v->is_wipe_tower) + // Move a wipe tower proxy. + wipe_tower_origin = v->get_volume_offset(); +#else + else if (object_idx == 1000) + // Move a wipe tower proxy. + wipe_tower_origin = v->get_volume_offset(); +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + } + + // Fixes flying instances + for (const std::pair& i : done) { + ModelObject* m = m_model->objects[i.first]; + const double shift_z = m->get_instance_min_z(i.second); + if (current_printer_technology() == ptSLA || shift_z > SINKING_Z_THRESHOLD) { + const Vec3d shift(0.0, 0.0, -shift_z); + m_selection.translate(i.first, i.second, shift); + m->translate_instance(i.second, shift); + } + wxGetApp().obj_list()->update_info_items(static_cast(i.first)); + } + + // if the selection is not valid to allow for layer editing after the move, we need to turn off the tool if it is running + // similar to void Plater::priv::selection_changed() + if (!wxGetApp().plater()->can_layers_editing() && is_layers_editing_enabled()) + post_event(SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); + + if (object_moved) + post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_MOVED)); + + if (wipe_tower_origin != Vec3d::Zero()) + post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_MOVED, std::move(wipe_tower_origin))); + + reset_sequential_print_clearance(); + + m_dirty = true; +} + +void GLCanvas3D::do_rotate(const std::string& snapshot_type) +{ + if (m_model == nullptr) + return; + + if (!snapshot_type.empty()) + wxGetApp().plater()->take_snapshot(_(snapshot_type)); + + // stores current min_z of instances + std::map, double> min_zs; + for (int i = 0; i < static_cast(m_model->objects.size()); ++i) { + const ModelObject* obj = m_model->objects[i]; + for (int j = 0; j < static_cast(obj->instances.size()); ++j) { + if (snapshot_type.empty() && m_selection.get_object_idx() == i) { + // This means we are flattening this object. In that case pretend + // that it is not sinking (even if it is), so it is placed on bed + // later on (whatever is sinking will be left sinking). + min_zs[{ i, j }] = SINKING_Z_THRESHOLD; + } else + min_zs[{ i, j }] = obj->instance_bounding_box(j).min.z(); + + } + } + + std::set> done; // keeps track of modified instances + + Selection::EMode selection_mode = m_selection.get_mode(); + + for (const GLVolume* v : m_volumes.volumes) { +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + if (v->is_wipe_tower) { +#else + int object_idx = v->object_idx(); + if (object_idx == 1000) { // the wipe tower +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + Vec3d offset = v->get_volume_offset(); + post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3d(offset(0), offset(1), v->get_volume_rotation()(2)))); + } +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + int object_idx = v->object_idx(); +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + if (object_idx < 0 || (int)m_model->objects.size() <= object_idx) + continue; + + int instance_idx = v->instance_idx(); + int volume_idx = v->volume_idx(); + + done.insert(std::pair(object_idx, instance_idx)); + + // Rotate instances/volumes. + ModelObject* model_object = m_model->objects[object_idx]; + if (model_object != nullptr) { + if (selection_mode == Selection::Instance) { + model_object->instances[instance_idx]->set_rotation(v->get_instance_rotation()); + model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); + } + else if (selection_mode == Selection::Volume) { + model_object->volumes[volume_idx]->set_rotation(v->get_volume_rotation()); + model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); + } + model_object->invalidate_bounding_box(); + } + } + + // Fixes sinking/flying instances + for (const std::pair& i : done) { + ModelObject* m = m_model->objects[i.first]; + const double shift_z = m->get_instance_min_z(i.second); + // leave sinking instances as sinking + if (min_zs.find({ i.first, i.second })->second >= SINKING_Z_THRESHOLD || shift_z > SINKING_Z_THRESHOLD) { + const Vec3d shift(0.0, 0.0, -shift_z); + m_selection.translate(i.first, i.second, shift); + m->translate_instance(i.second, shift); + } + + wxGetApp().obj_list()->update_info_items(static_cast(i.first)); + } + + if (!done.empty()) + post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED)); + + m_dirty = true; +} + +void GLCanvas3D::do_scale(const std::string& snapshot_type) +{ + if (m_model == nullptr) + return; + + if (!snapshot_type.empty()) + wxGetApp().plater()->take_snapshot(_(snapshot_type)); + + // stores current min_z of instances + std::map, double> min_zs; + if (!snapshot_type.empty()) { + for (int i = 0; i < static_cast(m_model->objects.size()); ++i) { + const ModelObject* obj = m_model->objects[i]; + for (int j = 0; j < static_cast(obj->instances.size()); ++j) { + min_zs[{ i, j }] = obj->instance_bounding_box(j).min.z(); + } + } + } + + std::set> done; // keeps track of modified instances + + Selection::EMode selection_mode = m_selection.get_mode(); + + for (const GLVolume* v : m_volumes.volumes) { + int object_idx = v->object_idx(); + if (object_idx < 0 || (int)m_model->objects.size() <= object_idx) + continue; + + int instance_idx = v->instance_idx(); + int volume_idx = v->volume_idx(); + + done.insert(std::pair(object_idx, instance_idx)); + + // Rotate instances/volumes + ModelObject* model_object = m_model->objects[object_idx]; + if (model_object != nullptr) { + if (selection_mode == Selection::Instance) { + model_object->instances[instance_idx]->set_scaling_factor(v->get_instance_scaling_factor()); + model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); + } + else if (selection_mode == Selection::Volume) { + model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); + model_object->volumes[volume_idx]->set_scaling_factor(v->get_volume_scaling_factor()); + model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); + } + model_object->invalidate_bounding_box(); + } + } + + // Fixes sinking/flying instances + for (const std::pair& i : done) { + ModelObject* m = m_model->objects[i.first]; + double shift_z = m->get_instance_min_z(i.second); + // leave sinking instances as sinking + if (min_zs.empty() || min_zs.find({ i.first, i.second })->second >= SINKING_Z_THRESHOLD || shift_z > SINKING_Z_THRESHOLD) { + Vec3d shift(0.0, 0.0, -shift_z); + m_selection.translate(i.first, i.second, shift); + m->translate_instance(i.second, shift); + } + wxGetApp().obj_list()->update_info_items(static_cast(i.first)); + } + + if (!done.empty()) + post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_SCALED)); + + m_dirty = true; +} + +void GLCanvas3D::do_mirror(const std::string& snapshot_type) +{ + if (m_model == nullptr) + return; + + if (!snapshot_type.empty()) + wxGetApp().plater()->take_snapshot(_(snapshot_type)); + + // stores current min_z of instances + std::map, double> min_zs; + if (!snapshot_type.empty()) { + for (int i = 0; i < static_cast(m_model->objects.size()); ++i) { + const ModelObject* obj = m_model->objects[i]; + for (int j = 0; j < static_cast(obj->instances.size()); ++j) { + min_zs[{ i, j }] = obj->instance_bounding_box(j).min.z(); + } + } + } + + std::set> done; // keeps track of modified instances + + Selection::EMode selection_mode = m_selection.get_mode(); + + for (const GLVolume* v : m_volumes.volumes) { + int object_idx = v->object_idx(); + if (object_idx < 0 || (int)m_model->objects.size() <= object_idx) + continue; + + int instance_idx = v->instance_idx(); + int volume_idx = v->volume_idx(); + + done.insert(std::pair(object_idx, instance_idx)); + + // Mirror instances/volumes + ModelObject* model_object = m_model->objects[object_idx]; + if (model_object != nullptr) { + if (selection_mode == Selection::Instance) + model_object->instances[instance_idx]->set_mirror(v->get_instance_mirror()); + else if (selection_mode == Selection::Volume) + model_object->volumes[volume_idx]->set_mirror(v->get_volume_mirror()); + + model_object->invalidate_bounding_box(); + } + } + + // Fixes sinking/flying instances + for (const std::pair& i : done) { + ModelObject* m = m_model->objects[i.first]; + double shift_z = m->get_instance_min_z(i.second); + // leave sinking instances as sinking + if (min_zs.empty() || min_zs.find({ i.first, i.second })->second >= SINKING_Z_THRESHOLD || shift_z > SINKING_Z_THRESHOLD) { + Vec3d shift(0.0, 0.0, -shift_z); + m_selection.translate(i.first, i.second, shift); + m->translate_instance(i.second, shift); + } + wxGetApp().obj_list()->update_info_items(static_cast(i.first)); + } + + post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + + m_dirty = true; +} + +void GLCanvas3D::update_gizmos_on_off_state() +{ + set_as_dirty(); + m_gizmos.update_data(); + m_gizmos.refresh_on_off_state(); +} + +void GLCanvas3D::handle_sidebar_focus_event(const std::string& opt_key, bool focus_on) +{ + m_sidebar_field = focus_on ? opt_key : ""; + if (!m_sidebar_field.empty()) + m_gizmos.reset_all_states(); + + m_dirty = true; +} + +void GLCanvas3D::handle_layers_data_focus_event(const t_layer_height_range range, const EditorType type) +{ + std::string field = "layer_" + std::to_string(type) + "_" + std::to_string(range.first) + "_" + std::to_string(range.second); + handle_sidebar_focus_event(field, true); +} + +void GLCanvas3D::update_ui_from_settings() +{ + m_dirty = true; + +#if __APPLE__ + // Update OpenGL scaling on OSX after the user toggled the "use_retina_opengl" settings in Preferences dialog. + const float orig_scaling = m_retina_helper->get_scale_factor(); + + const bool use_retina = wxGetApp().app_config->get("use_retina_opengl") == "1"; + BOOST_LOG_TRIVIAL(debug) << "GLCanvas3D: Use Retina OpenGL: " << use_retina; + m_retina_helper->set_use_retina(use_retina); + const float new_scaling = m_retina_helper->get_scale_factor(); + + if (new_scaling != orig_scaling) { + BOOST_LOG_TRIVIAL(debug) << "GLCanvas3D: Scaling factor: " << new_scaling; + + Camera& camera = wxGetApp().plater()->get_camera(); + camera.set_zoom(camera.get_zoom() * new_scaling / orig_scaling); + _refresh_if_shown_on_screen(); + } +#endif // ENABLE_RETINA_GL + + if (wxGetApp().is_editor()) + wxGetApp().plater()->enable_collapse_toolbar(wxGetApp().app_config->get("show_collapse_button") == "1"); +} + +GLCanvas3D::WipeTowerInfo GLCanvas3D::get_wipe_tower_info() const +{ + WipeTowerInfo wti; + + for (const GLVolume* vol : m_volumes.volumes) { + if (vol->is_wipe_tower) { + wti.m_pos = Vec2d(m_config->opt_float("wipe_tower_x"), + m_config->opt_float("wipe_tower_y")); + wti.m_rotation = (M_PI/180.) * m_config->opt_float("wipe_tower_rotation_angle"); + const BoundingBoxf3& bb = vol->bounding_box(); + wti.m_bb = BoundingBoxf{to_2d(bb.min), to_2d(bb.max)}; + break; + } + } + + return wti; +} + +Linef3 GLCanvas3D::mouse_ray(const Point& mouse_pos) +{ + float z0 = 0.0f; + float z1 = 1.0f; + return Linef3(_mouse_to_3d(mouse_pos, &z0), _mouse_to_3d(mouse_pos, &z1)); +} + +double GLCanvas3D::get_size_proportional_to_max_bed_size(double factor) const +{ + const BoundingBoxf& bbox = m_bed.build_volume().bounding_volume2d(); + return factor * std::max(bbox.size()[0], bbox.size()[1]); +} + +void GLCanvas3D::set_cursor(ECursorType type) +{ + if ((m_canvas != nullptr) && (m_cursor_type != type)) + { + switch (type) + { + case Standard: { m_canvas->SetCursor(*wxSTANDARD_CURSOR); break; } + case Cross: { m_canvas->SetCursor(*wxCROSS_CURSOR); break; } + } + + m_cursor_type = type; + } +} + +void GLCanvas3D::msw_rescale() +{ +#if ENABLE_PREVIEW_LAYOUT + m_gcode_viewer.invalidate_legend(); +#endif // ENABLE_PREVIEW_LAYOUT +} + +void GLCanvas3D::update_tooltip_for_settings_item_in_main_toolbar() +{ + std::string new_tooltip = _u8L("Switch to Settings") + + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "2] - " + _u8L("Print Settings Tab") + + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "3] - " + (current_printer_technology() == ptFFF ? _u8L("Filament Settings Tab") : _u8L("Material Settings Tab")) + + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "4] - " + _u8L("Printer Settings Tab") ; + + m_main_toolbar.set_tooltip(get_main_toolbar_item_id("settings"), new_tooltip); +} + +bool GLCanvas3D::has_toolpaths_to_export() const +{ + return m_gcode_viewer.can_export_toolpaths(); +} + +void GLCanvas3D::export_toolpaths_to_obj(const char* filename) const +{ + m_gcode_viewer.export_toolpaths_to_obj(filename); +} + +void GLCanvas3D::mouse_up_cleanup() +{ + m_moving = false; + m_mouse.drag.move_volume_idx = -1; + m_mouse.set_start_position_3D_as_invalid(); + m_mouse.set_start_position_2D_as_invalid(); + m_mouse.dragging = false; + m_mouse.ignore_left_up = false; + m_dirty = true; + + if (m_canvas->HasCapture()) + m_canvas->ReleaseMouse(); +} + +void GLCanvas3D::update_sequential_clearance() +{ + if (current_printer_technology() != ptFFF || !fff_print()->config().complete_objects) + return; + + if (m_layers_editing.is_enabled() || m_gizmos.is_dragging()) + return; + + // collects instance transformations from volumes + // first define temporary cache + unsigned int instances_count = 0; + std::vector>> instance_transforms; + for (size_t obj = 0; obj < m_model->objects.size(); ++obj) { + instance_transforms.emplace_back(std::vector>()); + const ModelObject* model_object = m_model->objects[obj]; + for (size_t i = 0; i < model_object->instances.size(); ++i) { + instance_transforms[obj].emplace_back(std::optional()); + ++instances_count; + } + } + + if (instances_count == 1) + return; + + // second fill temporary cache with data from volumes + for (const GLVolume* v : m_volumes.volumes) { + if (v->is_modifier || v->is_wipe_tower) + continue; + + auto& transform = instance_transforms[v->object_idx()][v->instance_idx()]; + if (!transform.has_value()) + transform = v->get_instance_transformation(); + } + + // calculates objects 2d hulls (see also: Print::sequential_print_horizontal_clearance_valid()) + // this is done only the first time this method is called while moving the mouse, + // the results are then cached for following displacements + if (m_sequential_print_clearance_first_displacement) { + m_sequential_print_clearance.m_hull_2d_cache.clear(); + float shrink_factor = static_cast(scale_(0.5 * fff_print()->config().extruder_clearance_radius.value - EPSILON)); + double mitter_limit = scale_(0.1); + m_sequential_print_clearance.m_hull_2d_cache.reserve(m_model->objects.size()); + for (size_t i = 0; i < m_model->objects.size(); ++i) { + ModelObject* model_object = m_model->objects[i]; + ModelInstance* model_instance0 = model_object->instances.front(); + Polygon hull_2d = offset(model_object->convex_hull_2d(Geometry::assemble_transform({ 0.0, 0.0, model_instance0->get_offset().z() }, model_instance0->get_rotation(), + model_instance0->get_scaling_factor(), model_instance0->get_mirror())), + // 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. + shrink_factor, + jtRound, mitter_limit).front(); + + Pointf3s& cache_hull_2d = m_sequential_print_clearance.m_hull_2d_cache.emplace_back(Pointf3s()); + cache_hull_2d.reserve(hull_2d.points.size()); + for (const Point& p : hull_2d.points) { + cache_hull_2d.emplace_back(unscale(p.x()), unscale(p.y()), 0.0); + } + } + m_sequential_print_clearance_first_displacement = false; + } + + // calculates instances 2d hulls (see also: Print::sequential_print_horizontal_clearance_valid()) + Polygons polygons; + polygons.reserve(instances_count); + for (size_t i = 0; i < instance_transforms.size(); ++i) { + const auto& instances = instance_transforms[i]; + double rotation_z0 = instances.front()->get_rotation().z(); + for (const auto& instance : instances) { + Geometry::Transformation transformation; + 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]; + Points inst_pts; + inst_pts.reserve(hull_2d.size()); + for (size_t j = 0; j < hull_2d.size(); ++j) { + const Vec3d p = trafo * hull_2d[j]; + inst_pts.emplace_back(scaled(p.x()), scaled(p.y())); + } + polygons.emplace_back(Geometry::convex_hull(std::move(inst_pts))); + } + } + + // sends instances 2d hulls to be rendered + set_sequential_print_clearance_visible(true); + set_sequential_print_clearance_render_fill(false); + set_sequential_print_clearance_polygons(polygons); +} + +bool GLCanvas3D::is_object_sinking(int object_idx) const +{ + for (const GLVolume* v : m_volumes.volumes) { + if (v->object_idx() == object_idx && (v->is_sinking() || (!v->is_modifier && v->is_below_printbed()))) + return true; + } + return false; +} + +bool GLCanvas3D::_is_shown_on_screen() const +{ + return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false; +} + +// Getter for the const char*[] +static bool string_getter(const bool is_undo, int idx, const char** out_text) +{ + return wxGetApp().plater()->undo_redo_string_getter(is_undo, idx, out_text); +} + +bool GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x) +{ + bool action_taken = false; + + ImGuiWrapper* imgui = wxGetApp().imgui(); + +#if ENABLE_GL_SHADERS_ATTRIBUTES + imgui->set_next_window_pos(pos_x, m_undoredo_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); +#else + const float x = pos_x * (float)wxGetApp().plater()->get_camera().get_zoom() + 0.5f * (float)get_canvas_size().get_width(); + imgui->set_next_window_pos(x, m_undoredo_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + std::string title = is_undo ? L("Undo History") : L("Redo History"); + imgui->begin(_(title), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + + int hovered = m_imgui_undo_redo_hovered_pos; + int selected = -1; + float em = static_cast(wxGetApp().em_unit()); +#if ENABLE_RETINA_GL + em *= m_retina_helper->get_scale_factor(); +#endif + + if (imgui->undo_redo_list(ImVec2(18 * em, 26 * em), is_undo, &string_getter, hovered, selected, m_mouse_wheel)) + m_imgui_undo_redo_hovered_pos = hovered; + else + m_imgui_undo_redo_hovered_pos = -1; + + if (selected >= 0) { + is_undo ? wxGetApp().plater()->undo_to(selected) : wxGetApp().plater()->redo_to(selected); + action_taken = true; + } + + imgui->text(wxString::Format(is_undo ? _L_PLURAL("Undo %1$d Action", "Undo %1$d Actions", hovered + 1) : _L_PLURAL("Redo %1$d Action", "Redo %1$d Actions", hovered + 1), hovered + 1)); + + imgui->end(); + + return action_taken; +} + +// Getter for the const char*[] for the search list +static bool search_string_getter(int idx, const char** label, const char** tooltip) +{ + return wxGetApp().plater()->search_string_getter(idx, label, tooltip); +} + +bool GLCanvas3D::_render_search_list(float pos_x) +{ + bool action_taken = false; + ImGuiWrapper* imgui = wxGetApp().imgui(); + +#if ENABLE_GL_SHADERS_ATTRIBUTES + imgui->set_next_window_pos(pos_x, m_main_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); +#else + const float x = /*pos_x * (float)wxGetApp().plater()->get_camera().get_zoom() + */0.5f * (float)get_canvas_size().get_width(); + imgui->set_next_window_pos(x, m_main_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + std::string title = L("Search"); + imgui->begin(_(title), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + + int selected = -1; + bool edited = false; + float em = static_cast(wxGetApp().em_unit()); +#if ENABLE_RETINA_GL + em *= m_retina_helper->get_scale_factor(); +#endif // ENABLE_RETINA_GL + + Sidebar& sidebar = wxGetApp().sidebar(); + + std::string& search_line = sidebar.get_search_line(); + char *s = new char[255]; + strcpy(s, search_line.empty() ? _u8L("Enter a search term").c_str() : search_line.c_str()); + + imgui->search_list(ImVec2(45 * em, 30 * em), &search_string_getter, s, + sidebar.get_searcher().view_params, + selected, edited, m_mouse_wheel, wxGetApp().is_localized()); + + search_line = s; + delete [] s; + if (search_line == _u8L("Enter a search term")) + search_line.clear(); + + if (edited) + sidebar.search(); + + if (selected >= 0) { + // selected == 9999 means that Esc kye was pressed + /*// revert commit https://github.com/prusa3d/PrusaSlicer/commit/91897589928789b261ca0dc735ffd46f2b0b99f2 + if (selected == 9999) + action_taken = true; + else + sidebar.jump_to_option(selected);*/ + if (selected != 9999) { + imgui->end(); // end imgui before the jump to option + sidebar.jump_to_option(selected); + return true; + } + action_taken = true; + } + + imgui->end(); + + return action_taken; +} + +bool GLCanvas3D::_render_arrange_menu(float pos_x) +{ + ImGuiWrapper *imgui = wxGetApp().imgui(); + +#if ENABLE_GL_SHADERS_ATTRIBUTES + imgui->set_next_window_pos(pos_x, m_main_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); +#else + auto canvas_w = float(get_canvas_size().get_width()); + const float x = pos_x * float(wxGetApp().plater()->get_camera().get_zoom()) + 0.5f * canvas_w; + imgui->set_next_window_pos(x, m_main_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + imgui->begin(_L("Arrange options"), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + + ArrangeSettings settings = get_arrange_settings(); + ArrangeSettings &settings_out = get_arrange_settings(); + + auto &appcfg = wxGetApp().app_config; + PrinterTechnology ptech = current_printer_technology(); + + bool settings_changed = false; + float dist_min = 0.f; + std::string dist_key = "min_object_distance", rot_key = "enable_rotation"; + std::string postfix; + + if (ptech == ptSLA) { + dist_min = 0.f; + postfix = "_sla"; + } else if (ptech == ptFFF) { + auto co_opt = m_config->option("complete_objects"); + if (co_opt && co_opt->value) { + dist_min = float(min_object_distance(*m_config)); + postfix = "_fff_seq_print"; + } else { + dist_min = 0.f; + postfix = "_fff"; + } + } + + dist_key += postfix; + rot_key += postfix; + + imgui->text(GUI::format_wxstr(_L("Press %1%left mouse button to enter the exact value"), shortkey_ctrl_prefix())); + + if (imgui->slider_float(_L("Spacing"), &settings.distance, dist_min, 100.0f, "%5.2f") || dist_min > settings.distance) { + settings.distance = std::max(dist_min, settings.distance); + settings_out.distance = settings.distance; + appcfg->set("arrange", dist_key.c_str(), float_to_string_decimal_point(settings_out.distance)); + settings_changed = true; + } + + if (imgui->checkbox(_L("Enable rotations (slow)"), settings.enable_rotation)) { + settings_out.enable_rotation = settings.enable_rotation; + appcfg->set("arrange", rot_key.c_str(), settings_out.enable_rotation? "1" : "0"); + settings_changed = true; + } + + ImGui::Separator(); + + if (imgui->button(_L("Reset"))) { + settings_out = ArrangeSettings{}; + settings_out.distance = std::max(dist_min, settings_out.distance); + appcfg->set("arrange", dist_key.c_str(), float_to_string_decimal_point(settings_out.distance)); + appcfg->set("arrange", rot_key.c_str(), settings_out.enable_rotation? "1" : "0"); + settings_changed = true; + } + + ImGui::SameLine(); + + if (imgui->button(_L("Arrange"))) { + wxGetApp().plater()->arrange(); + } + + imgui->end(); + + return settings_changed; +} + +#define ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT 0 +#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT +static void debug_output_thumbnail(const ThumbnailData& thumbnail_data) +{ + // debug export of generated image + wxImage image(thumbnail_data.width, thumbnail_data.height); + image.InitAlpha(); + + for (unsigned int r = 0; r < thumbnail_data.height; ++r) + { + unsigned int rr = (thumbnail_data.height - 1 - r) * thumbnail_data.width; + for (unsigned int c = 0; c < thumbnail_data.width; ++c) + { + unsigned char* px = (unsigned char*)thumbnail_data.pixels.data() + 4 * (rr + c); + image.SetRGB((int)c, (int)r, px[0], px[1], px[2]); + image.SetAlpha((int)c, (int)r, px[3]); + } + } + + image.SaveFile("C:/prusa/test/test.png", wxBITMAP_TYPE_PNG); +} +#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT + +void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type) +{ + auto is_visible = [](const GLVolume& v) { + bool ret = v.printable; + ret &= (!v.shader_outside_printer_detection_enabled || !v.is_outside); + return ret; + }; + + GLVolumePtrs visible_volumes; + + for (GLVolume* vol : volumes.volumes) { + if (!vol->is_modifier && !vol->is_wipe_tower && (!thumbnail_params.parts_only || vol->composite_id.volume_id >= 0)) { + if (!thumbnail_params.printable_only || is_visible(*vol)) + visible_volumes.emplace_back(vol); + } + } + + BoundingBoxf3 volumes_box; + if (!visible_volumes.empty()) { + for (const GLVolume* vol : visible_volumes) { + volumes_box.merge(vol->transformed_bounding_box()); + } + } + else + // This happens for empty projects + volumes_box = m_bed.extended_bounding_box(); + + Camera camera; + camera.set_type(camera_type); + camera.set_scene_box(scene_bounding_box()); + camera.apply_viewport(0, 0, thumbnail_data.width, thumbnail_data.height); + camera.zoom_to_box(volumes_box); +#if !ENABLE_LEGACY_OPENGL_REMOVAL + camera.apply_view_matrix(); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d& view_matrix = camera.get_view_matrix(); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + double near_z = -1.0; + double far_z = -1.0; + + if (thumbnail_params.show_bed) { + // extends the near and far z of the frustrum to avoid the bed being clipped + + // box in eye space +#if ENABLE_GL_SHADERS_ATTRIBUTES + const BoundingBoxf3 t_bed_box = m_bed.extended_bounding_box().transformed(view_matrix); +#else + const BoundingBoxf3 t_bed_box = m_bed.extended_bounding_box().transformed(camera.get_view_matrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + near_z = -t_bed_box.max.z(); + far_z = -t_bed_box.min.z(); + } + + camera.apply_projection(volumes_box, near_z, far_z); + + GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); + if (shader == nullptr) + return; + + if (thumbnail_params.transparent_background) + glsafe(::glClearColor(0.0f, 0.0f, 0.0f, 0.0f)); + + glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); + glsafe(::glEnable(GL_DEPTH_TEST)); + + shader->start_using(); + shader->set_uniform("emission_factor", 0.0f); + +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d& projection_matrix = camera.get_projection_matrix(); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + for (GLVolume* vol : visible_volumes) { +#if ENABLE_LEGACY_OPENGL_REMOVAL + vol->model.set_color((vol->printable && !vol->is_outside) ? (current_printer_technology() == ptSLA ? vol->color : ColorRGBA::ORANGE()) : ColorRGBA::GRAY()); +#else + shader->set_uniform("uniform_color", (vol->printable && !vol->is_outside) ? (current_printer_technology() == ptSLA ? vol->color : ColorRGBA::ORANGE()) : ColorRGBA::GRAY()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + // the volume may have been deactivated by an active gizmo + const bool is_active = vol->is_active; + vol->is_active = true; +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d matrix = view_matrix * vol->world_matrix(); + shader->set_uniform("view_model_matrix", matrix); + shader->set_uniform("projection_matrix", projection_matrix); + shader->set_uniform("normal_matrix", (Matrix3d)matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + vol->render(); + vol->is_active = is_active; + } + + shader->stop_using(); + + glsafe(::glDisable(GL_DEPTH_TEST)); + + if (thumbnail_params.show_bed) +#if ENABLE_GL_SHADERS_ATTRIBUTES + _render_bed(view_matrix, projection_matrix, !camera.is_looking_downward(), false); +#else + _render_bed(!camera.is_looking_downward(), false); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + // restore background color + if (thumbnail_params.transparent_background) + glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 1.0f)); +} + +void GLCanvas3D::_render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type) +{ + thumbnail_data.set(w, h); + if (!thumbnail_data.is_valid()) + return; + + bool multisample = m_multisample_allowed; + if (multisample) + glsafe(::glEnable(GL_MULTISAMPLE)); + + GLint max_samples; + glsafe(::glGetIntegerv(GL_MAX_SAMPLES, &max_samples)); + GLsizei num_samples = max_samples / 2; + + GLuint render_fbo; + glsafe(::glGenFramebuffers(1, &render_fbo)); + glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, render_fbo)); + + GLuint render_tex = 0; + GLuint render_tex_buffer = 0; + if (multisample) { + // use renderbuffer instead of texture to avoid the need to use glTexImage2DMultisample which is available only since OpenGL 3.2 + glsafe(::glGenRenderbuffers(1, &render_tex_buffer)); + glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, render_tex_buffer)); + glsafe(::glRenderbufferStorageMultisample(GL_RENDERBUFFER, num_samples, GL_RGBA8, w, h)); + glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, render_tex_buffer)); + } + else { + glsafe(::glGenTextures(1, &render_tex)); + glsafe(::glBindTexture(GL_TEXTURE_2D, render_tex)); + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, render_tex, 0)); + } + + GLuint render_depth; + glsafe(::glGenRenderbuffers(1, &render_depth)); + glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, render_depth)); + if (multisample) + glsafe(::glRenderbufferStorageMultisample(GL_RENDERBUFFER, num_samples, GL_DEPTH_COMPONENT24, w, h)); + else + glsafe(::glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, w, h)); + + glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, render_depth)); + + GLenum drawBufs[] = { GL_COLOR_ATTACHMENT0 }; + glsafe(::glDrawBuffers(1, drawBufs)); + + if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) { + _render_thumbnail_internal(thumbnail_data, thumbnail_params, volumes, camera_type); + + if (multisample) { + GLuint resolve_fbo; + glsafe(::glGenFramebuffers(1, &resolve_fbo)); + glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, resolve_fbo)); + + GLuint resolve_tex; + glsafe(::glGenTextures(1, &resolve_tex)); + glsafe(::glBindTexture(GL_TEXTURE_2D, resolve_tex)); + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, resolve_tex, 0)); + + glsafe(::glDrawBuffers(1, drawBufs)); + + if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) { + glsafe(::glBindFramebuffer(GL_READ_FRAMEBUFFER, render_fbo)); + glsafe(::glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolve_fbo)); + glsafe(::glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_LINEAR)); + + glsafe(::glBindFramebuffer(GL_READ_FRAMEBUFFER, resolve_fbo)); + glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); + } + + glsafe(::glDeleteTextures(1, &resolve_tex)); + glsafe(::glDeleteFramebuffers(1, &resolve_fbo)); + } + else + glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); + +#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT + debug_output_thumbnail(thumbnail_data); +#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT + } + + glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, 0)); + glsafe(::glDeleteRenderbuffers(1, &render_depth)); + if (render_tex_buffer != 0) + glsafe(::glDeleteRenderbuffers(1, &render_tex_buffer)); + if (render_tex != 0) + glsafe(::glDeleteTextures(1, &render_tex)); + glsafe(::glDeleteFramebuffers(1, &render_fbo)); + + if (multisample) + glsafe(::glDisable(GL_MULTISAMPLE)); +} + +void GLCanvas3D::_render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type) +{ + thumbnail_data.set(w, h); + if (!thumbnail_data.is_valid()) + return; + + bool multisample = m_multisample_allowed; + if (multisample) + glsafe(::glEnable(GL_MULTISAMPLE)); + + GLint max_samples; + glsafe(::glGetIntegerv(GL_MAX_SAMPLES_EXT, &max_samples)); + GLsizei num_samples = max_samples / 2; + + GLuint render_fbo; + glsafe(::glGenFramebuffersEXT(1, &render_fbo)); + glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, render_fbo)); + + GLuint render_tex = 0; + GLuint render_tex_buffer = 0; + if (multisample) { + // use renderbuffer instead of texture to avoid the need to use glTexImage2DMultisample which is available only since OpenGL 3.2 + glsafe(::glGenRenderbuffersEXT(1, &render_tex_buffer)); + glsafe(::glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, render_tex_buffer)); + glsafe(::glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, num_samples, GL_RGBA8, w, h)); + glsafe(::glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, render_tex_buffer)); + } + else { + glsafe(::glGenTextures(1, &render_tex)); + glsafe(::glBindTexture(GL_TEXTURE_2D, render_tex)); + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, render_tex, 0)); + } + + GLuint render_depth; + glsafe(::glGenRenderbuffersEXT(1, &render_depth)); + glsafe(::glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, render_depth)); + if (multisample) + glsafe(::glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, num_samples, GL_DEPTH_COMPONENT24, w, h)); + else + glsafe(::glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, w, h)); + + glsafe(::glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, render_depth)); + + GLenum drawBufs[] = { GL_COLOR_ATTACHMENT0 }; + glsafe(::glDrawBuffers(1, drawBufs)); + + if (::glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT) { + _render_thumbnail_internal(thumbnail_data, thumbnail_params, volumes, camera_type); + + if (multisample) { + GLuint resolve_fbo; + glsafe(::glGenFramebuffersEXT(1, &resolve_fbo)); + glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, resolve_fbo)); + + GLuint resolve_tex; + glsafe(::glGenTextures(1, &resolve_tex)); + glsafe(::glBindTexture(GL_TEXTURE_2D, resolve_tex)); + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + glsafe(::glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, resolve_tex, 0)); + + glsafe(::glDrawBuffers(1, drawBufs)); + + if (::glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT) { + glsafe(::glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, render_fbo)); + glsafe(::glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, resolve_fbo)); + glsafe(::glBlitFramebufferEXT(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_LINEAR)); + + glsafe(::glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, resolve_fbo)); + glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); + } + + glsafe(::glDeleteTextures(1, &resolve_tex)); + glsafe(::glDeleteFramebuffersEXT(1, &resolve_fbo)); + } + else + glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); + +#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT + debug_output_thumbnail(thumbnail_data); +#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT + } + + glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0)); + glsafe(::glDeleteRenderbuffersEXT(1, &render_depth)); + if (render_tex_buffer != 0) + glsafe(::glDeleteRenderbuffersEXT(1, &render_tex_buffer)); + if (render_tex != 0) + glsafe(::glDeleteTextures(1, &render_tex)); + glsafe(::glDeleteFramebuffersEXT(1, &render_fbo)); + + if (multisample) + glsafe(::glDisable(GL_MULTISAMPLE)); +} + +void GLCanvas3D::_render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type) +{ + // check that thumbnail size does not exceed the default framebuffer size + const Size& cnv_size = get_canvas_size(); + unsigned int cnv_w = (unsigned int)cnv_size.get_width(); + unsigned int cnv_h = (unsigned int)cnv_size.get_height(); + if (w > cnv_w || h > cnv_h) { + float ratio = std::min((float)cnv_w / (float)w, (float)cnv_h / (float)h); + w = (unsigned int)(ratio * (float)w); + h = (unsigned int)(ratio * (float)h); + } + + thumbnail_data.set(w, h); + if (!thumbnail_data.is_valid()) + return; + + _render_thumbnail_internal(thumbnail_data, thumbnail_params, volumes, camera_type); + + glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); +#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT + debug_output_thumbnail(thumbnail_data); +#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT + + // restore the default framebuffer size to avoid flickering on the 3D scene + wxGetApp().plater()->get_camera().apply_viewport(0, 0, cnv_size.get_width(), cnv_size.get_height()); +} + +bool GLCanvas3D::_init_toolbars() +{ + if (!_init_main_toolbar()) + return false; + + if (!_init_undoredo_toolbar()) + return false; + + if (!_init_view_toolbar()) + return false; + + if (!_init_collapse_toolbar()) + return false; + + return true; +} + +bool GLCanvas3D::_init_main_toolbar() +{ + if (!m_main_toolbar.is_enabled()) + return true; + + BackgroundTexture::Metadata background_data; + background_data.filename = "toolbar_background.png"; + background_data.left = 16; + background_data.top = 16; + background_data.right = 16; + background_data.bottom = 16; + + if (!m_main_toolbar.init(background_data)) { + // unable to init the toolbar texture, disable it + m_main_toolbar.set_enabled(false); + return true; + } + // init arrow +#if ENABLE_GL_SHADERS_ATTRIBUTES + if (!m_main_toolbar.init_arrow("toolbar_arrow_2.svg")) +#else + BackgroundTexture::Metadata arrow_data; + arrow_data.filename = "toolbar_arrow.svg"; + arrow_data.left = 0; + arrow_data.top = 0; + arrow_data.right = 0; + arrow_data.bottom = 0; + if (!m_main_toolbar.init_arrow(arrow_data)) +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + BOOST_LOG_TRIVIAL(error) << "Main toolbar failed to load arrow texture."; + + // m_gizmos is created at constructor, thus we can init arrow here. +#if ENABLE_GL_SHADERS_ATTRIBUTES + if (!m_gizmos.init_arrow("toolbar_arrow_2.svg")) +#else + if (!m_gizmos.init_arrow(arrow_data)) +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + BOOST_LOG_TRIVIAL(error) << "Gizmos manager failed to load arrow texture."; + +// m_main_toolbar.set_layout_type(GLToolbar::Layout::Vertical); + m_main_toolbar.set_layout_type(GLToolbar::Layout::Horizontal); + m_main_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Right); + m_main_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top); + m_main_toolbar.set_border(5.0f); + m_main_toolbar.set_separator_size(5); + m_main_toolbar.set_gap_size(4); + + GLToolbarItem::Data item; + + item.name = "add"; + item.icon_filename = "add.svg"; + item.tooltip = _utf8(L("Add...")) + " [" + GUI::shortkey_ctrl_prefix() + "I]"; + item.sprite_id = 0; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ADD)); }; + if (!m_main_toolbar.add_item(item)) + return false; + + item.name = "delete"; + item.icon_filename = "remove.svg"; + item.tooltip = _utf8(L("Delete")) + " [Del]"; + item.sprite_id = 1; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE)); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_delete(); }; + if (!m_main_toolbar.add_item(item)) + return false; + + item.name = "deleteall"; + item.icon_filename = "delete_all.svg"; + item.tooltip = _utf8(L("Delete all")) + " [" + GUI::shortkey_ctrl_prefix() + "Del]"; + item.sprite_id = 2; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_delete_all(); }; + if (!m_main_toolbar.add_item(item)) + return false; + + item.name = "arrange"; + item.icon_filename = "arrange.svg"; + item.tooltip = _utf8(L("Arrange")) + " [A]\n" + _utf8(L("Arrange selection")) + " [Shift+A]\n" + _utf8(L("Click right mouse button to show arrangement options")); + item.sprite_id = 3; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ARRANGE)); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_arrange(); }; + item.right.toggable = true; + item.right.render_callback = [this](float left, float right, float, float) { + if (m_canvas != nullptr) + _render_arrange_menu(0.5f * (left + right)); + }; + if (!m_main_toolbar.add_item(item)) + return false; + + item.right.toggable = false; + item.right.render_callback = GLToolbarItem::Default_Render_Callback; + + if (!m_main_toolbar.add_separator()) + return false; + + item.name = "copy"; + item.icon_filename = "copy.svg"; + item.tooltip = _utf8(L("Copy")) + " [" + GUI::shortkey_ctrl_prefix() + "C]"; + item.sprite_id = 4; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_COPY)); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_copy_to_clipboard(); }; + if (!m_main_toolbar.add_item(item)) + return false; + + item.name = "paste"; + item.icon_filename = "paste.svg"; + item.tooltip = _utf8(L("Paste")) + " [" + GUI::shortkey_ctrl_prefix() + "V]"; + item.sprite_id = 5; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_PASTE)); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_paste_from_clipboard(); }; + if (!m_main_toolbar.add_item(item)) + return false; + + if (!m_main_toolbar.add_separator()) + return false; + + item.name = "more"; + item.icon_filename = "instance_add.svg"; + item.tooltip = _utf8(L("Add instance")) + " [+]"; + item.sprite_id = 6; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_MORE)); }; + item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_increase_instances(); }; + + if (!m_main_toolbar.add_item(item)) + return false; + + item.name = "fewer"; + item.icon_filename = "instance_remove.svg"; + item.tooltip = _utf8(L("Remove instance")) + " [-]"; + item.sprite_id = 7; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_FEWER)); }; + item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_decrease_instances(); }; + if (!m_main_toolbar.add_item(item)) + return false; + + if (!m_main_toolbar.add_separator()) + return false; + + item.name = "splitobjects"; + item.icon_filename = "split_objects.svg"; + item.tooltip = _utf8(L("Split to objects")); + item.sprite_id = 8; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_OBJECTS)); }; + item.visibility_callback = GLToolbarItem::Default_Visibility_Callback; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_split_to_objects(); }; + if (!m_main_toolbar.add_item(item)) + return false; + + item.name = "splitvolumes"; + item.icon_filename = "split_parts.svg"; + item.tooltip = _utf8(L("Split to parts")); + item.sprite_id = 9; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_VOLUMES)); }; + item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_split_to_volumes(); }; + if (!m_main_toolbar.add_item(item)) + return false; + + if (!m_main_toolbar.add_separator()) + return false; + + item.name = "settings"; + item.icon_filename = "settings.svg"; + item.tooltip = _u8L("Switch to Settings") + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "2] - " + _u8L("Print Settings Tab") + + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "3] - " + (current_printer_technology() == ptFFF ? _u8L("Filament Settings Tab") : _u8L("Material Settings Tab")) + + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "4] - " + _u8L("Printer Settings Tab") ; + item.sprite_id = 10; + item.enabling_callback = GLToolbarItem::Default_Enabling_Callback; + item.visibility_callback = []() { return (wxGetApp().app_config->get("new_settings_layout_mode") == "1" || + wxGetApp().app_config->get("dlg_settings_layout_mode") == "1"); }; + item.left.action_callback = []() { wxGetApp().mainframe->select_tab(); }; + if (!m_main_toolbar.add_item(item)) + return false; + + /* + if (!m_main_toolbar.add_separator()) + return false; + */ + + item.name = "search"; + item.icon_filename = "search_.svg"; + item.tooltip = _utf8(L("Search")) + " [" + GUI::shortkey_ctrl_prefix() + "F]"; + item.sprite_id = 11; + item.left.toggable = true; + item.left.render_callback = [this](float left, float right, float, float) { + if (m_canvas != nullptr) { + if (_render_search_list(0.5f * (left + right))) + _deactivate_search_toolbar_item(); + } + }; + item.left.action_callback = GLToolbarItem::Default_Action_Callback; + item.visibility_callback = GLToolbarItem::Default_Visibility_Callback; + item.enabling_callback = GLToolbarItem::Default_Enabling_Callback; + if (!m_main_toolbar.add_item(item)) + return false; + + if (!m_main_toolbar.add_separator()) + return false; + + item.name = "layersediting"; + item.icon_filename = "layers_white.svg"; + item.tooltip = _utf8(L("Variable layer height")); + item.sprite_id = 12; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); }; + item.visibility_callback = [this]()->bool { + bool res = current_printer_technology() == ptFFF; + // turns off if changing printer technology + if (!res && m_main_toolbar.is_item_visible("layersediting") && m_main_toolbar.is_item_pressed("layersediting")) + force_main_toolbar_left_action(get_main_toolbar_item_id("layersediting")); + + return res; + }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_layers_editing(); }; + item.left.render_callback = GLToolbarItem::Default_Render_Callback; + if (!m_main_toolbar.add_item(item)) + return false; + + return true; +} + +bool GLCanvas3D::_init_undoredo_toolbar() +{ + if (!m_undoredo_toolbar.is_enabled()) + return true; + + BackgroundTexture::Metadata background_data; + background_data.filename = "toolbar_background.png"; + background_data.left = 16; + background_data.top = 16; + background_data.right = 16; + background_data.bottom = 16; + + if (!m_undoredo_toolbar.init(background_data)) { + // unable to init the toolbar texture, disable it + m_undoredo_toolbar.set_enabled(false); + return true; + } + + // init arrow +#if ENABLE_GL_SHADERS_ATTRIBUTES + if (!m_undoredo_toolbar.init_arrow("toolbar_arrow_2.svg")) +#else + BackgroundTexture::Metadata arrow_data; + arrow_data.filename = "toolbar_arrow.svg"; + arrow_data.left = 0; + arrow_data.top = 0; + arrow_data.right = 0; + arrow_data.bottom = 0; + if (!m_undoredo_toolbar.init_arrow(arrow_data)) +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + BOOST_LOG_TRIVIAL(error) << "Undo/Redo toolbar failed to load arrow texture."; + +// m_undoredo_toolbar.set_layout_type(GLToolbar::Layout::Vertical); + m_undoredo_toolbar.set_layout_type(GLToolbar::Layout::Horizontal); + m_undoredo_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Left); + m_undoredo_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top); + m_undoredo_toolbar.set_border(5.0f); + m_undoredo_toolbar.set_separator_size(5); + m_undoredo_toolbar.set_gap_size(4); + + GLToolbarItem::Data item; + + item.name = "undo"; + item.icon_filename = "undo_toolbar.svg"; + item.tooltip = _utf8(L("Undo")) + " [" + GUI::shortkey_ctrl_prefix() + "Z]\n" + _utf8(L("Click right mouse button to open/close History")); + item.sprite_id = 0; + item.left.action_callback = [this]() { post_event(SimpleEvent(EVT_GLCANVAS_UNDO)); }; + item.right.toggable = true; + item.right.action_callback = [this]() { m_imgui_undo_redo_hovered_pos = -1; }; + item.right.render_callback = [this](float left, float right, float, float) { + if (m_canvas != nullptr) { + if (_render_undo_redo_stack(true, 0.5f * (left + right))) + _deactivate_undo_redo_toolbar_items(); + } + }; + item.enabling_callback = [this]()->bool { + bool can_undo = wxGetApp().plater()->can_undo(); + int id = m_undoredo_toolbar.get_item_id("undo"); + + std::string curr_additional_tooltip; + m_undoredo_toolbar.get_additional_tooltip(id, curr_additional_tooltip); + + std::string new_additional_tooltip; + if (can_undo) { + std::string action; + wxGetApp().plater()->undo_redo_topmost_string_getter(true, action); + new_additional_tooltip = (boost::format(_utf8(L("Next Undo action: %1%"))) % action).str(); + } + + if (new_additional_tooltip != curr_additional_tooltip) { + m_undoredo_toolbar.set_additional_tooltip(id, new_additional_tooltip); + set_tooltip(""); + } + return can_undo; + }; + + if (!m_undoredo_toolbar.add_item(item)) + return false; + + item.name = "redo"; + item.icon_filename = "redo_toolbar.svg"; + item.tooltip = _utf8(L("Redo")) + " [" + GUI::shortkey_ctrl_prefix() + "Y]\n" + _utf8(L("Click right mouse button to open/close History")); + item.sprite_id = 1; + item.left.action_callback = [this]() { post_event(SimpleEvent(EVT_GLCANVAS_REDO)); }; + item.right.action_callback = [this]() { m_imgui_undo_redo_hovered_pos = -1; }; + item.right.render_callback = [this](float left, float right, float, float) { + if (m_canvas != nullptr) { + if (_render_undo_redo_stack(false, 0.5f * (left + right))) + _deactivate_undo_redo_toolbar_items(); + } + }; + item.enabling_callback = [this]()->bool { + bool can_redo = wxGetApp().plater()->can_redo(); + int id = m_undoredo_toolbar.get_item_id("redo"); + + std::string curr_additional_tooltip; + m_undoredo_toolbar.get_additional_tooltip(id, curr_additional_tooltip); + + std::string new_additional_tooltip; + if (can_redo) { + std::string action; + wxGetApp().plater()->undo_redo_topmost_string_getter(false, action); + new_additional_tooltip = (boost::format(_utf8(L("Next Redo action: %1%"))) % action).str(); + } + + if (new_additional_tooltip != curr_additional_tooltip) { + m_undoredo_toolbar.set_additional_tooltip(id, new_additional_tooltip); + set_tooltip(""); + } + return can_redo; + }; + + if (!m_undoredo_toolbar.add_item(item)) + return false; + /* + if (!m_undoredo_toolbar.add_separator()) + return false; + */ + return true; +} + +bool GLCanvas3D::_init_view_toolbar() +{ + return wxGetApp().plater()->init_view_toolbar(); +} + +bool GLCanvas3D::_init_collapse_toolbar() +{ + return wxGetApp().plater()->init_collapse_toolbar(); +} + +bool GLCanvas3D::_set_current() +{ + return m_context != nullptr && m_canvas->SetCurrent(*m_context); +} + +void GLCanvas3D::_resize(unsigned int w, unsigned int h) +{ + if (m_canvas == nullptr && m_context == nullptr) + return; + + const std::array new_size = { w, h }; + if (m_old_size == new_size) + return; + + m_old_size = new_size; + + auto *imgui = wxGetApp().imgui(); + imgui->set_display_size(static_cast(w), static_cast(h)); + const float font_size = 1.5f * wxGetApp().em_unit(); +#if ENABLE_RETINA_GL + imgui->set_scaling(font_size, 1.0f, m_retina_helper->get_scale_factor()); +#else + imgui->set_scaling(font_size, m_canvas->GetContentScaleFactor(), 1.0f); +#endif + + this->request_extra_frame(); + + // ensures that this canvas is current + _set_current(); +} + +BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_bed_model) const +{ + BoundingBoxf3 bb = volumes_bounding_box(); + + // The following is a workaround for gizmos not being taken in account when calculating the tight camera frustrum + // A better solution would ask the gizmo manager for the bounding box of the current active gizmo, if any + if (include_gizmos && m_gizmos.is_running()) { + const BoundingBoxf3 sel_bb = m_selection.get_bounding_box(); + const Vec3d sel_bb_center = sel_bb.center(); + const Vec3d extend_by = sel_bb.max_size() * Vec3d::Ones(); + bb.merge(BoundingBoxf3(sel_bb_center - extend_by, sel_bb_center + extend_by)); + } + + const BoundingBoxf3 bed_bb = include_bed_model ? m_bed.extended_bounding_box() : m_bed.build_volume().bounding_volume(); + bb.merge(bed_bb); + + if (!m_main_toolbar.is_enabled()) + bb.merge(m_gcode_viewer.get_max_bounding_box()); + + // clamp max bb size with respect to bed bb size + if (!m_picking_enabled) { + static const double max_scale_factor = 2.0; + const Vec3d bb_size = bb.size(); + const Vec3d bed_bb_size = m_bed.build_volume().bounding_volume().size(); + if (bb_size.x() > max_scale_factor * bed_bb_size.x() || + bb_size.y() > max_scale_factor * bed_bb_size.y() || + bb_size.z() > max_scale_factor * bed_bb_size.z()) { + const Vec3d bed_bb_center = bed_bb.center(); + const Vec3d extend_by = max_scale_factor * bed_bb_size; + bb = BoundingBoxf3(bed_bb_center - extend_by, bed_bb_center + extend_by); + } + } + + return bb; +} + +void GLCanvas3D::_zoom_to_box(const BoundingBoxf3& box, double margin_factor) +{ + wxGetApp().plater()->get_camera().zoom_to_box(box, margin_factor); + m_dirty = true; +} + +void GLCanvas3D::_update_camera_zoom(double zoom) +{ + wxGetApp().plater()->get_camera().update_zoom(zoom); + m_dirty = true; +} + +void GLCanvas3D::_refresh_if_shown_on_screen() +{ + if (_is_shown_on_screen()) { + const Size& cnv_size = get_canvas_size(); + _resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height()); + + // Because of performance problems on macOS, where PaintEvents are not delivered + // frequently enough, we call render() here directly when we can. + render(); + } +} + +void GLCanvas3D::_picking_pass() +{ + if (m_picking_enabled && !m_mouse.dragging && m_mouse.position != Vec2d(DBL_MAX, DBL_MAX)) { + m_hover_volume_idxs.clear(); + + // Render the object for picking. + // FIXME This cannot possibly work in a multi - sampled context as the color gets mangled by the anti - aliasing. + // Better to use software ray - casting on a bounding - box hierarchy. + + if (m_multisample_allowed) + // This flag is often ignored by NVIDIA drivers if rendering into a screen buffer. + glsafe(::glDisable(GL_MULTISAMPLE)); + + glsafe(::glDisable(GL_BLEND)); + glsafe(::glEnable(GL_DEPTH_TEST)); + + glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); + +#if !ENABLE_GL_SHADERS_ATTRIBUTES + m_camera_clipping_plane = m_gizmos.get_clipping_plane(); + if (m_camera_clipping_plane.is_active()) { + ::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_camera_clipping_plane.get_data().data()); + ::glEnable(GL_CLIP_PLANE0); + } +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + _render_volumes_for_picking(); +#if !ENABLE_GL_SHADERS_ATTRIBUTES + if (m_camera_clipping_plane.is_active()) + ::glDisable(GL_CLIP_PLANE0); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + _render_bed_for_picking(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward()); +#else + _render_bed_for_picking(!wxGetApp().plater()->get_camera().is_looking_downward()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + m_gizmos.render_current_gizmo_for_picking_pass(); + + if (m_multisample_allowed) + glsafe(::glEnable(GL_MULTISAMPLE)); + + int volume_id = -1; + int gizmo_id = -1; + + std::array color = { 0, 0, 0, 0 }; + const Size& cnv_size = get_canvas_size(); + bool inside = 0 <= m_mouse.position(0) && m_mouse.position(0) < cnv_size.get_width() && 0 <= m_mouse.position(1) && m_mouse.position(1) < cnv_size.get_height(); + if (inside) { + glsafe(::glReadPixels(m_mouse.position(0), cnv_size.get_height() - m_mouse.position.y() - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color.data())); + if (picking_checksum_alpha_channel(color[0], color[1], color[2]) == color[3]) { + // Only non-interpolated colors are valid, those have their lowest three bits zeroed. + // we reserve color = (0,0,0) for occluders (as the printbed) + // volumes' id are shifted by 1 + // see: _render_volumes_for_picking() + unsigned int id = picking_encode(color[0], color[1], color[2]); + volume_id = id - 1; + // gizmos' id are instead properly encoded by the color + gizmo_id = id; + } + } + if (0 <= volume_id && volume_id < (int)m_volumes.volumes.size()) { + // do not add the volume id if any gizmo is active and CTRL is pressed + if (m_gizmos.get_current_type() == GLGizmosManager::EType::Undefined || !wxGetKeyState(WXK_CONTROL)) + m_hover_volume_idxs.emplace_back(volume_id); + m_gizmos.set_hover_id(-1); + } + else + m_gizmos.set_hover_id(inside && (unsigned int)gizmo_id <= GLGizmoBase::BASE_ID ? ((int)GLGizmoBase::BASE_ID - gizmo_id) : -1); + + _update_volumes_hover_state(); + } +} + +void GLCanvas3D::_rectangular_selection_picking_pass() +{ + m_gizmos.set_hover_id(-1); + + std::set idxs; + + if (m_picking_enabled) { + if (m_multisample_allowed) + // This flag is often ignored by NVIDIA drivers if rendering into a screen buffer. + glsafe(::glDisable(GL_MULTISAMPLE)); + + glsafe(::glDisable(GL_BLEND)); + glsafe(::glEnable(GL_DEPTH_TEST)); + + glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); + + _render_volumes_for_picking(); +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + _render_bed_for_picking(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward()); +#else + _render_bed_for_picking(!wxGetApp().plater()->get_camera().is_looking_downward()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + if (m_multisample_allowed) + glsafe(::glEnable(GL_MULTISAMPLE)); + + int width = std::max((int)m_rectangle_selection.get_width(), 1); + int height = std::max((int)m_rectangle_selection.get_height(), 1); + int px_count = width * height; + + int left = (int)m_rectangle_selection.get_left(); + int top = get_canvas_size().get_height() - (int)m_rectangle_selection.get_top(); + if (left >= 0 && top >= 0) { +#define USE_PARALLEL 1 +#if USE_PARALLEL + struct Pixel + { + std::array data; + // Only non-interpolated colors are valid, those have their lowest three bits zeroed. + bool valid() const { return picking_checksum_alpha_channel(data[0], data[1], data[2]) == data[3]; } + // we reserve color = (0,0,0) for occluders (as the printbed) + // volumes' id are shifted by 1 + // see: _render_volumes_for_picking() + int id() const { return data[0] + (data[1] << 8) + (data[2] << 16) - 1; } + }; + + std::vector frame(px_count); + glsafe(::glReadPixels(left, top, width, height, GL_RGBA, GL_UNSIGNED_BYTE, (void*)frame.data())); + + tbb::spin_mutex mutex; + tbb::parallel_for(tbb::blocked_range(0, frame.size(), (size_t)width), + [this, &frame, &idxs, &mutex](const tbb::blocked_range& range) { + for (size_t i = range.begin(); i < range.end(); ++i) + if (frame[i].valid()) { + int volume_id = frame[i].id(); + if (0 <= volume_id && volume_id < (int)m_volumes.volumes.size()) { + mutex.lock(); + idxs.insert(volume_id); + mutex.unlock(); + } + } + }); +#else + std::vector frame(4 * px_count); + glsafe(::glReadPixels(left, top, width, height, GL_RGBA, GL_UNSIGNED_BYTE, (void*)frame.data())); + + for (int i = 0; i < px_count; ++i) + { + int px_id = 4 * i; + int volume_id = frame[px_id] + (frame[px_id + 1] << 8) + (frame[px_id + 2] << 16); + if (0 <= volume_id && volume_id < (int)m_volumes.volumes.size()) + idxs.insert(volume_id); + } +#endif // USE_PARALLEL + } + } + + m_hover_volume_idxs.assign(idxs.begin(), idxs.end()); + _update_volumes_hover_state(); +} + +void GLCanvas3D::_render_background() +{ + bool use_error_color = false; + if (wxGetApp().is_editor()) { + use_error_color = m_dynamic_background_enabled && + (current_printer_technology() != ptSLA || !m_volumes.empty()); + + if (!m_volumes.empty()) + use_error_color &= _is_any_volume_outside(); + else + use_error_color &= m_gcode_viewer.has_data() && !m_gcode_viewer.is_contained_in_bed(); + } + +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPushMatrix()); + glsafe(::glLoadIdentity()); + glsafe(::glMatrixMode(GL_PROJECTION)); + glsafe(::glPushMatrix()); + glsafe(::glLoadIdentity()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + + // Draws a bottom to top gradient over the complete screen. + glsafe(::glDisable(GL_DEPTH_TEST)); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + const ColorRGBA bottom_color = use_error_color ? ERROR_BG_DARK_COLOR : DEFAULT_BG_DARK_COLOR; + + if (!m_background.is_initialized()) { + m_background.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P2T2 }; + init_data.reserve_vertices(4); + init_data.reserve_indices(6); + + // vertices + init_data.add_vertex(Vec2f(-1.0f, -1.0f), Vec2f(0.0f, 0.0f)); + init_data.add_vertex(Vec2f(1.0f, -1.0f), Vec2f(1.0f, 0.0f)); + init_data.add_vertex(Vec2f(1.0f, 1.0f), Vec2f(1.0f, 1.0f)); + init_data.add_vertex(Vec2f(-1.0f, 1.0f), Vec2f(0.0f, 1.0f)); + + // indices + init_data.add_triangle(0, 1, 2); + init_data.add_triangle(2, 3, 0); + + m_background.init_from(std::move(init_data)); + } + + GLShaderProgram* shader = wxGetApp().get_shader("background"); + if (shader != nullptr) { + shader->start_using(); + shader->set_uniform("top_color", use_error_color ? ERROR_BG_LIGHT_COLOR : DEFAULT_BG_LIGHT_COLOR); + shader->set_uniform("bottom_color", bottom_color); + m_background.render(); + shader->stop_using(); + } +#else + ::glBegin(GL_QUADS); + ::glColor3fv(use_error_color ? ERROR_BG_DARK_COLOR.data(): DEFAULT_BG_DARK_COLOR.data()); + ::glVertex2f(-1.0f, -1.0f); + ::glVertex2f(1.0f, -1.0f); + + ::glColor3fv(use_error_color ? ERROR_BG_LIGHT_COLOR.data() : DEFAULT_BG_LIGHT_COLOR.data()); + ::glVertex2f(1.0f, 1.0f); + ::glVertex2f(-1.0f, 1.0f); + glsafe(::glEnd()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + glsafe(::glEnable(GL_DEPTH_TEST)); + +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); + glsafe(::glMatrixMode(GL_MODELVIEW)); + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES +} + +#if ENABLE_GL_SHADERS_ATTRIBUTES +void GLCanvas3D::_render_bed(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_axes) +#else +void GLCanvas3D::_render_bed(bool bottom, bool show_axes) +#endif // ENABLE_GL_SHADERS_ATTRIBUTES +{ + float scale_factor = 1.0; +#if ENABLE_RETINA_GL + scale_factor = m_retina_helper->get_scale_factor(); +#endif // ENABLE_RETINA_GL + + bool show_texture = ! bottom || + (m_gizmos.get_current_type() != GLGizmosManager::FdmSupports + && m_gizmos.get_current_type() != GLGizmosManager::SlaSupports + && m_gizmos.get_current_type() != GLGizmosManager::Hollow + && m_gizmos.get_current_type() != GLGizmosManager::Seam + && m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation); + +#if ENABLE_GL_SHADERS_ATTRIBUTES + m_bed.render(*this, view_matrix, projection_matrix, bottom, scale_factor, show_axes, show_texture); +#else + m_bed.render(*this, bottom, scale_factor, show_axes, show_texture); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES +} + +#if ENABLE_GL_SHADERS_ATTRIBUTES +void GLCanvas3D::_render_bed_for_picking(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom) +#else +void GLCanvas3D::_render_bed_for_picking(bool bottom) +#endif // ENABLE_GL_SHADERS_ATTRIBUTES +{ + float scale_factor = 1.0; +#if ENABLE_RETINA_GL + scale_factor = m_retina_helper->get_scale_factor(); +#endif // ENABLE_RETINA_GL + +#if ENABLE_GL_SHADERS_ATTRIBUTES + m_bed.render_for_picking(*this, view_matrix, projection_matrix, bottom, scale_factor); +#else + m_bed.render_for_picking(*this, bottom, scale_factor); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES +} + +void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type) +{ + if (m_volumes.empty()) + return; + + glsafe(::glEnable(GL_DEPTH_TEST)); + + m_camera_clipping_plane = m_gizmos.get_clipping_plane(); + + if (m_picking_enabled) + // Update the layer editing selection to the first object selected, update the current object maximum Z. + m_layers_editing.select_object(*m_model, this->is_layers_editing_enabled() ? m_selection.get_object_idx() : -1); + + if (const BuildVolume &build_volume = m_bed.build_volume(); build_volume.valid()) { + switch (build_volume.type()) { + case BuildVolume::Type::Rectangle: { + const BoundingBox3Base bed_bb = build_volume.bounding_volume().inflated(BuildVolume::SceneEpsilon); + m_volumes.set_print_volume({ 0, // circle + { float(bed_bb.min.x()), float(bed_bb.min.y()), float(bed_bb.max.x()), float(bed_bb.max.y()) }, + { 0.0f, float(build_volume.max_print_height()) } }); + break; + } + case BuildVolume::Type::Circle: { + m_volumes.set_print_volume({ 1, // rectangle + { unscaled(build_volume.circle().center.x()), unscaled(build_volume.circle().center.y()), unscaled(build_volume.circle().radius + BuildVolume::SceneEpsilon), 0.0f }, + { 0.0f, float(build_volume.max_print_height() + BuildVolume::SceneEpsilon) } }); + break; + } + default: + case BuildVolume::Type::Convex: + case BuildVolume::Type::Custom: { + m_volumes.set_print_volume({ static_cast(type), + { -FLT_MAX, -FLT_MAX, FLT_MAX, FLT_MAX }, + { -FLT_MAX, FLT_MAX } } + ); + } + } + if (m_requires_check_outside_state) { + m_volumes.check_outside_state(build_volume, nullptr); + m_requires_check_outside_state = false; + } + } + + if (m_use_clipping_planes) + m_volumes.set_z_range(-m_clipping_planes[0].get_data()[3], m_clipping_planes[1].get_data()[3]); + else + m_volumes.set_z_range(-FLT_MAX, FLT_MAX); + + m_volumes.set_clipping_plane(m_camera_clipping_plane.get_data()); + m_volumes.set_show_sinking_contours(! m_gizmos.is_hiding_instances()); +#if ENABLE_SHOW_NON_MANIFOLD_EDGES + m_volumes.set_show_non_manifold_edges(!m_gizmos.is_hiding_instances() && m_gizmos.get_current_type() != GLGizmosManager::Simplify); +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES + + GLShaderProgram* shader = wxGetApp().get_shader("gouraud"); + if (shader != nullptr) { + shader->start_using(); + + switch (type) + { + default: + case GLVolumeCollection::ERenderType::Opaque: + { + if (m_picking_enabled && !m_gizmos.is_dragging() && m_layers_editing.is_enabled() && (m_layers_editing.last_object_id != -1) && (m_layers_editing.object_max_z() > 0.0f)) { + int object_id = m_layers_editing.last_object_id; +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + m_volumes.render(type, false, camera.get_view_matrix(), camera.get_projection_matrix(), [object_id](const GLVolume& volume) { + // Which volume to paint without the layer height profile shader? + return volume.is_active && (volume.is_modifier || volume.composite_id.object_id != object_id); + }); +#else + m_volumes.render(type, false, wxGetApp().plater()->get_camera().get_view_matrix(), [object_id](const GLVolume& volume) { + // Which volume to paint without the layer height profile shader? + return volume.is_active && (volume.is_modifier || volume.composite_id.object_id != object_id); + }); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + // Let LayersEditing handle rendering of the active object using the layer height profile shader. + m_layers_editing.render_volumes(*this, m_volumes); + } + else { + // do not cull backfaces to show broken geometry, if any +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + m_volumes.render(type, m_picking_enabled, camera.get_view_matrix(), camera.get_projection_matrix(), [this](const GLVolume& volume) { + return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0); + }); +#else + m_volumes.render(type, m_picking_enabled, wxGetApp().plater()->get_camera().get_view_matrix(), [this](const GLVolume& volume) { + return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0); + }); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + } + + // In case a painting gizmo is open, it should render the painted triangles + // before transparent objects are rendered. Otherwise they would not be + // visible when inside modifier meshes etc. + { + GLGizmosManager& gm = get_gizmos_manager(); +// GLGizmosManager::EType type = gm.get_current_type(); + if (dynamic_cast(gm.get_current())) { + shader->stop_using(); + gm.render_painter_gizmo(); + shader->start_using(); + } + } + break; + } + case GLVolumeCollection::ERenderType::Transparent: + { +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + m_volumes.render(type, false, camera.get_view_matrix(), camera.get_projection_matrix()); +#else + m_volumes.render(type, false, wxGetApp().plater()->get_camera().get_view_matrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + break; + } + } + shader->stop_using(); + } + + m_camera_clipping_plane = ClippingPlane::ClipsNothing(); +} + +void GLCanvas3D::_render_gcode() +{ + m_gcode_viewer.render(); +} + +#if ENABLE_SHOW_TOOLPATHS_COG +void GLCanvas3D::_render_gcode_cog() +{ + m_gcode_viewer.render_cog(); +} +#endif // ENABLE_SHOW_TOOLPATHS_COG + +void GLCanvas3D::_render_selection() +{ + float scale_factor = 1.0; +#if ENABLE_RETINA_GL + scale_factor = m_retina_helper->get_scale_factor(); +#endif // ENABLE_RETINA_GL + + if (!m_gizmos.is_running()) + m_selection.render(scale_factor); +} + +void GLCanvas3D::_render_sequential_clearance() +{ + if (m_layers_editing.is_enabled() || m_gizmos.is_dragging()) + return; + + switch (m_gizmos.get_current_type()) + { + case GLGizmosManager::EType::Flatten: + case GLGizmosManager::EType::Cut: + case GLGizmosManager::EType::Hollow: + case GLGizmosManager::EType::SlaSupports: + case GLGizmosManager::EType::FdmSupports: + case GLGizmosManager::EType::Seam: { return; } + default: { break; } + } + + m_sequential_print_clearance.render(); +} + +#if ENABLE_RENDER_SELECTION_CENTER +void GLCanvas3D::_render_selection_center() +{ + m_selection.render_center(m_gizmos.is_dragging()); +} +#endif // ENABLE_RENDER_SELECTION_CENTER + +void GLCanvas3D::_check_and_update_toolbar_icon_scale() +{ + // Don't update a toolbar scale, when we are on a Preview + if (wxGetApp().plater()->is_preview_shown()) + return; + + float scale = wxGetApp().toolbar_icon_scale(); + Size cnv_size = get_canvas_size(); + + float size = GLToolbar::Default_Icons_Size * scale; + + // Set current size for all top toolbars. It will be used for next calculations + GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); +#if ENABLE_RETINA_GL + const float sc = m_retina_helper->get_scale_factor() * scale; + m_main_toolbar.set_scale(sc); + m_undoredo_toolbar.set_scale(sc); + collapse_toolbar.set_scale(sc); + size *= m_retina_helper->get_scale_factor(); +#else + m_main_toolbar.set_icons_size(size); + m_undoredo_toolbar.set_icons_size(size); + collapse_toolbar.set_icons_size(size); +#endif // ENABLE_RETINA_GL + + float top_tb_width = m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar.get_width(); + int items_cnt = m_main_toolbar.get_visible_items_cnt() + m_undoredo_toolbar.get_visible_items_cnt() + collapse_toolbar.get_visible_items_cnt(); + float noitems_width = top_tb_width - size * items_cnt; // width of separators and borders in top toolbars + + // calculate scale needed for items in all top toolbars + float new_h_scale = (cnv_size.get_width() - noitems_width) / (items_cnt * GLToolbar::Default_Icons_Size); + + items_cnt = m_gizmos.get_selectable_icons_cnt() + 3; // +3 means a place for top and view toolbars and separators in gizmos toolbar + + // calculate scale needed for items in the gizmos toolbar + float new_v_scale = cnv_size.get_height() / (items_cnt * GLGizmosManager::Default_Icons_Size); + + // set minimum scale as a auto scale for the toolbars + float new_scale = std::min(new_h_scale, new_v_scale); +#if ENABLE_RETINA_GL + new_scale /= m_retina_helper->get_scale_factor(); +#endif + if (fabs(new_scale - scale) > 0.01) // scale is changed by 1% and more + wxGetApp().set_auto_toolbar_icon_scale(new_scale); +} + +void GLCanvas3D::_render_overlays() +{ + glsafe(::glDisable(GL_DEPTH_TEST)); +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPushMatrix()); + glsafe(::glLoadIdentity()); + // ensure that the textures are renderered inside the frustrum + const Camera& camera = wxGetApp().plater()->get_camera(); + glsafe(::glTranslated(0.0, 0.0, -(camera.get_near_z() + 0.005))); + // ensure that the overlay fits the frustrum near z plane + double gui_scale = camera.get_gui_scale(); + glsafe(::glScaled(gui_scale, gui_scale, 1.0)); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + + _check_and_update_toolbar_icon_scale(); + + _render_gizmos_overlay(); + + // main toolbar and undoredo toolbar need to be both updated before rendering because both their sizes are needed + // to correctly place them +#if ENABLE_RETINA_GL + const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(/*true*/); + m_main_toolbar.set_scale(scale); + m_undoredo_toolbar.set_scale(scale); + wxGetApp().plater()->get_collapse_toolbar().set_scale(scale); +#else + const float size = int(GLToolbar::Default_Icons_Size * wxGetApp().toolbar_icon_scale(/*true*/)); + m_main_toolbar.set_icons_size(size); + m_undoredo_toolbar.set_icons_size(size); + wxGetApp().plater()->get_collapse_toolbar().set_icons_size(size); +#endif // ENABLE_RETINA_GL + + _render_main_toolbar(); + _render_undoredo_toolbar(); + _render_collapse_toolbar(); + _render_view_toolbar(); + + if (m_layers_editing.last_object_id >= 0 && m_layers_editing.object_max_z() > 0.0f) + m_layers_editing.render_overlay(*this); + + const ConfigOptionBool* opt = dynamic_cast(m_config->option("complete_objects")); + bool sequential_print = opt != nullptr && opt->value; + std::vector sorted_instances; + if (sequential_print) { + for (ModelObject* model_object : m_model->objects) + for (ModelInstance* model_instance : model_object->instances) { + sorted_instances.emplace_back(model_instance); + } + } + m_labels.render(sorted_instances); + +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES +} + +void GLCanvas3D::_render_volumes_for_picking() const +{ +#if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_GL_SHADERS_ATTRIBUTES + GLShaderProgram* shader = wxGetApp().get_shader("flat_clip"); +#else + GLShaderProgram* shader = wxGetApp().get_shader("flat"); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + if (shader == nullptr) + return; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + // do not cull backfaces to show broken geometry, if any + glsafe(::glDisable(GL_CULL_FACE)); + +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); + glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + + const Transform3d& view_matrix = wxGetApp().plater()->get_camera().get_view_matrix(); + for (size_t type = 0; type < 2; ++ type) { + GLVolumeWithIdAndZList to_render = volumes_to_render(m_volumes.volumes, (type == 0) ? GLVolumeCollection::ERenderType::Opaque : GLVolumeCollection::ERenderType::Transparent, view_matrix); + for (const GLVolumeWithIdAndZ& volume : to_render) + if (!volume.first->disabled && (volume.first->composite_id.volume_id >= 0 || m_render_sla_auxiliaries)) { + // Object picking mode. Render the object with a color encoding the object index. + // we reserve color = (0,0,0) for occluders (as the printbed) + // so we shift volumes' id by 1 to get the proper color + const unsigned int id = 1 + volume.second.first; +#if ENABLE_LEGACY_OPENGL_REMOVAL + volume.first->model.set_color(picking_decode(id)); + shader->start_using(); +#else + glsafe(::glColor4fv(picking_decode(id).data())); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix() * volume.first->world_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + shader->set_uniform("volume_world_matrix", volume.first->world_matrix()); + shader->set_uniform("z_range", m_volumes.get_z_range()); + shader->set_uniform("clipping_plane", m_volumes.get_clipping_plane()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + volume.first->render(); +#if ENABLE_LEGACY_OPENGL_REMOVAL + shader->stop_using(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + } + +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); + glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + + glsafe(::glEnable(GL_CULL_FACE)); +} + +void GLCanvas3D::_render_current_gizmo() const +{ + m_gizmos.render_current_gizmo(); +} + +void GLCanvas3D::_render_gizmos_overlay() +{ +#if ENABLE_RETINA_GL +// m_gizmos.set_overlay_scale(m_retina_helper->get_scale_factor()); + const float scale = m_retina_helper->get_scale_factor()*wxGetApp().toolbar_icon_scale(); + m_gizmos.set_overlay_scale(scale); //! #ys_FIXME_experiment +#else +// m_gizmos.set_overlay_scale(m_canvas->GetContentScaleFactor()); +// m_gizmos.set_overlay_scale(wxGetApp().em_unit()*0.1f); + const float size = int(GLGizmosManager::Default_Icons_Size * wxGetApp().toolbar_icon_scale()); + m_gizmos.set_overlay_icon_size(size); //! #ys_FIXME_experiment +#endif /* __WXMSW__ */ + + m_gizmos.render_overlay(); + + if (m_gizmo_highlighter.m_render_arrow) + m_gizmos.render_arrow(*this, m_gizmo_highlighter.m_gizmo_type); +} + +void GLCanvas3D::_render_main_toolbar() +{ + if (!m_main_toolbar.is_enabled()) + return; + + const Size cnv_size = get_canvas_size(); +#if ENABLE_GL_SHADERS_ATTRIBUTES + const float top = 0.5f * (float)cnv_size.get_height(); +#else + const float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); + const float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); + const float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f; +#if ENABLE_GL_SHADERS_ATTRIBUTES + const float left = -0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar_width); +#else + const float left = -0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar_width) * inv_zoom; +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + m_main_toolbar.set_position(top, left); + m_main_toolbar.render(*this); + if (m_toolbar_highlighter.m_render_arrow) + m_main_toolbar.render_arrow(*this, m_toolbar_highlighter.m_toolbar_item); +} + +void GLCanvas3D::_render_undoredo_toolbar() +{ + if (!m_undoredo_toolbar.is_enabled()) + return; + + const Size cnv_size = get_canvas_size(); +#if ENABLE_GL_SHADERS_ATTRIBUTES + const float top = 0.5f * (float)cnv_size.get_height(); +#else + float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); + + const float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); + const float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f; +#if ENABLE_GL_SHADERS_ATTRIBUTES + const float left = m_main_toolbar.get_width() - 0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar_width); +#else + const float left = (m_main_toolbar.get_width() - 0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar_width)) * inv_zoom; +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + m_undoredo_toolbar.set_position(top, left); + m_undoredo_toolbar.render(*this); + if (m_toolbar_highlighter.m_render_arrow) + m_undoredo_toolbar.render_arrow(*this, m_toolbar_highlighter.m_toolbar_item); +} + +void GLCanvas3D::_render_collapse_toolbar() const +{ + GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); + + const Size cnv_size = get_canvas_size(); + const float band = m_layers_editing.is_enabled() ? (wxGetApp().imgui()->get_style_scaling() * LayersEditing::THICKNESS_BAR_WIDTH) : 0.0; +#if ENABLE_GL_SHADERS_ATTRIBUTES + const float top = 0.5f * (float)cnv_size.get_height(); + const float left = 0.5f * (float)cnv_size.get_width() - collapse_toolbar.get_width() - band; +#else + const float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); + + const float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; + const float left = (0.5f * (float)cnv_size.get_width() - (float)collapse_toolbar.get_width() - band) * inv_zoom; +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + collapse_toolbar.set_position(top, left); + collapse_toolbar.render(*this); +} + +void GLCanvas3D::_render_view_toolbar() const +{ + GLToolbar& view_toolbar = wxGetApp().plater()->get_view_toolbar(); + +#if ENABLE_RETINA_GL + const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(); +#if __APPLE__ + view_toolbar.set_scale(scale); +#else // if GTK3 + const float size = int(GLGizmosManager::Default_Icons_Size * scale); + view_toolbar.set_icons_size(size); +#endif // __APPLE__ +#else + const float size = int(GLGizmosManager::Default_Icons_Size * wxGetApp().toolbar_icon_scale()); + view_toolbar.set_icons_size(size); +#endif // ENABLE_RETINA_GL + + const Size cnv_size = get_canvas_size(); +#if ENABLE_GL_SHADERS_ATTRIBUTES + // places the toolbar on the bottom-left corner of the 3d scene + float top = -0.5f * (float)cnv_size.get_height() + view_toolbar.get_height(); + float left = -0.5f * (float)cnv_size.get_width(); +#else + float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); + + // places the toolbar on the bottom-left corner of the 3d scene + float top = (-0.5f * (float)cnv_size.get_height() + view_toolbar.get_height()) * inv_zoom; + float left = -0.5f * (float)cnv_size.get_width() * inv_zoom; +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + view_toolbar.set_position(top, left); + view_toolbar.render(*this); +} + +#if ENABLE_SHOW_CAMERA_TARGET +void GLCanvas3D::_render_camera_target() +{ + static const float half_length = 5.0f; + + glsafe(::glDisable(GL_DEPTH_TEST)); + glsafe(::glLineWidth(2.0f)); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + const Vec3f& target = wxGetApp().plater()->get_camera().get_target().cast(); + m_camera_target.target = target.cast(); + + for (int i = 0; i < 3; ++i) { + if (!m_camera_target.axis[i].is_initialized()) { + m_camera_target.axis[i].reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + init_data.color = (i == X) ? ColorRGBA::X() : ((i == Y) ? ColorRGBA::Y() : ColorRGBA::Z()); + init_data.reserve_vertices(2); + init_data.reserve_indices(2); + + // vertices + if (i == X) { + init_data.add_vertex(Vec3f(-half_length, 0.0f, 0.0f)); + init_data.add_vertex(Vec3f(+half_length, 0.0f, 0.0f)); + } + else if (i == Y) { + init_data.add_vertex(Vec3f(0.0f, -half_length, 0.0f)); + init_data.add_vertex(Vec3f(0.0f, +half_length, 0.0f)); + } + else { + init_data.add_vertex(Vec3f(0.0f, 0.0f, -half_length)); + init_data.add_vertex(Vec3f(0.0f, 0.0f, +half_length)); + } + + // indices + init_data.add_line(0, 1); + + m_camera_target.axis[i].init_from(std::move(init_data)); + } + } + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix() * Geometry::assemble_transform(m_camera_target.target)); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + for (int i = 0; i < 3; ++i) { + m_camera_target.axis[i].render(); + } + shader->stop_using(); + } +#else + ::glBegin(GL_LINES); + const Vec3d& target = wxGetApp().plater()->get_camera().get_target(); + // draw line for x axis + ::glColor3f(1.0f, 0.0f, 0.0f); + ::glVertex3d(target.x() - half_length, target.y(), target.z()); + ::glVertex3d(target.x() + half_length, target.y(), target.z()); + // draw line for y axis + ::glColor3f(0.0f, 1.0f, 0.0f); + ::glVertex3d(target.x(), target.y() - half_length, target.z()); + ::glVertex3d(target.x(), target.y() + half_length, target.z()); + // draw line for z axis + ::glColor3f(0.0f, 0.0f, 1.0f); + ::glVertex3d(target.x(), target.y(), target.z() - half_length); + ::glVertex3d(target.x(), target.y(), target.z() + half_length); + glsafe(::glEnd()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} +#endif // ENABLE_SHOW_CAMERA_TARGET + +void GLCanvas3D::_render_sla_slices() +{ + if (!m_use_clipping_planes || current_printer_technology() != ptSLA) + return; + + const SLAPrint* print = this->sla_print(); + const PrintObjects& print_objects = print->objects(); + if (print_objects.empty()) + // nothing to render, return + return; + + double clip_min_z = -m_clipping_planes[0].get_data()[3]; + double clip_max_z = m_clipping_planes[1].get_data()[3]; + for (unsigned int i = 0; i < (unsigned int)print_objects.size(); ++i) { + const SLAPrintObject* obj = print_objects[i]; + + if (!obj->is_step_done(slaposSliceSupports)) + continue; + +#if ENABLE_LEGACY_OPENGL_REMOVAL + SlaCap::ObjectIdToModelsMap::iterator it_caps_bottom = m_sla_caps[0].triangles.find(i); + SlaCap::ObjectIdToModelsMap::iterator it_caps_top = m_sla_caps[1].triangles.find(i); +#else + SlaCap::ObjectIdToTrianglesMap::iterator it_caps_bottom = m_sla_caps[0].triangles.find(i); + SlaCap::ObjectIdToTrianglesMap::iterator it_caps_top = m_sla_caps[1].triangles.find(i); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + { + if (it_caps_bottom == m_sla_caps[0].triangles.end()) + it_caps_bottom = m_sla_caps[0].triangles.emplace(i, SlaCap::Triangles()).first; + if (!m_sla_caps[0].matches(clip_min_z)) { + m_sla_caps[0].z = clip_min_z; +#if ENABLE_LEGACY_OPENGL_REMOVAL + it_caps_bottom->second.object.reset(); + it_caps_bottom->second.supports.reset(); +#else + it_caps_bottom->second.object.clear(); + it_caps_bottom->second.supports.clear(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + if (it_caps_top == m_sla_caps[1].triangles.end()) + it_caps_top = m_sla_caps[1].triangles.emplace(i, SlaCap::Triangles()).first; + if (!m_sla_caps[1].matches(clip_max_z)) { + m_sla_caps[1].z = clip_max_z; +#if ENABLE_LEGACY_OPENGL_REMOVAL + it_caps_top->second.object.reset(); + it_caps_top->second.supports.reset(); +#else + it_caps_top->second.object.clear(); + it_caps_top->second.supports.clear(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + } +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLModel& bottom_obj_triangles = it_caps_bottom->second.object; + GLModel& bottom_sup_triangles = it_caps_bottom->second.supports; + GLModel& top_obj_triangles = it_caps_top->second.object; + GLModel& top_sup_triangles = it_caps_top->second.supports; +#else + Pointf3s &bottom_obj_triangles = it_caps_bottom->second.object; + Pointf3s &bottom_sup_triangles = it_caps_bottom->second.supports; + Pointf3s &top_obj_triangles = it_caps_top->second.object; + Pointf3s &top_sup_triangles = it_caps_top->second.supports; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + +#if ENABLE_LEGACY_OPENGL_REMOVAL + auto init_model = [](GLModel& model, const Pointf3s& triangles, const ColorRGBA& color) { + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(triangles.size()); + init_data.reserve_indices(triangles.size() / 3); + init_data.color = color; + + unsigned int vertices_count = 0; + for (const Vec3d& v : triangles) { + init_data.add_vertex((Vec3f)v.cast()); + ++vertices_count; + if (vertices_count % 3 == 0) + init_data.add_triangle(vertices_count - 3, vertices_count - 2, vertices_count - 1); + } + + if (!init_data.is_empty()) + model.init_from(std::move(init_data)); + }; + + if ((!bottom_obj_triangles.is_initialized() || !bottom_sup_triangles.is_initialized() || + !top_obj_triangles.is_initialized() || !top_sup_triangles.is_initialized()) && !obj->get_slice_index().empty()) { +#else + if ((bottom_obj_triangles.empty() || bottom_sup_triangles.empty() || top_obj_triangles.empty() || top_sup_triangles.empty()) && + !obj->get_slice_index().empty()) { +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + double layer_height = print->default_object_config().layer_height.value; + double initial_layer_height = print->material_config().initial_layer_height.value; + bool left_handed = obj->is_left_handed(); + + coord_t key_zero = obj->get_slice_index().front().print_level(); + // Slice at the center of the slab starting at clip_min_z will be rendered for the lower plane. + coord_t key_low = coord_t((clip_min_z - initial_layer_height + layer_height) / SCALING_FACTOR) + key_zero; + // Slice at the center of the slab ending at clip_max_z will be rendered for the upper plane. + coord_t key_high = coord_t((clip_max_z - initial_layer_height) / SCALING_FACTOR) + key_zero; + + const SliceRecord& slice_low = obj->closest_slice_to_print_level(key_low, coord_t(SCALED_EPSILON)); + const SliceRecord& slice_high = obj->closest_slice_to_print_level(key_high, coord_t(SCALED_EPSILON)); + + // Offset to avoid OpenGL Z fighting between the object's horizontal surfaces and the triangluated surfaces of the cuts. + const double plane_shift_z = 0.002; + + if (slice_low.is_valid()) { + const ExPolygons& obj_bottom = slice_low.get_slice(soModel); + const ExPolygons& sup_bottom = slice_low.get_slice(soSupport); +#if ENABLE_LEGACY_OPENGL_REMOVAL + // calculate model bottom cap + if (!bottom_obj_triangles.is_initialized() && !obj_bottom.empty()) + init_model(bottom_obj_triangles, triangulate_expolygons_3d(obj_bottom, clip_min_z - plane_shift_z, !left_handed), { 1.0f, 0.37f, 0.0f, 1.0f }); + // calculate support bottom cap + if (!bottom_sup_triangles.is_initialized() && !sup_bottom.empty()) + init_model(bottom_sup_triangles, triangulate_expolygons_3d(sup_bottom, clip_min_z - plane_shift_z, !left_handed), { 1.0f, 0.0f, 0.37f, 1.0f }); +#else + // calculate model bottom cap + if (bottom_obj_triangles.empty() && !obj_bottom.empty()) + bottom_obj_triangles = triangulate_expolygons_3d(obj_bottom, clip_min_z - plane_shift_z, ! left_handed); + // calculate support bottom cap + if (bottom_sup_triangles.empty() && !sup_bottom.empty()) + bottom_sup_triangles = triangulate_expolygons_3d(sup_bottom, clip_min_z - plane_shift_z, !left_handed); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + + if (slice_high.is_valid()) { + const ExPolygons& obj_top = slice_high.get_slice(soModel); + const ExPolygons& sup_top = slice_high.get_slice(soSupport); +#if ENABLE_LEGACY_OPENGL_REMOVAL + // calculate model top cap + if (!top_obj_triangles.is_initialized() && !obj_top.empty()) + init_model(top_obj_triangles, triangulate_expolygons_3d(obj_top, clip_max_z + plane_shift_z, left_handed), { 1.0f, 0.37f, 0.0f, 1.0f }); + // calculate support top cap + if (!top_sup_triangles.is_initialized() && !sup_top.empty()) + init_model(top_sup_triangles, triangulate_expolygons_3d(sup_top, clip_max_z + plane_shift_z, left_handed), { 1.0f, 0.0f, 0.37f, 1.0f }); +#else + // calculate model top cap + if (top_obj_triangles.empty() && !obj_top.empty()) + top_obj_triangles = triangulate_expolygons_3d(obj_top, clip_max_z + plane_shift_z, left_handed); + // calculate support top cap + if (top_sup_triangles.empty() && !sup_top.empty()) + top_sup_triangles = triangulate_expolygons_3d(sup_top, clip_max_z + plane_shift_z, left_handed); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + + for (const SLAPrintObject::Instance& inst : obj->instances()) { +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d view_model_matrix = camera.get_view_matrix() * + Geometry::assemble_transform(Vec3d(unscale(inst.shift.x()), unscale(inst.shift.y()), 0.0), + inst.rotation * Vec3d::UnitZ(), Vec3d::Ones(), + obj->is_left_handed() ? Vec3d(-1.0f, 1.0f, 1.0f) : Vec3d::Ones()); + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#else + glsafe(::glPushMatrix()); + glsafe(::glTranslated(unscale(inst.shift.x()), unscale(inst.shift.y()), 0.0)); + glsafe(::glRotatef(Geometry::rad2deg(inst.rotation), 0.0f, 0.0f, 1.0f)); + if (obj->is_left_handed()) + // The polygons are mirrored by X. + glsafe(::glScalef(-1.0f, 1.0f, 1.0f)); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + bottom_obj_triangles.render(); + top_obj_triangles.render(); + bottom_sup_triangles.render(); + top_sup_triangles.render(); + +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + } + + shader->stop_using(); + } +#else + if (!bottom_obj_triangles.empty() || !top_obj_triangles.empty() || !bottom_sup_triangles.empty() || !top_sup_triangles.empty()) { + for (const SLAPrintObject::Instance& inst : obj->instances()) { + glsafe(::glPushMatrix()); + glsafe(::glTranslated(unscale(inst.shift.x()), unscale(inst.shift.y()), 0.0)); + glsafe(::glRotatef(Geometry::rad2deg(inst.rotation), 0.0f, 0.0f, 1.0f)); + if (obj->is_left_handed()) + // The polygons are mirrored by X. + glsafe(::glScalef(-1.0f, 1.0f, 1.0f)); + glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); + glsafe(::glColor3f(1.0f, 0.37f, 0.0f)); + if (!bottom_obj_triangles.empty()) { + glsafe(::glVertexPointer(3, GL_DOUBLE, 0, (GLdouble*)bottom_obj_triangles.front().data())); + glsafe(::glDrawArrays(GL_TRIANGLES, 0, bottom_obj_triangles.size())); + } + if (! top_obj_triangles.empty()) { + glsafe(::glVertexPointer(3, GL_DOUBLE, 0, (GLdouble*)top_obj_triangles.front().data())); + glsafe(::glDrawArrays(GL_TRIANGLES, 0, top_obj_triangles.size())); + } + glsafe(::glColor3f(1.0f, 0.0f, 0.37f)); + if (! bottom_sup_triangles.empty()) { + glsafe(::glVertexPointer(3, GL_DOUBLE, 0, (GLdouble*)bottom_sup_triangles.front().data())); + glsafe(::glDrawArrays(GL_TRIANGLES, 0, bottom_sup_triangles.size())); + } + if (! top_sup_triangles.empty()) { + glsafe(::glVertexPointer(3, GL_DOUBLE, 0, (GLdouble*)top_sup_triangles.front().data())); + glsafe(::glDrawArrays(GL_TRIANGLES, 0, top_sup_triangles.size())); + } + glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); + glsafe(::glPopMatrix()); + } + } +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } +} + +void GLCanvas3D::_render_selection_sidebar_hints() +{ + m_selection.render_sidebar_hints(m_sidebar_field); +} + +void GLCanvas3D::_update_volumes_hover_state() +{ + for (GLVolume* v : m_volumes.volumes) { + v->hover = GLVolume::HS_None; + } + + if (m_hover_volume_idxs.empty()) + return; + + bool ctrl_pressed = wxGetKeyState(WXK_CONTROL); // additive select/deselect + bool shift_pressed = wxGetKeyState(WXK_SHIFT); // select by rectangle + bool alt_pressed = wxGetKeyState(WXK_ALT); // deselect by rectangle + + if (alt_pressed && (shift_pressed || ctrl_pressed)) { + // illegal combinations of keys + m_hover_volume_idxs.clear(); + return; + } + +#if !ENABLE_NEW_RECTANGLE_SELECTION + bool selection_modifiers_only = m_selection.is_empty() || m_selection.is_any_modifier(); +#endif // !ENABLE_NEW_RECTANGLE_SELECTION + + bool hover_modifiers_only = true; + for (int i : m_hover_volume_idxs) { + if (!m_volumes.volumes[i]->is_modifier) { + hover_modifiers_only = false; + break; + } + } + + std::set> hover_instances; + for (int i : m_hover_volume_idxs) { + const GLVolume& v = *m_volumes.volumes[i]; + hover_instances.insert(std::make_pair(v.object_idx(), v.instance_idx())); + } + + bool hover_from_single_instance = hover_instances.size() == 1; + + if (hover_modifiers_only && !hover_from_single_instance) { + // do not allow to select volumes from different instances + m_hover_volume_idxs.clear(); + return; + } + + for (int i : m_hover_volume_idxs) { + GLVolume& volume = *m_volumes.volumes[i]; + if (volume.hover != GLVolume::HS_None) + continue; + +#if ENABLE_NEW_RECTANGLE_SELECTION + bool deselect = volume.selected && ((shift_pressed && m_rectangle_selection.is_empty()) || (alt_pressed && !m_rectangle_selection.is_empty())); + bool select = !volume.selected && (m_rectangle_selection.is_empty() || (shift_pressed && !m_rectangle_selection.is_empty())); +#else + bool deselect = volume.selected && ((ctrl_pressed && !shift_pressed) || alt_pressed); + // (volume->is_modifier && !selection_modifiers_only && !is_ctrl_pressed) -> allows hovering on selected modifiers belonging to selection of type Instance + bool select = (!volume.selected || (volume.is_modifier && !selection_modifiers_only && !ctrl_pressed)) && !alt_pressed; +#endif // ENABLE_NEW_RECTANGLE_SELECTION + + if (select || deselect) { + bool as_volume = + volume.is_modifier && hover_from_single_instance && !ctrl_pressed && + ( + (!deselect) || + (deselect && !m_selection.is_single_full_instance() && (volume.object_idx() == m_selection.get_object_idx()) && (volume.instance_idx() == m_selection.get_instance_idx())) + ); + + if (as_volume) + volume.hover = deselect ? GLVolume::HS_Deselect : GLVolume::HS_Select; + else { + int object_idx = volume.object_idx(); + int instance_idx = volume.instance_idx(); + + for (GLVolume* v : m_volumes.volumes) { + if (v->object_idx() == object_idx && v->instance_idx() == instance_idx) + v->hover = deselect ? GLVolume::HS_Deselect : GLVolume::HS_Select; + } + } + } + else if (volume.selected) + volume.hover = GLVolume::HS_Hover; + } +} + +void GLCanvas3D::_perform_layer_editing_action(wxMouseEvent* evt) +{ + int object_idx_selected = m_layers_editing.last_object_id; + if (object_idx_selected == -1) + return; + + // A volume is selected. Test, whether hovering over a layer thickness bar. + if (evt != nullptr) { + const Rect& rect = LayersEditing::get_bar_rect_screen(*this); + float b = rect.get_bottom(); + m_layers_editing.last_z = m_layers_editing.object_max_z() * (b - evt->GetY() - 1.0f) / (b - rect.get_top()); + m_layers_editing.last_action = + evt->ShiftDown() ? (evt->RightIsDown() ? LAYER_HEIGHT_EDIT_ACTION_SMOOTH : LAYER_HEIGHT_EDIT_ACTION_REDUCE) : + (evt->RightIsDown() ? LAYER_HEIGHT_EDIT_ACTION_INCREASE : LAYER_HEIGHT_EDIT_ACTION_DECREASE); + } + + m_layers_editing.adjust_layer_height_profile(); + _refresh_if_shown_on_screen(); + + // Automatic action on mouse down with the same coordinate. + _start_timer(); +} + +Vec3d GLCanvas3D::_mouse_to_3d(const Point& mouse_pos, float* z) +{ + if (m_canvas == nullptr) + return Vec3d(DBL_MAX, DBL_MAX, DBL_MAX); + + const Camera& camera = wxGetApp().plater()->get_camera(); + const Matrix4d modelview = camera.get_view_matrix().matrix(); + const Matrix4d projection = camera.get_projection_matrix().matrix(); + const Vec4i viewport(camera.get_viewport().data()); + + const int y = viewport[3] - mouse_pos.y(); + float mouse_z; + if (z == nullptr) + glsafe(::glReadPixels(mouse_pos.x(), y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, (void*)&mouse_z)); + else + mouse_z = *z; + + Vec3d out; + igl::unproject(Vec3d(mouse_pos.x(), y, mouse_z), modelview, projection, viewport, out); + return out; +} + +Vec3d GLCanvas3D::_mouse_to_bed_3d(const Point& mouse_pos) +{ + return mouse_ray(mouse_pos).intersect_plane(0.0); +} + +void GLCanvas3D::_start_timer() +{ + m_timer.Start(100, wxTIMER_CONTINUOUS); +} + +void GLCanvas3D::_stop_timer() +{ + m_timer.Stop(); +} + +void GLCanvas3D::_load_print_toolpaths(const BuildVolume &build_volume) +{ + const Print *print = this->fff_print(); + if (print == nullptr) + return; + + if (! print->is_step_done(psSkirtBrim)) + return; + + if (!print->has_skirt() && !print->has_brim()) + return; + + const ColorRGBA color = ColorRGBA::GREENISH(); + + // number of skirt layers + size_t total_layer_count = 0; + for (const PrintObject* print_object : print->objects()) { + total_layer_count = std::max(total_layer_count, print_object->total_layer_count()); + } + size_t skirt_height = print->has_infinite_skirt() ? total_layer_count : std::min(print->config().skirt_height.value, total_layer_count); + if (skirt_height == 0 && print->has_brim()) + skirt_height = 1; + + // Get first skirt_height layers. + //FIXME This code is fishy. It may not work for multiple objects with different layering due to variable layer height feature. + // This is not critical as this is just an initial preview. + const PrintObject* highest_object = *std::max_element(print->objects().begin(), print->objects().end(), [](auto l, auto r){ return l->layers().size() < r->layers().size(); }); + std::vector print_zs; + print_zs.reserve(skirt_height * 2); + for (size_t i = 0; i < std::min(skirt_height, highest_object->layers().size()); ++ i) + print_zs.emplace_back(float(highest_object->layers()[i]->print_z)); + // Only add skirt for the raft layers. + for (size_t i = 0; i < std::min(skirt_height, std::min(highest_object->slicing_parameters().raft_layers(), highest_object->support_layers().size())); ++ i) + print_zs.emplace_back(float(highest_object->support_layers()[i]->print_z)); + sort_remove_duplicates(print_zs); + skirt_height = std::min(skirt_height, print_zs.size()); + print_zs.erase(print_zs.begin() + skirt_height, print_zs.end()); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLVolume* volume = m_volumes.new_toolpath_volume(color); + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; +#else + GLVolume *volume = m_volumes.new_toolpath_volume(color, VERTEX_BUFFER_RESERVE_SIZE); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + for (size_t i = 0; i < skirt_height; ++ i) { + volume->print_zs.emplace_back(print_zs[i]); +#if ENABLE_LEGACY_OPENGL_REMOVAL + volume->offsets.emplace_back(init_data.indices_count()); + if (i == 0) + _3DScene::extrusionentity_to_verts(print->brim(), print_zs[i], Point(0, 0), init_data); + _3DScene::extrusionentity_to_verts(print->skirt(), print_zs[i], Point(0, 0), init_data); +#else + volume->offsets.emplace_back(volume->indexed_vertex_array.quad_indices.size()); + volume->offsets.emplace_back(volume->indexed_vertex_array.triangle_indices.size()); + if (i == 0) + _3DScene::extrusionentity_to_verts(print->brim(), print_zs[i], Point(0, 0), *volume); + _3DScene::extrusionentity_to_verts(print->skirt(), print_zs[i], Point(0, 0), *volume); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + // Ensure that no volume grows over the limits. If the volume is too large, allocate a new one. +#if ENABLE_LEGACY_OPENGL_REMOVAL + if (init_data.vertices_size_bytes() > MAX_VERTEX_BUFFER_SIZE) { + volume->model.init_from(std::move(init_data)); +#else + if (volume->indexed_vertex_array.vertices_and_normals_interleaved.size() > MAX_VERTEX_BUFFER_SIZE) { +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + GLVolume &vol = *volume; + volume = m_volumes.new_toolpath_volume(vol.color); +#if !ENABLE_LEGACY_OPENGL_REMOVAL + reserve_new_volume_finalize_old_volume(*volume, vol, m_initialized); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + } + } +#if ENABLE_LEGACY_OPENGL_REMOVAL + volume->model.init_from(std::move(init_data)); + volume->is_outside = !contains(build_volume, volume->model); +#else + volume->is_outside = ! build_volume.all_paths_inside_vertices_and_normals_interleaved(volume->indexed_vertex_array.vertices_and_normals_interleaved, volume->indexed_vertex_array.bounding_box()); + volume->indexed_vertex_array.finalize_geometry(m_initialized); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} + +void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, const BuildVolume& build_volume, const std::vector& str_tool_colors, const std::vector& color_print_values) +{ + std::vector tool_colors; + decode_colors(str_tool_colors, tool_colors); + + struct Ctxt + { + const PrintInstances *shifted_copies; + std::vector layers; + bool has_perimeters; + bool has_infill; + bool has_support; + const std::vector* tool_colors; + bool is_single_material_print; + int extruders_cnt; + const std::vector* color_print_values; + + static ColorRGBA color_perimeters() { return ColorRGBA::YELLOW(); } + static ColorRGBA color_infill() { return ColorRGBA::REDISH(); } + static ColorRGBA color_support() { return ColorRGBA::GREENISH(); } + static ColorRGBA color_pause_or_custom_code() { return ColorRGBA::GRAY(); } + + // For cloring by a tool, return a parsed color. + bool color_by_tool() const { return tool_colors != nullptr; } + size_t number_tools() const { return color_by_tool() ? tool_colors->size() : 0; } + const ColorRGBA& color_tool(size_t tool) const { return (*tool_colors)[tool]; } + + // For coloring by a color_print(M600), return a parsed color. + bool color_by_color_print() const { return color_print_values!=nullptr; } + const size_t color_print_color_idx_by_layer_idx(const size_t layer_idx) const { + const CustomGCode::Item value{layers[layer_idx]->print_z + EPSILON, CustomGCode::Custom, 0, ""}; + auto it = std::lower_bound(color_print_values->begin(), color_print_values->end(), value); + return (it - color_print_values->begin()) % number_tools(); + } + + const size_t color_print_color_idx_by_layer_idx_and_extruder(const size_t layer_idx, const int extruder) const + { + const coordf_t print_z = layers[layer_idx]->print_z; + + auto it = std::find_if(color_print_values->begin(), color_print_values->end(), + [print_z](const CustomGCode::Item& code) + { return fabs(code.print_z - print_z) < EPSILON; }); + if (it != color_print_values->end()) { + CustomGCode::Type type = it->type; + // pause print or custom Gcode + if (type == CustomGCode::PausePrint || + (type != CustomGCode::ColorChange && type != CustomGCode::ToolChange)) + return number_tools()-1; // last color item is a gray color for pause print or custom G-code + + // change tool (extruder) + if (type == CustomGCode::ToolChange) + return get_color_idx_for_tool_change(it, extruder); + // change color for current extruder + if (type == CustomGCode::ColorChange) { + int color_idx = get_color_idx_for_color_change(it, extruder); + if (color_idx >= 0) + return color_idx; + } + } + + const CustomGCode::Item value{print_z + EPSILON, CustomGCode::Custom, 0, ""}; + it = std::lower_bound(color_print_values->begin(), color_print_values->end(), value); + while (it != color_print_values->begin()) { + --it; + // change color for current extruder + if (it->type == CustomGCode::ColorChange) { + int color_idx = get_color_idx_for_color_change(it, extruder); + if (color_idx >= 0) + return color_idx; + } + // change tool (extruder) + if (it->type == CustomGCode::ToolChange) + return get_color_idx_for_tool_change(it, extruder); + } + + return std::min(extruders_cnt - 1, std::max(extruder - 1, 0));; + } + + private: + int get_m600_color_idx(std::vector::const_iterator it) const + { + int shift = 0; + while (it != color_print_values->begin()) { + --it; + if (it->type == CustomGCode::ColorChange) + shift++; + } + return extruders_cnt + shift; + } + + int get_color_idx_for_tool_change(std::vector::const_iterator it, const int extruder) const + { + const int current_extruder = it->extruder == 0 ? extruder : it->extruder; + if (number_tools() == size_t(extruders_cnt + 1)) // there is no one "M600" + return std::min(extruders_cnt - 1, std::max(current_extruder - 1, 0)); + + auto it_n = it; + while (it_n != color_print_values->begin()) { + --it_n; + if (it_n->type == CustomGCode::ColorChange && it_n->extruder == current_extruder) + return get_m600_color_idx(it_n); + } + + return std::min(extruders_cnt - 1, std::max(current_extruder - 1, 0)); + } + + int get_color_idx_for_color_change(std::vector::const_iterator it, const int extruder) const + { + if (extruders_cnt == 1) + return get_m600_color_idx(it); + + auto it_n = it; + bool is_tool_change = false; + while (it_n != color_print_values->begin()) { + --it_n; + if (it_n->type == CustomGCode::ToolChange) { + is_tool_change = true; + if (it_n->extruder == it->extruder || (it_n->extruder == 0 && it->extruder == extruder)) + return get_m600_color_idx(it); + break; + } + } + if (!is_tool_change && it->extruder == extruder) + return get_m600_color_idx(it); + + return -1; + } + + } ctxt; + + ctxt.has_perimeters = print_object.is_step_done(posPerimeters); + ctxt.has_infill = print_object.is_step_done(posInfill); + ctxt.has_support = print_object.is_step_done(posSupportMaterial); + ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors; + ctxt.color_print_values = color_print_values.empty() ? nullptr : &color_print_values; + ctxt.is_single_material_print = this->fff_print()->extruders().size()==1; + ctxt.extruders_cnt = wxGetApp().extruders_edited_cnt(); + + ctxt.shifted_copies = &print_object.instances(); + + // order layers by print_z + { + size_t nlayers = 0; + if (ctxt.has_perimeters || ctxt.has_infill) + nlayers = print_object.layers().size(); + if (ctxt.has_support) + nlayers += print_object.support_layers().size(); + ctxt.layers.reserve(nlayers); + } + if (ctxt.has_perimeters || ctxt.has_infill) + for (const Layer *layer : print_object.layers()) + ctxt.layers.emplace_back(layer); + if (ctxt.has_support) + for (const Layer *layer : print_object.support_layers()) + ctxt.layers.emplace_back(layer); + std::sort(ctxt.layers.begin(), ctxt.layers.end(), [](const Layer *l1, const Layer *l2) { return l1->print_z < l2->print_z; }); + + // Maximum size of an allocation block: 32MB / sizeof(float) + BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - start" << m_volumes.log_memory_info() << log_memory_info(); + + const bool is_selected_separate_extruder = m_selected_extruder > 0 && ctxt.color_by_color_print(); + + //FIXME Improve the heuristics for a grain size. + size_t grain_size = std::max(ctxt.layers.size() / 16, size_t(1)); + tbb::spin_mutex new_volume_mutex; + auto new_volume = [this, &new_volume_mutex](const ColorRGBA& color) { + // Allocate the volume before locking. + GLVolume *volume = new GLVolume(color); + volume->is_extrusion_path = true; +#if ENABLE_LEGACY_OPENGL_REMOVAL + // to prevent sending data to gpu (in the main thread) while + // editing the model geometry + volume->model.disable_render(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + tbb::spin_mutex::scoped_lock lock; + // Lock by ROII, so if the emplace_back() fails, the lock will be released. + lock.acquire(new_volume_mutex); + m_volumes.volumes.emplace_back(volume); + lock.release(); + return volume; + }; + const size_t volumes_cnt_initial = m_volumes.volumes.size(); + tbb::parallel_for( + tbb::blocked_range(0, ctxt.layers.size(), grain_size), + [&ctxt, &new_volume, is_selected_separate_extruder, this](const tbb::blocked_range& range) { + GLVolumePtrs vols; +#if ENABLE_LEGACY_OPENGL_REMOVAL + std::vector geometries; + auto select_geometry = [&ctxt, &geometries](size_t layer_idx, int extruder, int feature) -> GLModel::Geometry& { + return geometries[ctxt.color_by_color_print() ? + ctxt.color_print_color_idx_by_layer_idx_and_extruder(layer_idx, extruder) : + ctxt.color_by_tool() ? + std::min(ctxt.number_tools() - 1, std::max(extruder - 1, 0)) : + feature + ]; + }; +#else + auto volume = [&ctxt, &vols](size_t layer_idx, int extruder, int feature) -> GLVolume& { + return *vols[ctxt.color_by_color_print() ? + ctxt.color_print_color_idx_by_layer_idx_and_extruder(layer_idx, extruder) : + ctxt.color_by_tool() ? + std::min(ctxt.number_tools() - 1, std::max(extruder - 1, 0)) : + feature + ]; + }; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + if (ctxt.color_by_color_print() || ctxt.color_by_tool()) { + for (size_t i = 0; i < ctxt.number_tools(); ++i) { + vols.emplace_back(new_volume(ctxt.color_tool(i))); +#if ENABLE_LEGACY_OPENGL_REMOVAL + geometries.emplace_back(GLModel::Geometry()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + } + else { + vols = { new_volume(ctxt.color_perimeters()), new_volume(ctxt.color_infill()), new_volume(ctxt.color_support()) }; +#if ENABLE_LEGACY_OPENGL_REMOVAL + geometries = { GLModel::Geometry(), GLModel::Geometry(), GLModel::Geometry() }; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + assert(vols.size() == geometries.size()); + for (GLModel::Geometry& g : geometries) { + g.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + } +#else + for (GLVolume *vol : vols) + // Reserving number of vertices (3x position + 3x color) + vol->indexed_vertex_array.reserve(VERTEX_BUFFER_RESERVE_SIZE / 6); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { + const Layer *layer = ctxt.layers[idx_layer]; + + if (is_selected_separate_extruder) { + bool at_least_one_has_correct_extruder = false; + for (const LayerRegion* layerm : layer->regions()) { + if (layerm->slices.surfaces.empty()) + continue; + const PrintRegionConfig& cfg = layerm->region().config(); + if (cfg.perimeter_extruder.value == m_selected_extruder || + cfg.infill_extruder.value == m_selected_extruder || + cfg.solid_infill_extruder.value == m_selected_extruder ) { + at_least_one_has_correct_extruder = true; + break; + } + } + if (!at_least_one_has_correct_extruder) + continue; + } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + for (size_t i = 0; i < vols.size(); ++i) { + GLVolume* vol = vols[i]; + if (vol->print_zs.empty() || vol->print_zs.back() != layer->print_z) { + vol->print_zs.emplace_back(layer->print_z); + vol->offsets.emplace_back(geometries[i].indices_count()); + } + } +#else + for (GLVolume* vol : vols) + if (vol->print_zs.empty() || vol->print_zs.back() != layer->print_z) { + vol->print_zs.emplace_back(layer->print_z); + vol->offsets.emplace_back(vol->indexed_vertex_array.quad_indices.size()); + vol->offsets.emplace_back(vol->indexed_vertex_array.triangle_indices.size()); + } +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + for (const PrintInstance &instance : *ctxt.shifted_copies) { + const Point © = instance.shift; + for (const LayerRegion *layerm : layer->regions()) { + if (is_selected_separate_extruder) { + const PrintRegionConfig& cfg = layerm->region().config(); + if (cfg.perimeter_extruder.value != m_selected_extruder || + cfg.infill_extruder.value != m_selected_extruder || + cfg.solid_infill_extruder.value != m_selected_extruder) + continue; + } + if (ctxt.has_perimeters) +#if ENABLE_LEGACY_OPENGL_REMOVAL + _3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy, + select_geometry(idx_layer, layerm->region().config().perimeter_extruder.value, 0)); +#else + _3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy, + volume(idx_layer, layerm->region().config().perimeter_extruder.value, 0)); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + if (ctxt.has_infill) { + for (const ExtrusionEntity *ee : layerm->fills.entities) { + // fill represents infill extrusions of a single island. + const auto *fill = dynamic_cast(ee); + if (! fill->entities.empty()) +#if ENABLE_LEGACY_OPENGL_REMOVAL + _3DScene::extrusionentity_to_verts(*fill, float(layer->print_z), copy, + select_geometry(idx_layer, is_solid_infill(fill->entities.front()->role()) ? + layerm->region().config().solid_infill_extruder : + layerm->region().config().infill_extruder, 1)); +#else + _3DScene::extrusionentity_to_verts(*fill, float(layer->print_z), copy, + volume(idx_layer, + is_solid_infill(fill->entities.front()->role()) ? + layerm->region().config().solid_infill_extruder : + layerm->region().config().infill_extruder, + 1)); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + } + } + if (ctxt.has_support) { + const SupportLayer *support_layer = dynamic_cast(layer); + if (support_layer) { + for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities) +#if ENABLE_LEGACY_OPENGL_REMOVAL + _3DScene::extrusionentity_to_verts(extrusion_entity, float(layer->print_z), copy, + select_geometry(idx_layer, (extrusion_entity->role() == erSupportMaterial) ? + support_layer->object()->config().support_material_extruder : + support_layer->object()->config().support_material_interface_extruder, 2)); +#else + _3DScene::extrusionentity_to_verts(extrusion_entity, float(layer->print_z), copy, + volume(idx_layer, + (extrusion_entity->role() == erSupportMaterial) ? + support_layer->object()->config().support_material_extruder : + support_layer->object()->config().support_material_interface_extruder, + 2)); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + } + } + // Ensure that no volume grows over the limits. If the volume is too large, allocate a new one. + for (size_t i = 0; i < vols.size(); ++i) { + GLVolume &vol = *vols[i]; +#if ENABLE_LEGACY_OPENGL_REMOVAL + if (geometries[i].vertices_size_bytes() > MAX_VERTEX_BUFFER_SIZE) { + vol.model.init_from(std::move(geometries[i])); +#else + if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() > MAX_VERTEX_BUFFER_SIZE) { +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + vols[i] = new_volume(vol.color); +#if !ENABLE_LEGACY_OPENGL_REMOVAL + reserve_new_volume_finalize_old_volume(*vols[i], vol, false); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + } + } + } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + for (size_t i = 0; i < vols.size(); ++i) { + if (!geometries[i].is_empty()) + vols[i]->model.init_from(std::move(geometries[i])); + } +#else + for (GLVolume *vol : vols) + // Ideally one would call vol->indexed_vertex_array.finalize() here to move the buffers to the OpenGL driver, + // but this code runs in parallel and the OpenGL driver is not thread safe. + vol->indexed_vertex_array.shrink_to_fit(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + }); + + BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - finalizing results" << m_volumes.log_memory_info() << log_memory_info(); + // Remove empty volumes from the newly added volumes. + { + for (auto ptr_it = m_volumes.volumes.begin() + volumes_cnt_initial; ptr_it != m_volumes.volumes.end(); ++ptr_it) + if ((*ptr_it)->empty()) { + delete *ptr_it; + *ptr_it = nullptr; + } + m_volumes.volumes.erase(std::remove(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), nullptr), m_volumes.volumes.end()); + } + for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) { + GLVolume* v = m_volumes.volumes[i]; +#if ENABLE_LEGACY_OPENGL_REMOVAL + v->is_outside = !contains(build_volume, v->model); + // We are done editinig the model, now it can be sent to gpu + v->model.enable_render(); +#else + v->is_outside = ! build_volume.all_paths_inside_vertices_and_normals_interleaved(v->indexed_vertex_array.vertices_and_normals_interleaved, v->indexed_vertex_array.bounding_box()); + v->indexed_vertex_array.finalize_geometry(m_initialized); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + + BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end" << m_volumes.log_memory_info() << log_memory_info(); +} + +void GLCanvas3D::_load_wipe_tower_toolpaths(const BuildVolume& build_volume, const std::vector& str_tool_colors) +{ + const Print *print = this->fff_print(); + if (print == nullptr || print->wipe_tower_data().tool_changes.empty()) + return; + + if (!print->is_step_done(psWipeTower)) + return; + + std::vector tool_colors; + decode_colors(str_tool_colors, tool_colors); + + struct Ctxt + { + const Print *print; + const std::vector *tool_colors; + Vec2f wipe_tower_pos; + float wipe_tower_angle; + + static ColorRGBA color_support() { return ColorRGBA::GREENISH(); } + + // For cloring by a tool, return a parsed color. + bool color_by_tool() const { return tool_colors != nullptr; } + size_t number_tools() const { return this->color_by_tool() ? tool_colors->size() : 0; } + const ColorRGBA& color_tool(size_t tool) const { return (*tool_colors)[tool]; } + int volume_idx(int tool, int feature) const { + return this->color_by_tool() ? std::min(this->number_tools() - 1, std::max(tool, 0)) : feature; + } + + const std::vector& tool_change(size_t idx) { + const auto &tool_changes = print->wipe_tower_data().tool_changes; + return priming.empty() ? + ((idx == tool_changes.size()) ? final : tool_changes[idx]) : + ((idx == 0) ? priming : (idx == tool_changes.size() + 1) ? final : tool_changes[idx - 1]); + } + std::vector priming; + std::vector final; + } ctxt; + + ctxt.print = print; + ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors; + if (print->wipe_tower_data().priming && print->config().single_extruder_multi_material_priming) + for (int i=0; i<(int)print->wipe_tower_data().priming.get()->size(); ++i) + ctxt.priming.emplace_back(print->wipe_tower_data().priming.get()->at(i)); + if (print->wipe_tower_data().final_purge) + ctxt.final.emplace_back(*print->wipe_tower_data().final_purge.get()); + + ctxt.wipe_tower_angle = ctxt.print->config().wipe_tower_rotation_angle.value/180.f * PI; + ctxt.wipe_tower_pos = Vec2f(ctxt.print->config().wipe_tower_x.value, ctxt.print->config().wipe_tower_y.value); + + BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - start" << m_volumes.log_memory_info() << log_memory_info(); + + //FIXME Improve the heuristics for a grain size. + size_t n_items = print->wipe_tower_data().tool_changes.size() + (ctxt.priming.empty() ? 0 : 1); + size_t grain_size = std::max(n_items / 128, size_t(1)); + tbb::spin_mutex new_volume_mutex; + auto new_volume = [this, &new_volume_mutex](const ColorRGBA& color) { + auto *volume = new GLVolume(color); + volume->is_extrusion_path = true; +#if ENABLE_LEGACY_OPENGL_REMOVAL + // to prevent sending data to gpu (in the main thread) while + // editing the model geometry + volume->model.disable_render(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + tbb::spin_mutex::scoped_lock lock; + lock.acquire(new_volume_mutex); + m_volumes.volumes.emplace_back(volume); + lock.release(); + return volume; + }; + const size_t volumes_cnt_initial = m_volumes.volumes.size(); + std::vector volumes_per_thread(n_items); + tbb::parallel_for( + tbb::blocked_range(0, n_items, grain_size), + [&ctxt, &new_volume](const tbb::blocked_range& range) { + // Bounding box of this slab of a wipe tower. + GLVolumePtrs vols; +#if ENABLE_LEGACY_OPENGL_REMOVAL + std::vector geometries; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + if (ctxt.color_by_tool()) { + for (size_t i = 0; i < ctxt.number_tools(); ++i) { + vols.emplace_back(new_volume(ctxt.color_tool(i))); +#if ENABLE_LEGACY_OPENGL_REMOVAL + geometries.emplace_back(GLModel::Geometry()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + } + else { + vols = { new_volume(ctxt.color_support()) }; +#if ENABLE_LEGACY_OPENGL_REMOVAL + geometries = { GLModel::Geometry() }; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + assert(vols.size() == geometries.size()); + for (GLModel::Geometry& g : geometries) { + g.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + } +#else + for (GLVolume *volume : vols) + // Reserving number of vertices (3x position + 3x color) + volume->indexed_vertex_array.reserve(VERTEX_BUFFER_RESERVE_SIZE / 6); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) { + const std::vector &layer = ctxt.tool_change(idx_layer); + for (size_t i = 0; i < vols.size(); ++i) { + GLVolume &vol = *vols[i]; + if (vol.print_zs.empty() || vol.print_zs.back() != layer.front().print_z) { + vol.print_zs.emplace_back(layer.front().print_z); +#if ENABLE_LEGACY_OPENGL_REMOVAL + vol.offsets.emplace_back(geometries[i].indices_count()); +#else + vol.offsets.emplace_back(vol.indexed_vertex_array.quad_indices.size()); + vol.offsets.emplace_back(vol.indexed_vertex_array.triangle_indices.size()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + } + for (const WipeTower::ToolChangeResult &extrusions : layer) { + for (size_t i = 1; i < extrusions.extrusions.size();) { + const WipeTower::Extrusion &e = extrusions.extrusions[i]; + if (e.width == 0.) { + ++i; + continue; + } + size_t j = i + 1; + if (ctxt.color_by_tool()) + for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].tool == e.tool && extrusions.extrusions[j].width > 0.f; ++j); + else + for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].width > 0.f; ++j); + size_t n_lines = j - i; + Lines lines; + std::vector widths; + std::vector heights; + lines.reserve(n_lines); + widths.reserve(n_lines); + heights.assign(n_lines, extrusions.layer_height); + WipeTower::Extrusion e_prev = extrusions.extrusions[i-1]; + + if (!extrusions.priming) { // wipe tower extrusions describe the wipe tower at the origin with no rotation + e_prev.pos = Eigen::Rotation2Df(ctxt.wipe_tower_angle) * e_prev.pos; + e_prev.pos += ctxt.wipe_tower_pos; + } + + for (; i < j; ++i) { + WipeTower::Extrusion e = extrusions.extrusions[i]; + assert(e.width > 0.f); + if (!extrusions.priming) { + e.pos = Eigen::Rotation2Df(ctxt.wipe_tower_angle) * e.pos; + e.pos += ctxt.wipe_tower_pos; + } + + lines.emplace_back(Point::new_scale(e_prev.pos.x(), e_prev.pos.y()), Point::new_scale(e.pos.x(), e.pos.y())); + widths.emplace_back(e.width); + + e_prev = e; + } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + _3DScene::thick_lines_to_verts(lines, widths, heights, lines.front().a == lines.back().b, extrusions.print_z, + geometries[ctxt.volume_idx(e.tool, 0)]); +#else + _3DScene::thick_lines_to_verts(lines, widths, heights, lines.front().a == lines.back().b, extrusions.print_z, + *vols[ctxt.volume_idx(e.tool, 0)]); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + } + } + for (size_t i = 0; i < vols.size(); ++i) { + GLVolume &vol = *vols[i]; +#if ENABLE_LEGACY_OPENGL_REMOVAL + if (geometries[i].vertices_size_bytes() > MAX_VERTEX_BUFFER_SIZE) { + vol.model.init_from(std::move(geometries[i])); +#else + if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() > MAX_VERTEX_BUFFER_SIZE) { +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + vols[i] = new_volume(vol.color); +#if !ENABLE_LEGACY_OPENGL_REMOVAL + reserve_new_volume_finalize_old_volume(*vols[i], vol, false); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + } + } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + for (size_t i = 0; i < vols.size(); ++i) { + if (!geometries[i].is_empty()) + vols[i]->model.init_from(std::move(geometries[i])); + } +#else + for (GLVolume *vol : vols) + vol->indexed_vertex_array.shrink_to_fit(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + }); + + BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - finalizing results" << m_volumes.log_memory_info() << log_memory_info(); + // Remove empty volumes from the newly added volumes. + { + for (auto ptr_it = m_volumes.volumes.begin() + volumes_cnt_initial; ptr_it != m_volumes.volumes.end(); ++ptr_it) + if ((*ptr_it)->empty()) { + delete *ptr_it; + *ptr_it = nullptr; + } + m_volumes.volumes.erase(std::remove(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), nullptr), m_volumes.volumes.end()); + } + for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) { + GLVolume* v = m_volumes.volumes[i]; +#if ENABLE_LEGACY_OPENGL_REMOVAL + v->is_outside = !contains(build_volume, v->model); + // We are done editinig the model, now it can be sent to gpu + v->model.enable_render(); +#else + v->is_outside = ! build_volume.all_paths_inside_vertices_and_normals_interleaved(v->indexed_vertex_array.vertices_and_normals_interleaved, v->indexed_vertex_array.bounding_box()); + v->indexed_vertex_array.finalize_geometry(m_initialized); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + + BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end" << m_volumes.log_memory_info() << log_memory_info(); +} + +// While it looks like we can call +// this->reload_scene(true, true) +// the two functions are quite different: +// 1) This function only loads objects, for which the step slaposSliceSupports already finished. Therefore objects outside of the print bed never load. +// 2) This function loads object mesh with the relative scaling correction (the "relative_correction" parameter) was applied, +// therefore the mesh may be slightly larger or smaller than the mesh shown in the 3D scene. +void GLCanvas3D::_load_sla_shells() +{ + const SLAPrint* print = this->sla_print(); + if (print->objects().empty()) + // nothing to render, return + return; + + auto add_volume = [this](const SLAPrintObject &object, int volume_id, const SLAPrintObject::Instance& instance, + const TriangleMesh& mesh, const ColorRGBA& color, bool outside_printer_detection_enabled) { + m_volumes.volumes.emplace_back(new GLVolume(color)); + GLVolume& v = *m_volumes.volumes.back(); +#if ENABLE_SMOOTH_NORMALS +#if ENABLE_LEGACY_OPENGL_REMOVAL + v.model.init_from(mesh, true); +#else + v.indexed_vertex_array.load_mesh(mesh, true); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +#else +#if ENABLE_LEGACY_OPENGL_REMOVAL + v.model.init_from(mesh); +#else + v.indexed_vertex_array.load_mesh(mesh); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +#endif // ENABLE_SMOOTH_NORMALS +#if !ENABLE_LEGACY_OPENGL_REMOVAL + v.indexed_vertex_array.finalize_geometry(m_initialized); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + v.shader_outside_printer_detection_enabled = outside_printer_detection_enabled; + v.composite_id.volume_id = volume_id; + v.set_instance_offset(unscale(instance.shift.x(), instance.shift.y(), 0.0)); + v.set_instance_rotation({ 0.0, 0.0, (double)instance.rotation }); + v.set_instance_mirror(X, object.is_left_handed() ? -1. : 1.); + v.set_convex_hull(mesh.convex_hull_3d()); + }; + + // adds objects' volumes + for (const SLAPrintObject* obj : print->objects()) + if (obj->is_step_done(slaposSliceSupports)) { + unsigned int initial_volumes_count = (unsigned int)m_volumes.volumes.size(); + for (const SLAPrintObject::Instance& instance : obj->instances()) { + add_volume(*obj, 0, instance, obj->get_mesh_to_print(), GLVolume::MODEL_COLOR[0], true); + // Set the extruder_id and volume_id to achieve the same color as in the 3D scene when + // through the update_volumes_colors_by_extruder() call. + m_volumes.volumes.back()->extruder_id = obj->model_object()->volumes.front()->extruder_id(); + if (obj->is_step_done(slaposSupportTree) && obj->has_mesh(slaposSupportTree)) + add_volume(*obj, -int(slaposSupportTree), instance, obj->support_mesh(), GLVolume::SLA_SUPPORT_COLOR, true); + if (obj->is_step_done(slaposPad) && obj->has_mesh(slaposPad)) + add_volume(*obj, -int(slaposPad), instance, obj->pad_mesh(), GLVolume::SLA_PAD_COLOR, false); + } + double shift_z = obj->get_current_elevation(); + for (unsigned int i = initial_volumes_count; i < m_volumes.volumes.size(); ++ i) { + // apply shift z + m_volumes.volumes[i]->set_sla_shift_z(shift_z); + } + } + + update_volumes_colors_by_extruder(); +} + +void GLCanvas3D::_update_sla_shells_outside_state() +{ + check_volumes_outside_state(); +} + +void GLCanvas3D::_set_warning_notification_if_needed(EWarning warning) +{ + _set_current(); + bool show = false; + if (!m_volumes.empty()) + show = _is_any_volume_outside(); + else { + if (wxGetApp().is_editor()) { + if (current_printer_technology() != ptSLA) + show = m_gcode_viewer.has_data() && !m_gcode_viewer.is_contained_in_bed(); + } + } + + _set_warning_notification(warning, show); +} + +void GLCanvas3D::_set_warning_notification(EWarning warning, bool state) +{ + enum ErrorType{ + PLATER_WARNING, + PLATER_ERROR, + SLICING_ERROR + }; + std::string text; + ErrorType error = ErrorType::PLATER_WARNING; + switch (warning) { + case EWarning::ObjectOutside: text = _u8L("An object outside the print area was detected."); break; + case EWarning::ToolpathOutside: text = _u8L("A toolpath outside the print area was detected."); error = ErrorType::SLICING_ERROR; break; + case EWarning::SlaSupportsOutside: text = _u8L("SLA supports outside the print area were detected."); error = ErrorType::PLATER_ERROR; break; + case EWarning::SomethingNotShown: text = _u8L("Some objects are not visible during editing."); break; + case EWarning::ObjectClashed: + text = _u8L("An object outside the print area was detected.\n" + "Resolve the current problem to continue slicing."); + error = ErrorType::PLATER_ERROR; + break; + } + auto& notification_manager = *wxGetApp().plater()->get_notification_manager(); + switch (error) + { + case PLATER_WARNING: + if (state) + notification_manager.push_plater_warning_notification(text); + else + notification_manager.close_plater_warning_notification(text); + break; + case PLATER_ERROR: + if (state) + notification_manager.push_plater_error_notification(text); + else + notification_manager.close_plater_error_notification(text); + break; + case SLICING_ERROR: + if (state) + notification_manager.push_slicing_error_notification(text); + else + notification_manager.close_slicing_error_notification(text); + break; + default: + break; + } +} + +bool GLCanvas3D::_is_any_volume_outside() const +{ + for (const GLVolume* volume : m_volumes.volumes) { + if (volume != nullptr && volume->is_outside) + return true; + } + + return false; +} + +void GLCanvas3D::_update_selection_from_hover() +{ + bool ctrl_pressed = wxGetKeyState(WXK_CONTROL); + + if (m_hover_volume_idxs.empty()) { + if (!ctrl_pressed && m_rectangle_selection.get_state() == GLSelectionRectangle::EState::Select) + m_selection.remove_all(); + + return; + } + + GLSelectionRectangle::EState state = m_rectangle_selection.get_state(); + + bool hover_modifiers_only = true; + for (int i : m_hover_volume_idxs) { + if (!m_volumes.volumes[i]->is_modifier) { + hover_modifiers_only = false; + break; + } + } + + bool selection_changed = false; +#if ENABLE_NEW_RECTANGLE_SELECTION + if (!m_rectangle_selection.is_empty()) { +#endif // ENABLE_NEW_RECTANGLE_SELECTION + if (state == GLSelectionRectangle::EState::Select) { + bool contains_all = true; + for (int i : m_hover_volume_idxs) { + if (!m_selection.contains_volume((unsigned int)i)) { + contains_all = false; + break; + } + } + + // the selection is going to be modified (Add) + if (!contains_all) { + wxGetApp().plater()->take_snapshot(_L("Selection-Add from rectangle"), UndoRedo::SnapshotType::Selection); + selection_changed = true; + } + } + else { + bool contains_any = false; + for (int i : m_hover_volume_idxs) { + if (m_selection.contains_volume((unsigned int)i)) { + contains_any = true; + break; + } + } + + // the selection is going to be modified (Remove) + if (contains_any) { + wxGetApp().plater()->take_snapshot(_L("Selection-Remove from rectangle"), UndoRedo::SnapshotType::Selection); + selection_changed = true; + } + } +#if ENABLE_NEW_RECTANGLE_SELECTION + } +#endif // ENABLE_NEW_RECTANGLE_SELECTION + + if (!selection_changed) + return; + + Plater::SuppressSnapshots suppress(wxGetApp().plater()); + + if (state == GLSelectionRectangle::EState::Select && !ctrl_pressed) + m_selection.clear(); + + for (int i : m_hover_volume_idxs) { + if (state == GLSelectionRectangle::EState::Select) { + if (hover_modifiers_only) { + const GLVolume& v = *m_volumes.volumes[i]; + m_selection.add_volume(v.object_idx(), v.volume_idx(), v.instance_idx(), false); + } + else + m_selection.add(i, false); + } + else + m_selection.remove(i); + } + + if (m_selection.is_empty()) + m_gizmos.reset_all_states(); + else + m_gizmos.refresh_on_off_state(); + + m_gizmos.update_data(); + post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); + m_dirty = true; +} + +bool GLCanvas3D::_deactivate_undo_redo_toolbar_items() +{ + if (m_undoredo_toolbar.is_item_pressed("undo")) { + m_undoredo_toolbar.force_right_action(m_undoredo_toolbar.get_item_id("undo"), *this); + return true; + } + else if (m_undoredo_toolbar.is_item_pressed("redo")) { + m_undoredo_toolbar.force_right_action(m_undoredo_toolbar.get_item_id("redo"), *this); + return true; + } + + return false; +} + +bool GLCanvas3D::is_search_pressed() const +{ + return m_main_toolbar.is_item_pressed("search"); +} + +bool GLCanvas3D::_deactivate_arrange_menu() +{ + if (m_main_toolbar.is_item_pressed("arrange")) { + m_main_toolbar.force_right_action(m_main_toolbar.get_item_id("arrange"), *this); + return true; + } + + return false; +} + +bool GLCanvas3D::_deactivate_search_toolbar_item() +{ + if (is_search_pressed()) { + m_main_toolbar.force_left_action(m_main_toolbar.get_item_id("search"), *this); + return true; + } + + return false; +} + +bool GLCanvas3D::_activate_search_toolbar_item() +{ + if (!m_main_toolbar.is_item_pressed("search")) { + m_main_toolbar.force_left_action(m_main_toolbar.get_item_id("search"), *this); + return true; + } + + return false; +} + +bool GLCanvas3D::_deactivate_collapse_toolbar_items() +{ + GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); + if (collapse_toolbar.is_item_pressed("print")) { + collapse_toolbar.force_left_action(collapse_toolbar.get_item_id("print"), *this); + return true; + } + + return false; +} + +void GLCanvas3D::highlight_toolbar_item(const std::string& item_name) +{ + GLToolbarItem* item = m_main_toolbar.get_item(item_name); + if (!item) + item = m_undoredo_toolbar.get_item(item_name); + if (!item || !item->is_visible()) + return; + m_toolbar_highlighter.init(item, this); +} + +void GLCanvas3D::highlight_gizmo(const std::string& gizmo_name) +{ + GLGizmosManager::EType gizmo = m_gizmos.get_gizmo_from_name(gizmo_name); + if(gizmo == GLGizmosManager::EType::Undefined) + return; + m_gizmo_highlighter.init(&m_gizmos, gizmo, this); +} + +const Print* GLCanvas3D::fff_print() const +{ + return (m_process == nullptr) ? nullptr : m_process->fff_print(); +} + +const SLAPrint* GLCanvas3D::sla_print() const +{ + return (m_process == nullptr) ? nullptr : m_process->sla_print(); +} + +void GLCanvas3D::WipeTowerInfo::apply_wipe_tower() const +{ + DynamicPrintConfig cfg; + cfg.opt("wipe_tower_x", true)->value = m_pos(X); + cfg.opt("wipe_tower_y", true)->value = m_pos(Y); + cfg.opt("wipe_tower_rotation_angle", true)->value = (180./M_PI) * m_rotation; + wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg); +} + +void GLCanvas3D::RenderTimer::Notify() +{ + wxPostEvent((wxEvtHandler*)GetOwner(), RenderTimerEvent( EVT_GLCANVAS_RENDER_TIMER, *this)); +} + +void GLCanvas3D::ToolbarHighlighterTimer::Notify() +{ + wxPostEvent((wxEvtHandler*)GetOwner(), ToolbarHighlighterTimerEvent(EVT_GLCANVAS_TOOLBAR_HIGHLIGHTER_TIMER, *this)); +} + +void GLCanvas3D::GizmoHighlighterTimer::Notify() +{ + wxPostEvent((wxEvtHandler*)GetOwner(), GizmoHighlighterTimerEvent(EVT_GLCANVAS_GIZMO_HIGHLIGHTER_TIMER, *this)); +} + +void GLCanvas3D::ToolbarHighlighter::set_timer_owner(wxEvtHandler* owner, int timerid/* = wxID_ANY*/) +{ + m_timer.SetOwner(owner, timerid); +} + +void GLCanvas3D::ToolbarHighlighter::init(GLToolbarItem* toolbar_item, GLCanvas3D* canvas) +{ + if (m_timer.IsRunning()) + invalidate(); + if (!toolbar_item || !canvas) + return; + + m_timer.Start(300, false); + + m_toolbar_item = toolbar_item; + m_canvas = canvas; +} + +void GLCanvas3D::ToolbarHighlighter::invalidate() +{ + m_timer.Stop(); + + if (m_toolbar_item) { + m_toolbar_item->set_highlight(GLToolbarItem::EHighlightState::NotHighlighted); + } + m_toolbar_item = nullptr; + m_blink_counter = 0; + m_render_arrow = false; +} + +void GLCanvas3D::ToolbarHighlighter::blink() +{ + if (m_toolbar_item) { + char state = m_toolbar_item->get_highlight(); + if (state != (char)GLToolbarItem::EHighlightState::HighlightedShown) + m_toolbar_item->set_highlight(GLToolbarItem::EHighlightState::HighlightedShown); + else + m_toolbar_item->set_highlight(GLToolbarItem::EHighlightState::HighlightedHidden); + + m_render_arrow = !m_render_arrow; + m_canvas->set_as_dirty(); + } + else + invalidate(); + + if ((++m_blink_counter) >= 11) + invalidate(); +} + +void GLCanvas3D::GizmoHighlighter::set_timer_owner(wxEvtHandler* owner, int timerid/* = wxID_ANY*/) +{ + m_timer.SetOwner(owner, timerid); +} + +void GLCanvas3D::GizmoHighlighter::init(GLGizmosManager* manager, GLGizmosManager::EType gizmo, GLCanvas3D* canvas) +{ + if (m_timer.IsRunning()) + invalidate(); + if (!gizmo || !canvas) + return; + + m_timer.Start(300, false); + + m_gizmo_manager = manager; + m_gizmo_type = gizmo; + m_canvas = canvas; +} + +void GLCanvas3D::GizmoHighlighter::invalidate() +{ + m_timer.Stop(); + + if (m_gizmo_manager) { + m_gizmo_manager->set_highlight(GLGizmosManager::EType::Undefined, false); + } + m_gizmo_manager = nullptr; + m_gizmo_type = GLGizmosManager::EType::Undefined; + m_blink_counter = 0; + m_render_arrow = false; +} + +void GLCanvas3D::GizmoHighlighter::blink() +{ + if (m_gizmo_manager) { + if (m_blink_counter % 2 == 0) + m_gizmo_manager->set_highlight(m_gizmo_type, true); + else + m_gizmo_manager->set_highlight(m_gizmo_type, false); + + m_render_arrow = !m_render_arrow; + m_canvas->set_as_dirty(); + } + else + invalidate(); + + if ((++m_blink_counter) >= 11) + invalidate(); +} + +} // namespace GUI +} // namespace Slic3r From a2a85af4ddde1351f6a54a0f468fed13540e85f5 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 3 May 2022 13:54:23 +0200 Subject: [PATCH 10/11] Fixed out of bounds when showing color prints in gcode preview legend --- src/slic3r/GUI/GCodeViewer.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 993070b453..afcc3f9541 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -3941,9 +3941,8 @@ void GCodeViewer::render_legend(float& legend_height) PartialTimes items; std::vector custom_gcode_per_print_z = wxGetApp().is_editor() ? wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes : m_custom_gcode_per_print_z; - int extruders_count = wxGetApp().extruders_edited_cnt(); - std::vector last_color(extruders_count); - for (int i = 0; i < extruders_count; ++i) { + std::vector last_color(m_extruders_count); + for (int i = 0; i < m_extruders_count; ++i) { last_color[i] = m_tool_colors[i]; } int last_extruder_id = 1; From ff58e73ef2d0197cb15f7c0915ee662aecc96dc4 Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Wed, 4 May 2022 09:05:00 +0200 Subject: [PATCH 11/11] Sync with PrusaSlicer-settings --- resources/profiles/TriLAB.idx | 1 + resources/profiles/TriLAB.ini | 14 +++++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/resources/profiles/TriLAB.idx b/resources/profiles/TriLAB.idx index 1b12439f73..75819e135a 100644 --- a/resources/profiles/TriLAB.idx +++ b/resources/profiles/TriLAB.idx @@ -1,4 +1,5 @@ min_slic3r_version = 2.4.1-rc1 +1.0.1 Fix missing AzteQ Industrial ABS material for 0.6, 0.8 nozzle, enabled elefant foot compensation 1.0.0 Added AzteQ Industrial profiles for 0.8 nozzle, updated spool weight and filament cost, some minor setting improvements min_slic3r_version = 2.3.2-alpha0 0.0.9 Added AzteQ Industrial materials PC/ABS (Fillamentum), PC-Max (Polymaker), Nylon FX256 (Fillamentum), Added DeltiQ 2 materials Nylon PA12 (Fiberlogy), Nylon CF15 Carbon (Fillamentum), PEBA 90A - FlexFill (Fillamentum), MoldLay (Wax-Alike), disabled retract only when crossing perimeters, some minor setting improvements diff --git a/resources/profiles/TriLAB.ini b/resources/profiles/TriLAB.ini index a5b194eca3..b5c31d1832 100644 --- a/resources/profiles/TriLAB.ini +++ b/resources/profiles/TriLAB.ini @@ -6,7 +6,7 @@ name = TriLAB # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the PrusaSlicer configuration to be downgraded. -config_version = 1.0.0 +config_version = 1.0.1 # Where to get the updates from? config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/TriLAB/ # changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1% @@ -118,7 +118,7 @@ complete_objects = 0 default_acceleration = 2000 dont_support_bridges = 0 draft_shield = 0 -elefant_foot_compensation = 0.0 +elefant_foot_compensation = 0.1 ensure_vertical_shell_thickness = 0 external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 30 @@ -338,6 +338,7 @@ support_material_extrusion_width = 0.55 support_material_interface_spacing = 0.6 support_material_xy_spacing = 0.9 top_infill_extrusion_width = 0.6 +elefant_foot_compensation = 0.2 [print:DeltiQ 0.30mm Strong @0.6 nozzle] inherits = DeltiQ 0.30mm Normal @0.6 nozzle @@ -356,7 +357,7 @@ bottom_solid_min_thickness = 1.2 bridge_flow_ratio = 0.90 bridge_speed = 20 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_TRILAB.*/ and printer_notes=~/.*PRINTER_FAMILY_DQ.*/ and nozzle_diameter[0]==0.8 -elefant_foot_compensation = 0.0 +elefant_foot_compensation = 0.2 external_perimeter_extrusion_width = 0.80 external_perimeter_speed = 30 extrusion_width = 0.80 @@ -415,7 +416,7 @@ complete_objects = 0 default_acceleration = 2000 dont_support_bridges = 0 draft_shield = 0 -elefant_foot_compensation = 0 +elefant_foot_compensation = 0.1 ensure_vertical_shell_thickness = 0 external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 30 @@ -558,6 +559,7 @@ support_material_extrusion_width = 0.55 support_material_interface_spacing = 0.6 support_material_xy_spacing = 0.9 top_infill_extrusion_width = 0.6 +elefant_foot_compensation = 0.2 [print:AzteQ Industrial 0.30mm Strong @0.6 nozzle] inherits = AzteQ Industrial 0.30mm Normal @0.6 nozzle @@ -579,7 +581,7 @@ bottom_solid_min_thickness = 0.7 bridge_flow_ratio = 0.95 bridge_speed = 30 dont_support_bridges = 0 -elefant_foot_compensation = 0 +elefant_foot_compensation = 0.2 ensure_vertical_shell_thickness = 0 external_perimeter_extrusion_width = 0.8 external_perimeter_speed = 30 @@ -1413,9 +1415,11 @@ filament_spool_weight = 229 [filament:AzteQ Industrial - ABS - ExtraFill (Fillamentum) @0.6 nozzle] inherits = AzteQ Industrial - ABS - ExtraFill (Fillamentum) +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_TRILAB.*/ and printer_notes=~/.*PRINTER_MODEL_AQI.*/ and nozzle_diameter[0]==0.6 [filament:AzteQ Industrial - ABS - ExtraFill (Fillamentum) @0.8 nozzle] inherits = AzteQ Industrial - ABS - ExtraFill (Fillamentum) +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_TRILAB.*/ and printer_notes=~/.*PRINTER_MODEL_AQI.*/ and nozzle_diameter[0]==0.8 extrusion_multiplier = 0.95 [filament:AzteQ Industrial - PC/ABS - (Fillamentum)]