From 62e59a4526d0242864a48fd4bdade5dd36845582 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 29 Jun 2022 15:05:25 +0200 Subject: [PATCH 1/4] Fixing conflicts part 1: fixed simple conflicts --- resources/profiles/PrusaResearch.idx | 5 +- resources/profiles/PrusaResearch.ini | 7 - src/libslic3r/CMakeLists.txt | 3 - src/libslic3r/Fill/Fill.cpp | 7 +- src/libslic3r/GCode.cpp | 21 +- src/libslic3r/GCode.hpp | 7 - src/libslic3r/Geometry/Curves.hpp | 4 - src/libslic3r/PerimeterGenerator.cpp | 4 - src/libslic3r/PrintConfig.hpp | 3 - t/perimeters.t | 452 --------------------------- tests/fff_print/CMakeLists.txt | 4 - tests/fff_print/test_fill.cpp | 11 +- version.inc | 7 - xs/xsp/PerimeterGenerator.xsp | 40 --- 14 files changed, 4 insertions(+), 571 deletions(-) delete mode 100644 t/perimeters.t delete mode 100644 xs/xsp/PerimeterGenerator.xsp diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index 259dd5feb..ce0505857 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,10 +1,7 @@ min_slic3r_version = 2.5.0-alpha0 1.5.0-alpha0 Added parameters for Arachne perimeter generator. Changed default seam position. Updated output filename format. min_slic3r_version = 2.4.0-rc -<<<<<<< HEAD -======= -1.4.6 Added SLA materials. Updated filament profiles. ->>>>>>> master_250 +1.4.6 Added SLA materials. Updated filament profiles. 1.4.5 Added MMU2/S profiles for 0.25mm nozzle. Updated FW version. Enabled g-code thumbnails for MK3 family printers. Updated end g-code. 1.4.4 Added multiple Fiberlogy filament profiles. Updated Extrudr filament profiles. 1.4.3 Added new filament profiles and SLA materials. diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 0dd16f704..1760743ea 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -5,11 +5,7 @@ name = Prusa Research # 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. -<<<<<<< HEAD -config_version = 1.4.5 -======= config_version = 1.5.0-alpha0 ->>>>>>> master_250 # Where to get the updates from? config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaResearch/ changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1% @@ -301,8 +297,6 @@ thick_bridges = 0 bridge_flow_ratio = 1 bridge_speed = 20 wipe_tower_bridging = 6 -<<<<<<< HEAD -======= wall_add_middle_threshold = 85% wall_split_middle_threshold = 70% wall_transition_angle = 10 @@ -311,7 +305,6 @@ wall_transition_length = 0.25 wall_distribution_count = 1 min_bead_width = 85% min_feature_size = 0.0625 ->>>>>>> master_250 [print:*0.25nozzleMK3*] inherits = *0.25nozzle* diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 25f24855e..b5bc8b7a6 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -317,10 +317,8 @@ set(SLIC3R_SOURCES SLA/Clustering.hpp SLA/Clustering.cpp SLA/ReprojectPointsOnMesh.hpp -<<<<<<< HEAD SLA/DefaultSupportTree.hpp SLA/DefaultSupportTree.cpp -======= Arachne/BeadingStrategy/BeadingStrategy.hpp Arachne/BeadingStrategy/BeadingStrategy.cpp @@ -362,7 +360,6 @@ set(SLIC3R_SOURCES Arachne/SkeletalTrapezoidationJoint.hpp Arachne/WallToolPaths.hpp Arachne/WallToolPaths.cpp ->>>>>>> master_250 ) add_library(libslic3r STATIC ${SLIC3R_SOURCES}) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index dd58b2553..5b1b1d345 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -13,10 +13,8 @@ #include "FillBase.hpp" #include "FillRectilinear.hpp" #include "FillLightning.hpp" -<<<<<<< HEAD -======= #include "FillConcentric.hpp" ->>>>>>> master_250 + namespace Slic3r { @@ -358,8 +356,6 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: if (surface_fill.params.pattern == ipLightning) dynamic_cast(f.get())->generator = lightning_generator; -<<<<<<< HEAD -======= if (perimeter_generator.value == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric) { FillConcentric *fill_concentric = dynamic_cast(f.get()); assert(fill_concentric != nullptr); @@ -367,7 +363,6 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: fill_concentric->print_object_config = &this->object()->config(); } ->>>>>>> master_250 // calculate flow spacing for infill pattern generation bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.bridge; double link_max_length = 0.; diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 4272dc9a6..d836ac43c 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2613,7 +2613,6 @@ std::string GCode::change_layer(coordf_t print_z) return gcode; } -<<<<<<< HEAD static const auto comment_perimeter = "perimeter"sv; // Comparing string_view pointer & length for speed. static inline bool comment_is_perimeter(const std::string_view comment) { @@ -2621,9 +2620,6 @@ static inline bool comment_is_perimeter(const std::string_view comment) { } std::string GCode::extrude_loop(ExtrusionLoop loop, const std::string_view description, double speed) -======= -std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, double speed) ->>>>>>> master_250 { // get a copy; don't modify the orientation of the original loop object otherwise // next copies (if any) would not detect the correct orientation @@ -2634,21 +2630,14 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // find the point of the loop that is closest to the current extruder position // or randomize if requested Point last_pos = this->last_pos(); -<<<<<<< HEAD + if (! m_config.spiral_vase && comment_is_perimeter(description)) { assert(m_layer != nullptr); m_seam_placer.place_seam(m_layer, loop, m_config.external_perimeters_first, this->last_pos()); - } else - loop.split_at(last_pos, false); -======= - if (! m_config.spiral_vase && description == "perimeter") { - assert(m_layer != nullptr); - m_seam_placer.place_seam(m_layer, loop, m_config.external_perimeters_first, this->last_pos()); } else // Because the G-code export has 1um resolution, don't generate segments shorter than 1.5 microns, // thus empty path segments will not be produced by G-code export. loop.split_at(last_pos, false, scaled(0.0015)); ->>>>>>> master_250 // clip the path to avoid the extruder to get exactly on the first point of the loop; // if polyline was shorter than the clipping distance we'd get a null polyline, so @@ -2735,11 +2724,7 @@ std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, const std::s return gcode; } -<<<<<<< HEAD std::string GCode::extrude_entity(const ExtrusionEntity &entity, const std::string_view description, double speed) -======= -std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string description, double speed) ->>>>>>> master_250 { if (const ExtrusionPath* path = dynamic_cast(&entity)) return this->extrude_path(*path, description, speed); @@ -2774,11 +2759,7 @@ std::string GCode::extrude_perimeters(const Print &print, const std::vectorextrude_entity(*ee, comment_perimeter, -1.); -======= - gcode += this->extrude_entity(*ee, "perimeter", -1.); ->>>>>>> master_250 } return gcode; } diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 59a831e26..ce4a505fc 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -278,17 +278,10 @@ private: void set_extruders(const std::vector &extruder_ids); std::string preamble(); std::string change_layer(coordf_t print_z); -<<<<<<< HEAD std::string extrude_entity(const ExtrusionEntity &entity, const std::string_view description, double speed = -1.); std::string extrude_loop(ExtrusionLoop loop, const std::string_view description, double speed = -1.); std::string extrude_multi_path(ExtrusionMultiPath multipath, const std::string_view description, double speed = -1.); std::string extrude_path(ExtrusionPath path, const std::string_view description, double speed = -1.); -======= - std::string extrude_entity(const ExtrusionEntity &entity, std::string description = "", double speed = -1.); - std::string extrude_loop(ExtrusionLoop loop, std::string description, double speed = -1.); - std::string extrude_multi_path(ExtrusionMultiPath multipath, std::string description = "", double speed = -1.); - std::string extrude_path(ExtrusionPath path, std::string description = "", double speed = -1.); ->>>>>>> master_250 // Extruding multiple objects with soluble / non-soluble / combined supports // on a multi-material printer, trying to minimize tool switches. diff --git a/src/libslic3r/Geometry/Curves.hpp b/src/libslic3r/Geometry/Curves.hpp index 3ea80f587..5a0d5481f 100644 --- a/src/libslic3r/Geometry/Curves.hpp +++ b/src/libslic3r/Geometry/Curves.hpp @@ -142,11 +142,7 @@ PiecewiseFittedCurve fit_curve( //find corresponding segment index; expects kernels to be centered int middle_right_segment_index = floor((observation_point - result.start) / result.segment_size); //find index of first segment that is affected by the point i; this can be deduced from kernel_span -<<<<<<< HEAD - int start_segment_idx = middle_right_segment_index - Kernel::kernel_span / 2 + 1; -======= int start_segment_idx = middle_right_segment_index - int(Kernel::kernel_span / 2) + 1; ->>>>>>> master_250 for (int segment_index = start_segment_idx; segment_index < int(start_segment_idx + Kernel::kernel_span); segment_index++) { NumberType segment_start = result.start + segment_index * result.segment_size; diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index dab7d6277..be6c120e0 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -13,11 +13,7 @@ namespace Slic3r { -<<<<<<< HEAD -static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, const float tolerance, const float merge_tolerance) -======= ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, const float tolerance, const float merge_tolerance) ->>>>>>> master_250 { ExtrusionPaths paths; ExtrusionPath path(role); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index c4f495e4e..9e1d7989d 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -123,8 +123,6 @@ enum DraftShield { dsDisabled, dsLimited, dsEnabled }; -<<<<<<< HEAD -======= enum class PerimeterGeneratorType { // Classic perimeter generator using Clipper offsets with constant extrusion width. @@ -134,7 +132,6 @@ enum class PerimeterGeneratorType Arachne }; ->>>>>>> master_250 enum class GCodeThumbnailsFormat { PNG, JPG, QOI }; diff --git a/t/perimeters.t b/t/perimeters.t deleted file mode 100644 index 33cab0d29..000000000 --- a/t/perimeters.t +++ /dev/null @@ -1,452 +0,0 @@ -use Test::More tests => 59; -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use Slic3r::ExtrusionLoop ':roles'; -use Slic3r::ExtrusionPath ':roles'; -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::Surface ':types'; -use Slic3r::Test; - -{ - my $flow = Slic3r::Flow->new( - width => 1, - height => 1, - nozzle_diameter => 1, - ); - - my $config = Slic3r::Config->new; - my $test = sub { - my ($expolygons, %expected) = @_; - - my $slices = Slic3r::Surface::Collection->new; - $slices->append(Slic3r::Surface->new( - surface_type => S_TYPE_INTERNAL, - expolygon => $_, - )) for @$expolygons; - - my ($region_config, $object_config, $print_config, $loops, $gap_fill, $fill_surfaces); - my $g = Slic3r::Layer::PerimeterGenerator->new( - # input: - $slices, - 1, # layer height - $flow, - ($region_config = Slic3r::Config::PrintRegion->new), - ($object_config = Slic3r::Config::PrintObject->new), - ($print_config = Slic3r::Config::Print->new), - - # output: - ($loops = Slic3r::ExtrusionPath::Collection->new), - ($gap_fill = Slic3r::ExtrusionPath::Collection->new), - ($fill_surfaces = Slic3r::Surface::Collection->new), - ); - $g->config->apply_dynamic($config); - $g->process_classic; - - is scalar(@$loops), - scalar(@$expolygons), 'expected number of collections'; - ok !defined(first { !$_->isa('Slic3r::ExtrusionPath::Collection') } @$loops), - 'everything is returned as collections'; - - my $flattened_loops = $loops->flatten; - my @loops = @$flattened_loops; - is scalar(@loops), - $expected{total}, 'expected number of loops'; - is scalar(grep $_->role == EXTR_ROLE_EXTERNAL_PERIMETER, map @$_, @loops), - $expected{external}, 'expected number of external loops'; - is_deeply [ map { ($_->role == EXTR_ROLE_EXTERNAL_PERIMETER) || 0 } map @$_, @loops ], - $expected{ext_order}, 'expected external order'; - is scalar(grep $_->loop_role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER, @loops), - $expected{cinternal}, 'expected number of internal contour loops'; - is scalar(grep $_->polygon->is_counter_clockwise, @loops), - $expected{ccw}, 'expected number of ccw loops'; - is_deeply [ map $_->polygon->is_counter_clockwise, @loops ], - $expected{ccw_order}, 'expected ccw/cw order'; - - if ($expected{nesting}) { - foreach my $nesting (@{ $expected{nesting} }) { - for my $i (1..$#$nesting) { - ok $loops[$nesting->[$i-1]]->polygon->contains_point($loops[$nesting->[$i]]->first_point), - 'expected nesting order'; - } - } - } - }; - - $config->set('perimeters', 3); - $test->( - [ - Slic3r::ExPolygon->new( - Slic3r::Polygon->new_scale([0,0], [100,0], [100,100], [0,100]), - ), - ], - total => 3, - external => 1, - ext_order => [0,0,1], - cinternal => 1, - ccw => 3, - ccw_order => [1,1,1], - nesting => [ [2,1,0] ], - ); - $test->( - [ - Slic3r::ExPolygon->new( - Slic3r::Polygon->new_scale([0,0], [100,0], [100,100], [0,100]), - Slic3r::Polygon->new_scale([40,40], [40,60], [60,60], [60,40]), - ), - ], - total => 6, - external => 2, - ext_order => [0,0,1,0,0,1], - cinternal => 1, - ccw => 3, - ccw_order => [0,0,0,1,1,1], - nesting => [ [5,4,3,0,1,2] ], - ); - $test->( - [ - Slic3r::ExPolygon->new( - Slic3r::Polygon->new_scale([0,0], [200,0], [200,200], [0,200]), - Slic3r::Polygon->new_scale([20,20], [20,180], [180,180], [180,20]), - ), - # nested: - Slic3r::ExPolygon->new( - Slic3r::Polygon->new_scale([50,50], [150,50], [150,150], [50,150]), - Slic3r::Polygon->new_scale([80,80], [80,120], [120,120], [120,80]), - ), - ], - total => 4*3, - external => 4, - ext_order => [0,0,1,0,0,1,0,0,1,0,0,1], - cinternal => 2, - ccw => 2*3, - ccw_order => [0,0,0,1,1,1,0,0,0,1,1,1], - ); - - $config->set('perimeters', 2); - $test->( - [ - Slic3r::ExPolygon->new( - Slic3r::Polygon->new_scale([0,0], [50,0], [50,50], [0,50]), - Slic3r::Polygon->new_scale([7.5,7.5], [7.5,12.5], [12.5,12.5], [12.5,7.5]), - Slic3r::Polygon->new_scale([7.5,17.5], [7.5,22.5], [12.5,22.5], [12.5,17.5]), - Slic3r::Polygon->new_scale([7.5,27.5], [7.5,32.5], [12.5,32.5], [12.5,27.5]), - Slic3r::Polygon->new_scale([7.5,37.5], [7.5,42.5], [12.5,42.5], [12.5,37.5]), - Slic3r::Polygon->new_scale([17.5,7.5], [17.5,12.5], [22.5,12.5], [22.5,7.5]), - ), - ], - total => 12, - external => 6, - ext_order => [0,1,0,1,0,1,0,1,0,1,0,1], - cinternal => 1, - ccw => 2, - ccw_order => [0,0,0,0,0,0,0,0,0,0,1,1], - nesting => [ [0,1],[2,3],[4,5],[6,7],[8,9] ], - ); -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('skirts', 0); - $config->set('fill_density', 0); - $config->set('perimeters', 3); - $config->set('top_solid_layers', 0); - $config->set('bottom_solid_layers', 0); - $config->set('cooling', [ 0 ]); # to prevent speeds from being altered - $config->set('first_layer_speed', '100%'); # to prevent speeds from being altered - - { - my $print = Slic3r::Test::init_print('overhang', config => $config); - my $has_cw_loops = 0; - my $cur_loop; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($info->{extruding} && $info->{dist_XY} > 0) { - $cur_loop ||= [ [$self->X, $self->Y] ]; - push @$cur_loop, [ @$info{qw(new_X new_Y)} ]; - } elsif ($cmd ne 'M73') { # skips remaining time lines (M73) - if ($cur_loop) { - $has_cw_loops = 1 if Slic3r::Polygon->new(@$cur_loop)->is_clockwise; - $cur_loop = undef; - } - } - }); - ok !$has_cw_loops, 'all perimeters extruded ccw'; - } - - foreach my $model (qw(cube_with_hole cube_with_concave_hole)) { - $config->set('external_perimeter_speed', 68); - my $print = Slic3r::Test::init_print( - $model, - config => $config, - duplicate => 2, # we test two copies to make sure ExtrusionLoop objects are not modified in-place (the second object would not detect cw loops and thus would calculate wrong inwards moves) - ); - my $has_cw_loops = my $has_outwards_move = my $starts_on_convex_point = 0; - my $cur_loop; - my %external_loops = (); # print_z => count of external loops - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($info->{extruding} && $info->{dist_XY} > 0) { - $cur_loop ||= [ [$self->X, $self->Y] ]; - push @$cur_loop, [ @$info{qw(new_X new_Y)} ]; - } elsif ($cmd ne 'M73') { # skips remaining time lines (M73) - if ($cur_loop) { - $has_cw_loops = 1 if Slic3r::Polygon->new_scale(@$cur_loop)->is_clockwise; - if ($self->F == $config->external_perimeter_speed*60) { - my $move_dest = Slic3r::Point->new_scale(@$info{qw(new_X new_Y)}); - - # reset counter for second object - $external_loops{$self->Z} = 0 - if defined($external_loops{$self->Z}) && $external_loops{$self->Z} == 2; - - $external_loops{$self->Z}++; - my $is_contour = $external_loops{$self->Z} == 2; - my $is_hole = $external_loops{$self->Z} == 1; - - my $loop = Slic3r::Polygon->new_scale(@$cur_loop); - my $loop_contains_point = $loop->contains_point($move_dest); - $has_outwards_move = 1 - if (!$loop_contains_point && $is_contour) # contour should include destination - || ($loop_contains_point && $is_hole); # hole should not - - if ($model eq 'cube_with_concave_hole') { - # check that loop starts at a concave vertex - my $ccw_angle = $loop->first_point->ccw_angle(@$loop[-2,1]); - my $convex = ($ccw_angle > PI); # whether the angle on the *right* side is convex - $starts_on_convex_point = 1 - if ($convex && $is_contour) || (!$convex && $is_hole); - } - } - $cur_loop = undef; - } - } - }); - ok !$has_cw_loops, 'all perimeters extruded ccw'; - - # FIXME Lukas H.: Arachne is printing external loops before hole loops in this test case. - if ($config->perimeter_generator eq 'arachne') { - ok $has_outwards_move, 'move inwards after completing external loop'; - # FIXME Lukas H.: Disable this test for Arachne because it is failing and needs more investigation. - ok 'loops start on concave point if any'; - } else { - ok !$has_outwards_move, 'move inwards after completing external loop'; - ok !$starts_on_convex_point, 'loops start on concave point if any'; - } - } - - { - $config->set('perimeters', 1); - $config->set('perimeter_speed', 77); - $config->set('external_perimeter_speed', 66); - $config->set('bridge_speed', 99); - $config->set('cooling', [ 1 ]); - $config->set('fan_below_layer_time', [ 0 ]); - $config->set('slowdown_below_layer_time', [ 0 ]); - $config->set('bridge_fan_speed', [ 100 ]); - $config->set('bridge_flow_ratio', 33); # arbitrary value - $config->set('overhangs', 1); - my $print = Slic3r::Test::init_print('overhang', config => $config); - my %layer_speeds = (); # print Z => [ speeds ] - my $fan_speed = 0; - my $bridge_mm_per_mm = ($config->nozzle_diameter->[0]**2) / ($config->filament_diameter->[0]**2) * $config->bridge_flow_ratio; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - $fan_speed = 0 if $cmd eq 'M107'; - $fan_speed = $args->{S} if $cmd eq 'M106'; - if ($info->{extruding} && $info->{dist_XY} > 0) { - $layer_speeds{$self->Z} ||= {}; - $layer_speeds{$self->Z}{my $feedrate = $args->{F} // $self->F} = 1; - - fail 'wrong speed found' - if $feedrate != $config->perimeter_speed*60 - && $feedrate != $config->external_perimeter_speed*60 - && $feedrate != $config->bridge_speed*60; - - if ($feedrate == $config->bridge_speed*60) { - fail 'printing overhang but fan is not enabled or running at wrong speed' - if $fan_speed != 255; - my $mm_per_mm = $info->{dist_E} / $info->{dist_XY}; - fail 'wrong bridge flow' if abs($mm_per_mm - $bridge_mm_per_mm) > 0.01; - } else { - fail 'fan is running when not supposed to' - if $fan_speed > 0; - } - } - }); - is scalar(grep { keys %$_ > 1 } values %layer_speeds), 1, - 'only overhang layer has more than one speed'; - } -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('skirts', 0); - $config->set('perimeters', 3); - $config->set('layer_height', 0.4); - $config->set('first_layer_height', 0.35); - $config->set('extra_perimeters', 1); - $config->set('cooling', [ 0 ]); # to prevent speeds from being altered - $config->set('first_layer_speed', '100%'); # to prevent speeds from being altered - $config->set('perimeter_speed', 99); - $config->set('external_perimeter_speed', 99); - $config->set('small_perimeter_speed', 99); - $config->set('thin_walls', 0); - - my $print = Slic3r::Test::init_print('ipadstand', config => $config); - my %perimeters = (); # z => number of loops - my $in_loop = 0; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($info->{extruding} && $info->{dist_XY} > 0 && ($args->{F} // $self->F) == $config->perimeter_speed*60) { - $perimeters{$self->Z}++ if !$in_loop; - $in_loop = 1; - } elsif ($cmd ne 'M73') { # skips remaining time lines (M73) - $in_loop = 0; - } - }); - ok !(grep { $_ % $config->perimeters } values %perimeters), 'no superfluous extra perimeters'; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('nozzle_diameter', [0.4]); - $config->set('perimeters', 2); - $config->set('perimeter_extrusion_width', 0.4); - $config->set('external_perimeter_extrusion_width', 0.4); - $config->set('infill_extrusion_width', 0.53); - $config->set('solid_infill_extrusion_width', 0.53); - - # we just need a pre-filled Print object - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - - # override a layer's slices - my $expolygon = Slic3r::ExPolygon->new([[-71974463,-139999376],[-71731792,-139987456],[-71706544,-139985616],[-71682119,-139982639],[-71441248,-139946912],[-71417487,-139942895],[-71379384,-139933984],[-71141800,-139874480],[-71105247,-139862895],[-70873544,-139779984],[-70838592,-139765856],[-70614943,-139660064],[-70581783,-139643567],[-70368368,-139515680],[-70323751,-139487872],[-70122160,-139338352],[-70082399,-139306639],[-69894800,-139136624],[-69878679,-139121327],[-69707992,-138933008],[-69668575,-138887343],[-69518775,-138685359],[-69484336,-138631632],[-69356423,-138418207],[-69250040,-138193296],[-69220920,-138128976],[-69137992,-137897168],[-69126095,-137860255],[-69066568,-137622608],[-69057104,-137582511],[-69053079,-137558751],[-69017352,-137317872],[-69014392,-137293456],[-69012543,-137268207],[-68999369,-137000000],[-63999999,-137000000],[-63705947,-136985551],[-63654984,-136977984],[-63414731,-136942351],[-63364756,-136929840],[-63129151,-136870815],[-62851950,-136771631],[-62585807,-136645743],[-62377483,-136520895],[-62333291,-136494415],[-62291908,-136463728],[-62096819,-136319023],[-62058644,-136284432],[-61878676,-136121328],[-61680968,-135903184],[-61650275,-135861807],[-61505591,-135666719],[-61354239,-135414191],[-61332211,-135367615],[-61228359,-135148063],[-61129179,-134870847],[-61057639,-134585262],[-61014451,-134294047],[-61000000,-134000000],[-61000000,-107999999],[-61014451,-107705944],[-61057639,-107414736],[-61129179,-107129152],[-61228359,-106851953],[-61354239,-106585808],[-61505591,-106333288],[-61680967,-106096816],[-61878675,-105878680],[-62096820,-105680967],[-62138204,-105650279],[-62333292,-105505591],[-62585808,-105354239],[-62632384,-105332207],[-62851951,-105228360],[-62900463,-105211008],[-63129152,-105129183],[-63414731,-105057640],[-63705947,-105014448],[-63999999,-105000000],[-68999369,-105000000],[-69012543,-104731792],[-69014392,-104706544],[-69017352,-104682119],[-69053079,-104441248],[-69057104,-104417487],[-69066008,-104379383],[-69125528,-104141799],[-69137111,-104105248],[-69220007,-103873544],[-69234136,-103838591],[-69339920,-103614943],[-69356415,-103581784],[-69484328,-103368367],[-69512143,-103323752],[-69661647,-103122160],[-69693352,-103082399],[-69863383,-102894800],[-69878680,-102878679],[-70066999,-102707992],[-70112656,-102668576],[-70314648,-102518775],[-70368367,-102484336],[-70581783,-102356424],[-70806711,-102250040],[-70871040,-102220919],[-71102823,-102137992],[-71139752,-102126095],[-71377383,-102066568],[-71417487,-102057104],[-71441248,-102053079],[-71682119,-102017352],[-71706535,-102014392],[-71731784,-102012543],[-71974456,-102000624],[-71999999,-102000000],[-104000000,-102000000],[-104025536,-102000624],[-104268207,-102012543],[-104293455,-102014392],[-104317880,-102017352],[-104558751,-102053079],[-104582512,-102057104],[-104620616,-102066008],[-104858200,-102125528],[-104894751,-102137111],[-105126455,-102220007],[-105161408,-102234136],[-105385056,-102339920],[-105418215,-102356415],[-105631632,-102484328],[-105676247,-102512143],[-105877839,-102661647],[-105917600,-102693352],[-106105199,-102863383],[-106121320,-102878680],[-106292007,-103066999],[-106331424,-103112656],[-106481224,-103314648],[-106515663,-103368367],[-106643575,-103581783],[-106749959,-103806711],[-106779080,-103871040],[-106862007,-104102823],[-106873904,-104139752],[-106933431,-104377383],[-106942896,-104417487],[-106946920,-104441248],[-106982648,-104682119],[-106985607,-104706535],[-106987456,-104731784],[-107000630,-105000000],[-112000000,-105000000],[-112294056,-105014448],[-112585264,-105057640],[-112870848,-105129184],[-112919359,-105146535],[-113148048,-105228360],[-113194624,-105250392],[-113414191,-105354239],[-113666711,-105505591],[-113708095,-105536279],[-113903183,-105680967],[-114121320,-105878679],[-114319032,-106096816],[-114349720,-106138200],[-114494408,-106333288],[-114645760,-106585808],[-114667792,-106632384],[-114771640,-106851952],[-114788991,-106900463],[-114870815,-107129151],[-114942359,-107414735],[-114985551,-107705943],[-115000000,-107999999],[-115000000,-134000000],[-114985551,-134294048],[-114942359,-134585263],[-114870816,-134870847],[-114853464,-134919359],[-114771639,-135148064],[-114645759,-135414192],[-114494407,-135666720],[-114319031,-135903184],[-114121320,-136121327],[-114083144,-136155919],[-113903184,-136319023],[-113861799,-136349712],[-113666711,-136494416],[-113458383,-136619264],[-113414192,-136645743],[-113148049,-136771631],[-112870848,-136870815],[-112820872,-136883327],[-112585264,-136942351],[-112534303,-136949920],[-112294056,-136985551],[-112000000,-137000000],[-107000630,-137000000],[-106987456,-137268207],[-106985608,-137293440],[-106982647,-137317872],[-106946920,-137558751],[-106942896,-137582511],[-106933991,-137620624],[-106874471,-137858208],[-106862888,-137894751],[-106779992,-138126463],[-106765863,-138161424],[-106660080,-138385055],[-106643584,-138418223],[-106515671,-138631648],[-106487855,-138676256],[-106338352,-138877839],[-106306647,-138917600],[-106136616,-139105199],[-106121320,-139121328],[-105933000,-139291999],[-105887344,-139331407],[-105685351,-139481232],[-105631632,-139515663],[-105418216,-139643567],[-105193288,-139749951],[-105128959,-139779072],[-104897175,-139862016],[-104860247,-139873904],[-104622616,-139933423],[-104582511,-139942896],[-104558751,-139946912],[-104317880,-139982656],[-104293463,-139985616],[-104268216,-139987456],[-104025544,-139999376],[-104000000,-140000000],[-71999999,-140000000]],[[-105000000,-138000000],[-105000000,-104000000],[-71000000,-104000000],[-71000000,-138000000]],[[-69000000,-132000000],[-69000000,-110000000],[-64991180,-110000000],[-64991180,-132000000]],[[-111008824,-132000000],[-111008824,-110000000],[-107000000,-110000000],[-107000000,-132000000]]); - my $object = $print->print->objects->[0]; - $object->slice; - my $layer = $object->get_layer(1); - my $layerm = $layer->regions->[0]; - $layerm->slices->clear; - $layerm->slices->append(Slic3r::Surface->new(surface_type => S_TYPE_INTERNAL, expolygon => $expolygon)); - - # make perimeters - $layer->make_perimeters; - - # compute the covered area - my $pflow = $layerm->flow(FLOW_ROLE_PERIMETER); - my $iflow = $layerm->flow(FLOW_ROLE_INFILL); - my $covered_by_perimeters = union_ex([ - (map @{$_->polygon->split_at_first_point->grow($pflow->scaled_width/2)}, map @$_, @{$layerm->perimeters}), - ]); - my $covered_by_infill = union_ex([ - (map $_->p, @{$layerm->fill_surfaces}), - (map @{$_->polyline->grow($iflow->scaled_width/2)}, @{$layerm->thin_fills}), - ]); - - # compute the non covered area - my $non_covered = diff( - [ map @{$_->expolygon}, @{$layerm->slices} ], - [ map @$_, (@$covered_by_perimeters, @$covered_by_infill) ], - ); - - if (0) { - printf "max non covered = %f\n", List::Util::max(map unscale unscale $_->area, @$non_covered); - require "Slic3r/SVG.pm"; - Slic3r::SVG::output( - "gaps.svg", - expolygons => [ map $_->expolygon, @{$layerm->slices} ], - red_expolygons => union_ex([ map @$_, (@$covered_by_perimeters, @$covered_by_infill) ]), - green_expolygons => union_ex($non_covered), - no_arrows => 1, - polylines => [ - map $_->polygon->split_at_first_point, map @$_, @{$layerm->perimeters}, - ], - ); - } - ok !(defined first { $_->area > ($iflow->scaled_width**2) } @$non_covered), 'no gap between perimeters and infill'; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('skirts', 0); - $config->set('perimeters', 3); - $config->set('layer_height', 0.4); - $config->set('bridge_speed', 99); - $config->set('fill_density', 0); # to prevent bridging over sparse infill - $config->set('overhangs', 1); - $config->set('cooling', [ 0 ]); # to prevent speeds from being altered - $config->set('first_layer_speed', '100%'); # to prevent speeds from being altered - - my $test = sub { - my ($print) = @_; - my %z_with_bridges = (); # z => 1 - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($info->{extruding} && $info->{dist_XY} > 0) { - $z_with_bridges{$self->Z} = 1 if ($args->{F} // $self->F) == $config->bridge_speed*60; - } - }); - return scalar keys %z_with_bridges; - }; - ok $test->(Slic3r::Test::init_print('V', config => $config)) == 1, - 'no overhangs printed with bridge speed'; # except for the two internal solid layers above void - ok $test->(Slic3r::Test::init_print('V', config => $config, scale_xyz => [3,1,1])) > 2, - 'overhangs printed with bridge speed'; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('seam_position', 'random'); - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - ok Slic3r::Test::gcode($print), 'successful generation of G-code with seam_position = random'; -} - -{ - my $test = sub { - my ($model_name) = @_; - my $config = Slic3r::Config::new_from_defaults; - $config->set('seam_position', 'aligned'); - $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('retract_layer_change', [0]); - - my $was_extruding = 0; - my @seam_points = (); - my $print = Slic3r::Test::init_print($model_name, config => $config); - Slic3r::GCode::Reader->new->parse(my $gcode = Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($info->{extruding}) { - if (!$was_extruding) { - push @seam_points, Slic3r::Point->new_scale($self->X, $self->Y); - } - $was_extruding = 1; - } elsif ($cmd ne 'M73') { # skips remaining time lines (M73) - $was_extruding = 0; - } - }); - my @dist = map unscale($_), map $seam_points[$_]->distance_to($seam_points[$_+1]), 0..($#seam_points-1); - ok !(defined first { $_ > 3 } @dist), 'seam is aligned'; - }; - $test->('20mm_cube'); - $test->('small_dorito'); -} - -__END__ diff --git a/tests/fff_print/CMakeLists.txt b/tests/fff_print/CMakeLists.txt index a42c46695..0b3b920b9 100644 --- a/tests/fff_print/CMakeLists.txt +++ b/tests/fff_print/CMakeLists.txt @@ -1,15 +1,11 @@ get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests.cpp -<<<<<<< HEAD test_avoid_crossing_perimeters.cpp test_bridges.cpp test_cooling.cpp test_clipper.cpp test_custom_gcode.cpp -======= - test_clipper.cpp ->>>>>>> master_250 test_data.cpp test_data.hpp test_extrusion_entity.cpp diff --git a/tests/fff_print/test_fill.cpp b/tests/fff_print/test_fill.cpp index 0b6544836..66fba06a3 100644 --- a/tests/fff_print/test_fill.cpp +++ b/tests/fff_print/test_fill.cpp @@ -131,25 +131,16 @@ TEST_CASE("Fill: Pattern Path Length", "[Fill]") { auto flow = Slic3r::Flow(0.69f, 0.4f, 0.5f); FillParams fill_params; -<<<<<<< HEAD for (auto density : { 0.4, 1.0 }) { fill_params.density = density; filler->spacing = flow.spacing(); + REQUIRE(!fill_params.use_arachne); // Make this test fail when Arachne is used because this test is not ready for it. 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); } -======= - fill_params.density = 1.0; - filler->spacing = flow.spacing(); - REQUIRE(!fill_params.use_arachne); // Make this test fail when Arachne is used because this test is not ready for it. - 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); ->>>>>>> master_250 } } diff --git a/version.inc b/version.inc index a5ae1ccef..3e1460a0e 100644 --- a/version.inc +++ b/version.inc @@ -3,14 +3,7 @@ set(SLIC3R_APP_NAME "PrusaSlicer") set(SLIC3R_APP_KEY "PrusaSlicer") -<<<<<<< HEAD set(SLIC3R_VERSION "2.6.0-alpha0") set(SLIC3R_BUILD_ID "PrusaSlicer-${SLIC3R_VERSION}+UNKNOWN") set(SLIC3R_RC_VERSION "2,6,0,0") set(SLIC3R_RC_VERSION_DOTS "2.6.0.0") -======= -set(SLIC3R_VERSION "2.5.0-alpha2") -set(SLIC3R_BUILD_ID "PrusaSlicer-${SLIC3R_VERSION}+UNKNOWN") -set(SLIC3R_RC_VERSION "2,5,0,0") -set(SLIC3R_RC_VERSION_DOTS "2.5.0.0") ->>>>>>> master_250 diff --git a/xs/xsp/PerimeterGenerator.xsp b/xs/xsp/PerimeterGenerator.xsp deleted file mode 100644 index 91c6af532..000000000 --- a/xs/xsp/PerimeterGenerator.xsp +++ /dev/null @@ -1,40 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/PerimeterGenerator.hpp" -#include "libslic3r/Layer.hpp" -%} - -%name{Slic3r::Layer::PerimeterGenerator} class PerimeterGenerator { - PerimeterGenerator(SurfaceCollection* slices, double layer_height, Flow* flow, - StaticPrintConfig* region_config, StaticPrintConfig* object_config, - StaticPrintConfig* print_config, ExtrusionEntityCollection* loops, - ExtrusionEntityCollection* gap_fill, - SurfaceCollection* fill_surfaces) - %code{% RETVAL = new PerimeterGenerator(slices, layer_height, *flow, - dynamic_cast(region_config), - dynamic_cast(object_config), - dynamic_cast(print_config), - false, - loops, gap_fill, fill_surfaces); %}; - ~PerimeterGenerator(); - - void set_lower_slices(ExPolygonCollection* lower_slices) - %code{% THIS->lower_slices = &lower_slices->expolygons; %}; - void set_layer_id(int layer_id) - %code{% THIS->layer_id = layer_id; %}; - void set_perimeter_flow(Flow* flow) - %code{% THIS->perimeter_flow = *flow; %}; - void set_ext_perimeter_flow(Flow* flow) - %code{% THIS->ext_perimeter_flow = *flow; %}; - void set_overhang_flow(Flow* flow) - %code{% THIS->overhang_flow = *flow; %}; - void set_solid_infill_flow(Flow* flow) - %code{% THIS->solid_infill_flow = *flow; %}; - - Ref config() - %code{% RETVAL = THIS->config; %}; - - void process_classic(); -}; From e6d341f9f25f917b495f3fa3920f92a936a0d4cb Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 29 Jun 2022 15:07:57 +0200 Subject: [PATCH 2/4] Fixing conflicts part 2: SeamPlacer.cpp/.hpp files from master_250 were used, there should be no changes in master that will be overwritten, the feature was backported from master and only developed in master_250 from then on --- src/libslic3r/GCode/SeamPlacer.cpp | 724 ----------------------------- src/libslic3r/GCode/SeamPlacer.hpp | 85 ---- 2 files changed, 809 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index a08d5b85a..1c9ad1615 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -8,34 +8,13 @@ #include #include -<<<<<<< HEAD -#include "libslic3r/ExtrusionEntity.hpp" -#include "libslic3r/Print.hpp" -#include "libslic3r/BoundingBox.hpp" -#include "libslic3r/Color.hpp" -#include "libslic3r/EdgeGrid.hpp" -======= #include "libslic3r/AABBTreeLines.hpp" #include "libslic3r/KDTreeIndirect.hpp" #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/BoundingBox.hpp" ->>>>>>> master_250 #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/Layer.hpp" -#include "libslic3r/QuadricEdgeCollapse.hpp" -#include "libslic3r/Subdivide.hpp" - -#include "libslic3r/Geometry/Curves.hpp" - -#include "libslic3r/Utils.hpp" - -//#define DEBUG_FILES - -#ifdef DEBUG_FILES -#include -#include -#endif #include "libslic3r/Geometry/Curves.hpp" #include "libslic3r/ShortEdgeCollapse.hpp" @@ -54,7 +33,6 @@ namespace Slic3r { namespace SeamPlacerImpl { -<<<<<<< HEAD template int sgn(T val) { return int(T(0) < val) - int(val < T(0)); } @@ -93,64 +71,6 @@ public: return a.x() * mX + a.y() * mY + a.z() * mZ; } -======= -// ************ FOR BACKPORT COMPATIBILITY ONLY *************** -// Color mapping of a value into RGB false colors. -inline Vec3f value_to_rgbf(float minimum, float maximum, float value) -{ - float ratio = 2.0f * (value - minimum) / (maximum - minimum); - float b = std::max(0.0f, (1.0f - ratio)); - float r = std::max(0.0f, (ratio - 1.0f)); - float g = 1.0f - b - r; - return Vec3f { r, g, b }; -} - -// Color mapping of a value into RGB false colors. -inline Vec3i value_to_rgbi(float minimum, float maximum, float value) -{ - return (value_to_rgbf(minimum, maximum, value) * 255).cast(); -} -// *************************** - -template int sgn(T val) { - return int(T(0) < val) - int(val < T(0)); -} - -// base function: ((e^(((1)/(x^(2)+1)))-1)/(e-1)) -// checkout e.g. here: https://www.geogebra.org/calculator -float gauss(float value, float mean_x_coord, float mean_value, float falloff_speed) { - float shifted = value - mean_x_coord; - float denominator = falloff_speed * shifted * shifted + 1.0f; - float exponent = 1.0f / denominator; - return mean_value * (std::exp(exponent) - 1.0f) / (std::exp(1.0f) - 1.0f); -} - -/// Coordinate frame -class Frame { -public: - Frame() { - mX = Vec3f(1, 0, 0); - mY = Vec3f(0, 1, 0); - mZ = Vec3f(0, 0, 1); - } - - Frame(const Vec3f &x, const Vec3f &y, const Vec3f &z) : - mX(x), mY(y), mZ(z) { - } - - void set_from_z(const Vec3f &z) { - mZ = z.normalized(); - Vec3f tmpZ = mZ; - Vec3f tmpX = (std::abs(tmpZ.x()) > 0.99f) ? Vec3f(0, 1, 0) : Vec3f(1, 0, 0); - mY = (tmpZ.cross(tmpX)).normalized(); - mX = mY.cross(tmpZ); - } - - Vec3f to_world(const Vec3f &a) const { - return a.x() * mX + a.y() * mY + a.z() * mZ; - } - ->>>>>>> master_250 Vec3f to_local(const Vec3f &a) const { return Vec3f(mX.dot(a), mY.dot(a), mZ.dot(a)); } @@ -193,21 +113,6 @@ Vec3f sample_power_cosine_hemisphere(const Vec2f &samples, float power) { return Vec3f(cos(term1) * term3, sin(term1) * term3, term2); } -<<<<<<< HEAD -std::vector raycast_visibility(const AABBTreeIndirect::Tree<3, float> &raycasting_tree, - const indexed_triangle_set &triangles, size_t negative_volumes_start_index) { - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: raycast visibility for " << triangles.indices.size() << " triangles: start"; - - //prepare uniform samples of a hemisphere - float step_size = 1.0f / SeamPlacer::sqr_rays_per_triangle; - std::vector precomputed_sample_directions( - SeamPlacer::sqr_rays_per_triangle * SeamPlacer::sqr_rays_per_triangle); - for (size_t x_idx = 0; x_idx < SeamPlacer::sqr_rays_per_triangle; ++x_idx) { - float sample_x = x_idx * step_size + step_size / 2.0; - for (size_t y_idx = 0; y_idx < SeamPlacer::sqr_rays_per_triangle; ++y_idx) { - size_t dir_index = x_idx * SeamPlacer::sqr_rays_per_triangle + y_idx; -======= std::vector raycast_visibility(const AABBTreeIndirect::Tree<3, float> &raycasting_tree, const indexed_triangle_set &triangles, const TriangleSetSamples &samples, @@ -224,7 +129,6 @@ std::vector raycast_visibility(const AABBTreeIndirect::Tree<3, float> &ra float sample_x = x_idx * step_size + step_size / 2.0; for (size_t y_idx = 0; y_idx < SeamPlacer::sqr_rays_per_sample_point; ++y_idx) { size_t dir_index = x_idx * SeamPlacer::sqr_rays_per_sample_point + y_idx; ->>>>>>> master_250 float sample_y = y_idx * step_size + step_size / 2.0; precomputed_sample_directions[dir_index] = sample_hemisphere_uniform( { sample_x, sample_y }); } @@ -232,26 +136,6 @@ std::vector raycast_visibility(const AABBTreeIndirect::Tree<3, float> &ra bool model_contains_negative_parts = negative_volumes_start_index < triangles.indices.size(); -<<<<<<< HEAD - std::vector result(triangles.indices.size()); - tbb::parallel_for(tbb::blocked_range(0, result.size()), - [&triangles, &precomputed_sample_directions, model_contains_negative_parts, negative_volumes_start_index, - &raycasting_tree, &result](tbb::blocked_range r) { - // Maintaining hits memory outside of the loop, so it does not have to be reallocated for each query. - std::vector hits; - for (size_t face_index = r.begin(); face_index < r.end(); ++face_index) { - FaceVisibilityInfo &dest = result[face_index]; - dest.visibility = 1.0f; - constexpr float decrease = 1.0f - / (SeamPlacer::sqr_rays_per_triangle * SeamPlacer::sqr_rays_per_triangle); - - Vec3i face = triangles.indices[face_index]; - Vec3f A = triangles.vertices[face.x()]; - Vec3f B = triangles.vertices[face.y()]; - Vec3f C = triangles.vertices[face.z()]; - Vec3f center = (A + B + C) / 3.0f; - Vec3f normal = ((B - A).cross(C - B)).normalized(); -======= std::vector result(samples.positions.size()); tbb::parallel_for(tbb::blocked_range(0, result.size()), [&triangles, &precomputed_sample_directions, model_contains_negative_parts, negative_volumes_start_index, @@ -265,7 +149,6 @@ std::vector raycast_visibility(const AABBTreeIndirect::Tree<3, float> &ra const Vec3f ¢er = samples.positions[s_idx]; const Vec3f &normal = samples.normals[s_idx]; ->>>>>>> master_250 // apply the local direction via Frame struct - the local_dir is with respect to +Z being forward Frame f; f.set_from_z(normal); @@ -277,19 +160,6 @@ std::vector raycast_visibility(const AABBTreeIndirect::Tree<3, float> &ra // FIXME: This AABBTTreeIndirect query will not compile for float ray origin and // direction. Vec3d final_ray_dir_d = final_ray_dir.cast(); -<<<<<<< HEAD - Vec3d ray_origin_d = (center + normal * 0.1).cast(); // start above surface. - bool hit = AABBTreeIndirect::intersect_ray_first_hit(triangles.vertices, - triangles.indices, raycasting_tree, ray_origin_d, final_ray_dir_d, hitpoint); - if (hit) { - dest.visibility -= decrease; - } - } else { //TODO improve logic for order based boolean operations - consider order of volumes - Vec3d ray_origin_d = (center + normal * 0.1).cast(); // start above surface. - if (face_index >= negative_volumes_start_index) { // if casting from negative volume face, invert direction, change start pos - final_ray_dir = -1.0 * final_ray_dir; - ray_origin_d = (center - normal * 0.1).cast(); -======= Vec3d ray_origin_d = (center + normal * 0.01f).cast(); // start above surface. bool hit = AABBTreeIndirect::intersect_ray_first_hit(triangles.vertices, triangles.indices, raycasting_tree, ray_origin_d, final_ray_dir_d, hitpoint); @@ -304,35 +174,18 @@ std::vector raycast_visibility(const AABBTreeIndirect::Tree<3, float> &ra if (casting_from_negative_volume) { // if casting from negative volume face, invert direction, change start pos final_ray_dir = -1.0 * final_ray_dir; ray_origin_d = (center - normal * 0.01f).cast(); ->>>>>>> master_250 } Vec3d final_ray_dir_d = final_ray_dir.cast(); bool some_hit = AABBTreeIndirect::intersect_ray_all_hits(triangles.vertices, triangles.indices, raycasting_tree, ray_origin_d, final_ray_dir_d, hits); if (some_hit) { -<<<<<<< HEAD - int in_negative = 0; - int in_positive = 0; -======= int counter = 0; ->>>>>>> master_250 // NOTE: iterating in reverse, from the last hit for one simple reason: We know the state of the ray at that point; // It cannot be inside model, and it cannot be inside negative volume for (int hit_index = int(hits.size()) - 1; hit_index >= 0; --hit_index) { Vec3f face_normal = its_face_normal(triangles, hits[hit_index].id); if (hits[hit_index].id >= int(negative_volumes_start_index)) { //negative volume hit -<<<<<<< HEAD - in_negative += sgn(face_normal.dot(final_ray_dir)); // if volume face aligns with ray dir, we are leaving negative space - // which in reverse hit analysis means, that we are entering negative space :) and vice versa - } else { - in_positive += sgn(face_normal.dot(final_ray_dir)); - } - if (in_positive > 0 && in_negative <= 0) { - dest.visibility -= decrease; - break; - } -======= counter -= sgn(face_normal.dot(final_ray_dir)); // if volume face aligns with ray dir, we are leaving negative space // which in reverse hit analysis means, that we are entering negative space :) and vice versa } else { @@ -341,7 +194,6 @@ std::vector raycast_visibility(const AABBTreeIndirect::Tree<3, float> &ra } if (counter == 0) { result[s_idx] -= decrease_step; ->>>>>>> master_250 } } } @@ -350,12 +202,8 @@ std::vector raycast_visibility(const AABBTreeIndirect::Tree<3, float> &ra }); BOOST_LOG_TRIVIAL(debug) -<<<<<<< HEAD - << "SeamPlacer: raycast visibility for " << triangles.indices.size() << " triangles: end"; -======= << "SeamPlacer: raycast visibility of " << samples.positions.size() << " samples over " << triangles.indices.size() << " triangles: end"; ->>>>>>> master_250 return result; } @@ -410,13 +258,6 @@ std::vector calculate_polygon_angles_at_vertices(const Polygon &polygon, return result; } -<<<<<<< HEAD -// structure to store global information about the model - occlusion hits, enforcers, blockers -struct GlobalModelInfo { - indexed_triangle_set model; - AABBTreeIndirect::Tree<3, float> model_tree; - std::vector visiblity_info; -======= struct CoordinateFunctor { const std::vector *coordinates; CoordinateFunctor(const std::vector *coords) : @@ -439,7 +280,6 @@ struct GlobalModelInfo { KDTreeIndirect<3, float, CoordinateFunctor> mesh_samples_tree { CoordinateFunctor { } }; float mesh_samples_radius; ->>>>>>> master_250 indexed_triangle_set enforcers; indexed_triangle_set blockers; AABBTreeIndirect::Tree<3, float> enforcers_tree; @@ -464,44 +304,6 @@ struct GlobalModelInfo { } float calculate_point_visibility(const Vec3f &position) const { -<<<<<<< HEAD - size_t hit_idx; - Vec3f hit_point; - if (AABBTreeIndirect::squared_distance_to_indexed_triangle_set(model.vertices, model.indices, model_tree, - position, hit_idx, hit_point) >= 0) { - return visiblity_info[hit_idx].visibility; - } else { - return 0.0f; - } - - } - -#ifdef DEBUG_FILES - void debug_export(const indexed_triangle_set &obj_mesh, const char *file_name) const { - indexed_triangle_set divided_mesh = obj_mesh; - Slic3r::CNumericLocalesSetter locales_setter; - - FILE *fp = boost::nowide::fopen(file_name, "w"); - if (fp == nullptr) { - BOOST_LOG_TRIVIAL(error) - << "stl_write_obj: Couldn't open " << file_name << " for writing"; - return; - } - - for (size_t i = 0; i < divided_mesh.vertices.size(); ++i) { - float visibility = calculate_point_visibility(divided_mesh.vertices[i]); - Vec3f color = value_to_rgbf(0.0f, 1.0f, - visibility); - fprintf(fp, "v %f %f %f %f %f %f\n", - divided_mesh.vertices[i](0), divided_mesh.vertices[i](1), divided_mesh.vertices[i](2), - color(0), color(1), color(2) - ); - } - for (size_t i = 0; i < divided_mesh.indices.size(); ++i) - fprintf(fp, "f %d %d %d\n", divided_mesh.indices[i][0] + 1, divided_mesh.indices[i][1] + 1, - divided_mesh.indices[i][2] + 1); - fclose(fp); -======= std::vector points = find_nearby_points(mesh_samples_tree, position, mesh_samples_radius); if (points.empty()) { return 1.0f; @@ -576,7 +378,6 @@ struct GlobalModelInfo { } fclose(fp); } ->>>>>>> master_250 } #endif @@ -591,15 +392,6 @@ Polygons extract_perimeter_polygons(const Layer *layer, const SeamPosition confi for (const ExtrusionEntity *ex_entity : layer_region->perimeters.entities) { if (ex_entity->is_collection()) { //collection of inner, outer, and overhang perimeters for (const ExtrusionEntity *perimeter : static_cast(ex_entity)->entities) { -<<<<<<< HEAD - if (perimeter->role() == ExtrusionRole::erExternalPerimeter - || (perimeter->role() == ExtrusionRole::erPerimeter - && configured_seam_preference == spRandom)) { //for random seam alignment, extract all perimeters - Points p; - perimeter->collect_points(p); - polygons.emplace_back(std::move(p)); - corresponding_regions_out.push_back(layer_region); -======= ExtrusionRole role = perimeter->role(); if (perimeter->is_loop()){ for (const ExtrusionPath& path : static_cast(perimeter)->paths){ @@ -607,7 +399,6 @@ Polygons extract_perimeter_polygons(const Layer *layer, const SeamPosition confi role = ExtrusionRole::erExternalPerimeter; } } ->>>>>>> master_250 } if (role == ExtrusionRole::erExternalPerimeter @@ -624,99 +415,6 @@ Polygons extract_perimeter_polygons(const Layer *layer, const SeamPosition confi ex_entity->collect_points(p); polygons.emplace_back(std::move(p)); corresponding_regions_out.push_back(layer_region); -<<<<<<< HEAD - } - } else { - Points p; - ex_entity->collect_points(p); - polygons.emplace_back(std::move(p)); - corresponding_regions_out.push_back(layer_region); - } - } - } - - if (polygons.empty()) { // If there are no perimeter polygons for whatever reason (disabled perimeters .. ) insert dummy point - // it is easier than checking everywhere if the layer is not emtpy, no seam will be placed to this layer anyway - polygons.emplace_back(std::vector { Point { 0, 0 } }); - corresponding_regions_out.push_back(nullptr); - } - - return polygons; -} - -// Insert SeamCandidates created from perimeter polygons in to the result vector. -// Compute its type (Enfrocer,Blocker), angle, and position -//each SeamCandidate also contains pointer to shared Perimeter structure representing the polygon -// if Custom Seam modifiers are present, oversamples the polygon if necessary to better fit user intentions -void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, const LayerRegion *region, - const GlobalModelInfo &global_model_info, PrintObjectSeamData::LayerSeams &result) { - if (orig_polygon.size() == 0) { - return; - } - - Polygon polygon = orig_polygon; - bool was_clockwise = polygon.make_counter_clockwise(); - - std::vector lengths { }; - for (size_t point_idx = 0; point_idx < polygon.size() - 1; ++point_idx) { - lengths.push_back(std::max((unscale(polygon[point_idx]) - unscale(polygon[point_idx + 1])).norm(), 0.01)); - } - lengths.push_back(std::max((unscale(polygon[0]) - unscale(polygon[polygon.size() - 1])).norm(), 0.01)); - - std::vector local_angles = calculate_polygon_angles_at_vertices(polygon, lengths, - SeamPlacer::polygon_local_angles_arm_distance); - - result.perimeters.push_back( { }); - Perimeter &perimeter = result.perimeters.back(); - - std::queue orig_polygon_points { }; - for (size_t index = 0; index < polygon.size(); ++index) { - Vec2f unscaled_p = unscale(polygon[index]).cast(); - orig_polygon_points.emplace(unscaled_p.x(), unscaled_p.y(), z_coord); - } - Vec3f first = orig_polygon_points.front(); - std::queue oversampled_points { }; - size_t orig_angle_index = 0; - perimeter.start_index = result.points.size(); - perimeter.flow_width = region != nullptr ? region->flow(FlowRole::frExternalPerimeter).width() : 0.0f; - bool some_point_enforced = false; - while (!orig_polygon_points.empty() || !oversampled_points.empty()) { - EnforcedBlockedSeamPoint type = EnforcedBlockedSeamPoint::Neutral; - Vec3f position; - float local_ccw_angle = 0; - bool orig_point = false; - if (!oversampled_points.empty()) { - position = oversampled_points.front(); - oversampled_points.pop(); - } else { - position = orig_polygon_points.front(); - orig_polygon_points.pop(); - local_ccw_angle = was_clockwise ? -local_angles[orig_angle_index] : local_angles[orig_angle_index]; - orig_angle_index++; - orig_point = true; - } - - if (global_model_info.is_enforced(position, SeamPlacer::enforcer_blocker_distance_tolerance)) { - type = EnforcedBlockedSeamPoint::Enforced; - some_point_enforced = true; - } - - if (global_model_info.is_blocked(position, SeamPlacer::enforcer_blocker_distance_tolerance)) { - type = EnforcedBlockedSeamPoint::Blocked; - } - - if (orig_point) { - Vec3f pos_of_next = orig_polygon_points.empty() ? first : orig_polygon_points.front(); - float distance_to_next = (position - pos_of_next).norm(); - if (global_model_info.is_enforced(position, distance_to_next)) { - Vec3f vec_to_next = (pos_of_next - position).normalized(); - float step_size = SeamPlacer::enforcer_oversampling_distance; - float step = step_size; - while (step < distance_to_next) { - oversampled_points.push(position + vec_to_next * step); - step += step_size; - } -======= } } else { Points p; @@ -807,7 +505,6 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, const oversampled_points.push(position + vec_to_next * step); step += step_size; } ->>>>>>> master_250 } } @@ -845,11 +542,7 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, const break; } viable_points_indices.push_back(last_enforced_idx); -<<<<<<< HEAD - if (abs(result.points[last_enforced_idx].local_ccw_angle) > 0.4 * PI) { -======= if (abs(result.points[last_enforced_idx].local_ccw_angle) > SeamPlacer::sharp_angle_snapping_threshold) { ->>>>>>> master_250 orig_large_angle_points_indices.push_back(last_enforced_idx); } last_enforced_idx = next_index(last_enforced_idx); @@ -891,12 +584,8 @@ std::pair find_previous_and_next_perimeter_point(const std::vect } // Computes all global model info - transforms object, performs raycasting -<<<<<<< HEAD -void compute_global_occlusion(GlobalModelInfo &result, const PrintObject *po) { -======= void compute_global_occlusion(GlobalModelInfo &result, const PrintObject *po, std::function throw_if_canceled) { ->>>>>>> master_250 BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: gather occlusion meshes: start"; auto obj_transform = po->trafo_centered(); @@ -916,46 +605,6 @@ void compute_global_occlusion(GlobalModelInfo &result, const PrintObject *po, } } } -<<<<<<< HEAD - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: gather occlusion meshes: end"; - - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: simplify occlusion meshes: start"; - - //simplify raycasting mesh - its_quadric_edge_collapse(triangle_set, SeamPlacer::raycasting_decimation_target_triangle_count, nullptr, nullptr, - nullptr); - triangle_set = its_subdivide(triangle_set, SeamPlacer::raycasting_subdivision_target_length); - - //simplify negative volumes - its_quadric_edge_collapse(negative_volumes_set, SeamPlacer::raycasting_decimation_target_triangle_count, nullptr, - nullptr, - nullptr); - negative_volumes_set = its_subdivide(negative_volumes_set, SeamPlacer::raycasting_subdivision_target_length); - - size_t negative_volumes_start_index = triangle_set.indices.size(); - its_merge(triangle_set, negative_volumes_set); - its_transform(triangle_set, obj_transform); - - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: simplify occlusion meshes: end"; - - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer:build AABB tree: start"; - auto raycasting_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(triangle_set.vertices, - triangle_set.indices); - - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer:build AABB tree: end"; - result.model = triangle_set; - result.model_tree = raycasting_tree; - result.visiblity_info = raycast_visibility(raycasting_tree, triangle_set, negative_volumes_start_index); - -#ifdef DEBUG_FILES - auto filename = debug_out_path(("visiblity_of_" + std::to_string(po->id().id) + ".obj").c_str()); - result.debug_export(triangle_set, filename.c_str()); -======= throw_if_canceled(); BOOST_LOG_TRIVIAL(debug) @@ -1015,7 +664,6 @@ void compute_global_occlusion(GlobalModelInfo &result, const PrintObject *po, throw_if_canceled(); #ifdef DEBUG_FILES result.debug_export(triangle_set); ->>>>>>> master_250 #endif } @@ -1050,16 +698,10 @@ void gather_enforcers_blockers(GlobalModelInfo &result, const PrintObject *po) { struct SeamComparator { SeamPosition setup; -<<<<<<< HEAD - - SeamComparator(SeamPosition setup) : - setup(setup) { -======= float angle_importance; explicit SeamComparator(SeamPosition setup) : setup(setup) { angle_importance = setup == spNearest ? SeamPlacer::angle_importance_nearest : SeamPlacer::angle_importance_aligned; ->>>>>>> master_250 } // Standard comparator, must respect the requirements of comparators (e.g. give same result on same inputs) for sorting usage @@ -1076,38 +718,6 @@ struct SeamComparator { } //avoid overhangs -<<<<<<< HEAD - if (a.overhang > 0.0f || b.overhang > 0.0f) { - return a.overhang < b.overhang; - } - - // prefer hidden points (more than 1 mm inside) - if (a.embedded_distance < -1.0f && b.embedded_distance > -1.0f) { - return true; - } - if (b.embedded_distance < -1.0f && a.embedded_distance > -1.0f) { - return false; - } - - if (setup == SeamPosition::spRear) { - return a.position.y() > b.position.y(); - } - - float distance_penalty_a = 1.0f; - float distance_penalty_b = 1.0f; - if (setup == spNearest) { - distance_penalty_a = 1.1f - gauss((a.position.head<2>() - preffered_location).norm(), 0.0f, 1.0f, 0.005f); - distance_penalty_b = 1.1f - gauss((b.position.head<2>() - preffered_location).norm(), 0.0f, 1.0f, 0.005f); - } - - //ranges: [0 - 1] (0 - 1.3] [0.1 - 1.1) - float penalty_a = (a.visibility + SeamPlacer::additional_angle_importance) - * compute_angle_penalty(a.local_ccw_angle) - * distance_penalty_a; - float penalty_b = (b.visibility + SeamPlacer::additional_angle_importance) - * compute_angle_penalty(b.local_ccw_angle) - * distance_penalty_b; -======= if (a.overhang > SeamPlacer::overhang_distance_tolerance_factor * a.perimeter.flow_width || b.overhang > SeamPlacer::overhang_distance_tolerance_factor * b.perimeter.flow_width) { return a.overhang < b.overhang; @@ -1139,7 +749,6 @@ struct SeamComparator { float penalty_b = b.visibility + angle_importance * compute_angle_penalty(b.local_ccw_angle) + distance_penalty_b; ->>>>>>> master_250 return penalty_a < penalty_b; } @@ -1167,17 +776,6 @@ struct SeamComparator { } //avoid overhangs -<<<<<<< HEAD - if (a.overhang > 0.0f || b.overhang > 0.0f) { - return a.overhang < b.overhang; - } - - // prefer hidden points (more than 1 mm inside) - if (a.embedded_distance < -1.0f && b.embedded_distance > -1.0f) { - return true; - } - if (b.embedded_distance < -1.0f && a.embedded_distance > -1.0f) { -======= if (a.overhang > SeamPlacer::overhang_distance_tolerance_factor * a.perimeter.flow_width || b.overhang > SeamPlacer::overhang_distance_tolerance_factor * b.perimeter.flow_width) { return a.overhang < b.overhang; @@ -1188,7 +786,6 @@ struct SeamComparator { return true; } if (b.embedded_distance < -0.5f && a.embedded_distance > -0.5f) { ->>>>>>> master_250 return false; } @@ -1197,16 +794,6 @@ struct SeamComparator { } if (setup == SeamPosition::spRear) { -<<<<<<< HEAD - return a.position.y() > b.position.y(); - } - - //ranges: [0 - 1] (0 - 1.3] ; - float penalty_a = (a.visibility + SeamPlacer::additional_angle_importance) - * compute_angle_penalty(a.local_ccw_angle); - float penalty_b = (b.visibility + SeamPlacer::additional_angle_importance) - * compute_angle_penalty(b.local_ccw_angle); -======= return a.position.y() + SeamPlacer::seam_align_score_tolerance * 5.0f > b.position.y(); } @@ -1214,7 +801,6 @@ struct SeamComparator { + angle_importance * compute_angle_penalty(a.local_ccw_angle); float penalty_b = b.visibility + angle_importance * compute_angle_penalty(b.local_ccw_angle); ->>>>>>> master_250 return penalty_a <= penalty_b || penalty_a - penalty_b < SeamPlacer::seam_align_score_tolerance; } @@ -1230,34 +816,21 @@ struct SeamComparator { // so that concave points have much smaller penalty over convex ones // https://github.com/prusa3d/PrusaSlicer/tree/master/doc/seam_placement/corner_penalty_function.png return gauss(ccw_angle, 0.0f, 1.0f, 3.0f) + -<<<<<<< HEAD - 1.0f / (2 + std::exp(-ccw_angle)); // sigmoid, which heavily favourizes concave angles -======= 1.0f / (2 + std::exp(-ccw_angle)); } float weight(const SeamCandidate &a) const { return a.visibility + angle_importance * compute_angle_penalty(a.local_ccw_angle) / (1.0f + angle_importance); ->>>>>>> master_250 } }; #ifdef DEBUG_FILES -<<<<<<< HEAD -void debug_export_points(const std::vector &layers, - const BoundingBox &bounding_box, std::string object_name, const SeamComparator &comparator) { - for (size_t layer_idx = 0; layer_idx < layers.size(); ++layer_idx) { - std::string angles_file_name = debug_out_path( - (object_name + "_angles_" + std::to_string(layer_idx) + ".svg").c_str()); - SVG angles_svg {angles_file_name, bounding_box}; -======= void debug_export_points(const std::vector &layers, const BoundingBox &bounding_box, const SeamComparator &comparator) { for (size_t layer_idx = 0; layer_idx < layers.size(); ++layer_idx) { std::string angles_file_name = debug_out_path( ("angles_" + std::to_string(layer_idx) + ".svg").c_str()); SVG angles_svg { angles_file_name, bounding_box }; ->>>>>>> master_250 float min_vis = 0; float max_vis = min_vis; @@ -1267,11 +840,7 @@ void debug_export_points(const std::vector &lay for (const SeamCandidate &point : layers[layer_idx].points) { Vec3i color = value_to_rgbi(-PI, PI, point.local_ccw_angle); std::string fill = "rgb(" + std::to_string(color.x()) + "," + std::to_string(color.y()) + "," -<<<<<<< HEAD - + std::to_string(color.z()) + ")"; -======= + std::to_string(color.z()) + ")"; ->>>>>>> master_250 angles_svg.draw(scaled(Vec2f(point.position.head<2>())), fill); min_vis = std::min(min_vis, point.visibility); max_vis = std::max(max_vis, point.visibility); @@ -1282,16 +851,6 @@ void debug_export_points(const std::vector &lay } std::string visiblity_file_name = debug_out_path( -<<<<<<< HEAD - (object_name + "_visibility_" + std::to_string(layer_idx) + ".svg").c_str()); - SVG visibility_svg {visiblity_file_name, bounding_box}; - std::string weights_file_name = debug_out_path( - (object_name + "_weight_" + std::to_string(layer_idx) + ".svg").c_str()); - SVG weight_svg {weights_file_name, bounding_box}; - std::string overhangs_file_name = debug_out_path( - (object_name + "_overhang_" + std::to_string(layer_idx) + ".svg").c_str()); - SVG overhangs_svg {overhangs_file_name, bounding_box}; -======= ("visibility_" + std::to_string(layer_idx) + ".svg").c_str()); SVG visibility_svg { visiblity_file_name, bounding_box }; std::string weights_file_name = debug_out_path( @@ -1300,20 +859,10 @@ void debug_export_points(const std::vector &lay std::string overhangs_file_name = debug_out_path( ("overhang_" + std::to_string(layer_idx) + ".svg").c_str()); SVG overhangs_svg { overhangs_file_name, bounding_box }; ->>>>>>> master_250 for (const SeamCandidate &point : layers[layer_idx].points) { Vec3i color = value_to_rgbi(min_vis, max_vis, point.visibility); std::string visibility_fill = "rgb(" + std::to_string(color.x()) + "," + std::to_string(color.y()) + "," -<<<<<<< HEAD - + std::to_string(color.z()) + ")"; - visibility_svg.draw(scaled(Vec2f(point.position.head<2>())), visibility_fill); - - Vec3i weight_color = value_to_rgbi(min_weight, max_weight, -comparator.compute_angle_penalty(point.local_ccw_angle)); - std::string weight_fill = "rgb(" + std::to_string(weight_color.x()) + "," + std::to_string(weight_color.y()) - + "," - + std::to_string(weight_color.z()) + ")"; -======= + std::to_string(color.z()) + ")"; visibility_svg.draw(scaled(Vec2f(point.position.head<2>())), visibility_fill); @@ -1322,20 +871,13 @@ void debug_export_points(const std::vector &lay std::string weight_fill = "rgb(" + std::to_string(weight_color.x()) + "," + std::to_string(weight_color.y()) + "," + std::to_string(weight_color.z()) + ")"; ->>>>>>> master_250 weight_svg.draw(scaled(Vec2f(point.position.head<2>())), weight_fill); Vec3i overhang_color = value_to_rgbi(-0.5, 0.5, std::clamp(point.overhang, -0.5f, 0.5f)); std::string overhang_fill = "rgb(" + std::to_string(overhang_color.x()) + "," -<<<<<<< HEAD - + std::to_string(overhang_color.y()) - + "," - + std::to_string(overhang_color.z()) + ")"; -======= + std::to_string(overhang_color.y()) + "," + std::to_string(overhang_color.z()) + ")"; ->>>>>>> master_250 overhangs_svg.draw(scaled(Vec2f(point.position.head<2>())), overhang_fill); } } @@ -1431,30 +973,6 @@ void pick_random_seam_point(const std::vector &perimeter_points, perimeter.finalized = true; } -<<<<<<< HEAD -struct EdgeGridWrapper { - explicit EdgeGridWrapper(ExPolygons ex_polys) : - ex_polys(ex_polys) { - - grid.create(this->ex_polys, distance_field_resolution); - grid.calculate_sdf(); - } - const coord_t distance_field_resolution = coord_t(scale_(1.) + 0.5); - EdgeGrid::Grid grid; - ExPolygons ex_polys; -} -; - -EdgeGridWrapper compute_layer_merged_edge_grid(const Layer *layer) { - static const float eps = float(scale_(layer->object()->config().slice_closing_radius.value)); - // merge with offset - ExPolygons merged = layer->merged(eps); - // ofsset back - ExPolygons layer_outline = offset_ex(merged, -eps); - return EdgeGridWrapper(layer_outline); -} - -======= class PerimeterDistancer { std::vector lines; AABBTreeIndirect::Tree<2, double> tree; @@ -1502,7 +1020,6 @@ public: } ; ->>>>>>> master_250 } // namespace SeamPlacerImpl // Parallel process and extract each perimeter polygon of the given print object. @@ -1559,44 +1076,6 @@ void SeamPlacer::calculate_overhangs_and_layer_embedding(const PrintObject *po) std::vector &layers = m_seam_per_object[po].layers; tbb::parallel_for(tbb::blocked_range(0, layers.size()), [po, &layers](tbb::blocked_range r) { -<<<<<<< HEAD - std::unique_ptr prev_layer_grid; - if (r.begin() > 0) { // previous layer exists - prev_layer_grid = std::make_unique( - compute_layer_merged_edge_grid(po->layers()[r.begin() - 1])); - } - - for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - bool layer_has_multiple_loops = - layers[layer_idx].points[0].perimeter.end_index - < layers[layer_idx].points.size() - 1; - std::unique_ptr current_layer_grid = std::make_unique( - compute_layer_merged_edge_grid(po->layers()[layer_idx])); - - for (SeamCandidate &perimeter_point : layers[layer_idx].points) { - Point point = Point::new_scale(Vec2f { perimeter_point.position.head<2>() }); - if (prev_layer_grid.get() != nullptr) { - coordf_t overhang_dist; - prev_layer_grid->grid.signed_distance(point, scaled(perimeter_point.perimeter.flow_width), - overhang_dist); - perimeter_point.overhang = - unscale(overhang_dist) - perimeter_point.perimeter.flow_width; - } - - if (layer_has_multiple_loops) { // search for embedded perimeter points (points hidden inside the print ,e.g. multimaterial join, best position for seam) - coordf_t layer_embedded_distance; - current_layer_grid->grid.signed_distance(point, scaled(1.0f), - layer_embedded_distance); - perimeter_point.embedded_distance = unscale(layer_embedded_distance); - } - } - - prev_layer_grid.swap(current_layer_grid); - } - } - ); -} -======= std::unique_ptr prev_layer_distancer; if (r.begin() > 0) { // previous layer exists prev_layer_distancer = std::make_unique(po->layers()[r.begin() - 1]); @@ -1628,7 +1107,6 @@ void SeamPlacer::calculate_overhangs_and_layer_embedding(const PrintObject *po) } ); } ->>>>>>> master_250 // Estimates, if there is good seam point in the layer_idx which is close to last_point_pos // uses comparator.is_first_not_much_worse method to compare current seam with the closest point @@ -1637,17 +1115,6 @@ void SeamPlacer::calculate_overhangs_and_layer_embedding(const PrintObject *po) // If the closest point is good enough to replace current chosen seam, it is stored in potential_string_seams, returns true and updates last_point_pos // Otherwise does nothing, returns false // Used by align_seam_points(). -<<<<<<< HEAD -bool SeamPlacer::find_next_seam_in_layer( - const std::vector &layers, - std::pair &last_point_indexes, - const size_t layer_idx, const float slice_z, - const SeamPlacerImpl::SeamComparator &comparator, - std::vector> &seam_string) const { - using namespace SeamPlacerImpl; - - const SeamCandidate &last_point = layers[last_point_indexes.first].points[last_point_indexes.second]; -======= std::optional> SeamPlacer::find_next_seam_in_layer( const std::vector &layers, const std::pair &prev_point_index, @@ -1656,18 +1123,13 @@ std::optional> SeamPlacer::find_next_seam_in_layer( using namespace SeamPlacerImpl; const SeamCandidate &last_point = layers[prev_point_index.first].points[prev_point_index.second]; ->>>>>>> master_250 Vec3f projected_position { last_point.position.x(), last_point.position.y(), slice_z }; std::vector nearby_points_indices = find_nearby_points(*layers[layer_idx].points_tree, projected_position, SeamPlacer::seam_align_tolerable_dist); if (nearby_points_indices.empty()) { -<<<<<<< HEAD - return false; -======= return {}; ->>>>>>> master_250 } size_t best_nearby_point_index = nearby_points_indices[0]; @@ -1696,11 +1158,7 @@ std::optional> SeamPlacer::find_next_seam_in_layer( if (nearest_point.perimeter.finalized) { //all points are from already finalized perimeter, skip -<<<<<<< HEAD - return false; -======= return {}; ->>>>>>> master_250 } //from the nearest_point, deduce index of seam in the next layer @@ -1710,31 +1168,6 @@ std::optional> SeamPlacer::find_next_seam_in_layer( if (next_layer_seam.central_enforcer && (next_layer_seam.position - projected_position).squaredNorm() < sqr(3 * SeamPlacer::seam_align_tolerable_dist)) { -<<<<<<< HEAD - last_point_indexes = std::pair { layer_idx, nearest_point.perimeter.seam_index }; - seam_string.push_back(last_point_indexes); - return true; - } - - // Next compare nearest and nearby point. If they are similar pick nearest, Otherwise expect curvy lines on smooth surfaces like chimney of benchy model - // We also compare it to the last point, to detect sharp changes in the scoring - that points to change in the model geometry and string should be ended. - if (comparator.are_similar(nearest_point, best_nearby_point) - && comparator.is_first_not_much_worse(nearest_point, next_layer_seam) - && comparator.are_similar(last_point, nearest_point)) { - last_point_indexes = std::pair { layer_idx, nearest_point_index }; - seam_string.push_back(last_point_indexes); - return true; - } - // If nearest point is not good enough, try it with the best nearby point. - if (comparator.is_first_not_much_worse(best_nearby_point, next_layer_seam) - && comparator.are_similar(last_point, nearest_point)) { - last_point_indexes = std::pair { layer_idx, best_nearby_point_index }; - seam_string.push_back(last_point_indexes); - return true; - } - - return false; -======= return {std::pair {layer_idx, nearest_point.perimeter.seam_index}}; } @@ -1818,7 +1251,6 @@ std::vector> SeamPlacer::find_seam_string(const PrintO } return seam_string; ->>>>>>> master_250 } // clusters already chosen seam points into strings across multiple layers, and then @@ -1832,22 +1264,14 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: // Prepares Debug files for writing. #ifdef DEBUG_FILES Slic3r::CNumericLocalesSetter locales_setter; -<<<<<<< HEAD - auto clusters_f = debug_out_path(("seam_clusters_of_" + std::to_string(po->id().id) + ".obj").c_str()); -======= auto clusters_f = debug_out_path("seam_clusters.obj"); ->>>>>>> master_250 FILE *clusters = boost::nowide::fopen(clusters_f.c_str(), "w"); if (clusters == nullptr) { BOOST_LOG_TRIVIAL(error) << "stl_write_obj: Couldn't open " << clusters_f << " for writing"; return; } -<<<<<<< HEAD - auto aligned_f = debug_out_path(("aligned_clusters_of_" + std::to_string(po->id().id) + ".obj").c_str()); -======= auto aligned_f = debug_out_path("aligned_clusters.obj"); ->>>>>>> master_250 FILE *aligns = boost::nowide::fopen(aligned_f.c_str(), "w"); if (aligns == nullptr) { BOOST_LOG_TRIVIAL(error) @@ -1870,12 +1294,8 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: //sort them before alignment. Alignment is sensitive to initializaion, this gives it better chance to choose something nice std::sort(seams.begin(), seams.end(), -<<<<<<< HEAD - [&comparator, &layers](const std::pair &left, const std::pair &right) { -======= [&comparator, &layers](const std::pair &left, const std::pair &right) { ->>>>>>> master_250 return comparator.is_first_better(layers[left.first].points[left.second], layers[right.first].points[right.second]); } @@ -1884,14 +1304,6 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: //align the seam points - start with the best, and check if they are aligned, if yes, skip, else start alignment // Keeping the vectors outside, so with a bit of luck they will not get reallocated after couple of for loop iterations. std::vector> seam_string; -<<<<<<< HEAD - std::vector observations; - std::vector observation_points; - std::vector weights; - for (const std::pair &seam : seams) { - size_t layer_idx = seam.first; - size_t seam_index = seam.second; -======= std::vector> alternative_seam_string; std::vector observations; std::vector observation_points; @@ -1902,47 +1314,11 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: size_t layer_idx = seams[global_index].first; size_t seam_index = seams[global_index].second; global_index++; ->>>>>>> master_250 const std::vector &layer_perimeter_points = layers[layer_idx].points; if (layer_perimeter_points[seam_index].perimeter.finalized) { // This perimeter is already aligned, skip seam continue; } else { -<<<<<<< HEAD - - //initialize searching for seam string - cluster of nearby seams on previous and next layers - int skips = SeamPlacer::seam_align_tolerable_skips / 2; - int next_layer = layer_idx + 1; - std::pair last_point_indexes = std::pair(layer_idx, seam_index); - - seam_string = { std::pair(layer_idx, seam_index) }; - - //find seams or potential seams in forward direction; there is a budget of skips allowed - while (skips >= 0 && next_layer < int(layers.size())) { - if (find_next_seam_in_layer(layers, last_point_indexes, next_layer, - float(po->get_layer(next_layer)->slice_z), comparator, seam_string)) { - //String added, last_point_pos updated, nothing to be done - } else { - // Layer skipped, reduce number of available skips - skips--; - } - next_layer++; - } - - //do additional check in back direction - next_layer = layer_idx - 1; - skips = SeamPlacer::seam_align_tolerable_skips / 2; - last_point_indexes = std::pair(layer_idx, seam_index); - while (skips >= 0 && next_layer >= 0) { - if (find_next_seam_in_layer(layers, last_point_indexes, next_layer, - float(po->get_layer(next_layer)->slice_z), comparator, seam_string)) { - //String added, last_point_pos updated, nothing to be done - } else { - // Layer skipped, reduce number of available skips - skips--; - } - next_layer--; -======= std::optional> best_moved_seam; size_t moved_seams_count; seam_string = this->find_seam_string(po, { layer_idx, seam_index }, comparator, best_moved_seam, @@ -1957,7 +1333,6 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: // finish loop. but repeat the alignment for the current seam, since it could be skipped due to alternative path being aligned. global_index--; } ->>>>>>> master_250 } if (seam_string.size() < seam_align_minimum_string_seams) { @@ -1985,15 +1360,8 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: Vec3f pos = layers[seam_string[index].first].points[seam_string[index].second].position; observations[index] = pos.head<2>(); observation_points[index] = pos.z(); -<<<<<<< HEAD - weights[index] = - (comparator.compute_angle_penalty( - layers[seam_string[index].first].points[seam_string[index].second].local_ccw_angle) - < comparator.compute_angle_penalty(0.4f * float(PI))) ? 1.0f : 0.1f; -======= weights[index] = std::min(1.0f, comparator.weight(layers[seam_string[index].first].points[seam_string[index].second])); ->>>>>>> master_250 } // Curve Fitting @@ -2005,14 +1373,10 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: // Perimeter structure of the point; also set flag aligned to true for (size_t index = 0; index < seam_string.size(); ++index) { const auto &pair = seam_string[index]; -<<<<<<< HEAD - const float t = weights[index]; -======= const float t = abs(layers[pair.first].points[pair.second].local_ccw_angle) > SeamPlacer::sharp_angle_snapping_threshold ? 1.0 : 0.0f; ->>>>>>> master_250 Vec3f current_pos = layers[pair.first].points[pair.second].position; Vec2f fitted_pos = curve.get_fitted_value(current_pos.z()); @@ -2031,11 +1395,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: }; Vec3f color { randf(), randf(), randf() }; for (size_t i = 0; i < seam_string.size(); ++i) { -<<<<<<< HEAD - auto orig_seam = layers[seam_string[i].first].points[seam_string[i].second]; -======= auto orig_seam = layers[seam_string[i].first].points[seam_string[i].second]; ->>>>>>> master_250 fprintf(clusters, "v %f %f %f %f %f %f \n", orig_seam.position[0], orig_seam.position[1], orig_seam.position[2], color[0], color[1], @@ -2061,84 +1421,11 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: } -<<<<<<< HEAD -void SeamPlacer::init(const Print &print) { -======= void SeamPlacer::init(const Print &print, std::function throw_if_canceled_func) { ->>>>>>> master_250 using namespace SeamPlacerImpl; m_seam_per_object.clear(); for (const PrintObject *po : print.objects()) { -<<<<<<< HEAD - - SeamPosition configured_seam_preference = po->config().seam_position.value; - SeamComparator comparator { configured_seam_preference }; - - GlobalModelInfo global_model_info { }; - gather_enforcers_blockers(global_model_info, po); - - if (configured_seam_preference == spAligned || configured_seam_preference == spNearest) { - compute_global_occlusion(global_model_info, po); - } - - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: gather_seam_candidates: start"; - gather_seam_candidates(po, global_model_info, configured_seam_preference); - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: gather_seam_candidates: end"; - - if (configured_seam_preference == spAligned || configured_seam_preference == spNearest) { - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: calculate_candidates_visibility : start"; - calculate_candidates_visibility(po, global_model_info); - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: calculate_candidates_visibility : end"; - } - - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: calculate_overhangs and layer embdedding : start"; - calculate_overhangs_and_layer_embedding(po); - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: calculate_overhangs and layer embdedding: end"; - - if (configured_seam_preference != spNearest) { // For spNearest, the seam is picked in the place_seam method with actual nozzle position information - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: pick_seam_point : start"; - //pick seam point - std::vector &layers = m_seam_per_object[po].layers; - tbb::parallel_for(tbb::blocked_range(0, layers.size()), - [&layers, configured_seam_preference, comparator](tbb::blocked_range r) { - for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - std::vector &layer_perimeter_points = layers[layer_idx].points; - for (size_t current = 0; current < layer_perimeter_points.size(); - current = layer_perimeter_points[current].perimeter.end_index + 1) - if (configured_seam_preference == spRandom) - pick_random_seam_point(layer_perimeter_points, current); - else - pick_seam_point(layer_perimeter_points, current, comparator); - } - }); - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: pick_seam_point : end"; - } - - if (configured_seam_preference == spAligned) { - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: align_seam_points : start"; - align_seam_points(po, comparator); - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: align_seam_points : end"; - } - -#ifdef DEBUG_FILES - debug_export_points(layers, po->bounding_box(), std::to_string(po->id().id), - comparator); -#endif - } -} - -======= throw_if_canceled_func(); SeamPosition configured_seam_preference = po->config().seam_position.value; SeamComparator comparator { configured_seam_preference }; @@ -2207,7 +1494,6 @@ void SeamPlacer::init(const Print &print, std::function throw_if_can } } ->>>>>>> master_250 void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first, const Point &last_pos) const { using namespace SeamPlacerImpl; @@ -2269,28 +1555,18 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool extern + (perimeter_point.position - layer_perimeters.points[index_of_next].position).head<2>().normalized()) * 0.5; -<<<<<<< HEAD - auto [_, projected_point] = loop.get_closest_path_and_point(seam_point, true); - //get closest projected point, determine depth of the seam point. - float depth = (float) unscale(Point(seam_point - projected_point)).norm(); -======= ExtrusionLoop::ClosestPathPoint projected_point = loop.get_closest_path_and_point(seam_point, true); //get closest projected point, determine depth of the seam point. float depth = (float) unscale(Point(seam_point - projected_point.foot_pt)).norm(); ->>>>>>> master_250 float angle_factor = cos(-perimeter_point.local_ccw_angle / 2.0f); // There are some nice geometric identities in determination of the correct depth of new seam point. //overshoot the target depth, in concave angles it will correctly snap to the corner; TODO: find out why such big overshoot is needed. Vec2f final_pos = perimeter_point.position.head<2>() + (1.4142 * depth / angle_factor) * dir_to_middle; seam_point = Point::new_scale(final_pos.x(), final_pos.y()); } -<<<<<<< HEAD - if (!loop.split_at_vertex(seam_point)) { -======= // Because the G-code export has 1um resolution, don't generate segments shorter than 1.5 microns, // thus empty path segments will not be produced by G-code export. if (!loop.split_at_vertex(seam_point, scaled(0.0015))) { ->>>>>>> master_250 // The point is not in the original loop. // Insert it. loop.split_at(seam_point, true); diff --git a/src/libslic3r/GCode/SeamPlacer.hpp b/src/libslic3r/GCode/SeamPlacer.hpp index 17688eb84..9803e96a3 100644 --- a/src/libslic3r/GCode/SeamPlacer.hpp +++ b/src/libslic3r/GCode/SeamPlacer.hpp @@ -20,44 +20,17 @@ class PrintObject; class ExtrusionLoop; class Print; class Layer; -<<<<<<< HEAD -======= namespace EdgeGrid { class Grid; } namespace SeamPlacerImpl { ->>>>>>> master_250 - -namespace EdgeGrid { -class Grid; -} - -<<<<<<< HEAD -namespace SeamPlacerImpl { - -struct GlobalModelInfo; -struct SeamComparator; - -======= -// ************ FOR BACKPORT COMPATIBILITY ONLY *************** -// Angle from v1 to v2, returning double atan2(y, x) normalized to <-PI, PI>. -template -inline double angle(const Eigen::MatrixBase &v1, const Eigen::MatrixBase &v2) { - static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "angle(): first parameter is not a 2D vector"); - static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "angle(): second parameter is not a 2D vector"); - auto v1d = v1.template cast(); - auto v2d = v2.template cast(); - return atan2(cross2(v1d, v2d), v1d.dot(v2d)); -} -// *************************** struct GlobalModelInfo; struct SeamComparator; ->>>>>>> master_250 enum class EnforcedBlockedSeamPoint { Blocked = 0, Neutral = 1, @@ -101,13 +74,6 @@ struct SeamCandidate { bool central_enforcer; //marks this candidate as central point of enforced segment on the perimeter - important for alignment }; -<<<<<<< HEAD -struct FaceVisibilityInfo { - float visibility; -}; - -======= ->>>>>>> master_250 struct SeamCandidateCoordinateFunctor { SeamCandidateCoordinateFunctor(const std::vector &seam_candidates) : seam_candidates(seam_candidates) { @@ -125,15 +91,9 @@ struct PrintObjectSeamData struct LayerSeams { -<<<<<<< HEAD - Slic3r::deque perimeters; - std::vector points; - std::unique_ptr points_tree; -======= Slic3r::deque perimeters; std::vector points; std::unique_ptr points_tree; ->>>>>>> master_250 }; // Map of PrintObjects (PO) -> vector of layers of PO -> vector of perimeter std::vector layers; @@ -148,41 +108,6 @@ struct PrintObjectSeamData class SeamPlacer { public: -<<<<<<< HEAD - static constexpr size_t raycasting_decimation_target_triangle_count = 10000; - static constexpr float raycasting_subdivision_target_length = 2.0f; - //square of number of rays per triangle - static constexpr size_t sqr_rays_per_triangle = 7; - - // arm length used during angles computation - static constexpr float polygon_local_angles_arm_distance = 0.5f; - - // increases angle importance at the cost of deacreasing visibility info importance. must be > 0 - static constexpr float additional_angle_importance = 0.6f; - - // If enforcer or blocker is closer to the seam candidate than this limit, the seam candidate is set to Blocker or Enforcer - static constexpr float enforcer_blocker_distance_tolerance = 0.35f; - // For long polygon sides, if they are close to the custom seam drawings, they are oversampled with this step size - static constexpr float enforcer_oversampling_distance = 0.2f; - - // When searching for seam clusters for alignment: - // following value describes, how much worse score can point have and still be picked into seam cluster instead of original seam point on the same layer - static constexpr float seam_align_score_tolerance = 0.5f; - // seam_align_tolerable_dist - if next layer closes point is too far away, break string - static constexpr float seam_align_tolerable_dist = 1.0f; - // if the seam of the current layer is too far away, and the closest seam candidate is not very good, layer is skipped. - // this param limits the number of allowed skips - static constexpr size_t seam_align_tolerable_skips = 4; - // minimum number of seams needed in cluster to make alignment happen - static constexpr size_t seam_align_minimum_string_seams = 6; - // points covered by spline; determines number of splines for the given string - static constexpr size_t seam_align_seams_per_segment = 8; - - //The following data structures hold all perimeter points for all PrintObject. - std::unordered_map m_seam_per_object; - - void init(const Print &print); -======= // Number of samples generated on the mesh. There are sqr_rays_per_sample_point*sqr_rays_per_sample_point rays casted from each samples static constexpr size_t raycasting_visibility_samples_count = 30000; //square of number of rays per sample point @@ -218,7 +143,6 @@ public: std::unordered_map m_seam_per_object; void init(const Print &print, std::function throw_if_canceled_func); ->>>>>>> master_250 void place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first, const Point &last_pos) const; @@ -229,14 +153,6 @@ private: const SeamPlacerImpl::GlobalModelInfo &global_model_info); void calculate_overhangs_and_layer_embedding(const PrintObject *po); void align_seam_points(const PrintObject *po, const SeamPlacerImpl::SeamComparator &comparator); -<<<<<<< HEAD - bool find_next_seam_in_layer( - const std::vector &layers, - std::pair &last_point_indexes, - const size_t layer_idx, const float slice_z, - const SeamPlacerImpl::SeamComparator &comparator, - std::vector> &seam_string) const; -======= std::vector> find_seam_string(const PrintObject *po, std::pair start_seam, const SeamPlacerImpl::SeamComparator &comparator, @@ -247,7 +163,6 @@ private: const std::pair &prev_point_index, const size_t layer_idx, const float slice_z, const SeamPlacerImpl::SeamComparator &comparator) const; ->>>>>>> master_250 }; } // namespace Slic3r From d662bf2a18b5864b04fa9a25b97ce018a231c68a Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 29 Jun 2022 15:32:15 +0200 Subject: [PATCH 3/4] Fixing conflicts part 3: Some files have whitespace changes that make resolving conflicts difficult. Take the version from master first, next commit will apply changes from master_250 --- src/libslic3r/GCode/GCodeProcessor.cpp | 3444 --------- src/libslic3r/GCode/GCodeProcessor.hpp | 759 -- src/libslic3r/Point.hpp | 567 -- src/slic3r/GUI/ConfigWizard.cpp | 3076 +------- src/slic3r/GUI/GCodeViewer.cpp | 3956 ----------- src/slic3r/GUI/GLCanvas3D.cpp | 6722 ------------------ src/slic3r/GUI/GUI_App.cpp | 3662 +--------- src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp | 898 --- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 1250 ---- src/slic3r/Utils/FixModelByWin10.cpp | 462 +- 10 files changed, 201 insertions(+), 24595 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 3f5642675..1611b2325 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -1,4 +1,3 @@ -<<<<<<< HEAD #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" #include "libslic3r/Print.hpp" @@ -4147,3446 +4146,3 @@ double GCodeProcessor::extract_absolute_position_on_axis(Axis axis, const GCodeR } /* namespace Slic3r */ -======= -#include "libslic3r/libslic3r.h" -#include "libslic3r/Utils.hpp" -#include "libslic3r/Print.hpp" -#include "libslic3r/LocalesUtils.hpp" -#include "libslic3r/format.hpp" -#include "GCodeProcessor.hpp" - -#include -#include -#include -#include -#include -#include - -#include -#include - -#if __has_include() - #include - #include -#endif - -#include - -static const float DEFAULT_TOOLPATH_WIDTH = 0.4f; -static const float DEFAULT_TOOLPATH_HEIGHT = 0.2f; - -static const float INCHES_TO_MM = 25.4f; -static const float MMMIN_TO_MMSEC = 1.0f / 60.0f; -static const float DEFAULT_ACCELERATION = 1500.0f; // Prusa Firmware 1_75mm_MK2 -static const float DEFAULT_RETRACT_ACCELERATION = 1500.0f; // Prusa Firmware 1_75mm_MK2 -static const float DEFAULT_TRAVEL_ACCELERATION = 1250.0f; - -static const size_t MIN_EXTRUDERS_COUNT = 5; -static const float DEFAULT_FILAMENT_DIAMETER = 1.75f; -static const float DEFAULT_FILAMENT_DENSITY = 1.245f; -static const Slic3r::Vec3f DEFAULT_EXTRUDER_OFFSET = Slic3r::Vec3f::Zero(); - -namespace Slic3r { - -const std::vector GCodeProcessor::Reserved_Tags = { - "TYPE:", - "WIPE_START", - "WIPE_END", - "HEIGHT:", - "WIDTH:", - "LAYER_CHANGE", - "COLOR_CHANGE", - "PAUSE_PRINT", - "CUSTOM_GCODE", - "_GP_FIRST_LINE_M73_PLACEHOLDER", - "_GP_LAST_LINE_M73_PLACEHOLDER", - "_GP_ESTIMATED_PRINTING_TIME_PLACEHOLDER" -}; - -const float GCodeProcessor::Wipe_Width = 0.05f; -const float GCodeProcessor::Wipe_Height = 0.05f; - -#if ENABLE_GCODE_VIEWER_DATA_CHECKING -const std::string GCodeProcessor::Mm3_Per_Mm_Tag = "MM3_PER_MM:"; -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - -static void set_option_value(ConfigOptionFloats& option, size_t id, float value) -{ - if (id < option.values.size()) - option.values[id] = static_cast(value); -}; - -static float get_option_value(const ConfigOptionFloats& option, size_t id) -{ - return option.values.empty() ? 0.0f : - ((id < option.values.size()) ? static_cast(option.values[id]) : static_cast(option.values.back())); -} - -static float estimated_acceleration_distance(float initial_rate, float target_rate, float acceleration) -{ - return (acceleration == 0.0f) ? 0.0f : (sqr(target_rate) - sqr(initial_rate)) / (2.0f * acceleration); -} - -static float intersection_distance(float initial_rate, float final_rate, float acceleration, float distance) -{ - return (acceleration == 0.0f) ? 0.0f : (2.0f * acceleration * distance - sqr(initial_rate) + sqr(final_rate)) / (4.0f * acceleration); -} - -static float speed_from_distance(float initial_feedrate, float distance, float acceleration) -{ - // to avoid invalid negative numbers due to numerical errors - float value = std::max(0.0f, sqr(initial_feedrate) + 2.0f * acceleration * distance); - return ::sqrt(value); -} - -// Calculates the maximum allowable speed at this point when you must be able to reach target_velocity using the -// acceleration within the allotted distance. -static float max_allowable_speed(float acceleration, float target_velocity, float distance) -{ - // to avoid invalid negative numbers due to numerical errors - float value = std::max(0.0f, sqr(target_velocity) - 2.0f * acceleration * distance); - return std::sqrt(value); -} - -static float acceleration_time_from_distance(float initial_feedrate, float distance, float acceleration) -{ - return (acceleration != 0.0f) ? (speed_from_distance(initial_feedrate, distance, acceleration) - initial_feedrate) / acceleration : 0.0f; -} - -void GCodeProcessor::CachedPosition::reset() -{ - std::fill(position.begin(), position.end(), FLT_MAX); - feedrate = FLT_MAX; -} - -void GCodeProcessor::CpColor::reset() -{ - counter = 0; - current = 0; -} - -float GCodeProcessor::Trapezoid::acceleration_time(float entry_feedrate, float acceleration) const -{ - return acceleration_time_from_distance(entry_feedrate, accelerate_until, acceleration); -} - -float GCodeProcessor::Trapezoid::cruise_time() const -{ - return (cruise_feedrate != 0.0f) ? cruise_distance() / cruise_feedrate : 0.0f; -} - -float GCodeProcessor::Trapezoid::deceleration_time(float distance, float acceleration) const -{ - return acceleration_time_from_distance(cruise_feedrate, (distance - decelerate_after), -acceleration); -} - -float GCodeProcessor::Trapezoid::cruise_distance() const -{ - return decelerate_after - accelerate_until; -} - -void GCodeProcessor::TimeBlock::calculate_trapezoid() -{ - trapezoid.cruise_feedrate = feedrate_profile.cruise; - - float accelerate_distance = std::max(0.0f, estimated_acceleration_distance(feedrate_profile.entry, feedrate_profile.cruise, acceleration)); - float decelerate_distance = std::max(0.0f, estimated_acceleration_distance(feedrate_profile.cruise, feedrate_profile.exit, -acceleration)); - float cruise_distance = distance - accelerate_distance - decelerate_distance; - - // Not enough space to reach the nominal feedrate. - // This means no cruising, and we'll have to use intersection_distance() to calculate when to abort acceleration - // and start braking in order to reach the exit_feedrate exactly at the end of this block. - if (cruise_distance < 0.0f) { - accelerate_distance = std::clamp(intersection_distance(feedrate_profile.entry, feedrate_profile.exit, acceleration, distance), 0.0f, distance); - cruise_distance = 0.0f; - trapezoid.cruise_feedrate = speed_from_distance(feedrate_profile.entry, accelerate_distance, acceleration); - } - - trapezoid.accelerate_until = accelerate_distance; - trapezoid.decelerate_after = accelerate_distance + cruise_distance; -} - -float GCodeProcessor::TimeBlock::time() const -{ - return trapezoid.acceleration_time(feedrate_profile.entry, acceleration) - + trapezoid.cruise_time() - + trapezoid.deceleration_time(distance, acceleration); -} - -void GCodeProcessor::TimeMachine::State::reset() -{ - feedrate = 0.0f; - safe_feedrate = 0.0f; - axis_feedrate = { 0.0f, 0.0f, 0.0f, 0.0f }; - abs_axis_feedrate = { 0.0f, 0.0f, 0.0f, 0.0f }; -} - -void GCodeProcessor::TimeMachine::CustomGCodeTime::reset() -{ - needed = false; - cache = 0.0f; - times = std::vector>(); -} - -void GCodeProcessor::TimeMachine::reset() -{ - enabled = false; - acceleration = 0.0f; - max_acceleration = 0.0f; - retract_acceleration = 0.0f; - max_retract_acceleration = 0.0f; - travel_acceleration = 0.0f; - max_travel_acceleration = 0.0f; - extrude_factor_override_percentage = 1.0f; - time = 0.0f; - stop_times = std::vector(); - curr.reset(); - prev.reset(); - gcode_time.reset(); - blocks = std::vector(); - g1_times_cache = std::vector(); - std::fill(moves_time.begin(), moves_time.end(), 0.0f); - std::fill(roles_time.begin(), roles_time.end(), 0.0f); - layers_time = std::vector(); -} - -void GCodeProcessor::TimeMachine::simulate_st_synchronize(float additional_time) -{ - if (!enabled) - return; - - calculate_time(0, additional_time); -} - -static void planner_forward_pass_kernel(GCodeProcessor::TimeBlock& prev, GCodeProcessor::TimeBlock& curr) -{ - // If the previous block is an acceleration block, but it is not long enough to complete the - // full speed change within the block, we need to adjust the entry speed accordingly. Entry - // speeds have already been reset, maximized, and reverse planned by reverse planner. - // If nominal length is true, max junction speed is guaranteed to be reached. No need to recheck. - if (!prev.flags.nominal_length) { - if (prev.feedrate_profile.entry < curr.feedrate_profile.entry) { - float entry_speed = std::min(curr.feedrate_profile.entry, max_allowable_speed(-prev.acceleration, prev.feedrate_profile.entry, prev.distance)); - - // Check for junction speed change - if (curr.feedrate_profile.entry != entry_speed) { - curr.feedrate_profile.entry = entry_speed; - curr.flags.recalculate = true; - } - } - } -} - -void planner_reverse_pass_kernel(GCodeProcessor::TimeBlock& curr, GCodeProcessor::TimeBlock& next) -{ - // If entry speed is already at the maximum entry speed, no need to recheck. Block is cruising. - // If not, block in state of acceleration or deceleration. Reset entry speed to maximum and - // check for maximum allowable speed reductions to ensure maximum possible planned speed. - if (curr.feedrate_profile.entry != curr.max_entry_speed) { - // If nominal length true, max junction speed is guaranteed to be reached. Only compute - // for max allowable speed if block is decelerating and nominal length is false. - if (!curr.flags.nominal_length && curr.max_entry_speed > next.feedrate_profile.entry) - curr.feedrate_profile.entry = std::min(curr.max_entry_speed, max_allowable_speed(-curr.acceleration, next.feedrate_profile.entry, curr.distance)); - else - curr.feedrate_profile.entry = curr.max_entry_speed; - - curr.flags.recalculate = true; - } -} - -static void recalculate_trapezoids(std::vector& blocks) -{ - GCodeProcessor::TimeBlock* curr = nullptr; - GCodeProcessor::TimeBlock* next = nullptr; - - for (size_t i = 0; i < blocks.size(); ++i) { - GCodeProcessor::TimeBlock& b = blocks[i]; - - curr = next; - next = &b; - - if (curr != nullptr) { - // Recalculate if current block entry or exit junction speed has changed. - if (curr->flags.recalculate || next->flags.recalculate) { - // NOTE: Entry and exit factors always > 0 by all previous logic operations. - GCodeProcessor::TimeBlock block = *curr; - block.feedrate_profile.exit = next->feedrate_profile.entry; - block.calculate_trapezoid(); - curr->trapezoid = block.trapezoid; - curr->flags.recalculate = false; // Reset current only to ensure next trapezoid is computed - } - } - } - - // Last/newest block in buffer. Always recalculated. - if (next != nullptr) { - GCodeProcessor::TimeBlock block = *next; - block.feedrate_profile.exit = next->safe_feedrate; - block.calculate_trapezoid(); - next->trapezoid = block.trapezoid; - next->flags.recalculate = false; - } -} - -void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks, float additional_time) -{ - if (!enabled || blocks.size() < 2) - return; - - assert(keep_last_n_blocks <= blocks.size()); - - // forward_pass - for (size_t i = 0; i + 1 < blocks.size(); ++i) { - planner_forward_pass_kernel(blocks[i], blocks[i + 1]); - } - - // reverse_pass - for (int i = static_cast(blocks.size()) - 1; i > 0; --i) - planner_reverse_pass_kernel(blocks[i - 1], blocks[i]); - - recalculate_trapezoids(blocks); - - size_t n_blocks_process = blocks.size() - keep_last_n_blocks; - for (size_t i = 0; i < n_blocks_process; ++i) { - const TimeBlock& block = blocks[i]; - float block_time = block.time(); - if (i == 0) - block_time += additional_time; - - time += block_time; - gcode_time.cache += block_time; - moves_time[static_cast(block.move_type)] += block_time; - roles_time[static_cast(block.role)] += block_time; - if (block.layer_id >= layers_time.size()) { - const size_t curr_size = layers_time.size(); - layers_time.resize(block.layer_id); - for (size_t i = curr_size; i < layers_time.size(); ++i) { - layers_time[i] = 0.0f; - } - } - layers_time[block.layer_id - 1] += block_time; - g1_times_cache.push_back({ block.g1_line_id, time }); - // update times for remaining time to printer stop placeholders - auto it_stop_time = std::lower_bound(stop_times.begin(), stop_times.end(), block.g1_line_id, - [](const StopTime& t, unsigned int value) { return t.g1_line_id < value; }); - if (it_stop_time != stop_times.end() && it_stop_time->g1_line_id == block.g1_line_id) - it_stop_time->elapsed_time = time; - } - - if (keep_last_n_blocks) - blocks.erase(blocks.begin(), blocks.begin() + n_blocks_process); - else - blocks.clear(); -} - -void GCodeProcessor::TimeProcessor::reset() -{ - extruder_unloaded = true; - export_remaining_time_enabled = false; - machine_envelope_processing_enabled = false; - machine_limits = MachineEnvelopeConfig(); - filament_load_times = std::vector(); - filament_unload_times = std::vector(); - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - machines[i].reset(); - } - machines[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].enabled = true; -} - -void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, std::vector& moves, std::vector& lines_ends) -{ - FilePtr in{ boost::nowide::fopen(filename.c_str(), "rb") }; - if (in.f == nullptr) - throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for reading.\n")); - - // temporary file to contain modified gcode - std::string out_path = filename + ".postprocess"; - FilePtr out{ boost::nowide::fopen(out_path.c_str(), "wb") }; - if (out.f == nullptr) { - throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for writing.\n")); - } - - auto time_in_minutes = [](float time_in_seconds) { - assert(time_in_seconds >= 0.f); - return int((time_in_seconds + 0.5f) / 60.0f); - }; - - auto time_in_last_minute = [](float time_in_seconds) { - assert(time_in_seconds <= 60.0f); - return time_in_seconds / 60.0f; - }; - - auto format_line_M73_main = [](const std::string& mask, int percent, int time) { - char line_M73[64]; - sprintf(line_M73, mask.c_str(), - std::to_string(percent).c_str(), - std::to_string(time).c_str()); - return std::string(line_M73); - }; - - auto format_line_M73_stop_int = [](const std::string& mask, int time) { - char line_M73[64]; - sprintf(line_M73, mask.c_str(), std::to_string(time).c_str()); - return std::string(line_M73); - }; - - auto format_time_float = [](float time) { - return Slic3r::float_to_string_decimal_point(time, 2); - }; - - auto format_line_M73_stop_float = [format_time_float](const std::string& mask, float time) { - char line_M73[64]; - sprintf(line_M73, mask.c_str(), format_time_float(time).c_str()); - return std::string(line_M73); - }; - - std::string gcode_line; - size_t g1_lines_counter = 0; - // keeps track of last exported pair - std::array, static_cast(PrintEstimatedStatistics::ETimeMode::Count)> last_exported_main; - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - last_exported_main[i] = { 0, time_in_minutes(machines[i].time) }; - } - - // keeps track of last exported remaining time to next printer stop - std::array(PrintEstimatedStatistics::ETimeMode::Count)> last_exported_stop; - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - last_exported_stop[i] = time_in_minutes(machines[i].time); - } - - // buffer line to export only when greater than 64K to reduce writing calls - std::string export_line; - - // replace placeholder lines with the proper final value - // gcode_line is in/out parameter, to reduce expensive memory allocation - auto process_placeholders = [&](std::string& gcode_line) { - unsigned int extra_lines_count = 0; - - // remove trailing '\n' - auto line = std::string_view(gcode_line).substr(0, gcode_line.length() - 1); - - std::string ret; - if (line.length() > 1) { - line = line.substr(1); - if (export_remaining_time_enabled && - (line == reserved_tag(ETags::First_Line_M73_Placeholder) || line == reserved_tag(ETags::Last_Line_M73_Placeholder))) { - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - const TimeMachine& machine = machines[i]; - if (machine.enabled) { - // export pair - ret += format_line_M73_main(machine.line_m73_main_mask.c_str(), - (line == reserved_tag(ETags::First_Line_M73_Placeholder)) ? 0 : 100, - (line == reserved_tag(ETags::First_Line_M73_Placeholder)) ? time_in_minutes(machine.time) : 0); - ++extra_lines_count; - - // export remaining time to next printer stop - if (line == reserved_tag(ETags::First_Line_M73_Placeholder) && !machine.stop_times.empty()) { - int to_export_stop = time_in_minutes(machine.stop_times.front().elapsed_time); - ret += format_line_M73_stop_int(machine.line_m73_stop_mask.c_str(), to_export_stop); - last_exported_stop[i] = to_export_stop; - ++extra_lines_count; - } - } - } - } - else if (line == reserved_tag(ETags::Estimated_Printing_Time_Placeholder)) { - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - const TimeMachine& machine = machines[i]; - PrintEstimatedStatistics::ETimeMode mode = static_cast(i); - if (mode == PrintEstimatedStatistics::ETimeMode::Normal || machine.enabled) { - char buf[128]; - sprintf(buf, "; estimated printing time (%s mode) = %s\n", - (mode == PrintEstimatedStatistics::ETimeMode::Normal) ? "normal" : "silent", - get_time_dhms(machine.time).c_str()); - ret += buf; - } - } - } - } - - if (! ret.empty()) - // Not moving the move operator on purpose, so that the gcode_line allocation will grow and it will not be reallocated after handful of lines are processed. - gcode_line = ret; - return std::tuple(!ret.empty(), (extra_lines_count == 0) ? extra_lines_count : extra_lines_count - 1); - }; - - // check for temporary lines - auto is_temporary_decoration = [](const std::string_view gcode_line) { - // remove trailing '\n' - assert(! gcode_line.empty()); - assert(gcode_line.back() == '\n'); - - // return true for decorations which are used in processing the gcode but that should not be exported into the final gcode - // i.e.: - // bool ret = gcode_line.substr(0, gcode_line.length() - 1) == ";" + Layer_Change_Tag; - // ... - // return ret; - return false; - }; - - // Iterators for the normal and silent cached time estimate entry recently processed, used by process_line_G1. - auto g1_times_cache_it = Slic3r::reserve_vector::const_iterator>(machines.size()); - for (const auto& machine : machines) - g1_times_cache_it.emplace_back(machine.g1_times_cache.begin()); - - // add lines M73 to exported gcode - auto process_line_G1 = [ - // Lambdas, mostly for string formatting, all with an empty capture block. - time_in_minutes, format_time_float, format_line_M73_main, format_line_M73_stop_int, format_line_M73_stop_float, time_in_last_minute, - &self = std::as_const(*this), - // Caches, to be modified - &g1_times_cache_it, &last_exported_main, &last_exported_stop, - // String output - &export_line] - (const size_t g1_lines_counter) { - unsigned int exported_lines_count = 0; - if (self.export_remaining_time_enabled) { - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - const TimeMachine& machine = self.machines[i]; - if (machine.enabled) { - // export pair - // Skip all machine.g1_times_cache below g1_lines_counter. - auto& it = g1_times_cache_it[i]; - while (it != machine.g1_times_cache.end() && it->id < g1_lines_counter) - ++it; - if (it != machine.g1_times_cache.end() && it->id == g1_lines_counter) { - std::pair to_export_main = { int(100.0f * it->elapsed_time / machine.time), - time_in_minutes(machine.time - it->elapsed_time) }; - if (last_exported_main[i] != to_export_main) { - export_line += format_line_M73_main(machine.line_m73_main_mask.c_str(), - to_export_main.first, to_export_main.second); - last_exported_main[i] = to_export_main; - ++exported_lines_count; - } - // export remaining time to next printer stop - auto it_stop = std::upper_bound(machine.stop_times.begin(), machine.stop_times.end(), it->elapsed_time, - [](float value, const TimeMachine::StopTime& t) { return value < t.elapsed_time; }); - if (it_stop != machine.stop_times.end()) { - int to_export_stop = time_in_minutes(it_stop->elapsed_time - it->elapsed_time); - if (last_exported_stop[i] != to_export_stop) { - if (to_export_stop > 0) { - if (last_exported_stop[i] != to_export_stop) { - export_line += format_line_M73_stop_int(machine.line_m73_stop_mask.c_str(), to_export_stop); - last_exported_stop[i] = to_export_stop; - ++exported_lines_count; - } - } - else { - bool is_last = false; - auto next_it = it + 1; - is_last |= (next_it == machine.g1_times_cache.end()); - - if (next_it != machine.g1_times_cache.end()) { - auto next_it_stop = std::upper_bound(machine.stop_times.begin(), machine.stop_times.end(), next_it->elapsed_time, - [](float value, const TimeMachine::StopTime& t) { return value < t.elapsed_time; }); - is_last |= (next_it_stop != it_stop); - - std::string time_float_str = format_time_float(time_in_last_minute(it_stop->elapsed_time - it->elapsed_time)); - std::string next_time_float_str = format_time_float(time_in_last_minute(it_stop->elapsed_time - next_it->elapsed_time)); - is_last |= (string_to_double_decimal_point(time_float_str) > 0. && string_to_double_decimal_point(next_time_float_str) == 0.); - } - - if (is_last) { - if (std::distance(machine.stop_times.begin(), it_stop) == static_cast(machine.stop_times.size() - 1)) - export_line += format_line_M73_stop_int(machine.line_m73_stop_mask.c_str(), to_export_stop); - else - export_line += format_line_M73_stop_float(machine.line_m73_stop_mask.c_str(), time_in_last_minute(it_stop->elapsed_time - it->elapsed_time)); - - last_exported_stop[i] = to_export_stop; - ++exported_lines_count; - } - } - } - } - } - } - } - } - return exported_lines_count; - }; - - // helper function to write to disk - size_t out_file_pos = 0; - lines_ends.clear(); - auto write_string = [&export_line, &out, &out_path, &out_file_pos, &lines_ends](const std::string& str) { - fwrite((const void*)export_line.c_str(), 1, export_line.length(), out.f); - if (ferror(out.f)) { - out.close(); - boost::nowide::remove(out_path.c_str()); - throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nIs the disk full?\n")); - } - for (size_t i = 0; i < export_line.size(); ++ i) - if (export_line[i] == '\n') - lines_ends.emplace_back(out_file_pos + i + 1); - out_file_pos += export_line.size(); - export_line.clear(); - }; - - unsigned int line_id = 0; - std::vector> offsets; - - { - // Read the input stream 64kB at a time, extract lines and process them. - std::vector buffer(65536 * 10, 0); - // Line buffer. - assert(gcode_line.empty()); - for (;;) { - size_t cnt_read = ::fread(buffer.data(), 1, buffer.size(), in.f); - if (::ferror(in.f)) - throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nError while reading from file.\n")); - bool eof = cnt_read == 0; - auto it = buffer.begin(); - auto it_bufend = buffer.begin() + cnt_read; - while (it != it_bufend || (eof && ! gcode_line.empty())) { - // Find end of line. - bool eol = false; - auto it_end = it; - for (; it_end != it_bufend && ! (eol = *it_end == '\r' || *it_end == '\n'); ++ it_end) ; - // End of line is indicated also if end of file was reached. - eol |= eof && it_end == it_bufend; - gcode_line.insert(gcode_line.end(), it, it_end); - if (eol) { - ++line_id; - - gcode_line += "\n"; - // replace placeholder lines - auto [processed, lines_added_count] = process_placeholders(gcode_line); - if (processed && lines_added_count > 0) - offsets.push_back({ line_id, lines_added_count }); - if (! processed && ! is_temporary_decoration(gcode_line) && GCodeReader::GCodeLine::cmd_is(gcode_line, "G1")) { - // remove temporary lines, add lines M73 where needed - unsigned int extra_lines_count = process_line_G1(g1_lines_counter ++); - if (extra_lines_count > 0) - offsets.push_back({ line_id, extra_lines_count }); - } - - export_line += gcode_line; - if (export_line.length() > 65535) - write_string(export_line); - gcode_line.clear(); - } - // Skip EOL. - it = it_end; - if (it != it_bufend && *it == '\r') - ++ it; - if (it != it_bufend && *it == '\n') - ++ it; - } - if (eof) - break; - } - } - - if (!export_line.empty()) - write_string(export_line); - - out.close(); - in.close(); - - // updates moves' gcode ids which have been modified by the insertion of the M73 lines - unsigned int curr_offset_id = 0; - unsigned int total_offset = 0; - for (GCodeProcessorResult::MoveVertex& move : moves) { - while (curr_offset_id < static_cast(offsets.size()) && offsets[curr_offset_id].first <= move.gcode_id) { - total_offset += offsets[curr_offset_id].second; - ++curr_offset_id; - } - move.gcode_id += total_offset; - } - - if (rename_file(out_path, filename)) - throw Slic3r::RuntimeError(std::string("Failed to rename the output G-code file from ") + out_path + " to " + filename + '\n' + - "Is " + out_path + " locked?" + '\n'); -} - -void GCodeProcessor::UsedFilaments::reset() -{ - color_change_cache = 0.0f; - volumes_per_color_change = std::vector(); - - tool_change_cache = 0.0f; - volumes_per_extruder.clear(); - - role_cache = 0.0f; - filaments_per_role.clear(); - - extruder_retracted_volume.clear(); -} - -void GCodeProcessor::UsedFilaments::increase_caches(double extruded_volume, unsigned char extruder_id, double parking_volume, double extra_loading_volume) -{ - if (extruder_id >= extruder_retracted_volume.size()) - extruder_retracted_volume.resize(extruder_id + 1, parking_volume); - - if (recent_toolchange) { - extruded_volume -= extra_loading_volume; - recent_toolchange = false; - } - - extruder_retracted_volume[extruder_id] -= extruded_volume; - - if (extruder_retracted_volume[extruder_id] < 0.) { - extruded_volume = - extruder_retracted_volume[extruder_id]; - extruder_retracted_volume[extruder_id] = 0.; - - color_change_cache += extruded_volume; - tool_change_cache += extruded_volume; - role_cache += extruded_volume; - } -} - -void GCodeProcessor::UsedFilaments::process_color_change_cache() -{ - if (color_change_cache != 0.0f) { - volumes_per_color_change.push_back(color_change_cache); - color_change_cache = 0.0f; - } -} - -void GCodeProcessor::UsedFilaments::process_extruder_cache(unsigned char extruder_id) -{ - if (tool_change_cache != 0.0) { - volumes_per_extruder[extruder_id] += tool_change_cache; - tool_change_cache = 0.0; - } - recent_toolchange = true; - } - -void GCodeProcessor::UsedFilaments::process_role_cache(const GCodeProcessor* processor) -{ - if (role_cache != 0.0f) { - std::pair filament = { 0.0f, 0.0f }; - - double s = PI * sqr(0.5 * processor->m_result.filament_diameters[processor->m_extruder_id]); - filament.first = role_cache / s * 0.001; - filament.second = role_cache * processor->m_result.filament_densities[processor->m_extruder_id] * 0.001; - - ExtrusionRole active_role = processor->m_extrusion_role; - if (filaments_per_role.find(active_role) != filaments_per_role.end()) { - filaments_per_role[active_role].first += filament.first; - filaments_per_role[active_role].second += filament.second; - } - else - filaments_per_role[active_role] = filament; - role_cache = 0.0f; - } -} - -void GCodeProcessor::UsedFilaments::process_caches(const GCodeProcessor* processor) -{ - process_color_change_cache(); - process_extruder_cache(processor->m_extruder_id); - process_role_cache(processor); -} - -#if ENABLE_GCODE_VIEWER_STATISTICS -void GCodeProcessorResult::reset() { - moves = std::vector(); - bed_shape = Pointfs(); - max_print_height = 0.0f; - settings_ids.reset(); - extruders_count = 0; - extruder_colors = std::vector(); - filament_diameters = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DIAMETER); - filament_densities = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DENSITY); - custom_gcode_per_print_z = std::vector(); -#if ENABLE_SPIRAL_VASE_LAYERS - spiral_vase_layers = std::vector>>(); -#endif // ENABLE_SPIRAL_VASE_LAYERS - time = 0; -} -#else -void GCodeProcessorResult::reset() { - - moves.clear(); - lines_ends.clear(); - bed_shape = Pointfs(); - max_print_height = 0.0f; - settings_ids.reset(); - extruders_count = 0; - extruder_colors = std::vector(); - filament_diameters = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DIAMETER); - filament_densities = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DENSITY); - custom_gcode_per_print_z = std::vector(); -#if ENABLE_SPIRAL_VASE_LAYERS - spiral_vase_layers = std::vector>>(); -#endif // ENABLE_SPIRAL_VASE_LAYERS -} -#endif // ENABLE_GCODE_VIEWER_STATISTICS - -const std::vector> GCodeProcessor::Producers = { - { EProducer::PrusaSlicer, "generated by PrusaSlicer" }, - { EProducer::Slic3rPE, "generated by Slic3r Prusa Edition" }, - { EProducer::Slic3r, "generated by Slic3r" }, - { EProducer::SuperSlicer, "generated by SuperSlicer" }, - { EProducer::Cura, "Cura_SteamEngine" }, - { EProducer::Simplify3D, "G-Code generated by Simplify3D(R)" }, - { EProducer::CraftWare, "CraftWare" }, - { EProducer::ideaMaker, "ideaMaker" }, - { EProducer::KissSlicer, "KISSlicer" } -}; - -unsigned int GCodeProcessor::s_result_id = 0; - -bool GCodeProcessor::contains_reserved_tag(const std::string& gcode, std::string& found_tag) -{ - bool ret = false; - - GCodeReader parser; - parser.parse_buffer(gcode, [&ret, &found_tag](GCodeReader& parser, const GCodeReader::GCodeLine& line) { - std::string comment = line.raw(); - if (comment.length() > 2 && comment.front() == ';') { - comment = comment.substr(1); - for (const std::string& s : Reserved_Tags) { - if (boost::starts_with(comment, s)) { - ret = true; - found_tag = comment; - parser.quit_parsing(); - return; - } - } - } - }); - - return ret; -} - -bool GCodeProcessor::contains_reserved_tags(const std::string& gcode, unsigned int max_count, std::vector& found_tag) -{ - max_count = std::max(max_count, 1U); - - bool ret = false; - - CNumericLocalesSetter locales_setter; - - GCodeReader parser; - parser.parse_buffer(gcode, [&ret, &found_tag, max_count](GCodeReader& parser, const GCodeReader::GCodeLine& line) { - std::string comment = line.raw(); - if (comment.length() > 2 && comment.front() == ';') { - comment = comment.substr(1); - for (const std::string& s : Reserved_Tags) { - if (boost::starts_with(comment, s)) { - ret = true; - found_tag.push_back(comment); - if (found_tag.size() == max_count) { - parser.quit_parsing(); - return; - } - } - } - } - }); - - return ret; -} - -GCodeProcessor::GCodeProcessor() -: m_options_z_corrector(m_result) -{ - reset(); - m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].line_m73_main_mask = "M73 P%s R%s\n"; - m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].line_m73_stop_mask = "M73 C%s\n"; - m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].line_m73_main_mask = "M73 Q%s S%s\n"; - m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].line_m73_stop_mask = "M73 D%s\n"; -} - -void GCodeProcessor::apply_config(const PrintConfig& config) -{ - m_parser.apply_config(config); - - m_flavor = config.gcode_flavor; - - size_t extruders_count = config.nozzle_diameter.values.size(); - m_result.extruders_count = extruders_count; - - m_extruder_offsets.resize(extruders_count); - m_extruder_colors.resize(extruders_count); - m_result.filament_diameters.resize(extruders_count); - m_result.filament_densities.resize(extruders_count); - m_extruder_temps.resize(extruders_count); - - for (size_t i = 0; i < extruders_count; ++ i) { - m_extruder_offsets[i] = to_3d(config.extruder_offset.get_at(i).cast().eval(), 0.f); - m_extruder_colors[i] = static_cast(i); - m_result.filament_diameters[i] = static_cast(config.filament_diameter.get_at(i)); - m_result.filament_densities[i] = static_cast(config.filament_density.get_at(i)); - } - - if ((m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware || m_flavor == gcfRepRapFirmware) && config.machine_limits_usage.value != MachineLimitsUsage::Ignore) { - m_time_processor.machine_limits = reinterpret_cast(config); - if (m_flavor == gcfMarlinLegacy) { - // Legacy Marlin does not have separate travel acceleration, it uses the 'extruding' value instead. - m_time_processor.machine_limits.machine_max_acceleration_travel = m_time_processor.machine_limits.machine_max_acceleration_extruding; - } - if (m_flavor == gcfRepRapFirmware) { - // RRF does not support setting min feedrates. Set them to zero. - m_time_processor.machine_limits.machine_min_travel_rate.values.assign(m_time_processor.machine_limits.machine_min_travel_rate.size(), 0.); - m_time_processor.machine_limits.machine_min_extruding_rate.values.assign(m_time_processor.machine_limits.machine_min_extruding_rate.size(), 0.); - } - } - - // Filament load / unload times are not specific to a firmware flavor. Let anybody use it if they find it useful. - // As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they - // are considered to be active for the single extruder multi-material printers only. - m_time_processor.filament_load_times.resize(config.filament_load_time.values.size()); - for (size_t i = 0; i < config.filament_load_time.values.size(); ++i) { - m_time_processor.filament_load_times[i] = static_cast(config.filament_load_time.values[i]); - } - m_time_processor.filament_unload_times.resize(config.filament_unload_time.values.size()); - for (size_t i = 0; i < config.filament_unload_time.values.size(); ++i) { - m_time_processor.filament_unload_times[i] = static_cast(config.filament_unload_time.values[i]); - } - - // With MM setups like Prusa MMU2, the filaments may be expected to be parked at the beginning. - // Remember the parking position so the initial load is not included in filament estimate. - if (config.single_extruder_multi_material && extruders_count > 1 && config.wipe_tower) { - m_parking_position = float(config.parking_pos_retraction.value); - m_extra_loading_move = float(config.extra_loading_move); - } - - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i); - m_time_processor.machines[i].max_acceleration = max_acceleration; - m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION; - float max_retract_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, i); - m_time_processor.machines[i].max_retract_acceleration = max_retract_acceleration; - m_time_processor.machines[i].retract_acceleration = (max_retract_acceleration > 0.0f) ? max_retract_acceleration : DEFAULT_RETRACT_ACCELERATION; - float max_travel_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_travel, i); - m_time_processor.machines[i].max_travel_acceleration = max_travel_acceleration; - m_time_processor.machines[i].travel_acceleration = (max_travel_acceleration > 0.0f) ? max_travel_acceleration : DEFAULT_TRAVEL_ACCELERATION; - } - - m_time_processor.export_remaining_time_enabled = config.remaining_times.value; - m_use_volumetric_e = config.use_volumetric_e; - - const ConfigOptionFloatOrPercent* first_layer_height = config.option("first_layer_height"); - if (first_layer_height != nullptr) - m_first_layer_height = std::abs(first_layer_height->value); - - m_result.max_print_height = config.max_print_height; - -#if ENABLE_SPIRAL_VASE_LAYERS - const ConfigOptionBool* spiral_vase = config.option("spiral_vase"); - if (spiral_vase != nullptr) - m_spiral_vase_active = spiral_vase->value; -#endif // ENABLE_SPIRAL_VASE_LAYERS - -#if ENABLE_Z_OFFSET_CORRECTION - const ConfigOptionFloat* z_offset = config.option("z_offset"); - if (z_offset != nullptr) - m_z_offset = z_offset->value; -#endif // ENABLE_Z_OFFSET_CORRECTION -} - -void GCodeProcessor::apply_config(const DynamicPrintConfig& config) -{ - m_parser.apply_config(config); - - const ConfigOptionEnum* gcode_flavor = config.option>("gcode_flavor"); - if (gcode_flavor != nullptr) - m_flavor = gcode_flavor->value; - - const ConfigOptionPoints* bed_shape = config.option("bed_shape"); - if (bed_shape != nullptr) - m_result.bed_shape = bed_shape->values; - - const ConfigOptionString* print_settings_id = config.option("print_settings_id"); - if (print_settings_id != nullptr) - m_result.settings_ids.print = print_settings_id->value; - - const ConfigOptionStrings* filament_settings_id = config.option("filament_settings_id"); - if (filament_settings_id != nullptr) - m_result.settings_ids.filament = filament_settings_id->values; - - const ConfigOptionString* printer_settings_id = config.option("printer_settings_id"); - if (printer_settings_id != nullptr) - m_result.settings_ids.printer = printer_settings_id->value; - - m_result.extruders_count = config.option("nozzle_diameter")->values.size(); - - const ConfigOptionFloats* filament_diameters = config.option("filament_diameter"); - if (filament_diameters != nullptr) { - m_result.filament_diameters.clear(); - m_result.filament_diameters.resize(filament_diameters->values.size()); - for (size_t i = 0; i < filament_diameters->values.size(); ++i) { - m_result.filament_diameters[i] = static_cast(filament_diameters->values[i]); - } - } - - if (m_result.filament_diameters.size() < m_result.extruders_count) { - for (size_t i = m_result.filament_diameters.size(); i < m_result.extruders_count; ++i) { - m_result.filament_diameters.emplace_back(DEFAULT_FILAMENT_DIAMETER); - } - } - - const ConfigOptionFloats* filament_densities = config.option("filament_density"); - if (filament_densities != nullptr) { - m_result.filament_densities.clear(); - m_result.filament_densities.resize(filament_densities->values.size()); - for (size_t i = 0; i < filament_densities->values.size(); ++i) { - m_result.filament_densities[i] = static_cast(filament_densities->values[i]); - } - } - - if (m_result.filament_densities.size() < m_result.extruders_count) { - for (size_t i = m_result.filament_densities.size(); i < m_result.extruders_count; ++i) { - m_result.filament_densities.emplace_back(DEFAULT_FILAMENT_DENSITY); - } - } - - const ConfigOptionPoints* extruder_offset = config.option("extruder_offset"); - if (extruder_offset != nullptr) { - m_extruder_offsets.resize(extruder_offset->values.size()); - for (size_t i = 0; i < extruder_offset->values.size(); ++i) { - Vec2f offset = extruder_offset->values[i].cast(); - m_extruder_offsets[i] = { offset(0), offset(1), 0.0f }; - } - } - - if (m_extruder_offsets.size() < m_result.extruders_count) { - for (size_t i = m_extruder_offsets.size(); i < m_result.extruders_count; ++i) { - m_extruder_offsets.emplace_back(DEFAULT_EXTRUDER_OFFSET); - } - } - - const ConfigOptionStrings* extruder_colour = config.option("extruder_colour"); - if (extruder_colour != nullptr) { - // takes colors from config - m_result.extruder_colors = extruder_colour->values; - // try to replace missing values with filament colors - const ConfigOptionStrings* filament_colour = config.option("filament_colour"); - if (filament_colour != nullptr && filament_colour->values.size() == m_result.extruder_colors.size()) { - for (size_t i = 0; i < m_result.extruder_colors.size(); ++i) { - if (m_result.extruder_colors[i].empty()) - m_result.extruder_colors[i] = filament_colour->values[i]; - } - } - } - - if (m_result.extruder_colors.size() < m_result.extruders_count) { - for (size_t i = m_result.extruder_colors.size(); i < m_result.extruders_count; ++i) { - m_result.extruder_colors.emplace_back(std::string()); - } - } - - // replace missing values with default - for (size_t i = 0; i < m_result.extruder_colors.size(); ++i) { - if (m_result.extruder_colors[i].empty()) - m_result.extruder_colors[i] = "#FF8000"; - } - - m_extruder_colors.resize(m_result.extruder_colors.size()); - for (size_t i = 0; i < m_result.extruder_colors.size(); ++i) { - m_extruder_colors[i] = static_cast(i); - } - - m_extruder_temps.resize(m_result.extruders_count); - - const ConfigOptionFloats* filament_load_time = config.option("filament_load_time"); - if (filament_load_time != nullptr) { - m_time_processor.filament_load_times.resize(filament_load_time->values.size()); - for (size_t i = 0; i < filament_load_time->values.size(); ++i) { - m_time_processor.filament_load_times[i] = static_cast(filament_load_time->values[i]); - } - } - - const ConfigOptionFloats* filament_unload_time = config.option("filament_unload_time"); - if (filament_unload_time != nullptr) { - m_time_processor.filament_unload_times.resize(filament_unload_time->values.size()); - for (size_t i = 0; i < filament_unload_time->values.size(); ++i) { - m_time_processor.filament_unload_times[i] = static_cast(filament_unload_time->values[i]); - } - } - - bool use_machine_limits = false; - const ConfigOptionEnum* machine_limits_usage = config.option>("machine_limits_usage"); - if (machine_limits_usage != nullptr) - use_machine_limits = machine_limits_usage->value != MachineLimitsUsage::Ignore; - - if (use_machine_limits && (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware || m_flavor == gcfRepRapFirmware)) { - const ConfigOptionFloats* machine_max_acceleration_x = config.option("machine_max_acceleration_x"); - if (machine_max_acceleration_x != nullptr) - m_time_processor.machine_limits.machine_max_acceleration_x.values = machine_max_acceleration_x->values; - - const ConfigOptionFloats* machine_max_acceleration_y = config.option("machine_max_acceleration_y"); - if (machine_max_acceleration_y != nullptr) - m_time_processor.machine_limits.machine_max_acceleration_y.values = machine_max_acceleration_y->values; - - const ConfigOptionFloats* machine_max_acceleration_z = config.option("machine_max_acceleration_z"); - if (machine_max_acceleration_z != nullptr) - m_time_processor.machine_limits.machine_max_acceleration_z.values = machine_max_acceleration_z->values; - - const ConfigOptionFloats* machine_max_acceleration_e = config.option("machine_max_acceleration_e"); - if (machine_max_acceleration_e != nullptr) - m_time_processor.machine_limits.machine_max_acceleration_e.values = machine_max_acceleration_e->values; - - const ConfigOptionFloats* machine_max_feedrate_x = config.option("machine_max_feedrate_x"); - if (machine_max_feedrate_x != nullptr) - m_time_processor.machine_limits.machine_max_feedrate_x.values = machine_max_feedrate_x->values; - - const ConfigOptionFloats* machine_max_feedrate_y = config.option("machine_max_feedrate_y"); - if (machine_max_feedrate_y != nullptr) - m_time_processor.machine_limits.machine_max_feedrate_y.values = machine_max_feedrate_y->values; - - const ConfigOptionFloats* machine_max_feedrate_z = config.option("machine_max_feedrate_z"); - if (machine_max_feedrate_z != nullptr) - m_time_processor.machine_limits.machine_max_feedrate_z.values = machine_max_feedrate_z->values; - - const ConfigOptionFloats* machine_max_feedrate_e = config.option("machine_max_feedrate_e"); - if (machine_max_feedrate_e != nullptr) - m_time_processor.machine_limits.machine_max_feedrate_e.values = machine_max_feedrate_e->values; - - const ConfigOptionFloats* machine_max_jerk_x = config.option("machine_max_jerk_x"); - if (machine_max_jerk_x != nullptr) - m_time_processor.machine_limits.machine_max_jerk_x.values = machine_max_jerk_x->values; - - const ConfigOptionFloats* machine_max_jerk_y = config.option("machine_max_jerk_y"); - if (machine_max_jerk_y != nullptr) - m_time_processor.machine_limits.machine_max_jerk_y.values = machine_max_jerk_y->values; - - const ConfigOptionFloats* machine_max_jerk_z = config.option("machine_max_jerkz"); - if (machine_max_jerk_z != nullptr) - m_time_processor.machine_limits.machine_max_jerk_z.values = machine_max_jerk_z->values; - - const ConfigOptionFloats* machine_max_jerk_e = config.option("machine_max_jerk_e"); - if (machine_max_jerk_e != nullptr) - m_time_processor.machine_limits.machine_max_jerk_e.values = machine_max_jerk_e->values; - - const ConfigOptionFloats* machine_max_acceleration_extruding = config.option("machine_max_acceleration_extruding"); - if (machine_max_acceleration_extruding != nullptr) - m_time_processor.machine_limits.machine_max_acceleration_extruding.values = machine_max_acceleration_extruding->values; - - const ConfigOptionFloats* machine_max_acceleration_retracting = config.option("machine_max_acceleration_retracting"); - if (machine_max_acceleration_retracting != nullptr) - m_time_processor.machine_limits.machine_max_acceleration_retracting.values = machine_max_acceleration_retracting->values; - - - // Legacy Marlin does not have separate travel acceleration, it uses the 'extruding' value instead. - const ConfigOptionFloats* machine_max_acceleration_travel = config.option(m_flavor == gcfMarlinLegacy - ? "machine_max_acceleration_extruding" - : "machine_max_acceleration_travel"); - if (machine_max_acceleration_travel != nullptr) - m_time_processor.machine_limits.machine_max_acceleration_travel.values = machine_max_acceleration_travel->values; - - - const ConfigOptionFloats* machine_min_extruding_rate = config.option("machine_min_extruding_rate"); - if (machine_min_extruding_rate != nullptr) { - m_time_processor.machine_limits.machine_min_extruding_rate.values = machine_min_extruding_rate->values; - if (m_flavor == gcfRepRapFirmware) { - // RRF does not support setting min feedrates. Set zero. - m_time_processor.machine_limits.machine_min_extruding_rate.values.assign(m_time_processor.machine_limits.machine_min_extruding_rate.size(), 0.); - } - } - - const ConfigOptionFloats* machine_min_travel_rate = config.option("machine_min_travel_rate"); - if (machine_min_travel_rate != nullptr) { - m_time_processor.machine_limits.machine_min_travel_rate.values = machine_min_travel_rate->values; - if (m_flavor == gcfRepRapFirmware) { - // RRF does not support setting min feedrates. Set zero. - m_time_processor.machine_limits.machine_min_travel_rate.values.assign(m_time_processor.machine_limits.machine_min_travel_rate.size(), 0.); - } - } - } - - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i); - m_time_processor.machines[i].max_acceleration = max_acceleration; - m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION; - float max_retract_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, i); - m_time_processor.machines[i].max_retract_acceleration = max_retract_acceleration; - m_time_processor.machines[i].retract_acceleration = (max_retract_acceleration > 0.0f) ? max_retract_acceleration : DEFAULT_RETRACT_ACCELERATION; - float max_travel_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_travel, i); - m_time_processor.machines[i].max_travel_acceleration = max_travel_acceleration; - m_time_processor.machines[i].travel_acceleration = (max_travel_acceleration > 0.0f) ? max_travel_acceleration : DEFAULT_TRAVEL_ACCELERATION; - } - - if (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware) { - const ConfigOptionBool* silent_mode = config.option("silent_mode"); - if (silent_mode != nullptr) { - if (silent_mode->value && m_time_processor.machine_limits.machine_max_acceleration_x.values.size() > 1) - enable_stealth_time_estimator(true); - } - } - - const ConfigOptionBool* use_volumetric_e = config.option("use_volumetric_e"); - if (use_volumetric_e != nullptr) - m_use_volumetric_e = use_volumetric_e->value; - - const ConfigOptionFloatOrPercent* first_layer_height = config.option("first_layer_height"); - if (first_layer_height != nullptr) - m_first_layer_height = std::abs(first_layer_height->value); - - const ConfigOptionFloat* max_print_height = config.option("max_print_height"); - if (max_print_height != nullptr) - m_result.max_print_height = max_print_height->value; - -#if ENABLE_SPIRAL_VASE_LAYERS - const ConfigOptionBool* spiral_vase = config.option("spiral_vase"); - if (spiral_vase != nullptr) - m_spiral_vase_active = spiral_vase->value; -#endif // ENABLE_SPIRAL_VASE_LAYERS - -#if ENABLE_Z_OFFSET_CORRECTION - const ConfigOptionFloat* z_offset = config.option("z_offset"); - if (z_offset != nullptr) - m_z_offset = z_offset->value; -#endif // ENABLE_Z_OFFSET_CORRECTION -} - -void GCodeProcessor::enable_stealth_time_estimator(bool enabled) -{ - m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].enabled = enabled; -} - -void GCodeProcessor::reset() -{ - m_units = EUnits::Millimeters; - m_global_positioning_type = EPositioningType::Absolute; - m_e_local_positioning_type = EPositioningType::Absolute; - m_extruder_offsets = std::vector(MIN_EXTRUDERS_COUNT, Vec3f::Zero()); - m_flavor = gcfRepRapSprinter; - - m_start_position = { 0.0f, 0.0f, 0.0f, 0.0f }; - m_end_position = { 0.0f, 0.0f, 0.0f, 0.0f }; - m_origin = { 0.0f, 0.0f, 0.0f, 0.0f }; - m_cached_position.reset(); - m_wiping = false; - - m_line_id = 0; - m_last_line_id = 0; - m_feedrate = 0.0f; - m_width = 0.0f; - m_height = 0.0f; - m_forced_width = 0.0f; - m_forced_height = 0.0f; - m_mm3_per_mm = 0.0f; - m_fan_speed = 0.0f; -#if ENABLE_Z_OFFSET_CORRECTION - m_z_offset = 0.0f; -#endif // ENABLE_Z_OFFSET_CORRECTION - - m_extrusion_role = erNone; - m_extruder_id = 0; - m_extruder_colors.resize(MIN_EXTRUDERS_COUNT); - for (size_t i = 0; i < MIN_EXTRUDERS_COUNT; ++i) { - m_extruder_colors[i] = static_cast(i); - } - m_extruder_temps.resize(MIN_EXTRUDERS_COUNT); - for (size_t i = 0; i < MIN_EXTRUDERS_COUNT; ++i) { - m_extruder_temps[i] = 0.0f; - } - - m_parking_position = 0.f; - m_extra_loading_move = 0.f; - m_extruded_last_z = 0.0f; - m_first_layer_height = 0.0f; - m_processing_start_custom_gcode = false; - m_g1_line_id = 0; - m_layer_id = 0; - m_cp_color.reset(); - - m_producer = EProducer::Unknown; - - m_time_processor.reset(); - m_used_filaments.reset(); - - m_result.reset(); - m_result.id = ++s_result_id; - - m_use_volumetric_e = false; - m_last_default_color_id = 0; - - m_options_z_corrector.reset(); - -#if ENABLE_SPIRAL_VASE_LAYERS - m_spiral_vase_active = false; -#endif // ENABLE_SPIRAL_VASE_LAYERS - -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - m_mm3_per_mm_compare.reset(); - m_height_compare.reset(); - m_width_compare.reset(); -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING -} - -static inline const char* skip_whitespaces(const char *begin, const char *end) { - for (; begin != end && (*begin == ' ' || *begin == '\t'); ++ begin); - return begin; -} - -static inline const char* remove_eols(const char *begin, const char *end) { - for (; begin != end && (*(end - 1) == '\r' || *(end - 1) == '\n'); -- end); - return end; -} - -// Load a G-code into a stand-alone G-code viewer. -// throws CanceledException through print->throw_if_canceled() (sent by the caller as callback). -void GCodeProcessor::process_file(const std::string& filename, std::function cancel_callback) -{ - CNumericLocalesSetter locales_setter; - -#if ENABLE_GCODE_VIEWER_STATISTICS - m_start_time = std::chrono::high_resolution_clock::now(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - // pre-processing - // parse the gcode file to detect its producer - { - m_parser.parse_file_raw(filename, [this](GCodeReader& reader, const char *begin, const char *end) { - begin = skip_whitespaces(begin, end); - if (begin != end && *begin == ';') { - // Comment. - begin = skip_whitespaces(++ begin, end); - end = remove_eols(begin, end); - if (begin != end && detect_producer(std::string_view(begin, end - begin))) - m_parser.quit_parsing(); - } - }); - m_parser.reset(); - - // if the gcode was produced by PrusaSlicer, - // extract the config from it - if (m_producer == EProducer::PrusaSlicer || m_producer == EProducer::Slic3rPE || m_producer == EProducer::Slic3r) { - DynamicPrintConfig config; - config.apply(FullPrintConfig::defaults()); - // Silently substitute unknown values by new ones for loading configurations from PrusaSlicer's own G-code. - // Showing substitution log or errors may make sense, but we are not really reading many values from the G-code config, - // thus a probability of incorrect substitution is low and the G-code viewer is a consumer-only anyways. - config.load_from_gcode_file(filename, ForwardCompatibilitySubstitutionRule::EnableSilent); - apply_config(config); - } - else if (m_producer == EProducer::Simplify3D) - apply_config_simplify3d(filename); - else if (m_producer == EProducer::SuperSlicer) - apply_config_superslicer(filename); - } - - // process gcode - m_result.filename = filename; - m_result.id = ++s_result_id; - // 1st move must be a dummy move - m_result.moves.emplace_back(GCodeProcessorResult::MoveVertex()); - size_t parse_line_callback_cntr = 10000; - m_parser.parse_file(filename, [this, cancel_callback, &parse_line_callback_cntr](GCodeReader& reader, const GCodeReader::GCodeLine& line) { - if (-- parse_line_callback_cntr == 0) { - // Don't call the cancel_callback() too often, do it every at every 10000'th line. - parse_line_callback_cntr = 10000; - if (cancel_callback) - cancel_callback(); - } - this->process_gcode_line(line, true); - }, m_result.lines_ends); - - // Don't post-process the G-code to update time stamps. - this->finalize(false); -} - -void GCodeProcessor::initialize(const std::string& filename) -{ - assert(is_decimal_separator_point()); - -#if ENABLE_GCODE_VIEWER_STATISTICS - m_start_time = std::chrono::high_resolution_clock::now(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - // process gcode - m_result.filename = filename; - m_result.id = ++s_result_id; - // 1st move must be a dummy move - m_result.moves.emplace_back(GCodeProcessorResult::MoveVertex()); -} - -void GCodeProcessor::process_buffer(const std::string &buffer) -{ - //FIXME maybe cache GCodeLine gline to be over multiple parse_buffer() invocations. - m_parser.parse_buffer(buffer, [this](GCodeReader&, const GCodeReader::GCodeLine& line) { - this->process_gcode_line(line, false); - }); -} - -void GCodeProcessor::finalize(bool post_process) -{ - // update width/height of wipe moves - for (GCodeProcessorResult::MoveVertex& move : m_result.moves) { - if (move.type == EMoveType::Wipe) { - move.width = Wipe_Width; - move.height = Wipe_Height; - } - } - - // process the time blocks - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - TimeMachine& machine = m_time_processor.machines[i]; - TimeMachine::CustomGCodeTime& gcode_time = machine.gcode_time; - machine.calculate_time(); - if (gcode_time.needed && gcode_time.cache != 0.0f) - gcode_time.times.push_back({ CustomGCode::ColorChange, gcode_time.cache }); - } - - m_used_filaments.process_caches(this); - - update_estimated_times_stats(); - -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - std::cout << "\n"; - m_mm3_per_mm_compare.output(); - m_height_compare.output(); - m_width_compare.output(); -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - - if (post_process) - m_time_processor.post_process(m_result.filename, m_result.moves, m_result.lines_ends); -#if ENABLE_GCODE_VIEWER_STATISTICS - m_result.time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - m_start_time).count(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS -} - -float GCodeProcessor::get_time(PrintEstimatedStatistics::ETimeMode mode) const -{ - return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? m_time_processor.machines[static_cast(mode)].time : 0.0f; -} - -std::string GCodeProcessor::get_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const -{ - return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? short_time(get_time_dhms(m_time_processor.machines[static_cast(mode)].time)) : std::string("N/A"); -} - -std::vector>> GCodeProcessor::get_custom_gcode_times(PrintEstimatedStatistics::ETimeMode mode, bool include_remaining) const -{ - std::vector>> ret; - if (mode < PrintEstimatedStatistics::ETimeMode::Count) { - const TimeMachine& machine = m_time_processor.machines[static_cast(mode)]; - float total_time = 0.0f; - for (const auto& [type, time] : machine.gcode_time.times) { - float remaining = include_remaining ? machine.time - total_time : 0.0f; - ret.push_back({ type, { time, remaining } }); - total_time += time; - } - } - return ret; -} - -std::vector> GCodeProcessor::get_moves_time(PrintEstimatedStatistics::ETimeMode mode) const -{ - std::vector> ret; - if (mode < PrintEstimatedStatistics::ETimeMode::Count) { - for (size_t i = 0; i < m_time_processor.machines[static_cast(mode)].moves_time.size(); ++i) { - float time = m_time_processor.machines[static_cast(mode)].moves_time[i]; - if (time > 0.0f) - ret.push_back({ static_cast(i), time }); - } - } - return ret; -} - -std::vector> GCodeProcessor::get_roles_time(PrintEstimatedStatistics::ETimeMode mode) const -{ - std::vector> ret; - if (mode < PrintEstimatedStatistics::ETimeMode::Count) { - for (size_t i = 0; i < m_time_processor.machines[static_cast(mode)].roles_time.size(); ++i) { - float time = m_time_processor.machines[static_cast(mode)].roles_time[i]; - if (time > 0.0f) - ret.push_back({ static_cast(i), time }); - } - } - return ret; -} - -ConfigSubstitutions load_from_superslicer_gcode_file(const std::string& filename, DynamicPrintConfig& config, ForwardCompatibilitySubstitutionRule compatibility_rule) -{ - // for reference, see: ConfigBase::load_from_gcode_file() - - boost::nowide::ifstream ifs(filename); - - auto header_end_pos = ifs.tellg(); - ConfigSubstitutionContext substitutions_ctxt(compatibility_rule); - size_t key_value_pairs = 0; - - ifs.seekg(0, ifs.end); - auto file_length = ifs.tellg(); - auto data_length = std::min(65535, file_length - header_end_pos); - ifs.seekg(file_length - data_length, ifs.beg); - std::vector data(size_t(data_length) + 1, 0); - ifs.read(data.data(), data_length); - ifs.close(); - key_value_pairs = ConfigBase::load_from_gcode_string_legacy(config, data.data(), substitutions_ctxt); - - if (key_value_pairs < 80) - throw Slic3r::RuntimeError(format("Suspiciously low number of configuration values extracted from %1%: %2%", filename, key_value_pairs)); - - return std::move(substitutions_ctxt.substitutions); -} - -void GCodeProcessor::apply_config_superslicer(const std::string& filename) -{ - DynamicPrintConfig config; - config.apply(FullPrintConfig::defaults()); - load_from_superslicer_gcode_file(filename, config, ForwardCompatibilitySubstitutionRule::EnableSilent); - apply_config(config); -} - -std::vector GCodeProcessor::get_layers_time(PrintEstimatedStatistics::ETimeMode mode) const -{ - return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? - m_time_processor.machines[static_cast(mode)].layers_time : - std::vector(); -} - -void GCodeProcessor::apply_config_simplify3d(const std::string& filename) -{ - struct BedSize - { - double x{ 0.0 }; - double y{ 0.0 }; - - bool is_defined() const { return x > 0.0 && y > 0.0; } - }; - - BedSize bed_size; - bool producer_detected = false; - - m_parser.parse_file_raw(filename, [this, &bed_size, &producer_detected](GCodeReader& reader, const char* begin, const char* end) { - - auto extract_double = [](const std::string_view cmt, const std::string& key, double& out) { - size_t pos = cmt.find(key); - if (pos != cmt.npos) { - pos = cmt.find(',', pos); - if (pos != cmt.npos) { - out = string_to_double_decimal_point(cmt.substr(pos+1)); - return true; - } - } - return false; - }; - - auto extract_floats = [](const std::string_view cmt, const std::string& key, std::vector& out) { - size_t pos = cmt.find(key); - if (pos != cmt.npos) { - pos = cmt.find(',', pos); - if (pos != cmt.npos) { - const std::string_view data_str = cmt.substr(pos + 1); - std::vector values_str; - boost::split(values_str, data_str, boost::is_any_of("|,"), boost::token_compress_on); - for (const std::string& s : values_str) { - out.emplace_back(static_cast(string_to_double_decimal_point(s))); - } - return true; - } - } - return false; - }; - - begin = skip_whitespaces(begin, end); - end = remove_eols(begin, end); - if (begin != end) { - if (*begin == ';') { - // Comment. - begin = skip_whitespaces(++ begin, end); - if (begin != end) { - std::string_view comment(begin, end - begin); - if (producer_detected) { - if (bed_size.x == 0.0 && comment.find("strokeXoverride") != comment.npos) - extract_double(comment, "strokeXoverride", bed_size.x); - else if (bed_size.y == 0.0 && comment.find("strokeYoverride") != comment.npos) - extract_double(comment, "strokeYoverride", bed_size.y); - else if (comment.find("filamentDiameters") != comment.npos) { - m_result.filament_diameters.clear(); - extract_floats(comment, "filamentDiameters", m_result.filament_diameters); - } else if (comment.find("filamentDensities") != comment.npos) { - m_result.filament_densities.clear(); - extract_floats(comment, "filamentDensities", m_result.filament_densities); - } else if (comment.find("extruderDiameter") != comment.npos) { - std::vector extruder_diameters; - extract_floats(comment, "extruderDiameter", extruder_diameters); - m_result.extruders_count = extruder_diameters.size(); - } - } else if (boost::starts_with(comment, "G-Code generated by Simplify3D(R)")) - producer_detected = true; - } - } else { - // Some non-empty G-code line detected, stop parsing config comments. - reader.quit_parsing(); - } - } - }); - - if (m_result.extruders_count == 0) - m_result.extruders_count = std::max(1, std::min(m_result.filament_diameters.size(), m_result.filament_densities.size())); - - if (bed_size.is_defined()) { - m_result.bed_shape = { - { 0.0, 0.0 }, - { bed_size.x, 0.0 }, - { bed_size.x, bed_size.y }, - { 0.0, bed_size.y } - }; - } -} - -void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line, bool producers_enabled) -{ -/* std::cout << line.raw() << std::endl; */ - - ++m_line_id; - - // update start position - m_start_position = m_end_position; - - const std::string_view cmd = line.cmd(); - if (cmd.length() > 1) { - // process command lines - switch (cmd[0]) - { - case 'g': - case 'G': - switch (cmd.size()) { - case 2: - switch (cmd[1]) { - case '0': { process_G0(line); break; } // Move - case '1': { process_G1(line); break; } // Move - default: break; - } - break; - case 3: - switch (cmd[1]) { - case '1': - switch (cmd[2]) { - case '0': { process_G10(line); break; } // Retract - case '1': { process_G11(line); break; } // Unretract - default: break; - } - break; - case '2': - switch (cmd[2]) { - case '0': { process_G20(line); break; } // Set Units to Inches - case '1': { process_G21(line); break; } // Set Units to Millimeters - case '2': { process_G22(line); break; } // Firmware controlled retract - case '3': { process_G23(line); break; } // Firmware controlled unretract - case '8': { process_G28(line); break; } // Move to origin - default: break; - } - break; - case '9': - switch (cmd[2]) { - case '0': { process_G90(line); break; } // Set to Absolute Positioning - case '1': { process_G91(line); break; } // Set to Relative Positioning - case '2': { process_G92(line); break; } // Set Position - default: break; - } - break; - } - break; - default: - break; - } - break; - case 'm': - case 'M': - switch (cmd.size()) { - case 2: - switch (cmd[1]) { - case '1': { process_M1(line); break; } // Sleep or Conditional stop - default: break; - } - break; - case 3: - switch (cmd[1]) { - case '8': - switch (cmd[2]) { - case '2': { process_M82(line); break; } // Set extruder to absolute mode - case '3': { process_M83(line); break; } // Set extruder to relative mode - default: break; - } - break; - default: - break; - } - break; - case 4: - switch (cmd[1]) { - case '1': - switch (cmd[2]) { - case '0': - switch (cmd[3]) { - case '4': { process_M104(line); break; } // Set extruder temperature - case '6': { process_M106(line); break; } // Set fan speed - case '7': { process_M107(line); break; } // Disable fan - case '8': { process_M108(line); break; } // Set tool (Sailfish) - case '9': { process_M109(line); break; } // Set extruder temperature and wait - default: break; - } - break; - case '3': - switch (cmd[3]) { - case '2': { process_M132(line); break; } // Recall stored home offsets - case '5': { process_M135(line); break; } // Set tool (MakerWare) - default: break; - } - break; - default: - break; - } - break; - case '2': - switch (cmd[2]) { - case '0': - switch (cmd[3]) { - case '1': { process_M201(line); break; } // Set max printing acceleration - case '3': { process_M203(line); break; } // Set maximum feedrate - case '4': { process_M204(line); break; } // Set default acceleration - case '5': { process_M205(line); break; } // Advanced settings - default: break; - } - break; - case '2': - switch (cmd[3]) { - case '1': { process_M221(line); break; } // Set extrude factor override percentage - default: break; - } - break; - default: - break; - } - break; - case '4': - switch (cmd[2]) { - case '0': - switch (cmd[3]) { - case '1': { process_M401(line); break; } // Repetier: Store x, y and z position - case '2': { process_M402(line); break; } // Repetier: Go to stored position - default: break; - } - break; - default: - break; - } - break; - case '5': - switch (cmd[2]) { - case '6': - switch (cmd[3]) { - case '6': { process_M566(line); break; } // Set allowable instantaneous speed change - default: break; - } - break; - default: - break; - } - break; - case '7': - switch (cmd[2]) { - case '0': - switch (cmd[3]) { - case '2': { process_M702(line); break; } // Unload the current filament into the MK3 MMU2 unit at the end of print. - default: break; - } - break; - default: - break; - } - break; - default: - break; - } - break; - default: - break; - } - break; - case 't': - case 'T': - process_T(line); // Select Tool - break; - default: - break; - } - } - else { - const std::string &comment = line.raw(); - if (comment.length() > 2 && comment.front() == ';') - // Process tags embedded into comments. Tag comments always start at the start of a line - // with a comment and continue with a tag without any whitespace separator. - process_tags(comment.substr(1), producers_enabled); - } -} - -#if __has_include() - template - struct is_from_chars_convertible : std::false_type {}; - template - struct is_from_chars_convertible(), std::declval(), std::declval()))>> : std::true_type {}; -#endif - -// Returns true if the number was parsed correctly into out and the number spanned the whole input string. -template -[[nodiscard]] static inline bool parse_number(const std::string_view sv, T &out) -{ - // https://www.bfilipek.com/2019/07/detect-overload-from-chars.html#example-stdfromchars -#if __has_include() - // Visual Studio 19 supports from_chars all right. - // OSX compiler that we use only implements std::from_chars just for ints. - // GCC that we compile on does not provide at all. - if constexpr (is_from_chars_convertible::value) { - auto str_end = sv.data() + sv.size(); - auto [end_ptr, error_code] = std::from_chars(sv.data(), str_end, out); - return error_code == std::errc() && end_ptr == str_end; - } - else -#endif - { - // Legacy conversion, which is costly due to having to make a copy of the string before conversion. - try { - assert(sv.size() < 1024); - assert(sv.data() != nullptr); - std::string str { sv }; - size_t read = 0; - if constexpr (std::is_same_v) - out = std::stoi(str, &read); - else if constexpr (std::is_same_v) - out = std::stol(str, &read); - else if constexpr (std::is_same_v) - out = string_to_double_decimal_point(str, &read); - else if constexpr (std::is_same_v) - out = string_to_double_decimal_point(str, &read); - return str.size() == read; - } catch (...) { - return false; - } - } -} - -void GCodeProcessor::process_tags(const std::string_view comment, bool producers_enabled) -{ - // producers tags - if (producers_enabled && process_producers_tags(comment)) - return; - - // extrusion role tag - if (boost::starts_with(comment, reserved_tag(ETags::Role))) { - set_extrusion_role(ExtrusionEntity::string_to_role(comment.substr(reserved_tag(ETags::Role).length()))); - if (m_extrusion_role == erExternalPerimeter) - m_seams_detector.activate(true); - m_processing_start_custom_gcode = (m_extrusion_role == erCustom && m_g1_line_id == 0); - return; - } - - // wipe start tag - if (boost::starts_with(comment, reserved_tag(ETags::Wipe_Start))) { - m_wiping = true; - return; - } - - // wipe end tag - if (boost::starts_with(comment, reserved_tag(ETags::Wipe_End))) { - m_wiping = false; - return; - } - - if (!producers_enabled || m_producer == EProducer::PrusaSlicer) { - // height tag - if (boost::starts_with(comment, reserved_tag(ETags::Height))) { - if (!parse_number(comment.substr(reserved_tag(ETags::Height).size()), m_forced_height)) - BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; - return; - } - // width tag - if (boost::starts_with(comment, reserved_tag(ETags::Width))) { - if (!parse_number(comment.substr(reserved_tag(ETags::Width).size()), m_forced_width)) - BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ")."; - return; - } - } - - // color change tag - if (boost::starts_with(comment, reserved_tag(ETags::Color_Change))) { - unsigned char extruder_id = 0; - static std::vector Default_Colors = { - "#0B2C7A", // { 0.043f, 0.173f, 0.478f }, // bluish - "#1C8891", // { 0.110f, 0.533f, 0.569f }, - "#AAF200", // { 0.667f, 0.949f, 0.000f }, - "#F5CE0A", // { 0.961f, 0.808f, 0.039f }, - "#D16830", // { 0.820f, 0.408f, 0.188f }, - "#942616", // { 0.581f, 0.149f, 0.087f } // reddish - }; - - std::string color = Default_Colors[0]; - auto is_valid_color = [](const std::string& color) { - auto is_hex_digit = [](char c) { - return ((c >= '0' && c <= '9') || - (c >= 'A' && c <= 'F') || - (c >= 'a' && c <= 'f')); - }; - - if (color[0] != '#' || color.length() != 7) - return false; - for (int i = 1; i <= 6; ++i) { - if (!is_hex_digit(color[i])) - return false; - } - return true; - }; - - std::vector tokens; - boost::split(tokens, comment, boost::is_any_of(","), boost::token_compress_on); - if (tokens.size() > 1) { - if (tokens[1][0] == 'T') { - int eid; - if (!parse_number(tokens[1].substr(1), eid) || eid < 0 || eid > 255) { - BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Color_Change (" << comment << ")."; - return; - } - extruder_id = static_cast(eid); - } - } - if (tokens.size() > 2) { - if (is_valid_color(tokens[2])) - color = tokens[2]; - } - else { - color = Default_Colors[m_last_default_color_id]; - ++m_last_default_color_id; - if (m_last_default_color_id == Default_Colors.size()) - m_last_default_color_id = 0; - } - - if (extruder_id < m_extruder_colors.size()) - m_extruder_colors[extruder_id] = static_cast(m_extruder_offsets.size()) + m_cp_color.counter; // color_change position in list of color for preview - ++m_cp_color.counter; - if (m_cp_color.counter == UCHAR_MAX) - m_cp_color.counter = 0; - - if (m_extruder_id == extruder_id) { - m_cp_color.current = m_extruder_colors[extruder_id]; - store_move_vertex(EMoveType::Color_change); - CustomGCode::Item item = { static_cast(m_end_position[2]), CustomGCode::ColorChange, extruder_id + 1, color, "" }; - m_result.custom_gcode_per_print_z.emplace_back(item); - m_options_z_corrector.set(); - process_custom_gcode_time(CustomGCode::ColorChange); - process_filaments(CustomGCode::ColorChange); - } - - return; - } - - // pause print tag - if (comment == reserved_tag(ETags::Pause_Print)) { - store_move_vertex(EMoveType::Pause_Print); - CustomGCode::Item item = { static_cast(m_end_position[2]), CustomGCode::PausePrint, m_extruder_id + 1, "", "" }; - m_result.custom_gcode_per_print_z.emplace_back(item); - m_options_z_corrector.set(); - process_custom_gcode_time(CustomGCode::PausePrint); - return; - } - - // custom code tag - if (comment == reserved_tag(ETags::Custom_Code)) { - store_move_vertex(EMoveType::Custom_GCode); - CustomGCode::Item item = { static_cast(m_end_position[2]), CustomGCode::Custom, m_extruder_id + 1, "", "" }; - m_result.custom_gcode_per_print_z.emplace_back(item); - m_options_z_corrector.set(); - return; - } - - // layer change tag - if (comment == reserved_tag(ETags::Layer_Change)) { - ++m_layer_id; -#if ENABLE_SPIRAL_VASE_LAYERS - if (m_spiral_vase_active) { - if (m_result.moves.empty()) - m_result.spiral_vase_layers.push_back({ m_first_layer_height, { 0, 0 } }); - else { - const size_t move_id = m_result.moves.size() - 1; - if (!m_result.spiral_vase_layers.empty() && m_end_position[Z] == m_result.spiral_vase_layers.back().first) - m_result.spiral_vase_layers.back().second.second = move_id; - else - m_result.spiral_vase_layers.push_back({ static_cast(m_end_position[Z]), { move_id, move_id } }); - } - } -#endif // ENABLE_SPIRAL_VASE_LAYERS - return; - } - -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - // mm3_per_mm print tag - if (boost::starts_with(comment, Mm3_Per_Mm_Tag)) { - if (! parse_number(comment.substr(Mm3_Per_Mm_Tag.size()), m_mm3_per_mm_compare.last_tag_value)) - BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Mm3_Per_Mm (" << comment << ")."; - return; - } -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING -} - -bool GCodeProcessor::process_producers_tags(const std::string_view comment) -{ - switch (m_producer) - { - case EProducer::Slic3rPE: - case EProducer::Slic3r: - case EProducer::SuperSlicer: - case EProducer::PrusaSlicer: { return process_prusaslicer_tags(comment); } - case EProducer::Cura: { return process_cura_tags(comment); } - case EProducer::Simplify3D: { return process_simplify3d_tags(comment); } - case EProducer::CraftWare: { return process_craftware_tags(comment); } - case EProducer::ideaMaker: { return process_ideamaker_tags(comment); } - case EProducer::KissSlicer: { return process_kissslicer_tags(comment); } - default: { return false; } - } -} - -bool GCodeProcessor::process_prusaslicer_tags(const std::string_view comment) -{ - return false; -} - -bool GCodeProcessor::process_cura_tags(const std::string_view comment) -{ - // TYPE -> extrusion role - std::string tag = "TYPE:"; - size_t pos = comment.find(tag); - if (pos != comment.npos) { - const std::string_view type = comment.substr(pos + tag.length()); - if (type == "SKIRT") - set_extrusion_role(erSkirt); - else if (type == "WALL-OUTER") - set_extrusion_role(erExternalPerimeter); - else if (type == "WALL-INNER") - set_extrusion_role(erPerimeter); - else if (type == "SKIN") - set_extrusion_role(erSolidInfill); - else if (type == "FILL") - set_extrusion_role(erInternalInfill); - else if (type == "SUPPORT") - set_extrusion_role(erSupportMaterial); - else if (type == "SUPPORT-INTERFACE") - set_extrusion_role(erSupportMaterialInterface); - else if (type == "PRIME-TOWER") - set_extrusion_role(erWipeTower); - else { - set_extrusion_role(erNone); - BOOST_LOG_TRIVIAL(warning) << "GCodeProcessor found unknown extrusion role: " << type; - } - - if (m_extrusion_role == erExternalPerimeter) - m_seams_detector.activate(true); - - return true; - } - - // flavor - tag = "FLAVOR:"; - pos = comment.find(tag); - if (pos != comment.npos) { - const std::string_view flavor = comment.substr(pos + tag.length()); - if (flavor == "BFB") - m_flavor = gcfMarlinLegacy; // is this correct ? - else if (flavor == "Mach3") - m_flavor = gcfMach3; - else if (flavor == "Makerbot") - m_flavor = gcfMakerWare; - else if (flavor == "UltiGCode") - m_flavor = gcfMarlinLegacy; // is this correct ? - else if (flavor == "Marlin(Volumetric)") - m_flavor = gcfMarlinLegacy; // is this correct ? - else if (flavor == "Griffin") - m_flavor = gcfMarlinLegacy; // is this correct ? - else if (flavor == "Repetier") - m_flavor = gcfRepetier; - else if (flavor == "RepRap") - m_flavor = gcfRepRapFirmware; - else if (flavor == "Marlin") - m_flavor = gcfMarlinLegacy; - else - BOOST_LOG_TRIVIAL(warning) << "GCodeProcessor found unknown flavor: " << flavor; - - return true; - } - - // layer - tag = "LAYER:"; - pos = comment.find(tag); - if (pos != comment.npos) { - ++m_layer_id; - return true; - } - - return false; -} - -bool GCodeProcessor::process_simplify3d_tags(const std::string_view comment) -{ - // extrusion roles - - // in older versions the comments did not contain the key 'feature' - std::string_view cmt = comment; - size_t pos = cmt.find(" feature"); - if (pos == 0) - cmt.remove_prefix(8); - - // ; skirt - pos = cmt.find(" skirt"); - if (pos == 0) { - set_extrusion_role(erSkirt); - return true; - } - - // ; outer perimeter - pos = cmt.find(" outer perimeter"); - if (pos == 0) { - set_extrusion_role(erExternalPerimeter); - m_seams_detector.activate(true); - return true; - } - - // ; inner perimeter - pos = cmt.find(" inner perimeter"); - if (pos == 0) { - set_extrusion_role(erPerimeter); - return true; - } - - // ; gap fill - pos = cmt.find(" gap fill"); - if (pos == 0) { - set_extrusion_role(erGapFill); - return true; - } - - // ; infill - pos = cmt.find(" infill"); - if (pos == 0) { - set_extrusion_role(erInternalInfill); - return true; - } - - // ; solid layer - pos = cmt.find(" solid layer"); - if (pos == 0) { - set_extrusion_role(erSolidInfill); - return true; - } - - // ; bridge - pos = cmt.find(" bridge"); - if (pos == 0) { - set_extrusion_role(erBridgeInfill); - return true; - } - - // ; support - pos = cmt.find(" support"); - if (pos == 0) { - set_extrusion_role(erSupportMaterial); - return true; - } - - // ; dense support - pos = cmt.find(" dense support"); - if (pos == 0) { - set_extrusion_role(erSupportMaterialInterface); - return true; - } - - // ; prime pillar - pos = cmt.find(" prime pillar"); - if (pos == 0) { - set_extrusion_role(erWipeTower); - return true; - } - - // ; ooze shield - pos = cmt.find(" ooze shield"); - if (pos == 0) { - set_extrusion_role(erNone); // Missing mapping - return true; - } - - // ; raft - pos = cmt.find(" raft"); - if (pos == 0) { - set_extrusion_role(erSupportMaterial); - return true; - } - - // ; internal single extrusion - pos = cmt.find(" internal single extrusion"); - if (pos == 0) { - set_extrusion_role(erNone); // Missing mapping - return true; - } - - // geometry - // ; tool - std::string tag = " tool"; - pos = cmt.find(tag); - if (pos == 0) { - const std::string_view data = cmt.substr(pos + tag.length()); - std::string h_tag = "H"; - size_t h_start = data.find(h_tag); - size_t h_end = data.find_first_of(' ', h_start); - std::string w_tag = "W"; - size_t w_start = data.find(w_tag); - size_t w_end = data.find_first_of(' ', w_start); - if (h_start != data.npos) { - if (!parse_number(data.substr(h_start + 1, (h_end != data.npos) ? h_end - h_start - 1 : h_end), m_forced_height)) - BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; - } - if (w_start != data.npos) { - if (!parse_number(data.substr(w_start + 1, (w_end != data.npos) ? w_end - w_start - 1 : w_end), m_forced_width)) - BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ")."; - } - - return true; - } - - // ; layer - tag = " layer"; - pos = cmt.find(tag); - if (pos == 0) { - // skip lines "; layer end" - const std::string_view data = cmt.substr(pos + tag.length()); - size_t end_start = data.find("end"); - if (end_start == data.npos) - ++m_layer_id; - - return true; - } - - return false; -} - -bool GCodeProcessor::process_craftware_tags(const std::string_view comment) -{ - // segType -> extrusion role - std::string tag = "segType:"; - size_t pos = comment.find(tag); - if (pos != comment.npos) { - const std::string_view type = comment.substr(pos + tag.length()); - if (type == "Skirt") - set_extrusion_role(erSkirt); - else if (type == "Perimeter") - set_extrusion_role(erExternalPerimeter); - else if (type == "HShell") - set_extrusion_role(erNone); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - else if (type == "InnerHair") - set_extrusion_role(erNone); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - else if (type == "Loop") - set_extrusion_role(erNone); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - else if (type == "Infill") - set_extrusion_role(erInternalInfill); - else if (type == "Raft") - set_extrusion_role(erSkirt); - else if (type == "Support") - set_extrusion_role(erSupportMaterial); - else if (type == "SupportTouch") - set_extrusion_role(erSupportMaterial); - else if (type == "SoftSupport") - set_extrusion_role(erSupportMaterialInterface); - else if (type == "Pillar") - set_extrusion_role(erWipeTower); - else { - set_extrusion_role(erNone); - BOOST_LOG_TRIVIAL(warning) << "GCodeProcessor found unknown extrusion role: " << type; - } - - if (m_extrusion_role == erExternalPerimeter) - m_seams_detector.activate(true); - - return true; - } - - // layer - pos = comment.find(" Layer #"); - if (pos == 0) { - ++m_layer_id; - return true; - } - - return false; -} - -bool GCodeProcessor::process_ideamaker_tags(const std::string_view comment) -{ - // TYPE -> extrusion role - std::string tag = "TYPE:"; - size_t pos = comment.find(tag); - if (pos != comment.npos) { - const std::string_view type = comment.substr(pos + tag.length()); - if (type == "RAFT") - set_extrusion_role(erSkirt); - else if (type == "WALL-OUTER") - set_extrusion_role(erExternalPerimeter); - else if (type == "WALL-INNER") - set_extrusion_role(erPerimeter); - else if (type == "SOLID-FILL") - set_extrusion_role(erSolidInfill); - else if (type == "FILL") - set_extrusion_role(erInternalInfill); - else if (type == "BRIDGE") - set_extrusion_role(erBridgeInfill); - else if (type == "SUPPORT") - set_extrusion_role(erSupportMaterial); - else { - set_extrusion_role(erNone); - BOOST_LOG_TRIVIAL(warning) << "GCodeProcessor found unknown extrusion role: " << type; - } - - if (m_extrusion_role == erExternalPerimeter) - m_seams_detector.activate(true); - - return true; - } - - // geometry - // width - tag = "WIDTH:"; - pos = comment.find(tag); - if (pos != comment.npos) { - if (!parse_number(comment.substr(pos + tag.length()), m_forced_width)) - BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ")."; - return true; - } - - // height - tag = "HEIGHT:"; - pos = comment.find(tag); - if (pos != comment.npos) { - if (!parse_number(comment.substr(pos + tag.length()), m_forced_height)) - BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; - return true; - } - - // layer - pos = comment.find("LAYER:"); - if (pos == 0) { - ++m_layer_id; - return true; - } - - return false; -} - -bool GCodeProcessor::process_kissslicer_tags(const std::string_view comment) -{ - // extrusion roles - - // ; 'Raft Path' - size_t pos = comment.find(" 'Raft Path'"); - if (pos == 0) { - set_extrusion_role(erSkirt); - return true; - } - - // ; 'Support Interface Path' - pos = comment.find(" 'Support Interface Path'"); - if (pos == 0) { - set_extrusion_role(erSupportMaterialInterface); - return true; - } - - // ; 'Travel/Ironing Path' - pos = comment.find(" 'Travel/Ironing Path'"); - if (pos == 0) { - set_extrusion_role(erIroning); - return true; - } - - // ; 'Support (may Stack) Path' - pos = comment.find(" 'Support (may Stack) Path'"); - if (pos == 0) { - set_extrusion_role(erSupportMaterial); - return true; - } - - // ; 'Perimeter Path' - pos = comment.find(" 'Perimeter Path'"); - if (pos == 0) { - set_extrusion_role(erExternalPerimeter); - m_seams_detector.activate(true); - return true; - } - - // ; 'Pillar Path' - pos = comment.find(" 'Pillar Path'"); - if (pos == 0) { - set_extrusion_role(erNone); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - return true; - } - - // ; 'Destring/Wipe/Jump Path' - pos = comment.find(" 'Destring/Wipe/Jump Path'"); - if (pos == 0) { - set_extrusion_role(erNone); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - return true; - } - - // ; 'Prime Pillar Path' - pos = comment.find(" 'Prime Pillar Path'"); - if (pos == 0) { - set_extrusion_role(erNone); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - return true; - } - - // ; 'Loop Path' - pos = comment.find(" 'Loop Path'"); - if (pos == 0) { - set_extrusion_role(erNone); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - return true; - } - - // ; 'Crown Path' - pos = comment.find(" 'Crown Path'"); - if (pos == 0) { - set_extrusion_role(erNone); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - return true; - } - - // ; 'Solid Path' - pos = comment.find(" 'Solid Path'"); - if (pos == 0) { - set_extrusion_role(erNone); - return true; - } - - // ; 'Stacked Sparse Infill Path' - pos = comment.find(" 'Stacked Sparse Infill Path'"); - if (pos == 0) { - set_extrusion_role(erInternalInfill); - return true; - } - - // ; 'Sparse Infill Path' - pos = comment.find(" 'Sparse Infill Path'"); - if (pos == 0) { - set_extrusion_role(erSolidInfill); - return true; - } - - // geometry - - // layer - pos = comment.find(" BEGIN_LAYER_"); - if (pos == 0) { - ++m_layer_id; - return true; - } - - return false; -} - -bool GCodeProcessor::detect_producer(const std::string_view comment) -{ - for (const auto& [id, search_string] : Producers) { - size_t pos = comment.find(search_string); - if (pos != comment.npos) { - m_producer = id; - BOOST_LOG_TRIVIAL(info) << "Detected gcode producer: " << search_string; - return true; - } - } - return false; -} - -void GCodeProcessor::process_G0(const GCodeReader::GCodeLine& line) -{ - process_G1(line); -} - -void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) -{ - float filament_diameter = (static_cast(m_extruder_id) < m_result.filament_diameters.size()) ? m_result.filament_diameters[m_extruder_id] : m_result.filament_diameters.back(); - float filament_radius = 0.5f * filament_diameter; - float area_filament_cross_section = static_cast(M_PI) * sqr(filament_radius); - auto absolute_position = [this, area_filament_cross_section](Axis axis, const GCodeReader::GCodeLine& lineG1) { - bool is_relative = (m_global_positioning_type == EPositioningType::Relative); - if (axis == E) - is_relative |= (m_e_local_positioning_type == EPositioningType::Relative); - - if (lineG1.has(Slic3r::Axis(axis))) { - float lengthsScaleFactor = (m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f; - float ret = lineG1.value(Slic3r::Axis(axis)) * lengthsScaleFactor; - if (axis == E && m_use_volumetric_e) - ret /= area_filament_cross_section; - return is_relative ? m_start_position[axis] + ret : m_origin[axis] + ret; - } - else - return m_start_position[axis]; - }; - - auto move_type = [this](const AxisCoords& delta_pos) { - EMoveType type = EMoveType::Noop; - - if (m_wiping) - type = EMoveType::Wipe; - else if (delta_pos[E] < 0.0f) - type = (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f || delta_pos[Z] != 0.0f) ? EMoveType::Travel : EMoveType::Retract; - else if (delta_pos[E] > 0.0f) { - if (delta_pos[X] == 0.0f && delta_pos[Y] == 0.0f) - type = (delta_pos[Z] == 0.0f) ? EMoveType::Unretract : EMoveType::Travel; - else if (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f) - type = EMoveType::Extrude; - } - else if (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f || delta_pos[Z] != 0.0f) - type = EMoveType::Travel; - - return type; - }; - - ++m_g1_line_id; - - // enable processing of lines M201/M203/M204/M205 - m_time_processor.machine_envelope_processing_enabled = true; - - // updates axes positions from line - for (unsigned char a = X; a <= E; ++a) { - m_end_position[a] = absolute_position((Axis)a, line); - } - - // updates feedrate from line, if present - if (line.has_f()) - m_feedrate = line.f() * MMMIN_TO_MMSEC; - - // calculates movement deltas - AxisCoords delta_pos; - for (unsigned char a = X; a <= E; ++a) - delta_pos[a] = m_end_position[a] - m_start_position[a]; - - if (std::all_of(delta_pos.begin(), delta_pos.end(), [](double d) { return d == 0.; })) - return; - - const float volume_extruded_filament = area_filament_cross_section * delta_pos[E]; - - if (volume_extruded_filament != 0.) - m_used_filaments.increase_caches(volume_extruded_filament, - this->m_extruder_id, area_filament_cross_section * this->m_parking_position, - area_filament_cross_section * this->m_extra_loading_move); - - EMoveType type = move_type(delta_pos); - if (type == EMoveType::Extrude) { - float delta_xyz = std::sqrt(sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z])); - float area_toolpath_cross_section = volume_extruded_filament / delta_xyz; - - // volume extruded filament / tool displacement = area toolpath cross section - m_mm3_per_mm = area_toolpath_cross_section; -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - m_mm3_per_mm_compare.update(area_toolpath_cross_section, m_extrusion_role); -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - - if (m_forced_height > 0.0f) - m_height = m_forced_height; - else { - if (m_end_position[Z] > m_extruded_last_z + EPSILON) - m_height = m_end_position[Z] - m_extruded_last_z; - } - - if (m_height == 0.0f) - m_height = DEFAULT_TOOLPATH_HEIGHT; - - if (m_end_position[Z] == 0.0f) - m_end_position[Z] = m_height; - - m_extruded_last_z = m_end_position[Z]; - m_options_z_corrector.update(m_height); - -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - m_height_compare.update(m_height, m_extrusion_role); -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - - if (m_forced_width > 0.0f) - m_width = m_forced_width; - else if (m_extrusion_role == erExternalPerimeter) - // cross section: rectangle - m_width = delta_pos[E] * static_cast(M_PI * sqr(1.05f * filament_radius)) / (delta_xyz * m_height); - else if (m_extrusion_role == erBridgeInfill || m_extrusion_role == erNone) - // cross section: circle - m_width = static_cast(m_result.filament_diameters[m_extruder_id]) * std::sqrt(delta_pos[E] / delta_xyz); - else - // cross section: rectangle + 2 semicircles - m_width = delta_pos[E] * static_cast(M_PI * sqr(filament_radius)) / (delta_xyz * m_height) + static_cast(1.0 - 0.25 * M_PI) * m_height; - - if (m_width == 0.0f) - m_width = DEFAULT_TOOLPATH_WIDTH; - - // clamp width to avoid artifacts which may arise from wrong values of m_height - m_width = std::min(m_width, std::max(2.0f, 4.0f * m_height)); - -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - m_width_compare.update(m_width, m_extrusion_role); -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - } - - // time estimate section - auto move_length = [](const AxisCoords& delta_pos) { - float sq_xyz_length = sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z]); - return (sq_xyz_length > 0.0f) ? std::sqrt(sq_xyz_length) : std::abs(delta_pos[E]); - }; - - auto is_extrusion_only_move = [](const AxisCoords& delta_pos) { - return delta_pos[X] == 0.0f && delta_pos[Y] == 0.0f && delta_pos[Z] == 0.0f && delta_pos[E] != 0.0f; - }; - - float distance = move_length(delta_pos); - assert(distance != 0.0f); - float inv_distance = 1.0f / distance; - - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - TimeMachine& machine = m_time_processor.machines[i]; - if (!machine.enabled) - continue; - - TimeMachine::State& curr = machine.curr; - TimeMachine::State& prev = machine.prev; - std::vector& blocks = machine.blocks; - - curr.feedrate = (delta_pos[E] == 0.0f) ? - minimum_travel_feedrate(static_cast(i), m_feedrate) : - minimum_feedrate(static_cast(i), m_feedrate); - - TimeBlock block; - block.move_type = type; - block.role = m_extrusion_role; - block.distance = distance; - block.g1_line_id = m_g1_line_id; - block.layer_id = std::max(1, m_layer_id); - - // calculates block cruise feedrate - float min_feedrate_factor = 1.0f; - for (unsigned char a = X; a <= E; ++a) { - curr.axis_feedrate[a] = curr.feedrate * delta_pos[a] * inv_distance; - if (a == E) - curr.axis_feedrate[a] *= machine.extrude_factor_override_percentage; - - curr.abs_axis_feedrate[a] = std::abs(curr.axis_feedrate[a]); - if (curr.abs_axis_feedrate[a] != 0.0f) { - float axis_max_feedrate = get_axis_max_feedrate(static_cast(i), static_cast(a)); - if (axis_max_feedrate != 0.0f) - min_feedrate_factor = std::min(min_feedrate_factor, axis_max_feedrate / curr.abs_axis_feedrate[a]); - } - } - - block.feedrate_profile.cruise = min_feedrate_factor * curr.feedrate; - - if (min_feedrate_factor < 1.0f) { - for (unsigned char a = X; a <= E; ++a) { - curr.axis_feedrate[a] *= min_feedrate_factor; - curr.abs_axis_feedrate[a] *= min_feedrate_factor; - } - } - - // calculates block acceleration - float acceleration = - (type == EMoveType::Travel) ? get_travel_acceleration(static_cast(i)) : - (is_extrusion_only_move(delta_pos) ? - get_retract_acceleration(static_cast(i)) : - get_acceleration(static_cast(i))); - - for (unsigned char a = X; a <= E; ++a) { - float axis_max_acceleration = get_axis_max_acceleration(static_cast(i), static_cast(a)); - if (acceleration * std::abs(delta_pos[a]) * inv_distance > axis_max_acceleration) - acceleration = axis_max_acceleration; - } - - block.acceleration = acceleration; - - // calculates block exit feedrate - curr.safe_feedrate = block.feedrate_profile.cruise; - - for (unsigned char a = X; a <= E; ++a) { - float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(a)); - if (curr.abs_axis_feedrate[a] > axis_max_jerk) - curr.safe_feedrate = std::min(curr.safe_feedrate, axis_max_jerk); - } - - block.feedrate_profile.exit = curr.safe_feedrate; - - static const float PREVIOUS_FEEDRATE_THRESHOLD = 0.0001f; - - // calculates block entry feedrate - float vmax_junction = curr.safe_feedrate; - if (!blocks.empty() && prev.feedrate > PREVIOUS_FEEDRATE_THRESHOLD) { - bool prev_speed_larger = prev.feedrate > block.feedrate_profile.cruise; - float smaller_speed_factor = prev_speed_larger ? (block.feedrate_profile.cruise / prev.feedrate) : (prev.feedrate / block.feedrate_profile.cruise); - // Pick the smaller of the nominal speeds. Higher speed shall not be achieved at the junction during coasting. - vmax_junction = prev_speed_larger ? block.feedrate_profile.cruise : prev.feedrate; - - float v_factor = 1.0f; - bool limited = false; - - for (unsigned char a = X; a <= E; ++a) { - // Limit an axis. We have to differentiate coasting from the reversal of an axis movement, or a full stop. - float v_exit = prev.axis_feedrate[a]; - float v_entry = curr.axis_feedrate[a]; - - if (prev_speed_larger) - v_exit *= smaller_speed_factor; - - if (limited) { - v_exit *= v_factor; - v_entry *= v_factor; - } - - // Calculate the jerk depending on whether the axis is coasting in the same direction or reversing a direction. - float jerk = - (v_exit > v_entry) ? - ((v_entry > 0.0f || v_exit < 0.0f) ? - // coasting - (v_exit - v_entry) : - // axis reversal - std::max(v_exit, -v_entry)) : - // v_exit <= v_entry - ((v_entry < 0.0f || v_exit > 0.0f) ? - // coasting - (v_entry - v_exit) : - // axis reversal - std::max(-v_exit, v_entry)); - - float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(a)); - if (jerk > axis_max_jerk) { - v_factor *= axis_max_jerk / jerk; - limited = true; - } - } - - if (limited) - vmax_junction *= v_factor; - - // Now the transition velocity is known, which maximizes the shared exit / entry velocity while - // respecting the jerk factors, it may be possible, that applying separate safe exit / entry velocities will achieve faster prints. - float vmax_junction_threshold = vmax_junction * 0.99f; - - // Not coasting. The machine will stop and start the movements anyway, better to start the segment from start. - if (prev.safe_feedrate > vmax_junction_threshold && curr.safe_feedrate > vmax_junction_threshold) - vmax_junction = curr.safe_feedrate; - } - - float v_allowable = max_allowable_speed(-acceleration, curr.safe_feedrate, block.distance); - block.feedrate_profile.entry = std::min(vmax_junction, v_allowable); - - block.max_entry_speed = vmax_junction; - block.flags.nominal_length = (block.feedrate_profile.cruise <= v_allowable); - block.flags.recalculate = true; - block.safe_feedrate = curr.safe_feedrate; - - // calculates block trapezoid - block.calculate_trapezoid(); - - // updates previous - prev = curr; - - blocks.push_back(block); - - if (blocks.size() > TimeProcessor::Planner::refresh_threshold) - machine.calculate_time(TimeProcessor::Planner::queue_size); - } - - if (m_seams_detector.is_active()) { - // check for seam starting vertex - if (type == EMoveType::Extrude && m_extrusion_role == erExternalPerimeter && !m_seams_detector.has_first_vertex()) - m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[m_extruder_id]); - // check for seam ending vertex and store the resulting move - else if ((type != EMoveType::Extrude || (m_extrusion_role != erExternalPerimeter && m_extrusion_role != erOverhangPerimeter)) && m_seams_detector.has_first_vertex()) { - auto set_end_position = [this](const Vec3f& pos) { - m_end_position[X] = pos.x(); m_end_position[Y] = pos.y(); m_end_position[Z] = pos.z(); - }; - - const Vec3f curr_pos(m_end_position[X], m_end_position[Y], m_end_position[Z]); - const Vec3f new_pos = m_result.moves.back().position - m_extruder_offsets[m_extruder_id]; - const std::optional first_vertex = m_seams_detector.get_first_vertex(); - // the threshold value = 0.0625f == 0.25 * 0.25 is arbitrary, we may find some smarter condition later - - if ((new_pos - *first_vertex).squaredNorm() < 0.0625f) { -#if ENABLE_Z_OFFSET_CORRECTION - set_end_position(0.5f * (new_pos + *first_vertex) + m_z_offset * Vec3f::UnitZ()); -#else - set_end_position(0.5f * (new_pos + *first_vertex)); -#endif // ENABLE_Z_OFFSET_CORRECTION - store_move_vertex(EMoveType::Seam); - set_end_position(curr_pos); - } - - m_seams_detector.activate(false); - } - } - else if (type == EMoveType::Extrude && m_extrusion_role == erExternalPerimeter) { - m_seams_detector.activate(true); - m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[m_extruder_id]); - } - -#if ENABLE_SPIRAL_VASE_LAYERS - if (m_spiral_vase_active && !m_result.spiral_vase_layers.empty() && !m_result.moves.empty()) - m_result.spiral_vase_layers.back().second.second = m_result.moves.size() - 1; -#endif // ENABLE_SPIRAL_VASE_LAYERS - - // store move - store_move_vertex(type); -} - -void GCodeProcessor::process_G10(const GCodeReader::GCodeLine& line) -{ - // stores retract move - store_move_vertex(EMoveType::Retract); -} - -void GCodeProcessor::process_G11(const GCodeReader::GCodeLine& line) -{ - // stores unretract move - store_move_vertex(EMoveType::Unretract); -} - -void GCodeProcessor::process_G20(const GCodeReader::GCodeLine& line) -{ - m_units = EUnits::Inches; -} - -void GCodeProcessor::process_G21(const GCodeReader::GCodeLine& line) -{ - m_units = EUnits::Millimeters; -} - -void GCodeProcessor::process_G22(const GCodeReader::GCodeLine& line) -{ - // stores retract move - store_move_vertex(EMoveType::Retract); -} - -void GCodeProcessor::process_G23(const GCodeReader::GCodeLine& line) -{ - // stores unretract move - store_move_vertex(EMoveType::Unretract); -} - -void GCodeProcessor::process_G28(const GCodeReader::GCodeLine& line) -{ - std::string_view cmd = line.cmd(); - std::string new_line_raw = { cmd.data(), cmd.size() }; - bool found = false; - if (line.has('X')) { - new_line_raw += " X0"; - found = true; - } - if (line.has('Y')) { - new_line_raw += " Y0"; - found = true; - } - if (line.has('Z')) { - new_line_raw += " Z0"; - found = true; - } - if (!found) - new_line_raw += " X0 Y0 Z0"; - - GCodeReader::GCodeLine new_gline; - GCodeReader reader; - reader.parse_line(new_line_raw, [&](GCodeReader& reader, const GCodeReader::GCodeLine& gline) { new_gline = gline; }); - process_G1(new_gline); -} - -void GCodeProcessor::process_G90(const GCodeReader::GCodeLine& line) -{ - m_global_positioning_type = EPositioningType::Absolute; -} - -void GCodeProcessor::process_G91(const GCodeReader::GCodeLine& line) -{ - m_global_positioning_type = EPositioningType::Relative; -} - -void GCodeProcessor::process_G92(const GCodeReader::GCodeLine& line) -{ - float lengths_scale_factor = (m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f; - bool any_found = false; - - if (line.has_x()) { - m_origin[X] = m_end_position[X] - line.x() * lengths_scale_factor; - any_found = true; - } - - if (line.has_y()) { - m_origin[Y] = m_end_position[Y] - line.y() * lengths_scale_factor; - any_found = true; - } - - if (line.has_z()) { - m_origin[Z] = m_end_position[Z] - line.z() * lengths_scale_factor; - any_found = true; - } - - if (line.has_e()) { - // extruder coordinate can grow to the point where its float representation does not allow for proper addition with small increments, - // we set the value taken from the G92 line as the new current position for it - m_end_position[E] = line.e() * lengths_scale_factor; - any_found = true; - } - else - simulate_st_synchronize(); - - if (!any_found && !line.has_unknown_axis()) { - // The G92 may be called for axes that PrusaSlicer does not recognize, for example see GH issue #3510, - // where G92 A0 B0 is called although the extruder axis is till E. - for (unsigned char a = X; a <= E; ++a) { - m_origin[a] = m_end_position[a]; - } - } -} - -void GCodeProcessor::process_M1(const GCodeReader::GCodeLine& line) -{ - simulate_st_synchronize(); -} - -void GCodeProcessor::process_M82(const GCodeReader::GCodeLine& line) -{ - m_e_local_positioning_type = EPositioningType::Absolute; -} - -void GCodeProcessor::process_M83(const GCodeReader::GCodeLine& line) -{ - m_e_local_positioning_type = EPositioningType::Relative; -} - -void GCodeProcessor::process_M104(const GCodeReader::GCodeLine& line) -{ - float new_temp; - if (line.has_value('S', new_temp)) - m_extruder_temps[m_extruder_id] = new_temp; -} - -void GCodeProcessor::process_M106(const GCodeReader::GCodeLine& line) -{ - if (!line.has('P')) { - // The absence of P means the print cooling fan, so ignore anything else. - float new_fan_speed; - if (line.has_value('S', new_fan_speed)) - m_fan_speed = (100.0f / 255.0f) * new_fan_speed; - else - m_fan_speed = 100.0f; - } -} - -void GCodeProcessor::process_M107(const GCodeReader::GCodeLine& line) -{ - m_fan_speed = 0.0f; -} - -void GCodeProcessor::process_M108(const GCodeReader::GCodeLine& line) -{ - // These M-codes are used by Sailfish to change active tool. - // They have to be processed otherwise toolchanges will be unrecognised - // by the analyzer - see https://github.com/prusa3d/PrusaSlicer/issues/2566 - - if (m_flavor != gcfSailfish) - return; - - std::string cmd = line.raw(); - size_t pos = cmd.find("T"); - if (pos != std::string::npos) - process_T(cmd.substr(pos)); -} - -void GCodeProcessor::process_M109(const GCodeReader::GCodeLine& line) -{ - float new_temp; - if (line.has_value('R', new_temp)) { - float val; - if (line.has_value('T', val)) { - size_t eid = static_cast(val); - if (eid < m_extruder_temps.size()) - m_extruder_temps[eid] = new_temp; - } - else - m_extruder_temps[m_extruder_id] = new_temp; - } - else if (line.has_value('S', new_temp)) - m_extruder_temps[m_extruder_id] = new_temp; -} - -void GCodeProcessor::process_M132(const GCodeReader::GCodeLine& line) -{ - // This command is used by Makerbot to load the current home position from EEPROM - // see: https://github.com/makerbot/s3g/blob/master/doc/GCodeProtocol.md - // Using this command to reset the axis origin to zero helps in fixing: https://github.com/prusa3d/PrusaSlicer/issues/3082 - - if (line.has('X')) - m_origin[X] = 0.0f; - - if (line.has('Y')) - m_origin[Y] = 0.0f; - - if (line.has('Z')) - m_origin[Z] = 0.0f; - - if (line.has('E')) - m_origin[E] = 0.0f; -} - -void GCodeProcessor::process_M135(const GCodeReader::GCodeLine& line) -{ - // These M-codes are used by MakerWare to change active tool. - // They have to be processed otherwise toolchanges will be unrecognised - // by the analyzer - see https://github.com/prusa3d/PrusaSlicer/issues/2566 - - if (m_flavor != gcfMakerWare) - return; - - std::string cmd = line.raw(); - size_t pos = cmd.find("T"); - if (pos != std::string::npos) - process_T(cmd.substr(pos)); -} - -void GCodeProcessor::process_M201(const GCodeReader::GCodeLine& line) -{ - // see http://reprap.org/wiki/G-code#M201:_Set_max_printing_acceleration - float factor = ((m_flavor != gcfRepRapSprinter && m_flavor != gcfRepRapFirmware) && m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f; - - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - if (static_cast(i) == PrintEstimatedStatistics::ETimeMode::Normal || - m_time_processor.machine_envelope_processing_enabled) { - if (line.has_x()) - set_option_value(m_time_processor.machine_limits.machine_max_acceleration_x, i, line.x() * factor); - - if (line.has_y()) - set_option_value(m_time_processor.machine_limits.machine_max_acceleration_y, i, line.y() * factor); - - if (line.has_z()) - set_option_value(m_time_processor.machine_limits.machine_max_acceleration_z, i, line.z() * factor); - - if (line.has_e()) - set_option_value(m_time_processor.machine_limits.machine_max_acceleration_e, i, line.e() * factor); - } - } -} - -void GCodeProcessor::process_M203(const GCodeReader::GCodeLine& line) -{ - // see http://reprap.org/wiki/G-code#M203:_Set_maximum_feedrate - if (m_flavor == gcfRepetier) - return; - - // see http://reprap.org/wiki/G-code#M203:_Set_maximum_feedrate - // http://smoothieware.org/supported-g-codes - float factor = (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware || m_flavor == gcfSmoothie) ? 1.0f : MMMIN_TO_MMSEC; - - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - if (static_cast(i) == PrintEstimatedStatistics::ETimeMode::Normal || - m_time_processor.machine_envelope_processing_enabled) { - if (line.has_x()) - set_option_value(m_time_processor.machine_limits.machine_max_feedrate_x, i, line.x() * factor); - - if (line.has_y()) - set_option_value(m_time_processor.machine_limits.machine_max_feedrate_y, i, line.y() * factor); - - if (line.has_z()) - set_option_value(m_time_processor.machine_limits.machine_max_feedrate_z, i, line.z() * factor); - - if (line.has_e()) - set_option_value(m_time_processor.machine_limits.machine_max_feedrate_e, i, line.e() * factor); - } - } -} - -void GCodeProcessor::process_M204(const GCodeReader::GCodeLine& line) -{ - float value; - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - if (static_cast(i) == PrintEstimatedStatistics::ETimeMode::Normal || - m_time_processor.machine_envelope_processing_enabled) { - if (line.has_value('S', value)) { - // Legacy acceleration format. This format is used by the legacy Marlin, MK2 or MK3 firmware - // It is also generated by PrusaSlicer to control acceleration per extrusion type - // (perimeters, first layer etc) when 'Marlin (legacy)' flavor is used. - set_acceleration(static_cast(i), value); - set_travel_acceleration(static_cast(i), value); - if (line.has_value('T', value)) - set_retract_acceleration(static_cast(i), value); - } - else { - // New acceleration format, compatible with the upstream Marlin. - if (line.has_value('P', value)) - set_acceleration(static_cast(i), value); - if (line.has_value('R', value)) - set_retract_acceleration(static_cast(i), value); - if (line.has_value('T', value)) - // Interpret the T value as the travel acceleration in the new Marlin format. - set_travel_acceleration(static_cast(i), value); - } - } - } -} - -void GCodeProcessor::process_M205(const GCodeReader::GCodeLine& line) -{ - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - if (static_cast(i) == PrintEstimatedStatistics::ETimeMode::Normal || - m_time_processor.machine_envelope_processing_enabled) { - if (line.has_x()) { - float max_jerk = line.x(); - set_option_value(m_time_processor.machine_limits.machine_max_jerk_x, i, max_jerk); - set_option_value(m_time_processor.machine_limits.machine_max_jerk_y, i, max_jerk); - } - - if (line.has_y()) - set_option_value(m_time_processor.machine_limits.machine_max_jerk_y, i, line.y()); - - if (line.has_z()) - set_option_value(m_time_processor.machine_limits.machine_max_jerk_z, i, line.z()); - - if (line.has_e()) - set_option_value(m_time_processor.machine_limits.machine_max_jerk_e, i, line.e()); - - float value; - if (line.has_value('S', value)) - set_option_value(m_time_processor.machine_limits.machine_min_extruding_rate, i, value); - - if (line.has_value('T', value)) - set_option_value(m_time_processor.machine_limits.machine_min_travel_rate, i, value); - } - } -} - -void GCodeProcessor::process_M221(const GCodeReader::GCodeLine& line) -{ - float value_s; - float value_t; - if (line.has_value('S', value_s) && !line.has_value('T', value_t)) { - value_s *= 0.01f; - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - m_time_processor.machines[i].extrude_factor_override_percentage = value_s; - } - } -} - -void GCodeProcessor::process_M401(const GCodeReader::GCodeLine& line) -{ - if (m_flavor != gcfRepetier) - return; - - for (unsigned char a = 0; a <= 3; ++a) { - m_cached_position.position[a] = m_start_position[a]; - } - m_cached_position.feedrate = m_feedrate; -} - -void GCodeProcessor::process_M402(const GCodeReader::GCodeLine& line) -{ - if (m_flavor != gcfRepetier) - return; - - // see for reference: - // https://github.com/repetier/Repetier-Firmware/blob/master/src/ArduinoAVR/Repetier/Printer.cpp - // void Printer::GoToMemoryPosition(bool x, bool y, bool z, bool e, float feed) - - bool has_xyz = !(line.has('X') || line.has('Y') || line.has('Z')); - - float p = FLT_MAX; - for (unsigned char a = X; a <= Z; ++a) { - if (has_xyz || line.has(a)) { - p = m_cached_position.position[a]; - if (p != FLT_MAX) - m_start_position[a] = p; - } - } - - p = m_cached_position.position[E]; - if (p != FLT_MAX) - m_start_position[E] = p; - - p = FLT_MAX; - if (!line.has_value(4, p)) - p = m_cached_position.feedrate; - - if (p != FLT_MAX) - m_feedrate = p; -} - -void GCodeProcessor::process_M566(const GCodeReader::GCodeLine& line) -{ - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - if (line.has_x()) - set_option_value(m_time_processor.machine_limits.machine_max_jerk_x, i, line.x() * MMMIN_TO_MMSEC); - - if (line.has_y()) - set_option_value(m_time_processor.machine_limits.machine_max_jerk_y, i, line.y() * MMMIN_TO_MMSEC); - - if (line.has_z()) - set_option_value(m_time_processor.machine_limits.machine_max_jerk_z, i, line.z() * MMMIN_TO_MMSEC); - - if (line.has_e()) - set_option_value(m_time_processor.machine_limits.machine_max_jerk_e, i, line.e() * MMMIN_TO_MMSEC); - } -} - -void GCodeProcessor::process_M702(const GCodeReader::GCodeLine& line) -{ - if (line.has('C')) { - // MK3 MMU2 specific M code: - // M702 C is expected to be sent by the custom end G-code when finalizing a print. - // The MK3 unit shall unload and park the active filament into the MMU2 unit. - m_time_processor.extruder_unloaded = true; - simulate_st_synchronize(get_filament_unload_time(m_extruder_id)); - } -} - -void GCodeProcessor::process_T(const GCodeReader::GCodeLine& line) -{ - process_T(line.cmd()); -} - -void GCodeProcessor::process_T(const std::string_view command) -{ - if (command.length() > 1) { - int eid = 0; - if (! parse_number(command.substr(1), eid) || eid < 0 || eid > 255) { - // Specific to the MMU2 V2 (see https://www.help.prusa3d.com/en/article/prusa-specific-g-codes_112173): - if ((m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware) && (command == "Tx" || command == "Tc" || command == "T?")) - return; - - // T-1 is a valid gcode line for RepRap Firmwares (used to deselects all tools) see https://github.com/prusa3d/PrusaSlicer/issues/5677 - if ((m_flavor != gcfRepRapFirmware && m_flavor != gcfRepRapSprinter) || eid != -1) - BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange (" << command << ")."; - } else { - unsigned char id = static_cast(eid); - if (m_extruder_id != id) { - if (id >= m_result.extruders_count) - BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange, maybe from a custom gcode."; - else { - unsigned char old_extruder_id = m_extruder_id; - process_filaments(CustomGCode::ToolChange); - m_extruder_id = id; - m_cp_color.current = m_extruder_colors[id]; - // Specific to the MK3 MMU2: - // The initial value of extruder_unloaded is set to true indicating - // that the filament is parked in the MMU2 unit and there is nothing to be unloaded yet. - float extra_time = get_filament_unload_time(static_cast(old_extruder_id)); - m_time_processor.extruder_unloaded = false; - extra_time += get_filament_load_time(static_cast(m_extruder_id)); - simulate_st_synchronize(extra_time); - } - - // store tool change move - store_move_vertex(EMoveType::Tool_change); - } - } - } -} - -void GCodeProcessor::store_move_vertex(EMoveType type) -{ - m_last_line_id = (type == EMoveType::Color_change || type == EMoveType::Pause_Print || type == EMoveType::Custom_GCode) ? - m_line_id + 1 : - ((type == EMoveType::Seam) ? m_last_line_id : m_line_id); - - m_result.moves.push_back({ - m_last_line_id, - type, - m_extrusion_role, - m_extruder_id, - m_cp_color.current, -#if ENABLE_Z_OFFSET_CORRECTION - Vec3f(m_end_position[X], m_end_position[Y], m_processing_start_custom_gcode ? m_first_layer_height : m_end_position[Z] - m_z_offset) + m_extruder_offsets[m_extruder_id], -#else - Vec3f(m_end_position[X], m_end_position[Y], m_processing_start_custom_gcode ? m_first_layer_height : m_end_position[Z]) + m_extruder_offsets[m_extruder_id], -#endif // ENABLE_Z_OFFSET_CORRECTION - static_cast(m_end_position[E] - m_start_position[E]), - m_feedrate, - m_width, - m_height, - m_mm3_per_mm, - m_fan_speed, - m_extruder_temps[m_extruder_id], - static_cast(m_result.moves.size()) - }); - - // stores stop time placeholders for later use - if (type == EMoveType::Color_change || type == EMoveType::Pause_Print) { - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - TimeMachine& machine = m_time_processor.machines[i]; - if (!machine.enabled) - continue; - - machine.stop_times.push_back({ m_g1_line_id, 0.0f }); - } - } -} - -void GCodeProcessor::set_extrusion_role(ExtrusionRole role) -{ - m_used_filaments.process_role_cache(this); - m_extrusion_role = role; -} - -float GCodeProcessor::minimum_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const -{ - if (m_time_processor.machine_limits.machine_min_extruding_rate.empty()) - return feedrate; - - return std::max(feedrate, get_option_value(m_time_processor.machine_limits.machine_min_extruding_rate, static_cast(mode))); -} - -float GCodeProcessor::minimum_travel_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const -{ - if (m_time_processor.machine_limits.machine_min_travel_rate.empty()) - return feedrate; - - return std::max(feedrate, get_option_value(m_time_processor.machine_limits.machine_min_travel_rate, static_cast(mode))); -} - -float GCodeProcessor::get_axis_max_feedrate(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const -{ - switch (axis) - { - case X: { return get_option_value(m_time_processor.machine_limits.machine_max_feedrate_x, static_cast(mode)); } - case Y: { return get_option_value(m_time_processor.machine_limits.machine_max_feedrate_y, static_cast(mode)); } - case Z: { return get_option_value(m_time_processor.machine_limits.machine_max_feedrate_z, static_cast(mode)); } - case E: { return get_option_value(m_time_processor.machine_limits.machine_max_feedrate_e, static_cast(mode)); } - default: { return 0.0f; } - } -} - -float GCodeProcessor::get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const -{ - switch (axis) - { - case X: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_x, static_cast(mode)); } - case Y: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_y, static_cast(mode)); } - case Z: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_z, static_cast(mode)); } - case E: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_e, static_cast(mode)); } - default: { return 0.0f; } - } -} - -float GCodeProcessor::get_axis_max_jerk(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const -{ - switch (axis) - { - case X: { return get_option_value(m_time_processor.machine_limits.machine_max_jerk_x, static_cast(mode)); } - case Y: { return get_option_value(m_time_processor.machine_limits.machine_max_jerk_y, static_cast(mode)); } - case Z: { return get_option_value(m_time_processor.machine_limits.machine_max_jerk_z, static_cast(mode)); } - case E: { return get_option_value(m_time_processor.machine_limits.machine_max_jerk_e, static_cast(mode)); } - default: { return 0.0f; } - } -} - -float GCodeProcessor::get_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode) const -{ - size_t id = static_cast(mode); - return (id < m_time_processor.machines.size()) ? m_time_processor.machines[id].retract_acceleration : DEFAULT_RETRACT_ACCELERATION; -} - -void GCodeProcessor::set_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value) -{ - size_t id = static_cast(mode); - if (id < m_time_processor.machines.size()) { - m_time_processor.machines[id].retract_acceleration = (m_time_processor.machines[id].max_retract_acceleration == 0.0f) ? value : - // Clamp the acceleration with the maximum. - std::min(value, m_time_processor.machines[id].max_retract_acceleration); - } -} - -float GCodeProcessor::get_acceleration(PrintEstimatedStatistics::ETimeMode mode) const -{ - size_t id = static_cast(mode); - return (id < m_time_processor.machines.size()) ? m_time_processor.machines[id].acceleration : DEFAULT_ACCELERATION; -} - -void GCodeProcessor::set_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value) -{ - size_t id = static_cast(mode); - if (id < m_time_processor.machines.size()) { - m_time_processor.machines[id].acceleration = (m_time_processor.machines[id].max_acceleration == 0.0f) ? value : - // Clamp the acceleration with the maximum. - std::min(value, m_time_processor.machines[id].max_acceleration); - } -} - -float GCodeProcessor::get_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode) const -{ - size_t id = static_cast(mode); - return (id < m_time_processor.machines.size()) ? m_time_processor.machines[id].travel_acceleration : DEFAULT_TRAVEL_ACCELERATION; -} - -void GCodeProcessor::set_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value) -{ - size_t id = static_cast(mode); - if (id < m_time_processor.machines.size()) { - m_time_processor.machines[id].travel_acceleration = (m_time_processor.machines[id].max_travel_acceleration == 0.0f) ? value : - // Clamp the acceleration with the maximum. - std::min(value, m_time_processor.machines[id].max_travel_acceleration); - } -} - -float GCodeProcessor::get_filament_load_time(size_t extruder_id) -{ - return (m_time_processor.filament_load_times.empty() || m_time_processor.extruder_unloaded) ? - 0.0f : - ((extruder_id < m_time_processor.filament_load_times.size()) ? - m_time_processor.filament_load_times[extruder_id] : m_time_processor.filament_load_times.front()); -} - -float GCodeProcessor::get_filament_unload_time(size_t extruder_id) -{ - return (m_time_processor.filament_unload_times.empty() || m_time_processor.extruder_unloaded) ? - 0.0f : - ((extruder_id < m_time_processor.filament_unload_times.size()) ? - m_time_processor.filament_unload_times[extruder_id] : m_time_processor.filament_unload_times.front()); -} - -void GCodeProcessor::process_custom_gcode_time(CustomGCode::Type code) -{ - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - TimeMachine& machine = m_time_processor.machines[i]; - if (!machine.enabled) - continue; - - TimeMachine::CustomGCodeTime& gcode_time = machine.gcode_time; - gcode_time.needed = true; - //FIXME this simulates st_synchronize! is it correct? - // The estimated time may be longer than the real print time. - machine.simulate_st_synchronize(); - if (gcode_time.cache != 0.0f) { - gcode_time.times.push_back({ code, gcode_time.cache }); - gcode_time.cache = 0.0f; - } - } -} - -void GCodeProcessor::process_filaments(CustomGCode::Type code) -{ - if (code == CustomGCode::ColorChange) - m_used_filaments.process_color_change_cache(); - - if (code == CustomGCode::ToolChange) - m_used_filaments.process_extruder_cache(this->m_extruder_id); -} - -void GCodeProcessor::simulate_st_synchronize(float additional_time) -{ - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - m_time_processor.machines[i].simulate_st_synchronize(additional_time); - } -} - -void GCodeProcessor::update_estimated_times_stats() -{ - auto update_mode = [this](PrintEstimatedStatistics::ETimeMode mode) { - PrintEstimatedStatistics::Mode& data = m_result.print_statistics.modes[static_cast(mode)]; - data.time = get_time(mode); - data.custom_gcode_times = get_custom_gcode_times(mode, true); - data.moves_times = get_moves_time(mode); - data.roles_times = get_roles_time(mode); - data.layers_times = get_layers_time(mode); - }; - - update_mode(PrintEstimatedStatistics::ETimeMode::Normal); - if (m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].enabled) - update_mode(PrintEstimatedStatistics::ETimeMode::Stealth); - else - m_result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].reset(); - - m_result.print_statistics.volumes_per_color_change = m_used_filaments.volumes_per_color_change; - m_result.print_statistics.volumes_per_extruder = m_used_filaments.volumes_per_extruder; - m_result.print_statistics.used_filaments_per_role = m_used_filaments.filaments_per_role; -} - -} /* namespace Slic3r */ - ->>>>>>> master_250 diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 2c5d3e538..08e70f2f5 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -1,4 +1,3 @@ -<<<<<<< HEAD #ifndef slic3r_GCodeProcessor_hpp_ #define slic3r_GCodeProcessor_hpp_ @@ -815,761 +814,3 @@ namespace Slic3r { #endif /* slic3r_GCodeProcessor_hpp_ */ -======= -#ifndef slic3r_GCodeProcessor_hpp_ -#define slic3r_GCodeProcessor_hpp_ - -#include "libslic3r/GCodeReader.hpp" -#include "libslic3r/Point.hpp" -#include "libslic3r/ExtrusionEntity.hpp" -#include "libslic3r/PrintConfig.hpp" -#include "libslic3r/CustomGCode.hpp" - -#include -#include -#include -#include -#include -#include - -namespace Slic3r { - - enum class EMoveType : unsigned char - { - Noop, - Retract, - Unretract, - Seam, - Tool_change, - Color_change, - Pause_Print, - Custom_GCode, - Travel, - Wipe, - Extrude, - Count - }; - - struct PrintEstimatedStatistics - { - enum class ETimeMode : unsigned char - { - Normal, - Stealth, - Count - }; - - struct Mode - { - float time; - std::vector>> custom_gcode_times; - std::vector> moves_times; - std::vector> roles_times; - std::vector layers_times; - - void reset() { - time = 0.0f; - custom_gcode_times.clear(); - moves_times.clear(); - roles_times.clear(); - layers_times.clear(); - } - }; - - std::vector volumes_per_color_change; - std::map volumes_per_extruder; - std::map> used_filaments_per_role; - - std::array(ETimeMode::Count)> modes; - - PrintEstimatedStatistics() { reset(); } - - void reset() { - for (auto m : modes) { - m.reset(); - } - volumes_per_color_change.clear(); - volumes_per_extruder.clear(); - used_filaments_per_role.clear(); - } - }; - - struct GCodeProcessorResult - { - struct SettingsIds - { - std::string print; - std::vector filament; - std::string printer; - - void reset() { - print.clear(); - filament.clear(); - printer.clear(); - } - }; - - struct MoveVertex - { - unsigned int gcode_id{ 0 }; - EMoveType type{ EMoveType::Noop }; - ExtrusionRole extrusion_role{ erNone }; - unsigned char extruder_id{ 0 }; - unsigned char cp_color_id{ 0 }; - Vec3f position{ Vec3f::Zero() }; // mm - float delta_extruder{ 0.0f }; // mm - float feedrate{ 0.0f }; // mm/s - float width{ 0.0f }; // mm - float height{ 0.0f }; // mm - float mm3_per_mm{ 0.0f }; - float fan_speed{ 0.0f }; // percentage - float temperature{ 0.0f }; // Celsius degrees - float time{ 0.0f }; // s - - float volumetric_rate() const { return feedrate * mm3_per_mm; } - }; - - std::string filename; - unsigned int id; - std::vector moves; - // Positions of ends of lines of the final G-code this->filename after TimeProcessor::post_process() finalizes the G-code. - std::vector lines_ends; - Pointfs bed_shape; - float max_print_height; - SettingsIds settings_ids; - size_t extruders_count; - std::vector extruder_colors; - std::vector filament_diameters; - std::vector filament_densities; - PrintEstimatedStatistics print_statistics; - std::vector custom_gcode_per_print_z; -#if ENABLE_SPIRAL_VASE_LAYERS - std::vector>> spiral_vase_layers; -#endif // ENABLE_SPIRAL_VASE_LAYERS - -#if ENABLE_GCODE_VIEWER_STATISTICS - int64_t time{ 0 }; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - void reset(); - }; - - - class GCodeProcessor - { - static const std::vector Reserved_Tags; - - public: - enum class ETags : unsigned char - { - Role, - Wipe_Start, - Wipe_End, - Height, - Width, - Layer_Change, - Color_Change, - Pause_Print, - Custom_Code, - First_Line_M73_Placeholder, - Last_Line_M73_Placeholder, - Estimated_Printing_Time_Placeholder - }; - - static const std::string& reserved_tag(ETags tag) { return Reserved_Tags[static_cast(tag)]; } - // checks the given gcode for reserved tags and returns true when finding the 1st (which is returned into found_tag) - static bool contains_reserved_tag(const std::string& gcode, std::string& found_tag); - // checks the given gcode for reserved tags and returns true when finding any - // (the first max_count found tags are returned into found_tag) - static bool contains_reserved_tags(const std::string& gcode, unsigned int max_count, std::vector& found_tag); - - static const float Wipe_Width; - static const float Wipe_Height; - -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - static const std::string Mm3_Per_Mm_Tag; -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - - private: - using AxisCoords = std::array; - using ExtruderColors = std::vector; - using ExtruderTemps = std::vector; - - enum class EUnits : unsigned char - { - Millimeters, - Inches - }; - - enum class EPositioningType : unsigned char - { - Absolute, - Relative - }; - - struct CachedPosition - { - AxisCoords position; // mm - float feedrate; // mm/s - - void reset(); - }; - - struct CpColor - { - unsigned char counter; - unsigned char current; - - void reset(); - }; - - public: - struct FeedrateProfile - { - float entry{ 0.0f }; // mm/s - float cruise{ 0.0f }; // mm/s - float exit{ 0.0f }; // mm/s - }; - - struct Trapezoid - { - float accelerate_until{ 0.0f }; // mm - float decelerate_after{ 0.0f }; // mm - float cruise_feedrate{ 0.0f }; // mm/sec - - float acceleration_time(float entry_feedrate, float acceleration) const; - float cruise_time() const; - float deceleration_time(float distance, float acceleration) const; - float cruise_distance() const; - }; - - struct TimeBlock - { - struct Flags - { - bool recalculate{ false }; - bool nominal_length{ false }; - }; - - EMoveType move_type{ EMoveType::Noop }; - ExtrusionRole role{ erNone }; - unsigned int g1_line_id{ 0 }; - unsigned int layer_id{ 0 }; - float distance{ 0.0f }; // mm - float acceleration{ 0.0f }; // mm/s^2 - float max_entry_speed{ 0.0f }; // mm/s - float safe_feedrate{ 0.0f }; // mm/s - Flags flags; - FeedrateProfile feedrate_profile; - Trapezoid trapezoid; - - // Calculates this block's trapezoid - void calculate_trapezoid(); - - float time() const; - }; - - private: - struct TimeMachine - { - struct State - { - float feedrate; // mm/s - float safe_feedrate; // mm/s - AxisCoords axis_feedrate; // mm/s - AxisCoords abs_axis_feedrate; // mm/s - - void reset(); - }; - - struct CustomGCodeTime - { - bool needed; - float cache; - std::vector> times; - - void reset(); - }; - - struct G1LinesCacheItem - { - unsigned int id; - float elapsed_time; - }; - - bool enabled; - float acceleration; // mm/s^2 - // hard limit for the acceleration, to which the firmware will clamp. - float max_acceleration; // mm/s^2 - float retract_acceleration; // mm/s^2 - // hard limit for the acceleration, to which the firmware will clamp. - float max_retract_acceleration; // mm/s^2 - float travel_acceleration; // mm/s^2 - // hard limit for the travel acceleration, to which the firmware will clamp. - float max_travel_acceleration; // mm/s^2 - float extrude_factor_override_percentage; - float time; // s - struct StopTime - { - unsigned int g1_line_id; - float elapsed_time; - }; - std::vector stop_times; - std::string line_m73_main_mask; - std::string line_m73_stop_mask; - State curr; - State prev; - CustomGCodeTime gcode_time; - std::vector blocks; - std::vector g1_times_cache; - std::array(EMoveType::Count)> moves_time; - std::array(ExtrusionRole::erCount)> roles_time; - std::vector layers_time; - - void reset(); - - // Simulates firmware st_synchronize() call - void simulate_st_synchronize(float additional_time = 0.0f); - void calculate_time(size_t keep_last_n_blocks = 0, float additional_time = 0.0f); - }; - - struct TimeProcessor - { - struct Planner - { - // Size of the firmware planner queue. The old 8-bit Marlins usually just managed 16 trapezoidal blocks. - // Let's be conservative and plan for newer boards with more memory. - static constexpr size_t queue_size = 64; - // The firmware recalculates last planner_queue_size trapezoidal blocks each time a new block is added. - // We are not simulating the firmware exactly, we calculate a sequence of blocks once a reasonable number of blocks accumulate. - static constexpr size_t refresh_threshold = queue_size * 4; - }; - - // extruder_id is currently used to correctly calculate filament load / unload times into the total print time. - // This is currently only really used by the MK3 MMU2: - // extruder_unloaded = true means no filament is loaded yet, all the filaments are parked in the MK3 MMU2 unit. - bool extruder_unloaded; - // whether or not to export post-process the gcode to export lines M73 in it - bool export_remaining_time_enabled; - // allow to skip the lines M201/M203/M204/M205 generated by GCode::print_machine_envelope() for non-Normal time estimate mode - bool machine_envelope_processing_enabled; - MachineEnvelopeConfig machine_limits; - // Additional load / unload times for a filament exchange sequence. - std::vector filament_load_times; - std::vector filament_unload_times; - std::array(PrintEstimatedStatistics::ETimeMode::Count)> machines; - - void reset(); - - // post process the file with the given filename to add remaining time lines M73 - // and updates moves' gcode ids accordingly - void post_process(const std::string& filename, std::vector& moves, std::vector& lines_ends); - }; - - struct UsedFilaments // filaments per ColorChange - { - double color_change_cache; - std::vector volumes_per_color_change; - - double tool_change_cache; - std::map volumes_per_extruder; - - double role_cache; - std::map> filaments_per_role; // ExtrusionRole -> (m, g) - - - void reset(); - - void increase_caches(double extruded_volume, unsigned char extruder_id, double parking_volume, double extra_loading_volume); - - void process_color_change_cache(); - void process_extruder_cache(unsigned char extruder_id); - void process_role_cache(const GCodeProcessor* processor); - void process_caches(const GCodeProcessor* processor); - private: - std::vector extruder_retracted_volume; - bool recent_toolchange = false; - }; - - public: - class SeamsDetector - { - bool m_active{ false }; - std::optional m_first_vertex; - - public: - void activate(bool active) { - if (m_active != active) { - m_active = active; - if (m_active) - m_first_vertex.reset(); - } - } - - std::optional get_first_vertex() const { return m_first_vertex; } - void set_first_vertex(const Vec3f& vertex) { m_first_vertex = vertex; } - - bool is_active() const { return m_active; } - bool has_first_vertex() const { return m_first_vertex.has_value(); } - }; - - // Helper class used to fix the z for color change, pause print and - // custom gcode markes - class OptionsZCorrector - { - GCodeProcessorResult& m_result; - std::optional m_move_id; - std::optional m_custom_gcode_per_print_z_id; - - public: - explicit OptionsZCorrector(GCodeProcessorResult& result) : m_result(result) { - } - - void set() { - m_move_id = m_result.moves.size() - 1; - m_custom_gcode_per_print_z_id = m_result.custom_gcode_per_print_z.size() - 1; - } - - void update(float height) { - if (!m_move_id.has_value() || !m_custom_gcode_per_print_z_id.has_value()) - return; - - const Vec3f position = m_result.moves.back().position; - - GCodeProcessorResult::MoveVertex& move = m_result.moves.emplace_back(m_result.moves[*m_move_id]); - move.position = position; - move.height = height; - m_result.moves.erase(m_result.moves.begin() + *m_move_id); - m_result.custom_gcode_per_print_z[*m_custom_gcode_per_print_z_id].print_z = position.z(); - reset(); - } - - void reset() { - m_move_id.reset(); - m_custom_gcode_per_print_z_id.reset(); - } - }; - -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - struct DataChecker - { - struct Error - { - float value; - float tag_value; - ExtrusionRole role; - }; - - std::string type; - float threshold{ 0.01f }; - float last_tag_value{ 0.0f }; - unsigned int count{ 0 }; - std::vector errors; - - DataChecker(const std::string& type, float threshold) - : type(type), threshold(threshold) - {} - - void update(float value, ExtrusionRole role) { - if (role != erCustom) { - ++count; - if (last_tag_value != 0.0f) { - if (std::abs(value - last_tag_value) / last_tag_value > threshold) - errors.push_back({ value, last_tag_value, role }); - } - } - } - - void reset() { last_tag_value = 0.0f; errors.clear(); count = 0; } - - std::pair get_min() const { - float delta_min = FLT_MAX; - float perc_min = 0.0f; - for (const Error& e : errors) { - if (delta_min > e.value - e.tag_value) { - delta_min = e.value - e.tag_value; - perc_min = 100.0f * delta_min / e.tag_value; - } - } - return { delta_min, perc_min }; - } - - std::pair get_max() const { - float delta_max = -FLT_MAX; - float perc_max = 0.0f; - for (const Error& e : errors) { - if (delta_max < e.value - e.tag_value) { - delta_max = e.value - e.tag_value; - perc_max = 100.0f * delta_max / e.tag_value; - } - } - return { delta_max, perc_max }; - } - - void output() const { - if (!errors.empty()) { - std::cout << type << ":\n"; - std::cout << "Errors: " << errors.size() << " (" << 100.0f * float(errors.size()) / float(count) << "%)\n"; - auto [min, perc_min] = get_min(); - auto [max, perc_max] = get_max(); - std::cout << "min: " << min << "(" << perc_min << "%) - max: " << max << "(" << perc_max << "%)\n"; - } - } - }; -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - - private: - GCodeReader m_parser; - - EUnits m_units; - EPositioningType m_global_positioning_type; - EPositioningType m_e_local_positioning_type; - std::vector m_extruder_offsets; - GCodeFlavor m_flavor; - - AxisCoords m_start_position; // mm - AxisCoords m_end_position; // mm - AxisCoords m_origin; // mm - CachedPosition m_cached_position; - bool m_wiping; - - unsigned int m_line_id; - unsigned int m_last_line_id; - float m_feedrate; // mm/s - float m_width; // mm - float m_height; // mm - float m_forced_width; // mm - float m_forced_height; // mm - float m_mm3_per_mm; - float m_fan_speed; // percentage -#if ENABLE_Z_OFFSET_CORRECTION - float m_z_offset; // mm -#endif // ENABLE_Z_OFFSET_CORRECTION - ExtrusionRole m_extrusion_role; - unsigned char m_extruder_id; - ExtruderColors m_extruder_colors; - ExtruderTemps m_extruder_temps; - float m_parking_position; - float m_extra_loading_move; - float m_extruded_last_z; - float m_first_layer_height; // mm - bool m_processing_start_custom_gcode; - unsigned int m_g1_line_id; - unsigned int m_layer_id; - CpColor m_cp_color; - bool m_use_volumetric_e; - SeamsDetector m_seams_detector; - OptionsZCorrector m_options_z_corrector; - size_t m_last_default_color_id; -#if ENABLE_SPIRAL_VASE_LAYERS - bool m_spiral_vase_active; -#endif // ENABLE_SPIRAL_VASE_LAYERS -#if ENABLE_GCODE_VIEWER_STATISTICS - std::chrono::time_point m_start_time; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - enum class EProducer - { - Unknown, - PrusaSlicer, - Slic3rPE, - Slic3r, - SuperSlicer, - Cura, - Simplify3D, - CraftWare, - ideaMaker, - KissSlicer - }; - - static const std::vector> Producers; - EProducer m_producer; - - TimeProcessor m_time_processor; - UsedFilaments m_used_filaments; - - GCodeProcessorResult m_result; - static unsigned int s_result_id; - -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - DataChecker m_mm3_per_mm_compare{ "mm3_per_mm", 0.01f }; - DataChecker m_height_compare{ "height", 0.01f }; - DataChecker m_width_compare{ "width", 0.01f }; -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - - public: - GCodeProcessor(); - - void apply_config(const PrintConfig& config); - void enable_stealth_time_estimator(bool enabled); - bool is_stealth_time_estimator_enabled() const { - return m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].enabled; - } - void enable_machine_envelope_processing(bool enabled) { m_time_processor.machine_envelope_processing_enabled = enabled; } - void reset(); - - const GCodeProcessorResult& get_result() const { return m_result; } - GCodeProcessorResult&& extract_result() { return std::move(m_result); } - - // Load a G-code into a stand-alone G-code viewer. - // throws CanceledException through print->throw_if_canceled() (sent by the caller as callback). - void process_file(const std::string& filename, std::function cancel_callback = nullptr); - - // Streaming interface, for processing G-codes just generated by PrusaSlicer in a pipelined fashion. - void initialize(const std::string& filename); - void process_buffer(const std::string& buffer); - void finalize(bool post_process); - - float get_time(PrintEstimatedStatistics::ETimeMode mode) const; - std::string get_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const; - std::vector>> get_custom_gcode_times(PrintEstimatedStatistics::ETimeMode mode, bool include_remaining) const; - - std::vector> get_moves_time(PrintEstimatedStatistics::ETimeMode mode) const; - std::vector> get_roles_time(PrintEstimatedStatistics::ETimeMode mode) const; - std::vector get_layers_time(PrintEstimatedStatistics::ETimeMode mode) const; - - private: - void apply_config(const DynamicPrintConfig& config); - void apply_config_simplify3d(const std::string& filename); - void apply_config_superslicer(const std::string& filename); - void process_gcode_line(const GCodeReader::GCodeLine& line, bool producers_enabled); - - // Process tags embedded into comments - void process_tags(const std::string_view comment, bool producers_enabled); - bool process_producers_tags(const std::string_view comment); - bool process_prusaslicer_tags(const std::string_view comment); - bool process_cura_tags(const std::string_view comment); - bool process_simplify3d_tags(const std::string_view comment); - bool process_craftware_tags(const std::string_view comment); - bool process_ideamaker_tags(const std::string_view comment); - bool process_kissslicer_tags(const std::string_view comment); - - bool detect_producer(const std::string_view comment); - - // Move - void process_G0(const GCodeReader::GCodeLine& line); - void process_G1(const GCodeReader::GCodeLine& line); - - // Retract - void process_G10(const GCodeReader::GCodeLine& line); - - // Unretract - void process_G11(const GCodeReader::GCodeLine& line); - - // Set Units to Inches - void process_G20(const GCodeReader::GCodeLine& line); - - // Set Units to Millimeters - void process_G21(const GCodeReader::GCodeLine& line); - - // Firmware controlled Retract - void process_G22(const GCodeReader::GCodeLine& line); - - // Firmware controlled Unretract - void process_G23(const GCodeReader::GCodeLine& line); - - // Move to origin - void process_G28(const GCodeReader::GCodeLine& line); - - // Set to Absolute Positioning - void process_G90(const GCodeReader::GCodeLine& line); - - // Set to Relative Positioning - void process_G91(const GCodeReader::GCodeLine& line); - - // Set Position - void process_G92(const GCodeReader::GCodeLine& line); - - // Sleep or Conditional stop - void process_M1(const GCodeReader::GCodeLine& line); - - // Set extruder to absolute mode - void process_M82(const GCodeReader::GCodeLine& line); - - // Set extruder to relative mode - void process_M83(const GCodeReader::GCodeLine& line); - - // Set extruder temperature - void process_M104(const GCodeReader::GCodeLine& line); - - // Set fan speed - void process_M106(const GCodeReader::GCodeLine& line); - - // Disable fan - void process_M107(const GCodeReader::GCodeLine& line); - - // Set tool (Sailfish) - void process_M108(const GCodeReader::GCodeLine& line); - - // Set extruder temperature and wait - void process_M109(const GCodeReader::GCodeLine& line); - - // Recall stored home offsets - void process_M132(const GCodeReader::GCodeLine& line); - - // Set tool (MakerWare) - void process_M135(const GCodeReader::GCodeLine& line); - - // Set max printing acceleration - void process_M201(const GCodeReader::GCodeLine& line); - - // Set maximum feedrate - void process_M203(const GCodeReader::GCodeLine& line); - - // Set default acceleration - void process_M204(const GCodeReader::GCodeLine& line); - - // Advanced settings - void process_M205(const GCodeReader::GCodeLine& line); - - // Set extrude factor override percentage - void process_M221(const GCodeReader::GCodeLine& line); - - // Repetier: Store x, y and z position - void process_M401(const GCodeReader::GCodeLine& line); - - // Repetier: Go to stored position - void process_M402(const GCodeReader::GCodeLine& line); - - // Set allowable instantaneous speed change - void process_M566(const GCodeReader::GCodeLine& line); - - // Unload the current filament into the MK3 MMU2 unit at the end of print. - void process_M702(const GCodeReader::GCodeLine& line); - - // Processes T line (Select Tool) - void process_T(const GCodeReader::GCodeLine& line); - void process_T(const std::string_view command); - - void store_move_vertex(EMoveType type); - - void set_extrusion_role(ExtrusionRole role); - - float minimum_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const; - float minimum_travel_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const; - float get_axis_max_feedrate(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const; - float get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const; - float get_axis_max_jerk(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const; - float get_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode) const; - void set_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value); - float get_acceleration(PrintEstimatedStatistics::ETimeMode mode) const; - void set_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value); - float get_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode) const; - void set_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value); - float get_filament_load_time(size_t extruder_id); - float get_filament_unload_time(size_t extruder_id); - - void process_custom_gcode_time(CustomGCode::Type code); - void process_filaments(CustomGCode::Type code); - - // Simulates firmware st_synchronize() call - void simulate_st_synchronize(float additional_time = 0.0f); - - void update_estimated_times_stats(); - }; - -} /* namespace Slic3r */ - -#endif /* slic3r_GCodeProcessor_hpp_ */ - - ->>>>>>> master_250 diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 0a92543cf..ec071673b 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -1,4 +1,3 @@ -<<<<<<< HEAD #ifndef slic3r_Point_hpp_ #define slic3r_Point_hpp_ @@ -568,569 +567,3 @@ const T* end(const Slic3r::Mat &mat) { return mat.data() + N * M; } } // namespace Eigen #endif -======= -#ifndef slic3r_Point_hpp_ -#define slic3r_Point_hpp_ - -#include "libslic3r.h" -#include -#include -#include -#include -#include -#include - -#include - -#include "LocalesUtils.hpp" - -namespace Slic3r { - -class BoundingBox; -class BoundingBoxf; -class Line; -class MultiPoint; -class Point; -using Vector = Point; - -// Base template for eigen derived vectors -template -using Mat = Eigen::Matrix; - -template using Vec = Mat; - -// Eigen types, to replace the Slic3r's own types in the future. -// Vector types with a fixed point coordinate base type. -using Vec2crd = Eigen::Matrix; -using Vec3crd = Eigen::Matrix; -using Vec2i = Eigen::Matrix; -using Vec3i = Eigen::Matrix; -using Vec4i = Eigen::Matrix; -using Vec2i32 = Eigen::Matrix; -using Vec2i64 = Eigen::Matrix; -using Vec3i32 = Eigen::Matrix; -using Vec3i64 = Eigen::Matrix; - -// Vector types with a double coordinate base type. -using Vec2f = Eigen::Matrix; -using Vec3f = Eigen::Matrix; -using Vec2d = Eigen::Matrix; -using Vec3d = Eigen::Matrix; - -using Points = std::vector; -using PointPtrs = std::vector; -using PointConstPtrs = std::vector; -using Points3 = std::vector; -using Pointfs = std::vector; -using Vec2ds = std::vector; -using Pointf3s = std::vector; - -using Matrix2f = Eigen::Matrix; -using Matrix2d = Eigen::Matrix; -using Matrix3f = Eigen::Matrix; -using Matrix3d = Eigen::Matrix; -using Matrix4f = Eigen::Matrix; -using Matrix4d = Eigen::Matrix; - -template -using Transform = Eigen::Transform; - -using Transform2f = Eigen::Transform; -using Transform2d = Eigen::Transform; -using Transform3f = Eigen::Transform; -using Transform3d = Eigen::Transform; - -// I don't know why Eigen::Transform::Identity() return a const object... -template Transform identity() { return Transform::Identity(); } -inline const auto &identity3f = identity<3, float>; -inline const auto &identity3d = identity<3, double>; - -inline bool operator<(const Vec2d &lhs, const Vec2d &rhs) { return lhs.x() < rhs.x() || (lhs.x() == rhs.x() && lhs.y() < rhs.y()); } - -template -int32_t cross2(const Eigen::MatrixBase> &v1, const Eigen::MatrixBase> &v2) = delete; - -template -inline T cross2(const Eigen::MatrixBase> &v1, const Eigen::MatrixBase> &v2) -{ - return v1.x() * v2.y() - v1.y() * v2.x(); -} - -template -inline typename Derived::Scalar cross2(const Eigen::MatrixBase &v1, const Eigen::MatrixBase &v2) -{ - static_assert(std::is_same::value, "cross2(): Scalar types of 1st and 2nd operand must be equal."); - return v1.x() * v2.y() - v1.y() * v2.x(); -} - -template -inline Eigen::Matrix perp(const Eigen::MatrixBase> &v) { return Eigen::Matrix(- v.y(), v.x()); } - -template -Eigen::Matrix to_2d(const Eigen::MatrixBase> &ptN) { return { ptN.x(), ptN.y() }; } - -template -Eigen::Matrix to_3d(const Eigen::MatrixBase> & pt, const T z) { return { pt.x(), pt.y(), z }; } - -inline Vec2d unscale(coord_t x, coord_t y) { return Vec2d(unscale(x), unscale(y)); } -inline Vec2d unscale(const Vec2crd &pt) { return Vec2d(unscale(pt.x()), unscale(pt.y())); } -inline Vec2d unscale(const Vec2d &pt) { return Vec2d(unscale(pt.x()), unscale(pt.y())); } -inline Vec3d unscale(coord_t x, coord_t y, coord_t z) { return Vec3d(unscale(x), unscale(y), unscale(z)); } -inline Vec3d unscale(const Vec3crd &pt) { return Vec3d(unscale(pt.x()), unscale(pt.y()), unscale(pt.z())); } -inline Vec3d unscale(const Vec3d &pt) { return Vec3d(unscale(pt.x()), unscale(pt.y()), unscale(pt.z())); } - -inline std::string to_string(const Vec2crd &pt) { return std::string("[") + float_to_string_decimal_point(pt.x()) + ", " + float_to_string_decimal_point(pt.y()) + "]"; } -inline std::string to_string(const Vec2d &pt) { return std::string("[") + float_to_string_decimal_point(pt.x()) + ", " + float_to_string_decimal_point(pt.y()) + "]"; } -inline std::string to_string(const Vec3crd &pt) { return std::string("[") + float_to_string_decimal_point(pt.x()) + ", " + float_to_string_decimal_point(pt.y()) + ", " + float_to_string_decimal_point(pt.z()) + "]"; } -inline std::string to_string(const Vec3d &pt) { return std::string("[") + float_to_string_decimal_point(pt.x()) + ", " + float_to_string_decimal_point(pt.y()) + ", " + float_to_string_decimal_point(pt.z()) + "]"; } - -std::vector transform(const std::vector& points, const Transform3f& t); -Pointf3s transform(const Pointf3s& points, const Transform3d& t); - -template using Vec = Eigen::Matrix; - -class Point : public Vec2crd -{ -public: - using coord_type = coord_t; - - Point() : Vec2crd(0, 0) {} - Point(int32_t x, int32_t y) : Vec2crd(coord_t(x), coord_t(y)) {} - Point(int64_t x, int64_t y) : Vec2crd(coord_t(x), coord_t(y)) {} - Point(double x, double y) : Vec2crd(coord_t(lrint(x)), coord_t(lrint(y))) {} - Point(const Point &rhs) { *this = rhs; } - explicit Point(const Vec2d& rhs) : Vec2crd(coord_t(lrint(rhs.x())), coord_t(lrint(rhs.y()))) {} - // This constructor allows you to construct Point from Eigen expressions - template - Point(const Eigen::MatrixBase &other) : Vec2crd(other) {} - static Point new_scale(coordf_t x, coordf_t y) { return Point(coord_t(scale_(x)), coord_t(scale_(y))); } - static Point new_scale(const Vec2d &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); } - static Point new_scale(const Vec2f &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); } - - // This method allows you to assign Eigen expressions to MyVectorType - template - Point& operator=(const Eigen::MatrixBase &other) - { - this->Vec2crd::operator=(other); - return *this; - } - - Point& operator+=(const Point& rhs) { this->x() += rhs.x(); this->y() += rhs.y(); return *this; } - Point& operator-=(const Point& rhs) { this->x() -= rhs.x(); this->y() -= rhs.y(); return *this; } - Point& operator*=(const double &rhs) { this->x() = coord_t(this->x() * rhs); this->y() = coord_t(this->y() * rhs); return *this; } - Point operator*(const double &rhs) { return Point(this->x() * rhs, this->y() * rhs); } - - void rotate(double angle) { this->rotate(std::cos(angle), std::sin(angle)); } - void rotate(double cos_a, double sin_a) { - double cur_x = (double)this->x(); - double cur_y = (double)this->y(); - this->x() = (coord_t)round(cos_a * cur_x - sin_a * cur_y); - this->y() = (coord_t)round(cos_a * cur_y + sin_a * cur_x); - } - - void rotate(double angle, const Point ¢er); - Point rotated(double angle) const { Point res(*this); res.rotate(angle); return res; } - Point rotated(double cos_a, double sin_a) const { Point res(*this); res.rotate(cos_a, sin_a); return res; } - Point rotated(double angle, const Point ¢er) const { Point res(*this); res.rotate(angle, center); return res; } - Point rotate_90_degree_ccw() const { return Point(-this->y(), this->x()); } - int nearest_point_index(const Points &points) const; - int nearest_point_index(const PointConstPtrs &points) const; - int nearest_point_index(const PointPtrs &points) const; - bool nearest_point(const Points &points, Point* point) const; - double ccw(const Point &p1, const Point &p2) const; - double ccw(const Line &line) const; - double ccw_angle(const Point &p1, const Point &p2) const; - Point projection_onto(const MultiPoint &poly) const; - Point projection_onto(const Line &line) const; -}; - -inline bool operator<(const Point &l, const Point &r) -{ - return l.x() < r.x() || (l.x() == r.x() && l.y() < r.y()); -} - -inline Point operator* (const Point& l, const double &r) -{ - return {coord_t(l.x() * r), coord_t(l.y() * r)}; -} - -inline bool is_approx(const Point &p1, const Point &p2, coord_t epsilon = coord_t(SCALED_EPSILON)) -{ - Point d = (p2 - p1).cwiseAbs(); - return d.x() < epsilon && d.y() < epsilon; -} - -inline bool is_approx(const Vec2f &p1, const Vec2f &p2, float epsilon = float(EPSILON)) -{ - Vec2f d = (p2 - p1).cwiseAbs(); - return d.x() < epsilon && d.y() < epsilon; -} - -inline bool is_approx(const Vec2d &p1, const Vec2d &p2, double epsilon = EPSILON) -{ - Vec2d d = (p2 - p1).cwiseAbs(); - return d.x() < epsilon && d.y() < epsilon; -} - -inline bool is_approx(const Vec3f &p1, const Vec3f &p2, float epsilon = float(EPSILON)) -{ - Vec3f d = (p2 - p1).cwiseAbs(); - return d.x() < epsilon && d.y() < epsilon && d.z() < epsilon; -} - -inline bool is_approx(const Vec3d &p1, const Vec3d &p2, double epsilon = EPSILON) -{ - Vec3d d = (p2 - p1).cwiseAbs(); - return d.x() < epsilon && d.y() < epsilon && d.z() < epsilon; -} - -inline Point lerp(const Point &a, const Point &b, double t) -{ - assert((t >= -EPSILON) && (t <= 1. + EPSILON)); - return ((1. - t) * a.cast() + t * b.cast()).cast(); -} - -BoundingBox get_extents(const Points &pts); -BoundingBox get_extents(const std::vector &pts); -BoundingBoxf get_extents(const std::vector &pts); - -// Test for duplicate points in a vector of points. -// The points are copied, sorted and checked for duplicates globally. -bool has_duplicate_points(std::vector &&pts); -inline bool has_duplicate_points(const std::vector &pts) -{ - std::vector cpy = pts; - return has_duplicate_points(std::move(cpy)); -} - -// Test for duplicate points in a vector of points. -// Only successive points are checked for equality. -inline bool has_duplicate_successive_points(const std::vector &pts) -{ - for (size_t i = 1; i < pts.size(); ++ i) - if (pts[i - 1] == pts[i]) - return true; - return false; -} - -// Test for duplicate points in a vector of points. -// Only successive points are checked for equality. Additionally, first and last points are compared for equality. -inline bool has_duplicate_successive_points_closed(const std::vector &pts) -{ - return has_duplicate_successive_points(pts) || (pts.size() >= 2 && pts.front() == pts.back()); -} - -inline bool shorter_then(const Point& p0, const coord_t len) -{ - if (p0.x() > len || p0.x() < -len) - return false; - if (p0.y() > len || p0.y() < -len) - return false; - return p0.cast().squaredNorm() <= Slic3r::sqr(int64_t(len)); -} - -namespace int128 { - // Exact orientation predicate, - // returns +1: CCW, 0: collinear, -1: CW. - int orient(const Vec2crd &p1, const Vec2crd &p2, const Vec2crd &p3); - // Exact orientation predicate, - // returns +1: CCW, 0: collinear, -1: CW. - int cross(const Vec2crd &v1, const Vec2crd &v2); -} - -// To be used by std::unordered_map, std::unordered_multimap and friends. -struct PointHash { - size_t operator()(const Vec2crd &pt) const { - return coord_t((89 * 31 + int64_t(pt.x())) * 31 + pt.y()); - } -}; - -// A generic class to search for a closest Point in a given radius. -// It uses std::unordered_multimap to implement an efficient 2D spatial hashing. -// The PointAccessor has to return const Point*. -// If a nullptr is returned, it is ignored by the query. -template class ClosestPointInRadiusLookup -{ -public: - ClosestPointInRadiusLookup(coord_t search_radius, PointAccessor point_accessor = PointAccessor()) : - m_search_radius(search_radius), m_point_accessor(point_accessor), m_grid_log2(0) - { - // Resolution of a grid, twice the search radius + some epsilon. - coord_t gridres = 2 * m_search_radius + 4; - m_grid_resolution = gridres; - assert(m_grid_resolution > 0); - assert(m_grid_resolution < (coord_t(1) << 30)); - // Compute m_grid_log2 = log2(m_grid_resolution) - if (m_grid_resolution > 32767) { - m_grid_resolution >>= 16; - m_grid_log2 += 16; - } - if (m_grid_resolution > 127) { - m_grid_resolution >>= 8; - m_grid_log2 += 8; - } - if (m_grid_resolution > 7) { - m_grid_resolution >>= 4; - m_grid_log2 += 4; - } - if (m_grid_resolution > 1) { - m_grid_resolution >>= 2; - m_grid_log2 += 2; - } - if (m_grid_resolution > 0) - ++ m_grid_log2; - m_grid_resolution = 1 << m_grid_log2; - assert(m_grid_resolution >= gridres); - assert(gridres > m_grid_resolution / 2); - } - - void insert(const ValueType &value) { - const Vec2crd *pt = m_point_accessor(value); - if (pt != nullptr) - m_map.emplace(std::make_pair(Vec2crd(pt->x()>>m_grid_log2, pt->y()>>m_grid_log2), value)); - } - - void insert(ValueType &&value) { - const Vec2crd *pt = m_point_accessor(value); - if (pt != nullptr) - m_map.emplace(std::make_pair(Vec2crd(pt->x()>>m_grid_log2, pt->y()>>m_grid_log2), std::move(value))); - } - - // Erase a data point equal to value. (ValueType has to declare the operator==). - // Returns true if the data point equal to value was found and removed. - bool erase(const ValueType &value) { - const Point *pt = m_point_accessor(value); - if (pt != nullptr) { - // Range of fragment starts around grid_corner, close to pt. - auto range = m_map.equal_range(Point((*pt).x()>>m_grid_log2, (*pt).y()>>m_grid_log2)); - // Remove the first item. - for (auto it = range.first; it != range.second; ++ it) { - if (it->second == value) { - m_map.erase(it); - return true; - } - } - } - return false; - } - - // Return a pair of - std::pair find(const Vec2crd &pt) { - // Iterate over 4 closest grid cells around pt, - // find the closest start point inside these cells to pt. - const ValueType *value_min = nullptr; - double dist_min = std::numeric_limits::max(); - // Round pt to a closest grid_cell corner. - Vec2crd grid_corner((pt.x()+(m_grid_resolution>>1))>>m_grid_log2, (pt.y()+(m_grid_resolution>>1))>>m_grid_log2); - // For four neighbors of grid_corner: - for (coord_t neighbor_y = -1; neighbor_y < 1; ++ neighbor_y) { - for (coord_t neighbor_x = -1; neighbor_x < 1; ++ neighbor_x) { - // Range of fragment starts around grid_corner, close to pt. - auto range = m_map.equal_range(Vec2crd(grid_corner.x() + neighbor_x, grid_corner.y() + neighbor_y)); - // Find the map entry closest to pt. - for (auto it = range.first; it != range.second; ++it) { - const ValueType &value = it->second; - const Vec2crd *pt2 = m_point_accessor(value); - if (pt2 != nullptr) { - const double d2 = (pt - *pt2).cast().squaredNorm(); - if (d2 < dist_min) { - dist_min = d2; - value_min = &value; - } - } - } - } - } - return (value_min != nullptr && dist_min < coordf_t(m_search_radius) * coordf_t(m_search_radius)) ? - std::make_pair(value_min, dist_min) : - std::make_pair(nullptr, std::numeric_limits::max()); - } - - // Returns all pairs of values and squared distances. - std::vector> find_all(const Vec2crd &pt) { - // Iterate over 4 closest grid cells around pt, - // Round pt to a closest grid_cell corner. - Vec2crd grid_corner((pt.x()+(m_grid_resolution>>1))>>m_grid_log2, (pt.y()+(m_grid_resolution>>1))>>m_grid_log2); - // For four neighbors of grid_corner: - std::vector> out; - const double r2 = double(m_search_radius) * m_search_radius; - for (coord_t neighbor_y = -1; neighbor_y < 1; ++ neighbor_y) { - for (coord_t neighbor_x = -1; neighbor_x < 1; ++ neighbor_x) { - // Range of fragment starts around grid_corner, close to pt. - auto range = m_map.equal_range(Vec2crd(grid_corner.x() + neighbor_x, grid_corner.y() + neighbor_y)); - // Find the map entry closest to pt. - for (auto it = range.first; it != range.second; ++it) { - const ValueType &value = it->second; - const Vec2crd *pt2 = m_point_accessor(value); - if (pt2 != nullptr) { - const double d2 = (pt - *pt2).cast().squaredNorm(); - if (d2 <= r2) - out.emplace_back(&value, d2); - } - } - } - } - return out; - } - -private: - using map_type = typename std::unordered_multimap; - PointAccessor m_point_accessor; - map_type m_map; - coord_t m_search_radius; - coord_t m_grid_resolution; - coord_t m_grid_log2; -}; - -std::ostream& operator<<(std::ostream &stm, const Vec2d &pointf); - - -// ///////////////////////////////////////////////////////////////////////////// -// Type safe conversions to and from scaled and unscaled coordinates -// ///////////////////////////////////////////////////////////////////////////// - -// Semantics are the following: -// Upscaling (scaled()): only from floating point types (or Vec) to either -// floating point or integer 'scaled coord' coordinates. -// Downscaling (unscaled()): from arithmetic (or Vec) to floating point only - -// Conversion definition from unscaled to floating point scaled -template> -inline constexpr FloatingOnly scaled(const Tin &v) noexcept -{ - return Tout(v / Tin(SCALING_FACTOR)); -} - -// Conversion definition from unscaled to integer 'scaled coord'. -// TODO: is the rounding necessary? Here it is commented out to show that -// it can be different for integers but it does not have to be. Using -// std::round means loosing noexcept and constexpr modifiers -template> -inline constexpr ScaledCoordOnly scaled(const Tin &v) noexcept -{ - //return static_cast(std::round(v / SCALING_FACTOR)); - return Tout(v / Tin(SCALING_FACTOR)); -} - -// Conversion for Eigen vectors (N dimensional points) -template, - int...EigenArgs> -inline Eigen::Matrix, N, EigenArgs...> -scaled(const Eigen::Matrix &v) -{ - return (v / SCALING_FACTOR).template cast(); -} - -// Conversion from arithmetic scaled type to floating point unscaled -template, - class = FloatingOnly> -inline constexpr Tout unscaled(const Tin &v) noexcept -{ - return Tout(v) * Tout(SCALING_FACTOR); -} - -// Unscaling for Eigen vectors. Input base type can be arithmetic, output base -// type can only be floating point. -template, - class = FloatingOnly, - int...EigenArgs> -inline constexpr Eigen::Matrix -unscaled(const Eigen::Matrix &v) noexcept -{ - return v.template cast() * Tout(SCALING_FACTOR); -} - -// Align a coordinate to a grid. The coordinate may be negative, -// the aligned value will never be bigger than the original one. -inline coord_t align_to_grid(const coord_t coord, const coord_t spacing) { - // Current C++ standard defines the result of integer division to be rounded to zero, - // for both positive and negative numbers. Here we want to round down for negative - // numbers as well. - coord_t aligned = (coord < 0) ? - ((coord - spacing + 1) / spacing) * spacing : - (coord / spacing) * spacing; - assert(aligned <= coord); - return aligned; -} -inline Point align_to_grid(Point coord, Point spacing) - { return Point(align_to_grid(coord.x(), spacing.x()), align_to_grid(coord.y(), spacing.y())); } -inline coord_t align_to_grid(coord_t coord, coord_t spacing, coord_t base) - { return base + align_to_grid(coord - base, spacing); } -inline Point align_to_grid(Point coord, Point spacing, Point base) - { return Point(align_to_grid(coord.x(), spacing.x(), base.x()), align_to_grid(coord.y(), spacing.y(), base.y())); } - -} // namespace Slic3r - -// start Boost -#include -#include -namespace boost { namespace polygon { - template <> - struct geometry_concept { using type = point_concept; }; - - template <> - struct point_traits { - using coordinate_type = coord_t; - - static inline coordinate_type get(const Slic3r::Point& point, orientation_2d orient) { - return static_cast(point((orient == HORIZONTAL) ? 0 : 1)); - } - }; - - template <> - struct point_mutable_traits { - using coordinate_type = coord_t; - static inline void set(Slic3r::Point& point, orientation_2d orient, coord_t value) { - point((orient == HORIZONTAL) ? 0 : 1) = value; - } - static inline Slic3r::Point construct(coord_t x_value, coord_t y_value) { - return Slic3r::Point(x_value, y_value); - } - }; -} } -// end Boost - -// Serialization through the Cereal library -namespace cereal { -// template void serialize(Archive& archive, Slic3r::Vec2crd &v) { archive(v.x(), v.y()); } -// template void serialize(Archive& archive, Slic3r::Vec3crd &v) { archive(v.x(), v.y(), v.z()); } - template void serialize(Archive& archive, Slic3r::Vec2i &v) { archive(v.x(), v.y()); } - template void serialize(Archive& archive, Slic3r::Vec3i &v) { archive(v.x(), v.y(), v.z()); } -// template void serialize(Archive& archive, Slic3r::Vec2i64 &v) { archive(v.x(), v.y()); } -// template void serialize(Archive& archive, Slic3r::Vec3i64 &v) { archive(v.x(), v.y(), v.z()); } - template void serialize(Archive& archive, Slic3r::Vec2f &v) { archive(v.x(), v.y()); } - template void serialize(Archive& archive, Slic3r::Vec3f &v) { archive(v.x(), v.y(), v.z()); } - template void serialize(Archive& archive, Slic3r::Vec2d &v) { archive(v.x(), v.y()); } - template void serialize(Archive& archive, Slic3r::Vec3d &v) { archive(v.x(), v.y(), v.z()); } - - template void load(Archive& archive, Slic3r::Matrix2f &m) { archive.loadBinary((char*)m.data(), sizeof(float) * 4); } - template void save(Archive& archive, Slic3r::Matrix2f &m) { archive.saveBinary((char*)m.data(), sizeof(float) * 4); } -} - -// To be able to use Vec<> and Mat<> in range based for loops: -namespace Eigen { -template -T* begin(Slic3r::Mat &mat) { return mat.data(); } - -template -T* end(Slic3r::Mat &mat) { return mat.data() + N * M; } - -template -const T* begin(const Slic3r::Mat &mat) { return mat.data(); } - -template -const T* end(const Slic3r::Mat &mat) { return mat.data() + N * M; } -} // namespace Eigen - -#endif ->>>>>>> master_250 diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index b13d02d12..7a24d3572 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -1,4 +1,4 @@ -<<<<<<< HEAD +<<<<<<< HEAD // FIXME: extract absolute units -> em #include "ConfigWizard_private.hpp" @@ -3077,3077 +3077,3 @@ void ConfigWizard::on_sys_color_changed() } } -======= -// FIXME: extract absolute units -> em - -#include "ConfigWizard_private.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef _MSW_DARK_MODE -#include -#endif // _MSW_DARK_MODE - -#include "libslic3r/Platform.hpp" -#include "libslic3r/Utils.hpp" -#include "libslic3r/Config.hpp" -#include "libslic3r/libslic3r.h" -#include "libslic3r/Model.hpp" -#include "GUI.hpp" -#include "GUI_App.hpp" -#include "GUI_Utils.hpp" -#include "GUI_ObjectManipulation.hpp" -#include "Field.hpp" -#include "DesktopIntegrationDialog.hpp" -#include "slic3r/Config/Snapshot.hpp" -#include "slic3r/Utils/PresetUpdater.hpp" -#include "format.hpp" -#include "MsgDialog.hpp" -#include "UnsavedChangesDialog.hpp" - -#if defined(__linux__) && defined(__WXGTK3__) -#define wxLinux_gtk3 true -#else -#define wxLinux_gtk3 false -#endif //defined(__linux__) && defined(__WXGTK3__) - -namespace Slic3r { -namespace GUI { - - -using Config::Snapshot; -using Config::SnapshotDB; - - -// Configuration data structures extensions needed for the wizard - -bool Bundle::load(fs::path source_path, bool ais_in_resources, bool ais_prusa_bundle) -{ - this->preset_bundle = std::make_unique(); - this->is_in_resources = ais_in_resources; - this->is_prusa_bundle = ais_prusa_bundle; - - std::string path_string = source_path.string(); - // Throw when parsing invalid configuration. Only valid configuration is supposed to be provided over the air. - auto [config_substitutions, presets_loaded] = preset_bundle->load_configbundle( - path_string, PresetBundle::LoadConfigBundleAttribute::LoadSystem, ForwardCompatibilitySubstitutionRule::Disable); - UNUSED(config_substitutions); - // No substitutions shall be reported when loading a system config bundle, no substitutions are allowed. - assert(config_substitutions.empty()); - auto first_vendor = preset_bundle->vendors.begin(); - if (first_vendor == preset_bundle->vendors.end()) { - BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No vendor information defined, cannot install.") % path_string; - return false; - } - if (presets_loaded == 0) { - BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No profile loaded.") % path_string; - return false; - } - - BOOST_LOG_TRIVIAL(trace) << boost::format("Vendor bundle: `%1%`: %2% profiles loaded.") % path_string % presets_loaded; - this->vendor_profile = &first_vendor->second; - return true; -} - -Bundle::Bundle(Bundle &&other) - : preset_bundle(std::move(other.preset_bundle)) - , vendor_profile(other.vendor_profile) - , is_in_resources(other.is_in_resources) - , is_prusa_bundle(other.is_prusa_bundle) -{ - other.vendor_profile = nullptr; -} - -BundleMap BundleMap::load() -{ - BundleMap res; - - const auto vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / "vendor").make_preferred(); - const auto rsrc_vendor_dir = (boost::filesystem::path(resources_dir()) / "profiles").make_preferred(); - - auto prusa_bundle_path = (vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini"); - auto prusa_bundle_rsrc = false; - if (! boost::filesystem::exists(prusa_bundle_path)) { - prusa_bundle_path = (rsrc_vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini"); - prusa_bundle_rsrc = true; - } - { - Bundle prusa_bundle; - if (prusa_bundle.load(std::move(prusa_bundle_path), prusa_bundle_rsrc, true)) - res.emplace(PresetBundle::PRUSA_BUNDLE, std::move(prusa_bundle)); - } - - // Load the other bundles in the datadir/vendor directory - // and then additionally from resources/profiles. - bool is_in_resources = false; - for (auto dir : { &vendor_dir, &rsrc_vendor_dir }) { - for (const auto &dir_entry : boost::filesystem::directory_iterator(*dir)) { - if (Slic3r::is_ini_file(dir_entry)) { - std::string id = dir_entry.path().stem().string(); // stem() = filename() without the trailing ".ini" part - - // Don't load this bundle if we've already loaded it. - if (res.find(id) != res.end()) { continue; } - - Bundle bundle; - if (bundle.load(dir_entry.path(), is_in_resources)) - res.emplace(std::move(id), std::move(bundle)); - } - } - - is_in_resources = true; - } - - return res; -} - -Bundle& BundleMap::prusa_bundle() -{ - auto it = find(PresetBundle::PRUSA_BUNDLE); - if (it == end()) { - throw Slic3r::RuntimeError("ConfigWizard: Internal error in BundleMap: PRUSA_BUNDLE not loaded"); - } - - return it->second; -} - -const Bundle& BundleMap::prusa_bundle() const -{ - return const_cast(this)->prusa_bundle(); -} - - -// Printer model picker GUI control - -struct PrinterPickerEvent : public wxEvent -{ - std::string vendor_id; - std::string model_id; - std::string variant_name; - bool enable; - - PrinterPickerEvent(wxEventType eventType, int winid, std::string vendor_id, std::string model_id, std::string variant_name, bool enable) - : wxEvent(winid, eventType) - , vendor_id(std::move(vendor_id)) - , model_id(std::move(model_id)) - , variant_name(std::move(variant_name)) - , enable(enable) - {} - - virtual wxEvent *Clone() const - { - return new PrinterPickerEvent(*this); - } -}; - -wxDEFINE_EVENT(EVT_PRINTER_PICK, PrinterPickerEvent); - -const std::string PrinterPicker::PRINTER_PLACEHOLDER = "printer_placeholder.png"; - -PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig, const ModelFilter &filter) - : wxPanel(parent) - , vendor_id(vendor.id) - , width(0) -{ - wxGetApp().UpdateDarkUI(this); - const auto &models = vendor.models; - - auto *sizer = new wxBoxSizer(wxVERTICAL); - - const auto font_title = GetFont().MakeBold().Scaled(1.3f); - const auto font_name = GetFont().MakeBold(); - const auto font_alt_nozzle = GetFont().Scaled(0.9f); - - // wxGrid appends widgets by rows, but we need to construct them in columns. - // These vectors are used to hold the elements so that they can be appended in the right order. - std::vector titles; - std::vector bitmaps; - std::vector variants_panels; - - int max_row_width = 0; - int current_row_width = 0; - - bool is_variants = false; - - for (const auto &model : models) { - if (! filter(model)) { continue; } - - wxBitmap bitmap; - int bitmap_width = 0; - auto load_bitmap = [](const wxString& bitmap_file, wxBitmap& bitmap, int& bitmap_width)->bool { - if (wxFileExists(bitmap_file)) { - bitmap.LoadFile(bitmap_file, wxBITMAP_TYPE_PNG); - bitmap_width = bitmap.GetWidth(); - return true; - } - return false; - }; - if (!load_bitmap(GUI::from_u8(Slic3r::data_dir() + "/vendor/" + vendor.id + "/" + model.id + "_thumbnail.png"), bitmap, bitmap_width)) { - if (!load_bitmap(GUI::from_u8(Slic3r::resources_dir() + "/profiles/" + vendor.id + "/" + model.id + "_thumbnail.png"), bitmap, bitmap_width)) { - BOOST_LOG_TRIVIAL(warning) << boost::format("Can't find bitmap file `%1%` for vendor `%2%`, printer `%3%`, using placeholder icon instead") - % (Slic3r::resources_dir() + "/profiles/" + vendor.id + "/" + model.id + "_thumbnail.png") - % vendor.id - % model.id; - load_bitmap(Slic3r::var(PRINTER_PLACEHOLDER), bitmap, bitmap_width); - } - } - auto *title = new wxStaticText(this, wxID_ANY, from_u8(model.name), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); - title->SetFont(font_name); - const int wrap_width = std::max((int)MODEL_MIN_WRAP, bitmap_width); - title->Wrap(wrap_width); - - current_row_width += wrap_width; - if (titles.size() % max_cols == max_cols - 1) { - max_row_width = std::max(max_row_width, current_row_width); - current_row_width = 0; - } - - titles.push_back(title); - - auto *bitmap_widget = new wxStaticBitmap(this, wxID_ANY, bitmap); - bitmaps.push_back(bitmap_widget); - - auto *variants_panel = new wxPanel(this); - wxGetApp().UpdateDarkUI(variants_panel); - auto *variants_sizer = new wxBoxSizer(wxVERTICAL); - variants_panel->SetSizer(variants_sizer); - const auto model_id = model.id; - - for (size_t i = 0; i < model.variants.size(); i++) { - const auto &variant = model.variants[i]; - - const auto label = model.technology == ptFFF - ? from_u8((boost::format("%1% %2% %3%") % variant.name % _utf8(L("mm")) % _utf8(L("nozzle"))).str()) - : from_u8(model.name); - - if (i == 1) { - auto *alt_label = new wxStaticText(variants_panel, wxID_ANY, _L("Alternate nozzles:")); - alt_label->SetFont(font_alt_nozzle); - variants_sizer->Add(alt_label, 0, wxBOTTOM, 3); - is_variants = true; - } - - auto *cbox = new Checkbox(variants_panel, label, model_id, variant.name); - i == 0 ? cboxes.push_back(cbox) : cboxes_alt.push_back(cbox); - - const bool enabled = appconfig.get_variant(vendor.id, model_id, variant.name); - cbox->SetValue(enabled); - - variants_sizer->Add(cbox, 0, wxBOTTOM, 3); - - cbox->Bind(wxEVT_CHECKBOX, [this, cbox](wxCommandEvent &event) { - on_checkbox(cbox, event.IsChecked()); - }); - } - - variants_panels.push_back(variants_panel); - } - - width = std::max(max_row_width, current_row_width); - - const size_t cols = std::min(max_cols, titles.size()); - - auto *printer_grid = new wxFlexGridSizer(cols, 0, 20); - printer_grid->SetFlexibleDirection(wxVERTICAL | wxHORIZONTAL); - - if (titles.size() > 0) { - const size_t odd_items = titles.size() % cols; - - for (size_t i = 0; i < titles.size() - odd_items; i += cols) { - for (size_t j = i; j < i + cols; j++) { printer_grid->Add(bitmaps[j], 0, wxBOTTOM, 20); } - for (size_t j = i; j < i + cols; j++) { printer_grid->Add(titles[j], 0, wxBOTTOM, 3); } - for (size_t j = i; j < i + cols; j++) { printer_grid->Add(variants_panels[j]); } - - // Add separator space to multiliners - if (titles.size() > cols) { - for (size_t j = i; j < i + cols; j++) { printer_grid->Add(1, 30); } - } - } - if (odd_items > 0) { - const size_t rem = titles.size() - odd_items; - - for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(bitmaps[i], 0, wxBOTTOM, 20); } - for (size_t i = 0; i < cols - odd_items; i++) { printer_grid->AddSpacer(1); } - for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(titles[i], 0, wxBOTTOM, 3); } - for (size_t i = 0; i < cols - odd_items; i++) { printer_grid->AddSpacer(1); } - for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(variants_panels[i]); } - } - } - - auto *title_sizer = new wxBoxSizer(wxHORIZONTAL); - if (! title.IsEmpty()) { - auto *title_widget = new wxStaticText(this, wxID_ANY, title); - title_widget->SetFont(font_title); - title_sizer->Add(title_widget); - } - title_sizer->AddStretchSpacer(); - - if (titles.size() > 1 || is_variants) { - // It only makes sense to add the All / None buttons if there's multiple printers - // All Standard button is added when there are more variants for at least one printer - auto *sel_all_std = new wxButton(this, wxID_ANY, titles.size() > 1 ? _L("All standard") : _L("Standard")); - auto *sel_all = new wxButton(this, wxID_ANY, _L("All")); - auto *sel_none = new wxButton(this, wxID_ANY, _L("None")); - if (is_variants) - sel_all_std->Bind(wxEVT_BUTTON, [this](const wxCommandEvent& event) { this->select_all(true, false); }); - sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(true, true); }); - sel_none->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(false); }); - if (is_variants) - title_sizer->Add(sel_all_std, 0, wxRIGHT, BTN_SPACING); - title_sizer->Add(sel_all, 0, wxRIGHT, BTN_SPACING); - title_sizer->Add(sel_none); - - wxGetApp().UpdateDarkUI(sel_all_std); - wxGetApp().UpdateDarkUI(sel_all); - wxGetApp().UpdateDarkUI(sel_none); - - // fill button indexes used later for buttons rescaling - if (is_variants) - m_button_indexes = { sel_all_std->GetId(), sel_all->GetId(), sel_none->GetId() }; - else { - sel_all_std->Destroy(); - m_button_indexes = { sel_all->GetId(), sel_none->GetId() }; - } - } - - sizer->Add(title_sizer, 0, wxEXPAND | wxBOTTOM, BTN_SPACING); - sizer->Add(printer_grid); - - SetSizer(sizer); -} - -PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig) - : PrinterPicker(parent, vendor, std::move(title), max_cols, appconfig, [](const VendorProfile::PrinterModel&) { return true; }) -{} - -void PrinterPicker::select_all(bool select, bool alternates) -{ - for (const auto &cb : cboxes) { - if (cb->GetValue() != select) { - cb->SetValue(select); - on_checkbox(cb, select); - } - } - - if (! select) { alternates = false; } - - for (const auto &cb : cboxes_alt) { - if (cb->GetValue() != alternates) { - cb->SetValue(alternates); - on_checkbox(cb, alternates); - } - } -} - -void PrinterPicker::select_one(size_t i, bool select) -{ - if (i < cboxes.size() && cboxes[i]->GetValue() != select) { - cboxes[i]->SetValue(select); - on_checkbox(cboxes[i], select); - } -} - -bool PrinterPicker::any_selected() const -{ - for (const auto &cb : cboxes) { - if (cb->GetValue()) { return true; } - } - - for (const auto &cb : cboxes_alt) { - if (cb->GetValue()) { return true; } - } - - return false; -} - -std::set PrinterPicker::get_selected_models() const -{ - std::set ret_set; - - for (const auto& cb : cboxes) - if (cb->GetValue()) - ret_set.emplace(cb->model); - - for (const auto& cb : cboxes_alt) - if (cb->GetValue()) - ret_set.emplace(cb->model); - - return ret_set; -} - -void PrinterPicker::on_checkbox(const Checkbox *cbox, bool checked) -{ - PrinterPickerEvent evt(EVT_PRINTER_PICK, GetId(), vendor_id, cbox->model, cbox->variant, checked); - AddPendingEvent(evt); -} - - -// Wizard page base - -ConfigWizardPage::ConfigWizardPage(ConfigWizard *parent, wxString title, wxString shortname, unsigned indent) - : wxPanel(parent->p->hscroll) - , parent(parent) - , shortname(std::move(shortname)) - , indent(indent) -{ - wxGetApp().UpdateDarkUI(this); - - auto *sizer = new wxBoxSizer(wxVERTICAL); - - auto *text = new wxStaticText(this, wxID_ANY, std::move(title), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); - const auto font = GetFont().MakeBold().Scaled(1.5); - text->SetFont(font); - sizer->Add(text, 0, wxALIGN_LEFT, 0); - sizer->AddSpacer(10); - - content = new wxBoxSizer(wxVERTICAL); - sizer->Add(content, 1, wxEXPAND); - - SetSizer(sizer); - - // There is strange layout on Linux with GTK3, - // see https://github.com/prusa3d/PrusaSlicer/issues/5103 and https://github.com/prusa3d/PrusaSlicer/issues/4861 - // So, non-active pages will be hidden later, on wxEVT_SHOW, after completed Layout() for all pages - if (!wxLinux_gtk3) - this->Hide(); - - Bind(wxEVT_SIZE, [this](wxSizeEvent &event) { - this->Layout(); - event.Skip(); - }); -} - -ConfigWizardPage::~ConfigWizardPage() {} - -wxStaticText* ConfigWizardPage::append_text(wxString text) -{ - auto *widget = new wxStaticText(this, wxID_ANY, text, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); - widget->Wrap(WRAP_WIDTH); - widget->SetMinSize(wxSize(WRAP_WIDTH, -1)); - append(widget); - return widget; -} - -void ConfigWizardPage::append_spacer(int space) -{ - // FIXME: scaling - content->AddSpacer(space); -} - -// Wizard pages - -PageWelcome::PageWelcome(ConfigWizard *parent) - : ConfigWizardPage(parent, from_u8((boost::format( -#ifdef __APPLE__ - _utf8(L("Welcome to the %s Configuration Assistant")) -#else - _utf8(L("Welcome to the %s Configuration Wizard")) -#endif - ) % SLIC3R_APP_NAME).str()), _L("Welcome")) - , welcome_text(append_text(from_u8((boost::format( - _utf8(L("Hello, welcome to %s! This %s helps you with the initial configuration; just a few settings and you will be ready to print."))) - % SLIC3R_APP_NAME - % _utf8(ConfigWizard::name())).str()) - )) - , cbox_reset(append( - new wxCheckBox(this, wxID_ANY, _L("Remove user profiles (a snapshot will be taken beforehand)")) - )) - , cbox_integrate(append( - new wxCheckBox(this, wxID_ANY, _L("Perform desktop integration (Sets this binary to be searchable by the system).")) - )) -{ - welcome_text->Hide(); - cbox_reset->Hide(); - cbox_integrate->Hide(); -} - -void PageWelcome::set_run_reason(ConfigWizard::RunReason run_reason) -{ - const bool data_empty = run_reason == ConfigWizard::RR_DATA_EMPTY; - welcome_text->Show(data_empty); - cbox_reset->Show(!data_empty); -#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) - if (!DesktopIntegrationDialog::is_integrated()) - cbox_integrate->Show(true); - else - cbox_integrate->Hide(); -#else - cbox_integrate->Hide(); -#endif -} - - -PagePrinters::PagePrinters(ConfigWizard *parent, - wxString title, - wxString shortname, - const VendorProfile &vendor, - unsigned indent, - Technology technology) - : ConfigWizardPage(parent, std::move(title), std::move(shortname), indent) - , technology(technology) - , install(false) // only used for 3rd party vendors -{ - enum { - COL_SIZE = 200, - }; - - AppConfig *appconfig = &this->wizard_p()->appconfig_new; - - const auto families = vendor.families(); - for (const auto &family : families) { - const auto filter = [&](const VendorProfile::PrinterModel &model) { - return ((model.technology == ptFFF && technology & T_FFF) - || (model.technology == ptSLA && technology & T_SLA)) - && model.family == family; - }; - - if (std::find_if(vendor.models.begin(), vendor.models.end(), filter) == vendor.models.end()) { - continue; - } - - const auto picker_title = family.empty() ? wxString() : from_u8((boost::format(_utf8(L("%s Family"))) % family).str()); - auto *picker = new PrinterPicker(this, vendor, picker_title, MAX_COLS, *appconfig, filter); - - picker->Bind(EVT_PRINTER_PICK, [this, appconfig](const PrinterPickerEvent &evt) { - appconfig->set_variant(evt.vendor_id, evt.model_id, evt.variant_name, evt.enable); - wizard_p()->on_printer_pick(this, evt); - }); - - append(new StaticLine(this)); - - append(picker); - printer_pickers.push_back(picker); - has_printers = true; - } - -} - -void PagePrinters::select_all(bool select, bool alternates) -{ - for (auto picker : printer_pickers) { - picker->select_all(select, alternates); - } -} - -int PagePrinters::get_width() const -{ - return std::accumulate(printer_pickers.begin(), printer_pickers.end(), 0, - [](int acc, const PrinterPicker *picker) { return std::max(acc, picker->get_width()); }); -} - -bool PagePrinters::any_selected() const -{ - for (const auto *picker : printer_pickers) { - if (picker->any_selected()) { return true; } - } - - return false; -} - -std::set PagePrinters::get_selected_models() -{ - std::set ret_set; - - for (const auto *picker : printer_pickers) - { - std::set tmp_models = picker->get_selected_models(); - ret_set.insert(tmp_models.begin(), tmp_models.end()); - } - - return ret_set; -} - -void PagePrinters::set_run_reason(ConfigWizard::RunReason run_reason) -{ - if (is_primary_printer_page - && (run_reason == ConfigWizard::RR_DATA_EMPTY || run_reason == ConfigWizard::RR_DATA_LEGACY) - && printer_pickers.size() > 0 - && printer_pickers[0]->vendor_id == PresetBundle::PRUSA_BUNDLE) { - printer_pickers[0]->select_one(0, true); - } -} - - -const std::string PageMaterials::EMPTY; - -PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxString title, wxString shortname, wxString list1name) - : ConfigWizardPage(parent, std::move(title), std::move(shortname)) - , materials(materials) - , list_printer(new StringList(this, wxLB_MULTIPLE)) - , list_type(new StringList(this)) - , list_vendor(new StringList(this)) - , list_profile(new PresetList(this)) -{ - append_spacer(VERTICAL_SPACING); - - const int em = parent->em_unit(); - const int list_h = 30*em; - - - list_printer->SetMinSize(wxSize(23*em, list_h)); - list_type->SetMinSize(wxSize(13*em, list_h)); - list_vendor->SetMinSize(wxSize(13*em, list_h)); - list_profile->SetMinSize(wxSize(23*em, list_h)); - - - - grid = new wxFlexGridSizer(4, em/2, em); - grid->AddGrowableCol(3, 1); - grid->AddGrowableRow(1, 1); - - grid->Add(new wxStaticText(this, wxID_ANY, _L("Printer:"))); - grid->Add(new wxStaticText(this, wxID_ANY, list1name)); - grid->Add(new wxStaticText(this, wxID_ANY, _L("Vendor:"))); - grid->Add(new wxStaticText(this, wxID_ANY, _L("Profile:"))); - - grid->Add(list_printer, 0, wxEXPAND); - grid->Add(list_type, 0, wxEXPAND); - grid->Add(list_vendor, 0, wxEXPAND); - grid->Add(list_profile, 1, wxEXPAND); - - auto *btn_sizer = new wxBoxSizer(wxHORIZONTAL); - auto *sel_all = new wxButton(this, wxID_ANY, _L("All")); - auto *sel_none = new wxButton(this, wxID_ANY, _L("None")); - btn_sizer->Add(sel_all, 0, wxRIGHT, em / 2); - btn_sizer->Add(sel_none); - - wxGetApp().UpdateDarkUI(list_printer); - wxGetApp().UpdateDarkUI(list_type); - wxGetApp().UpdateDarkUI(list_vendor); - wxGetApp().UpdateDarkUI(sel_all); - wxGetApp().UpdateDarkUI(sel_none); - - grid->Add(new wxBoxSizer(wxHORIZONTAL)); - grid->Add(new wxBoxSizer(wxHORIZONTAL)); - grid->Add(new wxBoxSizer(wxHORIZONTAL)); - grid->Add(btn_sizer, 0, wxALIGN_RIGHT); - - append(grid, 1, wxEXPAND); - - append_spacer(VERTICAL_SPACING); - - html_window = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, - wxSize(60 * em, 20 * em), wxHW_SCROLLBAR_AUTO); - append(html_window, 0, wxEXPAND); - - list_printer->Bind(wxEVT_LISTBOX, [this](wxCommandEvent& evt) { - update_lists(list_type->GetSelection(), list_vendor->GetSelection(), evt.GetInt()); - }); - list_type->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) { - update_lists(list_type->GetSelection(), list_vendor->GetSelection()); - }); - list_vendor->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) { - update_lists(list_type->GetSelection(), list_vendor->GetSelection()); - }); - - list_profile->Bind(wxEVT_CHECKLISTBOX, [this](wxCommandEvent &evt) { select_material(evt.GetInt()); }); - list_profile->Bind(wxEVT_LISTBOX, [this](wxCommandEvent& evt) { on_material_highlighted(evt.GetInt()); }); - - sel_all->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { select_all(true); }); - sel_none->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { select_all(false); }); - /* - Bind(wxEVT_PAINT, [this](wxPaintEvent& evt) {on_paint();}); - - list_profile->Bind(wxEVT_MOTION, [this](wxMouseEvent& evt) { on_mouse_move_on_profiles(evt); }); - list_profile->Bind(wxEVT_ENTER_WINDOW, [this](wxMouseEvent& evt) { on_mouse_enter_profiles(evt); }); - list_profile->Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& evt) { on_mouse_leave_profiles(evt); }); - */ - reload_presets(); - set_compatible_printers_html_window(std::vector(), false); -} -void PageMaterials::on_paint() -{ -} -void PageMaterials::on_mouse_move_on_profiles(wxMouseEvent& evt) -{ - const wxClientDC dc(list_profile); - const wxPoint pos = evt.GetLogicalPosition(dc); - int item = list_profile->HitTest(pos); - on_material_hovered(item); -} -void PageMaterials::on_mouse_enter_profiles(wxMouseEvent& evt) -{} -void PageMaterials::on_mouse_leave_profiles(wxMouseEvent& evt) -{ - on_material_hovered(-1); -} -void PageMaterials::reload_presets() -{ - clear(); - - list_printer->append(_L("(All)"), &EMPTY); - //list_printer->SetLabelMarkup("bald"); - for (const Preset* printer : materials->printers) { - list_printer->append(printer->name, &printer->name); - } - sort_list_data(list_printer, true, false); - if (list_printer->GetCount() > 0) { - list_printer->SetSelection(0); - sel_printers_prev.Clear(); - sel_type_prev = wxNOT_FOUND; - sel_vendor_prev = wxNOT_FOUND; - update_lists(0, 0, 0); - } - - presets_loaded = true; -} - -void PageMaterials::set_compatible_printers_html_window(const std::vector& printer_names, bool all_printers) -{ - const auto bgr_clr = -#if defined(__APPLE__) - html_window->GetParent()->GetBackgroundColour(); -#else -#if defined(_WIN32) - wxGetApp().get_window_default_clr(); -#else - wxSystemSettings::GetColour(wxSYS_COLOUR_MENU); -#endif -#endif - const auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()); - const auto text_clr = wxGetApp().get_label_clr_default(); - const auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue()); - wxString first_line = format_wxstr(_L("%1% marked with * are not compatible with some installed printers."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials")); - wxString text; - if (all_printers) { - wxString second_line = format_wxstr(_L("All installed printers are compatible with the selected %1%."), materials->technology == T_FFF ? _L("filament") : _L("SLA material")); - text = wxString::Format( - "" - "" - "" - "" - "" - "%s

%s" - "
" - "
" - "" - "" - , bgr_clr_str - , text_clr_str - , first_line - , second_line - ); - } else { - wxString second_line; - if (!printer_names.empty()) - second_line = (materials->technology == T_FFF ? - _L("Only the following installed printers are compatible with the selected filaments") : - _L("Only the following installed printers are compatible with the selected SLA materials")) + ":"; - text = wxString::Format( - "" - "" - "" - "" - "" - "%s

%s" - "" - "" - , bgr_clr_str - , text_clr_str - , first_line - , second_line); - for (size_t i = 0; i < printer_names.size(); ++i) - { - text += wxString::Format("", boost::nowide::widen(printer_names[i])); - if (i % 3 == 2) { - text += wxString::Format( - "" - ""); - } - } - text += wxString::Format( - "" - "
%s
" - "
" - "
" - "" - "" - ); - } - - wxFont font = get_default_font_for_dpi(this, get_dpi_for_window(this)); - const int fs = font.GetPointSize(); - int size[] = { fs,fs,fs,fs,fs,fs,fs }; - html_window->SetFonts(font.GetFaceName(), font.GetFaceName(), size); - html_window->SetPage(text); -} - -void PageMaterials::clear_compatible_printers_label() -{ - set_compatible_printers_html_window(std::vector(), false); -} - -void PageMaterials::on_material_hovered(int sel_material) -{ - -} - -void PageMaterials::on_material_highlighted(int sel_material) -{ - if (sel_material == last_hovered_item) - return; - if (sel_material == -1) { - clear_compatible_printers_label(); - return; - } - last_hovered_item = sel_material; - std::vector tabs; - tabs.push_back(std::string()); - tabs.push_back(std::string()); - tabs.push_back(std::string()); - //selected material string - std::string material_name = list_profile->get_data(sel_material); - // get material preset - const std::vector matching_materials = materials->get_presets_by_alias(material_name); - if (matching_materials.empty()) - { - clear_compatible_printers_label(); - return; - } - //find matching printers - std::vector names; - for (const Preset* printer : materials->printers) { - for (const Preset* material : matching_materials) { - if (is_compatible_with_printer(PresetWithVendorProfile(*material, material->vendor), PresetWithVendorProfile(*printer, printer->vendor))) { - names.push_back(printer->name); - break; - } - } - } - set_compatible_printers_html_window(names, names.size() == materials->printers.size()); -} - -void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected_printer/* = -1*/) -{ - wxWindowUpdateLocker freeze_guard(this); - (void)freeze_guard; - - wxArrayInt sel_printers; - int sel_printers_count = list_printer->GetSelections(sel_printers); - - // Does our wxWidgets version support operator== for wxArrayInt ? - // https://github.com/prusa3d/PrusaSlicer/issues/5152#issuecomment-787208614 -#if wxCHECK_VERSION(3, 1, 1) - if (sel_printers != sel_printers_prev) { -#else - auto are_equal = [](const wxArrayInt& arr_first, const wxArrayInt& arr_second) { - if (arr_first.GetCount() != arr_second.GetCount()) - return false; - for (size_t i = 0; i < arr_first.GetCount(); i++) - if (arr_first[i] != arr_second[i]) - return false; - return true; - }; - if (!are_equal(sel_printers, sel_printers_prev)) { -#endif - - // Refresh type list - list_type->Clear(); - list_type->append(_L("(All)"), &EMPTY); - if (sel_printers_count > 0) { - // If all is selected with other printers - // unselect "all" or all printers depending on last value - if (sel_printers[0] == 0 && sel_printers_count > 1) { - if (last_selected_printer == 0) { - list_printer->SetSelection(wxNOT_FOUND); - list_printer->SetSelection(0); - } else { - list_printer->SetSelection(0, false); - sel_printers_count = list_printer->GetSelections(sel_printers); - } - } - if (sel_printers[0] != 0) { - for (int i = 0; i < sel_printers_count; i++) { - const std::string& printer_name = list_printer->get_data(sel_printers[i]); - const Preset* printer = nullptr; - for (const Preset* it : materials->printers) { - if (it->name == printer_name) { - printer = it; - break; - } - } - materials->filter_presets(printer, EMPTY, EMPTY, [this](const Preset* p) { - const std::string& type = this->materials->get_type(p); - if (list_type->find(type) == wxNOT_FOUND) { - list_type->append(type, &type); - } - }); - } - } else { - //clear selection except "ALL" - list_printer->SetSelection(wxNOT_FOUND); - list_printer->SetSelection(0); - sel_printers_count = list_printer->GetSelections(sel_printers); - - materials->filter_presets(nullptr, EMPTY, EMPTY, [this](const Preset* p) { - const std::string& type = this->materials->get_type(p); - if (list_type->find(type) == wxNOT_FOUND) { - list_type->append(type, &type); - } - }); - } - sort_list_data(list_type, true, true); - } - - sel_printers_prev = sel_printers; - sel_type = 0; - sel_type_prev = wxNOT_FOUND; - list_type->SetSelection(sel_type); - list_profile->Clear(); - } - - if (sel_type != sel_type_prev) { - // Refresh vendor list - - // XXX: The vendor list is created with quadratic complexity here, - // but the number of vendors is going to be very small this shouldn't be a problem. - - list_vendor->Clear(); - list_vendor->append(_L("(All)"), &EMPTY); - if (sel_printers_count != 0 && sel_type != wxNOT_FOUND) { - const std::string& type = list_type->get_data(sel_type); - // find printer preset - for (int i = 0; i < sel_printers_count; i++) { - const std::string& printer_name = list_printer->get_data(sel_printers[i]); - const Preset* printer = nullptr; - for (const Preset* it : materials->printers) { - if (it->name == printer_name) { - printer = it; - break; - } - } - materials->filter_presets(printer, type, EMPTY, [this](const Preset* p) { - const std::string& vendor = this->materials->get_vendor(p); - if (list_vendor->find(vendor) == wxNOT_FOUND) { - list_vendor->append(vendor, &vendor); - } - }); - } - sort_list_data(list_vendor, true, false); - } - - sel_type_prev = sel_type; - sel_vendor = 0; - sel_vendor_prev = wxNOT_FOUND; - list_vendor->SetSelection(sel_vendor); - list_profile->Clear(); - } - - if (sel_vendor != sel_vendor_prev) { - // Refresh material list - - list_profile->Clear(); - clear_compatible_printers_label(); - if (sel_printers_count != 0 && sel_type != wxNOT_FOUND && sel_vendor != wxNOT_FOUND) { - const std::string& type = list_type->get_data(sel_type); - const std::string& vendor = list_vendor->get_data(sel_vendor); - // finst printer preset - std::vector to_list; - for (int i = 0; i < sel_printers_count; i++) { - const std::string& printer_name = list_printer->get_data(sel_printers[i]); - const Preset* printer = nullptr; - for (const Preset* it : materials->printers) { - if (it->name == printer_name) { - printer = it; - break; - } - } - - materials->filter_presets(printer, type, vendor, [this, &to_list](const Preset* p) { - const std::string& section = materials->appconfig_section(); - bool checked = wizard_p()->appconfig_new.has(section, p->name); - bool was_checked = false; - - int cur_i = list_profile->find(p->alias); - if (cur_i == wxNOT_FOUND) { - cur_i = list_profile->append(p->alias + (materials->get_omnipresent(p) ? "" : " *"), &p->alias); - to_list.emplace_back(p->alias, materials->get_omnipresent(p), checked); - } - else { - was_checked = list_profile->IsChecked(cur_i); - to_list[cur_i].checked = checked || was_checked; - } - list_profile->Check(cur_i, checked || was_checked); - - /* Update preset selection in config. - * If one preset from aliases bundle is selected, - * than mark all presets with this aliases as selected - * */ - if (checked && !was_checked) - wizard_p()->update_presets_in_config(section, p->alias, true); - else if (!checked && was_checked) - wizard_p()->appconfig_new.set(section, p->name, "1"); - }); - } - sort_list_data(list_profile, to_list); - } - - sel_vendor_prev = sel_vendor; - } - wxGetApp().UpdateDarkUI(list_profile); -} - -void PageMaterials::sort_list_data(StringList* list, bool add_All_item, bool material_type_ordering) -{ -// get data from list -// sort data -// first should be -// then prusa profiles -// then the rest -// in alphabetical order - - std::vector> prusa_profiles; - std::vector> other_profiles; - for (int i = 0 ; i < list->size(); ++i) { - const std::string& data = list->get_data(i); - if (data == EMPTY) // do not sort item - continue; - if (!material_type_ordering && data.find("Prusa") != std::string::npos) - prusa_profiles.push_back(data); - else - other_profiles.push_back(data); - } - if(material_type_ordering) { - - const ConfigOptionDef* def = print_config_def.get("filament_type"); - std::vectorenum_values = def->enum_values; - size_t end_of_sorted = 0; - for (size_t vals = 0; vals < enum_values.size(); vals++) { - for (size_t profs = end_of_sorted; profs < other_profiles.size(); profs++) - { - // find instead compare because PET vs PETG - if (other_profiles[profs].get().find(enum_values[vals]) != std::string::npos) { - //swap - if(profs != end_of_sorted) { - std::reference_wrapper aux = other_profiles[end_of_sorted]; - other_profiles[end_of_sorted] = other_profiles[profs]; - other_profiles[profs] = aux; - } - end_of_sorted++; - break; - } - } - } - } else { - std::sort(prusa_profiles.begin(), prusa_profiles.end(), [](std::reference_wrapper a, std::reference_wrapper b) { - return a.get() < b.get(); - }); - std::sort(other_profiles.begin(), other_profiles.end(), [](std::reference_wrapper a, std::reference_wrapper b) { - return a.get() < b.get(); - }); - } - - list->Clear(); - if (add_All_item) - list->append(_L("(All)"), &EMPTY); - for (const auto& item : prusa_profiles) - list->append(item, &const_cast(item.get())); - for (const auto& item : other_profiles) - list->append(item, &const_cast(item.get())); -} - -void PageMaterials::sort_list_data(PresetList* list, const std::vector& data) -{ - // sort data - // then prusa profiles - // then the rest - // in alphabetical order - std::vector prusa_profiles; - std::vector other_profiles; - //for (int i = 0; i < data.size(); ++i) { - for (const auto& item : data) { - const std::string& name = item.name; - if (name.find("Prusa") != std::string::npos) - prusa_profiles.emplace_back(item); - else - other_profiles.emplace_back(item); - } - std::sort(prusa_profiles.begin(), prusa_profiles.end(), [](ProfilePrintData a, ProfilePrintData b) { - return a.name.get() < b.name.get(); - }); - std::sort(other_profiles.begin(), other_profiles.end(), [](ProfilePrintData a, ProfilePrintData b) { - return a.name.get() < b.name.get(); - }); - list->Clear(); - for (size_t i = 0; i < prusa_profiles.size(); ++i) { - list->append(std::string(prusa_profiles[i].name) + (prusa_profiles[i].omnipresent ? "" : " *"), &const_cast(prusa_profiles[i].name.get())); - list->Check(i, prusa_profiles[i].checked); - } - for (size_t i = 0; i < other_profiles.size(); ++i) { - list->append(std::string(other_profiles[i].name) + (other_profiles[i].omnipresent ? "" : " *"), &const_cast(other_profiles[i].name.get())); - list->Check(i + prusa_profiles.size(), other_profiles[i].checked); - } -} - -void PageMaterials::select_material(int i) -{ - const bool checked = list_profile->IsChecked(i); - - const std::string& alias_key = list_profile->get_data(i); - wizard_p()->update_presets_in_config(materials->appconfig_section(), alias_key, checked); -} - -void PageMaterials::select_all(bool select) -{ - wxWindowUpdateLocker freeze_guard(this); - (void)freeze_guard; - - for (unsigned i = 0; i < list_profile->GetCount(); i++) { - const bool current = list_profile->IsChecked(i); - if (current != select) { - list_profile->Check(i, select); - select_material(i); - } - } -} - -void PageMaterials::clear() -{ - list_printer->Clear(); - list_type->Clear(); - list_vendor->Clear(); - list_profile->Clear(); - sel_printers_prev.Clear(); - sel_type_prev = wxNOT_FOUND; - sel_vendor_prev = wxNOT_FOUND; - presets_loaded = false; -} - -void PageMaterials::on_activate() -{ - if (! presets_loaded) { - wizard_p()->update_materials(materials->technology); - reload_presets(); - } - first_paint = true; -} - - -const char *PageCustom::default_profile_name = "My Settings"; - -PageCustom::PageCustom(ConfigWizard *parent) - : ConfigWizardPage(parent, _L("Custom Printer Setup"), _L("Custom Printer")) -{ - cb_custom = new wxCheckBox(this, wxID_ANY, _L("Define a custom printer profile")); - tc_profile_name = new wxTextCtrl(this, wxID_ANY, default_profile_name); - auto *label = new wxStaticText(this, wxID_ANY, _L("Custom profile name:")); - - wxGetApp().UpdateDarkUI(tc_profile_name); - - tc_profile_name->Enable(false); - tc_profile_name->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent &evt) { - if (tc_profile_name->GetValue().IsEmpty()) { - if (profile_name_prev.IsEmpty()) { tc_profile_name->SetValue(default_profile_name); } - else { tc_profile_name->SetValue(profile_name_prev); } - } else { - profile_name_prev = tc_profile_name->GetValue(); - } - evt.Skip(); - }); - - cb_custom->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { - tc_profile_name->Enable(custom_wanted()); - wizard_p()->on_custom_setup(custom_wanted()); - - }); - - append(cb_custom); - append(label); - append(tc_profile_name); -} - -PageUpdate::PageUpdate(ConfigWizard *parent) - : ConfigWizardPage(parent, _L("Automatic updates"), _L("Updates")) - , version_check(true) - , preset_update(true) -{ - const AppConfig *app_config = wxGetApp().app_config; - auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); - boldfont.SetWeight(wxFONTWEIGHT_BOLD); - - auto *box_slic3r = new wxCheckBox(this, wxID_ANY, _L("Check for application updates")); - box_slic3r->SetValue(app_config->get("notify_release") != "none"); - append(box_slic3r); - append_text(wxString::Format(_L( - "If enabled, %s checks for new application versions online. When a new version becomes available, " - "a notification is displayed at the next application startup (never during program usage). " - "This is only a notification mechanisms, no automatic installation is done."), SLIC3R_APP_NAME)); - - append_spacer(VERTICAL_SPACING); - - auto *box_presets = new wxCheckBox(this, wxID_ANY, _L("Update built-in Presets automatically")); - box_presets->SetValue(app_config->get("preset_update") == "1"); - append(box_presets); - append_text(wxString::Format(_L( - "If enabled, %s downloads updates of built-in system presets in the background." - "These updates are downloaded into a separate temporary location." - "When a new preset version becomes available it is offered at application startup."), SLIC3R_APP_NAME)); - const auto text_bold = _L("Updates are never applied without user's consent and never overwrite user's customized settings."); - auto *label_bold = new wxStaticText(this, wxID_ANY, text_bold); - label_bold->SetFont(boldfont); - label_bold->Wrap(WRAP_WIDTH); - append(label_bold); - append_text(_L("Additionally a backup snapshot of the whole configuration is created before an update is applied.")); - - box_slic3r->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->version_check = event.IsChecked(); }); - box_presets->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->preset_update = event.IsChecked(); }); -} - -PageReloadFromDisk::PageReloadFromDisk(ConfigWizard* parent) - : ConfigWizardPage(parent, _L("Reload from disk"), _L("Reload from disk")) - , full_pathnames(false) -{ - auto* box_pathnames = new wxCheckBox(this, wxID_ANY, _L("Export full pathnames of models and parts sources into 3mf and amf files")); - box_pathnames->SetValue(wxGetApp().app_config->get("export_sources_full_pathnames") == "1"); - append(box_pathnames); - append_text(_L( - "If enabled, allows the Reload from disk command to automatically find and load the files when invoked.\n" - "If not enabled, the Reload from disk command will ask to select each file using an open file dialog." - )); - - box_pathnames->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { this->full_pathnames = event.IsChecked(); }); -} - -#ifdef _WIN32 -PageFilesAssociation::PageFilesAssociation(ConfigWizard* parent) - : ConfigWizardPage(parent, _L("Files association"), _L("Files association")) -{ - cb_3mf = new wxCheckBox(this, wxID_ANY, _L("Associate .3mf files to PrusaSlicer")); - cb_stl = new wxCheckBox(this, wxID_ANY, _L("Associate .stl files to PrusaSlicer")); -// cb_gcode = new wxCheckBox(this, wxID_ANY, _L("Associate .gcode files to PrusaSlicer G-code Viewer")); - - append(cb_3mf); - append(cb_stl); -// append(cb_gcode); -} -#endif // _WIN32 - -PageMode::PageMode(ConfigWizard *parent) - : ConfigWizardPage(parent, _L("View mode"), _L("View mode")) -{ - append_text(_L("PrusaSlicer's user interfaces comes in three variants:\nSimple, Advanced, and Expert.\n" - "The Simple mode shows only the most frequently used settings relevant for regular 3D printing. " - "The other two offer progressively more sophisticated fine-tuning, " - "they are suitable for advanced and expert users, respectively.")); - - radio_simple = new wxRadioButton(this, wxID_ANY, _L("Simple mode")); - 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); - - if (mode == "advanced") { radio_advanced->SetValue(true); } - else if (mode == "expert") { radio_expert->SetValue(true); } - else { radio_simple->SetValue(true); } - - check_inch->SetValue(wxGetApp().app_config->get("use_inches") == "1"); -} - -void PageMode::serialize_mode(AppConfig *app_config) const -{ - std::string mode = ""; - - if (radio_simple->GetValue()) { mode = "simple"; } - 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"); -} - -PageVendors::PageVendors(ConfigWizard *parent) - : ConfigWizardPage(parent, _L("Other Vendors"), _L("Other Vendors")) -{ - const AppConfig &appconfig = this->wizard_p()->appconfig_new; - - append_text(wxString::Format(_L("Pick another vendor supported by %s"), SLIC3R_APP_NAME) + ":"); - - auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); - boldfont.SetWeight(wxFONTWEIGHT_BOLD); - - for (const auto &pair : wizard_p()->bundles) { - const VendorProfile *vendor = pair.second.vendor_profile; - if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; } - - auto *cbox = new wxCheckBox(this, wxID_ANY, vendor->name); - cbox->Bind(wxEVT_CHECKBOX, [=](wxCommandEvent &event) { - wizard_p()->on_3rdparty_install(vendor, cbox->IsChecked()); - }); - - const auto &vendors = appconfig.vendors(); - const bool enabled = vendors.find(pair.first) != vendors.end(); - if (enabled) { - cbox->SetValue(true); - - auto pages = wizard_p()->pages_3rdparty.find(vendor->id); - wxCHECK_RET(pages != wizard_p()->pages_3rdparty.end(), "Internal error: 3rd party vendor printers page not created"); - - for (PagePrinters* page : { pages->second.first, pages->second.second }) - if (page) page->install = true; - } - - append(cbox); - } -} - -PageFirmware::PageFirmware(ConfigWizard *parent) - : ConfigWizardPage(parent, _L("Firmware Type"), _L("Firmware"), 1) - , gcode_opt(*print_config_def.get("gcode_flavor")) - , gcode_picker(nullptr) -{ - append_text(_L("Choose the type of firmware used by your printer.")); - append_text(_(gcode_opt.tooltip)); - - wxArrayString choices; - choices.Alloc(gcode_opt.enum_labels.size()); - for (const auto &label : gcode_opt.enum_labels) { - choices.Add(label); - } - - gcode_picker = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, choices); - wxGetApp().UpdateDarkUI(gcode_picker); - const auto &enum_values = gcode_opt.enum_values; - auto needle = enum_values.cend(); - if (gcode_opt.default_value) { - needle = std::find(enum_values.cbegin(), enum_values.cend(), gcode_opt.default_value->serialize()); - } - if (needle != enum_values.cend()) { - gcode_picker->SetSelection(needle - enum_values.cbegin()); - } else { - gcode_picker->SetSelection(0); - } - - append(gcode_picker); -} - -void PageFirmware::apply_custom_config(DynamicPrintConfig &config) -{ - auto sel = gcode_picker->GetSelection(); - if (sel >= 0 && (size_t)sel < gcode_opt.enum_labels.size()) { - auto *opt = new ConfigOptionEnum(static_cast(sel)); - config.set_key_value("gcode_flavor", opt); - } -} - -PageBedShape::PageBedShape(ConfigWizard *parent) - : ConfigWizardPage(parent, _L("Bed Shape and Size"), _L("Bed Shape"), 1) - , shape_panel(new BedShapePanel(this)) -{ - append_text(_L("Set the shape of your printer's bed.")); - - shape_panel->build_panel(*wizard_p()->custom_config->option("bed_shape"), - *wizard_p()->custom_config->option("bed_custom_texture"), - *wizard_p()->custom_config->option("bed_custom_model")); - - append(shape_panel); -} - -void PageBedShape::apply_custom_config(DynamicPrintConfig &config) -{ - const std::vector& points = shape_panel->get_shape(); - const std::string& custom_texture = shape_panel->get_custom_texture(); - const std::string& custom_model = shape_panel->get_custom_model(); - config.set_key_value("bed_shape", new ConfigOptionPoints(points)); - config.set_key_value("bed_custom_texture", new ConfigOptionString(custom_texture)); - config.set_key_value("bed_custom_model", new ConfigOptionString(custom_model)); -} - -static void focus_event(wxFocusEvent& e, wxTextCtrl* ctrl, double def_value) -{ - e.Skip(); - wxString str = ctrl->GetValue(); - - const char dec_sep = is_decimal_separator_point() ? '.' : ','; - const char dec_sep_alt = dec_sep == '.' ? ',' : '.'; - // Replace the first incorrect separator in decimal number. - bool was_replaced = str.Replace(dec_sep_alt, dec_sep, false) != 0; - - double val = 0.0; - if (!str.ToDouble(&val)) { - if (val == 0.0) - val = def_value; - ctrl->SetValue(double_to_string(val)); - show_error(nullptr, _L("Invalid numeric input.")); - ctrl->SetFocus(); - } - else if (was_replaced) - ctrl->SetValue(double_to_string(val)); -} - -class DiamTextCtrl : public wxTextCtrl -{ -public: - DiamTextCtrl(wxWindow* parent) - { -#ifdef _WIN32 - long style = wxBORDER_SIMPLE; -#else - long style = 0; -#endif - Create(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(Field::def_width_thinner() * wxGetApp().em_unit(), wxDefaultCoord), style); - wxGetApp().UpdateDarkUI(this); - } - ~DiamTextCtrl() {} -}; - -PageDiameters::PageDiameters(ConfigWizard *parent) - : ConfigWizardPage(parent, _L("Filament and Nozzle Diameters"), _L("Print Diameters"), 1) - , diam_nozzle(new DiamTextCtrl(this)) - , diam_filam (new DiamTextCtrl(this)) -{ - auto *default_nozzle = print_config_def.get("nozzle_diameter")->get_default_value(); - wxString value = double_to_string(default_nozzle != nullptr && default_nozzle->size() > 0 ? default_nozzle->get_at(0) : 0.5); - diam_nozzle->SetValue(value); - - auto *default_filam = print_config_def.get("filament_diameter")->get_default_value(); - value = double_to_string(default_filam != nullptr && default_filam->size() > 0 ? default_filam->get_at(0) : 3.0); - diam_filam->SetValue(value); - - diam_nozzle->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { focus_event(e, diam_nozzle, 0.5); }, diam_nozzle->GetId()); - diam_filam ->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { focus_event(e, diam_filam , 3.0); }, diam_filam->GetId()); - - append_text(_L("Enter the diameter of your printer's hot end nozzle.")); - - auto *sizer_nozzle = new wxFlexGridSizer(3, 5, 5); - auto *text_nozzle = new wxStaticText(this, wxID_ANY, _L("Nozzle Diameter:")); - auto *unit_nozzle = new wxStaticText(this, wxID_ANY, _L("mm")); - sizer_nozzle->AddGrowableCol(0, 1); - sizer_nozzle->Add(text_nozzle, 0, wxALIGN_CENTRE_VERTICAL); - sizer_nozzle->Add(diam_nozzle); - sizer_nozzle->Add(unit_nozzle, 0, wxALIGN_CENTRE_VERTICAL); - append(sizer_nozzle); - - append_spacer(VERTICAL_SPACING); - - append_text(_L("Enter the diameter of your filament.")); - append_text(_L("Good precision is required, so use a caliper and do multiple measurements along the filament, then compute the average.")); - - auto *sizer_filam = new wxFlexGridSizer(3, 5, 5); - auto *text_filam = new wxStaticText(this, wxID_ANY, _L("Filament Diameter:")); - auto *unit_filam = new wxStaticText(this, wxID_ANY, _L("mm")); - sizer_filam->AddGrowableCol(0, 1); - sizer_filam->Add(text_filam, 0, wxALIGN_CENTRE_VERTICAL); - sizer_filam->Add(diam_filam, 0, wxALIGN_CENTRE_VERTICAL); - sizer_filam->Add(unit_filam, 0, wxALIGN_CENTRE_VERTICAL); - append(sizer_filam); -} - -void PageDiameters::apply_custom_config(DynamicPrintConfig &config) -{ - double val = 0.0; - diam_nozzle->GetValue().ToDouble(&val); - auto *opt_nozzle = new ConfigOptionFloats(1, val); - config.set_key_value("nozzle_diameter", opt_nozzle); - - val = 0.0; - diam_filam->GetValue().ToDouble(&val); - auto * opt_filam = new ConfigOptionFloats(1, val); - config.set_key_value("filament_diameter", opt_filam); - - auto set_extrusion_width = [&config, opt_nozzle](const char *key, double dmr) { - char buf[64]; // locales don't matter here (sprintf/atof) - sprintf(buf, "%.2lf", dmr * opt_nozzle->values.front() / 0.4); - config.set_key_value(key, new ConfigOptionFloatOrPercent(atof(buf), false)); - }; - - set_extrusion_width("support_material_extrusion_width", 0.35); - set_extrusion_width("top_infill_extrusion_width", 0.40); - set_extrusion_width("first_layer_extrusion_width", 0.42); - - set_extrusion_width("extrusion_width", 0.45); - set_extrusion_width("perimeter_extrusion_width", 0.45); - set_extrusion_width("external_perimeter_extrusion_width", 0.45); - set_extrusion_width("infill_extrusion_width", 0.45); - set_extrusion_width("solid_infill_extrusion_width", 0.45); -} - -class SpinCtrlDouble: public wxSpinCtrlDouble -{ -public: - SpinCtrlDouble(wxWindow* parent) - { -#ifdef _WIN32 - long style = wxSP_ARROW_KEYS | wxBORDER_SIMPLE; -#else - long style = wxSP_ARROW_KEYS; -#endif - Create(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, style); -#ifdef _WIN32 - wxGetApp().UpdateDarkUI(this->GetText()); -#endif - this->Refresh(); - } - ~SpinCtrlDouble() {} -}; - -PageTemperatures::PageTemperatures(ConfigWizard *parent) - : ConfigWizardPage(parent, _L("Nozzle and Bed Temperatures"), _L("Temperatures"), 1) - , spin_extr(new SpinCtrlDouble(this)) - , spin_bed (new SpinCtrlDouble(this)) -{ - spin_extr->SetIncrement(5.0); - const auto &def_extr = *print_config_def.get("temperature"); - spin_extr->SetRange(def_extr.min, def_extr.max); - auto *default_extr = def_extr.get_default_value(); - spin_extr->SetValue(default_extr != nullptr && default_extr->size() > 0 ? default_extr->get_at(0) : 200); - - spin_bed->SetIncrement(5.0); - const auto &def_bed = *print_config_def.get("bed_temperature"); - spin_bed->SetRange(def_bed.min, def_bed.max); - auto *default_bed = def_bed.get_default_value(); - spin_bed->SetValue(default_bed != nullptr && default_bed->size() > 0 ? default_bed->get_at(0) : 0); - - append_text(_L("Enter the temperature needed for extruding your filament.")); - append_text(_L("A rule of thumb is 160 to 230 °C for PLA, and 215 to 250 °C for ABS.")); - - auto *sizer_extr = new wxFlexGridSizer(3, 5, 5); - auto *text_extr = new wxStaticText(this, wxID_ANY, _L("Extrusion Temperature:")); - auto *unit_extr = new wxStaticText(this, wxID_ANY, _L("°C")); - sizer_extr->AddGrowableCol(0, 1); - sizer_extr->Add(text_extr, 0, wxALIGN_CENTRE_VERTICAL); - sizer_extr->Add(spin_extr); - sizer_extr->Add(unit_extr, 0, wxALIGN_CENTRE_VERTICAL); - append(sizer_extr); - - append_spacer(VERTICAL_SPACING); - - append_text(_L("Enter the bed temperature needed for getting your filament to stick to your heated bed.")); - append_text(_L("A rule of thumb is 60 °C for PLA and 110 °C for ABS. Leave zero if you have no heated bed.")); - - auto *sizer_bed = new wxFlexGridSizer(3, 5, 5); - auto *text_bed = new wxStaticText(this, wxID_ANY, _L("Bed Temperature:")); - auto *unit_bed = new wxStaticText(this, wxID_ANY, _L("°C")); - sizer_bed->AddGrowableCol(0, 1); - sizer_bed->Add(text_bed, 0, wxALIGN_CENTRE_VERTICAL); - sizer_bed->Add(spin_bed); - sizer_bed->Add(unit_bed, 0, wxALIGN_CENTRE_VERTICAL); - append(sizer_bed); -} - -void PageTemperatures::apply_custom_config(DynamicPrintConfig &config) -{ - auto *opt_extr = new ConfigOptionInts(1, spin_extr->GetValue()); - config.set_key_value("temperature", opt_extr); - auto *opt_extr1st = new ConfigOptionInts(1, spin_extr->GetValue()); - config.set_key_value("first_layer_temperature", opt_extr1st); - auto *opt_bed = new ConfigOptionInts(1, spin_bed->GetValue()); - config.set_key_value("bed_temperature", opt_bed); - auto *opt_bed1st = new ConfigOptionInts(1, spin_bed->GetValue()); - config.set_key_value("first_layer_bed_temperature", opt_bed1st); -} - - -// Index - -ConfigWizardIndex::ConfigWizardIndex(wxWindow *parent) - : wxPanel(parent) - , bg(ScalableBitmap(parent, "PrusaSlicer_192px_transparent.png", 192)) - , bullet_black(ScalableBitmap(parent, "bullet_black.png")) - , bullet_blue(ScalableBitmap(parent, "bullet_blue.png")) - , bullet_white(ScalableBitmap(parent, "bullet_white.png")) - , item_active(NO_ITEM) - , item_hover(NO_ITEM) - , last_page((size_t)-1) -{ -#ifndef __WXOSX__ - SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX -#endif //__WXOSX__ - SetMinSize(bg.bmp().GetSize()); - - const wxSize size = GetTextExtent("m"); - em_w = size.x; - em_h = size.y; - - Bind(wxEVT_PAINT, &ConfigWizardIndex::on_paint, this); - Bind(wxEVT_SIZE, [this](wxEvent& e) { e.Skip(); Refresh(); }); - Bind(wxEVT_MOTION, &ConfigWizardIndex::on_mouse_move, this); - - Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent &evt) { - if (item_hover != -1) { - item_hover = -1; - Refresh(); - } - evt.Skip(); - }); - - Bind(wxEVT_LEFT_UP, [this](wxMouseEvent &evt) { - if (item_hover >= 0) { go_to(item_hover); } - }); -} - -wxDECLARE_EVENT(EVT_INDEX_PAGE, wxCommandEvent); - -void ConfigWizardIndex::add_page(ConfigWizardPage *page) -{ - last_page = items.size(); - items.emplace_back(Item { page->shortname, page->indent, page }); - Refresh(); -} - -void ConfigWizardIndex::add_label(wxString label, unsigned indent) -{ - items.emplace_back(Item { std::move(label), indent, nullptr }); - Refresh(); -} - -ConfigWizardPage* ConfigWizardIndex::active_page() const -{ - if (item_active >= items.size()) { return nullptr; } - - return items[item_active].page; -} - -void ConfigWizardIndex::go_prev() -{ - // Search for a preceiding item that is a page (not a label, ie. page != nullptr) - - if (item_active == NO_ITEM) { return; } - - for (size_t i = item_active; i > 0; i--) { - if (items[i - 1].page != nullptr) { - go_to(i - 1); - return; - } - } -} - -void ConfigWizardIndex::go_next() -{ - // Search for a next item that is a page (not a label, ie. page != nullptr) - - if (item_active == NO_ITEM) { return; } - - for (size_t i = item_active + 1; i < items.size(); i++) { - if (items[i].page != nullptr) { - go_to(i); - return; - } - } -} - -// This one actually performs the go-to op -void ConfigWizardIndex::go_to(size_t i) -{ - if (i != item_active - && i < items.size() - && items[i].page != nullptr) { - auto *new_active = items[i].page; - auto *former_active = active_page(); - if (former_active != nullptr) { - former_active->Hide(); - } - - item_active = i; - new_active->Show(); - - wxCommandEvent evt(EVT_INDEX_PAGE, GetId()); - AddPendingEvent(evt); - - Refresh(); - - new_active->on_activate(); - } -} - -void ConfigWizardIndex::go_to(const ConfigWizardPage *page) -{ - if (page == nullptr) { return; } - - for (size_t i = 0; i < items.size(); i++) { - if (items[i].page == page) { - go_to(i); - return; - } - } -} - -void ConfigWizardIndex::clear() -{ - auto *former_active = active_page(); - if (former_active != nullptr) { former_active->Hide(); } - - items.clear(); - item_active = NO_ITEM; -} - -void ConfigWizardIndex::on_paint(wxPaintEvent & evt) -{ - const auto size = GetClientSize(); - if (size.GetHeight() == 0 || size.GetWidth() == 0) { return; } - - wxPaintDC dc(this); - - const auto bullet_w = bullet_black.bmp().GetSize().GetWidth(); - const auto bullet_h = bullet_black.bmp().GetSize().GetHeight(); - const int yoff_icon = bullet_h < em_h ? (em_h - bullet_h) / 2 : 0; - const int yoff_text = bullet_h > em_h ? (bullet_h - em_h) / 2 : 0; - const int yinc = item_height(); - - int index_width = 0; - - unsigned y = 0; - for (size_t i = 0; i < items.size(); i++) { - const Item& item = items[i]; - unsigned x = em_w/2 + item.indent * em_w; - - if (i == item_active || (item_hover >= 0 && i == (size_t)item_hover)) { - dc.DrawBitmap(bullet_blue.bmp(), x, y + yoff_icon, false); - } - else if (i < item_active) { dc.DrawBitmap(bullet_black.bmp(), x, y + yoff_icon, false); } - else if (i > item_active) { dc.DrawBitmap(bullet_white.bmp(), x, y + yoff_icon, false); } - - x += + bullet_w + em_w/2; - const auto text_size = dc.GetTextExtent(item.label); - dc.SetTextForeground(wxGetApp().get_label_clr_default()); - dc.DrawText(item.label, x, y + yoff_text); - - y += yinc; - index_width = std::max(index_width, (int)x + text_size.x); - } - - //draw logo - if (int y = size.y - bg.GetBmpHeight(); y>=0) { - dc.DrawBitmap(bg.bmp(), 0, y, false); - index_width = std::max(index_width, bg.GetBmpWidth() + em_w / 2); - } - - if (GetMinSize().x < index_width) { - CallAfter([this, index_width]() { - SetMinSize(wxSize(index_width, GetMinSize().y)); - Refresh(); - }); - } -} - -void ConfigWizardIndex::on_mouse_move(wxMouseEvent &evt) -{ - const wxClientDC dc(this); - const wxPoint pos = evt.GetLogicalPosition(dc); - - const ssize_t item_hover_new = pos.y / item_height(); - - if (item_hover_new < ssize_t(items.size()) && item_hover_new != item_hover) { - item_hover = item_hover_new; - Refresh(); - } - - evt.Skip(); -} - -void ConfigWizardIndex::msw_rescale() -{ - const wxSize size = GetTextExtent("m"); - em_w = size.x; - em_h = size.y; - - bg.msw_rescale(); - SetMinSize(bg.bmp().GetSize()); - - bullet_black.msw_rescale(); - bullet_blue.msw_rescale(); - bullet_white.msw_rescale(); - Refresh(); -} - - -// Materials - -const std::string Materials::UNKNOWN = "(Unknown)"; - -void Materials::push(const Preset *preset) -{ - presets.emplace_back(preset); - types.insert(technology & T_FFF - ? Materials::get_filament_type(preset) - : Materials::get_material_type(preset)); -} - -void Materials::add_printer(const Preset* preset) -{ - printers.insert(preset); -} - -void Materials::clear() -{ - presets.clear(); - types.clear(); - printers.clear(); - compatibility_counter.clear(); -} - -const std::string& Materials::appconfig_section() const -{ - return (technology & T_FFF) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS; -} - -const std::string& Materials::get_type(const Preset *preset) const -{ - return (technology & T_FFF) ? get_filament_type(preset) : get_material_type(preset); -} - -const std::string& Materials::get_vendor(const Preset *preset) const -{ - return (technology & T_FFF) ? get_filament_vendor(preset) : get_material_vendor(preset); -} - -const std::string& Materials::get_filament_type(const Preset *preset) -{ - const auto *opt = preset->config.opt("filament_type"); - if (opt != nullptr && opt->values.size() > 0) { - return opt->values[0]; - } else { - return UNKNOWN; - } -} - -const std::string& Materials::get_filament_vendor(const Preset *preset) -{ - const auto *opt = preset->config.opt("filament_vendor"); - return opt != nullptr ? opt->value : UNKNOWN; -} - -const std::string& Materials::get_material_type(const Preset *preset) -{ - const auto *opt = preset->config.opt("material_type"); - if (opt != nullptr) { - return opt->value; - } else { - return UNKNOWN; - } -} - -const std::string& Materials::get_material_vendor(const Preset *preset) -{ - const auto *opt = preset->config.opt("material_vendor"); - return opt != nullptr ? opt->value : UNKNOWN; -} - -// priv - -static const std::unordered_map> legacy_preset_map {{ - { "Original Prusa i3 MK2.ini", std::make_pair("MK2S", "0.4") }, - { "Original Prusa i3 MK2 MM Single Mode.ini", std::make_pair("MK2SMM", "0.4") }, - { "Original Prusa i3 MK2 MM Single Mode 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") }, - { "Original Prusa i3 MK2 MultiMaterial.ini", std::make_pair("MK2SMM", "0.4") }, - { "Original Prusa i3 MK2 MultiMaterial 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") }, - { "Original Prusa i3 MK2 0.25 nozzle.ini", std::make_pair("MK2S", "0.25") }, - { "Original Prusa i3 MK2 0.6 nozzle.ini", std::make_pair("MK2S", "0.6") }, - { "Original Prusa i3 MK3.ini", std::make_pair("MK3", "0.4") }, -}}; - -void ConfigWizard::priv::load_pages() -{ - wxWindowUpdateLocker freeze_guard(q); - (void)freeze_guard; - - const ConfigWizardPage *former_active = index->active_page(); - - index->clear(); - - index->add_page(page_welcome); - - // Printers - if (!only_sla_mode) - index->add_page(page_fff); - index->add_page(page_msla); - if (!only_sla_mode) { - index->add_page(page_vendors); - for (const auto &pages : pages_3rdparty) { - for ( PagePrinters* page : { pages.second.first, pages.second.second }) - if (page && page->install) - index->add_page(page); - } - - index->add_page(page_custom); - if (page_custom->custom_wanted()) { - index->add_page(page_firmware); - index->add_page(page_bed); - index->add_page(page_diams); - index->add_page(page_temps); - } - - // Filaments & Materials - if (any_fff_selected) { index->add_page(page_filaments); } - } - if (any_sla_selected) { index->add_page(page_sla_materials); } - - // there should to be selected at least one printer - btn_finish->Enable(any_fff_selected || any_sla_selected || custom_printer_selected); - - index->add_page(page_update); - index->add_page(page_reload_from_disk); -#ifdef _WIN32 - index->add_page(page_files_association); -#endif // _WIN32 - index->add_page(page_mode); - - index->go_to(former_active); // Will restore the active item/page if possible - - q->Layout(); -// This Refresh() is needed to avoid ugly artifacts after printer selection, when no one vendor was selected from the very beginnig - q->Refresh(); -} - -void ConfigWizard::priv::init_dialog_size() -{ - // Clamp the Wizard size based on screen dimensions - - const auto idx = wxDisplay::GetFromWindow(q); - wxDisplay display(idx != wxNOT_FOUND ? idx : 0u); - - const auto disp_rect = display.GetClientArea(); - wxRect window_rect( - disp_rect.x + disp_rect.width / 20, - disp_rect.y + disp_rect.height / 20, - 9*disp_rect.width / 10, - 9*disp_rect.height / 10); - - const int width_hint = index->GetSize().GetWidth() + std::max(90 * em(), (only_sla_mode ? page_msla->get_width() : page_fff->get_width()) + 30 * em()); // XXX: magic constant, I found no better solution - if (width_hint < window_rect.width) { - window_rect.x += (window_rect.width - width_hint) / 2; - window_rect.width = width_hint; - } - - q->SetSize(window_rect); -} - -void ConfigWizard::priv::load_vendors() -{ - bundles = BundleMap::load(); - - // Load up the set of vendors / models / variants the user has had enabled up till now - AppConfig *app_config = wxGetApp().app_config; - if (! app_config->legacy_datadir()) { - appconfig_new.set_vendors(*app_config); - } else { - // In case of legacy datadir, try to guess the preference based on the printer preset files that are present - const auto printer_dir = fs::path(Slic3r::data_dir()) / "printer"; - for (auto &dir_entry : boost::filesystem::directory_iterator(printer_dir)) - if (Slic3r::is_ini_file(dir_entry)) { - auto needle = legacy_preset_map.find(dir_entry.path().filename().string()); - if (needle == legacy_preset_map.end()) { continue; } - - const auto &model = needle->second.first; - const auto &variant = needle->second.second; - appconfig_new.set_variant("PrusaResearch", model, variant, true); - } - } - - // Initialize the is_visible flag in printer Presets - for (auto &pair : bundles) { - pair.second.preset_bundle->load_installed_printers(appconfig_new); - } - - // Copy installed filaments and SLA material names from app_config to appconfig_new - // while resolving current names of profiles, which were renamed in the meantime. - for (PrinterTechnology technology : { ptFFF, ptSLA }) { - const std::string §ion_name = (technology == ptFFF) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS; - std::map section_new; - if (app_config->has_section(section_name)) { - const std::map §ion_old = app_config->get_section(section_name); - for (const auto& material_name_and_installed : section_old) - if (material_name_and_installed.second == "1") { - // Material is installed. Resolve it in bundles. - size_t num_found = 0; - const std::string &material_name = material_name_and_installed.first; - for (auto &bundle : bundles) { - const PresetCollection &materials = bundle.second.preset_bundle->materials(technology); - const Preset *preset = materials.find_preset(material_name); - if (preset == nullptr) { - // Not found. Maybe the material preset is there, bu it was was renamed? - const std::string *new_name = materials.get_preset_name_renamed(material_name); - if (new_name != nullptr) - preset = materials.find_preset(*new_name); - } - if (preset != nullptr) { - // Materal preset was found, mark it as installed. - section_new[preset->name] = "1"; - ++ num_found; - } - } - if (num_found == 0) - BOOST_LOG_TRIVIAL(error) << boost::format("Profile %1% was not found in installed vendor Preset Bundles.") % material_name; - else if (num_found > 1) - BOOST_LOG_TRIVIAL(error) << boost::format("Profile %1% was found in %2% vendor Preset Bundles.") % material_name % num_found; - } - } - appconfig_new.set_section(section_name, section_new); - }; -} - -void ConfigWizard::priv::add_page(ConfigWizardPage *page) -{ - const int proportion = (page->shortname == _L("Filaments")) || (page->shortname == _L("SLA Materials")) ? 1 : 0; - hscroll_sizer->Add(page, proportion, wxEXPAND); - all_pages.push_back(page); -} - -void ConfigWizard::priv::enable_next(bool enable) -{ - btn_next->Enable(enable); - btn_finish->Enable(enable); -} - -void ConfigWizard::priv::set_start_page(ConfigWizard::StartPage start_page) -{ - switch (start_page) { - case ConfigWizard::SP_PRINTERS: - index->go_to(page_fff); - btn_next->SetFocus(); - break; - case ConfigWizard::SP_FILAMENTS: - index->go_to(page_filaments); - btn_finish->SetFocus(); - break; - case ConfigWizard::SP_MATERIALS: - index->go_to(page_sla_materials); - btn_finish->SetFocus(); - break; - default: - index->go_to(page_welcome); - btn_next->SetFocus(); - break; - } -} - -void ConfigWizard::priv::create_3rdparty_pages() -{ - for (const auto &pair : bundles) { - const VendorProfile *vendor = pair.second.vendor_profile; - if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; } - - bool is_fff_technology = false; - bool is_sla_technology = false; - - for (auto& model: vendor->models) - { - if (!is_fff_technology && model.technology == ptFFF) - is_fff_technology = true; - if (!is_sla_technology && model.technology == ptSLA) - is_sla_technology = true; - } - - PagePrinters* pageFFF = nullptr; - PagePrinters* pageSLA = nullptr; - - if (is_fff_technology) { - pageFFF = new PagePrinters(q, vendor->name + " " +_L("FFF Technology Printers"), vendor->name+" FFF", *vendor, 1, T_FFF); - add_page(pageFFF); - } - - if (is_sla_technology) { - pageSLA = new PagePrinters(q, vendor->name + " " + _L("SLA Technology Printers"), vendor->name+" MSLA", *vendor, 1, T_SLA); - add_page(pageSLA); - } - - pages_3rdparty.insert({vendor->id, {pageFFF, pageSLA}}); - } -} - -void ConfigWizard::priv::set_run_reason(RunReason run_reason) -{ - this->run_reason = run_reason; - for (auto &page : all_pages) { - page->set_run_reason(run_reason); - } -} - -void ConfigWizard::priv::update_materials(Technology technology) -{ - if (any_fff_selected && (technology & T_FFF)) { - filaments.clear(); - aliases_fff.clear(); - // Iterate filaments in all bundles - for (const auto &pair : bundles) { - for (const auto &filament : pair.second.preset_bundle->filaments) { - // Check if filament is already added - if (filaments.containts(&filament)) - continue; - // Iterate printers in all bundles - for (const auto &printer : pair.second.preset_bundle->printers) { - if (!printer.is_visible || printer.printer_technology() != ptFFF) - continue; - // Filter out inapplicable printers - if (is_compatible_with_printer(PresetWithVendorProfile(filament, filament.vendor), PresetWithVendorProfile(printer, printer.vendor))) { - if (!filaments.containts(&filament)) { - filaments.push(&filament); - if (!filament.alias.empty()) - aliases_fff[filament.alias].insert(filament.name); - } - filaments.add_printer(&printer); - } - } - - } - } - // count compatible printers - for (const auto& preset : filaments.presets) { - - const auto filter = [preset](const std::pair element) { - return preset->alias == element.first; - }; - if (std::find_if(filaments.compatibility_counter.begin(), filaments.compatibility_counter.end(), filter) != filaments.compatibility_counter.end()) { - continue; - } - std::vector idx_with_same_alias; - for (size_t i = 0; i < filaments.presets.size(); ++i) { - if (preset->alias == filaments.presets[i]->alias) - idx_with_same_alias.push_back(i); - } - size_t counter = 0; - for (const auto& printer : filaments.printers) { - if (!(*printer).is_visible || (*printer).printer_technology() != ptFFF) - continue; - bool compatible = false; - // Test otrher materials with same alias - for (size_t i = 0; i < idx_with_same_alias.size() && !compatible; ++i) { - const Preset& prst = *(filaments.presets[idx_with_same_alias[i]]); - const Preset& prntr = *printer; - if (is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) { - compatible = true; - break; - } - } - if (compatible) - counter++; - } - filaments.compatibility_counter.emplace_back(preset->alias, counter); - } - } - - if (any_sla_selected && (technology & T_SLA)) { - sla_materials.clear(); - aliases_sla.clear(); - - // Iterate SLA materials in all bundles - for (const auto &pair : bundles) { - for (const auto &material : pair.second.preset_bundle->sla_materials) { - // Check if material is already added - if (sla_materials.containts(&material)) - continue; - // Iterate printers in all bundles - // For now, we only allow the profiles to be compatible with another profiles inside the same bundle. - for (const auto& printer : pair.second.preset_bundle->printers) { - if(!printer.is_visible || printer.printer_technology() != ptSLA) - continue; - // Filter out inapplicable printers - if (is_compatible_with_printer(PresetWithVendorProfile(material, nullptr), PresetWithVendorProfile(printer, nullptr))) { - // Check if material is already added - if(!sla_materials.containts(&material)) { - sla_materials.push(&material); - if (!material.alias.empty()) - aliases_sla[material.alias].insert(material.name); - } - sla_materials.add_printer(&printer); - } - } - } - } - // count compatible printers - for (const auto& preset : sla_materials.presets) { - - const auto filter = [preset](const std::pair element) { - return preset->alias == element.first; - }; - if (std::find_if(sla_materials.compatibility_counter.begin(), sla_materials.compatibility_counter.end(), filter) != sla_materials.compatibility_counter.end()) { - continue; - } - std::vector idx_with_same_alias; - for (size_t i = 0; i < sla_materials.presets.size(); ++i) { - if(preset->alias == sla_materials.presets[i]->alias) - idx_with_same_alias.push_back(i); - } - size_t counter = 0; - for (const auto& printer : sla_materials.printers) { - if (!(*printer).is_visible || (*printer).printer_technology() != ptSLA) - continue; - bool compatible = false; - // Test otrher materials with same alias - for (size_t i = 0; i < idx_with_same_alias.size() && !compatible; ++i) { - const Preset& prst = *(sla_materials.presets[idx_with_same_alias[i]]); - const Preset& prntr = *printer; - if (is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) { - compatible = true; - break; - } - } - if (compatible) - counter++; - } - sla_materials.compatibility_counter.emplace_back(preset->alias, counter); - } - } -} - -void ConfigWizard::priv::on_custom_setup(const bool custom_wanted) -{ - custom_printer_selected = custom_wanted; - load_pages(); -} - -void ConfigWizard::priv::on_printer_pick(PagePrinters *page, const PrinterPickerEvent &evt) -{ - if (check_sla_selected() != any_sla_selected || - check_fff_selected() != any_fff_selected) { - any_fff_selected = check_fff_selected(); - any_sla_selected = check_sla_selected(); - - load_pages(); - } - - // Update the is_visible flag on relevant printer profiles - for (auto &pair : bundles) { - if (pair.first != evt.vendor_id) { continue; } - - for (auto &preset : pair.second.preset_bundle->printers) { - if (preset.config.opt_string("printer_model") == evt.model_id - && preset.config.opt_string("printer_variant") == evt.variant_name) { - preset.is_visible = evt.enable; - } - } - - // When a printer model is picked, but there is no material installed compatible with this printer model, - // install default materials for selected printer model silently. - check_and_install_missing_materials(page->technology, evt.model_id); - } - - if (page->technology & T_FFF) { - page_filaments->clear(); - } else if (page->technology & T_SLA) { - page_sla_materials->clear(); - } -} - -void ConfigWizard::priv::select_default_materials_for_printer_model(const VendorProfile::PrinterModel &printer_model, Technology technology) -{ - PageMaterials* page_materials = technology & T_FFF ? page_filaments : page_sla_materials; - for (const std::string& material : printer_model.default_materials) - appconfig_new.set(page_materials->materials->appconfig_section(), material, "1"); -} - -void ConfigWizard::priv::select_default_materials_for_printer_models(Technology technology, const std::set &printer_models) -{ - PageMaterials *page_materials = technology & T_FFF ? page_filaments : page_sla_materials; - const std::string &appconfig_section = page_materials->materials->appconfig_section(); - - // Following block was unnecessary. Its enough to iterate printer_models once. Not for every vendor printer page. - // Filament is selected on same page for all printers of same technology. - /* - auto select_default_materials_for_printer_page = [this, appconfig_section, printer_models, technology](PagePrinters *page_printers, Technology technology) - { - const std::string vendor_id = page_printers->get_vendor_id(); - for (auto& pair : bundles) - if (pair.first == vendor_id) - for (const VendorProfile::PrinterModel *printer_model : printer_models) - for (const std::string &material : printer_model->default_materials) - appconfig_new.set(appconfig_section, material, "1"); - }; - - PagePrinters* page_printers = technology & T_FFF ? page_fff : page_msla; - select_default_materials_for_printer_page(page_printers, technology); - - for (const auto& printer : pages_3rdparty) - { - page_printers = technology & T_FFF ? printer.second.first : printer.second.second; - if (page_printers) - select_default_materials_for_printer_page(page_printers, technology); - } - */ - - // Iterate printer_models and select default materials. If none available -> msg to user. - std::vector models_without_default; - for (const VendorProfile::PrinterModel* printer_model : printer_models) { - if (printer_model->default_materials.empty()) { - models_without_default.emplace_back(printer_model); - } else { - for (const std::string& material : printer_model->default_materials) - appconfig_new.set(appconfig_section, material, "1"); - } - } - - if (!models_without_default.empty()) { - std::string printer_names = "\n\n"; - for (const VendorProfile::PrinterModel* printer_model : models_without_default) { - printer_names += printer_model->name + "\n"; - } - printer_names += "\n\n"; - std::string message = (technology & T_FFF ? - GUI::format(_L("Following printer profiles has no default filament: %1%Please select one manually."), printer_names) : - GUI::format(_L("Following printer profiles has no default material: %1%Please select one manually."), printer_names)); - MessageDialog msg(q, message, _L("Notice"), wxOK); - msg.ShowModal(); - } - - update_materials(technology); - ((technology & T_FFF) ? page_filaments : page_sla_materials)->reload_presets(); -} - -void ConfigWizard::priv::on_3rdparty_install(const VendorProfile *vendor, bool install) -{ - auto it = pages_3rdparty.find(vendor->id); - wxCHECK_RET(it != pages_3rdparty.end(), "Internal error: GUI page not found for 3rd party vendor profile"); - - for (PagePrinters* page : { it->second.first, it->second.second }) - if (page) { - if (page->install && !install) - page->select_all(false); - page->install = install; - // if some 3rd vendor is selected, select first printer for them - if (install) - page->printer_pickers[0]->select_one(0, true); - page->Layout(); - } - - load_pages(); -} - -bool ConfigWizard::priv::on_bnt_finish() -{ - wxBusyCursor wait; - /* When Filaments or Sla Materials pages are activated, - * materials for this pages are automaticaly updated and presets are reloaded. - * - * But, if _Finish_ button was clicked without activation of those pages - * (for example, just some printers were added/deleted), - * than last changes wouldn't be updated for filaments/materials. - * SO, do that before close of Wizard - */ - update_materials(T_ANY); - if (any_fff_selected) - page_filaments->reload_presets(); - if (any_sla_selected) - page_sla_materials->reload_presets(); - - // theres no need to check that filament is selected if we have only custom printer - if (custom_printer_selected && !any_fff_selected && !any_sla_selected) return true; - // check, that there is selected at least one filament/material - return check_and_install_missing_materials(T_ANY); -} - -// This allmighty method verifies, whether there is at least a single compatible filament or SLA material installed -// for each Printer preset of each Printer Model installed. -// -// In case only_for_model_id is set, then the test is done for that particular printer model only, and the default materials are installed silently. -// Otherwise the user is quieried whether to install the missing default materials or not. -// -// Return true if the tested Printer Models already had materials installed. -// Return false if there were some Printer Models with missing materials, independent from whether the defaults were installed for these -// respective Printer Models or not. -bool ConfigWizard::priv::check_and_install_missing_materials(Technology technology, const std::string &only_for_model_id) -{ - // Walk over all installed Printer presets and verify whether there is a filament or SLA material profile installed at the same PresetBundle, - // which is compatible with it. - const auto printer_models_missing_materials = [this, only_for_model_id](PrinterTechnology technology, const std::string §ion) - { - const std::map &appconfig_presets = appconfig_new.has_section(section) ? appconfig_new.get_section(section) : std::map(); - std::set printer_models_without_material; - for (const auto &pair : bundles) { - const PresetCollection &materials = pair.second.preset_bundle->materials(technology); - for (const auto &printer : pair.second.preset_bundle->printers) { - if (printer.is_visible && printer.printer_technology() == technology) { - const VendorProfile::PrinterModel *printer_model = PresetUtils::system_printer_model(printer); - assert(printer_model != nullptr); - if ((only_for_model_id.empty() || only_for_model_id == printer_model->id) && - printer_models_without_material.find(printer_model) == printer_models_without_material.end()) { - bool has_material = false; - for (const auto& preset : appconfig_presets) { - if (preset.second == "1") { - const Preset *material = materials.find_preset(preset.first, false); - if (material != nullptr && is_compatible_with_printer(PresetWithVendorProfile(*material, nullptr), PresetWithVendorProfile(printer, nullptr))) { - has_material = true; - break; - } - } - } - if (! has_material) - printer_models_without_material.insert(printer_model); - } - } - } - } - assert(printer_models_without_material.empty() || only_for_model_id.empty() || only_for_model_id == (*printer_models_without_material.begin())->id); - return printer_models_without_material; - }; - - const auto ask_and_select_default_materials = [this](const wxString &message, const std::set &printer_models, Technology technology) - { - //wxMessageDialog msg(q, message, _L("Notice"), wxYES_NO); - MessageDialog msg(q, message, _L("Notice"), wxYES_NO); - if (msg.ShowModal() == wxID_YES) - select_default_materials_for_printer_models(technology, printer_models); - }; - - const auto printer_model_list = [](const std::set &printer_models) -> wxString { - wxString out; - for (const VendorProfile::PrinterModel *printer_model : printer_models) { - wxString name = from_u8(printer_model->name); - out += "\t\t"; - out += name; - out += "\n"; - } - return out; - }; - - if (any_fff_selected && (technology & T_FFF)) { - std::set printer_models_without_material = printer_models_missing_materials(ptFFF, AppConfig::SECTION_FILAMENTS); - if (! printer_models_without_material.empty()) { - if (only_for_model_id.empty()) - ask_and_select_default_materials( - _L("The following FFF printer models have no filament selected:") + - "\n\n\t" + - printer_model_list(printer_models_without_material) + - "\n\n\t" + - _L("Do you want to select default filaments for these FFF printer models?"), - printer_models_without_material, - T_FFF); - else - select_default_materials_for_printer_model(**printer_models_without_material.begin(), T_FFF); - return false; - } - } - - if (any_sla_selected && (technology & T_SLA)) { - std::set printer_models_without_material = printer_models_missing_materials(ptSLA, AppConfig::SECTION_MATERIALS); - if (! printer_models_without_material.empty()) { - if (only_for_model_id.empty()) - ask_and_select_default_materials( - _L("The following SLA printer models have no materials selected:") + - "\n\n\t" + - printer_model_list(printer_models_without_material) + - "\n\n\t" + - _L("Do you want to select default SLA materials for these printer models?"), - printer_models_without_material, - T_SLA); - else - select_default_materials_for_printer_model(**printer_models_without_material.begin(), T_SLA); - return false; - } - } - - return true; -} - -static std::set get_new_added_presets(const std::map& old_data, const std::map& new_data) -{ - auto get_aliases = [](const std::map& data) { - std::set old_aliases; - for (auto item : data) { - const std::string& name = item.first; - size_t pos = name.find("@"); - old_aliases.emplace(pos == std::string::npos ? name : name.substr(0, pos-1)); - } - return old_aliases; - }; - - std::set old_aliases = get_aliases(old_data); - std::set new_aliases = get_aliases(new_data); - std::set diff; - std::set_difference(new_aliases.begin(), new_aliases.end(), old_aliases.begin(), old_aliases.end(), std::inserter(diff, diff.begin())); - - return diff; -} - -static std::string get_first_added_preset(const std::map& old_data, const std::map& new_data) -{ - std::set diff = get_new_added_presets(old_data, new_data); - if (diff.empty()) - return std::string(); - return *diff.begin(); -} - -bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater, bool& apply_keeped_changes) -{ - wxString header, caption = _L("Configuration is edited in ConfigWizard"); - const auto enabled_vendors = appconfig_new.vendors(); - - bool suppress_sla_printer = model_has_multi_part_objects(wxGetApp().model()); - PrinterTechnology preferred_pt = ptAny; - auto get_preferred_printer_technology = [enabled_vendors, suppress_sla_printer](const std::string& bundle_name, const Bundle& bundle) { - const auto config = enabled_vendors.find(bundle_name); - PrinterTechnology pt = ptAny; - if (config != enabled_vendors.end()) { - for (const auto& model : bundle.vendor_profile->models) { - if (const auto model_it = config->second.find(model.id); - model_it != config->second.end() && model_it->second.size() > 0) { - if (pt == ptAny) - pt = model.technology; - // if preferred printer model has SLA printer technology it's important to check the model for multypart state - if (pt == ptSLA && suppress_sla_printer) - continue; - else - return pt; - } - } - } - return pt; - }; - // Prusa printers are considered first, then 3rd party. - if (preferred_pt = get_preferred_printer_technology("PrusaResearch", bundles.prusa_bundle()); - preferred_pt == ptAny || (preferred_pt == ptSLA && suppress_sla_printer)) { - for (const auto& bundle : bundles) { - if (bundle.second.is_prusa_bundle) { continue; } - if (PrinterTechnology pt = get_preferred_printer_technology(bundle.first, bundle.second); pt == ptAny) - continue; - else if (preferred_pt == ptAny) - preferred_pt = pt; - if(!(preferred_pt == ptAny || (preferred_pt == ptSLA && suppress_sla_printer))) - break; - } - } - - if (preferred_pt == ptSLA && !wxGetApp().may_switch_to_SLA_preset(caption)) - return false; - - bool check_unsaved_preset_changes = page_welcome->reset_user_profile(); - if (check_unsaved_preset_changes) - header = _L("All user presets will be deleted."); - int act_btns = UnsavedChangesDialog::ActionButtons::KEEP; - if (!check_unsaved_preset_changes) - act_btns |= UnsavedChangesDialog::ActionButtons::SAVE; - - // Install bundles from resources if needed: - std::vector install_bundles; - for (const auto &pair : bundles) { - if (! pair.second.is_in_resources) { continue; } - - if (pair.second.is_prusa_bundle) { - // Always install Prusa bundle, because it has a lot of filaments/materials - // likely to be referenced by other profiles. - install_bundles.emplace_back(pair.first); - continue; - } - - const auto vendor = enabled_vendors.find(pair.first); - if (vendor == enabled_vendors.end()) { continue; } - - size_t size_sum = 0; - for (const auto &model : vendor->second) { size_sum += model.second.size(); } - - if (size_sum > 0) { - // This vendor needs to be installed - install_bundles.emplace_back(pair.first); - } - } - if (!check_unsaved_preset_changes) - if ((check_unsaved_preset_changes = install_bundles.size() > 0)) - header = _L_PLURAL("A new vendor was installed and one of its printers will be activated", "New vendors were installed and one of theirs printers will be activated", install_bundles.size()); - -#ifdef __linux__ - // Desktop integration on Linux - if (page_welcome->integrate_desktop()) - DesktopIntegrationDialog::perform_desktop_integration(); -#endif - - // Decide whether to create snapshot based on run_reason and the reset profile checkbox - bool snapshot = true; - Snapshot::Reason snapshot_reason = Snapshot::SNAPSHOT_UPGRADE; - switch (run_reason) { - case ConfigWizard::RR_DATA_EMPTY: - snapshot = false; - break; - case ConfigWizard::RR_DATA_LEGACY: - snapshot = true; - break; - case ConfigWizard::RR_DATA_INCOMPAT: - // In this case snapshot has already been taken by - // PresetUpdater with the appropriate reason - snapshot = false; - break; - case ConfigWizard::RR_USER: - snapshot = page_welcome->reset_user_profile(); - snapshot_reason = Snapshot::SNAPSHOT_USER; - break; - } - - if (snapshot && ! take_config_snapshot_cancel_on_error(*app_config, snapshot_reason, "", _u8L("Do you want to continue changing the configuration?"))) - return false; - - if (check_unsaved_preset_changes && - !wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) - return false; - - if (install_bundles.size() > 0) { - // Install bundles from resources. - // Don't create snapshot - we've already done that above if applicable. - if (! updater->install_bundles_rsrc(std::move(install_bundles), false)) - return false; - } else { - BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources"; - } - - if (page_welcome->reset_user_profile()) { - BOOST_LOG_TRIVIAL(info) << "Resetting user profiles..."; - preset_bundle->reset(true); - } - - std::string preferred_model; - std::string preferred_variant; - const auto enabled_vendors_old = app_config->vendors(); - auto get_preferred_printer_model = [enabled_vendors, enabled_vendors_old, preferred_pt](const std::string& bundle_name, const Bundle& bundle, std::string& variant) { - const auto config = enabled_vendors.find(bundle_name); - if (config == enabled_vendors.end()) - return std::string(); - for (const auto& model : bundle.vendor_profile->models) { - if (const auto model_it = config->second.find(model.id); - model_it != config->second.end() && model_it->second.size() > 0 && - preferred_pt == model.technology) { - variant = *model_it->second.begin(); - const auto config_old = enabled_vendors_old.find(bundle_name); - if (config_old == enabled_vendors_old.end()) - return model.id; - const auto model_it_old = config_old->second.find(model.id); - if (model_it_old == config_old->second.end()) - return model.id; - else if (model_it_old->second != model_it->second) { - for (const auto& var : model_it->second) - if (model_it_old->second.find(var) == model_it_old->second.end()) { - variant = var; - return model.id; - } - } - } - } - if (!variant.empty()) - variant.clear(); - return std::string(); - }; - // Prusa printers are considered first, then 3rd party. - if (preferred_model = get_preferred_printer_model("PrusaResearch", bundles.prusa_bundle(), preferred_variant); - preferred_model.empty()) { - for (const auto& bundle : bundles) { - if (bundle.second.is_prusa_bundle) { continue; } - if (preferred_model = get_preferred_printer_model(bundle.first, bundle.second, preferred_variant); - !preferred_model.empty()) - break; - } - } - - // if unsaved changes was not cheched till this moment - if (!check_unsaved_preset_changes) { - if ((check_unsaved_preset_changes = !preferred_model.empty())) { - header = _L("A new Printer was installed and it will be activated."); - if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) - return false; - } - else if ((check_unsaved_preset_changes = enabled_vendors_old != enabled_vendors)) { - header = _L("Some Printers were uninstalled."); - if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) - return false; - } - } - - std::string first_added_filament, first_added_sla_material; - auto get_first_added_material_preset = [this, app_config](const std::string& section_name, std::string& first_added_preset) { - if (appconfig_new.has_section(section_name)) { - // get first of new added preset names - const std::map& old_presets = app_config->has_section(section_name) ? app_config->get_section(section_name) : std::map(); - first_added_preset = get_first_added_preset(old_presets, appconfig_new.get_section(section_name)); - } - }; - get_first_added_material_preset(AppConfig::SECTION_FILAMENTS, first_added_filament); - get_first_added_material_preset(AppConfig::SECTION_MATERIALS, first_added_sla_material); - - // if unsaved changes was not cheched till this moment - if (!check_unsaved_preset_changes) { - if ((check_unsaved_preset_changes = !first_added_filament.empty() || !first_added_sla_material.empty())) { - header = !first_added_filament.empty() ? - _L("A new filament was installed and it will be activated.") : - _L("A new SLA material was installed and it will be activated."); - if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) - return false; - } - else { - auto changed = [app_config, &appconfig_new = std::as_const(this->appconfig_new)](const std::string& section_name) { - return (app_config->has_section(section_name) ? app_config->get_section(section_name) : std::map()) != appconfig_new.get_section(section_name); - }; - bool is_filaments_changed = changed(AppConfig::SECTION_FILAMENTS); - bool is_sla_materials_changed = changed(AppConfig::SECTION_MATERIALS); - if ((check_unsaved_preset_changes = is_filaments_changed || is_sla_materials_changed)) { - header = is_filaments_changed ? _L("Some filaments were uninstalled.") : _L("Some SLA materials were uninstalled."); - if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) - return false; - } - } - } - - // apply materials in app_config - for (const std::string& section_name : {AppConfig::SECTION_FILAMENTS, AppConfig::SECTION_MATERIALS}) - app_config->set_section(section_name, appconfig_new.get_section(section_name)); - - app_config->set_vendors(appconfig_new); - - app_config->set("notify_release", page_update->version_check ? "all" : "none"); - app_config->set("preset_update", page_update->preset_update ? "1" : "0"); - app_config->set("export_sources_full_pathnames", page_reload_from_disk->full_pathnames ? "1" : "0"); - -#ifdef _WIN32 - app_config->set("associate_3mf", page_files_association->associate_3mf() ? "1" : "0"); - app_config->set("associate_stl", page_files_association->associate_stl() ? "1" : "0"); -// app_config->set("associate_gcode", page_files_association->associate_gcode() ? "1" : "0"); - - if (wxGetApp().is_editor()) { - if (page_files_association->associate_3mf()) - wxGetApp().associate_3mf_files(); - if (page_files_association->associate_stl()) - wxGetApp().associate_stl_files(); - } -// else { -// if (page_files_association->associate_gcode()) -// wxGetApp().associate_gcode_files(); -// } - -#endif // _WIN32 - - page_mode->serialize_mode(app_config); - - if (check_unsaved_preset_changes) - preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem, - {preferred_model, preferred_variant, first_added_filament, first_added_sla_material}); - - if (!only_sla_mode && page_custom->custom_wanted()) { - // if unsaved changes was not cheched till this moment - if (!check_unsaved_preset_changes && - !wxGetApp().check_and_keep_current_preset_changes(caption, _L("Custom printer was installed and it will be activated."), act_btns, &apply_keeped_changes)) - return false; - - page_firmware->apply_custom_config(*custom_config); - page_bed->apply_custom_config(*custom_config); - page_diams->apply_custom_config(*custom_config); - page_temps->apply_custom_config(*custom_config); - - const std::string profile_name = page_custom->profile_name(); - preset_bundle->load_config_from_wizard(profile_name, *custom_config); - } - - // Update the selections from the compatibilty. - preset_bundle->export_selections(*app_config); - - return true; -} -void ConfigWizard::priv::update_presets_in_config(const std::string& section, const std::string& alias_key, bool add) -{ - const PresetAliases& aliases = section == AppConfig::SECTION_FILAMENTS ? aliases_fff : aliases_sla; - - auto update = [this, add](const std::string& s, const std::string& key) { - assert(! s.empty()); - if (add) - appconfig_new.set(s, key, "1"); - else - appconfig_new.erase(s, key); - }; - - // add or delete presets had a same alias - auto it = aliases.find(alias_key); - if (it != aliases.end()) - for (const std::string& name : it->second) - update(section, name); -} - -bool ConfigWizard::priv::check_fff_selected() -{ - bool ret = page_fff->any_selected(); - for (const auto& printer: pages_3rdparty) - if (printer.second.first) // FFF page - ret |= printer.second.first->any_selected(); - return ret; -} - -bool ConfigWizard::priv::check_sla_selected() -{ - bool ret = page_msla->any_selected(); - for (const auto& printer: pages_3rdparty) - if (printer.second.second) // SLA page - ret |= printer.second.second->any_selected(); - return ret; -} - - -// Public - -ConfigWizard::ConfigWizard(wxWindow *parent) - : DPIDialog(parent, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(name()), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) - , p(new priv(this)) -{ - this->SetFont(wxGetApp().normal_font()); - - p->load_vendors(); - p->custom_config.reset(DynamicPrintConfig::new_from_defaults_keys({ - "gcode_flavor", "bed_shape", "bed_custom_texture", "bed_custom_model", "nozzle_diameter", "filament_diameter", "temperature", "bed_temperature", - })); - - p->index = new ConfigWizardIndex(this); - - auto *vsizer = new wxBoxSizer(wxVERTICAL); - auto *topsizer = new wxBoxSizer(wxHORIZONTAL); - auto* hline = new StaticLine(this); - p->btnsizer = new wxBoxSizer(wxHORIZONTAL); - - // Initially we _do not_ SetScrollRate in order to figure out the overall width of the Wizard without scrolling. - // Later, we compare that to the size of the current screen and set minimum width based on that (see below). - p->hscroll = new wxScrolledWindow(this); - p->hscroll_sizer = new wxBoxSizer(wxHORIZONTAL); - p->hscroll->SetSizer(p->hscroll_sizer); - - topsizer->Add(p->index, 0, wxEXPAND); - topsizer->AddSpacer(INDEX_MARGIN); - topsizer->Add(p->hscroll, 1, wxEXPAND); - - p->btn_sel_all = new wxButton(this, wxID_ANY, _L("Select all standard printers")); - p->btnsizer->Add(p->btn_sel_all); - - p->btn_prev = new wxButton(this, wxID_ANY, _L("< &Back")); - p->btn_next = new wxButton(this, wxID_ANY, _L("&Next >")); - p->btn_finish = new wxButton(this, wxID_APPLY, _L("&Finish")); - p->btn_cancel = new wxButton(this, wxID_CANCEL, _L("Cancel")); // Note: The label needs to be present, otherwise we get accelerator bugs on Mac - p->btnsizer->AddStretchSpacer(); - p->btnsizer->Add(p->btn_prev, 0, wxLEFT, BTN_SPACING); - p->btnsizer->Add(p->btn_next, 0, wxLEFT, BTN_SPACING); - p->btnsizer->Add(p->btn_finish, 0, wxLEFT, BTN_SPACING); - p->btnsizer->Add(p->btn_cancel, 0, wxLEFT, BTN_SPACING); - - wxGetApp().UpdateDarkUI(p->btn_sel_all); - wxGetApp().UpdateDarkUI(p->btn_prev); - wxGetApp().UpdateDarkUI(p->btn_next); - wxGetApp().UpdateDarkUI(p->btn_finish); - wxGetApp().UpdateDarkUI(p->btn_cancel); - - const auto prusa_it = p->bundles.find("PrusaResearch"); - wxCHECK_RET(prusa_it != p->bundles.cend(), "Vendor PrusaResearch not found"); - const VendorProfile *vendor_prusa = prusa_it->second.vendor_profile; - - p->add_page(p->page_welcome = new PageWelcome(this)); - - - p->page_fff = new PagePrinters(this, _L("Prusa FFF Technology Printers"), "Prusa FFF", *vendor_prusa, 0, T_FFF); - p->only_sla_mode = !p->page_fff->has_printers; - if (!p->only_sla_mode) { - p->add_page(p->page_fff); - p->page_fff->is_primary_printer_page = true; - } - - - p->page_msla = new PagePrinters(this, _L("Prusa MSLA Technology Printers"), "Prusa MSLA", *vendor_prusa, 0, T_SLA); - p->add_page(p->page_msla); - if (p->only_sla_mode) { - p->page_msla->is_primary_printer_page = true; - } - - if (!p->only_sla_mode) { - // Pages for 3rd party vendors - p->create_3rdparty_pages(); // Needs to be done _before_ creating PageVendors - p->add_page(p->page_vendors = new PageVendors(this)); - p->add_page(p->page_custom = new PageCustom(this)); - p->custom_printer_selected = p->page_custom->custom_wanted(); - } - - p->any_sla_selected = p->check_sla_selected(); - p->any_fff_selected = ! p->only_sla_mode && p->check_fff_selected(); - - p->update_materials(T_ANY); - if (!p->only_sla_mode) - p->add_page(p->page_filaments = new PageMaterials(this, &p->filaments, - _L("Filament Profiles Selection"), _L("Filaments"), _L("Type:") )); - - p->add_page(p->page_sla_materials = new PageMaterials(this, &p->sla_materials, - _L("SLA Material Profiles Selection") + " ", _L("SLA Materials"), _L("Type:") )); - - - p->add_page(p->page_update = new PageUpdate(this)); - p->add_page(p->page_reload_from_disk = new PageReloadFromDisk(this)); -#ifdef _WIN32 - p->add_page(p->page_files_association = new PageFilesAssociation(this)); -#endif // _WIN32 - p->add_page(p->page_mode = new PageMode(this)); - p->add_page(p->page_firmware = new PageFirmware(this)); - p->add_page(p->page_bed = new PageBedShape(this)); - p->add_page(p->page_diams = new PageDiameters(this)); - p->add_page(p->page_temps = new PageTemperatures(this)); - - p->load_pages(); - p->index->go_to(size_t{0}); - - vsizer->Add(topsizer, 1, wxEXPAND | wxALL, DIALOG_MARGIN); - vsizer->Add(hline, 0, wxEXPAND | wxLEFT | wxRIGHT, VERTICAL_SPACING); - vsizer->Add(p->btnsizer, 0, wxEXPAND | wxALL, DIALOG_MARGIN); - - SetSizer(vsizer); - SetSizerAndFit(vsizer); - - // We can now enable scrolling on hscroll - p->hscroll->SetScrollRate(30, 30); - - on_window_geometry(this, [this]() { - p->init_dialog_size(); - }); - - p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) { this->p->index->go_prev(); }); - - p->btn_next->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) - { - // check, that there is selected at least one filament/material - ConfigWizardPage* active_page = this->p->index->active_page(); - if (// Leaving the filaments or SLA materials page and - (active_page == p->page_filaments || active_page == p->page_sla_materials) && - // some Printer models had no filament or SLA material selected. - ! p->check_and_install_missing_materials(dynamic_cast(active_page)->materials->technology)) - // In that case don't leave the page and the function above queried the user whether to install default materials. - return; - this->p->index->go_next(); - }); - - p->btn_finish->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) - { - if (p->on_bnt_finish()) - this->EndModal(wxID_OK); - }); - - p->btn_sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) { - p->any_sla_selected = true; - p->load_pages(); - p->page_fff->select_all(true, false); - p->page_msla->select_all(true, false); - p->index->go_to(p->page_mode); - }); - - p->index->Bind(EVT_INDEX_PAGE, [this](const wxCommandEvent &) { - const bool is_last = p->index->active_is_last(); - p->btn_next->Show(! is_last); - if (is_last) - p->btn_finish->SetFocus(); - - Layout(); - }); - - if (wxLinux_gtk3) - this->Bind(wxEVT_SHOW, [this, vsizer](const wxShowEvent& e) { - ConfigWizardPage* active_page = p->index->active_page(); - if (!active_page) - return; - for (auto page : p->all_pages) - if (page != active_page) - page->Hide(); - // update best size for the dialog after hiding of the non-active pages - vsizer->SetSizeHints(this); - // set initial dialog size - p->init_dialog_size(); - }); -} - -ConfigWizard::~ConfigWizard() {} - -bool ConfigWizard::run(RunReason reason, StartPage start_page) -{ - BOOST_LOG_TRIVIAL(info) << boost::format("Running ConfigWizard, reason: %1%, start_page: %2%") % reason % start_page; - - GUI_App &app = wxGetApp(); - - p->set_run_reason(reason); - p->set_start_page(start_page); - - if (ShowModal() == wxID_OK) { - bool apply_keeped_changes = false; - if (! p->apply_config(app.app_config, app.preset_bundle, app.preset_updater, apply_keeped_changes)) - return false; - - if (apply_keeped_changes) - app.apply_keeped_preset_modifications(); - - app.app_config->set_legacy_datadir(false); - app.update_mode(); - app.obj_manipul()->update_ui_from_settings(); - BOOST_LOG_TRIVIAL(info) << "ConfigWizard applied"; - return true; - } else { - BOOST_LOG_TRIVIAL(info) << "ConfigWizard cancelled"; - return false; - } -} - -const wxString& ConfigWizard::name(const bool from_menu/* = false*/) -{ - // A different naming convention is used for the Wizard on Windows & GTK vs. OSX. - // Note: Don't call _() macro here. - // This function just return the current name according to the OS. - // Translation is implemented inside GUI_App::add_config_menu() -#if __APPLE__ - static const wxString config_wizard_name = L("Configuration Assistant"); - static const wxString config_wizard_name_menu = L("Configuration &Assistant"); -#else - static const wxString config_wizard_name = L("Configuration Wizard"); - static const wxString config_wizard_name_menu = L("Configuration &Wizard"); -#endif - return from_menu ? config_wizard_name_menu : config_wizard_name; -} - -void ConfigWizard::on_dpi_changed(const wxRect &suggested_rect) -{ - p->index->msw_rescale(); - - const int em = em_unit(); - - msw_buttons_rescale(this, em, { wxID_APPLY, - wxID_CANCEL, - p->btn_sel_all->GetId(), - p->btn_next->GetId(), - p->btn_prev->GetId() }); - - for (auto printer_picker: p->page_fff->printer_pickers) - msw_buttons_rescale(this, em, printer_picker->get_button_indexes()); - - p->init_dialog_size(); - - Refresh(); -} - -void ConfigWizard::on_sys_color_changed() -{ - wxGetApp().UpdateDlgDarkUI(this); - Refresh(); -} - -} -} ->>>>>>> master_250 diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 1942971de..1e28d1287 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1,4 +1,3 @@ -<<<<<<< HEAD #include "libslic3r/libslic3r.h" #include "GCodeViewer.hpp" @@ -4672,3958 +4671,3 @@ ColorRGBA GCodeViewer::option_color(EMoveType move_type) const } // namespace GUI } // namespace Slic3r -======= -#include "libslic3r/libslic3r.h" -#include "GCodeViewer.hpp" - -#include "libslic3r/BuildVolume.hpp" -#include "libslic3r/Print.hpp" -#include "libslic3r/Geometry.hpp" -#include "libslic3r/Model.hpp" -#include "libslic3r/Utils.hpp" -#include "libslic3r/LocalesUtils.hpp" -#include "libslic3r/PresetBundle.hpp" - -#include "GUI_App.hpp" -#include "MainFrame.hpp" -#include "Plater.hpp" -#include "Camera.hpp" -#include "I18N.hpp" -#include "GUI_Utils.hpp" -#include "GUI.hpp" -#include "DoubleSlider.hpp" -#include "GLCanvas3D.hpp" -#include "GLToolbar.hpp" -#include "GUI_Preview.hpp" -#include "GUI_ObjectManipulation.hpp" - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace Slic3r { -namespace GUI { - -static unsigned char buffer_id(EMoveType type) { - return static_cast(type) - static_cast(EMoveType::Retract); -} - -static EMoveType buffer_type(unsigned char id) { - return static_cast(static_cast(EMoveType::Retract) + id); -} - -static std::array decode_color(const std::string& color) { - static const float INV_255 = 1.0f / 255.0f; - - std::array ret = { 0.0f, 0.0f, 0.0f, 1.0f }; - const char* c = color.data() + 1; - if (color.size() == 7 && color.front() == '#') { - for (size_t j = 0; j < 3; ++j) { - int digit1 = hex_digit_to_int(*c++); - int digit2 = hex_digit_to_int(*c++); - if (digit1 == -1 || digit2 == -1) - break; - - ret[j] = float(digit1 * 16 + digit2) * INV_255; - } - } - return ret; -} - -static std::vector> decode_colors(const std::vector& colors) { - std::vector> output(colors.size(), { 0.0f, 0.0f, 0.0f, 1.0f }); - for (size_t i = 0; i < colors.size(); ++i) { - output[i] = decode_color(colors[i]); - } - return output; -} - -// Round to a bin with minimum two digits resolution. -// Equivalent to conversion to string with sprintf(buf, "%.2g", value) and conversion back to float, but faster. -static float round_to_bin(const float value) -{ -// assert(value > 0); - constexpr float const scale [5] = { 100.f, 1000.f, 10000.f, 100000.f, 1000000.f }; - constexpr float const invscale [5] = { 0.01f, 0.001f, 0.0001f, 0.00001f, 0.000001f }; - constexpr float const threshold[5] = { 0.095f, 0.0095f, 0.00095f, 0.000095f, 0.0000095f }; - // Scaling factor, pointer to the tables above. - int i = 0; - // While the scaling factor is not yet large enough to get two integer digits after scaling and rounding: - for (; value < threshold[i] && i < 4; ++ i) ; - return std::round(value * scale[i]) * invscale[i]; -} - -void GCodeViewer::VBuffer::reset() -{ - // release gpu memory - if (!vbos.empty()) { - glsafe(::glDeleteBuffers(static_cast(vbos.size()), static_cast(vbos.data()))); - vbos.clear(); - } - sizes.clear(); - count = 0; -} - -void GCodeViewer::InstanceVBuffer::Ranges::reset() -{ - for (Range& range : ranges) { - // release gpu memory - if (range.vbo > 0) - glsafe(::glDeleteBuffers(1, &range.vbo)); - } - - ranges.clear(); -} - -void GCodeViewer::InstanceVBuffer::reset() -{ - s_ids.clear(); - buffer.clear(); - render_ranges.reset(); -} - -void GCodeViewer::IBuffer::reset() -{ - // release gpu memory - if (ibo > 0) { - glsafe(::glDeleteBuffers(1, &ibo)); - ibo = 0; - } - - vbo = 0; - count = 0; -} - -bool GCodeViewer::Path::matches(const GCodeProcessorResult::MoveVertex& move) const -{ - auto matches_percent = [](float value1, float value2, float max_percent) { - return std::abs(value2 - value1) / value1 <= max_percent; - }; - - switch (move.type) - { - case EMoveType::Tool_change: - case EMoveType::Color_change: - case EMoveType::Pause_Print: - case EMoveType::Custom_GCode: - case EMoveType::Retract: - case EMoveType::Unretract: - case EMoveType::Seam: - case EMoveType::Extrude: { - // use rounding to reduce the number of generated paths - return type == move.type && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && role == move.extrusion_role && - move.position.z() <= sub_paths.front().first.position.z() && feedrate == move.feedrate && fan_speed == move.fan_speed && - height == round_to_bin(move.height) && width == round_to_bin(move.width) && - matches_percent(volumetric_rate, move.volumetric_rate(), 0.05f); - } - case EMoveType::Travel: { - return type == move.type && feedrate == move.feedrate && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id; - } - default: { return false; } - } -} - -void GCodeViewer::TBuffer::Model::reset() -{ - instances.reset(); -} - -void GCodeViewer::TBuffer::reset() -{ - vertices.reset(); - for (IBuffer& buffer : indices) { - buffer.reset(); - } - - indices.clear(); - paths.clear(); - render_paths.clear(); - model.reset(); -} - -void GCodeViewer::TBuffer::add_path(const GCodeProcessorResult::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id) -{ - Path::Endpoint endpoint = { b_id, i_id, s_id, move.position }; - // use rounding to reduce the number of generated paths - paths.push_back({ move.type, move.extrusion_role, move.delta_extruder, - round_to_bin(move.height), round_to_bin(move.width), - move.feedrate, move.fan_speed, move.temperature, - move.volumetric_rate(), move.extruder_id, move.cp_color_id, { { endpoint, endpoint } } }); -} - -GCodeViewer::Color GCodeViewer::Extrusions::Range::get_color_at(float value) const -{ - // Input value scaled to the colors range - const float step = step_size(); - const float global_t = (step != 0.0f) ? std::max(0.0f, value - min) / step : 0.0f; // lower limit of 0.0f - - const size_t color_max_idx = Range_Colors.size() - 1; - - // Compute the two colors just below (low) and above (high) the input value - const size_t color_low_idx = std::clamp(static_cast(global_t), 0, color_max_idx); - const size_t color_high_idx = std::clamp(color_low_idx + 1, 0, color_max_idx); - - // Compute how far the value is between the low and high colors so that they can be interpolated - const float local_t = std::clamp(global_t - static_cast(color_low_idx), 0.0f, 1.0f); - - // Interpolate between the low and high colors to find exactly which color the input value should get - Color ret = { 0.0f, 0.0f, 0.0f, 1.0f }; - for (unsigned int i = 0; i < 3; ++i) { - ret[i] = lerp(Range_Colors[color_low_idx][i], Range_Colors[color_high_idx][i], local_t); - } - return ret; -} - -GCodeViewer::SequentialRangeCap::~SequentialRangeCap() { - if (ibo > 0) - glsafe(::glDeleteBuffers(1, &ibo)); -} - -void GCodeViewer::SequentialRangeCap::reset() { - if (ibo > 0) - glsafe(::glDeleteBuffers(1, &ibo)); - - buffer = nullptr; - ibo = 0; - vbo = 0; - color = { 0.0f, 0.0f, 0.0f, 1.0f }; -} - -void GCodeViewer::SequentialView::Marker::init() -{ - m_model.init_from(stilized_arrow(16, 2.0f, 4.0f, 1.0f, 8.0f)); - m_model.set_color(-1, { 1.0f, 1.0f, 1.0f, 0.5f }); -} - -void GCodeViewer::SequentialView::Marker::set_world_position(const Vec3f& position) -{ - m_world_position = position; - m_world_transform = (Geometry::assemble_transform((position + m_z_offset * Vec3f::UnitZ()).cast()) * Geometry::assemble_transform(m_model.get_bounding_box().size().z() * Vec3d::UnitZ(), { M_PI, 0.0, 0.0 })).cast(); -} - -void GCodeViewer::SequentialView::Marker::render() const -{ - if (!m_visible) - return; - - GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); - if (shader == nullptr) - return; - - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - - shader->start_using(); - shader->set_uniform("emission_factor", 0.0f); - - glsafe(::glPushMatrix()); - glsafe(::glMultMatrixf(m_world_transform.data())); - - m_model.render(); - - glsafe(::glPopMatrix()); - - shader->stop_using(); - - glsafe(::glDisable(GL_BLEND)); - - static float last_window_width = 0.0f; - static size_t last_text_length = 0; - - ImGuiWrapper& imgui = *wxGetApp().imgui(); - Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); - imgui.set_next_window_pos(0.5f * static_cast(cnv_size.get_width()), static_cast(cnv_size.get_height()), ImGuiCond_Always, 0.5f, 1.0f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - ImGui::SetNextWindowBgAlpha(0.25f); - imgui.begin(std::string("ToolPosition"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _u8L("Tool position") + ":"); - ImGui::SameLine(); - char buf[1024]; - const Vec3f position = m_world_position + m_world_offset; - sprintf(buf, "X: %.3f, Y: %.3f, Z: %.3f", position.x(), position.y(), position.z()); - imgui.text(std::string(buf)); - - // force extra frame to automatically update window size - float width = ImGui::GetWindowWidth(); - size_t length = strlen(buf); - if (width != last_window_width || length != last_text_length) { - last_window_width = width; - last_text_length = length; - imgui.set_requires_extra_frame(); - } - - imgui.end(); - ImGui::PopStyleVar(); -} - -void GCodeViewer::SequentialView::GCodeWindow::load_gcode(const std::string& filename, std::vector &&lines_ends) -{ - assert(! m_file.is_open()); - if (m_file.is_open()) - return; - - m_filename = filename; - m_lines_ends = std::move(lines_ends); - - m_selected_line_id = 0; - m_last_lines_size = 0; - - try - { - m_file.open(boost::filesystem::path(m_filename)); - } - catch (...) - { - BOOST_LOG_TRIVIAL(error) << "Unable to map file " << m_filename << ". Cannot show G-code window."; - reset(); - } -} - -void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, uint64_t curr_line_id) const -{ - auto update_lines = [this](uint64_t start_id, uint64_t end_id) { - std::vector ret; - ret.reserve(end_id - start_id + 1); - for (uint64_t id = start_id; id <= end_id; ++id) { - // read line from file - const size_t start = id == 1 ? 0 : m_lines_ends[id - 2]; - const size_t len = m_lines_ends[id - 1] - start; - std::string gline(m_file.data() + start, len); - - std::string command; - std::string parameters; - std::string comment; - - // extract comment - std::vector tokens; - boost::split(tokens, gline, boost::is_any_of(";"), boost::token_compress_on); - command = tokens.front(); - if (tokens.size() > 1) - comment = ";" + tokens.back(); - - // extract gcode command and parameters - if (!command.empty()) { - boost::split(tokens, command, boost::is_any_of(" "), boost::token_compress_on); - command = tokens.front(); - if (tokens.size() > 1) { - for (size_t i = 1; i < tokens.size(); ++i) { - parameters += " " + tokens[i]; - } - } - } - ret.push_back({ command, parameters, comment }); - } - return ret; - }; - - static const ImVec4 LINE_NUMBER_COLOR = ImGuiWrapper::COL_ORANGE_LIGHT; - static const ImVec4 SELECTION_RECT_COLOR = ImGuiWrapper::COL_ORANGE_DARK; - static const ImVec4 COMMAND_COLOR = { 0.8f, 0.8f, 0.0f, 1.0f }; - static const ImVec4 PARAMETERS_COLOR = { 1.0f, 1.0f, 1.0f, 1.0f }; - static const ImVec4 COMMENT_COLOR = { 0.7f, 0.7f, 0.7f, 1.0f }; - - if (!m_visible || m_filename.empty() || m_lines_ends.empty() || curr_line_id == 0) - return; - - // window height - const float wnd_height = bottom - top; - - // number of visible lines - const float text_height = ImGui::CalcTextSize("0").y; - const ImGuiStyle& style = ImGui::GetStyle(); - const uint64_t lines_count = static_cast((wnd_height - 2.0f * style.WindowPadding.y + style.ItemSpacing.y) / (text_height + style.ItemSpacing.y)); - - if (lines_count == 0) - return; - - // visible range - const uint64_t half_lines_count = lines_count / 2; - uint64_t start_id = (curr_line_id >= half_lines_count) ? curr_line_id - half_lines_count : 0; - uint64_t end_id = start_id + lines_count - 1; - if (end_id >= static_cast(m_lines_ends.size())) { - end_id = static_cast(m_lines_ends.size()) - 1; - start_id = end_id - lines_count + 1; - } - - // updates list of lines to show, if needed - if (m_selected_line_id != curr_line_id || m_last_lines_size != end_id - start_id + 1) { - try - { - *const_cast*>(&m_lines) = update_lines(start_id, end_id); - } - catch (...) - { - BOOST_LOG_TRIVIAL(error) << "Error while loading from file " << m_filename << ". Cannot show G-code window."; - return; - } - *const_cast(&m_selected_line_id) = curr_line_id; - *const_cast(&m_last_lines_size) = m_lines.size(); - } - - // line number's column width - const float id_width = ImGui::CalcTextSize(std::to_string(end_id).c_str()).x; - - ImGuiWrapper& imgui = *wxGetApp().imgui(); - - imgui.set_next_window_pos(0.0f, top, ImGuiCond_Always, 0.0f, 0.0f); - imgui.set_next_window_size(0.0f, wnd_height, ImGuiCond_Always); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - ImGui::SetNextWindowBgAlpha(0.6f); - imgui.begin(std::string("G-code"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); - - // center the text in the window by pushing down the first line - const float f_lines_count = static_cast(lines_count); - ImGui::SetCursorPosY(0.5f * (wnd_height - f_lines_count * text_height - (f_lines_count - 1.0f) * style.ItemSpacing.y)); - - // render text lines - for (uint64_t id = start_id; id <= end_id; ++id) { - const Line& line = m_lines[id - start_id]; - - // rect around the current selected line - if (id == curr_line_id) { - const float pos_y = ImGui::GetCursorScreenPos().y; - const float half_ItemSpacing_y = 0.5f * style.ItemSpacing.y; - const float half_padding_x = 0.5f * style.WindowPadding.x; - ImGui::GetWindowDrawList()->AddRect({ half_padding_x, pos_y - half_ItemSpacing_y }, - { ImGui::GetCurrentWindow()->Size.x - half_padding_x, pos_y + text_height + half_ItemSpacing_y }, - ImGui::GetColorU32(SELECTION_RECT_COLOR)); - } - - // render line number - const std::string id_str = std::to_string(id); - // spacer to right align text - ImGui::Dummy({ id_width - ImGui::CalcTextSize(id_str.c_str()).x, text_height }); - ImGui::SameLine(0.0f, 0.0f); - ImGui::PushStyleColor(ImGuiCol_Text, LINE_NUMBER_COLOR); - imgui.text(id_str); - ImGui::PopStyleColor(); - - if (!line.command.empty() || !line.comment.empty()) - ImGui::SameLine(); - - // render command - if (!line.command.empty()) { - ImGui::PushStyleColor(ImGuiCol_Text, COMMAND_COLOR); - imgui.text(line.command); - ImGui::PopStyleColor(); - } - - // render parameters - if (!line.parameters.empty()) { - ImGui::SameLine(0.0f, 0.0f); - ImGui::PushStyleColor(ImGuiCol_Text, PARAMETERS_COLOR); - imgui.text(line.parameters); - ImGui::PopStyleColor(); - } - - // render comment - if (!line.comment.empty()) { - if (!line.command.empty()) - ImGui::SameLine(0.0f, 0.0f); - ImGui::PushStyleColor(ImGuiCol_Text, COMMENT_COLOR); - imgui.text(line.comment); - ImGui::PopStyleColor(); - } - } - - imgui.end(); - ImGui::PopStyleVar(); -} - -void GCodeViewer::SequentialView::GCodeWindow::stop_mapping_file() -{ - if (m_file.is_open()) - m_file.close(); -} - -void GCodeViewer::SequentialView::render(float legend_height) const -{ - marker.render(); - float bottom = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size().get_height(); - if (wxGetApp().is_editor()) - bottom -= wxGetApp().plater()->get_view_toolbar().get_height(); - gcode_window.render(legend_height, bottom, static_cast(gcode_ids[current.last])); -} - -const std::vector GCodeViewer::Extrusion_Role_Colors {{ - { 0.90f, 0.70f, 0.70f, 1.0f }, // erNone - { 1.00f, 0.90f, 0.30f, 1.0f }, // erPerimeter - { 1.00f, 0.49f, 0.22f, 1.0f }, // erExternalPerimeter - { 0.12f, 0.12f, 1.00f, 1.0f }, // erOverhangPerimeter - { 0.69f, 0.19f, 0.16f, 1.0f }, // erInternalInfill - { 0.59f, 0.33f, 0.80f, 1.0f }, // erSolidInfill - { 0.94f, 0.25f, 0.25f, 1.0f }, // erTopSolidInfill - { 1.00f, 0.55f, 0.41f, 1.0f }, // erIroning - { 0.30f, 0.50f, 0.73f, 1.0f }, // erBridgeInfill - { 1.00f, 1.00f, 1.00f, 1.0f }, // erGapFill - { 0.00f, 0.53f, 0.43f, 1.0f }, // erSkirt - { 0.00f, 1.00f, 0.00f, 1.0f }, // erSupportMaterial - { 0.00f, 0.50f, 0.00f, 1.0f }, // erSupportMaterialInterface - { 0.70f, 0.89f, 0.67f, 1.0f }, // erWipeTower - { 0.37f, 0.82f, 0.58f, 1.0f }, // erCustom - { 0.00f, 0.00f, 0.00f, 1.0f } // erMixed -}}; - -const std::vector GCodeViewer::Options_Colors {{ - { 0.803f, 0.135f, 0.839f, 1.0f }, // Retractions - { 0.287f, 0.679f, 0.810f, 1.0f }, // Unretractions - { 0.900f, 0.900f, 0.900f, 1.0f }, // Seams - { 0.758f, 0.744f, 0.389f, 1.0f }, // ToolChanges - { 0.856f, 0.582f, 0.546f, 1.0f }, // ColorChanges - { 0.322f, 0.942f, 0.512f, 1.0f }, // PausePrints - { 0.886f, 0.825f, 0.262f, 1.0f } // CustomGCodes -}}; - -const std::vector GCodeViewer::Travel_Colors {{ - { 0.219f, 0.282f, 0.609f, 1.0f }, // Move - { 0.112f, 0.422f, 0.103f, 1.0f }, // Extrude - { 0.505f, 0.064f, 0.028f, 1.0f } // Retract -}}; - -#if 1 -// Normal ranges -const std::vector GCodeViewer::Range_Colors {{ - { 0.043f, 0.173f, 0.478f, 1.0f }, // bluish - { 0.075f, 0.349f, 0.522f, 1.0f }, - { 0.110f, 0.533f, 0.569f, 1.0f }, - { 0.016f, 0.839f, 0.059f, 1.0f }, - { 0.667f, 0.949f, 0.000f, 1.0f }, - { 0.988f, 0.975f, 0.012f, 1.0f }, - { 0.961f, 0.808f, 0.039f, 1.0f }, - { 0.890f, 0.533f, 0.125f, 1.0f }, - { 0.820f, 0.408f, 0.188f, 1.0f }, - { 0.761f, 0.322f, 0.235f, 1.0f }, - { 0.581f, 0.149f, 0.087f, 1.0f } // reddish -}}; -#else -// Detailed ranges -const std::vector GCodeViewer::Range_Colors{ { - { 0.043f, 0.173f, 0.478f, 1.0f }, // bluish - { 0.5f * (0.043f + 0.075f), 0.5f * (0.173f + 0.349f), 0.5f * (0.478f + 0.522f), 1.0f }, - { 0.075f, 0.349f, 0.522f, 1.0f }, - { 0.5f * (0.075f + 0.110f), 0.5f * (0.349f + 0.533f), 0.5f * (0.522f + 0.569f), 1.0f }, - { 0.110f, 0.533f, 0.569f, 1.0f }, - { 0.5f * (0.110f + 0.016f), 0.5f * (0.533f + 0.839f), 0.5f * (0.569f + 0.059f), 1.0f }, - { 0.016f, 0.839f, 0.059f, 1.0f }, - { 0.5f * (0.016f + 0.667f), 0.5f * (0.839f + 0.949f), 0.5f * (0.059f + 0.000f), 1.0f }, - { 0.667f, 0.949f, 0.000f, 1.0f }, - { 0.5f * (0.667f + 0.988f), 0.5f * (0.949f + 0.975f), 0.5f * (0.000f + 0.012f), 1.0f }, - { 0.988f, 0.975f, 0.012f, 1.0f }, - { 0.5f * (0.988f + 0.961f), 0.5f * (0.975f + 0.808f), 0.5f * (0.012f + 0.039f), 1.0f }, - { 0.961f, 0.808f, 0.039f, 1.0f }, - { 0.5f * (0.961f + 0.890f), 0.5f * (0.808f + 0.533f), 0.5f * (0.039f + 0.125f), 1.0f }, - { 0.890f, 0.533f, 0.125f, 1.0f }, - { 0.5f * (0.890f + 0.820f), 0.5f * (0.533f + 0.408f), 0.5f * (0.125f + 0.188f), 1.0f }, - { 0.820f, 0.408f, 0.188f, 1.0f }, - { 0.5f * (0.820f + 0.761f), 0.5f * (0.408f + 0.322f), 0.5f * (0.188f + 0.235f), 1.0f }, - { 0.761f, 0.322f, 0.235f, 1.0f }, - { 0.5f * (0.761f + 0.581f), 0.5f * (0.322f + 0.149f), 0.5f * (0.235f + 0.087f), 1.0f }, - { 0.581f, 0.149f, 0.087f, 1.0f } // reddishgit -} }; -#endif - -const GCodeViewer::Color GCodeViewer::Wipe_Color = { 1.0f, 1.0f, 0.0f, 1.0f }; -const GCodeViewer::Color GCodeViewer::Neutral_Color = { 0.25f, 0.25f, 0.25f, 1.0f }; - -GCodeViewer::GCodeViewer() -{ - m_extrusions.reset_role_visibility_flags(); - -// m_sequential_view.skip_invisible_moves = true; -} - -void GCodeViewer::init() -{ - if (m_gl_data_initialized) - return; - - // initializes opengl data of TBuffers - for (size_t i = 0; i < m_buffers.size(); ++i) { - TBuffer& buffer = m_buffers[i]; - EMoveType type = buffer_type(i); - switch (type) - { - default: { break; } - case EMoveType::Tool_change: - case EMoveType::Color_change: - case EMoveType::Pause_Print: - case EMoveType::Custom_GCode: - case EMoveType::Retract: - case EMoveType::Unretract: - case EMoveType::Seam: { -// if (wxGetApp().is_gl_version_greater_or_equal_to(3, 3)) { -// buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::InstancedModel; -// buffer.shader = "gouraud_light_instanced"; -// buffer.model.model.init_from(diamond(16)); -// buffer.model.color = option_color(type); -// buffer.model.instances.format = InstanceVBuffer::EFormat::InstancedModel; -// } -// else { - buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::BatchedModel; - buffer.vertices.format = VBuffer::EFormat::PositionNormal3; - buffer.shader = "gouraud_light"; - - buffer.model.data = diamond(16); - buffer.model.color = option_color(type); - buffer.model.instances.format = InstanceVBuffer::EFormat::BatchedModel; -// } - break; - } - case EMoveType::Wipe: - case EMoveType::Extrude: { - buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Triangle; - buffer.vertices.format = VBuffer::EFormat::PositionNormal3; - buffer.shader = "gouraud_light"; - break; - } - case EMoveType::Travel: { - buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Line; - buffer.vertices.format = VBuffer::EFormat::PositionNormal3; - buffer.shader = "toolpaths_lines"; - break; - } - } - - set_toolpath_move_type_visible(EMoveType::Extrude, true); - } - - // initializes tool marker - m_sequential_view.marker.init(); - - // initializes point sizes - std::array point_sizes; - ::glGetIntegerv(GL_ALIASED_POINT_SIZE_RANGE, point_sizes.data()); - m_detected_point_sizes = { static_cast(point_sizes[0]), static_cast(point_sizes[1]) }; - - m_gl_data_initialized = true; -} - -void GCodeViewer::load(const GCodeProcessorResult& gcode_result, const Print& print, bool initialized) -{ - // avoid processing if called with the same gcode_result - if (m_last_result_id == gcode_result.id) - return; - - m_last_result_id = gcode_result.id; - - // release gpu memory, if used - reset(); - - m_sequential_view.gcode_window.load_gcode(gcode_result.filename, - // Stealing out lines_ends should be safe because this gcode_result is processed only once (see the 1st if in this function). - std::move(const_cast&>(gcode_result.lines_ends))); - - if (wxGetApp().is_gcode_viewer()) - m_custom_gcode_per_print_z = gcode_result.custom_gcode_per_print_z; - - m_max_print_height = gcode_result.max_print_height; - - load_toolpaths(gcode_result); - - if (m_layers.empty()) - return; - - m_settings_ids = gcode_result.settings_ids; - m_filament_diameters = gcode_result.filament_diameters; - m_filament_densities = gcode_result.filament_densities; - - if (wxGetApp().is_editor()) - load_shells(print, initialized); - else { - Pointfs bed_shape; - std::string texture; - std::string model; - - if (!gcode_result.bed_shape.empty()) { - // bed shape detected in the gcode - bed_shape = gcode_result.bed_shape; - const auto bundle = wxGetApp().preset_bundle; - if (bundle != nullptr && !m_settings_ids.printer.empty()) { - const Preset* preset = bundle->printers.find_preset(m_settings_ids.printer); - if (preset != nullptr) { - model = PresetUtils::system_printer_bed_model(*preset); - texture = PresetUtils::system_printer_bed_texture(*preset); - } - } - } - else { - // adjust printbed size in dependence of toolpaths bbox - const double margin = 10.0; - const Vec2d min(m_paths_bounding_box.min.x() - margin, m_paths_bounding_box.min.y() - margin); - const Vec2d max(m_paths_bounding_box.max.x() + margin, m_paths_bounding_box.max.y() + margin); - - const Vec2d size = max - min; - bed_shape = { - { min.x(), min.y() }, - { max.x(), min.y() }, - { max.x(), min.y() + 0.442265 * size.y()}, - { max.x() - 10.0, min.y() + 0.4711325 * size.y()}, - { max.x() + 10.0, min.y() + 0.5288675 * size.y()}, - { max.x(), min.y() + 0.557735 * size.y()}, - { max.x(), max.y() }, - { min.x() + 0.557735 * size.x(), max.y()}, - { min.x() + 0.5288675 * size.x(), max.y() - 10.0}, - { min.x() + 0.4711325 * size.x(), max.y() + 10.0}, - { min.x() + 0.442265 * size.x(), max.y()}, - { min.x(), max.y() } }; - } - - wxGetApp().plater()->set_bed_shape(bed_shape, gcode_result.max_print_height, texture, model, gcode_result.bed_shape.empty()); - } - - m_print_statistics = gcode_result.print_statistics; - - if (m_time_estimate_mode != PrintEstimatedStatistics::ETimeMode::Normal) { - const float time = m_print_statistics.modes[static_cast(m_time_estimate_mode)].time; - if (time == 0.0f || - short_time(get_time_dhms(time)) == short_time(get_time_dhms(m_print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time))) - m_time_estimate_mode = PrintEstimatedStatistics::ETimeMode::Normal; - } -} - -void GCodeViewer::refresh(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors) -{ -#if ENABLE_GCODE_VIEWER_STATISTICS - auto start_time = std::chrono::high_resolution_clock::now(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - if (m_moves_count == 0) - return; - - wxBusyCursor busy; - - if (m_view_type == EViewType::Tool && !gcode_result.extruder_colors.empty()) - // update tool colors from config stored in the gcode - m_tool_colors = decode_colors(gcode_result.extruder_colors); - else - // update tool colors - m_tool_colors = decode_colors(str_tool_colors); - - // ensure there are enough colors defined - while (m_tool_colors.size() < std::max(size_t(1), gcode_result.extruders_count)) - m_tool_colors.push_back(decode_color("#FF8000")); - - // update ranges for coloring / legend - m_extrusions.reset_ranges(); - for (size_t i = 0; i < m_moves_count; ++i) { - // skip first vertex - if (i == 0) - continue; - - const GCodeProcessorResult::MoveVertex& curr = gcode_result.moves[i]; - - switch (curr.type) - { - case EMoveType::Extrude: - { - m_extrusions.ranges.height.update_from(round_to_bin(curr.height)); - m_extrusions.ranges.width.update_from(round_to_bin(curr.width)); - m_extrusions.ranges.fan_speed.update_from(curr.fan_speed); - m_extrusions.ranges.temperature.update_from(curr.temperature); - if (curr.extrusion_role != erCustom || is_visible(erCustom)) - m_extrusions.ranges.volumetric_rate.update_from(round_to_bin(curr.volumetric_rate())); - [[fallthrough]]; - } - case EMoveType::Travel: - { - if (m_buffers[buffer_id(curr.type)].visible) - m_extrusions.ranges.feedrate.update_from(curr.feedrate); - - break; - } - default: { break; } - } - } - -#if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.refresh_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - // update buffers' render paths - refresh_render_paths(); - log_memory_used("Refreshed G-code extrusion paths, "); -} - -void GCodeViewer::refresh_render_paths() -{ - refresh_render_paths(false, false); -} - -void GCodeViewer::update_shells_color_by_extruder(const DynamicPrintConfig* config) -{ - if (config != nullptr) - m_shells.volumes.update_colors_by_extruder(config); -} - -void GCodeViewer::reset() -{ - m_moves_count = 0; - for (TBuffer& buffer : m_buffers) { - buffer.reset(); - } - - m_paths_bounding_box = BoundingBoxf3(); - m_max_bounding_box = BoundingBoxf3(); - m_max_print_height = 0.0f; - m_tool_colors = std::vector(); - m_extruders_count = 0; - m_extruder_ids = std::vector(); - m_filament_diameters = std::vector(); - m_filament_densities = std::vector(); - m_extrusions.reset_ranges(); - m_shells.volumes.clear(); - m_layers.reset(); - m_layers_z_range = { 0, 0 }; - m_roles = std::vector(); - m_print_statistics.reset(); - m_custom_gcode_per_print_z = std::vector(); - m_sequential_view.gcode_window.reset(); -#if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.reset_all(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - m_contained_in_bed = true; -} - -void GCodeViewer::render() -{ -#if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.reset_opengl(); - m_statistics.total_instances_gpu_size = 0; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - if (m_roles.empty()) - return; - - glsafe(::glEnable(GL_DEPTH_TEST)); - render_toolpaths(); - render_shells(); - float legend_height = 0.0f; - render_legend(legend_height); - if (m_sequential_view.current.last != m_sequential_view.endpoints.last) { - m_sequential_view.marker.set_world_position(m_sequential_view.current_position); - m_sequential_view.marker.set_world_offset(m_sequential_view.current_offset); - m_sequential_view.render(legend_height); - } -#if ENABLE_GCODE_VIEWER_STATISTICS - render_statistics(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS -} - -bool GCodeViewer::can_export_toolpaths() const -{ - return has_data() && m_buffers[buffer_id(EMoveType::Extrude)].render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle; -} - -void GCodeViewer::update_sequential_view_current(unsigned int first, unsigned int last) -{ - auto is_visible = [this](unsigned int id) { - for (const TBuffer& buffer : m_buffers) { - if (buffer.visible) { - for (const Path& path : buffer.paths) { - if (path.sub_paths.front().first.s_id <= id && id <= path.sub_paths.back().last.s_id) - return true; - } - } - } - return false; - }; - - const int first_diff = static_cast(first) - static_cast(m_sequential_view.last_current.first); - const int last_diff = static_cast(last) - static_cast(m_sequential_view.last_current.last); - - unsigned int new_first = first; - unsigned int new_last = last; - - if (m_sequential_view.skip_invisible_moves) { - while (!is_visible(new_first)) { - if (first_diff > 0) - ++new_first; - else - --new_first; - } - - while (!is_visible(new_last)) { - if (last_diff > 0) - ++new_last; - else - --new_last; - } - } - - m_sequential_view.current.first = new_first; - m_sequential_view.current.last = new_last; - m_sequential_view.last_current = m_sequential_view.current; - - refresh_render_paths(true, true); - - if (new_first != first || new_last != last) - wxGetApp().plater()->update_preview_moves_slider(); -} - -bool GCodeViewer::is_toolpath_move_type_visible(EMoveType type) const -{ - size_t id = static_cast(buffer_id(type)); - return (id < m_buffers.size()) ? m_buffers[id].visible : false; -} - -void GCodeViewer::set_toolpath_move_type_visible(EMoveType type, bool visible) -{ - size_t id = static_cast(buffer_id(type)); - if (id < m_buffers.size()) - m_buffers[id].visible = visible; -} - -unsigned int GCodeViewer::get_options_visibility_flags() const -{ - auto set_flag = [](unsigned int flags, unsigned int flag, bool active) { - return active ? (flags | (1 << flag)) : flags; - }; - - unsigned int flags = 0; - flags = set_flag(flags, static_cast(Preview::OptionType::Travel), is_toolpath_move_type_visible(EMoveType::Travel)); - flags = set_flag(flags, static_cast(Preview::OptionType::Wipe), is_toolpath_move_type_visible(EMoveType::Wipe)); - flags = set_flag(flags, static_cast(Preview::OptionType::Retractions), is_toolpath_move_type_visible(EMoveType::Retract)); - flags = set_flag(flags, static_cast(Preview::OptionType::Unretractions), is_toolpath_move_type_visible(EMoveType::Unretract)); - flags = set_flag(flags, static_cast(Preview::OptionType::Seams), is_toolpath_move_type_visible(EMoveType::Seam)); - flags = set_flag(flags, static_cast(Preview::OptionType::ToolChanges), is_toolpath_move_type_visible(EMoveType::Tool_change)); - flags = set_flag(flags, static_cast(Preview::OptionType::ColorChanges), is_toolpath_move_type_visible(EMoveType::Color_change)); - flags = set_flag(flags, static_cast(Preview::OptionType::PausePrints), is_toolpath_move_type_visible(EMoveType::Pause_Print)); - flags = set_flag(flags, static_cast(Preview::OptionType::CustomGCodes), is_toolpath_move_type_visible(EMoveType::Custom_GCode)); - flags = set_flag(flags, static_cast(Preview::OptionType::Shells), m_shells.visible); - flags = set_flag(flags, static_cast(Preview::OptionType::ToolMarker), m_sequential_view.marker.is_visible()); - flags = set_flag(flags, static_cast(Preview::OptionType::Legend), is_legend_enabled()); - return flags; -} - -void GCodeViewer::set_options_visibility_from_flags(unsigned int flags) -{ - auto is_flag_set = [flags](unsigned int flag) { - return (flags & (1 << flag)) != 0; - }; - - set_toolpath_move_type_visible(EMoveType::Travel, is_flag_set(static_cast(Preview::OptionType::Travel))); - set_toolpath_move_type_visible(EMoveType::Wipe, is_flag_set(static_cast(Preview::OptionType::Wipe))); - set_toolpath_move_type_visible(EMoveType::Retract, is_flag_set(static_cast(Preview::OptionType::Retractions))); - set_toolpath_move_type_visible(EMoveType::Unretract, is_flag_set(static_cast(Preview::OptionType::Unretractions))); - set_toolpath_move_type_visible(EMoveType::Seam, is_flag_set(static_cast(Preview::OptionType::Seams))); - set_toolpath_move_type_visible(EMoveType::Tool_change, is_flag_set(static_cast(Preview::OptionType::ToolChanges))); - set_toolpath_move_type_visible(EMoveType::Color_change, is_flag_set(static_cast(Preview::OptionType::ColorChanges))); - set_toolpath_move_type_visible(EMoveType::Pause_Print, is_flag_set(static_cast(Preview::OptionType::PausePrints))); - set_toolpath_move_type_visible(EMoveType::Custom_GCode, is_flag_set(static_cast(Preview::OptionType::CustomGCodes))); - m_shells.visible = is_flag_set(static_cast(Preview::OptionType::Shells)); - m_sequential_view.marker.set_visible(is_flag_set(static_cast(Preview::OptionType::ToolMarker))); - enable_legend(is_flag_set(static_cast(Preview::OptionType::Legend))); -} - -void GCodeViewer::set_layers_z_range(const std::array& layers_z_range) -{ - bool keep_sequential_current_first = layers_z_range[0] >= m_layers_z_range[0]; - bool keep_sequential_current_last = layers_z_range[1] <= m_layers_z_range[1]; - m_layers_z_range = layers_z_range; - refresh_render_paths(keep_sequential_current_first, keep_sequential_current_last); - wxGetApp().plater()->update_preview_moves_slider(); -} - -void GCodeViewer::export_toolpaths_to_obj(const char* filename) const -{ - if (filename == nullptr) - return; - - if (!has_data()) - return; - - wxBusyCursor busy; - - // the data needed is contained into the Extrude TBuffer - const TBuffer& t_buffer = m_buffers[buffer_id(EMoveType::Extrude)]; - if (!t_buffer.has_data()) - return; - - if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Triangle) - return; - - // collect color information to generate materials - std::vector colors; - for (const RenderPath& path : t_buffer.render_paths) { - colors.push_back(path.color); - } - sort_remove_duplicates(colors); - - // save materials file - boost::filesystem::path mat_filename(filename); - mat_filename.replace_extension("mtl"); - - CNumericLocalesSetter locales_setter; - - FILE* fp = boost::nowide::fopen(mat_filename.string().c_str(), "w"); - if (fp == nullptr) { - BOOST_LOG_TRIVIAL(error) << "GCodeViewer::export_toolpaths_to_obj: Couldn't open " << mat_filename.string().c_str() << " for writing"; - return; - } - - fprintf(fp, "# G-Code Toolpaths Materials\n"); - fprintf(fp, "# Generated by %s-%s based on Slic3r\n", SLIC3R_APP_NAME, SLIC3R_VERSION); - - unsigned int colors_count = 1; - for (const Color& color : colors) { - fprintf(fp, "\nnewmtl material_%d\n", colors_count++); - fprintf(fp, "Ka 1 1 1\n"); - fprintf(fp, "Kd %g %g %g\n", color[0], color[1], color[2]); - fprintf(fp, "Ks 0 0 0\n"); - } - - fclose(fp); - - // save geometry file - fp = boost::nowide::fopen(filename, "w"); - if (fp == nullptr) { - BOOST_LOG_TRIVIAL(error) << "GCodeViewer::export_toolpaths_to_obj: Couldn't open " << filename << " for writing"; - return; - } - - fprintf(fp, "# G-Code Toolpaths\n"); - fprintf(fp, "# Generated by %s-%s based on Slic3r\n", SLIC3R_APP_NAME, SLIC3R_VERSION); - fprintf(fp, "\nmtllib ./%s\n", mat_filename.filename().string().c_str()); - - const size_t floats_per_vertex = t_buffer.vertices.vertex_size_floats(); - - std::vector out_vertices; - std::vector out_normals; - - struct VerticesOffset - { - unsigned int vbo; - size_t offset; - }; - std::vector vertices_offsets; - vertices_offsets.push_back({ t_buffer.vertices.vbos.front(), 0 }); - - // get vertices/normals data from vertex buffers on gpu - for (size_t i = 0; i < t_buffer.vertices.vbos.size(); ++i) { - const size_t floats_count = t_buffer.vertices.sizes[i] / sizeof(float); - VertexBuffer vertices(floats_count); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, t_buffer.vertices.vbos[i])); - glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, 0, static_cast(t_buffer.vertices.sizes[i]), static_cast(vertices.data()))); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - const size_t vertices_count = floats_count / floats_per_vertex; - for (size_t j = 0; j < vertices_count; ++j) { - const size_t base = j * floats_per_vertex; - out_vertices.push_back({ vertices[base + 0], vertices[base + 1], vertices[base + 2] }); - out_normals.push_back({ vertices[base + 3], vertices[base + 4], vertices[base + 5] }); - } - - if (i < t_buffer.vertices.vbos.size() - 1) - vertices_offsets.push_back({ t_buffer.vertices.vbos[i + 1], vertices_offsets.back().offset + vertices_count }); - } - - // save vertices to file - fprintf(fp, "\n# vertices\n"); - for (const Vec3f& v : out_vertices) { - fprintf(fp, "v %g %g %g\n", v.x(), v.y(), v.z()); - } - - // save normals to file - fprintf(fp, "\n# normals\n"); - for (const Vec3f& n : out_normals) { - fprintf(fp, "vn %g %g %g\n", n.x(), n.y(), n.z()); - } - - size_t i = 0; - for (const Color& color : colors) { - // save material triangles to file - fprintf(fp, "\nusemtl material_%zu\n", i + 1); - fprintf(fp, "# triangles material %zu\n", i + 1); - - for (const RenderPath& render_path : t_buffer.render_paths) { - if (render_path.color != color) - continue; - - const IBuffer& ibuffer = t_buffer.indices[render_path.ibuffer_id]; - size_t vertices_offset = 0; - for (size_t j = 0; j < vertices_offsets.size(); ++j) { - const VerticesOffset& offset = vertices_offsets[j]; - if (offset.vbo == ibuffer.vbo) { - vertices_offset = offset.offset; - break; - } - } - - // get indices data from index buffer on gpu - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibuffer.ibo)); - for (size_t j = 0; j < render_path.sizes.size(); ++j) { - IndexBuffer indices(render_path.sizes[j]); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast(render_path.offsets[j]), - static_cast(render_path.sizes[j] * sizeof(IBufferType)), static_cast(indices.data()))); - - const size_t triangles_count = render_path.sizes[j] / 3; - for (size_t k = 0; k < triangles_count; ++k) { - const size_t base = k * 3; - const size_t v1 = 1 + static_cast(indices[base + 0]) + vertices_offset; - const size_t v2 = 1 + static_cast(indices[base + 1]) + vertices_offset; - const size_t v3 = 1 + static_cast(indices[base + 2]) + vertices_offset; - if (v1 != v2) - // do not export dummy triangles - fprintf(fp, "f %zu//%zu %zu//%zu %zu//%zu\n", v1, v1, v2, v2, v3, v3); - } - } - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - } - ++i; - } - - fclose(fp); -} - -void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) -{ - // max index buffer size, in bytes - static const size_t IBUFFER_THRESHOLD_BYTES = 64 * 1024 * 1024; - - auto log_memory_usage = [this](const std::string& label, const std::vector& vertices, const std::vector& indices) { - int64_t vertices_size = 0; - for (const MultiVertexBuffer& buffers : vertices) { - for (const VertexBuffer& buffer : buffers) { - vertices_size += SLIC3R_STDVEC_MEMSIZE(buffer, float); - } - } - int64_t indices_size = 0; - for (const MultiIndexBuffer& buffers : indices) { - for (const IndexBuffer& buffer : buffers) { - indices_size += SLIC3R_STDVEC_MEMSIZE(buffer, IBufferType); - } - } - log_memory_used(label, vertices_size + indices_size); - }; - - // format data into the buffers to be rendered as points - auto add_vertices_as_point = [](const GCodeProcessorResult::MoveVertex& curr, VertexBuffer& vertices) { - vertices.push_back(curr.position.x()); - vertices.push_back(curr.position.y()); - vertices.push_back(curr.position.z()); - }; - auto add_indices_as_point = [](const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer, - unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { - buffer.add_path(curr, ibuffer_id, indices.size(), move_id); - indices.push_back(static_cast(indices.size())); - }; - - // format data into the buffers to be rendered as lines - auto add_vertices_as_line = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, VertexBuffer& vertices) { - // x component of the normal to the current segment (the normal is parallel to the XY plane) - const Vec3f dir = (curr.position - prev.position).normalized(); - Vec3f normal(dir.y(), -dir.x(), 0.0); - normal.normalize(); - - auto add_vertex = [&vertices, &normal](const GCodeProcessorResult::MoveVertex& vertex) { - // add position - vertices.push_back(vertex.position.x()); - vertices.push_back(vertex.position.y()); - vertices.push_back(vertex.position.z()); - // add normal - vertices.push_back(normal.x()); - vertices.push_back(normal.y()); - vertices.push_back(normal.z()); - }; - - // add previous vertex - add_vertex(prev); - // add current vertex - add_vertex(curr); - }; - auto add_indices_as_line = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer, - unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { - if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr)) { - // add starting index - indices.push_back(static_cast(indices.size())); - buffer.add_path(curr, ibuffer_id, indices.size() - 1, move_id - 1); - buffer.paths.back().sub_paths.front().first.position = prev.position; - } - - Path& last_path = buffer.paths.back(); - if (last_path.sub_paths.front().first.i_id != last_path.sub_paths.back().last.i_id) { - // add previous index - indices.push_back(static_cast(indices.size())); - } - - // add current index - indices.push_back(static_cast(indices.size())); - last_path.sub_paths.back().last = { ibuffer_id, indices.size() - 1, move_id, curr.position }; - }; - - // format data into the buffers to be rendered as solid - auto add_vertices_as_solid = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer, unsigned int vbuffer_id, VertexBuffer& vertices, size_t move_id) { - auto store_vertex = [](VertexBuffer& vertices, const Vec3f& position, const Vec3f& normal) { - // append position - vertices.push_back(position.x()); - vertices.push_back(position.y()); - vertices.push_back(position.z()); - // append normal - vertices.push_back(normal.x()); - vertices.push_back(normal.y()); - vertices.push_back(normal.z()); - }; - - if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr)) { - buffer.add_path(curr, vbuffer_id, vertices.size(), move_id - 1); - buffer.paths.back().sub_paths.back().first.position = prev.position; - } - - Path& last_path = buffer.paths.back(); - - const Vec3f dir = (curr.position - prev.position).normalized(); - const Vec3f right = Vec3f(dir.y(), -dir.x(), 0.0f).normalized(); - const Vec3f left = -right; - const Vec3f up = right.cross(dir); - const Vec3f down = -up; - const float half_width = 0.5f * last_path.width; - const float half_height = 0.5f * last_path.height; - const Vec3f prev_pos = prev.position - half_height * up; - const Vec3f curr_pos = curr.position - half_height * up; - const Vec3f d_up = half_height * up; - const Vec3f d_down = -half_height * up; - const Vec3f d_right = half_width * right; - const Vec3f d_left = -half_width * right; - - // vertices 1st endpoint - if (last_path.vertices_count() == 1 || vertices.empty()) { - // 1st segment or restart into a new vertex buffer - // =============================================== - store_vertex(vertices, prev_pos + d_up, up); - store_vertex(vertices, prev_pos + d_right, right); - store_vertex(vertices, prev_pos + d_down, down); - store_vertex(vertices, prev_pos + d_left, left); - } - else { - // any other segment - // ================= - store_vertex(vertices, prev_pos + d_right, right); - store_vertex(vertices, prev_pos + d_left, left); - } - - // vertices 2nd endpoint - store_vertex(vertices, curr_pos + d_up, up); - store_vertex(vertices, curr_pos + d_right, right); - store_vertex(vertices, curr_pos + d_down, down); - store_vertex(vertices, curr_pos + d_left, left); - - last_path.sub_paths.back().last = { vbuffer_id, vertices.size(), move_id, curr.position }; - }; - auto add_indices_as_solid = [&](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, const GCodeProcessorResult::MoveVertex* next, - TBuffer& buffer, size_t& vbuffer_size, unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { - static Vec3f prev_dir; - static Vec3f prev_up; - static float sq_prev_length; - auto store_triangle = [](IndexBuffer& indices, IBufferType i1, IBufferType i2, IBufferType i3) { - indices.push_back(i1); - indices.push_back(i2); - indices.push_back(i3); - }; - auto append_dummy_cap = [store_triangle](IndexBuffer& indices, IBufferType id) { - store_triangle(indices, id, id, id); - store_triangle(indices, id, id, id); - }; - auto convert_vertices_offset = [](size_t vbuffer_size, const std::array& v_offsets) { - std::array ret = { - static_cast(static_cast(vbuffer_size) + v_offsets[0]), - static_cast(static_cast(vbuffer_size) + v_offsets[1]), - static_cast(static_cast(vbuffer_size) + v_offsets[2]), - static_cast(static_cast(vbuffer_size) + v_offsets[3]), - static_cast(static_cast(vbuffer_size) + v_offsets[4]), - static_cast(static_cast(vbuffer_size) + v_offsets[5]), - static_cast(static_cast(vbuffer_size) + v_offsets[6]), - static_cast(static_cast(vbuffer_size) + v_offsets[7]) - }; - return ret; - }; - auto append_starting_cap_triangles = [&](IndexBuffer& indices, const std::array& v_offsets) { - store_triangle(indices, v_offsets[0], v_offsets[2], v_offsets[1]); - store_triangle(indices, v_offsets[0], v_offsets[3], v_offsets[2]); - }; - auto append_stem_triangles = [&](IndexBuffer& indices, const std::array& v_offsets) { - store_triangle(indices, v_offsets[0], v_offsets[1], v_offsets[4]); - store_triangle(indices, v_offsets[1], v_offsets[5], v_offsets[4]); - store_triangle(indices, v_offsets[1], v_offsets[2], v_offsets[5]); - store_triangle(indices, v_offsets[2], v_offsets[6], v_offsets[5]); - store_triangle(indices, v_offsets[2], v_offsets[3], v_offsets[6]); - store_triangle(indices, v_offsets[3], v_offsets[7], v_offsets[6]); - store_triangle(indices, v_offsets[3], v_offsets[0], v_offsets[7]); - store_triangle(indices, v_offsets[0], v_offsets[4], v_offsets[7]); - }; - auto append_ending_cap_triangles = [&](IndexBuffer& indices, const std::array& v_offsets) { - store_triangle(indices, v_offsets[4], v_offsets[6], v_offsets[7]); - store_triangle(indices, v_offsets[4], v_offsets[5], v_offsets[6]); - }; - - if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr)) { - buffer.add_path(curr, ibuffer_id, indices.size(), move_id - 1); - buffer.paths.back().sub_paths.back().first.position = prev.position; - } - - Path& last_path = buffer.paths.back(); - - const Vec3f dir = (curr.position - prev.position).normalized(); - const Vec3f right = Vec3f(dir.y(), -dir.x(), 0.0f).normalized(); - const Vec3f up = right.cross(dir); - const float sq_length = (curr.position - prev.position).squaredNorm(); - - const std::array first_seg_v_offsets = convert_vertices_offset(vbuffer_size, { 0, 1, 2, 3, 4, 5, 6, 7 }); - const std::array non_first_seg_v_offsets = convert_vertices_offset(vbuffer_size, { -4, 0, -2, 1, 2, 3, 4, 5 }); - const bool is_first_segment = (last_path.vertices_count() == 1); - if (is_first_segment || vbuffer_size == 0) { - // 1st segment or restart into a new vertex buffer - // =============================================== - if (is_first_segment) - // starting cap triangles - append_starting_cap_triangles(indices, first_seg_v_offsets); - // dummy triangles outer corner cap - append_dummy_cap(indices, vbuffer_size); - - // stem triangles - append_stem_triangles(indices, first_seg_v_offsets); - - vbuffer_size += 8; - } - else { - // any other segment - // ================= - float displacement = 0.0f; - const float cos_dir = prev_dir.dot(dir); - if (cos_dir > -0.9998477f) { - // if the angle between adjacent segments is smaller than 179 degrees - const Vec3f med_dir = (prev_dir + dir).normalized(); - const float half_width = 0.5f * last_path.width; - displacement = half_width * ::tan(::acos(std::clamp(dir.dot(med_dir), -1.0f, 1.0f))); - } - - const float sq_displacement = sqr(displacement); - const bool can_displace = displacement > 0.0f && sq_displacement < sq_prev_length && sq_displacement < sq_length; - - const bool is_right_turn = prev_up.dot(prev_dir.cross(dir)) <= 0.0f; - // whether the angle between adjacent segments is greater than 45 degrees - const bool is_sharp = cos_dir < 0.7071068f; - - bool right_displaced = false; - bool left_displaced = false; - - if (!is_sharp && can_displace) { - if (is_right_turn) - left_displaced = true; - else - right_displaced = true; - } - - // triangles outer corner cap - if (is_right_turn) { - if (left_displaced) - // dummy triangles - append_dummy_cap(indices, vbuffer_size); - else { - store_triangle(indices, vbuffer_size - 4, vbuffer_size + 1, vbuffer_size - 1); - store_triangle(indices, vbuffer_size + 1, vbuffer_size - 2, vbuffer_size - 1); - } - } - else { - if (right_displaced) - // dummy triangles - append_dummy_cap(indices, vbuffer_size); - else { - store_triangle(indices, vbuffer_size - 4, vbuffer_size - 3, vbuffer_size + 0); - store_triangle(indices, vbuffer_size - 3, vbuffer_size - 2, vbuffer_size + 0); - } - } - - // stem triangles - append_stem_triangles(indices, non_first_seg_v_offsets); - - vbuffer_size += 6; - } - - if (next != nullptr && (curr.type != next->type || !last_path.matches(*next))) - // ending cap triangles - append_ending_cap_triangles(indices, is_first_segment ? first_seg_v_offsets : non_first_seg_v_offsets); - - last_path.sub_paths.back().last = { ibuffer_id, indices.size() - 1, move_id, curr.position }; - prev_dir = dir; - prev_up = up; - sq_prev_length = sq_length; - }; - - // format data into the buffers to be rendered as instanced model - auto add_model_instance = [](const GCodeProcessorResult::MoveVertex& curr, InstanceBuffer& instances, InstanceIdBuffer& instances_ids, size_t move_id) { - // append position - instances.push_back(curr.position.x()); - instances.push_back(curr.position.y()); - instances.push_back(curr.position.z()); - // append width - instances.push_back(curr.width); - // append height - instances.push_back(curr.height); - - // append id - instances_ids.push_back(move_id); - }; - - // format data into the buffers to be rendered as batched model - auto add_vertices_as_model_batch = [](const GCodeProcessorResult::MoveVertex& curr, const GLModel::InitializationData& data, VertexBuffer& vertices, InstanceBuffer& instances, InstanceIdBuffer& instances_ids, size_t move_id) { - const double width = static_cast(1.5f * curr.width); - const double height = static_cast(1.5f * curr.height); - - const Transform3d trafo = Geometry::assemble_transform((curr.position - 0.5f * curr.height * Vec3f::UnitZ()).cast(), Vec3d::Zero(), { width, width, height }); - const Eigen::Matrix normal_matrix = trafo.matrix().template block<3, 3>(0, 0).inverse().transpose(); - - for (const auto& entity : data.entities) { - // append vertices - for (size_t i = 0; i < entity.positions.size(); ++i) { - // append position - const Vec3d position = trafo * entity.positions[i].cast(); - vertices.push_back(static_cast(position.x())); - vertices.push_back(static_cast(position.y())); - vertices.push_back(static_cast(position.z())); - - // append normal - const Vec3d normal = normal_matrix * entity.normals[i].cast(); - vertices.push_back(static_cast(normal.x())); - vertices.push_back(static_cast(normal.y())); - vertices.push_back(static_cast(normal.z())); - } - } - - // append instance position - instances.push_back(curr.position.x()); - instances.push_back(curr.position.y()); - instances.push_back(curr.position.z()); - // append instance id - instances_ids.push_back(move_id); - }; - - auto add_indices_as_model_batch = [](const GLModel::InitializationData& data, IndexBuffer& indices, IBufferType base_index) { - for (const auto& entity : data.entities) { - for (size_t i = 0; i < entity.indices.size(); ++i) { - indices.push_back(static_cast(entity.indices[i] + base_index)); - } - } - }; - -#if ENABLE_GCODE_VIEWER_STATISTICS - auto start_time = std::chrono::high_resolution_clock::now(); - m_statistics.results_size = SLIC3R_STDVEC_MEMSIZE(gcode_result.moves, GCodeProcessorResult::MoveVertex); - m_statistics.results_time = gcode_result.time; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - m_moves_count = gcode_result.moves.size(); - if (m_moves_count == 0) - return; - - m_extruders_count = gcode_result.extruders_count; - - unsigned int progress_count = 0; - static const unsigned int progress_threshold = 1000; - wxProgressDialog* progress_dialog = wxGetApp().is_gcode_viewer() ? - new wxProgressDialog(_L("Generating toolpaths"), "...", - 100, wxGetApp().mainframe, wxPD_AUTO_HIDE | wxPD_APP_MODAL) : nullptr; - - wxBusyCursor busy; - - // extract approximate paths bounding box from result - for (const GCodeProcessorResult::MoveVertex& move : gcode_result.moves) { - if (wxGetApp().is_gcode_viewer()) - // for the gcode viewer we need to take in account all moves to correctly size the printbed - m_paths_bounding_box.merge(move.position.cast()); - else { - if (move.type == EMoveType::Extrude && move.extrusion_role != erCustom && move.width != 0.0f && move.height != 0.0f) - m_paths_bounding_box.merge(move.position.cast()); - } - } - - // set approximate max bounding box (take in account also the tool marker) - m_max_bounding_box = m_paths_bounding_box; - m_max_bounding_box.merge(m_paths_bounding_box.max + m_sequential_view.marker.get_bounding_box().size().z() * Vec3d::UnitZ()); - - if (wxGetApp().is_editor()) - m_contained_in_bed = wxGetApp().plater()->build_volume().all_paths_inside(gcode_result, m_paths_bounding_box); - - m_sequential_view.gcode_ids.clear(); - for (size_t i = 0; i < gcode_result.moves.size(); ++i) { - const GCodeProcessorResult::MoveVertex& move = gcode_result.moves[i]; - if (move.type != EMoveType::Seam) - m_sequential_view.gcode_ids.push_back(move.gcode_id); - } - - std::vector vertices(m_buffers.size()); - std::vector indices(m_buffers.size()); - std::vector instances(m_buffers.size()); - std::vector instances_ids(m_buffers.size()); - std::vector instances_offsets(m_buffers.size()); - std::vector options_zs; - - size_t seams_count = 0; - std::vector biased_seams_ids; - - // toolpaths data -> extract vertices from result - for (size_t i = 0; i < m_moves_count; ++i) { - const GCodeProcessorResult::MoveVertex& curr = gcode_result.moves[i]; - if (curr.type == EMoveType::Seam) { - ++seams_count; - biased_seams_ids.push_back(i - biased_seams_ids.size() - 1); - } - - size_t move_id = i - seams_count; - - // skip first vertex - if (i == 0) - continue; - - const GCodeProcessorResult::MoveVertex& prev = gcode_result.moves[i - 1]; - - // update progress dialog - ++progress_count; - if (progress_dialog != nullptr && progress_count % progress_threshold == 0) { - progress_dialog->Update(int(100.0f * float(i) / (2.0f * float(m_moves_count))), - _L("Generating vertex buffer") + ": " + wxNumberFormatter::ToString(100.0 * double(i) / double(m_moves_count), 0, wxNumberFormatter::Style_None) + "%"); - progress_dialog->Fit(); - progress_count = 0; - } - - const unsigned char id = buffer_id(curr.type); - TBuffer& t_buffer = m_buffers[id]; - MultiVertexBuffer& v_multibuffer = vertices[id]; - InstanceBuffer& inst_buffer = instances[id]; - InstanceIdBuffer& inst_id_buffer = instances_ids[id]; - InstancesOffsets& inst_offsets = instances_offsets[id]; - - // ensure there is at least one vertex buffer - if (v_multibuffer.empty()) - v_multibuffer.push_back(VertexBuffer()); - - // if adding the vertices for the current segment exceeds the threshold size of the current vertex buffer - // add another vertex buffer - size_t vertices_size_to_add = (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) ? t_buffer.model.data.vertices_size_bytes() : t_buffer.max_vertices_per_segment_size_bytes(); - if (v_multibuffer.back().size() * sizeof(float) > t_buffer.vertices.max_size_bytes() - vertices_size_to_add) { - v_multibuffer.push_back(VertexBuffer()); - if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { - Path& last_path = t_buffer.paths.back(); - if (prev.type == curr.type && last_path.matches(curr)) - last_path.add_sub_path(prev, static_cast(v_multibuffer.size()) - 1, 0, move_id - 1); - } - } - - VertexBuffer& v_buffer = v_multibuffer.back(); - - switch (t_buffer.render_primitive_type) - { - case TBuffer::ERenderPrimitiveType::Point: { add_vertices_as_point(curr, v_buffer); break; } - case TBuffer::ERenderPrimitiveType::Line: { add_vertices_as_line(prev, curr, v_buffer); break; } - case TBuffer::ERenderPrimitiveType::Triangle: { add_vertices_as_solid(prev, curr, t_buffer, static_cast(v_multibuffer.size()) - 1, v_buffer, move_id); break; } - case TBuffer::ERenderPrimitiveType::InstancedModel: - { - add_model_instance(curr, inst_buffer, inst_id_buffer, move_id); - inst_offsets.push_back(prev.position - curr.position); -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.instances_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - break; - } - case TBuffer::ERenderPrimitiveType::BatchedModel: - { - add_vertices_as_model_batch(curr, t_buffer.model.data, v_buffer, inst_buffer, inst_id_buffer, move_id); - inst_offsets.push_back(prev.position - curr.position); -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.batched_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - break; - } - } - - // collect options zs for later use - if (curr.type == EMoveType::Pause_Print || curr.type == EMoveType::Custom_GCode) { - const float* const last_z = options_zs.empty() ? nullptr : &options_zs.back(); - if (last_z == nullptr || curr.position[2] < *last_z - EPSILON || *last_z + EPSILON < curr.position[2]) - options_zs.emplace_back(curr.position[2]); - } - } - - // smooth toolpaths corners for the given TBuffer using triangles - auto smooth_triangle_toolpaths_corners = [&gcode_result, &biased_seams_ids](const TBuffer& t_buffer, MultiVertexBuffer& v_multibuffer) { - auto extract_position_at = [](const VertexBuffer& vertices, size_t offset) { - return Vec3f(vertices[offset + 0], vertices[offset + 1], vertices[offset + 2]); - }; - auto update_position_at = [](VertexBuffer& vertices, size_t offset, const Vec3f& position) { - vertices[offset + 0] = position.x(); - vertices[offset + 1] = position.y(); - vertices[offset + 2] = position.z(); - }; - auto match_right_vertices = [&](const Path::Sub_Path& prev_sub_path, const Path::Sub_Path& next_sub_path, - size_t curr_s_id, size_t vertex_size_floats, const Vec3f& displacement_vec) { - if (&prev_sub_path == &next_sub_path) { // previous and next segment are both contained into to the same vertex buffer - VertexBuffer& vbuffer = v_multibuffer[prev_sub_path.first.b_id]; - // offset into the vertex buffer of the next segment 1st vertex - const size_t next_1st_offset = (prev_sub_path.last.s_id - curr_s_id) * 6 * vertex_size_floats; - // offset into the vertex buffer of the right vertex of the previous segment - const size_t prev_right_offset = prev_sub_path.last.i_id - next_1st_offset - 3 * vertex_size_floats; - // new position of the right vertices - const Vec3f shared_vertex = extract_position_at(vbuffer, prev_right_offset) + displacement_vec; - // update previous segment - update_position_at(vbuffer, prev_right_offset, shared_vertex); - // offset into the vertex buffer of the right vertex of the next segment - const size_t next_right_offset = next_sub_path.last.i_id - next_1st_offset; - // update next segment - update_position_at(vbuffer, next_right_offset, shared_vertex); - } - else { // previous and next segment are contained into different vertex buffers - VertexBuffer& prev_vbuffer = v_multibuffer[prev_sub_path.first.b_id]; - VertexBuffer& next_vbuffer = v_multibuffer[next_sub_path.first.b_id]; - // offset into the previous vertex buffer of the right vertex of the previous segment - const size_t prev_right_offset = prev_sub_path.last.i_id - 3 * vertex_size_floats; - // new position of the right vertices - const Vec3f shared_vertex = extract_position_at(prev_vbuffer, prev_right_offset) + displacement_vec; - // update previous segment - update_position_at(prev_vbuffer, prev_right_offset, shared_vertex); - // offset into the next vertex buffer of the right vertex of the next segment - const size_t next_right_offset = next_sub_path.first.i_id + 1 * vertex_size_floats; - // update next segment - update_position_at(next_vbuffer, next_right_offset, shared_vertex); - } - }; - auto match_left_vertices = [&](const Path::Sub_Path& prev_sub_path, const Path::Sub_Path& next_sub_path, - size_t curr_s_id, size_t vertex_size_floats, const Vec3f& displacement_vec) { - if (&prev_sub_path == &next_sub_path) { // previous and next segment are both contained into to the same vertex buffer - VertexBuffer& vbuffer = v_multibuffer[prev_sub_path.first.b_id]; - // offset into the vertex buffer of the next segment 1st vertex - const size_t next_1st_offset = (prev_sub_path.last.s_id - curr_s_id) * 6 * vertex_size_floats; - // offset into the vertex buffer of the left vertex of the previous segment - const size_t prev_left_offset = prev_sub_path.last.i_id - next_1st_offset - 1 * vertex_size_floats; - // new position of the left vertices - const Vec3f shared_vertex = extract_position_at(vbuffer, prev_left_offset) + displacement_vec; - // update previous segment - update_position_at(vbuffer, prev_left_offset, shared_vertex); - // offset into the vertex buffer of the left vertex of the next segment - const size_t next_left_offset = next_sub_path.last.i_id - next_1st_offset + 1 * vertex_size_floats; - // update next segment - update_position_at(vbuffer, next_left_offset, shared_vertex); - } - else { // previous and next segment are contained into different vertex buffers - VertexBuffer& prev_vbuffer = v_multibuffer[prev_sub_path.first.b_id]; - VertexBuffer& next_vbuffer = v_multibuffer[next_sub_path.first.b_id]; - // offset into the previous vertex buffer of the left vertex of the previous segment - const size_t prev_left_offset = prev_sub_path.last.i_id - 1 * vertex_size_floats; - // new position of the left vertices - const Vec3f shared_vertex = extract_position_at(prev_vbuffer, prev_left_offset) + displacement_vec; - // update previous segment - update_position_at(prev_vbuffer, prev_left_offset, shared_vertex); - // offset into the next vertex buffer of the left vertex of the next segment - const size_t next_left_offset = next_sub_path.first.i_id + 3 * vertex_size_floats; - // update next segment - update_position_at(next_vbuffer, next_left_offset, shared_vertex); - } - }; - - auto extract_move_id = [&biased_seams_ids](size_t id) { - size_t new_id = size_t(-1); - auto it = std::lower_bound(biased_seams_ids.begin(), biased_seams_ids.end(), id); - if (it == biased_seams_ids.end()) - new_id = id + biased_seams_ids.size(); - else { - if (it == biased_seams_ids.begin() && *it < id) - new_id = id; - else if (it != biased_seams_ids.begin()) - new_id = id + std::distance(biased_seams_ids.begin(), it); - } - return (new_id == size_t(-1)) ? id : new_id; - }; - - const size_t vertex_size_floats = t_buffer.vertices.vertex_size_floats(); - for (const Path& path : t_buffer.paths) { - // the two segments of the path sharing the current vertex may belong - // to two different vertex buffers - size_t prev_sub_path_id = 0; - size_t next_sub_path_id = 0; - const size_t path_vertices_count = path.vertices_count(); - const float half_width = 0.5f * path.width; - for (size_t j = 1; j < path_vertices_count - 1; ++j) { - const size_t curr_s_id = path.sub_paths.front().first.s_id + j; - const size_t move_id = extract_move_id(curr_s_id); - const Vec3f& prev = gcode_result.moves[move_id - 1].position; - const Vec3f& curr = gcode_result.moves[move_id].position; - const Vec3f& next = gcode_result.moves[move_id + 1].position; - - // select the subpaths which contains the previous/next segments - if (!path.sub_paths[prev_sub_path_id].contains(curr_s_id)) - ++prev_sub_path_id; - if (!path.sub_paths[next_sub_path_id].contains(curr_s_id + 1)) - ++next_sub_path_id; - const Path::Sub_Path& prev_sub_path = path.sub_paths[prev_sub_path_id]; - const Path::Sub_Path& next_sub_path = path.sub_paths[next_sub_path_id]; - - const Vec3f prev_dir = (curr - prev).normalized(); - const Vec3f prev_right = Vec3f(prev_dir.y(), -prev_dir.x(), 0.0f).normalized(); - const Vec3f prev_up = prev_right.cross(prev_dir); - - const Vec3f next_dir = (next - curr).normalized(); - - const bool is_right_turn = prev_up.dot(prev_dir.cross(next_dir)) <= 0.0f; - const float cos_dir = prev_dir.dot(next_dir); - // whether the angle between adjacent segments is greater than 45 degrees - const bool is_sharp = cos_dir < 0.7071068f; - - float displacement = 0.0f; - if (cos_dir > -0.9998477f) { - // if the angle between adjacent segments is smaller than 179 degrees - const Vec3f med_dir = (prev_dir + next_dir).normalized(); - displacement = half_width * ::tan(::acos(std::clamp(next_dir.dot(med_dir), -1.0f, 1.0f))); - } - - const float sq_prev_length = (curr - prev).squaredNorm(); - const float sq_next_length = (next - curr).squaredNorm(); - const float sq_displacement = sqr(displacement); - const bool can_displace = displacement > 0.0f && sq_displacement < sq_prev_length && sq_displacement < sq_next_length; - - if (can_displace) { - // displacement to apply to the vertices to match - const Vec3f displacement_vec = displacement * prev_dir; - // matches inner corner vertices - if (is_right_turn) - match_right_vertices(prev_sub_path, next_sub_path, curr_s_id, vertex_size_floats, -displacement_vec); - else - match_left_vertices(prev_sub_path, next_sub_path, curr_s_id, vertex_size_floats, -displacement_vec); - - if (!is_sharp) { - // matches outer corner vertices - if (is_right_turn) - match_left_vertices(prev_sub_path, next_sub_path, curr_s_id, vertex_size_floats, displacement_vec); - else - match_right_vertices(prev_sub_path, next_sub_path, curr_s_id, vertex_size_floats, displacement_vec); - } - } - } - } - }; - -#if ENABLE_GCODE_VIEWER_STATISTICS - auto load_vertices_time = std::chrono::high_resolution_clock::now(); - m_statistics.load_vertices = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - // smooth toolpaths corners for TBuffers using triangles - for (size_t i = 0; i < m_buffers.size(); ++i) { - const TBuffer& t_buffer = m_buffers[i]; - if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) - smooth_triangle_toolpaths_corners(t_buffer, vertices[i]); - } - - // dismiss, no more needed - std::vector().swap(biased_seams_ids); - - for (MultiVertexBuffer& v_multibuffer : vertices) { - for (VertexBuffer& v_buffer : v_multibuffer) { - v_buffer.shrink_to_fit(); - } - } - - // move the wipe toolpaths half height up to render them on proper position - MultiVertexBuffer& wipe_vertices = vertices[buffer_id(EMoveType::Wipe)]; - for (VertexBuffer& v_buffer : wipe_vertices) { - for (size_t i = 2; i < v_buffer.size(); i += 3) { - v_buffer[i] += 0.5f * GCodeProcessor::Wipe_Height; - } - } - - // send vertices data to gpu, where needed - for (size_t i = 0; i < m_buffers.size(); ++i) { - TBuffer& t_buffer = m_buffers[i]; - if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel) { - const InstanceBuffer& inst_buffer = instances[i]; - if (!inst_buffer.empty()) { - t_buffer.model.instances.buffer = inst_buffer; - t_buffer.model.instances.s_ids = instances_ids[i]; - t_buffer.model.instances.offsets = instances_offsets[i]; - } - } - else { - if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) { - const InstanceBuffer& inst_buffer = instances[i]; - if (!inst_buffer.empty()) { - t_buffer.model.instances.buffer = inst_buffer; - t_buffer.model.instances.s_ids = instances_ids[i]; - t_buffer.model.instances.offsets = instances_offsets[i]; - } - } - const MultiVertexBuffer& v_multibuffer = vertices[i]; - for (const VertexBuffer& v_buffer : v_multibuffer) { - const size_t size_elements = v_buffer.size(); - const size_t size_bytes = size_elements * sizeof(float); - const size_t vertices_count = size_elements / t_buffer.vertices.vertex_size_floats(); - t_buffer.vertices.count += vertices_count; - -#if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.total_vertices_gpu_size += static_cast(size_bytes); - m_statistics.max_vbuffer_gpu_size = std::max(m_statistics.max_vbuffer_gpu_size, static_cast(size_bytes)); - ++m_statistics.vbuffers_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - GLuint id = 0; - glsafe(::glGenBuffers(1, &id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, id)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, size_bytes, v_buffer.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - - t_buffer.vertices.vbos.push_back(static_cast(id)); - t_buffer.vertices.sizes.push_back(size_bytes); - } - } - } - -#if ENABLE_GCODE_VIEWER_STATISTICS - auto smooth_vertices_time = std::chrono::high_resolution_clock::now(); - m_statistics.smooth_vertices = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - load_vertices_time).count(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - log_memory_usage("Loaded G-code generated vertex buffers ", vertices, indices); - - // dismiss vertices data, no more needed - std::vector().swap(vertices); - std::vector().swap(instances); - std::vector().swap(instances_ids); - - // toolpaths data -> extract indices from result - // paths may have been filled while extracting vertices, - // so reset them, they will be filled again while extracting indices - for (TBuffer& buffer : m_buffers) { - buffer.paths.clear(); - } - - // variable used to keep track of the current vertex buffers index and size - using CurrVertexBuffer = std::pair; - std::vector curr_vertex_buffers(m_buffers.size(), { 0, 0 }); - - // variable used to keep track of the vertex buffers ids - using VboIndexList = std::vector; - std::vector vbo_indices(m_buffers.size()); - - seams_count = 0; - - for (size_t i = 0; i < m_moves_count; ++i) { - const GCodeProcessorResult::MoveVertex& curr = gcode_result.moves[i]; - if (curr.type == EMoveType::Seam) - ++seams_count; - - size_t move_id = i - seams_count; - - // skip first vertex - if (i == 0) - continue; - - const GCodeProcessorResult::MoveVertex& prev = gcode_result.moves[i - 1]; - const GCodeProcessorResult::MoveVertex* next = nullptr; - if (i < m_moves_count - 1) - next = &gcode_result.moves[i + 1]; - - ++progress_count; - if (progress_dialog != nullptr && progress_count % progress_threshold == 0) { - progress_dialog->Update(int(100.0f * float(m_moves_count + i) / (2.0f * float(m_moves_count))), - _L("Generating index buffers") + ": " + wxNumberFormatter::ToString(100.0 * double(i) / double(m_moves_count), 0, wxNumberFormatter::Style_None) + "%"); - progress_dialog->Fit(); - progress_count = 0; - } - - const unsigned char id = buffer_id(curr.type); - TBuffer& t_buffer = m_buffers[id]; - MultiIndexBuffer& i_multibuffer = indices[id]; - CurrVertexBuffer& curr_vertex_buffer = curr_vertex_buffers[id]; - VboIndexList& vbo_index_list = vbo_indices[id]; - - // ensure there is at least one index buffer - if (i_multibuffer.empty()) { - i_multibuffer.push_back(IndexBuffer()); - if (!t_buffer.vertices.vbos.empty()) - vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]); - } - - // if adding the indices for the current segment exceeds the threshold size of the current index buffer - // create another index buffer - size_t indiced_size_to_add = (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) ? t_buffer.model.data.indices_size_bytes() : t_buffer.max_indices_per_segment_size_bytes(); - if (i_multibuffer.back().size() * sizeof(IBufferType) >= IBUFFER_THRESHOLD_BYTES - indiced_size_to_add) { - i_multibuffer.push_back(IndexBuffer()); - vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]); - if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Point && - t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::BatchedModel) { - Path& last_path = t_buffer.paths.back(); - last_path.add_sub_path(prev, static_cast(i_multibuffer.size()) - 1, 0, move_id - 1); - } - } - - // if adding the vertices for the current segment exceeds the threshold size of the current vertex buffer - // create another index buffer - size_t vertices_size_to_add = (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) ? t_buffer.model.data.vertices_size_bytes() : t_buffer.max_vertices_per_segment_size_bytes(); - if (curr_vertex_buffer.second * t_buffer.vertices.vertex_size_bytes() > t_buffer.vertices.max_size_bytes() - vertices_size_to_add) { - i_multibuffer.push_back(IndexBuffer()); - - ++curr_vertex_buffer.first; - curr_vertex_buffer.second = 0; - vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]); - - if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Point && - t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::BatchedModel) { - Path& last_path = t_buffer.paths.back(); - last_path.add_sub_path(prev, static_cast(i_multibuffer.size()) - 1, 0, move_id - 1); - } - } - - IndexBuffer& i_buffer = i_multibuffer.back(); - - switch (t_buffer.render_primitive_type) - { - case TBuffer::ERenderPrimitiveType::Point: { - add_indices_as_point(curr, t_buffer, static_cast(i_multibuffer.size()) - 1, i_buffer, move_id); - curr_vertex_buffer.second += t_buffer.max_vertices_per_segment(); - break; - } - case TBuffer::ERenderPrimitiveType::Line: { - add_indices_as_line(prev, curr, t_buffer, static_cast(i_multibuffer.size()) - 1, i_buffer, move_id); - curr_vertex_buffer.second += t_buffer.max_vertices_per_segment(); - break; - } - case TBuffer::ERenderPrimitiveType::Triangle: { - add_indices_as_solid(prev, curr, next, t_buffer, curr_vertex_buffer.second, static_cast(i_multibuffer.size()) - 1, i_buffer, move_id); - break; - } - case TBuffer::ERenderPrimitiveType::BatchedModel: { - add_indices_as_model_batch(t_buffer.model.data, i_buffer, curr_vertex_buffer.second); - curr_vertex_buffer.second += t_buffer.model.data.vertices_count(); - break; - } - default: { break; } - } - } - - for (MultiIndexBuffer& i_multibuffer : indices) { - for (IndexBuffer& i_buffer : i_multibuffer) { - i_buffer.shrink_to_fit(); - } - } - - // toolpaths data -> send indices data to gpu - for (size_t i = 0; i < m_buffers.size(); ++i) { - TBuffer& t_buffer = m_buffers[i]; - if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::InstancedModel) { - const MultiIndexBuffer& i_multibuffer = indices[i]; - for (const IndexBuffer& i_buffer : i_multibuffer) { - const size_t size_elements = i_buffer.size(); - const size_t size_bytes = size_elements * sizeof(IBufferType); - - // stores index buffer informations into TBuffer - t_buffer.indices.push_back(IBuffer()); - IBuffer& ibuf = t_buffer.indices.back(); - ibuf.count = size_elements; - ibuf.vbo = vbo_indices[i][t_buffer.indices.size() - 1]; - -#if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.total_indices_gpu_size += static_cast(size_bytes); - m_statistics.max_ibuffer_gpu_size = std::max(m_statistics.max_ibuffer_gpu_size, static_cast(size_bytes)); - ++m_statistics.ibuffers_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - glsafe(::glGenBuffers(1, &ibuf.ibo)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibuf.ibo)); - glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, size_bytes, i_buffer.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - } - } - } - - if (progress_dialog != nullptr) { - progress_dialog->Update(100, ""); - progress_dialog->Fit(); - } - -#if ENABLE_GCODE_VIEWER_STATISTICS - for (const TBuffer& buffer : m_buffers) { - m_statistics.paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path); - } - - auto update_segments_count = [&](EMoveType type, int64_t& count) { - unsigned int id = buffer_id(type); - const MultiIndexBuffer& buffers = indices[id]; - int64_t indices_count = 0; - for (const IndexBuffer& buffer : buffers) { - indices_count += buffer.size(); - } - const TBuffer& t_buffer = m_buffers[id]; - if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) - indices_count -= static_cast(12 * t_buffer.paths.size()); // remove the starting + ending caps = 4 triangles - - count += indices_count / t_buffer.indices_per_segment(); - }; - - update_segments_count(EMoveType::Travel, m_statistics.travel_segments_count); - update_segments_count(EMoveType::Wipe, m_statistics.wipe_segments_count); - update_segments_count(EMoveType::Extrude, m_statistics.extrude_segments_count); - - m_statistics.load_indices = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - smooth_vertices_time).count(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - log_memory_usage("Loaded G-code generated indices buffers ", vertices, indices); - - // dismiss indices data, no more needed - std::vector().swap(indices); - - // layers zs / roles / extruder ids -> extract from result - size_t last_travel_s_id = 0; - seams_count = 0; - for (size_t i = 0; i < m_moves_count; ++i) { - const GCodeProcessorResult::MoveVertex& move = gcode_result.moves[i]; - if (move.type == EMoveType::Seam) - ++seams_count; - - size_t move_id = i - seams_count; - - if (move.type == EMoveType::Extrude) { - // layers zs - const double* const last_z = m_layers.empty() ? nullptr : &m_layers.get_zs().back(); - const double z = static_cast(move.position.z()); - if (last_z == nullptr || z < *last_z - EPSILON || *last_z + EPSILON < z) - m_layers.append(z, { last_travel_s_id, move_id }); - else - m_layers.get_endpoints().back().last = move_id; - // extruder ids - m_extruder_ids.emplace_back(move.extruder_id); - // roles - if (i > 0) - m_roles.emplace_back(move.extrusion_role); - } - else if (move.type == EMoveType::Travel) { - if (move_id - last_travel_s_id > 1 && !m_layers.empty()) - m_layers.get_endpoints().back().last = move_id; - - last_travel_s_id = move_id; - } - } - - // roles -> remove duplicates - sort_remove_duplicates(m_roles); - m_roles.shrink_to_fit(); - - // extruder ids -> remove duplicates - sort_remove_duplicates(m_extruder_ids); - m_extruder_ids.shrink_to_fit(); - -#if ENABLE_SPIRAL_VASE_LAYERS - // replace layers for spiral vase mode - if (!gcode_result.spiral_vase_layers.empty()) { - m_layers.reset(); - for (const auto& layer : gcode_result.spiral_vase_layers) { - m_layers.append(layer.first, { layer.second.first, layer.second.second }); - } - } -#endif // ENABLE_SPIRAL_VASE_LAYERS - - // set layers z range - if (!m_layers.empty()) - m_layers_z_range = { 0, static_cast(m_layers.size() - 1) }; - - // change color of paths whose layer contains option points - if (!options_zs.empty()) { - TBuffer& extrude_buffer = m_buffers[buffer_id(EMoveType::Extrude)]; - for (Path& path : extrude_buffer.paths) { - const float z = path.sub_paths.front().first.position.z(); - if (std::find_if(options_zs.begin(), options_zs.end(), [z](float f) { return f - EPSILON <= z && z <= f + EPSILON; }) != options_zs.end()) - path.cp_color_id = 255 - path.cp_color_id; - } - } - -#if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.load_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - if (progress_dialog != nullptr) - progress_dialog->Destroy(); -} - -void GCodeViewer::load_shells(const Print& print, bool initialized) -{ - if (print.objects().empty()) - // no shells, return - return; - - // adds objects' volumes - int object_id = 0; - for (const PrintObject* obj : print.objects()) { - const ModelObject* model_obj = obj->model_object(); - - std::vector instance_ids(model_obj->instances.size()); - for (int i = 0; i < (int)model_obj->instances.size(); ++i) { - instance_ids[i] = i; - } - - size_t current_volumes_count = m_shells.volumes.volumes.size(); - m_shells.volumes.load_object(model_obj, object_id, instance_ids, "object", initialized); - - // adjust shells' z if raft is present - const SlicingParameters& slicing_parameters = obj->slicing_parameters(); - if (slicing_parameters.object_print_z_min != 0.0) { - const Vec3d z_offset = slicing_parameters.object_print_z_min * Vec3d::UnitZ(); - for (size_t i = current_volumes_count; i < m_shells.volumes.volumes.size(); ++i) { - GLVolume* v = m_shells.volumes.volumes[i]; - v->set_volume_offset(v->get_volume_offset() + z_offset); - } - } - - ++object_id; - } - - if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF) { - // adds wipe tower's volume - const double max_z = print.objects()[0]->model_object()->get_model()->bounding_box().max(2); - const PrintConfig& config = print.config(); - const size_t extruders_count = config.nozzle_diameter.size(); - if (extruders_count > 1 && config.wipe_tower && !config.complete_objects) { - const float depth = print.wipe_tower_data(extruders_count).depth; - const float brim_width = print.wipe_tower_data(extruders_count).brim_width; - - m_shells.volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, - !print.is_step_done(psWipeTower), brim_width, initialized); - } - } - - // remove modifiers - while (true) { - GLVolumePtrs::iterator it = std::find_if(m_shells.volumes.volumes.begin(), m_shells.volumes.volumes.end(), [](GLVolume* volume) { return volume->is_modifier; }); - if (it != m_shells.volumes.volumes.end()) { - delete (*it); - m_shells.volumes.volumes.erase(it); - } - else - break; - } - - for (GLVolume* volume : m_shells.volumes.volumes) { - volume->zoom_to_volumes = false; - volume->color[3] = 0.25f; - volume->force_native_color = true; - volume->set_render_color(); - } -} - -void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const -{ -#if ENABLE_GCODE_VIEWER_STATISTICS - auto start_time = std::chrono::high_resolution_clock::now(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - auto extrusion_color = [this](const Path& path) { - Color color; - switch (m_view_type) - { - case EViewType::FeatureType: { color = Extrusion_Role_Colors[static_cast(path.role)]; break; } - case EViewType::Height: { color = m_extrusions.ranges.height.get_color_at(path.height); break; } - case EViewType::Width: { color = m_extrusions.ranges.width.get_color_at(path.width); break; } - case EViewType::Feedrate: { color = m_extrusions.ranges.feedrate.get_color_at(path.feedrate); break; } - case EViewType::FanSpeed: { color = m_extrusions.ranges.fan_speed.get_color_at(path.fan_speed); break; } - case EViewType::Temperature: { color = m_extrusions.ranges.temperature.get_color_at(path.temperature); break; } - case EViewType::VolumetricRate: { color = m_extrusions.ranges.volumetric_rate.get_color_at(path.volumetric_rate); break; } - case EViewType::Tool: { color = m_tool_colors[path.extruder_id]; break; } - case EViewType::ColorPrint: { - if (path.cp_color_id >= static_cast(m_tool_colors.size())) - color = { 0.5f, 0.5f, 0.5f, 1.0f }; - else - color = m_tool_colors[path.cp_color_id]; - - break; - } - default: { color = { 1.0f, 1.0f, 1.0f, 1.0f }; break; } - } - - return color; - }; - - auto travel_color = [](const Path& path) { - return (path.delta_extruder < 0.0f) ? Travel_Colors[2] /* Retract */ : - ((path.delta_extruder > 0.0f) ? Travel_Colors[1] /* Extrude */ : - Travel_Colors[0] /* Move */); - }; - - auto is_in_layers_range = [this](const Path& path, size_t min_id, size_t max_id) { - auto in_layers_range = [this, min_id, max_id](size_t id) { - return m_layers.get_endpoints_at(min_id).first <= id && id <= m_layers.get_endpoints_at(max_id).last; - }; - - return in_layers_range(path.sub_paths.front().first.s_id) && in_layers_range(path.sub_paths.back().last.s_id); - }; - - auto is_travel_in_layers_range = [this](size_t path_id, size_t min_id, size_t max_id) { - const TBuffer& buffer = m_buffers[buffer_id(EMoveType::Travel)]; - if (path_id >= buffer.paths.size()) - return false; - - Path path = buffer.paths[path_id]; - size_t first = path_id; - size_t last = path_id; - - // check adjacent paths - while (first > 0 && path.sub_paths.front().first.position.isApprox(buffer.paths[first - 1].sub_paths.back().last.position)) { - --first; - path.sub_paths.front().first = buffer.paths[first].sub_paths.front().first; - } - while (last < buffer.paths.size() - 1 && path.sub_paths.back().last.position.isApprox(buffer.paths[last + 1].sub_paths.front().first.position)) { - ++last; - path.sub_paths.back().last = buffer.paths[last].sub_paths.back().last; - } - - const size_t min_s_id = m_layers.get_endpoints_at(min_id).first; - const size_t max_s_id = m_layers.get_endpoints_at(max_id).last; - - return (min_s_id <= path.sub_paths.front().first.s_id && path.sub_paths.front().first.s_id <= max_s_id) || - (min_s_id <= path.sub_paths.back().last.s_id && path.sub_paths.back().last.s_id <= max_s_id); - }; - -#if ENABLE_GCODE_VIEWER_STATISTICS - Statistics* statistics = const_cast(&m_statistics); - statistics->render_paths_size = 0; - statistics->models_instances_size = 0; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - const bool top_layer_only = get_app_config()->get("seq_top_layer_only") == "1"; - - SequentialView::Endpoints global_endpoints = { m_moves_count , 0 }; - SequentialView::Endpoints top_layer_endpoints = global_endpoints; - SequentialView* sequential_view = const_cast(&m_sequential_view); - if (top_layer_only || !keep_sequential_current_first) sequential_view->current.first = 0; - if (!keep_sequential_current_last) sequential_view->current.last = m_moves_count; - - // first pass: collect visible paths and update sequential view data - std::vector> paths; - for (size_t b = 0; b < m_buffers.size(); ++b) { - TBuffer& buffer = const_cast(m_buffers[b]); - // reset render paths - buffer.render_paths.clear(); - - if (!buffer.visible) - continue; - - if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel || - buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) { - for (size_t id : buffer.model.instances.s_ids) { - if (id < m_layers.get_endpoints_at(m_layers_z_range[0]).first || m_layers.get_endpoints_at(m_layers_z_range[1]).last < id) - continue; - - global_endpoints.first = std::min(global_endpoints.first, id); - global_endpoints.last = std::max(global_endpoints.last, id); - - if (top_layer_only) { - if (id < m_layers.get_endpoints_at(m_layers_z_range[1]).first || m_layers.get_endpoints_at(m_layers_z_range[1]).last < id) - continue; - - top_layer_endpoints.first = std::min(top_layer_endpoints.first, id); - top_layer_endpoints.last = std::max(top_layer_endpoints.last, id); - } - } - } - else { - for (size_t i = 0; i < buffer.paths.size(); ++i) { - const Path& path = buffer.paths[i]; - if (path.type == EMoveType::Travel) { - if (!is_travel_in_layers_range(i, m_layers_z_range[0], m_layers_z_range[1])) - continue; - } - else if (!is_in_layers_range(path, m_layers_z_range[0], m_layers_z_range[1])) - continue; - - if (path.type == EMoveType::Extrude && !is_visible(path)) - continue; - - // store valid path - for (size_t j = 0; j < path.sub_paths.size(); ++j) { - paths.push_back({ static_cast(b), path.sub_paths[j].first.b_id, static_cast(i), static_cast(j) }); - } - - global_endpoints.first = std::min(global_endpoints.first, path.sub_paths.front().first.s_id); - global_endpoints.last = std::max(global_endpoints.last, path.sub_paths.back().last.s_id); - - if (top_layer_only) { - if (path.type == EMoveType::Travel) { - if (is_travel_in_layers_range(i, m_layers_z_range[1], m_layers_z_range[1])) { - top_layer_endpoints.first = std::min(top_layer_endpoints.first, path.sub_paths.front().first.s_id); - top_layer_endpoints.last = std::max(top_layer_endpoints.last, path.sub_paths.back().last.s_id); - } - } - else if (is_in_layers_range(path, m_layers_z_range[1], m_layers_z_range[1])) { - top_layer_endpoints.first = std::min(top_layer_endpoints.first, path.sub_paths.front().first.s_id); - top_layer_endpoints.last = std::max(top_layer_endpoints.last, path.sub_paths.back().last.s_id); - } - } - } - } - } - - // update current sequential position - sequential_view->current.first = !top_layer_only && keep_sequential_current_first ? std::clamp(sequential_view->current.first, global_endpoints.first, global_endpoints.last) : global_endpoints.first; - sequential_view->current.last = keep_sequential_current_last ? std::clamp(sequential_view->current.last, global_endpoints.first, global_endpoints.last) : global_endpoints.last; - - // get the world position from the vertex buffer - bool found = false; - for (const TBuffer& buffer : m_buffers) { - if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel || - buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) { - for (size_t i = 0; i < buffer.model.instances.s_ids.size(); ++i) { - if (buffer.model.instances.s_ids[i] == m_sequential_view.current.last) { - size_t offset = i * buffer.model.instances.instance_size_floats(); - sequential_view->current_position.x() = buffer.model.instances.buffer[offset + 0]; - sequential_view->current_position.y() = buffer.model.instances.buffer[offset + 1]; - sequential_view->current_position.z() = buffer.model.instances.buffer[offset + 2]; - sequential_view->current_offset = buffer.model.instances.offsets[i]; - found = true; - break; - } - } - } - else { - // searches the path containing the current position - for (const Path& path : buffer.paths) { - if (path.contains(m_sequential_view.current.last)) { - const int sub_path_id = path.get_id_of_sub_path_containing(m_sequential_view.current.last); - if (sub_path_id != -1) { - const Path::Sub_Path& sub_path = path.sub_paths[sub_path_id]; - unsigned int offset = static_cast(m_sequential_view.current.last - sub_path.first.s_id); - if (offset > 0) { - if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Line) - offset = 2 * offset - 1; - else if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { - unsigned int indices_count = buffer.indices_per_segment(); - offset = indices_count * (offset - 1) + (indices_count - 2); - if (sub_path_id == 0) - offset += 6; // add 2 triangles for starting cap - } - } - offset += static_cast(sub_path.first.i_id); - - // gets the vertex index from the index buffer on gpu - const IBuffer& i_buffer = buffer.indices[sub_path.first.b_id]; - unsigned int index = 0; - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast(offset * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&index))); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - - // gets the position from the vertices buffer on gpu - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo)); - glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, static_cast(index * buffer.vertices.vertex_size_bytes()), static_cast(3 * sizeof(float)), static_cast(sequential_view->current_position.data()))); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - - sequential_view->current_offset = Vec3f::Zero(); - found = true; - break; - } - } - } - } - - if (found) - break; - } - - // second pass: filter paths by sequential data and collect them by color - RenderPath* render_path = nullptr; - for (const auto& [tbuffer_id, ibuffer_id, path_id, sub_path_id] : paths) { - TBuffer& buffer = const_cast(m_buffers[tbuffer_id]); - const Path& path = buffer.paths[path_id]; - const Path::Sub_Path& sub_path = path.sub_paths[sub_path_id]; - if (m_sequential_view.current.last < sub_path.first.s_id || sub_path.last.s_id < m_sequential_view.current.first) - continue; - - Color color; - switch (path.type) - { - case EMoveType::Tool_change: - case EMoveType::Color_change: - case EMoveType::Pause_Print: - case EMoveType::Custom_GCode: - case EMoveType::Retract: - case EMoveType::Unretract: - case EMoveType::Seam: { color = option_color(path.type); break; } - case EMoveType::Extrude: { - if (!top_layer_only || - m_sequential_view.current.last == global_endpoints.last || - is_in_layers_range(path, m_layers_z_range[1], m_layers_z_range[1])) - color = extrusion_color(path); - else - color = Neutral_Color; - - break; - } - case EMoveType::Travel: { - if (!top_layer_only || m_sequential_view.current.last == global_endpoints.last || is_travel_in_layers_range(path_id, m_layers_z_range[1], m_layers_z_range[1])) - color = (m_view_type == EViewType::Feedrate || m_view_type == EViewType::Tool || m_view_type == EViewType::ColorPrint) ? extrusion_color(path) : travel_color(path); - else - color = Neutral_Color; - - break; - } - case EMoveType::Wipe: { color = Wipe_Color; break; } - default: { color = { 0.0f, 0.0f, 0.0f, 1.0f }; break; } - } - - RenderPath key{ tbuffer_id, color, static_cast(ibuffer_id), path_id }; - if (render_path == nullptr || !RenderPathPropertyEqual()(*render_path, key)) { - buffer.render_paths.emplace_back(key); - render_path = const_cast(&buffer.render_paths.back()); - } - - unsigned int delta_1st = 0; - if (sub_path.first.s_id < m_sequential_view.current.first && m_sequential_view.current.first <= sub_path.last.s_id) - delta_1st = static_cast(m_sequential_view.current.first - sub_path.first.s_id); - - unsigned int size_in_indices = 0; - switch (buffer.render_primitive_type) - { - case TBuffer::ERenderPrimitiveType::Point: { - size_in_indices = buffer.indices_per_segment(); - break; - } - case TBuffer::ERenderPrimitiveType::Line: - case TBuffer::ERenderPrimitiveType::Triangle: { - unsigned int segments_count = std::min(m_sequential_view.current.last, sub_path.last.s_id) - std::max(m_sequential_view.current.first, sub_path.first.s_id); - size_in_indices = buffer.indices_per_segment() * segments_count; - break; - } - default: { break; } - } - - if (size_in_indices == 0) - continue; - - if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { - if (sub_path_id == 0 && delta_1st == 0) - size_in_indices += 6; // add 2 triangles for starting cap - if (sub_path_id == path.sub_paths.size() - 1 && path.sub_paths.back().last.s_id <= m_sequential_view.current.last) - size_in_indices += 6; // add 2 triangles for ending cap - if (delta_1st > 0) - size_in_indices -= 6; // remove 2 triangles for corner cap - } - - render_path->sizes.push_back(size_in_indices); - - if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { - delta_1st *= buffer.indices_per_segment(); - if (delta_1st > 0) { - delta_1st += 6; // skip 2 triangles for corner cap - if (sub_path_id == 0) - delta_1st += 6; // skip 2 triangles for starting cap - } - } - - render_path->offsets.push_back(static_cast((sub_path.first.i_id + delta_1st) * sizeof(IBufferType))); - -#if 0 - // check sizes and offsets against index buffer size on gpu - GLint buffer_size; - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer->indices[render_path->ibuffer_id].ibo)); - glsafe(::glGetBufferParameteriv(GL_ELEMENT_ARRAY_BUFFER, GL_BUFFER_SIZE, &buffer_size)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - if (render_path->offsets.back() + render_path->sizes.back() * sizeof(IBufferType) > buffer_size) - BOOST_LOG_TRIVIAL(error) << "GCodeViewer::refresh_render_paths: Invalid render path data"; -#endif - } - - // Removes empty render paths and sort. - for (size_t b = 0; b < m_buffers.size(); ++b) { - TBuffer* buffer = const_cast(&m_buffers[b]); - buffer->render_paths.erase(std::remove_if(buffer->render_paths.begin(), buffer->render_paths.end(), - [](const auto &path){ return path.sizes.empty() || path.offsets.empty(); }), - buffer->render_paths.end()); - } - - // second pass: for buffers using instanced and batched models, update the instances render ranges - for (size_t b = 0; b < m_buffers.size(); ++b) { - TBuffer& buffer = const_cast(m_buffers[b]); - if (buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::InstancedModel && - buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::BatchedModel) - continue; - - buffer.model.instances.render_ranges.reset(); - - if (!buffer.visible || buffer.model.instances.s_ids.empty()) - continue; - - buffer.model.instances.render_ranges.ranges.push_back({ 0, 0, 0, buffer.model.color }); - bool has_second_range = top_layer_only && m_sequential_view.current.last != m_sequential_view.global.last; - if (has_second_range) - buffer.model.instances.render_ranges.ranges.push_back({ 0, 0, 0, Neutral_Color }); - - if (m_sequential_view.current.first <= buffer.model.instances.s_ids.back() && buffer.model.instances.s_ids.front() <= m_sequential_view.current.last) { - for (size_t id : buffer.model.instances.s_ids) { - if (has_second_range) { - if (id < m_sequential_view.endpoints.first) { - ++buffer.model.instances.render_ranges.ranges.front().offset; - if (id <= m_sequential_view.current.first) - ++buffer.model.instances.render_ranges.ranges.back().offset; - else - ++buffer.model.instances.render_ranges.ranges.back().count; - } - else if (id <= m_sequential_view.current.last) - ++buffer.model.instances.render_ranges.ranges.front().count; - else - break; - } - else { - if (id <= m_sequential_view.current.first) - ++buffer.model.instances.render_ranges.ranges.front().offset; - else if (id <= m_sequential_view.current.last) - ++buffer.model.instances.render_ranges.ranges.front().count; - else - break; - } - } - } - } - - // set sequential data to their final value - sequential_view->endpoints = top_layer_only ? top_layer_endpoints : global_endpoints; - sequential_view->current.first = !top_layer_only && keep_sequential_current_first ? std::clamp(sequential_view->current.first, sequential_view->endpoints.first, sequential_view->endpoints.last) : sequential_view->endpoints.first; - sequential_view->global = global_endpoints; - - // updates sequential range caps - std::array* sequential_range_caps = const_cast*>(&m_sequential_range_caps); - (*sequential_range_caps)[0].reset(); - (*sequential_range_caps)[1].reset(); - - if (m_sequential_view.current.first != m_sequential_view.current.last) { - for (const auto& [tbuffer_id, ibuffer_id, path_id, sub_path_id] : paths) { - TBuffer& buffer = const_cast(m_buffers[tbuffer_id]); - if (buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Triangle) - continue; - - const Path& path = buffer.paths[path_id]; - const Path::Sub_Path& sub_path = path.sub_paths[sub_path_id]; - if (m_sequential_view.current.last <= sub_path.first.s_id || sub_path.last.s_id <= m_sequential_view.current.first) - continue; - - // update cap for first endpoint of current range - if (m_sequential_view.current.first > sub_path.first.s_id) { - SequentialRangeCap& cap = (*sequential_range_caps)[0]; - const IBuffer& i_buffer = buffer.indices[ibuffer_id]; - cap.buffer = &buffer; - cap.vbo = i_buffer.vbo; - - // calculate offset into the index buffer - unsigned int offset = sub_path.first.i_id; - offset += 6; // add 2 triangles for corner cap - offset += static_cast(m_sequential_view.current.first - sub_path.first.s_id) * buffer.indices_per_segment(); - if (sub_path_id == 0) - offset += 6; // add 2 triangles for starting cap - - // extract indices from index buffer - std::array indices{ 0, 0, 0, 0, 0, 0 }; - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 0) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[0]))); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 7) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[1]))); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 1) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[2]))); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 13) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[4]))); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - indices[3] = indices[0]; - indices[5] = indices[1]; - - // send indices to gpu - glsafe(::glGenBuffers(1, &cap.ibo)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cap.ibo)); - glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(IBufferType), indices.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - - // extract color from render path - size_t offset_bytes = offset * sizeof(IBufferType); - for (const RenderPath& render_path : buffer.render_paths) { - if (render_path.ibuffer_id == ibuffer_id) { - for (size_t j = 0; j < render_path.offsets.size(); ++j) { - if (render_path.contains(offset_bytes)) { - cap.color = render_path.color; - break; - } - } - } - } - } - - // update cap for last endpoint of current range - if (m_sequential_view.current.last < sub_path.last.s_id) { - SequentialRangeCap& cap = (*sequential_range_caps)[1]; - const IBuffer& i_buffer = buffer.indices[ibuffer_id]; - cap.buffer = &buffer; - cap.vbo = i_buffer.vbo; - - // calculate offset into the index buffer - unsigned int offset = sub_path.first.i_id; - offset += 6; // add 2 triangles for corner cap - offset += static_cast(m_sequential_view.current.last - 1 - sub_path.first.s_id) * buffer.indices_per_segment(); - if (sub_path_id == 0) - offset += 6; // add 2 triangles for starting cap - - // extract indices from index buffer - std::array indices{ 0, 0, 0, 0, 0, 0 }; - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 2) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[0]))); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 4) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[1]))); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 10) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[2]))); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 16) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[5]))); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - indices[3] = indices[0]; - indices[4] = indices[2]; - - // send indices to gpu - glsafe(::glGenBuffers(1, &cap.ibo)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cap.ibo)); - glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(IBufferType), indices.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - - // extract color from render path - size_t offset_bytes = offset * sizeof(IBufferType); - for (const RenderPath& render_path : buffer.render_paths) { - if (render_path.ibuffer_id == ibuffer_id) { - for (size_t j = 0; j < render_path.offsets.size(); ++j) { - if (render_path.contains(offset_bytes)) { - cap.color = render_path.color; - break; - } - } - } - } - } - - if ((*sequential_range_caps)[0].is_renderable() && (*sequential_range_caps)[1].is_renderable()) - break; - } - } - - wxGetApp().plater()->enable_preview_moves_slider(!paths.empty()); - -#if ENABLE_GCODE_VIEWER_STATISTICS - for (const TBuffer& buffer : m_buffers) { - statistics->render_paths_size += SLIC3R_STDUNORDEREDSET_MEMSIZE(buffer.render_paths, RenderPath); - for (const RenderPath& path : buffer.render_paths) { - statistics->render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.sizes, unsigned int); - statistics->render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.offsets, size_t); - } - statistics->models_instances_size += SLIC3R_STDVEC_MEMSIZE(buffer.model.instances.buffer, float); - statistics->models_instances_size += SLIC3R_STDVEC_MEMSIZE(buffer.model.instances.s_ids, size_t); - statistics->models_instances_size += SLIC3R_STDVEC_MEMSIZE(buffer.model.instances.render_ranges.ranges, InstanceVBuffer::Ranges::Range); - } - statistics->refresh_paths_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS -} - -void GCodeViewer::render_toolpaths() -{ -#if ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS - float point_size = 20.0f; -#else - float point_size = 0.8f; -#endif // ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS - std::array light_intensity = { 0.25f, 0.70f, 0.75f, 0.75f }; - const Camera& camera = wxGetApp().plater()->get_camera(); - double zoom = camera.get_zoom(); - const std::array& viewport = camera.get_viewport(); - float near_plane_height = camera.get_type() == Camera::EType::Perspective ? static_cast(viewport[3]) / (2.0f * static_cast(2.0 * std::tan(0.5 * Geometry::deg2rad(camera.get_fov())))) : - static_cast(viewport[3]) * 0.0005; - - auto shader_init_as_points = [zoom, point_size, near_plane_height](GLShaderProgram& shader) { -#if ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS - shader.set_uniform("use_fixed_screen_size", 1); -#else - shader.set_uniform("use_fixed_screen_size", 0); -#endif // ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS - shader.set_uniform("zoom", zoom); - shader.set_uniform("percent_outline_radius", 0.0f); - shader.set_uniform("percent_center_radius", 0.33f); - shader.set_uniform("point_size", point_size); - shader.set_uniform("near_plane_height", near_plane_height); - }; - - auto render_as_points = [ -#if ENABLE_GCODE_VIEWER_STATISTICS - this -#endif // ENABLE_GCODE_VIEWER_STATISTICS - ](std::vector::iterator it_path, std::vector::iterator it_end, GLShaderProgram& shader, int uniform_color) { - glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)); - glsafe(::glEnable(GL_POINT_SPRITE)); - - for (auto it = it_path; it != it_end && it_path->ibuffer_id == it->ibuffer_id; ++it) { - const RenderPath& path = *it; - // Some OpenGL drivers crash on empty glMultiDrawElements, see GH #7415. - assert(! path.sizes.empty()); - assert(! path.offsets.empty()); - glsafe(::glUniform4fv(uniform_color, 1, static_cast(path.color.data()))); - glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_multi_points_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } - - glsafe(::glDisable(GL_POINT_SPRITE)); - glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE)); - }; - - auto shader_init_as_lines = [light_intensity](GLShaderProgram &shader) { - shader.set_uniform("light_intensity", light_intensity); - }; - auto render_as_lines = [ -#if ENABLE_GCODE_VIEWER_STATISTICS - this -#endif // ENABLE_GCODE_VIEWER_STATISTICS - ](std::vector::iterator it_path, std::vector::iterator it_end, GLShaderProgram& shader, int uniform_color) { - for (auto it = it_path; it != it_end && it_path->ibuffer_id == it->ibuffer_id; ++it) { - const RenderPath& path = *it; - // Some OpenGL drivers crash on empty glMultiDrawElements, see GH #7415. - assert(! path.sizes.empty()); - assert(! path.offsets.empty()); - glsafe(::glUniform4fv(uniform_color, 1, static_cast(path.color.data()))); - glsafe(::glMultiDrawElements(GL_LINES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_multi_lines_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } - }; - - auto render_as_triangles = [ -#if ENABLE_GCODE_VIEWER_STATISTICS - this -#endif // ENABLE_GCODE_VIEWER_STATISTICS - ](std::vector::iterator it_path, std::vector::iterator it_end, GLShaderProgram& shader, int uniform_color) { - for (auto it = it_path; it != it_end && it_path->ibuffer_id == it->ibuffer_id; ++it) { - const RenderPath& path = *it; - // Some OpenGL drivers crash on empty glMultiDrawElements, see GH #7415. - assert(! path.sizes.empty()); - assert(! path.offsets.empty()); - glsafe(::glUniform4fv(uniform_color, 1, static_cast(path.color.data()))); - glsafe(::glMultiDrawElements(GL_TRIANGLES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_multi_triangles_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } - }; - - auto render_as_instanced_model = [ -#if ENABLE_GCODE_VIEWER_STATISTICS - this -#endif // ENABLE_GCODE_VIEWER_STATISTICS - ](TBuffer& buffer, GLShaderProgram & shader) { - for (auto& range : buffer.model.instances.render_ranges.ranges) { - if (range.vbo == 0 && range.count > 0) { - glsafe(::glGenBuffers(1, &range.vbo)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, range.vbo)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, range.count * buffer.model.instances.instance_size_bytes(), (const void*)&buffer.model.instances.buffer[range.offset * buffer.model.instances.instance_size_floats()], GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - } - - if (range.vbo > 0) { - buffer.model.model.set_color(-1, range.color); - buffer.model.model.render_instanced(range.vbo, range.count); -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_instanced_models_calls_count; - m_statistics.total_instances_gpu_size += static_cast(range.count * buffer.model.instances.instance_size_bytes()); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } - } - }; - -#if ENABLE_GCODE_VIEWER_STATISTICS - auto render_as_batched_model = [this](TBuffer& buffer, GLShaderProgram& shader) { -#else - auto render_as_batched_model = [](TBuffer& buffer, GLShaderProgram& shader) { -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - struct Range - { - unsigned int first; - unsigned int last; - bool intersects(const Range& other) const { return (other.last < first || other.first > last) ? false : true; } - }; - Range buffer_range = { 0, 0 }; - size_t indices_per_instance = buffer.model.data.indices_count(); - - for (size_t j = 0; j < buffer.indices.size(); ++j) { - const IBuffer& i_buffer = buffer.indices[j]; - buffer_range.last = buffer_range.first + i_buffer.count / indices_per_instance; - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo)); - glsafe(::glVertexPointer(buffer.vertices.position_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes())); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - bool has_normals = buffer.vertices.normal_size_floats() > 0; - if (has_normals) { - glsafe(::glNormalPointer(GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes())); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - } - - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); - - for (auto& range : buffer.model.instances.render_ranges.ranges) { - Range range_range = { range.offset, range.offset + range.count }; - if (range_range.intersects(buffer_range)) { - shader.set_uniform("uniform_color", range.color); - unsigned int offset = (range_range.first > buffer_range.first) ? range_range.first - buffer_range.first : 0; - size_t offset_bytes = static_cast(offset) * indices_per_instance * sizeof(IBufferType); - Range render_range = { std::max(range_range.first, buffer_range.first), std::min(range_range.last, buffer_range.last) }; - size_t count = static_cast(render_range.last - render_range.first) * indices_per_instance; - if (count > 0) { - glsafe(::glDrawElements(GL_TRIANGLES, (GLsizei)count, GL_UNSIGNED_SHORT, (const void*)offset_bytes)); -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_batched_models_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } - } - } - - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - - if (has_normals) - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - - buffer_range.first = buffer_range.last; - } - }; - - auto line_width = [](double zoom) { - return (zoom < 5.0) ? 1.0 : (1.0 + 5.0 * (zoom - 5.0) / (100.0 - 5.0)); - }; - - unsigned char begin_id = buffer_id(EMoveType::Retract); - unsigned char end_id = buffer_id(EMoveType::Count); - - for (unsigned char i = begin_id; i < end_id; ++i) { - TBuffer& buffer = m_buffers[i]; - if (!buffer.visible || !buffer.has_data()) - continue; - - GLShaderProgram* shader = wxGetApp().get_shader(buffer.shader.c_str()); - if (shader != nullptr) { - shader->start_using(); - - if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel) { - shader->set_uniform("emission_factor", 0.25f); - render_as_instanced_model(buffer, *shader); - shader->set_uniform("emission_factor", 0.0f); - } - else if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) { - shader->set_uniform("emission_factor", 0.25f); - render_as_batched_model(buffer, *shader); - shader->set_uniform("emission_factor", 0.0f); - } - else { - switch (buffer.render_primitive_type) { - case TBuffer::ERenderPrimitiveType::Point: shader_init_as_points(*shader); break; - case TBuffer::ERenderPrimitiveType::Line: shader_init_as_lines(*shader); break; - default: break; - } - int uniform_color = shader->get_uniform_location("uniform_color"); - auto it_path = buffer.render_paths.begin(); - for (unsigned int ibuffer_id = 0; ibuffer_id < static_cast(buffer.indices.size()); ++ibuffer_id) { - const IBuffer& i_buffer = buffer.indices[ibuffer_id]; - // Skip all paths with ibuffer_id < ibuffer_id. - for (; it_path != buffer.render_paths.end() && it_path->ibuffer_id < ibuffer_id; ++ it_path) ; - if (it_path == buffer.render_paths.end() || it_path->ibuffer_id > ibuffer_id) - // Not found. This shall not happen. - continue; - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo)); - glsafe(::glVertexPointer(buffer.vertices.position_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes())); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - bool has_normals = buffer.vertices.normal_size_floats() > 0; - if (has_normals) { - glsafe(::glNormalPointer(GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes())); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - } - - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); - - // Render all elements with it_path->ibuffer_id == ibuffer_id, possible with varying colors. - switch (buffer.render_primitive_type) - { - case TBuffer::ERenderPrimitiveType::Point: { - render_as_points(it_path, buffer.render_paths.end(), *shader, uniform_color); - break; - } - case TBuffer::ERenderPrimitiveType::Line: { - glsafe(::glLineWidth(static_cast(line_width(zoom)))); - render_as_lines(it_path, buffer.render_paths.end(), *shader, uniform_color); - break; - } - case TBuffer::ERenderPrimitiveType::Triangle: { - render_as_triangles(it_path, buffer.render_paths.end(), *shader, uniform_color); - break; - } - default: { break; } - } - - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - - if (has_normals) - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - } - } - - shader->stop_using(); - } - } - -#if ENABLE_GCODE_VIEWER_STATISTICS - auto render_sequential_range_cap = [this] -#else - auto render_sequential_range_cap = [] -#endif // ENABLE_GCODE_VIEWER_STATISTICS - (const SequentialRangeCap& cap) { - GLShaderProgram* shader = wxGetApp().get_shader(cap.buffer->shader.c_str()); - if (shader != nullptr) { - shader->start_using(); - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, cap.vbo)); - glsafe(::glVertexPointer(cap.buffer->vertices.position_size_floats(), GL_FLOAT, cap.buffer->vertices.vertex_size_bytes(), (const void*)cap.buffer->vertices.position_offset_bytes())); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - bool has_normals = cap.buffer->vertices.normal_size_floats() > 0; - if (has_normals) { - glsafe(::glNormalPointer(GL_FLOAT, cap.buffer->vertices.vertex_size_bytes(), (const void*)cap.buffer->vertices.normal_offset_bytes())); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - } - - shader->set_uniform("uniform_color", cap.color); - - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cap.ibo)); - glsafe(::glDrawElements(GL_TRIANGLES, (GLsizei)cap.indices_count(), GL_UNSIGNED_SHORT, nullptr)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_triangles_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - if (has_normals) - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - - shader->stop_using(); - } - }; - - for (unsigned int i = 0; i < 2; ++i) { - if (m_sequential_range_caps[i].is_renderable()) - render_sequential_range_cap(m_sequential_range_caps[i]); - } -} - -void GCodeViewer::render_shells() -{ - if (!m_shells.visible || m_shells.volumes.empty()) - return; - - GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); - if (shader == nullptr) - return; - - // when the background processing is enabled, it may happen that the shells data have been loaded - // before opengl has been initialized for the preview canvas. - // when this happens, the volumes' data have not been sent to gpu yet. - for (GLVolume* v : m_shells.volumes.volumes) { - if (!v->indexed_vertex_array.has_VBOs()) - v->finalize_geometry(true); - } - -// glsafe(::glDepthMask(GL_FALSE)); - - shader->start_using(); - m_shells.volumes.render(GLVolumeCollection::ERenderType::Transparent, true, wxGetApp().plater()->get_camera().get_view_matrix()); - shader->stop_using(); - -// glsafe(::glDepthMask(GL_TRUE)); -} - -void GCodeViewer::render_legend(float& legend_height) -{ - if (!m_legend_enabled) - return; - - const Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); - - ImGuiWrapper& imgui = *wxGetApp().imgui(); - - imgui.set_next_window_pos(0.0f, 0.0f, ImGuiCond_Always); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - ImGui::SetNextWindowBgAlpha(0.6f); - const float max_height = 0.75f * static_cast(cnv_size.get_height()); - const float child_height = 0.3333f * max_height; - ImGui::SetNextWindowSizeConstraints({ 0.0f, 0.0f }, { -1.0f, max_height }); - imgui.begin(std::string("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove); - - enum class EItemType : unsigned char - { - Rect, - Circle, - Hexagon, - Line - }; - - const PrintEstimatedStatistics::Mode& time_mode = m_print_statistics.modes[static_cast(m_time_estimate_mode)]; - bool show_estimated_time = time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType || - (m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty())); - - const float icon_size = ImGui::GetTextLineHeight(); - const float percent_bar_size = 2.0f * ImGui::GetTextLineHeight(); - - bool imperial_units = wxGetApp().app_config->get("use_inches") == "1"; - - auto append_item = [icon_size, percent_bar_size, &imgui, imperial_units](EItemType type, const Color& color, const std::string& label, - bool visible = true, const std::string& time = "", float percent = 0.0f, float max_percent = 0.0f, const std::array& offsets = { 0.0f, 0.0f, 0.0f, 0.0f }, - double used_filament_m = 0.0, double used_filament_g = 0.0, - std::function callback = nullptr) { - if (!visible) - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); - - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - ImVec2 pos = ImGui::GetCursorScreenPos(); - switch (type) { - default: - case EItemType::Rect: { - draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, - ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f })); - break; - } - case EItemType::Circle: { - ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); - draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); - break; - } - case EItemType::Hexagon: { - ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); - draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 6); - break; - } - case EItemType::Line: { - draw_list->AddLine({ pos.x + 1, pos.y + icon_size - 1 }, { pos.x + icon_size - 1, pos.y + 1 }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 3.0f); - break; - } - } - - // draw text - ImGui::Dummy({ icon_size, icon_size }); - ImGui::SameLine(); - if (callback != nullptr) { - if (ImGui::MenuItem(label.c_str())) - callback(); - else { - // show tooltip - if (ImGui::IsItemHovered()) { - if (!visible) - ImGui::PopStyleVar(); - ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); - ImGui::BeginTooltip(); - imgui.text(visible ? _u8L("Click to hide") : _u8L("Click to show")); - ImGui::EndTooltip(); - ImGui::PopStyleColor(); - if (!visible) - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); - - // to avoid the tooltip to change size when moving the mouse - imgui.set_requires_extra_frame(); - } - } - - if (!time.empty()) { - ImGui::SameLine(offsets[0]); - imgui.text(time); - ImGui::SameLine(offsets[1]); - pos = ImGui::GetCursorScreenPos(); - const float width = std::max(1.0f, percent_bar_size * percent / max_percent); - draw_list->AddRectFilled({ pos.x, pos.y + 2.0f }, { pos.x + width, pos.y + icon_size - 2.0f }, - ImGui::GetColorU32(ImGuiWrapper::COL_ORANGE_LIGHT)); - ImGui::Dummy({ percent_bar_size, icon_size }); - ImGui::SameLine(); - char buf[64]; - ::sprintf(buf, "%.1f%%", 100.0f * percent); - ImGui::TextUnformatted((percent > 0.0f) ? buf : ""); - ImGui::SameLine(offsets[2]); - ::sprintf(buf, imperial_units ? "%.2f in" : "%.2f m", used_filament_m); - imgui.text(buf); - ImGui::SameLine(offsets[3]); - ::sprintf(buf, "%.2f g", used_filament_g); - imgui.text(buf); - } - } - else { - imgui.text(label); - if (used_filament_m > 0.0) { - char buf[64]; - ImGui::SameLine(offsets[0]); - ::sprintf(buf, imperial_units ? "%.2f in" : "%.2f m", used_filament_m); - imgui.text(buf); - ImGui::SameLine(offsets[1]); - ::sprintf(buf, "%.2f g", used_filament_g); - imgui.text(buf); - } - } - - if (!visible) - ImGui::PopStyleVar(); - }; - - auto append_range = [append_item](const Extrusions::Range& range, unsigned int decimals) { - auto append_range_item = [append_item](int i, float value, unsigned int decimals) { - char buf[1024]; - ::sprintf(buf, "%.*f", decimals, value); - append_item(EItemType::Rect, Range_Colors[i], buf); - }; - - if (range.count == 1) - // single item use case - append_range_item(0, range.min, decimals); - else if (range.count == 2) { - append_range_item(static_cast(Range_Colors.size()) - 1, range.max, decimals); - append_range_item(0, range.min, decimals); - } - else { - const float step_size = range.step_size(); - for (int i = static_cast(Range_Colors.size()) - 1; i >= 0; --i) { - append_range_item(i, range.min + static_cast(i) * step_size, decimals); - } - } - }; - - auto append_headers = [&imgui](const std::array& texts, const std::array& offsets) { - size_t i = 0; - for (; i < offsets.size(); i++) { - imgui.text(texts[i]); - ImGui::SameLine(offsets[i]); - } - imgui.text(texts[i]); - ImGui::Separator(); - }; - - auto max_width = [](const std::vector& items, const std::string& title, float extra_size = 0.0f) { - float ret = ImGui::CalcTextSize(title.c_str()).x; - for (const std::string& item : items) { - ret = std::max(ret, extra_size + ImGui::CalcTextSize(item.c_str()).x); - } - return ret; - }; - - auto calculate_offsets = [max_width](const std::vector& labels, const std::vector& times, - const std::array& titles, float extra_size = 0.0f) { - const ImGuiStyle& style = ImGui::GetStyle(); - std::array ret = { 0.0f, 0.0f, 0.0f, 0.0f }; - ret[0] = max_width(labels, titles[0], extra_size) + 3.0f * style.ItemSpacing.x; - for (size_t i = 1; i < titles.size(); i++) - ret[i] = ret[i-1] + max_width(times, titles[i]) + style.ItemSpacing.x; - return ret; - }; - - auto color_print_ranges = [this](unsigned char extruder_id, const std::vector& custom_gcode_per_print_z) { - std::vector>> ret; - ret.reserve(custom_gcode_per_print_z.size()); - - for (const auto& item : custom_gcode_per_print_z) { - if (extruder_id + 1 != static_cast(item.extruder)) - continue; - - if (item.type != ColorChange) - continue; - - const std::vector zs = m_layers.get_zs(); - auto lower_b = std::lower_bound(zs.begin(), zs.end(), item.print_z - Slic3r::DoubleSlider::epsilon()); - if (lower_b == zs.end()) - continue; - - const double current_z = *lower_b; - const double previous_z = (lower_b == zs.begin()) ? 0.0 : *(--lower_b); - - // to avoid duplicate values, check adding values - if (ret.empty() || !(ret.back().second.first == previous_z && ret.back().second.second == current_z)) - ret.push_back({ decode_color(item.color), { previous_z, current_z } }); - } - - return ret; - }; - - auto upto_label = [](double z) { - char buf[64]; - ::sprintf(buf, "%.2f", z); - return _u8L("up to") + " " + std::string(buf) + " " + _u8L("mm"); - }; - - auto above_label = [](double z) { - char buf[64]; - ::sprintf(buf, "%.2f", z); - return _u8L("above") + " " + std::string(buf) + " " + _u8L("mm"); - }; - - auto fromto_label = [](double z1, double z2) { - char buf1[64]; - ::sprintf(buf1, "%.2f", z1); - char buf2[64]; - ::sprintf(buf2, "%.2f", z2); - return _u8L("from") + " " + std::string(buf1) + " " + _u8L("to") + " " + std::string(buf2) + " " + _u8L("mm"); - }; - - auto role_time_and_percent = [time_mode](ExtrusionRole role) { - auto it = std::find_if(time_mode.roles_times.begin(), time_mode.roles_times.end(), [role](const std::pair& item) { return role == item.first; }); - return (it != time_mode.roles_times.end()) ? std::make_pair(it->second, it->second / time_mode.time) : std::make_pair(0.0f, 0.0f); - }; - - auto used_filament_per_role = [this, imperial_units](ExtrusionRole role) { - auto it = m_print_statistics.used_filaments_per_role.find(role); - if (it == m_print_statistics.used_filaments_per_role.end()) - return std::make_pair(0.0, 0.0); - - double koef = imperial_units ? 1000.0 / ObjectManipulation::in_to_mm : 1.0; - return std::make_pair(it->second.first * koef, it->second.second); - }; - - // data used to properly align items in columns when showing time - std::array offsets = { 0.0f, 0.0f, 0.0f, 0.0f }; - std::vector labels; - std::vector times; - std::vector percents; - std::vector used_filaments_m; - std::vector used_filaments_g; - float max_percent = 0.0f; - - if (m_view_type == EViewType::FeatureType) { - // calculate offsets to align time/percentage data - for (size_t i = 0; i < m_roles.size(); ++i) { - ExtrusionRole role = m_roles[i]; - if (role < erCount) { - labels.push_back(_u8L(ExtrusionEntity::role_to_string(role))); - auto [time, percent] = role_time_and_percent(role); - times.push_back((time > 0.0f) ? short_time(get_time_dhms(time)) : ""); - percents.push_back(percent); - max_percent = std::max(max_percent, percent); - auto [used_filament_m, used_filament_g] = used_filament_per_role(role); - used_filaments_m.push_back(used_filament_m); - used_filaments_g.push_back(used_filament_g); - } - } - - std::string longest_percentage_string; - for (double item : percents) { - char buffer[64]; - ::sprintf(buffer, "%.2f %%", item); - if (::strlen(buffer) > longest_percentage_string.length()) - longest_percentage_string = buffer; - } - longest_percentage_string += " "; - if (_u8L("Percentage").length() > longest_percentage_string.length()) - longest_percentage_string = _u8L("Percentage"); - - std::string longest_used_filament_string; - for (double item : used_filaments_m) { - char buffer[64]; - ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", item); - if (::strlen(buffer) > longest_used_filament_string.length()) - longest_used_filament_string = buffer; - } - - offsets = calculate_offsets(labels, times, { _u8L("Feature type"), _u8L("Time"), longest_percentage_string, longest_used_filament_string }, icon_size); - } - - // get used filament (meters and grams) from used volume in respect to the active extruder - auto get_used_filament_from_volume = [this, imperial_units](double volume, int extruder_id) { - double koef = imperial_units ? 1.0 / ObjectManipulation::in_to_mm : 0.001; - std::pair ret = { koef * volume / (PI * sqr(0.5 * m_filament_diameters[extruder_id])), - volume * m_filament_densities[extruder_id] * 0.001 }; - return ret; - }; - - if (m_view_type == EViewType::Tool) { - // calculate used filaments data - for (size_t extruder_id : m_extruder_ids) { - if (m_print_statistics.volumes_per_extruder.find(extruder_id) == m_print_statistics.volumes_per_extruder.end()) - continue; - double volume = m_print_statistics.volumes_per_extruder.at(extruder_id); - - auto [used_filament_m, used_filament_g] = get_used_filament_from_volume(volume, extruder_id); - used_filaments_m.push_back(used_filament_m); - used_filaments_g.push_back(used_filament_g); - } - - std::string longest_used_filament_string; - for (double item : used_filaments_m) { - char buffer[64]; - ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", item); - if (::strlen(buffer) > longest_used_filament_string.length()) - longest_used_filament_string = buffer; - } - - offsets = calculate_offsets(labels, times, { "Extruder NNN", longest_used_filament_string }, icon_size); - } - - // extrusion paths section -> title - switch (m_view_type) - { - case EViewType::FeatureType: - { - append_headers({ _u8L("Feature type"), _u8L("Time"), _u8L("Percentage"), _u8L("Used filament") }, offsets); - break; - } - case EViewType::Height: { imgui.title(_u8L("Height (mm)")); break; } - case EViewType::Width: { imgui.title(_u8L("Width (mm)")); break; } - case EViewType::Feedrate: { imgui.title(_u8L("Speed (mm/s)")); break; } - case EViewType::FanSpeed: { imgui.title(_u8L("Fan Speed (%)")); break; } - case EViewType::Temperature: { imgui.title(_u8L("Temperature (°C)")); break; } - case EViewType::VolumetricRate: { imgui.title(_u8L("Volumetric flow rate (mm³/s)")); break; } - case EViewType::Tool: - { - append_headers({ _u8L("Tool"), _u8L("Used filament") }, offsets); - break; - } - case EViewType::ColorPrint: { imgui.title(_u8L("Color Print")); break; } - default: { break; } - } - - // extrusion paths section -> items - switch (m_view_type) - { - case EViewType::FeatureType: - { - for (size_t i = 0; i < m_roles.size(); ++i) { - ExtrusionRole role = m_roles[i]; - if (role >= erCount) - continue; - const bool visible = is_visible(role); - append_item(EItemType::Rect, Extrusion_Role_Colors[static_cast(role)], labels[i], - visible, times[i], percents[i], max_percent, offsets, used_filaments_m[i], used_filaments_g[i], [this, role, visible]() { - m_extrusions.role_visibility_flags = visible ? m_extrusions.role_visibility_flags & ~(1 << role) : m_extrusions.role_visibility_flags | (1 << role); - // update buffers' render paths - refresh_render_paths(false, false); - wxGetApp().plater()->update_preview_moves_slider(); - wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); - wxGetApp().plater()->update_preview_bottom_toolbar(); - } - ); - } - break; - } - case EViewType::Height: { append_range(m_extrusions.ranges.height, 3); break; } - case EViewType::Width: { append_range(m_extrusions.ranges.width, 3); break; } - case EViewType::Feedrate: { append_range(m_extrusions.ranges.feedrate, 1); break; } - case EViewType::FanSpeed: { append_range(m_extrusions.ranges.fan_speed, 0); break; } - case EViewType::Temperature: { append_range(m_extrusions.ranges.temperature, 0); break; } - case EViewType::VolumetricRate: { append_range(m_extrusions.ranges.volumetric_rate, 3); break; } - case EViewType::Tool: - { - // shows only extruders actually used - size_t i = 0; - for (unsigned char extruder_id : m_extruder_ids) { - append_item(EItemType::Rect, m_tool_colors[extruder_id], _u8L("Extruder") + " " + std::to_string(extruder_id + 1), - true, "", 0.0f, 0.0f, offsets, used_filaments_m[i], used_filaments_g[i]); - i++; - } - break; - } - case EViewType::ColorPrint: - { - const 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; - size_t total_items = 1; - for (unsigned char i : m_extruder_ids) { - total_items += color_print_ranges(i, custom_gcode_per_print_z).size(); - } - - const bool need_scrollable = static_cast(total_items) * (icon_size + ImGui::GetStyle().ItemSpacing.y) > child_height; - - // add scrollable region, if needed - if (need_scrollable) - ImGui::BeginChild("color_prints", { -1.0f, child_height }, false); - if (m_extruders_count == 1) { // single extruder use case - const std::vector>> cp_values = color_print_ranges(0, custom_gcode_per_print_z); - const int items_cnt = static_cast(cp_values.size()); - if (items_cnt == 0) { // There are no color changes, but there are some pause print or custom Gcode - append_item(EItemType::Rect, m_tool_colors.front(), _u8L("Default color")); - } - else { - for (int i = items_cnt; i >= 0; --i) { - // create label for color change item - if (i == 0) { - append_item(EItemType::Rect, m_tool_colors[0], upto_label(cp_values.front().second.first)); - break; - } - else if (i == items_cnt) { - append_item(EItemType::Rect, cp_values[i - 1].first, above_label(cp_values[i - 1].second.second)); - continue; - } - append_item(EItemType::Rect, cp_values[i - 1].first, fromto_label(cp_values[i - 1].second.second, cp_values[i].second.first)); - } - } - } - else { // multi extruder use case - // shows only extruders actually used - for (unsigned char i : m_extruder_ids) { - const std::vector>> cp_values = color_print_ranges(i, custom_gcode_per_print_z); - const int items_cnt = static_cast(cp_values.size()); - if (items_cnt == 0) { // There are no color changes, but there are some pause print or custom Gcode - append_item(EItemType::Rect, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1) + " " + _u8L("default color")); - } - else { - for (int j = items_cnt; j >= 0; --j) { - // create label for color change item - std::string label = _u8L("Extruder") + " " + std::to_string(i + 1); - if (j == 0) { - label += " " + upto_label(cp_values.front().second.first); - append_item(EItemType::Rect, m_tool_colors[i], label); - break; - } - else if (j == items_cnt) { - label += " " + above_label(cp_values[j - 1].second.second); - append_item(EItemType::Rect, cp_values[j - 1].first, label); - continue; - } - - label += " " + fromto_label(cp_values[j - 1].second.second, cp_values[j].second.first); - append_item(EItemType::Rect, cp_values[j - 1].first, label); - } - } - } - } - if (need_scrollable) - ImGui::EndChild(); - - break; - } - default: { break; } - } - - // partial estimated printing time section - if (m_view_type == EViewType::ColorPrint) { - using Times = std::pair; - using TimesList = std::vector>; - - // helper structure containig the data needed to render the time items - struct PartialTime - { - enum class EType : unsigned char - { - Print, - ColorChange, - Pause - }; - EType type; - int extruder_id; - Color color1; - Color color2; - Times times; - std::pair used_filament {0.0f, 0.0f}; - }; - using PartialTimes = std::vector; - - auto generate_partial_times = [this, get_used_filament_from_volume](const TimesList& times, const std::vector& used_filaments) { - 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; - std::vector last_color(m_extruders_count); - for (size_t i = 0; i < m_extruders_count; ++i) { - last_color[i] = m_tool_colors[i]; - } - int last_extruder_id = 1; - int color_change_idx = 0; - for (const auto& time_rec : times) { - switch (time_rec.first) - { - case CustomGCode::PausePrint: { - auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; }); - if (it != custom_gcode_per_print_z.end()) { - items.push_back({ PartialTime::EType::Print, it->extruder, last_color[it->extruder - 1], Color(), time_rec.second }); - items.push_back({ PartialTime::EType::Pause, it->extruder, Color(), Color(), time_rec.second }); - custom_gcode_per_print_z.erase(it); - } - break; - } - case CustomGCode::ColorChange: { - auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; }); - if (it != custom_gcode_per_print_z.end()) { - items.push_back({ PartialTime::EType::Print, it->extruder, last_color[it->extruder - 1], Color(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], it->extruder-1) }); - items.push_back({ PartialTime::EType::ColorChange, it->extruder, last_color[it->extruder - 1], decode_color(it->color), time_rec.second }); - last_color[it->extruder - 1] = decode_color(it->color); - last_extruder_id = it->extruder; - custom_gcode_per_print_z.erase(it); - } - else - items.push_back({ PartialTime::EType::Print, last_extruder_id, last_color[last_extruder_id - 1], Color(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], last_extruder_id -1) }); - - break; - } - default: { break; } - } - } - - return items; - }; - - auto append_color_change = [&imgui](const Color& color1, const Color& color2, const std::array& offsets, const Times& times) { - imgui.text(_u8L("Color change")); - ImGui::SameLine(); - - float icon_size = ImGui::GetTextLineHeight(); - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - ImVec2 pos = ImGui::GetCursorScreenPos(); - pos.x -= 0.5f * ImGui::GetStyle().ItemSpacing.x; - - draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, - ImGui::GetColorU32({ color1[0], color1[1], color1[2], 1.0f })); - pos.x += icon_size; - draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, - ImGui::GetColorU32({ color2[0], color2[1], color2[2], 1.0f })); - - ImGui::SameLine(offsets[0]); - imgui.text(short_time(get_time_dhms(times.second - times.first))); - }; - - auto append_print = [&imgui, imperial_units](const Color& color, const std::array& offsets, const Times& times, std::pair used_filament) { - imgui.text(_u8L("Print")); - ImGui::SameLine(); - - float icon_size = ImGui::GetTextLineHeight(); - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - ImVec2 pos = ImGui::GetCursorScreenPos(); - pos.x -= 0.5f * ImGui::GetStyle().ItemSpacing.x; - - draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, - ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f })); - - ImGui::SameLine(offsets[0]); - imgui.text(short_time(get_time_dhms(times.second))); - ImGui::SameLine(offsets[1]); - imgui.text(short_time(get_time_dhms(times.first))); - if (used_filament.first > 0.0f) { - char buffer[64]; - ImGui::SameLine(offsets[2]); - ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", used_filament.first); - imgui.text(buffer); - - ImGui::SameLine(offsets[3]); - ::sprintf(buffer, "%.2f g", used_filament.second); - imgui.text(buffer); - } - }; - - PartialTimes partial_times = generate_partial_times(time_mode.custom_gcode_times, m_print_statistics.volumes_per_color_change); - if (!partial_times.empty()) { - labels.clear(); - times.clear(); - - for (const PartialTime& item : partial_times) { - switch (item.type) - { - case PartialTime::EType::Print: { labels.push_back(_u8L("Print")); break; } - case PartialTime::EType::Pause: { labels.push_back(_u8L("Pause")); break; } - case PartialTime::EType::ColorChange: { labels.push_back(_u8L("Color change")); break; } - } - times.push_back(short_time(get_time_dhms(item.times.second))); - } - - - std::string longest_used_filament_string; - for (const PartialTime& item : partial_times) { - if (item.used_filament.first > 0.0f) { - char buffer[64]; - ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", item.used_filament.first); - if (::strlen(buffer) > longest_used_filament_string.length()) - longest_used_filament_string = buffer; - } - } - - offsets = calculate_offsets(labels, times, { _u8L("Event"), _u8L("Remaining time"), _u8L("Duration"), longest_used_filament_string }, 2.0f * icon_size); - - ImGui::Spacing(); - append_headers({ _u8L("Event"), _u8L("Remaining time"), _u8L("Duration"), _u8L("Used filament") }, offsets); - const bool need_scrollable = static_cast(partial_times.size()) * (icon_size + ImGui::GetStyle().ItemSpacing.y) > child_height; - if (need_scrollable) - // add scrollable region - ImGui::BeginChild("events", { -1.0f, child_height }, false); - - for (const PartialTime& item : partial_times) { - switch (item.type) - { - case PartialTime::EType::Print: { - append_print(item.color1, offsets, item.times, item.used_filament); - break; - } - case PartialTime::EType::Pause: { - imgui.text(_u8L("Pause")); - ImGui::SameLine(offsets[0]); - imgui.text(short_time(get_time_dhms(item.times.second - item.times.first))); - break; - } - case PartialTime::EType::ColorChange: { - append_color_change(item.color1, item.color2, offsets, item.times); - break; - } - } - } - - if (need_scrollable) - ImGui::EndChild(); - } - } - - // travel paths section - if (m_buffers[buffer_id(EMoveType::Travel)].visible) { - switch (m_view_type) - { - case EViewType::Feedrate: - case EViewType::Tool: - case EViewType::ColorPrint: { - break; - } - default: { - // title - ImGui::Spacing(); - imgui.title(_u8L("Travel")); - - // items - append_item(EItemType::Line, Travel_Colors[0], _u8L("Movement")); - append_item(EItemType::Line, Travel_Colors[1], _u8L("Extrusion")); - append_item(EItemType::Line, Travel_Colors[2], _u8L("Retraction")); - - break; - } - } - } - - // wipe paths section - if (m_buffers[buffer_id(EMoveType::Wipe)].visible) { - switch (m_view_type) - { - case EViewType::Feedrate: - case EViewType::Tool: - case EViewType::ColorPrint: { break; } - default: { - // title - ImGui::Spacing(); - imgui.title(_u8L("Wipe")); - - // items - append_item(EItemType::Line, Wipe_Color, _u8L("Wipe")); - - break; - } - } - } - - auto any_option_available = [this]() { - auto available = [this](EMoveType type) { - const TBuffer& buffer = m_buffers[buffer_id(type)]; - return buffer.visible && buffer.has_data(); - }; - - return available(EMoveType::Color_change) || - available(EMoveType::Custom_GCode) || - available(EMoveType::Pause_Print) || - available(EMoveType::Retract) || - available(EMoveType::Tool_change) || - available(EMoveType::Unretract) || - available(EMoveType::Seam); - }; - - auto add_option = [this, append_item](EMoveType move_type, EOptionsColors color, const std::string& text) { - const TBuffer& buffer = m_buffers[buffer_id(move_type)]; - if (buffer.visible && buffer.has_data()) - append_item(EItemType::Circle, Options_Colors[static_cast(color)], text); - }; - - // options section - if (any_option_available()) { - // title - ImGui::Spacing(); - imgui.title(_u8L("Options")); - - // items - add_option(EMoveType::Retract, EOptionsColors::Retractions, _u8L("Retractions")); - add_option(EMoveType::Unretract, EOptionsColors::Unretractions, _u8L("Deretractions")); - add_option(EMoveType::Seam, EOptionsColors::Seams, _u8L("Seams")); - add_option(EMoveType::Tool_change, EOptionsColors::ToolChanges, _u8L("Tool changes")); - add_option(EMoveType::Color_change, EOptionsColors::ColorChanges, _u8L("Color changes")); - add_option(EMoveType::Pause_Print, EOptionsColors::PausePrints, _u8L("Print pauses")); - add_option(EMoveType::Custom_GCode, EOptionsColors::CustomGCodes, _u8L("Custom G-codes")); - } - - // settings section - bool has_settings = false; - has_settings |= !m_settings_ids.print.empty(); - has_settings |= !m_settings_ids.printer.empty(); - bool has_filament_settings = true; - has_filament_settings &= !m_settings_ids.filament.empty(); - for (const std::string& fs : m_settings_ids.filament) { - has_filament_settings &= !fs.empty(); - } - has_settings |= has_filament_settings; - bool show_settings = wxGetApp().is_gcode_viewer(); - show_settings &= (m_view_type == EViewType::FeatureType || m_view_type == EViewType::Tool); - show_settings &= has_settings; - if (show_settings) { - auto calc_offset = [this]() { - float ret = 0.0f; - if (!m_settings_ids.printer.empty()) - ret = std::max(ret, ImGui::CalcTextSize((_u8L("Printer") + std::string(":")).c_str()).x); - if (!m_settings_ids.print.empty()) - ret = std::max(ret, ImGui::CalcTextSize((_u8L("Print settings") + std::string(":")).c_str()).x); - if (!m_settings_ids.filament.empty()) { - for (unsigned char i : m_extruder_ids) { - ret = std::max(ret, ImGui::CalcTextSize((_u8L("Filament") + " " + std::to_string(i + 1) + ":").c_str()).x); - } - } - if (ret > 0.0f) - ret += 2.0f * ImGui::GetStyle().ItemSpacing.x; - return ret; - }; - - ImGui::Spacing(); - imgui.title(_u8L("Settings")); - - float offset = calc_offset(); - - if (!m_settings_ids.printer.empty()) { - imgui.text(_u8L("Printer") + ":"); - ImGui::SameLine(offset); - imgui.text(m_settings_ids.printer); - } - if (!m_settings_ids.print.empty()) { - imgui.text(_u8L("Print settings") + ":"); - ImGui::SameLine(offset); - imgui.text(m_settings_ids.print); - } - if (!m_settings_ids.filament.empty()) { - for (unsigned char i : m_extruder_ids) { - if (i < static_cast(m_settings_ids.filament.size()) && !m_settings_ids.filament[i].empty()) { - std::string txt = _u8L("Filament"); - txt += (m_extruder_ids.size() == 1) ? ":" : " " + std::to_string(i + 1); - imgui.text(txt); - ImGui::SameLine(offset); - imgui.text(m_settings_ids.filament[i]); - } - } - } - } - - // total estimated printing time section - if (show_estimated_time) { - ImGui::Spacing(); - std::string time_title = _u8L("Estimated printing times"); - auto can_show_mode_button = [this](PrintEstimatedStatistics::ETimeMode mode) { - bool show = false; - if (m_print_statistics.modes.size() > 1 && m_print_statistics.modes[static_cast(mode)].roles_times.size() > 0) { - for (size_t i = 0; i < m_print_statistics.modes.size(); ++i) { - if (i != static_cast(mode) && - m_print_statistics.modes[i].time > 0.0f && - short_time(get_time_dhms(m_print_statistics.modes[static_cast(mode)].time)) != short_time(get_time_dhms(m_print_statistics.modes[i].time))) { - show = true; - break; - } - } - } - return show; - }; - - if (can_show_mode_button(m_time_estimate_mode)) { - switch (m_time_estimate_mode) - { - case PrintEstimatedStatistics::ETimeMode::Normal: { time_title += " [" + _u8L("Normal mode") + "]"; break; } - case PrintEstimatedStatistics::ETimeMode::Stealth: { time_title += " [" + _u8L("Stealth mode") + "]"; break; } - default: { assert(false); break; } - } - } - - imgui.title(time_title + ":"); - - std::string first_str = _u8L("First layer"); - std::string total_str = _u8L("Total"); - - float max_len = 10.0f + ImGui::GetStyle().ItemSpacing.x; - if (time_mode.layers_times.empty()) - max_len += ImGui::CalcTextSize(total_str.c_str()).x; - else - max_len += std::max(ImGui::CalcTextSize(first_str.c_str()).x, ImGui::CalcTextSize(total_str.c_str()).x); - - if (!time_mode.layers_times.empty()) { - imgui.text(first_str + ":"); - ImGui::SameLine(max_len); - imgui.text(short_time(get_time_dhms(time_mode.layers_times.front()))); - } - - imgui.text(total_str + ":"); - ImGui::SameLine(max_len); - imgui.text(short_time(get_time_dhms(time_mode.time))); - - auto show_mode_button = [this, &imgui, can_show_mode_button](const wxString& label, PrintEstimatedStatistics::ETimeMode mode) { - if (can_show_mode_button(mode)) { - if (imgui.button(label)) { - m_time_estimate_mode = mode; - imgui.set_requires_extra_frame(); - } - } - }; - - switch (m_time_estimate_mode) { - case PrintEstimatedStatistics::ETimeMode::Normal: { - show_mode_button(_L("Show stealth mode"), PrintEstimatedStatistics::ETimeMode::Stealth); - break; - } - case PrintEstimatedStatistics::ETimeMode::Stealth: { - show_mode_button(_L("Show normal mode"), PrintEstimatedStatistics::ETimeMode::Normal); - break; - } - default : { assert(false); break; } - } - } - - legend_height = ImGui::GetCurrentWindow()->Size.y; - - imgui.end(); - ImGui::PopStyleVar(); -} - -#if ENABLE_GCODE_VIEWER_STATISTICS -void GCodeViewer::render_statistics() -{ - static const float offset = 275.0f; - - ImGuiWrapper& imgui = *wxGetApp().imgui(); - - auto add_time = [this, &imgui](const std::string& label, int64_t time) { - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label); - ImGui::SameLine(offset); - imgui.text(std::to_string(time) + " ms (" + get_time_dhms(static_cast(time) * 0.001f) + ")"); - }; - - auto add_memory = [this, &imgui](const std::string& label, int64_t memory) { - auto format_string = [memory](const std::string& units, float value) { - return std::to_string(memory) + " bytes (" + - Slic3r::float_to_string_decimal_point(float(memory) * value, 3) - + " " + units + ")"; - }; - - static const float kb = 1024.0f; - static const float inv_kb = 1.0f / kb; - static const float mb = 1024.0f * kb; - static const float inv_mb = 1.0f / mb; - static const float gb = 1024.0f * mb; - static const float inv_gb = 1.0f / gb; - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label); - ImGui::SameLine(offset); - if (static_cast(memory) < mb) - imgui.text(format_string("KB", inv_kb)); - else if (static_cast(memory) < gb) - imgui.text(format_string("MB", inv_mb)); - else - imgui.text(format_string("GB", inv_gb)); - }; - - auto add_counter = [this, &imgui](const std::string& label, int64_t counter) { - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label); - ImGui::SameLine(offset); - imgui.text(std::to_string(counter)); - }; - - imgui.set_next_window_pos(0.5f * wxGetApp().plater()->get_current_canvas3D()->get_canvas_size().get_width(), 0.0f, ImGuiCond_Once, 0.5f, 0.0f); - ImGui::SetNextWindowSizeConstraints({ 300.0f, 100.0f }, { 600.0f, 900.0f }); - imgui.begin(std::string("GCodeViewer Statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize); - ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); - - if (ImGui::CollapsingHeader("Time")) { - add_time(std::string("GCodeProcessor:"), m_statistics.results_time); - - ImGui::Separator(); - add_time(std::string("Load:"), m_statistics.load_time); - add_time(std::string(" Load vertices:"), m_statistics.load_vertices); - add_time(std::string(" Smooth vertices:"), m_statistics.smooth_vertices); - add_time(std::string(" Load indices:"), m_statistics.load_indices); - add_time(std::string("Refresh:"), m_statistics.refresh_time); - add_time(std::string("Refresh paths:"), m_statistics.refresh_paths_time); - } - - if (ImGui::CollapsingHeader("OpenGL calls")) { - add_counter(std::string("Multi GL_POINTS:"), m_statistics.gl_multi_points_calls_count); - add_counter(std::string("Multi GL_LINES:"), m_statistics.gl_multi_lines_calls_count); - add_counter(std::string("Multi GL_TRIANGLES:"), m_statistics.gl_multi_triangles_calls_count); - add_counter(std::string("GL_TRIANGLES:"), m_statistics.gl_triangles_calls_count); - ImGui::Separator(); - add_counter(std::string("Instanced models:"), m_statistics.gl_instanced_models_calls_count); - add_counter(std::string("Batched models:"), m_statistics.gl_batched_models_calls_count); - } - - if (ImGui::CollapsingHeader("CPU memory")) { - add_memory(std::string("GCodeProcessor results:"), m_statistics.results_size); - - ImGui::Separator(); - add_memory(std::string("Paths:"), m_statistics.paths_size); - add_memory(std::string("Render paths:"), m_statistics.render_paths_size); - add_memory(std::string("Models instances:"), m_statistics.models_instances_size); - } - - if (ImGui::CollapsingHeader("GPU memory")) { - add_memory(std::string("Vertices:"), m_statistics.total_vertices_gpu_size); - add_memory(std::string("Indices:"), m_statistics.total_indices_gpu_size); - add_memory(std::string("Instances:"), m_statistics.total_instances_gpu_size); - ImGui::Separator(); - add_memory(std::string("Max VBuffer:"), m_statistics.max_vbuffer_gpu_size); - add_memory(std::string("Max IBuffer:"), m_statistics.max_ibuffer_gpu_size); - } - - if (ImGui::CollapsingHeader("Other")) { - add_counter(std::string("Travel segments count:"), m_statistics.travel_segments_count); - add_counter(std::string("Wipe segments count:"), m_statistics.wipe_segments_count); - add_counter(std::string("Extrude segments count:"), m_statistics.extrude_segments_count); - add_counter(std::string("Instances count:"), m_statistics.instances_count); - add_counter(std::string("Batched count:"), m_statistics.batched_count); - ImGui::Separator(); - add_counter(std::string("VBuffers count:"), m_statistics.vbuffers_count); - add_counter(std::string("IBuffers count:"), m_statistics.ibuffers_count); - } - - imgui.end(); -} -#endif // ENABLE_GCODE_VIEWER_STATISTICS - -void GCodeViewer::log_memory_used(const std::string& label, int64_t additional) const -{ - if (Slic3r::get_logging_level() >= 5) { - int64_t paths_size = 0; - int64_t render_paths_size = 0; - for (const TBuffer& buffer : m_buffers) { - paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path); - render_paths_size += SLIC3R_STDUNORDEREDSET_MEMSIZE(buffer.render_paths, RenderPath); - for (const RenderPath& path : buffer.render_paths) { - render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.sizes, unsigned int); - render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.offsets, size_t); - } - } - int64_t layers_size = SLIC3R_STDVEC_MEMSIZE(m_layers.get_zs(), double); - layers_size += SLIC3R_STDVEC_MEMSIZE(m_layers.get_endpoints(), Layers::Endpoints); - BOOST_LOG_TRIVIAL(trace) << label - << "(" << format_memsize_MB(additional + paths_size + render_paths_size + layers_size) << ");" - << log_memory_info(); - } -} - -GCodeViewer::Color GCodeViewer::option_color(EMoveType move_type) const -{ - switch (move_type) - { - case EMoveType::Tool_change: { return Options_Colors[static_cast(EOptionsColors::ToolChanges)]; } - case EMoveType::Color_change: { return Options_Colors[static_cast(EOptionsColors::ColorChanges)]; } - case EMoveType::Pause_Print: { return Options_Colors[static_cast(EOptionsColors::PausePrints)]; } - case EMoveType::Custom_GCode: { return Options_Colors[static_cast(EOptionsColors::CustomGCodes)]; } - case EMoveType::Retract: { return Options_Colors[static_cast(EOptionsColors::Retractions)]; } - case EMoveType::Unretract: { return Options_Colors[static_cast(EOptionsColors::Unretractions)]; } - case EMoveType::Seam: { return Options_Colors[static_cast(EOptionsColors::Seams)]; } - default: { return { 0.0f, 0.0f, 0.0f, 1.0f }; } - } -} - -} // namespace GUI -} // namespace Slic3r - ->>>>>>> master_250 diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index ea3d20a66..eb5883f19 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1,4 +1,3 @@ -<<<<<<< HEAD #include "libslic3r/libslic3r.h" #include "GLCanvas3D.hpp" @@ -7734,6724 +7733,3 @@ void GLCanvas3D::GizmoHighlighter::blink() } // 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 "slic3r/GUI/3DBed.hpp" -#include "slic3r/GUI/3DScene.hpp" -#include "slic3r/GUI/BackgroundSlicingProcess.hpp" -#include "slic3r/GUI/GLShader.hpp" -#include "slic3r/GUI/GUI.hpp" -#include "slic3r/GUI/Tab.hpp" -#include "slic3r/GUI/GUI_Preview.hpp" -#include "slic3r/GUI/OpenGLManager.hpp" -#include "slic3r/GUI/Plater.hpp" -#include "slic3r/GUI/MainFrame.hpp" -#include "slic3r/Utils/UndoRedo.hpp" -#include "slic3r/GUI/Gizmos/GLGizmoPainterBase.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" - -#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; - -static constexpr const float DEFAULT_BG_DARK_COLOR[3] = { 0.478f, 0.478f, 0.478f }; -static constexpr const float DEFAULT_BG_LIGHT_COLOR[3] = { 0.753f, 0.753f, 0.753f }; -static constexpr const float ERROR_BG_DARK_COLOR[3] = { 0.478f, 0.192f, 0.039f }; -static constexpr const float ERROR_BG_LIGHT_COLOR[3] = { 0.753f, 0.192f, 0.039f }; -//static constexpr const float AXES_COLOR[3][3] = { { 1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } }; - -// Number of floats -static constexpr const size_t MAX_VERTEX_BUFFER_SIZE = 131072 * 6; // 3.15MB -// Reserve size in number of floats. -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 - -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 - -Size::Size() - : m_width(0) - , m_height(0) -{ -} - -Size::Size(int width, int height, float scale_factor) - : m_width(width) - , m_height(height) - , m_scale_factor(scale_factor) -{ -} - -int Size::get_width() const -{ - return m_width; -} - -void Size::set_width(int width) -{ - m_width = width; -} - -int Size::get_height() const -{ - return m_height; -} - -void Size::set_height(int height) -{ - m_height = height; -} - -int Size::get_scale_factor() const -{ - return m_scale_factor; -} - -void Size::set_scale_factor(int scale_factor) -{ - m_scale_factor = scale_factor; -} - -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) const -{ - 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(); - - const Rect& bar_rect = get_bar_rect_viewport(canvas); - render_active_object_annotations(canvas, bar_rect); - render_profile(bar_rect); -} - -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(0); - float y = (float)mouse_pos(1); - 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 }; -} - -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 }; -} - -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; -} - -void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3D& canvas, const Rect& bar_rect) const -{ - 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); - - glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); - glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id)); - - // Render the color bar - 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()); - glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); - - shader->stop_using(); -} - -void GLCanvas3D::LayersEditing::render_profile(const Rect& bar_rect) const -{ - //FIXME show some kind of legend. - - if (!m_slicing_parameters) - return; - - // Make the vertical bar a bit wider so the layer height curve does not touch the edge of the bar region. - float scale_x = bar_rect.get_width() / (float)(1.12 * m_slicing_parameters->max_layer_height); - float scale_y = bar_rect.get_height() / m_object_max_z; - 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()); -} - -void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const GLVolumeCollection& volumes) -{ - assert(this->is_allowed()); - assert(this->last_object_id != -1); - GLShaderProgram* shader = wxGetApp().get_shader("variable_layer_height"); - if (shader == nullptr) - return; - - GLShaderProgram* current_shader = wxGetApp().get_current_shader(); - if (shader->get_id() != current_shader->get_id()) - // The layer editing shader is not yet active. Activate it. - shader->start_using(); - else - // The layer editing shader was already active. - current_shader = nullptr; - - 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)); - - // Initialize the layer height texture mapping. - GLsizei w = (GLsizei)m_layers_texture.width; - GLsizei h = (GLsizei)m_layers_texture.height; - GLsizei half_w = w / 2; - 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 (const 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", GLfloat(0)); - glvolume->render(); - } - // Revert back to the previous shader. - glBindTexture(GL_TEXTURE_2D, 0); - if (current_shader != nullptr) - current_shader->start_using(); -} - -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) { - 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() + 16.0f, 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; - - size_t triangles_count = 0; - for (const Polygon& poly : polygons) { - triangles_count += poly.points.size() - 2; - } - const size_t vertices_count = 3 * triangles_count; - - if (m_render_fill) { - GLModel::InitializationData fill_data; - GLModel::InitializationData::Entity entity; - entity.type = GLModel::PrimitiveType::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); - } - - GLModel::InitializationData perimeter_data; - for (const Polygon& poly : polygons) { - GLModel::InitializationData::Entity ent; - ent.type = GLModel::PrimitiveType::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); -} - -void GLCanvas3D::SequentialPrintClearance::render() -{ - std::array FILL_COLOR = { 1.0f, 0.0f, 0.0f, 0.5f }; - std::array NO_FILL_COLOR = { 1.0f, 1.0f, 1.0f, 0.75f }; - - GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); - if (shader == nullptr) - return; - - shader->start_using(); - - glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glDisable(GL_CULL_FACE)); - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - - m_perimeter.set_color(-1, m_render_fill ? FILL_COLOR : NO_FILL_COLOR); - 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_color_by("volume") - , 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)); - - // 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)); - - if (m_multisample_allowed) - glsafe(::glEnable(GL_MULTISAMPLE)); - - if (m_main_toolbar.is_enabled()) - m_layers_editing.init(); - - // 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); - - 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) -{ - m_render_sla_auxiliaries = visible; - - for (GLVolume* vol : m_volumes.volumes) { - if (vol->composite_id.object_id == 1000) - continue; // the wipe tower - 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 (vol->composite_id.object_id == 1000) { // wipe tower - vol->is_active = (visible && mo == nullptr); - } - 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::set_color_by(const std::string& value) -{ - m_color_by = value; -} - -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 - - 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; - } - - camera.apply_view_matrix(); - camera.apply_projection(_max_bounding_box(true, true)); - - 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)); - - wxGetApp().imgui()->new_frame(); - - if (m_picking_enabled) { - if (m_rectangle_selection.is_dragging()) - // 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 - // 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(); - _render_bed(!camera.is_looking_downward(), true); - _render_objects(GLVolumeCollection::ERenderType::Transparent); - - _render_sequential_clearance(); -#if ENABLE_RENDER_SELECTION_CENTER - _render_selection_center(); -#endif // ENABLE_RENDER_SELECTION_CENTER - - // 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 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 - - 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; - wxGetApp().obj_manipul()->set_dirty(); - m_gizmos.reset_all_states(); - m_gizmos.update_data(); - post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); -} - -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); - } - } - return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_color_by, m_initialized); -} - -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 - bool transparent = volume->color[3] < 1.0f; - if (transparent) - volume->force_transparent = true; - volume->set_render_color(); - if (transparent) - volume->force_transparent = false; - - // 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(); - m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, m_color_by, m_initialized); - 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. - volume.indexed_vertex_array.release_geometry(); - 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 - volume.indexed_vertex_array.load_mesh(mesh, true); -#else - volume.indexed_vertex_array.load_mesh(mesh); -#endif // ENABLE_SMOOTH_NORMALS - } else { - // Reload the original volume. -#if ENABLE_SMOOTH_NORMALS - volume.indexed_vertex_array.load_mesh(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()); -#endif // ENABLE_SMOOTH_NORMALS - } - volume.finalize_geometry(true); - } - //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()) - m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp, m_initialized); - } - - // 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; - - 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); - 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; -} - -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); -} - -void GLCanvas3D::load_gcode_preview(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors) -{ - m_gcode_viewer.load(gcode_result, *this->fff_print(), m_initialized); - - 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(); -} - -void GLCanvas3D::refresh_gcode_preview_render_paths() -{ - m_gcode_viewer.refresh_render_paths(); - set_as_dirty(); - request_extra_frame(); -} - -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()) { - m_gcode_viewer.enable_legend(!m_gcode_viewer.is_legend_enabled()); - m_dirty = true; - wxGetApp().plater()->update_preview_bottom_toolbar(); - } - 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.start_dragging(); - 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_selection.stop_dragging(); - 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; - m_dirty = true; - } -// 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) - 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); - } - } - 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.start_dragging(); - m_selection.rotate(Vec3d(0.0, 0.0, angle_z_rad), TransformationType(TransformationType::World_Relative_Joint)); - m_selection.stop_dragging(); - 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(); - 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(); - 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(); - 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(); - 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(); - - if (evt.Dragging() && current_printer_technology() == ptFFF && fff_print()->config().complete_objects) { - switch (m_gizmos.get_current_type()) - { - case GLGizmosManager::EType::Move: - case GLGizmosManager::EType::Scale: - case GLGizmosManager::EType::Rotate: - { - update_sequential_clearance(); - break; - } - default: { break; } - } - } - else if (evt.Dragging()) { - switch (m_gizmos.get_current_type()) - { - case GLGizmosManager::EType::Move: - case GLGizmosManager::EType::Scale: - case GLGizmosManager::EType::Rotate: - { - show_sinking_contours(); - break; - } - default: { break; } - } - } - - 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 (evt.ButtonDown() && wxWindow::FindFocus() != m_canvas) - // Grab keyboard focus on any mouse click event. - m_canvas->SetFocus(); - - if (evt.Entering()) { -//#if defined(__WXMSW__) || defined(__linux__) -// // On Windows and Linux needs focus in order to catch key events - // Set focus in order to remove it from sidebar fields - if (m_canvas != nullptr) { - // Only set focus, if the top level window of this canvas is active. - auto p = dynamic_cast(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(); - 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); - } - 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; - } - } - else { - // 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 (m_picking_enabled && (!any_gizmo_active || !evt.CmdDown()) && (!m_hover_volume_idxs.empty() || !is_layers_editing_enabled())) { - 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); - bool ctrl_down = evt.CmdDown(); - - Selection::IndicesList curr_idxs = m_selection.get_volume_idxs(); - - if (already_selected && ctrl_down) - m_selection.remove(volume_idx); - else { - m_selection.add(volume_idx, !ctrl_down, true); - 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 (!m_hover_volume_idxs.empty()) { - 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. - 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; - m_selection.start_dragging(); - m_mouse.drag.start_position_3D = m_mouse.scene_position; - m_sequential_print_clearance_first_displacement = true; - m_moving = true; - } - } - } - } - } - else if (evt.Dragging() && evt.LeftIsDown() && m_layers_editing.state == LayersEditing::Unknown && m_mouse.drag.move_volume_idx != -1) { - 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()(2)) < EPSILON) { - // side view -> move selected volumes orthogonally to camera view direction - Linef3 ray = mouse_ray(pos); - 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 - 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 - Vec3d inters_vec = inters - m_mouse.drag.start_position_3D; - - Vec3d camera_right = camera.get_dir_right(); - Vec3d camera_up = camera.get_dir_up(); - - // finds projection of the vector along the camera axes - double projection_x = inters_vec.dot(camera_right); - 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. - float z0 = 0.0f; - float z1 = 1.0f; - cur_pos = Linef3(_mouse_to_3d(pos, &z0), _mouse_to_3d(pos, &z1)).intersect_plane(m_mouse.drag.start_position_3D(2)); - } - } - - 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()) { - m_rectangle_selection.dragging(pos.cast()); - 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 - else if (evt.LeftIsDown()) { - // if dragging over blank area with left button, rotate - if ((any_gizmo_active || m_hover_volume_idxs.empty()) && m_mouse.is_start_position_3D_defined()) { - const Vec3d rot = (Vec3d(pos.x(), pos.y(), 0.) - m_mouse.drag.start_position_3D) * (PI * TRACKBALLSIZE / 180.); - 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.)); - 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(0), (double)pos(1), 0.0); - } - else if (evt.MiddleIsDown() || evt.RightIsDown()) { - // If dragging over blank area with right 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); - 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 (evt.LeftUp()) - m_selection.stop_dragging(); - - 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 - 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(); - } - } - else if (object_idx == 1000) - // Move a wipe tower proxy. - wipe_tower_origin = v->get_volume_offset(); - } - - // 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) { - int object_idx = v->object_idx(); - if (object_idx == 1000) { // the wipe tower - Vec3d offset = v->get_volume_offset(); - post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3d(offset(0), offset(1), v->get_volume_rotation()(2)))); - } - 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_flatten(const Vec3d& normal, const std::string& snapshot_type) -{ - if (!snapshot_type.empty()) - wxGetApp().plater()->take_snapshot(_(snapshot_type)); - - m_selection.flattening_rotate(normal); - do_rotate(""); // avoid taking another snapshot -} - -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() -{ -} - -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(); - - 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); - 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(); - - 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); - 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) - sidebar.jump_to_option(selected); - action_taken = true; - } - - imgui->end(); - - return action_taken; -} - -bool GLCanvas3D::_render_arrange_menu(float pos_x) -{ - ImGuiWrapper *imgui = wxGetApp().imgui(); - - 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); - - 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; - }; - - static const std::array orange = { 0.923f, 0.504f, 0.264f, 1.0f }; - static const std::array gray = { 0.64f, 0.64f, 0.64f, 1.0f }; - - 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); - camera.apply_view_matrix(); - - 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 - BoundingBoxf3 t_bed_box = m_bed.extended_bounding_box().transformed(camera.get_view_matrix()); - 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); - - for (GLVolume* vol : visible_volumes) { - shader->set_uniform("uniform_color", (vol->printable && !vol->is_outside) ? (current_printer_technology() == ptSLA ? vol->color : orange) : gray); - // the volume may have been deactivated by an active gizmo - bool is_active = vol->is_active; - vol->is_active = true; - vol->render(); - vol->is_active = is_active; - } - - shader->stop_using(); - - glsafe(::glDisable(GL_DEPTH_TEST)); - - if (thumbnail_params.show_bed) - _render_bed(!camera.is_looking_downward(), false); - - // 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 - 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)) - { - BOOST_LOG_TRIVIAL(error) << "Main toolbar failed to load arrow texture."; - } - // m_gizmos is created at constructor, thus we can init arrow here. - if (!m_gizmos.init_arrow(arrow_data)) - { - 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 - 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)) - { - 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()) - { - BoundingBoxf3 sel_bb = m_selection.get_bounding_box(); - Vec3d sel_bb_center = sel_bb.center(); - Vec3d extend_by = sel_bb.max_size() * Vec3d::Ones(); - bb.merge(BoundingBoxf3(sel_bb_center - extend_by, sel_bb_center + extend_by)); - } - - bb.merge(include_bed_model ? m_bed.extended_bounding_box() : m_bed.build_volume().bounding_volume()); - - if (!m_main_toolbar.is_enabled()) - bb.merge(m_gcode_viewer.get_max_bounding_box()); - - 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)); - - 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()); - ::glEnable(GL_CLIP_PLANE0); - } - _render_volumes_for_picking(); - if (m_camera_clipping_plane.is_active()) - ::glDisable(GL_CLIP_PLANE0); - - _render_bed_for_picking(!wxGetApp().plater()->get_camera().is_looking_downward()); - - m_gizmos.render_current_gizmo_for_picking_pass(); - - if (m_multisample_allowed) - glsafe(::glEnable(GL_MULTISAMPLE)); - - int volume_id = -1; - int gizmo_id = -1; - - GLubyte color[4] = { 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(1) - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color)); - 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() - volume_id = color[0] + (color[1] << 8) + (color[2] << 16) - 1; - // gizmos' id are instead properly encoded by the color - gizmo_id = color[0] + (color[1] << 8) + (color[2] << 16); - } - } - 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(); - _render_bed_for_picking(!wxGetApp().plater()->get_camera().is_looking_downward()); - - 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() const -{ - 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(); - } - - glsafe(::glPushMatrix()); - glsafe(::glLoadIdentity()); - glsafe(::glMatrixMode(GL_PROJECTION)); - glsafe(::glPushMatrix()); - glsafe(::glLoadIdentity()); - - // Draws a bottom to top gradient over the complete screen. - glsafe(::glDisable(GL_DEPTH_TEST)); - - ::glBegin(GL_QUADS); - if (use_error_color) - ::glColor3fv(ERROR_BG_DARK_COLOR); - else - ::glColor3fv(DEFAULT_BG_DARK_COLOR); - - ::glVertex2f(-1.0f, -1.0f); - ::glVertex2f(1.0f, -1.0f); - - if (use_error_color) - ::glColor3fv(ERROR_BG_LIGHT_COLOR); - else - ::glColor3fv(DEFAULT_BG_LIGHT_COLOR); - - ::glVertex2f(1.0f, 1.0f); - ::glVertex2f(-1.0f, 1.0f); - glsafe(::glEnd()); - - glsafe(::glEnable(GL_DEPTH_TEST)); - - glsafe(::glPopMatrix()); - glsafe(::glMatrixMode(GL_MODELVIEW)); - glsafe(::glPopMatrix()); -} - -void GLCanvas3D::_render_bed(bool bottom, bool show_axes) -{ - 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); - - m_bed.render(*this, bottom, scale_factor, show_axes, show_texture); -} - -void GLCanvas3D::_render_bed_for_picking(bool bottom) -{ - float scale_factor = 1.0; -#if ENABLE_RETINA_GL - scale_factor = m_retina_helper->get_scale_factor(); -#endif // ENABLE_RETINA_GL - - m_bed.render_for_picking(*this, bottom, scale_factor); -} - -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()); - - 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; - 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); - }); - // 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 - 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); - }); - } - - // 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. - { - const 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: - { - m_volumes.render(type, false, wxGetApp().plater()->get_camera().get_view_matrix()); - break; - } - } - shader->stop_using(); - } - - m_camera_clipping_plane = ClippingPlane::ClipsNothing(); -} - -void GLCanvas3D::_render_gcode() -{ - m_gcode_viewer.render(); -} - -void GLCanvas3D::_render_selection() const -{ - 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() const -{ - 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)); - 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)); - - _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); - - glsafe(::glPopMatrix()); -} - -void GLCanvas3D::_render_volumes_for_picking() const -{ - static const GLfloat INV_255 = 1.0f / 255.0f; - - // do not cull backfaces to show broken geometry, if any - glsafe(::glDisable(GL_CULL_FACE)); - - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - - 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 - unsigned int id = 1 + volume.second.first; - unsigned int r = (id & (0x000000FF << 0)) << 0; - unsigned int g = (id & (0x000000FF << 8)) >> 8; - unsigned int b = (id & (0x000000FF << 16)) >> 16; - unsigned int a = picking_checksum_alpha_channel(r, g, b); - glsafe(::glColor4f((GLfloat)r * INV_255, (GLfloat)g * INV_255, (GLfloat)b * INV_255, (GLfloat)a * INV_255)); - volume.first->render(); - } - } - - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - - 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; - - Size cnv_size = get_canvas_size(); - float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); - - float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; - GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); - float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f; - float left = -0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar_width) * inv_zoom; - - 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; - - Size cnv_size = get_canvas_size(); - float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); - - float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; - GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); - float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f; - float left = (m_main_toolbar.get_width() - 0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar_width)) * inv_zoom; - - 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(); - - Size cnv_size = get_canvas_size(); - float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); - - float band = m_layers_editing.is_enabled() ? (wxGetApp().imgui()->get_style_scaling() * LayersEditing::THICKNESS_BAR_WIDTH) : 0.0; - - float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; - float left = (0.5f * (float)cnv_size.get_width() - (float)collapse_toolbar.get_width() - band) * inv_zoom; - - 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 - - Size cnv_size = get_canvas_size(); - 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; - view_toolbar.set_position(top, left); - view_toolbar.render(*this); -} - -#if ENABLE_SHOW_CAMERA_TARGET -void GLCanvas3D::_render_camera_target() const -{ - double half_length = 5.0; - - glsafe(::glDisable(GL_DEPTH_TEST)); - - glsafe(::glLineWidth(2.0f)); - ::glBegin(GL_LINES); - const Vec3d& target = m_camera.get_target(); - // draw line for x axis - ::glColor3f(1.0f, 0.0f, 0.0f); - ::glVertex3d(target(0) - half_length, target(1), target(2)); - ::glVertex3d(target(0) + half_length, target(1), target(2)); - // draw line for y axis - ::glColor3f(0.0f, 1.0f, 0.0f); - ::glVertex3d(target(0), target(1) - half_length, target(2)); - ::glVertex3d(target(0), target(1) + half_length, target(2)); - // draw line for z axis - ::glColor3f(0.0f, 0.0f, 1.0f); - ::glVertex3d(target(0), target(1), target(2) - half_length); - ::glVertex3d(target(0), target(1), target(2) + half_length); - glsafe(::glEnd()); -} -#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; - - 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); - { - 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; - it_caps_bottom->second.object.clear(); - it_caps_bottom->second.supports.clear(); - } - 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; - it_caps_top->second.object.clear(); - it_caps_top->second.supports.clear(); - } - } - 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; - - if ((bottom_obj_triangles.empty() || bottom_sup_triangles.empty() || top_obj_triangles.empty() || top_sup_triangles.empty()) && - !obj->get_slice_index().empty()) - { - 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. - 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); - // 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); - } - - if (slice_high.is_valid()) { - const ExPolygons& obj_top = slice_high.get_slice(soModel); - const ExPolygons& sup_top = slice_high.get_slice(soSupport); - // 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); - } - } - - 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)); - glsafe(::glRotatef(Geometry::rad2deg(inst.rotation), 0.0, 0.0, 1.0)); - if (obj->is_left_handed()) - // The polygons are mirrored by X. - glsafe(::glScalef(-1.0, 1.0, 1.0)); - 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()); - } - } - } -} - -void GLCanvas3D::_render_selection_sidebar_hints() const -{ - 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; - } - - bool selection_modifiers_only = m_selection.is_empty() || m_selection.is_any_modifier(); - - 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; - - 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; - - 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(); - Matrix4d modelview = camera.get_view_matrix().matrix(); - Matrix4d projection= camera.get_projection_matrix().matrix(); - Vec4i viewport(camera.get_viewport().data()); - - GLint y = viewport[3] - (GLint)mouse_pos(1); - GLfloat mouse_z; - if (z == nullptr) - glsafe(::glReadPixels((GLint)mouse_pos(0), y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, (void*)&mouse_z)); - else - mouse_z = *z; - - Vec3d out; - igl::unproject(Vec3d(mouse_pos(0), 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 std::array color = { 0.5f, 1.0f, 0.5f, 1.0f }; // 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()); - - GLVolume *volume = m_volumes.new_toolpath_volume(color, VERTEX_BUFFER_RESERVE_SIZE); - for (size_t i = 0; i < skirt_height; ++ i) { - volume->print_zs.emplace_back(print_zs[i]); - 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); - // Ensure that no volume grows over the limits. If the volume is too large, allocate a new one. - if (volume->indexed_vertex_array.vertices_and_normals_interleaved.size() > MAX_VERTEX_BUFFER_SIZE) { - GLVolume &vol = *volume; - volume = m_volumes.new_toolpath_volume(vol.color); - reserve_new_volume_finalize_old_volume(*volume, vol, m_initialized); - } - } - 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); -} - -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 = _parse_colors(str_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 const std::array& color_perimeters() { static std::array color = { 1.0f, 1.0f, 0.0f, 1.f }; return color; } // yellow - static const std::array& color_infill() { static std::array color = { 1.0f, 0.5f, 0.5f, 1.f }; return color; } // redish - static const std::array& color_support() { static std::array color = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // greenish - static const std::array& color_pause_or_custom_code() { static std::array color = { 0.5f, 0.5f, 0.5f, 1.f }; return color; } // 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 std::array& 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 std::array& color) { - // Allocate the volume before locking. - GLVolume *volume = new GLVolume(color); - volume->is_extrusion_path = true; - 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; - 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 - ]; - }; - 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))); - } - else - vols = { new_volume(ctxt.color_perimeters()), new_volume(ctxt.color_infill()), new_volume(ctxt.color_support()) }; - for (GLVolume *vol : vols) - // Reserving number of vertices (3x position + 3x color) - vol->indexed_vertex_array.reserve(VERTEX_BUFFER_RESERVE_SIZE / 6); - 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; - } - - 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()); - } - 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) - _3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy, - volume(idx_layer, layerm->region().config().perimeter_extruder.value, 0)); - 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()) - _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)); - } - } - } - if (ctxt.has_support) { - const SupportLayer *support_layer = dynamic_cast(layer); - if (support_layer) { - for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities) - _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)); - } - } - } - // 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 (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() > MAX_VERTEX_BUFFER_SIZE) { - vols[i] = new_volume(vol.color); - reserve_new_volume_finalize_old_volume(*vols[i], vol, false); - } - } - } - 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(); - }); - - 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. - m_volumes.volumes.erase( - std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), - [](const GLVolume *volume) { return volume->empty(); }), - m_volumes.volumes.end()); - for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) { - GLVolume* v = m_volumes.volumes[i]; - 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); - } - - 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 = _parse_colors(str_tool_colors); - - struct Ctxt - { - const Print *print; - const std::vector>* tool_colors; - Vec2f wipe_tower_pos; - float wipe_tower_angle; - - static const std::array& color_support() { static std::array color = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // 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 std::array& 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 std::array& color) { - auto *volume = new GLVolume(color); - volume->is_extrusion_path = true; - 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 (ctxt.color_by_tool()) { - for (size_t i = 0; i < ctxt.number_tools(); ++i) - vols.emplace_back(new_volume(ctxt.color_tool(i))); - } - else - vols = { new_volume(ctxt.color_support()) }; - for (GLVolume *volume : vols) - // Reserving number of vertices (3x position + 3x color) - volume->indexed_vertex_array.reserve(VERTEX_BUFFER_RESERVE_SIZE / 6); - 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); - vol.offsets.emplace_back(vol.indexed_vertex_array.quad_indices.size()); - vol.offsets.emplace_back(vol.indexed_vertex_array.triangle_indices.size()); - } - } - 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; - } - _3DScene::thick_lines_to_verts(lines, widths, heights, lines.front().a == lines.back().b, extrusions.print_z, - *vols[ctxt.volume_idx(e.tool, 0)]); - } - } - } - for (size_t i = 0; i < vols.size(); ++i) { - GLVolume &vol = *vols[i]; - if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() > MAX_VERTEX_BUFFER_SIZE) { - vols[i] = new_volume(vol.color); - reserve_new_volume_finalize_old_volume(*vols[i], vol, false); - } - } - for (GLVolume *vol : vols) - vol->indexed_vertex_array.shrink_to_fit(); - }); - - 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. - m_volumes.volumes.erase( - std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), - [](const GLVolume *volume) { return volume->empty(); }), - m_volumes.volumes.end()); - for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) { - GLVolume* v = m_volumes.volumes[i]; - 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); - } - - 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 std::array& color, bool outside_printer_detection_enabled) { - m_volumes.volumes.emplace_back(new GLVolume(color)); - GLVolume& v = *m_volumes.volumes.back(); -#if ENABLE_SMOOTH_NORMALS - v.indexed_vertex_array.load_mesh(mesh, true); -#else - v.indexed_vertex_array.load_mesh(mesh); -#endif // ENABLE_SMOOTH_NORMALS - v.indexed_vertex_array.finalize_geometry(m_initialized); - 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); -} - -std::vector> GLCanvas3D::_parse_colors(const std::vector& colors) -{ - static const float INV_255 = 1.0f / 255.0f; - - std::vector> output(colors.size(), { 1.0f, 1.0f, 1.0f, 1.0f }); - for (size_t i = 0; i < colors.size(); ++i) { - const std::string& color = colors[i]; - const char* c = color.data() + 1; - if (color.size() == 7 && color.front() == '#') { - for (size_t j = 0; j < 3; ++j) { - int digit1 = hex_digit_to_int(*c++); - int digit2 = hex_digit_to_int(*c++); - if (digit1 == -1 || digit2 == -1) - break; - - output[i][j] = float(digit1 * 16 + digit2) * INV_255; - } - } - } - return output; -} - -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); - bool selection_changed = false; - - if (m_hover_volume_idxs.empty()) { - if (!ctrl_pressed && (m_rectangle_selection.get_state() == GLSelectionRectangle::Select)) { - selection_changed = ! m_selection.is_empty(); - m_selection.remove_all(); - } - } - - 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; - } - } - - if (state == GLSelectionRectangle::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 (!selection_changed) - return; - - Plater::SuppressSnapshots suppress(wxGetApp().plater()); - - if ((state == GLSelectionRectangle::Select) && !ctrl_pressed) - m_selection.clear(); - - for (int i : m_hover_volume_idxs) { - if (state == GLSelectionRectangle::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 ->>>>>>> master_250 diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index cec8b4242..933192c23 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1,3242 +1,3 @@ -<<<<<<< HEAD -#include "libslic3r/Technologies.hpp" -#include "GUI_App.hpp" -#include "GUI_Init.hpp" -#include "GUI_ObjectList.hpp" -#include "GUI_ObjectManipulation.hpp" -#include "GUI_Factories.hpp" -#include "format.hpp" -#include "I18N.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "libslic3r/Utils.hpp" -#include "libslic3r/Model.hpp" -#include "libslic3r/I18N.hpp" -#include "libslic3r/PresetBundle.hpp" -#include "libslic3r/Color.hpp" - -#include "GUI.hpp" -#include "GUI_Utils.hpp" -#include "3DScene.hpp" -#include "MainFrame.hpp" -#include "Plater.hpp" -#include "GLCanvas3D.hpp" - -#include "../Utils/PresetUpdater.hpp" -#include "../Utils/PrintHost.hpp" -#include "../Utils/Process.hpp" -#include "../Utils/MacDarkMode.hpp" -#include "../Utils/AppUpdater.hpp" -#include "../Utils/WinRegistry.hpp" -#include "slic3r/Config/Snapshot.hpp" -#include "ConfigSnapshotDialog.hpp" -#include "FirmwareDialog.hpp" -#include "Preferences.hpp" -#include "Tab.hpp" -#include "SysInfoDialog.hpp" -#include "KBShortcutsDialog.hpp" -#include "UpdateDialogs.hpp" -#include "Mouse3DController.hpp" -#include "RemovableDriveManager.hpp" -#include "InstanceCheck.hpp" -#include "NotificationManager.hpp" -#include "UnsavedChangesDialog.hpp" -#include "SavePresetDialog.hpp" -#include "PrintHostDialogs.hpp" -#include "DesktopIntegrationDialog.hpp" -#include "SendSystemInfoDialog.hpp" - -#include "BitmapCache.hpp" -#include "Notebook.hpp" - -#ifdef __WXMSW__ -#include -#include -#ifdef _MSW_DARK_MODE -#include -#endif // _MSW_DARK_MODE -#endif -#ifdef _WIN32 -#include -#endif - -#if ENABLE_THUMBNAIL_GENERATOR_DEBUG -#include -#include -#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG - -// Needed for forcing menu icons back under gtk2 and gtk3 -#if defined(__WXGTK20__) || defined(__WXGTK3__) - #include -#endif - -using namespace std::literals; - -namespace Slic3r { -namespace GUI { - -class MainFrame; - -class SplashScreen : public wxSplashScreen -{ -public: - SplashScreen(const wxBitmap& bitmap, long splashStyle, int milliseconds, wxPoint pos = wxDefaultPosition) - : wxSplashScreen(bitmap, splashStyle, milliseconds, static_cast(wxGetApp().mainframe), wxID_ANY, wxDefaultPosition, wxDefaultSize, -#ifdef __APPLE__ - wxSIMPLE_BORDER | wxFRAME_NO_TASKBAR | wxSTAY_ON_TOP -#else - wxSIMPLE_BORDER | wxFRAME_NO_TASKBAR -#endif // !__APPLE__ - ) - { - wxASSERT(bitmap.IsOk()); - - int init_dpi = get_dpi_for_window(this); - this->SetPosition(pos); - this->CenterOnScreen(); - int new_dpi = get_dpi_for_window(this); - - m_scale = (float)(new_dpi) / (float)(init_dpi); - m_main_bitmap = bitmap; - - scale_bitmap(m_main_bitmap, m_scale); - - // init constant texts and scale fonts - init_constant_text(); - - // this font will be used for the action string - m_action_font = m_constant_text.credits_font.Bold(); - - // draw logo and constant info text - Decorate(m_main_bitmap); - } - - void SetText(const wxString& text) - { - set_bitmap(m_main_bitmap); - if (!text.empty()) { - wxBitmap bitmap(m_main_bitmap); - - wxMemoryDC memDC; - memDC.SelectObject(bitmap); - - memDC.SetFont(m_action_font); - memDC.SetTextForeground(wxColour(237, 107, 33)); - memDC.DrawText(text, int(m_scale * 60), m_action_line_y_position); - - memDC.SelectObject(wxNullBitmap); - set_bitmap(bitmap); -#ifdef __WXOSX__ - // without this code splash screen wouldn't be updated under OSX - wxYield(); -#endif - } - } - - static wxBitmap MakeBitmap(wxBitmap bmp) - { - if (!bmp.IsOk()) - return wxNullBitmap; - - // create dark grey background for the splashscreen - // It will be 5/3 of the weight of the bitmap - int width = lround((double)5 / 3 * bmp.GetWidth()); - int height = bmp.GetHeight(); - - wxImage image(width, height); - unsigned char* imgdata_ = image.GetData(); - for (int i = 0; i < width * height; ++i) { - *imgdata_++ = 51; - *imgdata_++ = 51; - *imgdata_++ = 51; - } - - wxBitmap new_bmp(image); - - wxMemoryDC memDC; - memDC.SelectObject(new_bmp); - memDC.DrawBitmap(bmp, width - bmp.GetWidth(), 0, true); - - return new_bmp; - } - - void Decorate(wxBitmap& bmp) - { - if (!bmp.IsOk()) - return; - - // draw text to the box at the left of the splashscreen. - // this box will be 2/5 of the weight of the bitmap, and be at the left. - int width = lround(bmp.GetWidth() * 0.4); - - // load bitmap for logo - BitmapCache bmp_cache; - int logo_size = lround(width * 0.25); - wxBitmap logo_bmp = *bmp_cache.load_svg(wxGetApp().logo_name(), logo_size, logo_size); - - wxCoord margin = int(m_scale * 20); - - wxRect banner_rect(wxPoint(0, logo_size), wxPoint(width, bmp.GetHeight())); - banner_rect.Deflate(margin, 2 * margin); - - // use a memory DC to draw directly onto the bitmap - wxMemoryDC memDc(bmp); - - // draw logo - memDc.DrawBitmap(logo_bmp, margin, margin, true); - - // draw the (white) labels inside of our black box (at the left of the splashscreen) - memDc.SetTextForeground(wxColour(255, 255, 255)); - - memDc.SetFont(m_constant_text.title_font); - memDc.DrawLabel(m_constant_text.title, banner_rect, wxALIGN_TOP | wxALIGN_LEFT); - - int title_height = memDc.GetTextExtent(m_constant_text.title).GetY(); - banner_rect.SetTop(banner_rect.GetTop() + title_height); - banner_rect.SetHeight(banner_rect.GetHeight() - title_height); - - memDc.SetFont(m_constant_text.version_font); - memDc.DrawLabel(m_constant_text.version, banner_rect, wxALIGN_TOP | wxALIGN_LEFT); - int version_height = memDc.GetTextExtent(m_constant_text.version).GetY(); - - memDc.SetFont(m_constant_text.credits_font); - memDc.DrawLabel(m_constant_text.credits, banner_rect, wxALIGN_BOTTOM | wxALIGN_LEFT); - int credits_height = memDc.GetMultiLineTextExtent(m_constant_text.credits).GetY(); - int text_height = memDc.GetTextExtent("text").GetY(); - - // calculate position for the dynamic text - int logo_and_header_height = margin + logo_size + title_height + version_height; - m_action_line_y_position = logo_and_header_height + 0.5 * (bmp.GetHeight() - margin - credits_height - logo_and_header_height - text_height); - } - -private: - wxBitmap m_main_bitmap; - wxFont m_action_font; - int m_action_line_y_position; - float m_scale {1.0}; - - struct ConstantText - { - wxString title; - wxString version; - wxString credits; - - wxFont title_font; - wxFont version_font; - wxFont credits_font; - - void init(wxFont init_font) - { - // title - title = wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME; - - // dynamically get the version to display - version = _L("Version") + " " + std::string(SLIC3R_VERSION); - - // credits infornation - credits = title + " " + - _L("is based on Slic3r by Alessandro Ranellucci and the RepRap community.") + "\n" + - _L("Developed by Prusa Research.")+ "\n\n" + - title + " " + _L("is licensed under the") + " " + _L("GNU Affero General Public License, version 3") + "\n\n" + - _L("Contributions by Vojtech Bubnik, Enrico Turri, Oleksandra Iushchenko, Tamas Meszaros, Lukas Matena, Vojtech Kral, David Kocik and numerous others.") + "\n\n" + - _L("Artwork model by M Boyer"); - - title_font = version_font = credits_font = init_font; - } - } - m_constant_text; - - void init_constant_text() - { - m_constant_text.init(get_default_font(this)); - - // As default we use a system font for current display. - // Scale fonts in respect to banner width - - int text_banner_width = lround(0.4 * m_main_bitmap.GetWidth()) - roundl(m_scale * 50); // banner_width - margins - - float title_font_scale = (float)text_banner_width / GetTextExtent(m_constant_text.title).GetX(); - scale_font(m_constant_text.title_font, title_font_scale > 3.5f ? 3.5f : title_font_scale); - - float version_font_scale = (float)text_banner_width / GetTextExtent(m_constant_text.version).GetX(); - scale_font(m_constant_text.version_font, version_font_scale > 2.f ? 2.f : version_font_scale); - - // The width of the credits information string doesn't respect to the banner width some times. - // So, scale credits_font in the respect to the longest string width - int longest_string_width = word_wrap_string(m_constant_text.credits); - float font_scale = (float)text_banner_width / longest_string_width; - scale_font(m_constant_text.credits_font, font_scale); - } - - void set_bitmap(wxBitmap& bmp) - { - m_window->SetBitmap(bmp); - m_window->Refresh(); - m_window->Update(); - } - - void scale_bitmap(wxBitmap& bmp, float scale) - { - if (scale == 1.0) - return; - - wxImage image = bmp.ConvertToImage(); - if (!image.IsOk() || image.GetWidth() == 0 || image.GetHeight() == 0) - return; - - int width = int(scale * image.GetWidth()); - int height = int(scale * image.GetHeight()); - image.Rescale(width, height, wxIMAGE_QUALITY_BILINEAR); - - bmp = wxBitmap(std::move(image)); - } - - void scale_font(wxFont& font, float scale) - { -#ifdef __WXMSW__ - // Workaround for the font scaling in respect to the current active display, - // not for the primary display, as it's implemented in Font.cpp - // See https://github.com/wxWidgets/wxWidgets/blob/master/src/msw/font.cpp - // void wxNativeFontInfo::SetFractionalPointSize(float pointSizeNew) - wxNativeFontInfo nfi= *font.GetNativeFontInfo(); - float pointSizeNew = scale * font.GetPointSize(); - nfi.lf.lfHeight = nfi.GetLogFontHeightAtPPI(pointSizeNew, get_dpi_for_window(this)); - nfi.pointSize = pointSizeNew; - font = wxFont(nfi); -#else - font.Scale(scale); -#endif //__WXMSW__ - } - - // wrap a string for the strings no longer then 55 symbols - // return extent of the longest string - int word_wrap_string(wxString& input) - { - size_t line_len = 55;// count of symbols in one line - int idx = -1; - size_t cur_len = 0; - - wxString longest_sub_string; - auto get_longest_sub_string = [input](wxString &longest_sub_str, size_t cur_len, size_t i) { - if (cur_len > longest_sub_str.Len()) - longest_sub_str = input.SubString(i - cur_len + 1, i); - }; - - for (size_t i = 0; i < input.Len(); i++) - { - cur_len++; - if (input[i] == ' ') - idx = i; - if (input[i] == '\n') - { - get_longest_sub_string(longest_sub_string, cur_len, i); - idx = -1; - cur_len = 0; - } - if (cur_len >= line_len && idx >= 0) - { - get_longest_sub_string(longest_sub_string, cur_len, i); - input[idx] = '\n'; - cur_len = i - static_cast(idx); - } - } - - return GetTextExtent(longest_sub_string).GetX(); - } -}; - - -#ifdef __linux__ -bool static check_old_linux_datadir(const wxString& app_name) { - // If we are on Linux and the datadir does not exist yet, look into the old - // location where the datadir was before version 2.3. If we find it there, - // tell the user that he might wanna migrate to the new location. - // (https://github.com/prusa3d/PrusaSlicer/issues/2911) - // To be precise, the datadir should exist, it is created when single instance - // lock happens. Instead of checking for existence, check the contents. - - namespace fs = boost::filesystem; - - std::string new_path = Slic3r::data_dir(); - - wxString dir; - if (! wxGetEnv(wxS("XDG_CONFIG_HOME"), &dir) || dir.empty() ) - dir = wxFileName::GetHomeDir() + wxS("/.config"); - std::string default_path = (dir + "/" + app_name).ToUTF8().data(); - - if (new_path != default_path) { - // This happens when the user specifies a custom --datadir. - // Do not show anything in that case. - return true; - } - - fs::path data_dir = fs::path(new_path); - if (! fs::is_directory(data_dir)) - return true; // This should not happen. - - int file_count = std::distance(fs::directory_iterator(data_dir), fs::directory_iterator()); - - if (file_count <= 1) { // just cache dir with an instance lock - std::string old_path = wxStandardPaths::Get().GetUserDataDir().ToUTF8().data(); - - if (fs::is_directory(old_path)) { - wxString msg = from_u8((boost::format(_u8L("Starting with %1% 2.3, configuration " - "directory on Linux has changed (according to XDG Base Directory Specification) to \n%2%.\n\n" - "This directory did not exist yet (maybe you run the new version for the first time).\nHowever, " - "an old %1% configuration directory was detected in \n%3%.\n\n" - "Consider moving the contents of the old directory to the new location in order to access " - "your profiles, etc.\nNote that if you decide to downgrade %1% in future, it will use the old " - "location again.\n\n" - "What do you want to do now?")) % SLIC3R_APP_NAME % new_path % old_path).str()); - wxString caption = from_u8((boost::format(_u8L("%s - BREAKING CHANGE")) % SLIC3R_APP_NAME).str()); - RichMessageDialog dlg(nullptr, msg, caption, wxYES_NO); - dlg.SetYesNoLabels(_L("Quit, I will move my data now"), _L("Start the application")); - if (dlg.ShowModal() != wxID_NO) - return false; - } - } else { - // If the new directory exists, be silent. The user likely already saw the message. - } - return true; -} -#endif - -#ifdef _WIN32 -#if 0 // External Updater is replaced with AppUpdater.cpp -static bool run_updater_win() -{ - // find updater exe - boost::filesystem::path path_updater = boost::dll::program_location().parent_path() / "prusaslicer-updater.exe"; - // run updater. Original args: /silent -restartapp prusa-slicer.exe -startappfirst - std::string msg; - bool res = create_process(path_updater, L"/silent", msg); - if (!res) - BOOST_LOG_TRIVIAL(error) << msg; - return res; -} -#endif // 0 -#endif // _WIN32 - -struct FileWildcards { - std::string_view title; - std::vector file_extensions; -}; - -static const FileWildcards file_wildcards_by_type[FT_SIZE] = { - /* FT_STL */ { "STL files"sv, { ".stl"sv } }, - /* FT_OBJ */ { "OBJ files"sv, { ".obj"sv } }, - /* FT_OBJECT */ { "Object files"sv, { ".stl"sv, ".obj"sv } }, - /* FT_AMF */ { "AMF files"sv, { ".amf"sv, ".zip.amf"sv, ".xml"sv } }, - /* FT_3MF */ { "3MF files"sv, { ".3mf"sv } }, - /* FT_GCODE */ { "G-code files"sv, { ".gcode"sv, ".gco"sv, ".g"sv, ".ngc"sv } }, - /* FT_MODEL */ { "Known files"sv, { ".stl"sv, ".obj"sv, ".3mf"sv, ".amf"sv, ".zip.amf"sv, ".xml"sv } }, - /* FT_PROJECT */ { "Project files"sv, { ".3mf"sv, ".amf"sv, ".zip.amf"sv } }, - /* FT_GALLERY */ { "Known files"sv, { ".stl"sv, ".obj"sv } }, - - /* FT_INI */ { "INI files"sv, { ".ini"sv } }, - /* FT_SVG */ { "SVG files"sv, { ".svg"sv } }, - - /* FT_TEX */ { "Texture"sv, { ".png"sv, ".svg"sv } }, - - /* FT_SL1 */ { "Masked SLA files"sv, { ".sl1"sv, ".sl1s"sv, ".pwmx"sv } }, -}; - -#if ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR -wxString file_wildcards(FileType file_type) -{ - const FileWildcards& data = file_wildcards_by_type[file_type]; - std::string title; - std::string mask; - - // Generate cumulative first item - for (const std::string_view& ext : data.file_extensions) { - if (title.empty()) { - title = "*"; - title += ext; - mask = title; - } - else { - title += ", *"; - title += ext; - mask += ";*"; - mask += ext; - } - mask += ";*"; - mask += boost::to_upper_copy(std::string(ext)); - } - - wxString ret = GUI::format_wxstr("%s (%s)|%s", data.title, title, mask); - - // Adds an item for each of the extensions - if (data.file_extensions.size() > 1) { - for (const std::string_view& ext : data.file_extensions) { - title = "*"; - title += ext; - ret += GUI::format_wxstr("|%s (%s)|%s", data.title, title, title); - } - } - - return ret; -} -#else -// This function produces a Win32 file dialog file template mask to be consumed by wxWidgets on all platforms. -// The function accepts a custom extension parameter. If the parameter is provided, the custom extension -// will be added as a fist to the list. This is important for a "file save" dialog on OSX, which strips -// an extension from the provided initial file name and substitutes it with the default extension (the first one in the template). -wxString file_wildcards(FileType file_type, const std::string &custom_extension) -{ - const FileWildcards& data = file_wildcards_by_type[file_type]; - std::string title; - std::string mask; - std::string custom_ext_lower; - - if (! custom_extension.empty()) { - // Generate an extension into the title mask and into the list of extensions. - custom_ext_lower = boost::to_lower_copy(custom_extension); - const std::string custom_ext_upper = boost::to_upper_copy(custom_extension); - if (custom_ext_lower == custom_extension) { - // Add a lower case version. - title = std::string("*") + custom_ext_lower; - mask = title; - // Add an upper case version. - mask += ";*"; - mask += custom_ext_upper; - } else if (custom_ext_upper == custom_extension) { - // Add an upper case version. - title = std::string("*") + custom_ext_upper; - mask = title; - // Add a lower case version. - mask += ";*"; - mask += custom_ext_lower; - } else { - // Add the mixed case version only. - title = std::string("*") + custom_extension; - mask = title; - } - } - - for (const std::string_view &ext : data.file_extensions) - // Only add an extension if it was not added first as the custom extension. - if (ext != custom_ext_lower) { - if (title.empty()) { - title = "*"; - title += ext; - mask = title; - } else { - title += ", *"; - title += ext; - mask += ";*"; - mask += ext; - } - mask += ";*"; - mask += boost::to_upper_copy(std::string(ext)); - } - - return GUI::format_wxstr("%s (%s)|%s", data.title, title, mask); -} -#endif // ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR - -static std::string libslic3r_translate_callback(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str().data(); } - -#ifdef WIN32 -#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) -static void register_win32_dpi_event() -{ - enum { WM_DPICHANGED_ = 0x02e0 }; - - wxWindow::MSWRegisterMessageHandler(WM_DPICHANGED_, [](wxWindow *win, WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) { - const int dpi = wParam & 0xffff; - const auto rect = reinterpret_cast(lParam); - const wxRect wxrect(wxPoint(rect->top, rect->left), wxPoint(rect->bottom, rect->right)); - - DpiChangedEvent evt(EVT_DPI_CHANGED_SLICER, dpi, wxrect); - win->GetEventHandler()->AddPendingEvent(evt); - - return true; - }); -} -#endif // !wxVERSION_EQUAL_OR_GREATER_THAN - -static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 }; - -static void register_win32_device_notification_event() -{ - wxWindow::MSWRegisterMessageHandler(WM_DEVICECHANGE, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) { - // Some messages are sent to top level windows by default, some messages are sent to only registered windows, and we explictely register on MainFrame only. - auto main_frame = dynamic_cast(win); - auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater(); - if (plater == nullptr) - // Maybe some other top level window like a dialog or maybe a pop-up menu? - return true; - PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam; - switch (wParam) { - case DBT_DEVICEARRIVAL: - if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME) - plater->GetEventHandler()->AddPendingEvent(VolumeAttachedEvent(EVT_VOLUME_ATTACHED)); - else if (lpdb->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) { - PDEV_BROADCAST_DEVICEINTERFACE lpdbi = (PDEV_BROADCAST_DEVICEINTERFACE)lpdb; -// if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_VOLUME) { -// printf("DBT_DEVICEARRIVAL %d - Media has arrived: %ws\n", msg_count, lpdbi->dbcc_name); - if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_HID) - plater->GetEventHandler()->AddPendingEvent(HIDDeviceAttachedEvent(EVT_HID_DEVICE_ATTACHED, boost::nowide::narrow(lpdbi->dbcc_name))); - } - break; - case DBT_DEVICEREMOVECOMPLETE: - if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME) - plater->GetEventHandler()->AddPendingEvent(VolumeDetachedEvent(EVT_VOLUME_DETACHED)); - else if (lpdb->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) { - PDEV_BROADCAST_DEVICEINTERFACE lpdbi = (PDEV_BROADCAST_DEVICEINTERFACE)lpdb; -// if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_VOLUME) -// printf("DBT_DEVICEARRIVAL %d - Media was removed: %ws\n", msg_count, lpdbi->dbcc_name); - if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_HID) - plater->GetEventHandler()->AddPendingEvent(HIDDeviceDetachedEvent(EVT_HID_DEVICE_DETACHED, boost::nowide::narrow(lpdbi->dbcc_name))); - } - break; - default: - break; - } - return true; - }); - - wxWindow::MSWRegisterMessageHandler(MainFrame::WM_USER_MEDIACHANGED, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) { - // Some messages are sent to top level windows by default, some messages are sent to only registered windows, and we explictely register on MainFrame only. - auto main_frame = dynamic_cast(win); - auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater(); - if (plater == nullptr) - // Maybe some other top level window like a dialog or maybe a pop-up menu? - return true; - wchar_t sPath[MAX_PATH]; - if (lParam == SHCNE_MEDIAINSERTED || lParam == SHCNE_MEDIAREMOVED) { - struct _ITEMIDLIST* pidl = *reinterpret_cast(wParam); - if (! SHGetPathFromIDList(pidl, sPath)) { - BOOST_LOG_TRIVIAL(error) << "MediaInserted: SHGetPathFromIDList failed"; - return false; - } - } - switch (lParam) { - case SHCNE_MEDIAINSERTED: - { - //printf("SHCNE_MEDIAINSERTED %S\n", sPath); - plater->GetEventHandler()->AddPendingEvent(VolumeAttachedEvent(EVT_VOLUME_ATTACHED)); - break; - } - case SHCNE_MEDIAREMOVED: - { - //printf("SHCNE_MEDIAREMOVED %S\n", sPath); - plater->GetEventHandler()->AddPendingEvent(VolumeDetachedEvent(EVT_VOLUME_DETACHED)); - break; - } - default: -// printf("Unknown\n"); - break; - } - return true; - }); - - wxWindow::MSWRegisterMessageHandler(WM_INPUT, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) { - auto main_frame = dynamic_cast(Slic3r::GUI::find_toplevel_parent(win)); - auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater(); -// if (wParam == RIM_INPUTSINK && plater != nullptr && main_frame->IsActive()) { - if (wParam == RIM_INPUT && plater != nullptr && main_frame->IsActive()) { - RAWINPUT raw; - UINT rawSize = sizeof(RAWINPUT); - ::GetRawInputData((HRAWINPUT)lParam, RID_INPUT, &raw, &rawSize, sizeof(RAWINPUTHEADER)); - if (raw.header.dwType == RIM_TYPEHID && plater->get_mouse3d_controller().handle_raw_input_win32(raw.data.hid.bRawData, raw.data.hid.dwSizeHid)) - return true; - } - return false; - }); - - wxWindow::MSWRegisterMessageHandler(WM_COPYDATA, [](wxWindow* win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) { - COPYDATASTRUCT* copy_data_structure = { 0 }; - copy_data_structure = (COPYDATASTRUCT*)lParam; - if (copy_data_structure->dwData == 1) { - LPCWSTR arguments = (LPCWSTR)copy_data_structure->lpData; - Slic3r::GUI::wxGetApp().other_instance_message_handler()->handle_message(boost::nowide::narrow(arguments)); - } - return true; - }); -} -#endif // WIN32 - -static void generic_exception_handle() -{ - // Note: Some wxWidgets APIs use wxLogError() to report errors, eg. wxImage - // - see https://docs.wxwidgets.org/3.1/classwx_image.html#aa249e657259fe6518d68a5208b9043d0 - // - // wxLogError typically goes around exception handling and display an error dialog some time - // after an error is logged even if exception handling and OnExceptionInMainLoop() take place. - // This is why we use wxLogError() here as well instead of a custom dialog, because it accumulates - // errors if multiple have been collected and displays just one error message for all of them. - // Otherwise we would get multiple error messages for one missing png, for example. - // - // If a custom error message window (or some other solution) were to be used, it would be necessary - // to turn off wxLogError() usage in wx APIs, most notably in wxImage - // - see https://docs.wxwidgets.org/trunk/classwx_image.html#aa32e5d3507cc0f8c3330135bc0befc6a - - try { - throw; - } catch (const std::bad_alloc& ex) { - // bad_alloc in main thread is most likely fatal. Report immediately to the user (wxLogError would be delayed) - // and terminate the app so it is at least certain to happen now. - wxString errmsg = wxString::Format(_L("%s has encountered an error. It was likely caused by running out of memory. " - "If you are sure you have enough RAM on your system, this may also be a bug and we would " - "be glad if you reported it.\n\nThe application will now terminate."), SLIC3R_APP_NAME); - wxMessageBox(errmsg + "\n\n" + wxString(ex.what()), _L("Fatal error"), wxOK | wxICON_ERROR); - BOOST_LOG_TRIVIAL(error) << boost::format("std::bad_alloc exception: %1%") % ex.what(); - std::terminate(); - } catch (const boost::io::bad_format_string& ex) { - wxString errmsg = _L("PrusaSlicer has encountered a localization error. " - "Please report to PrusaSlicer team, what language was active and in which scenario " - "this issue happened. Thank you.\n\nThe application will now terminate."); - wxMessageBox(errmsg + "\n\n" + wxString(ex.what()), _L("Critical error"), wxOK | wxICON_ERROR); - BOOST_LOG_TRIVIAL(error) << boost::format("Uncaught exception: %1%") % ex.what(); - std::terminate(); - throw; - } catch (const std::exception& ex) { - wxLogError(format_wxstr(_L("Internal error: %1%"), ex.what())); - BOOST_LOG_TRIVIAL(error) << boost::format("Uncaught exception: %1%") % ex.what(); - throw; - } -} - -void GUI_App::post_init() -{ - assert(initialized()); - if (! this->initialized()) - throw Slic3r::RuntimeError("Calling post_init() while not yet initialized"); - - if (this->is_gcode_viewer()) { - if (! this->init_params->input_files.empty()) - this->plater()->load_gcode(wxString::FromUTF8(this->init_params->input_files[0].c_str())); - } - else { - if (! this->init_params->preset_substitutions.empty()) - show_substitutions_info(this->init_params->preset_substitutions); - -#if 0 - // Load the cummulative config over the currently active profiles. - //FIXME if multiple configs are loaded, only the last one will have an effect. - // We need to decide what to do about loading of separate presets (just print preset, just filament preset etc). - // As of now only the full configs are supported here. - if (!m_print_config.empty()) - this->gui->mainframe->load_config(m_print_config); -#endif - if (! this->init_params->load_configs.empty()) - // Load the last config to give it a name at the UI. The name of the preset may be later - // changed by loading an AMF or 3MF. - //FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config. - this->mainframe->load_config_file(this->init_params->load_configs.back()); - // If loading a 3MF file, the config is loaded from the last one. - if (!this->init_params->input_files.empty()) { - const std::vector res = this->plater()->load_files(this->init_params->input_files, true, true); - if (!res.empty() && this->init_params->input_files.size() == 1) { - // Update application titlebar when opening a project file - const std::string& filename = this->init_params->input_files.front(); - if (boost::algorithm::iends_with(filename, ".amf") || - boost::algorithm::iends_with(filename, ".amf.xml") || - boost::algorithm::iends_with(filename, ".3mf")) - this->plater()->set_project_filename(from_u8(filename)); - } - } - if (! this->init_params->extra_config.empty()) - this->mainframe->load_config(this->init_params->extra_config); - } - - // show "Did you know" notification - if (app_config->get("show_hints") == "1" && ! is_gcode_viewer()) - plater_->get_notification_manager()->push_hint_notification(true); - - // The extra CallAfter() is needed because of Mac, where this is the only way - // to popup a modal dialog on start without screwing combo boxes. - // This is ugly but I honestly found no better way to do it. - // Neither wxShowEvent nor wxWindowCreateEvent work reliably. - if (this->preset_updater) { // G-Code Viewer does not initialize preset_updater. - if (! this->check_updates(false)) - // Configuration is not compatible and reconfigure was refused by the user. Application is closing. - return; - CallAfter([this] { - bool cw_showed = this->config_wizard_startup(); - this->preset_updater->sync(preset_bundle); - this->app_version_check(false); - if (! cw_showed) { - // The CallAfter is needed as well, without it, GL extensions did not show. - // Also, we only want to show this when the wizard does not, so the new user - // sees something else than "we want something" on the first start. - show_send_system_info_dialog_if_needed(); - } - }); - } - - // Set PrusaSlicer version and save to PrusaSlicer.ini or PrusaSlicerGcodeViewer.ini. - app_config->set("version", SLIC3R_VERSION); - app_config->save(); - -#ifdef _WIN32 - // Sets window property to mainframe so other instances can indentify it. - OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int); -#endif //WIN32 -} - -IMPLEMENT_APP(GUI_App) - -GUI_App::GUI_App(EAppMode mode) - : wxApp() - , m_app_mode(mode) - , m_em_unit(10) - , m_imgui(new ImGuiWrapper()) - , m_removable_drive_manager(std::make_unique()) - , m_other_instance_message_handler(std::make_unique()) -{ - //app config initializes early becasuse it is used in instance checking in PrusaSlicer.cpp - this->init_app_config(); - // init app downloader after path to datadir is set - m_app_updater = std::make_unique(); -} - -GUI_App::~GUI_App() -{ - if (app_config != nullptr) - delete app_config; - - if (preset_bundle != nullptr) - delete preset_bundle; - - if (preset_updater != nullptr) - delete preset_updater; -} - -// If formatted for github, plaintext with OpenGL extensions enclosed into
. -// Otherwise HTML formatted for the system info dialog. -std::string GUI_App::get_gl_info(bool for_github) -{ - return OpenGLManager::get_gl_info().to_string(for_github); -} - -wxGLContext* GUI_App::init_glcontext(wxGLCanvas& canvas) -{ - return m_opengl_mgr.init_glcontext(canvas); -} - -bool GUI_App::init_opengl() -{ -#ifdef __linux__ - bool status = m_opengl_mgr.init_gl(); - m_opengl_initialized = true; - return status; -#else - return m_opengl_mgr.init_gl(); -#endif -} - -// gets path to PrusaSlicer.ini, returns semver from first line comment -static boost::optional parse_semver_from_ini(std::string path) -{ - std::ifstream stream(path); - std::stringstream buffer; - buffer << stream.rdbuf(); - std::string body = buffer.str(); - size_t start = body.find("PrusaSlicer "); - if (start == std::string::npos) - return boost::none; - body = body.substr(start + 12); - size_t end = body.find_first_of(" \n"); - if (end < body.size()) - body.resize(end); - return Semver::parse(body); -} - -void GUI_App::init_app_config() -{ - // Profiles for the alpha are stored into the PrusaSlicer-alpha directory to not mix with the current release. -// SetAppName(SLIC3R_APP_KEY); - SetAppName(SLIC3R_APP_KEY "-alpha"); -// SetAppName(SLIC3R_APP_KEY "-beta"); - - -// SetAppDisplayName(SLIC3R_APP_NAME); - - // Set the Slic3r data directory at the Slic3r XS module. - // Unix: ~/ .Slic3r - // Windows : "C:\Users\username\AppData\Roaming\Slic3r" or "C:\Documents and Settings\username\Application Data\Slic3r" - // Mac : "~/Library/Application Support/Slic3r" - - if (data_dir().empty()) { - #ifndef __linux__ - set_data_dir(wxStandardPaths::Get().GetUserDataDir().ToUTF8().data()); - #else - // Since version 2.3, config dir on Linux is in ${XDG_CONFIG_HOME}. - // https://github.com/prusa3d/PrusaSlicer/issues/2911 - wxString dir; - if (! wxGetEnv(wxS("XDG_CONFIG_HOME"), &dir) || dir.empty() ) - dir = wxFileName::GetHomeDir() + wxS("/.config"); - set_data_dir((dir + "/" + GetAppName()).ToUTF8().data()); - #endif - } else { - m_datadir_redefined = true; - } - - if (!app_config) - app_config = new AppConfig(is_editor() ? AppConfig::EAppMode::Editor : AppConfig::EAppMode::GCodeViewer); - - // load settings - m_app_conf_exists = app_config->exists(); - if (m_app_conf_exists) { - std::string error = app_config->load(); - if (!error.empty()) { - // Error while parsing config file. We'll customize the error message and rethrow to be displayed. - if (is_editor()) { - throw Slic3r::RuntimeError( - _u8L("Error parsing PrusaSlicer config file, it is probably corrupted. " - "Try to manually delete the file to recover from the error. Your user profiles will not be affected.") + - "\n\n" + app_config->config_path() + "\n\n" + error); - } - else { - throw Slic3r::RuntimeError( - _u8L("Error parsing PrusaGCodeViewer config file, it is probably corrupted. " - "Try to manually delete the file to recover from the error.") + - "\n\n" + app_config->config_path() + "\n\n" + error); - } - } - } -} - -// returns old config path to copy from if such exists, -// returns an empty string if such config path does not exists or if it cannot be loaded. -std::string GUI_App::check_older_app_config(Semver current_version, bool backup) -{ - std::string older_data_dir_path; - - // If the config folder is redefined - do not check - if (m_datadir_redefined) - return {}; - - // find other version app config (alpha / beta / release) - std::string config_path = app_config->config_path(); - boost::filesystem::path parent_file_path(config_path); - std::string filename = parent_file_path.filename().string(); - parent_file_path.remove_filename().remove_filename(); - - std::vector candidates; - - if (SLIC3R_APP_KEY "-alpha" != GetAppName()) candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY "-alpha" / filename); - if (SLIC3R_APP_KEY "-beta" != GetAppName()) candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY "-beta" / filename); - if (SLIC3R_APP_KEY != GetAppName()) candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY / filename); - - Semver last_semver = current_version; - for (const auto& candidate : candidates) { - if (boost::filesystem::exists(candidate)) { - // parse - boost::optionalother_semver = parse_semver_from_ini(candidate.string()); - if (other_semver && *other_semver > last_semver) { - last_semver = *other_semver; - older_data_dir_path = candidate.parent_path().string(); - } - } - } - if (older_data_dir_path.empty()) - return {}; - BOOST_LOG_TRIVIAL(info) << "last app config file used: " << older_data_dir_path; - // ask about using older data folder - - InfoDialog msg(nullptr - , format_wxstr(_L("You are opening %1% version %2%."), SLIC3R_APP_NAME, SLIC3R_VERSION) - , backup ? - format_wxstr(_L( - "The active configuration was created by %1% %2%," - "\nwhile a newer configuration was found in %3%" - "\ncreated by %1% %4%." - "\n\nShall the newer configuration be imported?" - "\nIf so, your active configuration will be backed up before importing the new configuration." - ) - , SLIC3R_APP_NAME, current_version.to_string(), older_data_dir_path, last_semver.to_string()) - : format_wxstr(_L( - "An existing configuration was found in %3%" - "\ncreated by %1% %2%." - "\n\nShall this configuration be imported?" - ) - , SLIC3R_APP_NAME, last_semver.to_string(), older_data_dir_path) - , true, wxYES_NO); - - if (backup) { - msg.SetButtonLabel(wxID_YES, _L("Import")); - msg.SetButtonLabel(wxID_NO, _L("Don't import")); - } - - if (msg.ShowModal() == wxID_YES) { - std::string snapshot_id; - if (backup) { - const Config::Snapshot* snapshot{ nullptr }; - if (! GUI::Config::take_config_snapshot_cancel_on_error(*app_config, Config::Snapshot::SNAPSHOT_USER, "", - _u8L("Continue and import newer configuration?"), &snapshot)) - return {}; - if (snapshot) { - // Save snapshot ID before loading the alternate AppConfig, as loading the alternate AppConfig may fail. - snapshot_id = snapshot->id; - assert(! snapshot_id.empty()); - app_config->set("on_snapshot", snapshot_id); - } else - BOOST_LOG_TRIVIAL(error) << "Failed to take congiguration snapshot"; - } - - // load app config from older file - std::string error = app_config->load((boost::filesystem::path(older_data_dir_path) / filename).string()); - if (!error.empty()) { - // Error while parsing config file. We'll customize the error message and rethrow to be displayed. - if (is_editor()) { - throw Slic3r::RuntimeError( - _u8L("Error parsing PrusaSlicer config file, it is probably corrupted. " - "Try to manually delete the file to recover from the error. Your user profiles will not be affected.") + - "\n\n" + app_config->config_path() + "\n\n" + error); - } - else { - throw Slic3r::RuntimeError( - _u8L("Error parsing PrusaGCodeViewer config file, it is probably corrupted. " - "Try to manually delete the file to recover from the error.") + - "\n\n" + app_config->config_path() + "\n\n" + error); - } - } - if (!snapshot_id.empty()) - app_config->set("on_snapshot", snapshot_id); - m_app_conf_exists = true; - return older_data_dir_path; - } - return {}; -} - -void GUI_App::init_single_instance_checker(const std::string &name, const std::string &path) -{ - BOOST_LOG_TRIVIAL(debug) << "init wx instance checker " << name << " "<< path; - m_single_instance_checker = std::make_unique(boost::nowide::widen(name), boost::nowide::widen(path)); -} - -bool GUI_App::OnInit() -{ - try { - return on_init_inner(); - } catch (const std::exception&) { - generic_exception_handle(); - return false; - } -} - -bool GUI_App::on_init_inner() -{ - // Set initialization of image handlers before any UI actions - See GH issue #7469 - wxInitAllImageHandlers(); - -#if defined(_WIN32) && ! defined(_WIN64) - // Win32 32bit build. - if (wxPlatformInfo::Get().GetArchName().substr(0, 2) == "64") { - RichMessageDialog dlg(nullptr, - _L("You are running a 32 bit build of PrusaSlicer on 64-bit Windows." - "\n32 bit build of PrusaSlicer will likely not be able to utilize all the RAM available in the system." - "\nPlease download and install a 64 bit build of PrusaSlicer from https://www.prusa3d.cz/prusaslicer/." - "\nDo you wish to continue?"), - "PrusaSlicer", wxICON_QUESTION | wxYES_NO); - if (dlg.ShowModal() != wxID_YES) - return false; - } -#endif // _WIN64 - - // Forcing back menu icons under gtk2 and gtk3. Solution is based on: - // https://docs.gtk.org/gtk3/class.Settings.html - // see also https://docs.wxwidgets.org/3.0/classwx_menu_item.html#a2b5d6bcb820b992b1e4709facbf6d4fb - // TODO: Find workaround for GTK4 -#if defined(__WXGTK20__) || defined(__WXGTK3__) - g_object_set (gtk_settings_get_default (), "gtk-menu-images", TRUE, NULL); -#endif - - // Verify resources path - const wxString resources_dir = from_u8(Slic3r::resources_dir()); - wxCHECK_MSG(wxDirExists(resources_dir), false, - wxString::Format("Resources path does not exist or is not a directory: %s", resources_dir)); - -#ifdef __linux__ - if (! check_old_linux_datadir(GetAppName())) { - std::cerr << "Quitting, user chose to move their data to new location." << std::endl; - return false; - } -#endif - - // Enable this to get the default Win32 COMCTRL32 behavior of static boxes. -// wxSystemOptions::SetOption("msw.staticbox.optimized-paint", 0); - // Enable this to disable Windows Vista themes for all wxNotebooks. The themes seem to lead to terrible - // performance when working on high resolution multi-display setups. -// wxSystemOptions::SetOption("msw.notebook.themed-background", 0); - -// Slic3r::debugf "wxWidgets version %s, Wx version %s\n", wxVERSION_STRING, wxVERSION; - - // !!! Initialization of UI settings as a language, application color mode, fonts... have to be done before first UI action. - // Like here, before the show InfoDialog in check_older_app_config() - - // If load_language() fails, the application closes. - load_language(wxString(), true); -#ifdef _MSW_DARK_MODE - bool init_dark_color_mode = app_config->get("dark_color_mode") == "1"; - bool init_sys_menu_enabled = app_config->get("sys_menu_enabled") == "1"; - NppDarkMode::InitDarkMode(init_dark_color_mode, init_sys_menu_enabled); -#endif - // initialize label colors and fonts - init_label_colours(); - init_fonts(); - - std::string older_data_dir_path; - if (m_app_conf_exists) { - if (app_config->orig_version().valid() && app_config->orig_version() < *Semver::parse(SLIC3R_VERSION)) - // Only copying configuration if it was saved with a newer slicer than the one currently running. - older_data_dir_path = check_older_app_config(app_config->orig_version(), true); - } else { - // No AppConfig exists, fresh install. Always try to copy from an alternate location, don't make backup of the current configuration. - older_data_dir_path = check_older_app_config(Semver(), false); - } - -#ifdef _MSW_DARK_MODE - // app_config can be updated in check_older_app_config(), so check if dark_color_mode and sys_menu_enabled was changed - if (bool new_dark_color_mode = app_config->get("dark_color_mode") == "1"; - init_dark_color_mode != new_dark_color_mode) { - NppDarkMode::SetDarkMode(new_dark_color_mode); - init_label_colours(); - update_label_colours_from_appconfig(); - } - if (bool new_sys_menu_enabled = app_config->get("sys_menu_enabled") == "1"; - init_sys_menu_enabled != new_sys_menu_enabled) - NppDarkMode::SetSystemMenuForApp(new_sys_menu_enabled); -#endif - - if (is_editor()) { - std::string msg = Http::tls_global_init(); - std::string ssl_cert_store = app_config->get("tls_accepted_cert_store_location"); - bool ssl_accept = app_config->get("tls_cert_store_accepted") == "yes" && ssl_cert_store == Http::tls_system_cert_store(); - - if (!msg.empty() && !ssl_accept) { - RichMessageDialog - dlg(nullptr, - wxString::Format(_L("%s\nDo you want to continue?"), msg), - "PrusaSlicer", wxICON_QUESTION | wxYES_NO); - dlg.ShowCheckBox(_L("Remember my choice")); - if (dlg.ShowModal() != wxID_YES) return false; - - app_config->set("tls_cert_store_accepted", - dlg.IsCheckBoxChecked() ? "yes" : "no"); - app_config->set("tls_accepted_cert_store_location", - dlg.IsCheckBoxChecked() ? Http::tls_system_cert_store() : ""); - } - } - - SplashScreen* scrn = nullptr; - if (app_config->get("show_splash_screen") == "1") { - // make a bitmap with dark grey banner on the left side - wxBitmap bmp = SplashScreen::MakeBitmap(wxBitmap(from_u8(var(is_editor() ? "splashscreen.jpg" : "splashscreen-gcodepreview.jpg")), wxBITMAP_TYPE_JPEG)); - - // Detect position (display) to show the splash screen - // Now this position is equal to the mainframe position - wxPoint splashscreen_pos = wxDefaultPosition; - bool default_splashscreen_pos = true; - if (app_config->has("window_mainframe") && app_config->get("restore_win_position") == "1") { - auto metrics = WindowMetrics::deserialize(app_config->get("window_mainframe")); - default_splashscreen_pos = metrics == boost::none; - if (!default_splashscreen_pos) - splashscreen_pos = metrics->get_rect().GetPosition(); - } - - if (!default_splashscreen_pos) { - // workaround for crash related to the positioning of the window on secondary monitor - get_app_config()->set("restore_win_position", "crashed_at_splashscreen_pos"); - get_app_config()->save(); - } - - // create splash screen with updated bmp - scrn = new SplashScreen(bmp.IsOk() ? bmp : create_scaled_bitmap("PrusaSlicer", nullptr, 400), - wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 4000, splashscreen_pos); - - if (!default_splashscreen_pos) - // revert "restore_win_position" value if application wasn't crashed - get_app_config()->set("restore_win_position", "1"); -#ifndef __linux__ - wxYield(); -#endif - scrn->SetText(_L("Loading configuration")+ dots); - } - - preset_bundle = new PresetBundle(); - - // just checking for existence of Slic3r::data_dir is not enough : it may be an empty directory - // supplied as argument to --datadir; in that case we should still run the wizard - preset_bundle->setup_directories(); - - if (! older_data_dir_path.empty()) { - preset_bundle->import_newer_configs(older_data_dir_path); - app_config->save(); - } - - if (is_editor()) { -#ifdef __WXMSW__ - if (app_config->get("associate_3mf") == "1") - associate_3mf_files(); - if (app_config->get("associate_stl") == "1") - associate_stl_files(); -#endif // __WXMSW__ - - preset_updater = new PresetUpdater(); - Bind(EVT_SLIC3R_VERSION_ONLINE, &GUI_App::on_version_read, this); - Bind(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, [this](const wxCommandEvent& evt) { - if (this->plater_ != nullptr && app_config->get("notify_release") == "all") { - std::string evt_string = into_u8(evt.GetString()); - if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(evt_string)) { - auto notif_type = (evt_string.find("beta") != std::string::npos ? NotificationType::NewBetaAvailable : NotificationType::NewAlphaAvailable); - this->plater_->get_notification_manager()->push_notification( notif_type - , NotificationManager::NotificationLevel::ImportantNotificationLevel - , Slic3r::format(_u8L("New prerelease version %1% is available."), evt_string) - , _u8L("See Releases page.") - , [](wxEvtHandler* evnthndlr) {wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; } - ); - } - } - }); - Bind(EVT_SLIC3R_APP_DOWNLOAD_PROGRESS, [this](const wxCommandEvent& evt) { - //lm:This does not force a render. The progress bar only updateswhen the mouse is moved. - if (this->plater_ != nullptr) - this->plater_->get_notification_manager()->set_download_progress_percentage((float)std::stoi(into_u8(evt.GetString())) / 100.f ); - }); - - Bind(EVT_SLIC3R_APP_DOWNLOAD_FAILED, [this](const wxCommandEvent& evt) { - if (this->plater_ != nullptr) - this->plater_->get_notification_manager()->close_notification_of_type(NotificationType::AppDownload); - if(!evt.GetString().IsEmpty()) - show_error(nullptr, evt.GetString()); - }); - - Bind(EVT_SLIC3R_APP_OPEN_FAILED, [](const wxCommandEvent& evt) { - show_error(nullptr, evt.GetString()); - }); - } - else { -#ifdef __WXMSW__ - if (app_config->get("associate_gcode") == "1") - associate_gcode_files(); -#endif // __WXMSW__ - } - - // Suppress the '- default -' presets. - preset_bundle->set_default_suppressed(app_config->get("no_defaults") == "1"); - try { - // Enable all substitutions (in both user and system profiles), but log the substitutions in user profiles only. - // If there are substitutions in system profiles, then a "reconfigure" event shall be triggered, which will force - // installation of a compatible system preset, thus nullifying the system preset substitutions. - init_params->preset_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSystemSilent); - } catch (const std::exception &ex) { - show_error(nullptr, ex.what()); - } - -#ifdef WIN32 -#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) - register_win32_dpi_event(); -#endif // !wxVERSION_EQUAL_OR_GREATER_THAN - register_win32_device_notification_event(); -#endif // WIN32 - - // Let the libslic3r know the callback, which will translate messages on demand. - Slic3r::I18N::set_translate_callback(libslic3r_translate_callback); - - // application frame - if (scrn && is_editor()) - scrn->SetText(_L("Preparing settings tabs") + dots); - - mainframe = new MainFrame(); - // hide settings tabs after first Layout - if (is_editor()) - mainframe->select_tab(size_t(0)); - - sidebar().obj_list()->init_objects(); // propagate model objects to object list -// update_mode(); // !!! do that later - SetTopWindow(mainframe); - - plater_->init_notification_manager(); - - m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg())); - - if (is_gcode_viewer()) { - mainframe->update_layout(); - if (plater_ != nullptr) - // ensure the selected technology is ptFFF - plater_->set_printer_technology(ptFFF); - } - else - load_current_presets(); - - // Save the active profiles as a "saved into project". - update_saved_preset_from_current_preset(); - - if (plater_ != nullptr) { - // Save the names of active presets and project specific config into ProjectDirtyStateManager. - plater_->reset_project_dirty_initial_presets(); - // Update Project dirty state, update application title bar. - plater_->update_project_dirty_from_presets(); - } - - mainframe->Show(true); - - obj_list()->set_min_height(); - - update_mode(); // update view mode after fix of the object_list size - -#ifdef __APPLE__ - other_instance_message_handler()->bring_instance_forward(); -#endif //__APPLE__ - - Bind(wxEVT_IDLE, [this](wxIdleEvent& event) - { - if (! plater_) - return; - - this->obj_manipul()->update_if_dirty(); - - // An ugly solution to GH #5537 in which GUI_App::init_opengl (normally called from events wxEVT_PAINT - // and wxEVT_SET_FOCUS before GUI_App::post_init is called) wasn't called before GUI_App::post_init and OpenGL wasn't initialized. -#ifdef __linux__ - if (! m_post_initialized && m_opengl_initialized) { -#else - if (! m_post_initialized) { -#endif - m_post_initialized = true; -#ifdef WIN32 - this->mainframe->register_win32_callbacks(); -#endif - this->post_init(); - } - - if (m_post_initialized && app_config->dirty() && app_config->get("autosave") == "1") - app_config->save(); - }); - - m_initialized = true; - - if (const std::string& crash_reason = app_config->get("restore_win_position"); - boost::starts_with(crash_reason,"crashed")) - { - wxString preferences_item = _L("Restore window position on start"); - InfoDialog dialog(nullptr, - _L("PrusaSlicer started after a crash"), - format_wxstr(_L("PrusaSlicer crashed last time when attempting to set window position.\n" - "We are sorry for the inconvenience, it unfortunately happens with certain multiple-monitor setups.\n" - "More precise reason for the crash: \"%1%\".\n" - "For more information see our GitHub issue tracker: \"%2%\" and \"%3%\"\n\n" - "To avoid this problem, consider disabling \"%4%\" in \"Preferences\". " - "Otherwise, the application will most likely crash again next time."), - "" + from_u8(crash_reason) + "", - "#2939", - "#5573", - "" + preferences_item + ""), - true, wxYES_NO); - - dialog.SetButtonLabel(wxID_YES, format_wxstr(_L("Disable \"%1%\""), preferences_item)); - dialog.SetButtonLabel(wxID_NO, format_wxstr(_L("Leave \"%1%\" enabled") , preferences_item)); - - auto answer = dialog.ShowModal(); - if (answer == wxID_YES) - app_config->set("restore_win_position", "0"); - else if (answer == wxID_NO) - app_config->set("restore_win_position", "1"); - app_config->save(); - } - - return true; -} - -unsigned GUI_App::get_colour_approx_luma(const wxColour &colour) -{ - double r = colour.Red(); - double g = colour.Green(); - double b = colour.Blue(); - - return std::round(std::sqrt( - r * r * .241 + - g * g * .691 + - b * b * .068 - )); -} - -bool GUI_App::dark_mode() -{ -#if __APPLE__ - // The check for dark mode returns false positive on 10.12 and 10.13, - // which allowed setting dark menu bar and dock area, which is - // is detected as dark mode. We must run on at least 10.14 where the - // proper dark mode was first introduced. - return wxPlatformInfo::Get().CheckOSVersion(10, 14) && mac_dark_mode(); -#else - return wxGetApp().app_config->get("dark_color_mode") == "1" ? true : check_dark_mode(); - //const unsigned luma = get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); - //return luma < 128; -#endif -} - -const wxColour GUI_App::get_label_default_clr_system() -{ - return dark_mode() ? wxColour(115, 220, 103) : wxColour(26, 132, 57); -} - -const wxColour GUI_App::get_label_default_clr_modified() -{ - return dark_mode() ? wxColour(253, 111, 40) : wxColour(252, 77, 1); -} - -void GUI_App::init_label_colours() -{ - m_color_label_modified = get_label_default_clr_modified(); - m_color_label_sys = get_label_default_clr_system(); - - bool is_dark_mode = dark_mode(); -#ifdef _WIN32 - m_color_label_default = is_dark_mode ? wxColour(250, 250, 250): wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); - m_color_highlight_label_default = is_dark_mode ? wxColour(230, 230, 230): wxSystemSettings::GetColour(/*wxSYS_COLOUR_HIGHLIGHTTEXT*/wxSYS_COLOUR_WINDOWTEXT); - m_color_highlight_default = is_dark_mode ? wxColour(78, 78, 78) : wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT); - m_color_hovered_btn_label = is_dark_mode ? wxColour(253, 111, 40) : wxColour(252, 77, 1); - m_color_default_btn_label = is_dark_mode ? wxColour(255, 181, 100): wxColour(203, 61, 0); - m_color_selected_btn_bg = is_dark_mode ? wxColour(95, 73, 62) : wxColour(228, 220, 216); -#else - m_color_label_default = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); -#endif - m_color_window_default = is_dark_mode ? wxColour(43, 43, 43) : wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); -} - -void GUI_App::update_label_colours_from_appconfig() -{ - if (app_config->has("label_clr_sys")) { - auto str = app_config->get("label_clr_sys"); - if (str != "") - m_color_label_sys = wxColour(str); - } - - if (app_config->has("label_clr_modified")) { - auto str = app_config->get("label_clr_modified"); - if (str != "") - m_color_label_modified = wxColour(str); - } -} - -void GUI_App::update_label_colours() -{ - for (Tab* tab : tabs_list) - tab->update_label_colours(); -} - -#ifdef _WIN32 -static bool is_focused(HWND hWnd) -{ - HWND hFocusedWnd = ::GetFocus(); - return hFocusedWnd && hWnd == hFocusedWnd; -} - -static bool is_default(wxWindow* win) -{ - wxTopLevelWindow* tlw = find_toplevel_parent(win); - if (!tlw) - return false; - - return win == tlw->GetDefaultItem(); -} -#endif - -void GUI_App::UpdateDarkUI(wxWindow* window, bool highlited/* = false*/, bool just_font/* = false*/) -{ -#ifdef _WIN32 - bool is_focused_button = false; - bool is_default_button = false; - if (wxButton* btn = dynamic_cast(window)) { - if (!(btn->GetWindowStyle() & wxNO_BORDER)) { - btn->SetWindowStyle(btn->GetWindowStyle() | wxNO_BORDER); - highlited = true; - } - // button marking - { - auto mark_button = [this, btn, highlited](const bool mark) { - if (btn->GetLabel().IsEmpty()) - btn->SetBackgroundColour(mark ? m_color_selected_btn_bg : highlited ? m_color_highlight_default : m_color_window_default); - else - btn->SetForegroundColour(mark ? m_color_hovered_btn_label : (is_default(btn) ? m_color_default_btn_label : m_color_label_default)); - btn->Refresh(); - btn->Update(); - }; - - // hovering - btn->Bind(wxEVT_ENTER_WINDOW, [mark_button](wxMouseEvent& event) { mark_button(true); event.Skip(); }); - btn->Bind(wxEVT_LEAVE_WINDOW, [mark_button, btn](wxMouseEvent& event) { mark_button(is_focused(btn->GetHWND())); event.Skip(); }); - // focusing - btn->Bind(wxEVT_SET_FOCUS, [mark_button](wxFocusEvent& event) { mark_button(true); event.Skip(); }); - btn->Bind(wxEVT_KILL_FOCUS, [mark_button](wxFocusEvent& event) { mark_button(false); event.Skip(); }); - - is_focused_button = is_focused(btn->GetHWND()); - is_default_button = is_default(btn); - if (is_focused_button || is_default_button) - mark_button(is_focused_button); - } - } - else if (wxTextCtrl* text = dynamic_cast(window)) { - if (text->GetBorder() != wxBORDER_SIMPLE) - text->SetWindowStyle(text->GetWindowStyle() | wxBORDER_SIMPLE); - } - else if (wxCheckListBox* list = dynamic_cast(window)) { - list->SetWindowStyle(list->GetWindowStyle() | wxBORDER_SIMPLE); - list->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default); - for (size_t i = 0; i < list->GetCount(); i++) - if (wxOwnerDrawn* item = list->GetItem(i)) { - item->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default); - item->SetTextColour(m_color_label_default); - } - return; - } - else if (dynamic_cast(window)) - window->SetWindowStyle(window->GetWindowStyle() | wxBORDER_SIMPLE); - - if (!just_font) - window->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default); - if (!is_focused_button && !is_default_button) - window->SetForegroundColour(m_color_label_default); -#endif -} - -// recursive function for scaling fonts for all controls in Window -#ifdef _WIN32 -static void update_dark_children_ui(wxWindow* window, bool just_buttons_update = false) -{ - bool is_btn = dynamic_cast(window) != nullptr; - if (!(just_buttons_update && !is_btn)) - wxGetApp().UpdateDarkUI(window, is_btn); - - auto children = window->GetChildren(); - for (auto child : children) { - update_dark_children_ui(child); - } -} -#endif - -// Note: Don't use this function for Dialog contains ScalableButtons -void GUI_App::UpdateDlgDarkUI(wxDialog* dlg, bool just_buttons_update/* = false*/) -{ -#ifdef _WIN32 - update_dark_children_ui(dlg, just_buttons_update); -#endif -} -void GUI_App::UpdateDVCDarkUI(wxDataViewCtrl* dvc, bool highlited/* = false*/) -{ -#ifdef _WIN32 - UpdateDarkUI(dvc, highlited ? dark_mode() : false); -#ifdef _MSW_DARK_MODE - dvc->RefreshHeaderDarkMode(&m_normal_font); -#endif //_MSW_DARK_MODE - if (dvc->HasFlag(wxDV_ROW_LINES)) - dvc->SetAlternateRowColour(m_color_highlight_default); - if (dvc->GetBorder() != wxBORDER_SIMPLE) - dvc->SetWindowStyle(dvc->GetWindowStyle() | wxBORDER_SIMPLE); -#endif -} - -void GUI_App::UpdateAllStaticTextDarkUI(wxWindow* parent) -{ -#ifdef _WIN32 - wxGetApp().UpdateDarkUI(parent); - - auto children = parent->GetChildren(); - for (auto child : children) { - if (dynamic_cast(child)) - child->SetForegroundColour(m_color_label_default); - } -#endif -} - -void GUI_App::init_fonts() -{ - m_small_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); - m_bold_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold(); - m_normal_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); - -#ifdef __WXMAC__ - m_small_font.SetPointSize(11); - m_bold_font.SetPointSize(13); -#endif /*__WXMAC__*/ - - // wxSYS_OEM_FIXED_FONT and wxSYS_ANSI_FIXED_FONT use the same as - // DEFAULT in wxGtk. Use the TELETYPE family as a work-around - m_code_font = wxFont(wxFontInfo().Family(wxFONTFAMILY_TELETYPE)); - m_code_font.SetPointSize(m_normal_font.GetPointSize()); -} - -void GUI_App::update_fonts(const MainFrame *main_frame) -{ - /* Only normal and bold fonts are used for an application rescale, - * because of under MSW small and normal fonts are the same. - * To avoid same rescaling twice, just fill this values - * from rescaled MainFrame - */ - if (main_frame == nullptr) - main_frame = this->mainframe; - m_normal_font = main_frame->normal_font(); - m_small_font = m_normal_font; - m_bold_font = main_frame->normal_font().Bold(); - m_link_font = m_bold_font.Underlined(); - m_em_unit = main_frame->em_unit(); - m_code_font.SetPointSize(m_normal_font.GetPointSize()); -} - -void GUI_App::set_label_clr_modified(const wxColour& clr) -{ - if (m_color_label_modified == clr) - return; - m_color_label_modified = clr; - const std::string str = encode_color(ColorRGB(clr.Red(), clr.Green(), clr.Blue())); - app_config->set("label_clr_modified", str); - app_config->save(); -} - -void GUI_App::set_label_clr_sys(const wxColour& clr) -{ - if (m_color_label_sys == clr) - return; - m_color_label_sys = clr; - const std::string str = encode_color(ColorRGB(clr.Red(), clr.Green(), clr.Blue())); - app_config->set("label_clr_sys", str); - app_config->save(); -} - -bool GUI_App::tabs_as_menu() const -{ - return app_config->get("tabs_as_menu") == "1"; // || dark_mode(); -} - -wxSize GUI_App::get_min_size() const -{ - return wxSize(76*m_em_unit, 49 * m_em_unit); -} - -float GUI_App::toolbar_icon_scale(const bool is_limited/* = false*/) const -{ -#ifdef __APPLE__ - const float icon_sc = 1.0f; // for Retina display will be used its own scale -#else - const float icon_sc = m_em_unit*0.1f; -#endif // __APPLE__ - - const std::string& use_val = app_config->get("use_custom_toolbar_size"); - const std::string& val = app_config->get("custom_toolbar_size"); - const std::string& auto_val = app_config->get("auto_toolbar_size"); - - if (val.empty() || auto_val.empty() || use_val.empty()) - return icon_sc; - - int int_val = use_val == "0" ? 100 : atoi(val.c_str()); - // correct value in respect to auto_toolbar_size - int_val = std::min(atoi(auto_val.c_str()), int_val); - - if (is_limited && int_val < 50) - int_val = 50; - - return 0.01f * int_val * icon_sc; -} - -void GUI_App::set_auto_toolbar_icon_scale(float scale) const -{ -#ifdef __APPLE__ - const float icon_sc = 1.0f; // for Retina display will be used its own scale -#else - const float icon_sc = m_em_unit * 0.1f; -#endif // __APPLE__ - - long int_val = std::min(int(std::lround(scale / icon_sc * 100)), 100); - std::string val = std::to_string(int_val); - - app_config->set("auto_toolbar_size", val); -} - -// check user printer_presets for the containing information about "Print Host upload" -void GUI_App::check_printer_presets() -{ - std::vector preset_names = PhysicalPrinter::presets_with_print_host_information(preset_bundle->printers); - if (preset_names.empty()) - return; - - wxString msg_text = _L("You have the following presets with saved options for \"Print Host upload\"") + ":"; - for (const std::string& preset_name : preset_names) - msg_text += "\n \"" + from_u8(preset_name) + "\","; - msg_text.RemoveLast(); - msg_text += "\n\n" + _L("But since this version of PrusaSlicer we don't show this information in Printer Settings anymore.\n" - "Settings will be available in physical printers settings.") + "\n\n" + - _L("By default new Printer devices will be named as \"Printer N\" during its creation.\n" - "Note: This name can be changed later from the physical printers settings"); - - //wxMessageDialog(nullptr, msg_text, _L("Information"), wxOK | wxICON_INFORMATION).ShowModal(); - MessageDialog(nullptr, msg_text, _L("Information"), wxOK | wxICON_INFORMATION).ShowModal(); - - preset_bundle->physical_printers.load_printers_from_presets(preset_bundle->printers); -} - -void GUI_App::recreate_GUI(const wxString& msg_name) -{ - m_is_recreating_gui = true; - - mainframe->shutdown(); - - wxProgressDialog dlg(msg_name, msg_name, 100, nullptr, wxPD_AUTO_HIDE); - dlg.Pulse(); - dlg.Update(10, _L("Recreating") + dots); - - MainFrame *old_main_frame = mainframe; - mainframe = new MainFrame(); - if (is_editor()) - // hide settings tabs after first Layout - mainframe->select_tab(size_t(0)); - // Propagate model objects to object list. - sidebar().obj_list()->init_objects(); - SetTopWindow(mainframe); - - dlg.Update(30, _L("Recreating") + dots); - old_main_frame->Destroy(); - - dlg.Update(80, _L("Loading of current presets") + dots); - m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg())); - load_current_presets(); - mainframe->Show(true); - - dlg.Update(90, _L("Loading of a mode view") + dots); - - obj_list()->set_min_height(); - update_mode(); - - // #ys_FIXME_delete_after_testing Do we still need this ? -// CallAfter([]() { -// // Run the config wizard, don't offer the "reset user profile" checkbox. -// config_wizard_startup(true); -// }); - - m_is_recreating_gui = false; -} - -void GUI_App::system_info() -{ - SysInfoDialog dlg; - dlg.ShowModal(); -} - -void GUI_App::keyboard_shortcuts() -{ - KBShortcutsDialog dlg; - dlg.ShowModal(); -} - -// static method accepting a wxWindow object as first parameter -bool GUI_App::catch_error(std::function cb, - // wxMessageDialog* message_dialog, - const std::string& err /*= ""*/) -{ - if (!err.empty()) { - if (cb) - cb(); - // if (message_dialog) - // message_dialog->(err, "Error", wxOK | wxICON_ERROR); - show_error(/*this*/nullptr, err); - return true; - } - return false; -} - -// static method accepting a wxWindow object as first parameter -void fatal_error(wxWindow* parent) -{ - show_error(parent, ""); - // exit 1; // #ys_FIXME -} - -#ifdef _WIN32 - -#ifdef _MSW_DARK_MODE -static void update_scrolls(wxWindow* window) -{ - wxWindowList::compatibility_iterator node = window->GetChildren().GetFirst(); - while (node) - { - wxWindow* win = node->GetData(); - if (dynamic_cast(win) || - dynamic_cast(win) || - dynamic_cast(win)) - NppDarkMode::SetDarkExplorerTheme(win->GetHWND()); - - update_scrolls(win); - node = node->GetNext(); - } -} -#endif //_MSW_DARK_MODE - - -#ifdef _MSW_DARK_MODE -void GUI_App::force_menu_update() -{ - NppDarkMode::SetSystemMenuForApp(app_config->get("sys_menu_enabled") == "1"); -} -#endif //_MSW_DARK_MODE - -void GUI_App::force_colors_update() -{ -#ifdef _MSW_DARK_MODE - NppDarkMode::SetDarkMode(app_config->get("dark_color_mode") == "1"); - if (WXHWND wxHWND = wxToolTip::GetToolTipCtrl()) - NppDarkMode::SetDarkExplorerTheme((HWND)wxHWND); - NppDarkMode::SetDarkTitleBar(mainframe->GetHWND()); - NppDarkMode::SetDarkTitleBar(mainframe->m_settings_dialog.GetHWND()); -#endif //_MSW_DARK_MODE - m_force_colors_update = true; -} -#endif //_WIN32 - -// Called after the Preferences dialog is closed and the program settings are saved. -// Update the UI based on the current preferences. -void GUI_App::update_ui_from_settings() -{ - update_label_colours(); -#ifdef _WIN32 - // Upadte UI colors before Update UI from settings - if (m_force_colors_update) { - m_force_colors_update = false; - mainframe->force_color_changed(); - mainframe->diff_dialog.force_color_changed(); - mainframe->preferences_dialog->force_color_changed(); - mainframe->printhost_queue_dlg()->force_color_changed(); -#ifdef _MSW_DARK_MODE - update_scrolls(mainframe); - if (mainframe->is_dlg_layout()) { - // update for tabs bar - UpdateDarkUI(&mainframe->m_settings_dialog); - mainframe->m_settings_dialog.Fit(); - mainframe->m_settings_dialog.Refresh(); - // update scrollbars - update_scrolls(&mainframe->m_settings_dialog); - } -#endif //_MSW_DARK_MODE - } -#endif - mainframe->update_ui_from_settings(); -} - -void GUI_App::persist_window_geometry(wxTopLevelWindow *window, bool default_maximized) -{ - const std::string name = into_u8(window->GetName()); - - window->Bind(wxEVT_CLOSE_WINDOW, [=](wxCloseEvent &event) { - window_pos_save(window, name); - event.Skip(); - }); - - window_pos_restore(window, name, default_maximized); - - on_window_geometry(window, [=]() { - window_pos_sanitize(window); - }); -} - -void GUI_App::load_project(wxWindow *parent, wxString& input_file) const -{ - input_file.Clear(); - wxFileDialog dialog(parent ? parent : GetTopWindow(), - _L("Choose one file (3MF/AMF):"), - app_config->get_last_dir(), "", - file_wildcards(FT_PROJECT), wxFD_OPEN | wxFD_FILE_MUST_EXIST); - - if (dialog.ShowModal() == wxID_OK) - input_file = dialog.GetPath(); -} - -void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) const -{ - input_files.Clear(); - wxFileDialog dialog(parent ? parent : GetTopWindow(), - _L("Choose one or more files (STL/OBJ/AMF/3MF/PRUSA):"), - from_u8(app_config->get_last_dir()), "", - file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); - - if (dialog.ShowModal() == wxID_OK) - dialog.GetPaths(input_files); -} - -void GUI_App::load_gcode(wxWindow* parent, wxString& input_file) const -{ - input_file.Clear(); - wxFileDialog dialog(parent ? parent : GetTopWindow(), - _L("Choose one file (GCODE/.GCO/.G/.ngc/NGC):"), - app_config->get_last_dir(), "", - file_wildcards(FT_GCODE), wxFD_OPEN | wxFD_FILE_MUST_EXIST); - - if (dialog.ShowModal() == wxID_OK) - input_file = dialog.GetPath(); -} - -bool GUI_App::switch_language() -{ - if (select_language()) { - recreate_GUI(_L("Changing of an application language") + dots); - return true; - } else { - return false; - } -} - -#ifdef __linux__ -static const wxLanguageInfo* linux_get_existing_locale_language(const wxLanguageInfo* language, - const wxLanguageInfo* system_language) -{ - constexpr size_t max_len = 50; - char path[max_len] = ""; - std::vector locales; - const std::string lang_prefix = into_u8(language->CanonicalName.BeforeFirst('_')); - - // Call locale -a so we can parse the output to get the list of available locales - // We expect lines such as "en_US.utf8". Pick ones starting with the language code - // we are switching to. Lines with different formatting will be removed later. - FILE* fp = popen("locale -a", "r"); - if (fp != NULL) { - while (fgets(path, max_len, fp) != NULL) { - std::string line(path); - line = line.substr(0, line.find('\n')); - if (boost::starts_with(line, lang_prefix)) - locales.push_back(line); - } - pclose(fp); - } - - // locales now contain all candidates for this language. - // Sort them so ones containing anything about UTF-8 are at the end. - std::sort(locales.begin(), locales.end(), [](const std::string& a, const std::string& b) - { - auto has_utf8 = [](const std::string & s) { - auto S = boost::to_upper_copy(s); - return S.find("UTF8") != std::string::npos || S.find("UTF-8") != std::string::npos; - }; - return ! has_utf8(a) && has_utf8(b); - }); - - // Remove the suffix behind a dot, if there is one. - for (std::string& s : locales) - s = s.substr(0, s.find(".")); - - // We just hope that dear Linux "locale -a" returns country codes - // in ISO 3166-1 alpha-2 code (two letter) format. - // https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes - // To be sure, remove anything not looking as expected - // (any number of lowercase letters, underscore, two uppercase letters). - locales.erase(std::remove_if(locales.begin(), - locales.end(), - [](const std::string& s) { - return ! std::regex_match(s, - std::regex("^[a-z]+_[A-Z]{2}$")); - }), - locales.end()); - - // Is there a candidate matching a country code of a system language? Move it to the end, - // while maintaining the order of matches, so that the best match ends up at the very end. - std::string system_country = "_" + into_u8(system_language->CanonicalName.AfterFirst('_')).substr(0, 2); - int cnt = locales.size(); - for (int i=0; iempty()) { - const std::string &locale = *it; - const wxLanguageInfo* lang = wxLocale::FindLanguageInfo(from_u8(locale)); - if (wxLocale::IsAvailable(lang->Language)) - return lang; - } - return language; -} -#endif - -int GUI_App::GetSingleChoiceIndex(const wxString& message, - const wxString& caption, - const wxArrayString& choices, - int initialSelection) -{ -#ifdef _WIN32 - wxSingleChoiceDialog dialog(nullptr, message, caption, choices); - wxGetApp().UpdateDlgDarkUI(&dialog); - - dialog.SetSelection(initialSelection); - return dialog.ShowModal() == wxID_OK ? dialog.GetSelection() : -1; -#else - return wxGetSingleChoiceIndex(message, caption, choices, initialSelection); -#endif -} - -// select language from the list of installed languages -bool GUI_App::select_language() -{ - wxArrayString translations = wxTranslations::Get()->GetAvailableTranslations(SLIC3R_APP_KEY); - std::vector language_infos; - language_infos.emplace_back(wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH)); - for (size_t i = 0; i < translations.GetCount(); ++ i) { - const wxLanguageInfo *langinfo = wxLocale::FindLanguageInfo(translations[i]); - if (langinfo != nullptr) - language_infos.emplace_back(langinfo); - } - sort_remove_duplicates(language_infos); - std::sort(language_infos.begin(), language_infos.end(), [](const wxLanguageInfo* l, const wxLanguageInfo* r) { return l->Description < r->Description; }); - - wxArrayString names; - names.Alloc(language_infos.size()); - - // Some valid language should be selected since the application start up. - const wxLanguage current_language = wxLanguage(m_wxLocale->GetLanguage()); - int init_selection = -1; - int init_selection_alt = -1; - int init_selection_default = -1; - for (size_t i = 0; i < language_infos.size(); ++ i) { - if (wxLanguage(language_infos[i]->Language) == current_language) - // The dictionary matches the active language and country. - init_selection = i; - else if ((language_infos[i]->CanonicalName.BeforeFirst('_') == m_wxLocale->GetCanonicalName().BeforeFirst('_')) || - // if the active language is Slovak, mark the Czech language as active. - (language_infos[i]->CanonicalName.BeforeFirst('_') == "cs" && m_wxLocale->GetCanonicalName().BeforeFirst('_') == "sk")) - // The dictionary matches the active language, it does not necessarily match the country. - init_selection_alt = i; - if (language_infos[i]->CanonicalName.BeforeFirst('_') == "en") - // This will be the default selection if the active language does not match any dictionary. - init_selection_default = i; - names.Add(language_infos[i]->Description); - } - if (init_selection == -1) - // This is the dictionary matching the active language. - init_selection = init_selection_alt; - if (init_selection != -1) - // This is the language to highlight in the choice dialog initially. - init_selection_default = init_selection; - - const long index = GetSingleChoiceIndex(_L("Select the language"), _L("Language"), names, init_selection_default); - // Try to load a new language. - if (index != -1 && (init_selection == -1 || init_selection != index)) { - const wxLanguageInfo *new_language_info = language_infos[index]; - if (this->load_language(new_language_info->CanonicalName, false)) { - // Save language at application config. - // Which language to save as the selected dictionary language? - // 1) Hopefully the language set to wxTranslations by this->load_language(), but that API is weird and we don't want to rely on its - // stability in the future: - // wxTranslations::Get()->GetBestTranslation(SLIC3R_APP_KEY, wxLANGUAGE_ENGLISH); - // 2) Current locale language may not match the dictionary name, see GH issue #3901 - // m_wxLocale->GetCanonicalName() - // 3) new_language_info->CanonicalName is a safe bet. It points to a valid dictionary name. - app_config->set("translation_language", new_language_info->CanonicalName.ToUTF8().data()); - app_config->save(); - return true; - } - } - - return false; -} - -// Load gettext translation files and activate them at the start of the application, -// based on the "translation_language" key stored in the application config. -bool GUI_App::load_language(wxString language, bool initial) -{ - if (initial) { - // There is a static list of lookup path prefixes in wxWidgets. Add ours. - wxFileTranslationsLoader::AddCatalogLookupPathPrefix(from_u8(localization_dir())); - // Get the active language from PrusaSlicer.ini, or empty string if the key does not exist. - language = app_config->get("translation_language"); - if (! language.empty()) - BOOST_LOG_TRIVIAL(trace) << boost::format("translation_language provided by PrusaSlicer.ini: %1%") % language; - - // Get the system language. - { - const wxLanguage lang_system = wxLanguage(wxLocale::GetSystemLanguage()); - if (lang_system != wxLANGUAGE_UNKNOWN) { - m_language_info_system = wxLocale::GetLanguageInfo(lang_system); - BOOST_LOG_TRIVIAL(trace) << boost::format("System language detected (user locales and such): %1%") % m_language_info_system->CanonicalName.ToUTF8().data(); - } - } - { - // Allocating a temporary locale will switch the default wxTranslations to its internal wxTranslations instance. - wxLocale temp_locale; - // Set the current translation's language to default, otherwise GetBestTranslation() may not work (see the wxWidgets source code). - wxTranslations::Get()->SetLanguage(wxLANGUAGE_DEFAULT); - // Let the wxFileTranslationsLoader enumerate all translation dictionaries for PrusaSlicer - // and try to match them with the system specific "preferred languages". - // There seems to be a support for that on Windows and OSX, while on Linuxes the code just returns wxLocale::GetSystemLanguage(). - // The last parameter gets added to the list of detected dictionaries. This is a workaround - // for not having the English dictionary. Let's hope wxWidgets of various versions process this call the same way. - wxString best_language = wxTranslations::Get()->GetBestTranslation(SLIC3R_APP_KEY, wxLANGUAGE_ENGLISH); - if (! best_language.IsEmpty()) { - m_language_info_best = wxLocale::FindLanguageInfo(best_language); - BOOST_LOG_TRIVIAL(trace) << boost::format("Best translation language detected (may be different from user locales): %1%") % m_language_info_best->CanonicalName.ToUTF8().data(); - } - #ifdef __linux__ - wxString lc_all; - if (wxGetEnv("LC_ALL", &lc_all) && ! lc_all.IsEmpty()) { - // Best language returned by wxWidgets on Linux apparently does not respect LC_ALL. - // Disregard the "best" suggestion in case LC_ALL is provided. - m_language_info_best = nullptr; - } - #endif - } - } - - const wxLanguageInfo *language_info = language.empty() ? nullptr : wxLocale::FindLanguageInfo(language); - if (! language.empty() && (language_info == nullptr || language_info->CanonicalName.empty())) { - // Fix for wxWidgets issue, where the FindLanguageInfo() returns locales with undefined ANSII code (wxLANGUAGE_KONKANI or wxLANGUAGE_MANIPURI). - language_info = nullptr; - BOOST_LOG_TRIVIAL(error) << boost::format("Language code \"%1%\" is not supported") % language.ToUTF8().data(); - } - - if (language_info != nullptr && language_info->LayoutDirection == wxLayout_RightToLeft) { - BOOST_LOG_TRIVIAL(trace) << boost::format("The following language code requires right to left layout, which is not supported by PrusaSlicer: %1%") % language_info->CanonicalName.ToUTF8().data(); - language_info = nullptr; - } - - if (language_info == nullptr) { - // PrusaSlicer does not support the Right to Left languages yet. - if (m_language_info_system != nullptr && m_language_info_system->LayoutDirection != wxLayout_RightToLeft) - language_info = m_language_info_system; - if (m_language_info_best != nullptr && m_language_info_best->LayoutDirection != wxLayout_RightToLeft) - language_info = m_language_info_best; - if (language_info == nullptr) - language_info = wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH_US); - } - - BOOST_LOG_TRIVIAL(trace) << boost::format("Switching wxLocales to %1%") % language_info->CanonicalName.ToUTF8().data(); - - // Alternate language code. - wxLanguage language_dict = wxLanguage(language_info->Language); - if (language_info->CanonicalName.BeforeFirst('_') == "sk") { - // Slovaks understand Czech well. Give them the Czech translation. - language_dict = wxLANGUAGE_CZECH; - BOOST_LOG_TRIVIAL(trace) << "Using Czech dictionaries for Slovak language"; - } - - // Select language for locales. This language may be different from the language of the dictionary. - if (language_info == m_language_info_best || language_info == m_language_info_system) { - // The current language matches user's default profile exactly. That's great. - } else if (m_language_info_best != nullptr && language_info->CanonicalName.BeforeFirst('_') == m_language_info_best->CanonicalName.BeforeFirst('_')) { - // Use whatever the operating system recommends, if it the language code of the dictionary matches the recommended language. - // This allows a Swiss guy to use a German dictionary without forcing him to German locales. - language_info = m_language_info_best; - } else if (m_language_info_system != nullptr && language_info->CanonicalName.BeforeFirst('_') == m_language_info_system->CanonicalName.BeforeFirst('_')) - language_info = m_language_info_system; - -#ifdef __linux__ - // If we can't find this locale , try to use different one for the language - // instead of just reporting that it is impossible to switch. - if (! wxLocale::IsAvailable(language_info->Language)) { - std::string original_lang = into_u8(language_info->CanonicalName); - language_info = linux_get_existing_locale_language(language_info, m_language_info_system); - BOOST_LOG_TRIVIAL(trace) << boost::format("Can't switch language to %1% (missing locales). Using %2% instead.") - % original_lang % language_info->CanonicalName.ToUTF8().data(); - } -#endif - - if (! wxLocale::IsAvailable(language_info->Language)) { - // Loading the language dictionary failed. - wxString message = "Switching PrusaSlicer to language " + language_info->CanonicalName + " failed."; -#if !defined(_WIN32) && !defined(__APPLE__) - // likely some linux system - message += "\nYou may need to reconfigure the missing locales, likely by running the \"locale-gen\" and \"dpkg-reconfigure locales\" commands.\n"; -#endif - if (initial) - message + "\n\nApplication will close."; - wxMessageBox(message, "PrusaSlicer - Switching language failed", wxOK | wxICON_ERROR); - if (initial) - std::exit(EXIT_FAILURE); - else - return false; - } - - // Release the old locales, create new locales. - //FIXME wxWidgets cause havoc if the current locale is deleted. We just forget it causing memory leaks for now. - m_wxLocale.release(); - m_wxLocale = Slic3r::make_unique(); - m_wxLocale->Init(language_info->Language); - // Override language at the active wxTranslations class (which is stored in the active m_wxLocale) - // to load possibly different dictionary, for example, load Czech dictionary for Slovak language. - wxTranslations::Get()->SetLanguage(language_dict); - m_wxLocale->AddCatalog(SLIC3R_APP_KEY); - m_imgui->set_language(into_u8(language_info->CanonicalName)); - //FIXME This is a temporary workaround, the correct solution is to switch to "C" locale during file import / export only. - //wxSetlocale(LC_NUMERIC, "C"); - Preset::update_suffix_modified((" (" + _L("modified") + ")").ToUTF8().data()); - return true; -} - -Tab* GUI_App::get_tab(Preset::Type type) -{ - for (Tab* tab: tabs_list) - if (tab->type() == type) - return tab->completed() ? tab : nullptr; // To avoid actions with no-completed Tab - return nullptr; -} - -ConfigOptionMode GUI_App::get_mode() -{ - if (!app_config->has("view_mode")) - return comSimple; - - const auto mode = app_config->get("view_mode"); - return mode == "expert" ? comExpert : - mode == "simple" ? comSimple : comAdvanced; -} - -void GUI_App::save_mode(const /*ConfigOptionMode*/int mode) -{ - const std::string mode_str = mode == comExpert ? "expert" : - mode == comSimple ? "simple" : "advanced"; - app_config->set("view_mode", mode_str); - app_config->save(); - update_mode(); -} - -// Update view mode according to selected menu -void GUI_App::update_mode() -{ - sidebar().update_mode(); - -#ifdef _MSW_DARK_MODE - if (!wxGetApp().tabs_as_menu()) - dynamic_cast(mainframe->m_tabpanel)->UpdateMode(); -#endif - - for (auto tab : tabs_list) - tab->update_mode(); - - plater()->update_menus(); - plater()->canvas3D()->update_gizmos_on_off_state(); -} - -void GUI_App::add_config_menu(wxMenuBar *menu) -{ - auto local_menu = new wxMenu(); - wxWindowID config_id_base = wxWindow::NewControlId(int(ConfigMenuCnt)); - - const auto config_wizard_name = _(ConfigWizard::name(true)); - const auto config_wizard_tooltip = from_u8((boost::format(_utf8(L("Run %s"))) % config_wizard_name).str()); - // Cmd+, is standard on OS X - what about other operating systems? - if (is_editor()) { - local_menu->Append(config_id_base + ConfigMenuWizard, config_wizard_name + dots, config_wizard_tooltip); - local_menu->Append(config_id_base + ConfigMenuSnapshots, _L("&Configuration Snapshots") + dots, _L("Inspect / activate configuration snapshots")); - local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _L("Take Configuration &Snapshot"), _L("Capture a configuration snapshot")); - local_menu->Append(config_id_base + ConfigMenuUpdateConf, _L("Check for Configuration Updates"), _L("Check for configuration updates")); - local_menu->Append(config_id_base + ConfigMenuUpdateApp, _L("Check for Application Updates"), _L("Check for new version of application")); -#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) - //if (DesktopIntegrationDialog::integration_possible()) - local_menu->Append(config_id_base + ConfigMenuDesktopIntegration, _L("Desktop Integration"), _L("Desktop Integration")); -#endif //(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) - local_menu->AppendSeparator(); - } - local_menu->Append(config_id_base + ConfigMenuPreferences, _L("&Preferences") + dots + -#ifdef __APPLE__ - "\tCtrl+,", -#else - "\tCtrl+P", -#endif - _L("Application preferences")); - wxMenu* mode_menu = nullptr; - if (is_editor()) { - local_menu->AppendSeparator(); - mode_menu = new wxMenu(); - mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeSimple, _L("Simple"), _L("Simple View Mode")); -// mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _L("Advanced"), _L("Advanced View Mode")); - mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _CTX(L_CONTEXT("Advanced", "Mode"), "Mode"), _L("Advanced View Mode")); - mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeExpert, _L("Expert"), _L("Expert View Mode")); - Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comSimple) evt.Check(true); }, config_id_base + ConfigMenuModeSimple); - Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comAdvanced) evt.Check(true); }, config_id_base + ConfigMenuModeAdvanced); - Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comExpert) evt.Check(true); }, config_id_base + ConfigMenuModeExpert); - - local_menu->AppendSubMenu(mode_menu, _L("Mode"), wxString::Format(_L("%s View Mode"), SLIC3R_APP_NAME)); - } - local_menu->AppendSeparator(); - local_menu->Append(config_id_base + ConfigMenuLanguage, _L("&Language")); - if (is_editor()) { - local_menu->AppendSeparator(); - local_menu->Append(config_id_base + ConfigMenuFlashFirmware, _L("Flash Printer &Firmware"), _L("Upload a firmware image into an Arduino based printer")); - // TODO: for when we're able to flash dictionaries - // local_menu->Append(config_id_base + FirmwareMenuDict, _L("Flash Language File"), _L("Upload a language dictionary file into a Prusa printer")); - } - - local_menu->Bind(wxEVT_MENU, [this, config_id_base](wxEvent &event) { - switch (event.GetId() - config_id_base) { - case ConfigMenuWizard: - run_wizard(ConfigWizard::RR_USER); - break; - case ConfigMenuUpdateConf: - check_updates(true); - break; - case ConfigMenuUpdateApp: - app_version_check(true); - break; -#ifdef __linux__ - case ConfigMenuDesktopIntegration: - show_desktop_integration_dialog(); - break; -#endif - case ConfigMenuTakeSnapshot: - // Take a configuration snapshot. - if (wxString action_name = _L("Taking a configuration snapshot"); - check_and_save_current_preset_changes(action_name, _L("Some presets are modified and the unsaved changes will not be captured by the configuration snapshot."), false, true)) { - wxTextEntryDialog dlg(nullptr, action_name, _L("Snapshot name")); - UpdateDlgDarkUI(&dlg); - - // set current normal font for dialog children, - // because of just dlg.SetFont(normal_font()) has no result; - for (auto child : dlg.GetChildren()) - child->SetFont(normal_font()); - - if (dlg.ShowModal() == wxID_OK) - if (const Config::Snapshot *snapshot = Config::take_config_snapshot_report_error( - *app_config, Config::Snapshot::SNAPSHOT_USER, dlg.GetValue().ToUTF8().data()); - snapshot != nullptr) - app_config->set("on_snapshot", snapshot->id); - } - break; - case ConfigMenuSnapshots: - if (check_and_save_current_preset_changes(_L("Loading a configuration snapshot"), "", false)) { - std::string on_snapshot; - if (Config::SnapshotDB::singleton().is_on_snapshot(*app_config)) - on_snapshot = app_config->get("on_snapshot"); - ConfigSnapshotDialog dlg(Slic3r::GUI::Config::SnapshotDB::singleton(), on_snapshot); - dlg.ShowModal(); - if (!dlg.snapshot_to_activate().empty()) { - if (! Config::SnapshotDB::singleton().is_on_snapshot(*app_config) && - ! Config::take_config_snapshot_cancel_on_error(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK, "", - GUI::format(_L("Continue to activate a configuration snapshot %1%?"), dlg.snapshot_to_activate()))) - break; - try { - app_config->set("on_snapshot", Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *app_config).id); - // Enable substitutions, log both user and system substitutions. There should not be any substitutions performed when loading system - // presets because compatibility of profiles shall be verified using the min_slic3r_version keys in config index, but users - // are known to be creative and mess with the config files in various ways. - if (PresetsConfigSubstitutions all_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::Enable); - ! all_substitutions.empty()) - show_substitutions_info(all_substitutions); - - // Load the currently selected preset into the GUI, update the preset selection box. - load_current_presets(); - } catch (std::exception &ex) { - GUI::show_error(nullptr, _L("Failed to activate configuration snapshot.") + "\n" + into_u8(ex.what())); - } - } - } - break; - case ConfigMenuPreferences: - { - open_preferences(); - break; - } - case ConfigMenuLanguage: - { - /* Before change application language, let's check unsaved changes on 3D-Scene - * and draw user's attention to the application restarting after a language change - */ - { - // the dialog needs to be destroyed before the call to switch_language() - // or sometimes the application crashes into wxDialogBase() destructor - // so we put it into an inner scope - wxString title = is_editor() ? wxString(SLIC3R_APP_NAME) : wxString(GCODEVIEWER_APP_NAME); - title += " - " + _L("Language selection"); - //wxMessageDialog dialog(nullptr, - MessageDialog dialog(nullptr, - _L("Switching the language will trigger application restart.\n" - "You will lose content of the plater.") + "\n\n" + - _L("Do you want to proceed?"), - title, - wxICON_QUESTION | wxOK | wxCANCEL); - if (dialog.ShowModal() == wxID_CANCEL) - return; - } - - switch_language(); - break; - } - case ConfigMenuFlashFirmware: - FirmwareDialog::run(mainframe); - break; - default: - break; - } - }); - - using std::placeholders::_1; - - if (mode_menu != nullptr) { - auto modfn = [this](int mode, wxCommandEvent&) { if (get_mode() != mode) save_mode(mode); }; - mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comSimple, _1), config_id_base + ConfigMenuModeSimple); - mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comAdvanced, _1), config_id_base + ConfigMenuModeAdvanced); - mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comExpert, _1), config_id_base + ConfigMenuModeExpert); - } - - menu->Append(local_menu, _L("&Configuration")); -} - -void GUI_App::open_preferences(const std::string& highlight_option /*= std::string()*/, const std::string& tab_name/*= std::string()*/) -{ - mainframe->preferences_dialog->show(highlight_option, tab_name); - - if (mainframe->preferences_dialog->recreate_GUI()) - recreate_GUI(_L("Restart application") + dots); - -#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER - if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed()) -#else - if (mainframe->preferences_dialog->seq_top_layer_only_changed()) -#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER - this->plater_->refresh_print(); - -#ifdef _WIN32 - if (is_editor()) { - if (app_config->get("associate_3mf") == "1") - associate_3mf_files(); - if (app_config->get("associate_stl") == "1") - associate_stl_files(); - } - else { - if (app_config->get("associate_gcode") == "1") - associate_gcode_files(); - } -#endif // _WIN32 - - if (mainframe->preferences_dialog->settings_layout_changed()) { - // hide full main_sizer for mainFrame - mainframe->GetSizer()->Show(false); - mainframe->update_layout(); - mainframe->select_tab(size_t(0)); - } -} - -bool GUI_App::has_unsaved_preset_changes() const -{ - PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); - for (const Tab* const tab : tabs_list) { - if (tab->supports_printer_technology(printer_technology) && tab->saved_preset_is_dirty()) - return true; - } - return false; -} - -bool GUI_App::has_current_preset_changes() const -{ - PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); - for (const Tab* const tab : tabs_list) { - if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) - return true; - } - return false; -} - -void GUI_App::update_saved_preset_from_current_preset() -{ - PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); - for (Tab* tab : tabs_list) { - if (tab->supports_printer_technology(printer_technology)) - tab->update_saved_preset_from_current_preset(); - } -} - -std::vector GUI_App::get_active_preset_collections() const -{ - std::vector ret; - PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); - for (const Tab* tab : tabs_list) - if (tab->supports_printer_technology(printer_technology)) - ret.push_back(tab->get_presets()); - return ret; -} - -// To notify the user whether he is aware that some preset changes will be lost, -// UnsavedChangesDialog: "Discard / Save / Cancel" -// This is called when: -// - Close Application & Current project isn't saved -// - Load Project & Current project isn't saved -// - Undo / Redo with change of print technologie -// - Loading snapshot -// - Loading config_file/bundle -// UnsavedChangesDialog: "Don't save / Save / Cancel" -// This is called when: -// - Exporting config_bundle -// - Taking snapshot -bool GUI_App::check_and_save_current_preset_changes(const wxString& caption, const wxString& header, bool remember_choice/* = true*/, bool dont_save_insted_of_discard/* = false*/) -{ - if (has_current_preset_changes()) { - const std::string app_config_key = remember_choice ? "default_action_on_close_application" : ""; - int act_buttons = UnsavedChangesDialog::ActionButtons::SAVE; - if (dont_save_insted_of_discard) - act_buttons |= UnsavedChangesDialog::ActionButtons::DONT_SAVE; - UnsavedChangesDialog dlg(caption, header, app_config_key, act_buttons); - std::string act = app_config_key.empty() ? "none" : wxGetApp().app_config->get(app_config_key); - if (act == "none" && dlg.ShowModal() == wxID_CANCEL) - return false; - - if (dlg.save_preset()) // save selected changes - { - for (const std::pair& nt : dlg.get_names_and_types()) - preset_bundle->save_changes_for_preset(nt.first, nt.second, dlg.get_unselected_options(nt.second)); - - load_current_presets(false); - - // if we saved changes to the new presets, we should to - // synchronize config.ini with the current selections. - preset_bundle->export_selections(*app_config); - - MessageDialog(nullptr, _L_PLURAL("The preset modifications are successfully saved", - "The presets modifications are successfully saved", dlg.get_names_and_types().size())).ShowModal(); - } - } - - return true; -} - -void GUI_App::apply_keeped_preset_modifications() -{ - PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); - for (Tab* tab : tabs_list) { - if (tab->supports_printer_technology(printer_technology)) - tab->apply_config_from_cache(); - } - load_current_presets(false); -} - -// This is called when creating new project or load another project -// OR close ConfigWizard -// to ask the user what should we do with unsaved changes for presets. -// New Project => Current project is saved => UnsavedChangesDialog: "Keep / Discard / Cancel" -// => Current project isn't saved => UnsavedChangesDialog: "Keep / Discard / Save / Cancel" -// Close ConfigWizard => Current project is saved => UnsavedChangesDialog: "Keep / Discard / Save / Cancel" -// Note: no_nullptr postponed_apply_of_keeped_changes indicates that thie function is called after ConfigWizard is closed -bool GUI_App::check_and_keep_current_preset_changes(const wxString& caption, const wxString& header, int action_buttons, bool* postponed_apply_of_keeped_changes/* = nullptr*/) -{ - if (has_current_preset_changes()) { - bool is_called_from_configwizard = postponed_apply_of_keeped_changes != nullptr; - - const std::string app_config_key = is_called_from_configwizard ? "" : "default_action_on_new_project"; - UnsavedChangesDialog dlg(caption, header, app_config_key, action_buttons); - std::string act = app_config_key.empty() ? "none" : wxGetApp().app_config->get(app_config_key); - if (act == "none" && dlg.ShowModal() == wxID_CANCEL) - return false; - - auto reset_modifications = [this, is_called_from_configwizard]() { - if (is_called_from_configwizard) - return; // no need to discared changes. It will be done fromConfigWizard closing - - PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); - for (const Tab* const tab : tabs_list) { - if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) - tab->m_presets->discard_current_changes(); - } - load_current_presets(false); - }; - - if (dlg.discard()) - reset_modifications(); - else // save selected changes - { - const auto& preset_names_and_types = dlg.get_names_and_types(); - if (dlg.save_preset()) { - for (const std::pair& nt : preset_names_and_types) - preset_bundle->save_changes_for_preset(nt.first, nt.second, dlg.get_unselected_options(nt.second)); - - // if we saved changes to the new presets, we should to - // synchronize config.ini with the current selections. - preset_bundle->export_selections(*app_config); - - wxString text = _L_PLURAL("The preset modifications are successfully saved", - "The presets modifications are successfully saved", preset_names_and_types.size()); - if (!is_called_from_configwizard) - text += "\n\n" + _L("For new project all modifications will be reseted"); - - MessageDialog(nullptr, text).ShowModal(); - reset_modifications(); - } - else if (dlg.transfer_changes() && (dlg.has_unselected_options() || is_called_from_configwizard)) { - // execute this part of code only if not all modifications are keeping to the new project - // OR this function is called when ConfigWizard is closed and "Keep modifications" is selected - for (const std::pair& nt : preset_names_and_types) { - Preset::Type type = nt.second; - Tab* tab = get_tab(type); - std::vector selected_options = dlg.get_selected_options(type); - if (type == Preset::TYPE_PRINTER) { - auto it = std::find(selected_options.begin(), selected_options.end(), "extruders_count"); - if (it != selected_options.end()) { - // erase "extruders_count" option from the list - selected_options.erase(it); - // cache the extruders count - static_cast(tab)->cache_extruder_cnt(); - } - } - tab->cache_config_diff(selected_options); - if (!is_called_from_configwizard) - tab->m_presets->discard_current_changes(); - } - if (is_called_from_configwizard) - *postponed_apply_of_keeped_changes = true; - else - apply_keeped_preset_modifications(); - } - } - } - - return true; -} - -bool GUI_App::can_load_project() -{ - int saved_project = plater()->save_project_if_dirty(_L("Loading a new project while the current project is modified.")); - if (saved_project == wxID_CANCEL || - (plater()->is_project_dirty() && saved_project == wxID_NO && - !check_and_save_current_preset_changes(_L("Project is loading"), _L("Opening new project while some presets are unsaved.")))) - return false; - return true; -} - -bool GUI_App::check_print_host_queue() -{ - wxString dirty; - std::vector> jobs; - // Get ongoing jobs from dialog - mainframe->m_printhost_queue_dlg->get_active_jobs(jobs); - if (jobs.empty()) - return true; - // Show dialog - wxString job_string = wxString(); - for (const auto& job : jobs) { - job_string += format_wxstr(" %1% : %2% \n", job.first, job.second); - } - wxString message; - message += _(L("The uploads are still ongoing")) + ":\n\n" + job_string +"\n" + _(L("Stop them and continue anyway?")); - //wxMessageDialog dialog(mainframe, - MessageDialog dialog(mainframe, - message, - wxString(SLIC3R_APP_NAME) + " - " + _(L("Ongoing uploads")), - wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT); - if (dialog.ShowModal() == wxID_YES) - return true; - - // TODO: If already shown, bring forward - mainframe->m_printhost_queue_dlg->Show(); - return false; -} - -bool GUI_App::checked_tab(Tab* tab) -{ - bool ret = true; - if (find(tabs_list.begin(), tabs_list.end(), tab) == tabs_list.end()) - ret = false; - return ret; -} - -// Update UI / Tabs to reflect changes in the currently loaded presets -void GUI_App::load_current_presets(bool check_printer_presets_ /*= true*/) -{ - // check printer_presets for the containing information about "Print Host upload" - // and create physical printer from it, if any exists - if (check_printer_presets_) - check_printer_presets(); - - PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); - this->plater()->set_printer_technology(printer_technology); - for (Tab *tab : tabs_list) - if (tab->supports_printer_technology(printer_technology)) { - if (tab->type() == Preset::TYPE_PRINTER) { - static_cast(tab)->update_pages(); - // Mark the plater to update print bed by tab->load_current_preset() from Plater::on_config_change(). - this->plater()->force_print_bed_update(); - } - tab->load_current_preset(); - } -} - -bool GUI_App::OnExceptionInMainLoop() -{ - generic_exception_handle(); - return false; -} - -#ifdef __APPLE__ -// This callback is called from wxEntry()->wxApp::CallOnInit()->NSApplication run -// that is, before GUI_App::OnInit(), so we have a chance to switch GUI_App -// to a G-code viewer. -void GUI_App::OSXStoreOpenFiles(const wxArrayString &fileNames) -{ - size_t num_gcodes = 0; - for (const wxString &filename : fileNames) - if (is_gcode_file(into_u8(filename))) - ++ num_gcodes; - if (fileNames.size() == num_gcodes) { - // Opening PrusaSlicer by drag & dropping a G-Code onto PrusaSlicer icon in Finder, - // just G-codes were passed. Switch to G-code viewer mode. - m_app_mode = EAppMode::GCodeViewer; - unlock_lockfile(get_instance_hash_string() + ".lock", data_dir() + "/cache/"); - if(app_config != nullptr) - delete app_config; - app_config = nullptr; - init_app_config(); - } - wxApp::OSXStoreOpenFiles(fileNames); -} -// wxWidgets override to get an event on open files. -void GUI_App::MacOpenFiles(const wxArrayString &fileNames) -{ - std::vector files; - std::vector gcode_files; - std::vector non_gcode_files; - for (const auto& filename : fileNames) { - if (is_gcode_file(into_u8(filename))) - gcode_files.emplace_back(filename); - else { - files.emplace_back(into_u8(filename)); - non_gcode_files.emplace_back(filename); - } - } - if (m_app_mode == EAppMode::GCodeViewer) { - // Running in G-code viewer. - // Load the first G-code into the G-code viewer. - // Or if no G-codes, send other files to slicer. - if (! gcode_files.empty()) { - if (m_post_initialized) - this->plater()->load_gcode(gcode_files.front()); - else - this->init_params->input_files = { into_u8(gcode_files.front()) }; - } - if (!non_gcode_files.empty()) - start_new_slicer(non_gcode_files, true); - } else { - if (! files.empty()) { - if (m_post_initialized) { - wxArrayString input_files; - for (size_t i = 0; i < non_gcode_files.size(); ++i) - input_files.push_back(non_gcode_files[i]); - this->plater()->load_files(input_files); - } else { - for (const auto &f : non_gcode_files) - this->init_params->input_files.emplace_back(into_u8(f)); - } - } - for (const wxString &filename : gcode_files) - start_new_gcodeviewer(&filename); - } -} -#endif /* __APPLE */ - -Sidebar& GUI_App::sidebar() -{ - return plater_->sidebar(); -} - -ObjectManipulation* GUI_App::obj_manipul() -{ - // If this method is called before plater_ has been initialized, return nullptr (to avoid a crash) - return (plater_ != nullptr) ? sidebar().obj_manipul() : nullptr; -} - -ObjectSettings* GUI_App::obj_settings() -{ - return sidebar().obj_settings(); -} - -ObjectList* GUI_App::obj_list() -{ - return sidebar().obj_list(); -} - -ObjectLayers* GUI_App::obj_layers() -{ - return sidebar().obj_layers(); -} - -Plater* GUI_App::plater() -{ - return plater_; -} - -const Plater* GUI_App::plater() const -{ - return plater_; -} - -Model& GUI_App::model() -{ - return plater_->model(); -} -wxBookCtrlBase* GUI_App::tab_panel() const -{ - return mainframe->m_tabpanel; -} - -NotificationManager * GUI_App::notification_manager() -{ - return plater_->get_notification_manager(); -} - -// extruders count from selected printer preset -int GUI_App::extruders_cnt() const -{ - const Preset& preset = preset_bundle->printers.get_selected_preset(); - return preset.printer_technology() == ptSLA ? 1 : - preset.config.option("nozzle_diameter")->values.size(); -} - -// extruders count from edited printer preset -int GUI_App::extruders_edited_cnt() const -{ - const Preset& preset = preset_bundle->printers.get_edited_preset(); - return preset.printer_technology() == ptSLA ? 1 : - preset.config.option("nozzle_diameter")->values.size(); -} - -wxString GUI_App::current_language_code_safe() const -{ - // Translate the language code to a code, for which Prusa Research maintains translations. - const std::map mapping { - { "cs", "cs_CZ", }, - { "sk", "cs_CZ", }, - { "de", "de_DE", }, - { "es", "es_ES", }, - { "fr", "fr_FR", }, - { "it", "it_IT", }, - { "ja", "ja_JP", }, - { "ko", "ko_KR", }, - { "pl", "pl_PL", }, - { "uk", "uk_UA", }, - { "zh", "zh_CN", }, - { "ru", "ru_RU", }, - }; - wxString language_code = this->current_language_code().BeforeFirst('_'); - auto it = mapping.find(language_code); - if (it != mapping.end()) - language_code = it->second; - else - language_code = "en_US"; - return language_code; -} - -void GUI_App::open_web_page_localized(const std::string &http_address) -{ - open_browser_with_warning_dialog(http_address + "&lng=" + this->current_language_code_safe(), nullptr, false); -} - -// If we are switching from the FFF-preset to the SLA, we should to control the printed objects if they have a part(s). -// Because of we can't to print the multi-part objects with SLA technology. -bool GUI_App::may_switch_to_SLA_preset(const wxString& caption) -{ - if (model_has_multi_part_objects(model())) { - show_info(nullptr, - _L("It's impossible to print multi-part object(s) with SLA technology.") + "\n\n" + - _L("Please check your object list before preset changing."), - caption); - return false; - } - return true; -} - -bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page) -{ - wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null"); - - if (reason == ConfigWizard::RR_USER) { - if (preset_updater->config_update(app_config->orig_version(), PresetUpdater::UpdateParams::FORCED_BEFORE_WIZARD) == PresetUpdater::R_ALL_CANCELED) - return false; - } - - auto wizard = new ConfigWizard(mainframe); - const bool res = wizard->run(reason, start_page); - - if (res) { - load_current_presets(); - - // #ysFIXME - delete after testing: This part of code looks redundant. All checks are inside ConfigWizard::priv::apply_config() - if (preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) - may_switch_to_SLA_preset(_L("Configuration is editing from ConfigWizard")); - } - - return res; -} - -void GUI_App::show_desktop_integration_dialog() -{ -#ifdef __linux__ - //wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null"); - DesktopIntegrationDialog dialog(mainframe); - dialog.ShowModal(); -#endif //__linux__ -} - -#if ENABLE_THUMBNAIL_GENERATOR_DEBUG -void GUI_App::gcode_thumbnails_debug() -{ - const std::string BEGIN_MASK = "; thumbnail begin"; - const std::string END_MASK = "; thumbnail end"; - std::string gcode_line; - bool reading_image = false; - unsigned int width = 0; - unsigned int height = 0; - - wxFileDialog dialog(GetTopWindow(), _L("Select a gcode file:"), "", "", "G-code files (*.gcode)|*.gcode;*.GCODE;", wxFD_OPEN | wxFD_FILE_MUST_EXIST); - if (dialog.ShowModal() != wxID_OK) - return; - - std::string in_filename = into_u8(dialog.GetPath()); - std::string out_path = boost::filesystem::path(in_filename).remove_filename().append(L"thumbnail").string(); - - boost::nowide::ifstream in_file(in_filename.c_str()); - std::vector rows; - std::string row; - if (in_file.good()) - { - while (std::getline(in_file, gcode_line)) - { - if (in_file.good()) - { - if (boost::starts_with(gcode_line, BEGIN_MASK)) - { - reading_image = true; - gcode_line = gcode_line.substr(BEGIN_MASK.length() + 1); - std::string::size_type x_pos = gcode_line.find('x'); - std::string width_str = gcode_line.substr(0, x_pos); - width = (unsigned int)::atoi(width_str.c_str()); - std::string height_str = gcode_line.substr(x_pos + 1); - height = (unsigned int)::atoi(height_str.c_str()); - row.clear(); - } - else if (reading_image && boost::starts_with(gcode_line, END_MASK)) - { - std::string out_filename = out_path + std::to_string(width) + "x" + std::to_string(height) + ".png"; - boost::nowide::ofstream out_file(out_filename.c_str(), std::ios::binary); - if (out_file.good()) - { - std::string decoded; - decoded.resize(boost::beast::detail::base64::decoded_size(row.size())); - decoded.resize(boost::beast::detail::base64::decode((void*)&decoded[0], row.data(), row.size()).first); - - out_file.write(decoded.c_str(), decoded.size()); - out_file.close(); - } - - reading_image = false; - width = 0; - height = 0; - rows.clear(); - } - else if (reading_image) - row += gcode_line.substr(2); - } - } - - in_file.close(); - } -} -#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG - -void GUI_App::window_pos_save(wxTopLevelWindow* window, const std::string &name) -{ - if (name.empty()) { return; } - const auto config_key = (boost::format("window_%1%") % name).str(); - - WindowMetrics metrics = WindowMetrics::from_window(window); - app_config->set(config_key, metrics.serialize()); - app_config->save(); -} - -void GUI_App::window_pos_restore(wxTopLevelWindow* window, const std::string &name, bool default_maximized) -{ - if (name.empty()) { return; } - const auto config_key = (boost::format("window_%1%") % name).str(); - - if (! app_config->has(config_key)) { - window->Maximize(default_maximized); - return; - } - - auto metrics = WindowMetrics::deserialize(app_config->get(config_key)); - if (! metrics) { - window->Maximize(default_maximized); - return; - } - - const wxRect& rect = metrics->get_rect(); - - if (app_config->get("restore_win_position") == "1") { - // workaround for crash related to the positioning of the window on secondary monitor - app_config->set("restore_win_position", (boost::format("crashed_at_%1%_pos") % name).str()); - app_config->save(); - window->SetPosition(rect.GetPosition()); - - // workaround for crash related to the positioning of the window on secondary monitor - app_config->set("restore_win_position", (boost::format("crashed_at_%1%_size") % name).str()); - app_config->save(); - window->SetSize(rect.GetSize()); - - // revert "restore_win_position" value if application wasn't crashed - app_config->set("restore_win_position", "1"); - app_config->save(); - } - else - window->CenterOnScreen(); - - window->Maximize(metrics->get_maximized()); -} - -void GUI_App::window_pos_sanitize(wxTopLevelWindow* window) -{ - /*unsigned*/int display_idx = wxDisplay::GetFromWindow(window); - wxRect display; - if (display_idx == wxNOT_FOUND) { - display = wxDisplay(0u).GetClientArea(); - window->Move(display.GetTopLeft()); - } else { - display = wxDisplay(display_idx).GetClientArea(); - } - - auto metrics = WindowMetrics::from_window(window); - metrics.sanitize_for_display(display); - if (window->GetScreenRect() != metrics.get_rect()) { - window->SetSize(metrics.get_rect()); - } -} - -bool GUI_App::config_wizard_startup() -{ - if (!m_app_conf_exists || preset_bundle->printers.only_default_printers()) { - run_wizard(ConfigWizard::RR_DATA_EMPTY); - return true; - } else if (get_app_config()->legacy_datadir()) { - // Looks like user has legacy pre-vendorbundle data directory, - // explain what this is and run the wizard - - MsgDataLegacy dlg; - dlg.ShowModal(); - - run_wizard(ConfigWizard::RR_DATA_LEGACY); - return true; - } - return false; -} - -bool GUI_App::check_updates(const bool verbose) -{ - PresetUpdater::UpdateResult updater_result; - try { - updater_result = preset_updater->config_update(app_config->orig_version(), verbose ? PresetUpdater::UpdateParams::SHOW_TEXT_BOX : PresetUpdater::UpdateParams::SHOW_NOTIFICATION); - if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) { - mainframe->Close(); - // Applicaiton is closing. - return false; - } - else if (updater_result == PresetUpdater::R_INCOMPAT_CONFIGURED) { - m_app_conf_exists = true; - } - else if (verbose && updater_result == PresetUpdater::R_NOOP) { - MsgNoUpdates dlg; - dlg.ShowModal(); - } - } - catch (const std::exception & ex) { - show_error(nullptr, ex.what()); - } - // Applicaiton will continue. - return true; -} - -bool GUI_App::open_browser_with_warning_dialog(const wxString& url, wxWindow* parent/* = nullptr*/, bool force_remember_choice /*= true*/, int flags/* = 0*/) -{ - bool launch = true; - - // warning dialog containes a "Remember my choice" checkbox - std::string option_key = "suppress_hyperlinks"; - if (force_remember_choice || app_config->get(option_key).empty()) { - if (app_config->get(option_key).empty()) { - RichMessageDialog dialog(parent, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO); - dialog.ShowCheckBox(_L("Remember my choice")); - auto answer = dialog.ShowModal(); - launch = answer == wxID_YES; - if (dialog.IsCheckBoxChecked()) { - wxString preferences_item = _L("Suppress to open hyperlink in browser"); - wxString msg = - _L("PrusaSlicer will remember your choice.") + "\n\n" + - _L("You will not be asked about it again on hyperlinks hovering.") + "\n\n" + - format_wxstr(_L("Visit \"Preferences\" and check \"%1%\"\nto changes your choice."), preferences_item); - - MessageDialog msg_dlg(parent, msg, _L("PrusaSlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION); - if (msg_dlg.ShowModal() == wxID_CANCEL) - return false; - app_config->set(option_key, answer == wxID_NO ? "1" : "0"); - } - } - if (launch) - launch = app_config->get(option_key) != "1"; - } - // warning dialog doesn't containe a "Remember my choice" checkbox - // and will be shown only when "Suppress to open hyperlink in browser" is ON. - else if (app_config->get(option_key) == "1") { - MessageDialog dialog(parent, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO); - launch = dialog.ShowModal() == wxID_YES; - } - - return launch && wxLaunchDefaultBrowser(url, flags); -} - -// static method accepting a wxWindow object as first parameter -// void warning_catcher{ -// my($self, $message_dialog) = @_; -// return sub{ -// my $message = shift; -// return if $message = ~/ GLUquadricObjPtr | Attempt to free unreferenced scalar / ; -// my @params = ($message, 'Warning', wxOK | wxICON_WARNING); -// $message_dialog -// ? $message_dialog->(@params) -// : Wx::MessageDialog->new($self, @params)->ShowModal; -// }; -// } - -// Do we need this function??? -// void GUI_App::notify(message) { -// auto frame = GetTopWindow(); -// // try harder to attract user attention on OS X -// if (!frame->IsActive()) -// frame->RequestUserAttention(defined(__WXOSX__/*&Wx::wxMAC */)? wxUSER_ATTENTION_ERROR : wxUSER_ATTENTION_INFO); -// -// // There used to be notifier using a Growl application for OSX, but Growl is dead. -// // The notifier also supported the Linux X D - bus notifications, but that support was broken. -// //TODO use wxNotificationMessage ? -// } - - -#ifdef __WXMSW__ -void GUI_App::associate_3mf_files() -{ - associate_file_type(L".3mf", L"Prusa.Slicer.1", L"PrusaSlicer", true); -} - -void GUI_App::associate_stl_files() -{ - associate_file_type(L".stl", L"Prusa.Slicer.1", L"PrusaSlicer", true); -} - -void GUI_App::associate_gcode_files() -{ - associate_file_type(L".gcode", L"PrusaSlicer.GCodeViewer.1", L"PrusaSlicerGCodeViewer", true); -} -#endif // __WXMSW__ - -void GUI_App::on_version_read(wxCommandEvent& evt) -{ - app_config->set("version_online", into_u8(evt.GetString())); - app_config->save(); - std::string opt = app_config->get("notify_release"); - if (this->plater_ == nullptr || (opt != "all" && opt != "release")) { - return; - } - if (*Semver::parse(SLIC3R_VERSION) >= *Semver::parse(into_u8(evt.GetString()))) { - return; - } - // notification - /* - this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAvailable - , NotificationManager::NotificationLevel::ImportantNotificationLevel - , Slic3r::format(_u8L("New release version %1% is available."), evt.GetString()) - , _u8L("See Download page.") - , [](wxEvtHandler* evnthndlr) {wxGetApp().open_web_page_localized("https://www.prusa3d.com/slicerweb"); return true; } - ); - */ - // updater - // read triggered_by_user that was set when calling GUI_App::app_version_check - app_updater(m_app_updater->get_triggered_by_user()); -} - -void GUI_App::app_updater(bool from_user) -{ - DownloadAppData app_data = m_app_updater->get_app_data(); - - if (from_user && (!app_data.version || *app_data.version <= *Semver::parse(SLIC3R_VERSION))) - { - BOOST_LOG_TRIVIAL(info) << "There is no newer version online."; - MsgNoAppUpdates no_update_dialog; - no_update_dialog.ShowModal(); - return; - - } - - assert(!app_data.url.empty()); - assert(!app_data.target_path.empty()); - - // dialog with new version info - AppUpdateAvailableDialog dialog(*Semver::parse(SLIC3R_VERSION), *app_data.version); - auto dialog_result = dialog.ShowModal(); - // checkbox "do not show again" - if (dialog.disable_version_check()) { - app_config->set("notify_release", "none"); - } - // Doesn't wish to update - if (dialog_result != wxID_OK) { - return; - } - // dialog with new version download (installer or app dependent on system) including path selection - AppUpdateDownloadDialog dwnld_dlg(*app_data.version, app_data.target_path); - dialog_result = dwnld_dlg.ShowModal(); - // Doesn't wish to download - if (dialog_result != wxID_OK) { - return; - } - app_data.target_path =dwnld_dlg.get_download_path(); - - // start download - this->plater_->get_notification_manager()->push_download_progress_notification(_utf8("Download"), std::bind(&AppUpdater::cancel_callback, this->m_app_updater.get())); - app_data.start_after = dwnld_dlg.run_after_download(); - m_app_updater->set_app_data(std::move(app_data)); - m_app_updater->sync_download(); -} - -void GUI_App::app_version_check(bool from_user) -{ - if (from_user) { - if (m_app_updater->get_download_ongoing()) { - MessageDialog msgdlg(nullptr, _L("Download of new version is already ongoing. Do you wish to continue?"), _L("Notice"), wxYES_NO); - if (msgdlg.ShowModal() != wxID_YES) - return; - } - } - std::string version_check_url = app_config->version_check_url(); - m_app_updater->sync_version(version_check_url, from_user); -} - -} // GUI -} //Slic3r -======= #include "libslic3r/Technologies.hpp" #include "GUI_App.hpp" #include "GUI_Init.hpp" @@ -3252,6 +13,7 @@ void GUI_App::app_version_check(bool from_user) #include #include #include +#include #include #include #include @@ -3283,6 +45,7 @@ void GUI_App::app_version_check(bool from_user) #include "libslic3r/Model.hpp" #include "libslic3r/I18N.hpp" #include "libslic3r/PresetBundle.hpp" +#include "libslic3r/Color.hpp" #include "GUI.hpp" #include "GUI_Utils.hpp" @@ -3295,6 +58,8 @@ void GUI_App::app_version_check(bool from_user) #include "../Utils/PrintHost.hpp" #include "../Utils/Process.hpp" #include "../Utils/MacDarkMode.hpp" +#include "../Utils/AppUpdater.hpp" +#include "../Utils/WinRegistry.hpp" #include "slic3r/Config/Snapshot.hpp" #include "ConfigSnapshotDialog.hpp" #include "FirmwareDialog.hpp" @@ -3504,9 +269,9 @@ private: credits = title + " " + _L("is based on Slic3r by Alessandro Ranellucci and the RepRap community.") + "\n" + _L("Developed by Prusa Research.")+ "\n\n" + - title + " " + _L("is licensed under the") + " " + _L("GNU Affero General Public License, version 3") + ".\n\n" + + title + " " + _L("is licensed under the") + " " + _L("GNU Affero General Public License, version 3") + "\n\n" + _L("Contributions by Vojtech Bubnik, Enrico Turri, Oleksandra Iushchenko, Tamas Meszaros, Lukas Matena, Vojtech Kral, David Kocik and numerous others.") + "\n\n" + - _L("Artwork model by Leslie Ing") + "." ; + _L("Artwork model by M Boyer"); title_font = version_font = credits_font = init_font; } @@ -3668,50 +433,21 @@ bool static check_old_linux_datadir(const wxString& app_name) { } #endif - #ifdef _WIN32 +#if 0 // External Updater is replaced with AppUpdater.cpp static bool run_updater_win() { // find updater exe boost::filesystem::path path_updater = boost::dll::program_location().parent_path() / "prusaslicer-updater.exe"; - if (boost::filesystem::exists(path_updater)) { - // run updater. Original args: /silent -restartapp prusa-slicer.exe -startappfirst - - // Using quoted string as mentioned in CreateProcessW docs, silent execution parameter. - std::wstring wcmd = L"\"" + path_updater.wstring() + L"\" /silent"; - - // additional information - STARTUPINFOW si; - PROCESS_INFORMATION pi; - - // set the size of the structures - ZeroMemory(&si, sizeof(si)); - si.cb = sizeof(si); - ZeroMemory(&pi, sizeof(pi)); - - // start the program up - if (CreateProcessW(NULL, // the path - wcmd.data(), // Command line - NULL, // Process handle not inheritable - NULL, // Thread handle not inheritable - FALSE, // Set handle inheritance to FALSE - 0, // No creation flags - NULL, // Use parent's environment block - NULL, // Use parent's starting directory - &si, // Pointer to STARTUPINFO structure - &pi // Pointer to PROCESS_INFORMATION structure (removed extra parentheses) - )) { - // Close process and thread handles. - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - return true; - } else { - BOOST_LOG_TRIVIAL(error) << "Failed to start prusaslicer-updater.exe with command " << wcmd; - } - } - return false; + // run updater. Original args: /silent -restartapp prusa-slicer.exe -startappfirst + std::string msg; + bool res = create_process(path_updater, L"/silent", msg); + if (!res) + BOOST_LOG_TRIVIAL(error) << msg; + return res; } -#endif //_WIN32 +#endif // 0 +#endif // _WIN32 struct FileWildcards { std::string_view title; @@ -3721,6 +457,7 @@ struct FileWildcards { static const FileWildcards file_wildcards_by_type[FT_SIZE] = { /* FT_STL */ { "STL files"sv, { ".stl"sv } }, /* FT_OBJ */ { "OBJ files"sv, { ".obj"sv } }, + /* FT_OBJECT */ { "Object files"sv, { ".stl"sv, ".obj"sv } }, /* FT_AMF */ { "AMF files"sv, { ".amf"sv, ".zip.amf"sv, ".xml"sv } }, /* FT_3MF */ { "3MF files"sv, { ".3mf"sv } }, /* FT_GCODE */ { "G-code files"sv, { ".gcode"sv, ".gco"sv, ".g"sv, ".ngc"sv } }, @@ -3733,9 +470,47 @@ static const FileWildcards file_wildcards_by_type[FT_SIZE] = { /* FT_TEX */ { "Texture"sv, { ".png"sv, ".svg"sv } }, - /* FT_SL1 */ { "Masked SLA files"sv, { ".sl1"sv, ".sl1s"sv } }, + /* FT_SL1 */ { "Masked SLA files"sv, { ".sl1"sv, ".sl1s"sv, ".pwmx"sv } }, }; +#if ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR +wxString file_wildcards(FileType file_type) +{ + const FileWildcards& data = file_wildcards_by_type[file_type]; + std::string title; + std::string mask; + + // Generate cumulative first item + for (const std::string_view& ext : data.file_extensions) { + if (title.empty()) { + title = "*"; + title += ext; + mask = title; + } + else { + title += ", *"; + title += ext; + mask += ";*"; + mask += ext; + } + mask += ";*"; + mask += boost::to_upper_copy(std::string(ext)); + } + + wxString ret = GUI::format_wxstr("%s (%s)|%s", data.title, title, mask); + + // Adds an item for each of the extensions + if (data.file_extensions.size() > 1) { + for (const std::string_view& ext : data.file_extensions) { + title = "*"; + title += ext; + ret += GUI::format_wxstr("|%s (%s)|%s", data.title, title, title); + } + } + + return ret; +} +#else // This function produces a Win32 file dialog file template mask to be consumed by wxWidgets on all platforms. // The function accepts a custom extension parameter. If the parameter is provided, the custom extension // will be added as a fist to the list. This is important for a "file save" dialog on OSX, which strips @@ -3788,8 +563,10 @@ wxString file_wildcards(FileType file_type, const std::string &custom_extension) mask += ";*"; mask += boost::to_upper_copy(std::string(ext)); } + return GUI::format_wxstr("%s (%s)|%s", data.title, title, mask); } +#endif // ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR static std::string libslic3r_translate_callback(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str().data(); } @@ -4013,18 +790,13 @@ void GUI_App::post_init() CallAfter([this] { bool cw_showed = this->config_wizard_startup(); this->preset_updater->sync(preset_bundle); + this->app_version_check(false); if (! cw_showed) { // The CallAfter is needed as well, without it, GL extensions did not show. // Also, we only want to show this when the wizard does not, so the new user // sees something else than "we want something" on the first start. show_send_system_info_dialog_if_needed(); } - #ifdef _WIN32 - // Run external updater on Windows if version check is enabled. - if (this->preset_updater->version_check_enabled() && ! run_updater_win()) - // "prusaslicer-updater.exe" was not started, run our own update check. - #endif // _WIN32 - this->preset_updater->slic3r_update_notify(); }); } @@ -4050,6 +822,8 @@ GUI_App::GUI_App(EAppMode mode) { //app config initializes early becasuse it is used in instance checking in PrusaSlicer.cpp this->init_app_config(); + // init app downloader after path to datadir is set + m_app_updater = std::make_unique(); } GUI_App::~GUI_App() @@ -4109,7 +883,8 @@ void GUI_App::init_app_config() // Profiles for the alpha are stored into the PrusaSlicer-alpha directory to not mix with the current release. // SetAppName(SLIC3R_APP_KEY); SetAppName(SLIC3R_APP_KEY "-alpha"); -// SetAppName(SLIC3R_APP_KEY "-beta"); +// SetAppName(SLIC3R_APP_KEY "-beta"); + // SetAppDisplayName(SLIC3R_APP_NAME); @@ -4436,23 +1211,8 @@ bool GUI_App::on_init_inner() #endif // __WXMSW__ preset_updater = new PresetUpdater(); - Bind(EVT_SLIC3R_VERSION_ONLINE, [this](const wxCommandEvent& evt) { - app_config->set("version_online", into_u8(evt.GetString())); - app_config->save(); - std::string opt = app_config->get("notify_release"); - if (this->plater_ != nullptr && (opt == "all" || opt == "release")) { - if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(into_u8(evt.GetString()))) { - this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAvailable - , NotificationManager::NotificationLevel::ImportantNotificationLevel - , Slic3r::format(_u8L("New release version %1% is available."), evt.GetString()) - , _u8L("See Download page.") - , [](wxEvtHandler* evnthndlr) {wxGetApp().open_web_page_localized("https://www.prusa3d.com/slicerweb"); return true; } - ); - } - } - }); + Bind(EVT_SLIC3R_VERSION_ONLINE, &GUI_App::on_version_read, this); Bind(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, [this](const wxCommandEvent& evt) { - app_config->save(); if (this->plater_ != nullptr && app_config->get("notify_release") == "all") { std::string evt_string = into_u8(evt.GetString()); if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(evt_string)) { @@ -4466,6 +1226,22 @@ bool GUI_App::on_init_inner() } } }); + Bind(EVT_SLIC3R_APP_DOWNLOAD_PROGRESS, [this](const wxCommandEvent& evt) { + //lm:This does not force a render. The progress bar only updateswhen the mouse is moved. + if (this->plater_ != nullptr) + this->plater_->get_notification_manager()->set_download_progress_percentage((float)std::stoi(into_u8(evt.GetString())) / 100.f ); + }); + + Bind(EVT_SLIC3R_APP_DOWNLOAD_FAILED, [this](const wxCommandEvent& evt) { + if (this->plater_ != nullptr) + this->plater_->get_notification_manager()->close_notification_of_type(NotificationType::AppDownload); + if(!evt.GetString().IsEmpty()) + show_error(nullptr, evt.GetString()); + }); + + Bind(EVT_SLIC3R_APP_OPEN_FAILED, [](const wxCommandEvent& evt) { + show_error(nullptr, evt.GetString()); + }); } else { #ifdef __WXMSW__ @@ -4841,8 +1617,7 @@ void GUI_App::set_label_clr_modified(const wxColour& clr) if (m_color_label_modified == clr) return; m_color_label_modified = clr; - auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), clr.Red(), clr.Green(), clr.Blue()); - std::string str = clr_str.ToStdString(); + const std::string str = encode_color(ColorRGB(clr.Red(), clr.Green(), clr.Blue())); app_config->set("label_clr_modified", str); app_config->save(); } @@ -4852,8 +1627,7 @@ void GUI_App::set_label_clr_sys(const wxColour& clr) if (m_color_label_sys == clr) return; m_color_label_sys = clr; - auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), clr.Red(), clr.Green(), clr.Blue()); - std::string str = clr_str.ToStdString(); + const std::string str = encode_color(ColorRGB(clr.Red(), clr.Green(), clr.Blue())); app_config->set("label_clr_sys", str); app_config->save(); } @@ -5057,6 +1831,7 @@ void GUI_App::update_ui_from_settings() m_force_colors_update = false; mainframe->force_color_changed(); mainframe->diff_dialog.force_color_changed(); + mainframe->preferences_dialog->force_color_changed(); mainframe->printhost_queue_dlg()->force_color_changed(); #ifdef _MSW_DARK_MODE update_scrolls(mainframe); @@ -5477,7 +2252,8 @@ void GUI_App::add_config_menu(wxMenuBar *menu) local_menu->Append(config_id_base + ConfigMenuWizard, config_wizard_name + dots, config_wizard_tooltip); local_menu->Append(config_id_base + ConfigMenuSnapshots, _L("&Configuration Snapshots") + dots, _L("Inspect / activate configuration snapshots")); local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _L("Take Configuration &Snapshot"), _L("Capture a configuration snapshot")); - local_menu->Append(config_id_base + ConfigMenuUpdate, _L("Check for Configuration Updates"), _L("Check for configuration updates")); + local_menu->Append(config_id_base + ConfigMenuUpdateConf, _L("Check for Configuration Updates"), _L("Check for configuration updates")); + local_menu->Append(config_id_base + ConfigMenuUpdateApp, _L("Check for Application Updates"), _L("Check for new version of application")); #if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) //if (DesktopIntegrationDialog::integration_possible()) local_menu->Append(config_id_base + ConfigMenuDesktopIntegration, _L("Desktop Integration"), _L("Desktop Integration")); @@ -5519,9 +2295,12 @@ void GUI_App::add_config_menu(wxMenuBar *menu) case ConfigMenuWizard: run_wizard(ConfigWizard::RR_USER); break; - case ConfigMenuUpdate: + case ConfigMenuUpdateConf: check_updates(true); break; + case ConfigMenuUpdateApp: + app_version_check(true); + break; #ifdef __linux__ case ConfigMenuDesktopIntegration: show_desktop_integration_dialog(); @@ -5577,40 +2356,7 @@ void GUI_App::add_config_menu(wxMenuBar *menu) break; case ConfigMenuPreferences: { - bool app_layout_changed = false; - { - // the dialog needs to be destroyed before the call to recreate_GUI() - // or sometimes the application crashes into wxDialogBase() destructor - // so we put it into an inner scope - PreferencesDialog dlg(mainframe); - dlg.ShowModal(); - app_layout_changed = dlg.settings_layout_changed(); - if (dlg.seq_top_layer_only_changed()) - this->plater_->refresh_print(); - - if (dlg.recreate_GUI()) { - recreate_GUI(_L("Restart application") + dots); - return; - } -#ifdef _WIN32 - if (is_editor()) { - if (app_config->get("associate_3mf") == "1") - associate_3mf_files(); - if (app_config->get("associate_stl") == "1") - associate_stl_files(); - } - else { - if (app_config->get("associate_gcode") == "1") - associate_gcode_files(); - } -#endif // _WIN32 - } - if (app_layout_changed) { - // hide full main_sizer for mainFrame - mainframe->GetSizer()->Show(false); - mainframe->update_layout(); - mainframe->select_tab(size_t(0)); - } + open_preferences(); break; } case ConfigMenuLanguage: @@ -5658,36 +2404,34 @@ void GUI_App::add_config_menu(wxMenuBar *menu) menu->Append(local_menu, _L("&Configuration")); } -void GUI_App::open_preferences(size_t open_on_tab, const std::string& highlight_option) +void GUI_App::open_preferences(const std::string& highlight_option /*= std::string()*/, const std::string& tab_name/*= std::string()*/) { - bool app_layout_changed = false; - { - // the dialog needs to be destroyed before the call to recreate_GUI() - // or sometimes the application crashes into wxDialogBase() destructor - // so we put it into an inner scope - PreferencesDialog dlg(mainframe, open_on_tab, highlight_option); - dlg.ShowModal(); - app_layout_changed = dlg.settings_layout_changed(); + mainframe->preferences_dialog->show(highlight_option, tab_name); + + if (mainframe->preferences_dialog->recreate_GUI()) + recreate_GUI(_L("Restart application") + dots); + #if ENABLE_GCODE_LINES_ID_IN_H_SLIDER - if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed()) + if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed()) #else - if (dlg.seq_top_layer_only_changed()) + if (mainframe->preferences_dialog->seq_top_layer_only_changed()) #endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER - this->plater_->refresh_print(); + this->plater_->refresh_print(); + #ifdef _WIN32 - if (is_editor()) { - if (app_config->get("associate_3mf") == "1") - associate_3mf_files(); - if (app_config->get("associate_stl") == "1") - associate_stl_files(); - } - else { - if (app_config->get("associate_gcode") == "1") - associate_gcode_files(); - } -#endif // _WIN32 + if (is_editor()) { + if (app_config->get("associate_3mf") == "1") + associate_3mf_files(); + if (app_config->get("associate_stl") == "1") + associate_stl_files(); } - if (app_layout_changed) { + else { + if (app_config->get("associate_gcode") == "1") + associate_gcode_files(); + } +#endif // _WIN32 + + if (mainframe->preferences_dialog->settings_layout_changed()) { // hide full main_sizer for mainFrame mainframe->GetSizer()->Show(false); mainframe->update_layout(); @@ -6392,118 +3136,102 @@ bool GUI_App::open_browser_with_warning_dialog(const wxString& url, wxWindow* pa #ifdef __WXMSW__ -static bool set_into_win_registry(HKEY hkeyHive, const wchar_t* pszVar, const wchar_t* pszValue) -{ - // see as reference: https://stackoverflow.com/questions/20245262/c-program-needs-an-file-association - wchar_t szValueCurrent[1000]; - DWORD dwType; - DWORD dwSize = sizeof(szValueCurrent); - - int iRC = ::RegGetValueW(hkeyHive, pszVar, nullptr, RRF_RT_ANY, &dwType, szValueCurrent, &dwSize); - - bool bDidntExist = iRC == ERROR_FILE_NOT_FOUND; - - if ((iRC != ERROR_SUCCESS) && !bDidntExist) - // an error occurred - return false; - - if (!bDidntExist) { - if (dwType != REG_SZ) - // invalid type - return false; - - if (::wcscmp(szValueCurrent, pszValue) == 0) - // value already set - return false; - } - - DWORD dwDisposition; - HKEY hkey; - iRC = ::RegCreateKeyExW(hkeyHive, pszVar, 0, 0, 0, KEY_ALL_ACCESS, nullptr, &hkey, &dwDisposition); - bool ret = false; - if (iRC == ERROR_SUCCESS) { - iRC = ::RegSetValueExW(hkey, L"", 0, REG_SZ, (BYTE*)pszValue, (::wcslen(pszValue) + 1) * sizeof(wchar_t)); - if (iRC == ERROR_SUCCESS) - ret = true; - } - - RegCloseKey(hkey); - return ret; -} - void GUI_App::associate_3mf_files() { - wchar_t app_path[MAX_PATH]; - ::GetModuleFileNameW(nullptr, app_path, sizeof(app_path)); - - std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\""; - std::wstring prog_id = L"Prusa.Slicer.1"; - std::wstring prog_desc = L"PrusaSlicer"; - std::wstring prog_command = prog_path + L" \"%1\""; - std::wstring reg_base = L"Software\\Classes"; - std::wstring reg_extension = reg_base + L"\\.3mf"; - std::wstring reg_prog_id = reg_base + L"\\" + prog_id; - std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command"; - - bool is_new = false; - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str()); - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str()); - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str()); - - if (is_new) - // notify Windows only when any of the values gets changed - ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); + associate_file_type(L".3mf", L"Prusa.Slicer.1", L"PrusaSlicer", true); } void GUI_App::associate_stl_files() { - wchar_t app_path[MAX_PATH]; - ::GetModuleFileNameW(nullptr, app_path, sizeof(app_path)); - - std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\""; - std::wstring prog_id = L"Prusa.Slicer.1"; - std::wstring prog_desc = L"PrusaSlicer"; - std::wstring prog_command = prog_path + L" \"%1\""; - std::wstring reg_base = L"Software\\Classes"; - std::wstring reg_extension = reg_base + L"\\.stl"; - std::wstring reg_prog_id = reg_base + L"\\" + prog_id; - std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command"; - - bool is_new = false; - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str()); - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str()); - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str()); - - if (is_new) - // notify Windows only when any of the values gets changed - ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); + associate_file_type(L".stl", L"Prusa.Slicer.1", L"PrusaSlicer", true); } void GUI_App::associate_gcode_files() { - wchar_t app_path[MAX_PATH]; - ::GetModuleFileNameW(nullptr, app_path, sizeof(app_path)); - - std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\""; - std::wstring prog_id = L"PrusaSlicer.GCodeViewer.1"; - std::wstring prog_desc = L"PrusaSlicerGCodeViewer"; - std::wstring prog_command = prog_path + L" \"%1\""; - std::wstring reg_base = L"Software\\Classes"; - std::wstring reg_extension = reg_base + L"\\.gcode"; - std::wstring reg_prog_id = reg_base + L"\\" + prog_id; - std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command"; - - bool is_new = false; - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str()); - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str()); - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str()); - - if (is_new) - // notify Windows only when any of the values gets changed - ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); + associate_file_type(L".gcode", L"PrusaSlicer.GCodeViewer.1", L"PrusaSlicerGCodeViewer", true); } #endif // __WXMSW__ +void GUI_App::on_version_read(wxCommandEvent& evt) +{ + app_config->set("version_online", into_u8(evt.GetString())); + app_config->save(); + std::string opt = app_config->get("notify_release"); + if (this->plater_ == nullptr || (opt != "all" && opt != "release")) { + return; + } + if (*Semver::parse(SLIC3R_VERSION) >= *Semver::parse(into_u8(evt.GetString()))) { + return; + } + // notification + /* + this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAvailable + , NotificationManager::NotificationLevel::ImportantNotificationLevel + , Slic3r::format(_u8L("New release version %1% is available."), evt.GetString()) + , _u8L("See Download page.") + , [](wxEvtHandler* evnthndlr) {wxGetApp().open_web_page_localized("https://www.prusa3d.com/slicerweb"); return true; } + ); + */ + // updater + // read triggered_by_user that was set when calling GUI_App::app_version_check + app_updater(m_app_updater->get_triggered_by_user()); +} + +void GUI_App::app_updater(bool from_user) +{ + DownloadAppData app_data = m_app_updater->get_app_data(); + + if (from_user && (!app_data.version || *app_data.version <= *Semver::parse(SLIC3R_VERSION))) + { + BOOST_LOG_TRIVIAL(info) << "There is no newer version online."; + MsgNoAppUpdates no_update_dialog; + no_update_dialog.ShowModal(); + return; + + } + + assert(!app_data.url.empty()); + assert(!app_data.target_path.empty()); + + // dialog with new version info + AppUpdateAvailableDialog dialog(*Semver::parse(SLIC3R_VERSION), *app_data.version); + auto dialog_result = dialog.ShowModal(); + // checkbox "do not show again" + if (dialog.disable_version_check()) { + app_config->set("notify_release", "none"); + } + // Doesn't wish to update + if (dialog_result != wxID_OK) { + return; + } + // dialog with new version download (installer or app dependent on system) including path selection + AppUpdateDownloadDialog dwnld_dlg(*app_data.version, app_data.target_path); + dialog_result = dwnld_dlg.ShowModal(); + // Doesn't wish to download + if (dialog_result != wxID_OK) { + return; + } + app_data.target_path =dwnld_dlg.get_download_path(); + + // start download + this->plater_->get_notification_manager()->push_download_progress_notification(_utf8("Download"), std::bind(&AppUpdater::cancel_callback, this->m_app_updater.get())); + app_data.start_after = dwnld_dlg.run_after_download(); + m_app_updater->set_app_data(std::move(app_data)); + m_app_updater->sync_download(); +} + +void GUI_App::app_version_check(bool from_user) +{ + if (from_user) { + if (m_app_updater->get_download_ongoing()) { + MessageDialog msgdlg(nullptr, _L("Download of new version is already ongoing. Do you wish to continue?"), _L("Notice"), wxYES_NO); + if (msgdlg.ShowModal() != wxID_YES) + return; + } + } + std::string version_check_url = app_config->version_check_url(); + m_app_updater->sync_version(version_check_url, from_user); +} + } // GUI } //Slic3r ->>>>>>> master_250 diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index e01b2c1aa..7272a5ef7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -1,4 +1,3 @@ -<<<<<<< HEAD #include "GLGizmoHollow.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/Camera.hpp" @@ -991,900 +990,3 @@ void GLGizmoHollow::on_set_hover_id() } // namespace GUI } // namespace Slic3r -======= -#include "GLGizmoHollow.hpp" -#include "slic3r/GUI/GLCanvas3D.hpp" -#include "slic3r/GUI/Camera.hpp" -#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" - -#include - -#include "slic3r/GUI/GUI_App.hpp" -#include "slic3r/GUI/GUI_ObjectSettings.hpp" -#include "slic3r/GUI/GUI_ObjectList.hpp" -#include "slic3r/GUI/Plater.hpp" -#include "libslic3r/PresetBundle.hpp" - -#include "libslic3r/Model.hpp" - - -namespace Slic3r { -namespace GUI { - -GLGizmoHollow::GLGizmoHollow(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) - : GLGizmoBase(parent, icon_filename, sprite_id) -{ - m_vbo_cylinder.init_from(its_make_cylinder(1., 1.)); -} - - -bool GLGizmoHollow::on_init() -{ - m_shortcut_key = WXK_CONTROL_H; - m_desc["enable"] = _(L("Hollow this object")); - m_desc["preview"] = _(L("Preview hollowed and drilled model")); - m_desc["offset"] = _(L("Offset")) + ": "; - m_desc["quality"] = _(L("Quality")) + ": "; - m_desc["closing_distance"] = _(L("Closing distance")) + ": "; - m_desc["hole_diameter"] = _(L("Hole diameter")) + ": "; - m_desc["hole_depth"] = _(L("Hole depth")) + ": "; - m_desc["remove_selected"] = _(L("Remove selected holes")); - m_desc["remove_all"] = _(L("Remove all holes")); - m_desc["clipping_of_view"] = _(L("Clipping of view"))+ ": "; - m_desc["reset_direction"] = _(L("Reset direction")); - m_desc["show_supports"] = _(L("Show supports")); - - return true; -} - -void GLGizmoHollow::set_sla_support_data(ModelObject*, const Selection&) -{ - if (! m_c->selection_info()) - return; - - const ModelObject* mo = m_c->selection_info()->model_object(); - if (m_state == On && mo) { - if (m_old_mo_id != mo->id()) { - reload_cache(); - m_old_mo_id = mo->id(); - } - if (m_c->hollowed_mesh() && m_c->hollowed_mesh()->get_hollowed_mesh()) - m_holes_in_drilled_mesh = mo->sla_drain_holes; - } -} - - - -void GLGizmoHollow::on_render() -{ - const Selection& selection = m_parent.get_selection(); - const CommonGizmosDataObjects::SelectionInfo* sel_info = m_c->selection_info(); - - // If current m_c->m_model_object does not match selection, ask GLCanvas3D to turn us off - if (m_state == On - && (sel_info->model_object() != selection.get_model()->objects[selection.get_object_idx()] - || sel_info->get_active_instance() != selection.get_instance_idx())) { - m_parent.post_event(SimpleEvent(EVT_GLCANVAS_RESETGIZMOS)); - return; - } - - glsafe(::glEnable(GL_BLEND)); - glsafe(::glEnable(GL_DEPTH_TEST)); - - if (selection.is_from_single_instance()) - render_points(selection, false); - - m_selection_rectangle.render(m_parent); - m_c->object_clipper()->render_cut(); - m_c->supports_clipper()->render_cut(); - - glsafe(::glDisable(GL_BLEND)); -} - - -void GLGizmoHollow::on_render_for_picking() -{ - const Selection& selection = m_parent.get_selection(); -//#if ENABLE_RENDER_PICKING_PASS -// m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z(); -//#endif - - glsafe(::glEnable(GL_DEPTH_TEST)); - render_points(selection, true); -} - -void GLGizmoHollow::render_points(const Selection& selection, bool picking) const -{ - GLShaderProgram* shader = picking ? nullptr : wxGetApp().get_shader("gouraud_light"); - if (shader) - shader->start_using(); - ScopeGuard guard([shader]() { if (shader) shader->stop_using(); }); - - const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); - Geometry::Transformation trafo = vol->get_instance_transformation() * vol->get_volume_transformation(); - const Transform3d& instance_scaling_matrix_inverse = trafo.get_matrix(true, true, false, true).inverse(); - const Transform3d& instance_matrix = trafo.get_matrix(); - - glsafe(::glPushMatrix()); - glsafe(::glTranslated(0.0, 0.0, m_c->selection_info()->get_sla_shift())); - glsafe(::glMultMatrixd(instance_matrix.data())); - - std::array render_color; - const sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; - size_t cache_size = drain_holes.size(); - - for (size_t i = 0; i < cache_size; ++i) { - const sla::DrainHole& drain_hole = drain_holes[i]; - const bool& point_selected = m_selected[i]; - - if (is_mesh_point_clipped(drain_hole.pos.cast())) - continue; - - // First decide about the color of the point. - if (picking) { - std::array color = picking_color_component(i); - render_color = color; - } - else { - if (size_t(m_hover_id) == i) { - render_color = {0.f, 1.f, 1.f, 1.f}; - } - else if (m_c->hollowed_mesh() && - i < m_c->hollowed_mesh()->get_drainholes().size() && - m_c->hollowed_mesh()->get_drainholes()[i].failed) { - render_color = {1.f, 0.f, 0.f, .5f}; - } - else { // neigher hover nor picking - - render_color[0] = point_selected ? 1.0f : 1.f; - render_color[1] = point_selected ? 0.3f : 1.f; - render_color[2] = point_selected ? 0.3f : 1.f; - render_color[3] = 0.5f; - } - } - - const_cast(&m_vbo_cylinder)->set_color(-1, render_color); - - // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. - glsafe(::glPushMatrix()); - glsafe(::glTranslatef(drain_hole.pos(0), drain_hole.pos(1), drain_hole.pos(2))); - glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data())); - - if (vol->is_left_handed()) - glFrontFace(GL_CW); - - // Matrices set, we can render the point mark now. - Eigen::Quaterniond q; - q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * (-drain_hole.normal).cast()); - Eigen::AngleAxisd aa(q); - glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2))); - glsafe(::glPushMatrix()); - glsafe(::glTranslated(0., 0., -drain_hole.height)); - glsafe(::glScaled(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength)); - m_vbo_cylinder.render(); - glsafe(::glPopMatrix()); - - if (vol->is_left_handed()) - glFrontFace(GL_CCW); - glsafe(::glPopMatrix()); - } - - glsafe(::glPopMatrix()); -} - - - -bool GLGizmoHollow::is_mesh_point_clipped(const Vec3d& point) const -{ - if (m_c->object_clipper()->get_position() == 0.) - return false; - - auto sel_info = m_c->selection_info(); - int active_inst = m_c->selection_info()->get_active_instance(); - const ModelInstance* mi = sel_info->model_object()->instances[active_inst]; - const Transform3d& trafo = mi->get_transformation().get_matrix() * sel_info->model_object()->volumes.front()->get_matrix(); - - Vec3d transformed_point = trafo * point; - transformed_point(2) += sel_info->get_sla_shift(); - return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point); -} - - - -// Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal -// Return false if no intersection was found, true otherwise. -bool GLGizmoHollow::unproject_on_mesh(const Vec2d& mouse_pos, std::pair& pos_and_normal) -{ - if (! m_c->raycaster()->raycaster()) - return false; - - const Camera& camera = wxGetApp().plater()->get_camera(); - const Selection& selection = m_parent.get_selection(); - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); - Geometry::Transformation trafo = volume->get_instance_transformation() * volume->get_volume_transformation(); - trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift())); - - double clp_dist = m_c->object_clipper()->get_position(); - const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane(); - - // The raycaster query - Vec3f hit; - Vec3f normal; - if (m_c->raycaster()->raycaster()->unproject_on_mesh( - mouse_pos, - trafo.get_matrix(), - camera, - hit, - normal, - clp_dist != 0. ? clp : nullptr)) - { - if (m_c->hollowed_mesh() && m_c->hollowed_mesh()->get_hollowed_mesh()) { - // in this case the raycaster sees the hollowed and drilled mesh. - // if the point lies on the surface created by the hole, we want - // to ignore it. - for (const sla::DrainHole& hole : m_holes_in_drilled_mesh) { - sla::DrainHole outer(hole); - outer.radius *= 1.001f; - outer.height *= 1.001f; - if (outer.is_inside(hit)) - return false; - } - } - - // Return both the point and the facet normal. - pos_and_normal = std::make_pair(hit, normal); - return true; - } - else - return false; -} - -// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. -// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is -// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo -// concludes that the event was not intended for it, it should return false. -bool GLGizmoHollow::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) -{ - ModelObject* mo = m_c->selection_info()->model_object(); - int active_inst = m_c->selection_info()->get_active_instance(); - - - // left down with shift - show the selection rectangle: - if (action == SLAGizmoEventType::LeftDown && (shift_down || alt_down || control_down)) { - if (m_hover_id == -1) { - if (shift_down || alt_down) { - m_selection_rectangle.start_dragging(mouse_position, shift_down ? GLSelectionRectangle::Select : GLSelectionRectangle::Deselect); - } - } - else { - if (m_selected[m_hover_id]) - unselect_point(m_hover_id); - else { - if (!alt_down) - select_point(m_hover_id); - } - } - - return true; - } - - // left down without selection rectangle - place point on the mesh: - if (action == SLAGizmoEventType::LeftDown && !m_selection_rectangle.is_dragging() && !shift_down) { - // If any point is in hover state, this should initiate its move - return control back to GLCanvas: - if (m_hover_id != -1) - return false; - - // If there is some selection, don't add new point and deselect everything instead. - if (m_selection_empty) { - std::pair pos_and_normal; - if (unproject_on_mesh(mouse_position, pos_and_normal)) { // we got an intersection - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Add drainage hole"))); - - mo->sla_drain_holes.emplace_back(pos_and_normal.first, - -pos_and_normal.second, m_new_hole_radius, m_new_hole_height); - m_selected.push_back(false); - assert(m_selected.size() == mo->sla_drain_holes.size()); - m_parent.set_as_dirty(); - m_wait_for_up_event = true; - } - else - return false; - } - else - select_point(NoPoints); - - return true; - } - - // left up with selection rectangle - select points inside the rectangle: - if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp || action == SLAGizmoEventType::AltUp) && m_selection_rectangle.is_dragging()) { - // Is this a selection or deselection rectangle? - GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state(); - - // First collect positions of all the points in world coordinates. - Geometry::Transformation trafo = mo->instances[active_inst]->get_transformation(); - trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift())); - std::vector points; - for (unsigned int i=0; isla_drain_holes.size(); ++i) - points.push_back(trafo.get_matrix() * mo->sla_drain_holes[i].pos.cast()); - - // Now ask the rectangle which of the points are inside. - std::vector points_inside; - std::vector points_idxs = m_selection_rectangle.stop_dragging(m_parent, points); - for (size_t idx : points_idxs) - points_inside.push_back(points[idx].cast()); - - // Only select/deselect points that are actually visible - for (size_t idx : m_c->raycaster()->raycaster()->get_unobscured_idxs( - trafo, wxGetApp().plater()->get_camera(), points_inside, - m_c->object_clipper()->get_clipping_plane())) - { - if (rectangle_status == GLSelectionRectangle::Deselect) - unselect_point(points_idxs[idx]); - else - select_point(points_idxs[idx]); - } - return true; - } - - // left up with no selection rectangle - if (action == SLAGizmoEventType::LeftUp) { - if (m_wait_for_up_event) { - m_wait_for_up_event = false; - return true; - } - } - - // dragging the selection rectangle: - if (action == SLAGizmoEventType::Dragging) { - if (m_wait_for_up_event) - return true; // point has been placed and the button not released yet - // this prevents GLCanvas from starting scene rotation - - if (m_selection_rectangle.is_dragging()) { - m_selection_rectangle.dragging(mouse_position); - return true; - } - - return false; - } - - if (action == SLAGizmoEventType::Delete) { - // delete key pressed - delete_selected_points(); - return true; - } - - if (action == SLAGizmoEventType::RightDown) { - if (m_hover_id != -1) { - select_point(NoPoints); - select_point(m_hover_id); - delete_selected_points(); - return true; - } - return false; - } - - if (action == SLAGizmoEventType::SelectAll) { - select_point(AllPoints); - return true; - } - - if (action == SLAGizmoEventType::MouseWheelUp && control_down) { - double pos = m_c->object_clipper()->get_position(); - pos = std::min(1., pos + 0.01); - m_c->object_clipper()->set_position(pos, true); - return true; - } - - if (action == SLAGizmoEventType::MouseWheelDown && control_down) { - double pos = m_c->object_clipper()->get_position(); - pos = std::max(0., pos - 0.01); - m_c->object_clipper()->set_position(pos, true); - return true; - } - - if (action == SLAGizmoEventType::ResetClippingPlane) { - m_c->object_clipper()->set_position(-1., false); - return true; - } - - return false; -} - -void GLGizmoHollow::delete_selected_points() -{ - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Delete drainage hole"))); - sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; - - for (unsigned int idx=0; idxselection_info()->model_object()->sla_drain_holes; - - if (m_hover_id != -1) { - std::pair pos_and_normal; - if (! unproject_on_mesh(data.mouse_pos.cast(), pos_and_normal)) - return; - drain_holes[m_hover_id].pos = pos_and_normal.first; - drain_holes[m_hover_id].normal = -pos_and_normal.second; - } -} - - -void GLGizmoHollow::hollow_mesh(bool postpone_error_messages) -{ - wxGetApp().CallAfter([this, postpone_error_messages]() { - wxGetApp().plater()->reslice_SLA_hollowing( - *m_c->selection_info()->model_object(), postpone_error_messages); - }); -} - - -std::vector> -GLGizmoHollow::get_config_options(const std::vector& keys) const -{ - std::vector> out; - const ModelObject* mo = m_c->selection_info()->model_object(); - - if (! mo) - return out; - - const DynamicPrintConfig& object_cfg = mo->config.get(); - const DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; - std::unique_ptr default_cfg = nullptr; - - for (const std::string& key : keys) { - if (object_cfg.has(key)) - out.emplace_back(object_cfg.option(key), &object_cfg.def()->options.at(key)); // at() needed for const map - else - if (print_cfg.has(key)) - out.emplace_back(print_cfg.option(key), &print_cfg.def()->options.at(key)); - else { // we must get it from defaults - if (default_cfg == nullptr) - default_cfg.reset(DynamicPrintConfig::new_from_defaults_keys(keys)); - out.emplace_back(default_cfg->option(key), &default_cfg->def()->options.at(key)); - } - } - - return out; -} - - -void GLGizmoHollow::on_render_input_window(float x, float y, float bottom_limit) -{ - ModelObject* mo = m_c->selection_info()->model_object(); - if (! mo) - return; - - bool first_run = true; // This is a hack to redraw the button when all points are removed, - // so it is not delayed until the background process finishes. - - ConfigOptionMode current_mode = wxGetApp().get_mode(); - - std::vector opts_keys = {"hollowing_min_thickness", "hollowing_quality", "hollowing_closing_distance"}; - auto opts = get_config_options(opts_keys); - auto* offset_cfg = static_cast(opts[0].first); - float offset = offset_cfg->value; - double offset_min = opts[0].second->min; - double offset_max = opts[0].second->max; - - auto* quality_cfg = static_cast(opts[1].first); - float quality = quality_cfg->value; - double quality_min = opts[1].second->min; - double quality_max = opts[1].second->max; - ConfigOptionMode quality_mode = opts[1].second->mode; - - auto* closing_d_cfg = static_cast(opts[2].first); - float closing_d = closing_d_cfg->value; - double closing_d_min = opts[2].second->min; - double closing_d_max = opts[2].second->max; - ConfigOptionMode closing_d_mode = opts[2].second->mode; - - m_desc["offset"] = _(opts[0].second->label) + ":"; - m_desc["quality"] = _(opts[1].second->label) + ":"; - m_desc["closing_distance"] = _(opts[2].second->label) + ":"; - - -RENDER_AGAIN: - const float approx_height = m_imgui->scaled(20.0f); - y = std::min(y, bottom_limit - approx_height); - m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); - - m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); - - // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: - const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, - m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(0.5f); - - const float settings_sliders_left = - std::max(std::max({m_imgui->calc_text_size(m_desc.at("offset")).x, - m_imgui->calc_text_size(m_desc.at("quality")).x, - m_imgui->calc_text_size(m_desc.at("closing_distance")).x, - m_imgui->calc_text_size(m_desc.at("hole_diameter")).x, - m_imgui->calc_text_size(m_desc.at("hole_depth")).x}) + m_imgui->scaled(0.5f), clipping_slider_left); - - const float diameter_slider_left = settings_sliders_left; //m_imgui->calc_text_size(m_desc.at("hole_diameter")).x + m_imgui->scaled(1.f); - const float minimal_slider_width = m_imgui->scaled(4.f); - - const float button_preview_width = m_imgui->calc_button_size(m_desc.at("preview")).x; - - float window_width = minimal_slider_width + std::max({settings_sliders_left, clipping_slider_left, diameter_slider_left}); - window_width = std::max(window_width, button_preview_width); - - if (m_imgui->button(m_desc["preview"])) - hollow_mesh(); - - bool config_changed = false; - - ImGui::Separator(); - - { - auto opts = get_config_options({"hollowing_enable"}); - m_enable_hollowing = static_cast(opts[0].first)->value; - if (m_imgui->checkbox(m_desc["enable"], m_enable_hollowing)) { - mo->config.set("hollowing_enable", m_enable_hollowing); - wxGetApp().obj_list()->update_and_show_object_settings_item(); - config_changed = true; - } - } - - m_imgui->disabled_begin(! m_enable_hollowing); - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("offset")); - ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x); - ImGui::PushItemWidth(window_width - settings_sliders_left); - m_imgui->slider_float("##offset", &offset, offset_min, offset_max, "%.1f mm", 1.0f, true, _L(opts[0].second->tooltip)); - - bool slider_clicked = m_imgui->get_last_slider_status().clicked; // someone clicked the slider - bool slider_edited =m_imgui->get_last_slider_status().edited; // someone is dragging the slider - bool slider_released =m_imgui->get_last_slider_status().deactivated_after_edit; // someone has just released the slider - - if (current_mode >= quality_mode) { - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("quality")); - ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x); - m_imgui->slider_float("##quality", &quality, quality_min, quality_max, "%.1f", 1.0f, true, _L(opts[1].second->tooltip)); - - slider_clicked |= m_imgui->get_last_slider_status().clicked; - slider_edited |= m_imgui->get_last_slider_status().edited; - slider_released |= m_imgui->get_last_slider_status().deactivated_after_edit; - } - - if (current_mode >= closing_d_mode) { - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("closing_distance")); - ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x); - m_imgui->slider_float("##closing_distance", &closing_d, closing_d_min, closing_d_max, "%.1f mm", 1.0f, true, _L(opts[2].second->tooltip)); - - slider_clicked |= m_imgui->get_last_slider_status().clicked; - slider_edited |= m_imgui->get_last_slider_status().edited; - slider_released |= m_imgui->get_last_slider_status().deactivated_after_edit; - } - - if (slider_clicked) { - m_offset_stash = offset; - m_quality_stash = quality; - m_closing_d_stash = closing_d; - } - if (slider_edited || slider_released) { - if (slider_released) { - mo->config.set("hollowing_min_thickness", m_offset_stash); - mo->config.set("hollowing_quality", m_quality_stash); - mo->config.set("hollowing_closing_distance", m_closing_d_stash); - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Hollowing parameter change"))); - } - mo->config.set("hollowing_min_thickness", offset); - mo->config.set("hollowing_quality", quality); - mo->config.set("hollowing_closing_distance", closing_d); - if (slider_released) { - wxGetApp().obj_list()->update_and_show_object_settings_item(); - config_changed = true; - } - } - - m_imgui->disabled_end(); - - bool force_refresh = false; - bool remove_selected = false; - bool remove_all = false; - - ImGui::Separator(); - - float diameter_upper_cap = 60.; - if (m_new_hole_radius * 2.f > diameter_upper_cap) - m_new_hole_radius = diameter_upper_cap / 2.f; - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("hole_diameter")); - ImGui::SameLine(diameter_slider_left, m_imgui->get_item_spacing().x); - ImGui::PushItemWidth(window_width - diameter_slider_left); - - float diam = 2.f * m_new_hole_radius; - m_imgui->slider_float("##hole_diameter", &diam, 1.f, 25.f, "%.1f mm", 1.f, false); - // Let's clamp the value (which could have been entered by keyboard) to a larger range - // than the slider. This allows entering off-scale values and still protects against - //complete non-sense. - diam = std::clamp(diam, 0.1f, diameter_upper_cap); - m_new_hole_radius = diam / 2.f; - bool clicked = m_imgui->get_last_slider_status().clicked; - bool edited = m_imgui->get_last_slider_status().edited; - bool deactivated = m_imgui->get_last_slider_status().deactivated_after_edit; - - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc["hole_depth"]); - ImGui::SameLine(diameter_slider_left, m_imgui->get_item_spacing().x); - m_imgui->slider_float("##hole_depth", &m_new_hole_height, 0.f, 10.f, "%.1f mm", 1.f, false); - // Same as above: - m_new_hole_height = std::clamp(m_new_hole_height, 0.f, 100.f); - - clicked |= m_imgui->get_last_slider_status().clicked; - edited |= m_imgui->get_last_slider_status().edited; - deactivated |= m_imgui->get_last_slider_status().deactivated_after_edit;; - - // Following is a nasty way to: - // - save the initial value of the slider before one starts messing with it - // - keep updating the head radius during sliding so it is continuosly refreshed in 3D scene - // - take correct undo/redo snapshot after the user is done with moving the slider - if (! m_selection_empty) { - if (clicked) { - m_holes_stash = mo->sla_drain_holes; - } - if (edited) { - for (size_t idx=0; idxsla_drain_holes[idx].radius = m_new_hole_radius; - mo->sla_drain_holes[idx].height = m_new_hole_height; - } - } - if (deactivated) { - // momentarily restore the old value to take snapshot - sla::DrainHoles new_holes = mo->sla_drain_holes; - mo->sla_drain_holes = m_holes_stash; - float backup_rad = m_new_hole_radius; - float backup_hei = m_new_hole_height; - for (size_t i=0; isla_drain_holes = new_holes; - } - } - - m_imgui->disabled_begin(m_selection_empty); - remove_selected = m_imgui->button(m_desc.at("remove_selected")); - m_imgui->disabled_end(); - - m_imgui->disabled_begin(mo->sla_drain_holes.empty()); - remove_all = m_imgui->button(m_desc.at("remove_all")); - m_imgui->disabled_end(); - - // Following is rendered in both editing and non-editing mode: - // m_imgui->text(""); - ImGui::Separator(); - if (m_c->object_clipper()->get_position() == 0.f) { - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("clipping_of_view")); - } - else { - if (m_imgui->button(m_desc.at("reset_direction"))) { - wxGetApp().CallAfter([this](){ - m_c->object_clipper()->set_position(-1., false); - }); - } - } - - ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x); - ImGui::PushItemWidth(window_width - settings_sliders_left); - float clp_dist = m_c->object_clipper()->get_position(); - if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f")) - m_c->object_clipper()->set_position(clp_dist, true); - - // make sure supports are shown/hidden as appropriate - bool show_sups = m_c->instances_hider()->are_supports_shown(); - if (m_imgui->checkbox(m_desc["show_supports"], show_sups)) { - m_c->instances_hider()->show_supports(show_sups); - force_refresh = true; - } - - m_imgui->end(); - - - if (remove_selected || remove_all) { - force_refresh = false; - m_parent.set_as_dirty(); - - if (remove_all) { - select_point(AllPoints); - delete_selected_points(); - } - if (remove_selected) - delete_selected_points(); - - if (first_run) { - first_run = false; - goto RENDER_AGAIN; - } - } - - if (force_refresh) - m_parent.set_as_dirty(); - - if (config_changed) - m_parent.post_event(SimpleEvent(EVT_GLCANVAS_FORCE_UPDATE)); -} - -bool GLGizmoHollow::on_is_activable() const -{ - const Selection& selection = m_parent.get_selection(); - - if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA - || !selection.is_from_single_instance()) - return false; - - // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside. - const Selection::IndicesList& list = selection.get_volume_idxs(); - for (const auto& idx : list) - if (selection.get_volume(idx)->is_outside && selection.get_volume(idx)->composite_id.volume_id >= 0) - return false; - - return true; -} - -bool GLGizmoHollow::on_is_selectable() const -{ - return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA); -} - -std::string GLGizmoHollow::on_get_name() const -{ - return _u8L("Hollow and drill"); -} - - -CommonGizmosDataID GLGizmoHollow::on_get_requirements() const -{ - return CommonGizmosDataID( - int(CommonGizmosDataID::SelectionInfo) - | int(CommonGizmosDataID::InstancesHider) - | int(CommonGizmosDataID::Raycaster) - | int(CommonGizmosDataID::HollowedMesh) - | int(CommonGizmosDataID::ObjectClipper) - | int(CommonGizmosDataID::SupportsClipper)); -} - - -void GLGizmoHollow::on_set_state() -{ - if (m_state == m_old_state) - return; - - if (m_state == Off && m_old_state != Off) // the gizmo was just turned Off - m_parent.post_event(SimpleEvent(EVT_GLCANVAS_FORCE_UPDATE)); - m_old_state = m_state; -} - - - -void GLGizmoHollow::on_start_dragging() -{ - if (m_hover_id != -1) { - select_point(NoPoints); - select_point(m_hover_id); - m_hole_before_drag = m_c->selection_info()->model_object()->sla_drain_holes[m_hover_id].pos; - } - else - m_hole_before_drag = Vec3f::Zero(); -} - - -void GLGizmoHollow::on_stop_dragging() -{ - sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; - if (m_hover_id != -1) { - Vec3f backup = drain_holes[m_hover_id].pos; - - if (m_hole_before_drag != Vec3f::Zero() // some point was touched - && backup != m_hole_before_drag) // and it was moved, not just selected - { - drain_holes[m_hover_id].pos = m_hole_before_drag; - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Move drainage hole"))); - drain_holes[m_hover_id].pos = backup; - } - } - m_hole_before_drag = Vec3f::Zero(); -} - - - -void GLGizmoHollow::on_load(cereal::BinaryInputArchive& ar) -{ - ar(m_new_hole_radius, - m_new_hole_height, - m_selected, - m_selection_empty - ); -} - - - -void GLGizmoHollow::on_save(cereal::BinaryOutputArchive& ar) const -{ - ar(m_new_hole_radius, - m_new_hole_height, - m_selected, - m_selection_empty - ); -} - - - -void GLGizmoHollow::select_point(int i) -{ - const sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; - - if (i == AllPoints || i == NoPoints) { - m_selected.assign(m_selected.size(), i == AllPoints); - m_selection_empty = (i == NoPoints); - - if (i == AllPoints && ! drain_holes.empty()) { - m_new_hole_radius = drain_holes[0].radius; - m_new_hole_height = drain_holes[0].height; - } - } - else { - while (size_t(i) >= m_selected.size()) - m_selected.push_back(false); - m_selected[i] = true; - m_selection_empty = false; - m_new_hole_radius = drain_holes[i].radius; - m_new_hole_height = drain_holes[i].height; - } -} - - -void GLGizmoHollow::unselect_point(int i) -{ - m_selected[i] = false; - m_selection_empty = true; - for (const bool sel : m_selected) { - if (sel) { - m_selection_empty = false; - break; - } - } -} - -void GLGizmoHollow::reload_cache() -{ - m_selected.clear(); - m_selected.assign(m_c->selection_info()->model_object()->sla_drain_holes.size(), false); -} - - -void GLGizmoHollow::on_set_hover_id() -{ - if (int(m_c->selection_info()->model_object()->sla_drain_holes.size()) <= m_hover_id) - m_hover_id = -1; -} - - - - -} // namespace GUI -} // namespace Slic3r ->>>>>>> master_250 diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 6f3ed3e2c..987fd325f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -1,4 +1,3 @@ -<<<<<<< HEAD // Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. #include "GLGizmoSlaSupports.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" @@ -1373,1252 +1372,3 @@ SlaGizmoHelpDialog::SlaGizmoHelpDialog() } // namespace GUI } // namespace Slic3r -======= -// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. -#include "GLGizmoSlaSupports.hpp" -#include "slic3r/GUI/GLCanvas3D.hpp" -#include "slic3r/GUI/Camera.hpp" -#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" -#include "slic3r/GUI/MainFrame.hpp" -#include "slic3r/Utils/UndoRedo.hpp" - -#include - -#include -#include -#include - -#include "slic3r/GUI/GUI_App.hpp" -#include "slic3r/GUI/GUI.hpp" -#include "slic3r/GUI/GUI_ObjectSettings.hpp" -#include "slic3r/GUI/GUI_ObjectList.hpp" -#include "slic3r/GUI/Plater.hpp" -#include "slic3r/GUI/NotificationManager.hpp" -#include "slic3r/GUI/MsgDialog.hpp" -#include "libslic3r/PresetBundle.hpp" -#include "libslic3r/SLAPrint.hpp" - - -namespace Slic3r { -namespace GUI { - -GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) - : GLGizmoBase(parent, icon_filename, sprite_id) -{ -} - - -bool GLGizmoSlaSupports::on_init() -{ - m_shortcut_key = WXK_CONTROL_L; - - m_desc["head_diameter"] = _L("Head diameter") + ": "; - m_desc["lock_supports"] = _L("Lock supports under new islands"); - m_desc["remove_selected"] = _L("Remove selected points"); - m_desc["remove_all"] = _L("Remove all points"); - m_desc["apply_changes"] = _L("Apply changes"); - m_desc["discard_changes"] = _L("Discard changes"); - m_desc["minimal_distance"] = _L("Minimal points distance") + ": "; - m_desc["points_density"] = _L("Support points density") + ": "; - m_desc["auto_generate"] = _L("Auto-generate points"); - m_desc["manual_editing"] = _L("Manual editing"); - m_desc["clipping_of_view"] = _L("Clipping of view")+ ": "; - m_desc["reset_direction"] = _L("Reset direction"); - - return true; -} - -void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const Selection& selection) -{ - if (! m_c->selection_info()) - return; - - ModelObject* mo = m_c->selection_info()->model_object(); - - if (m_state == On && mo && mo->id() != m_old_mo_id) { - disable_editing_mode(); - reload_cache(); - m_old_mo_id = mo->id(); - m_c->instances_hider()->show_supports(true); - } - - // If we triggered autogeneration before, check backend and fetch results if they are there - if (mo) { - if (mo->sla_points_status == sla::PointsStatus::Generating) - get_data_from_backend(); - } -} - - - -void GLGizmoSlaSupports::on_render() -{ - ModelObject* mo = m_c->selection_info()->model_object(); - const Selection& selection = m_parent.get_selection(); - - // If current m_c->m_model_object does not match selection, ask GLCanvas3D to turn us off - if (m_state == On - && (mo != selection.get_model()->objects[selection.get_object_idx()] - || m_c->selection_info()->get_active_instance() != selection.get_instance_idx())) { - m_parent.post_event(SimpleEvent(EVT_GLCANVAS_RESETGIZMOS)); - return; - } - - glsafe(::glEnable(GL_BLEND)); - glsafe(::glEnable(GL_DEPTH_TEST)); - - if (selection.is_from_single_instance()) - render_points(selection, false); - - m_selection_rectangle.render(m_parent); - m_c->object_clipper()->render_cut(); - m_c->supports_clipper()->render_cut(); - - glsafe(::glDisable(GL_BLEND)); -} - - -void GLGizmoSlaSupports::on_render_for_picking() -{ - const Selection& selection = m_parent.get_selection(); - //glsafe(::glEnable(GL_DEPTH_TEST)); - render_points(selection, true); -} - -void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) const -{ - size_t cache_size = m_editing_mode ? m_editing_cache.size() : m_normal_cache.size(); - - bool has_points = (cache_size != 0); - bool has_holes = (! m_c->hollowed_mesh()->get_hollowed_mesh() - && ! m_c->selection_info()->model_object()->sla_drain_holes.empty()); - - if (! has_points && ! has_holes) - return; - - GLShaderProgram* shader = picking ? nullptr : wxGetApp().get_shader("gouraud_light"); - if (shader != nullptr) - shader->start_using(); - ScopeGuard guard([shader]() { - if (shader != nullptr) - shader->stop_using(); - }); - - const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); - Geometry::Transformation transformation(vol->get_instance_transformation().get_matrix() * vol->get_volume_transformation().get_matrix()); - const Transform3d& instance_scaling_matrix_inverse = transformation.get_matrix(true, true, false, true).inverse(); - const Transform3d& instance_matrix = transformation.get_matrix(); - const float z_shift = m_c->selection_info()->get_sla_shift(); - glsafe(::glPushMatrix()); - glsafe(::glTranslated(0.0, 0.0, z_shift)); - glsafe(::glMultMatrixd(instance_matrix.data())); - - std::array render_color; - for (size_t i = 0; i < cache_size; ++i) { - const sla::SupportPoint& support_point = m_editing_mode ? m_editing_cache[i].support_point : m_normal_cache[i]; - const bool& point_selected = m_editing_mode ? m_editing_cache[i].selected : false; - - if (is_mesh_point_clipped(support_point.pos.cast())) - continue; - - // First decide about the color of the point. - if (picking) - render_color = picking_color_component(i); - else { - if (size_t(m_hover_id) == i && m_editing_mode) // ignore hover state unless editing mode is active - render_color = { 0.f, 1.f, 1.f, 1.f }; - else { // neigher hover nor picking - bool supports_new_island = m_lock_unique_islands && support_point.is_new_island; - if (m_editing_mode) { - if (point_selected) - render_color = { 1.f, 0.3f, 0.3f, 1.f}; - else - if (supports_new_island) - render_color = { 0.3f, 0.3f, 1.f, 1.f }; - else - render_color = { 0.7f, 0.7f, 0.7f, 1.f }; - } - else - render_color = { 0.5f, 0.5f, 0.5f, 1.f }; - } - } - - const_cast(&m_cone)->set_color(-1, render_color); - const_cast(&m_sphere)->set_color(-1, render_color); - if (shader && !picking) - shader->set_uniform("emission_factor", 0.5f); - - // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. - glsafe(::glPushMatrix()); - glsafe(::glTranslatef(support_point.pos(0), support_point.pos(1), support_point.pos(2))); - glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data())); - - if (vol->is_left_handed()) - glFrontFace(GL_CW); - - // Matrices set, we can render the point mark now. - // If in editing mode, we'll also render a cone pointing to the sphere. - if (m_editing_mode) { - // in case the normal is not yet cached, find and cache it - if (m_editing_cache[i].normal == Vec3f::Zero()) - m_c->raycaster()->raycaster()->get_closest_point(m_editing_cache[i].support_point.pos, &m_editing_cache[i].normal); - - Eigen::Quaterniond q; - q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * m_editing_cache[i].normal.cast()); - Eigen::AngleAxisd aa(q); - glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2))); - - const double cone_radius = 0.25; // mm - const double cone_height = 0.75; - glsafe(::glPushMatrix()); - glsafe(::glTranslatef(0.f, 0.f, cone_height + support_point.head_front_radius * RenderPointScale)); - glsafe(::glPushMatrix()); - glsafe(::glRotated(180., 1., 0., 0.)); - glsafe(::glScaled(cone_radius, cone_radius, cone_height)); - m_cone.render(); - glsafe(::glPopMatrix()); - glsafe(::glTranslatef(0.f, 0.f, cone_height)); - glsafe(::glPopMatrix()); - } - - glsafe(::glPushMatrix()); - double radius = (double)support_point.head_front_radius * RenderPointScale; - glsafe(::glScaled(radius, radius, radius)); - m_sphere.render(); - glsafe(::glPopMatrix()); - - if (vol->is_left_handed()) - glFrontFace(GL_CCW); - - glsafe(::glPopMatrix()); - } - - // Now render the drain holes: - if (has_holes && ! picking) { - render_color[0] = 0.7f; - render_color[1] = 0.7f; - render_color[2] = 0.7f; - render_color[3] = 0.7f; - const_cast(&m_cylinder)->set_color(-1, render_color); - if (shader) - shader->set_uniform("emission_factor", 0.5f); - for (const sla::DrainHole& drain_hole : m_c->selection_info()->model_object()->sla_drain_holes) { - if (is_mesh_point_clipped(drain_hole.pos.cast())) - continue; - - // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. - glsafe(::glPushMatrix()); - glsafe(::glTranslatef(drain_hole.pos(0), drain_hole.pos(1), drain_hole.pos(2))); - glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data())); - - if (vol->is_left_handed()) - glFrontFace(GL_CW); - - // Matrices set, we can render the point mark now. - - Eigen::Quaterniond q; - q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * (-drain_hole.normal).cast()); - Eigen::AngleAxisd aa(q); - glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2))); - glsafe(::glPushMatrix()); - glsafe(::glTranslated(0., 0., -drain_hole.height)); - glsafe(::glScaled(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength)); - m_cylinder.render(); - glsafe(::glPopMatrix()); - - if (vol->is_left_handed()) - glFrontFace(GL_CCW); - glsafe(::glPopMatrix()); - } - } - - glsafe(::glPopMatrix()); -} - - - -bool GLGizmoSlaSupports::is_mesh_point_clipped(const Vec3d& point) const -{ - if (m_c->object_clipper()->get_position() == 0.) - return false; - - auto sel_info = m_c->selection_info(); - int active_inst = m_c->selection_info()->get_active_instance(); - const ModelInstance* mi = sel_info->model_object()->instances[active_inst]; - const Transform3d& trafo = mi->get_transformation().get_matrix() * sel_info->model_object()->volumes.front()->get_matrix(); - - Vec3d transformed_point = trafo * point; - transformed_point(2) += sel_info->get_sla_shift(); - return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point); -} - - - -// Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal -// Return false if no intersection was found, true otherwise. -bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pair& pos_and_normal) -{ - if (! m_c->raycaster()->raycaster()) - return false; - - const Camera& camera = wxGetApp().plater()->get_camera(); - const Selection& selection = m_parent.get_selection(); - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); - Geometry::Transformation trafo = volume->get_instance_transformation() * volume->get_volume_transformation(); - trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift())); - - double clp_dist = m_c->object_clipper()->get_position(); - const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane(); - - // The raycaster query - Vec3f hit; - Vec3f normal; - if (m_c->raycaster()->raycaster()->unproject_on_mesh( - mouse_pos, - trafo.get_matrix(), - camera, - hit, - normal, - clp_dist != 0. ? clp : nullptr)) - { - // Check whether the hit is in a hole - bool in_hole = false; - // In case the hollowed and drilled mesh is available, we can allow - // placing points in holes, because they should never end up - // on surface that's been drilled away. - if (! m_c->hollowed_mesh()->get_hollowed_mesh()) { - sla::DrainHoles drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; - for (const sla::DrainHole& hole : drain_holes) { - if (hole.is_inside(hit)) { - in_hole = true; - break; - } - } - } - if (! in_hole) { - // Return both the point and the facet normal. - pos_and_normal = std::make_pair(hit, normal); - return true; - } - } - - return false; -} - -// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. -// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is -// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo -// concludes that the event was not intended for it, it should return false. -bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) -{ - ModelObject* mo = m_c->selection_info()->model_object(); - int active_inst = m_c->selection_info()->get_active_instance(); - - if (m_editing_mode) { - - // left down with shift - show the selection rectangle: - if (action == SLAGizmoEventType::LeftDown && (shift_down || alt_down || control_down)) { - if (m_hover_id == -1) { - if (shift_down || alt_down) { - m_selection_rectangle.start_dragging(mouse_position, shift_down ? GLSelectionRectangle::Select : GLSelectionRectangle::Deselect); - } - } - else { - if (m_editing_cache[m_hover_id].selected) - unselect_point(m_hover_id); - else { - if (!alt_down) - select_point(m_hover_id); - } - } - - return true; - } - - // left down without selection rectangle - place point on the mesh: - if (action == SLAGizmoEventType::LeftDown && !m_selection_rectangle.is_dragging() && !shift_down) { - // If any point is in hover state, this should initiate its move - return control back to GLCanvas: - if (m_hover_id != -1) - return false; - - // If there is some selection, don't add new point and deselect everything instead. - if (m_selection_empty) { - std::pair pos_and_normal; - if (unproject_on_mesh(mouse_position, pos_and_normal)) { // we got an intersection - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add support point")); - m_editing_cache.emplace_back(sla::SupportPoint(pos_and_normal.first, m_new_point_head_diameter/2.f, false), false, pos_and_normal.second); - m_parent.set_as_dirty(); - m_wait_for_up_event = true; - } - else - return false; - } - else - select_point(NoPoints); - - return true; - } - - // left up with selection rectangle - select points inside the rectangle: - if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp || action == SLAGizmoEventType::AltUp) && m_selection_rectangle.is_dragging()) { - // Is this a selection or deselection rectangle? - GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state(); - - // First collect positions of all the points in world coordinates. - Geometry::Transformation trafo = mo->instances[active_inst]->get_transformation(); - trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift())); - std::vector points; - for (unsigned int i=0; i()); - - // Now ask the rectangle which of the points are inside. - std::vector points_inside; - std::vector points_idxs = m_selection_rectangle.stop_dragging(m_parent, points); - for (size_t idx : points_idxs) - points_inside.push_back(points[idx].cast()); - - // Only select/deselect points that are actually visible. We want to check not only - // the point itself, but also the center of base of its cone, so the points don't hide - // under every miniature irregularity on the model. Remember the actual number and - // append the cone bases. - size_t orig_pts_num = points_inside.size(); - for (size_t idx : points_idxs) - points_inside.emplace_back((trafo.get_matrix().cast() * (m_editing_cache[idx].support_point.pos + m_editing_cache[idx].normal)).cast()); - - for (size_t idx : m_c->raycaster()->raycaster()->get_unobscured_idxs( - trafo, wxGetApp().plater()->get_camera(), points_inside, - m_c->object_clipper()->get_clipping_plane())) - { - if (idx >= orig_pts_num) // this is a cone-base, get index of point it belongs to - idx -= orig_pts_num; - if (rectangle_status == GLSelectionRectangle::Deselect) - unselect_point(points_idxs[idx]); - else - select_point(points_idxs[idx]); - } - return true; - } - - // left up with no selection rectangle - if (action == SLAGizmoEventType::LeftUp) { - if (m_wait_for_up_event) { - m_wait_for_up_event = false; - return true; - } - } - - // dragging the selection rectangle: - if (action == SLAGizmoEventType::Dragging) { - if (m_wait_for_up_event) - return true; // point has been placed and the button not released yet - // this prevents GLCanvas from starting scene rotation - - if (m_selection_rectangle.is_dragging()) { - m_selection_rectangle.dragging(mouse_position); - return true; - } - - return false; - } - - if (action == SLAGizmoEventType::Delete) { - // delete key pressed - delete_selected_points(); - return true; - } - - if (action == SLAGizmoEventType::ApplyChanges) { - editing_mode_apply_changes(); - return true; - } - - if (action == SLAGizmoEventType::DiscardChanges) { - ask_about_changes_call_after([this](){ editing_mode_apply_changes(); }, - [this](){ editing_mode_discard_changes(); }); - return true; - } - - if (action == SLAGizmoEventType::RightDown) { - if (m_hover_id != -1) { - select_point(NoPoints); - select_point(m_hover_id); - delete_selected_points(); - return true; - } - return false; - } - - if (action == SLAGizmoEventType::SelectAll) { - select_point(AllPoints); - return true; - } - } - - if (!m_editing_mode) { - if (action == SLAGizmoEventType::AutomaticGeneration) { - auto_generate(); - return true; - } - - if (action == SLAGizmoEventType::ManualEditing) { - switch_to_editing_mode(); - return true; - } - } - - if (action == SLAGizmoEventType::MouseWheelUp && control_down) { - double pos = m_c->object_clipper()->get_position(); - pos = std::min(1., pos + 0.01); - m_c->object_clipper()->set_position(pos, true); - return true; - } - - if (action == SLAGizmoEventType::MouseWheelDown && control_down) { - double pos = m_c->object_clipper()->get_position(); - pos = std::max(0., pos - 0.01); - m_c->object_clipper()->set_position(pos, true); - return true; - } - - if (action == SLAGizmoEventType::ResetClippingPlane) { - m_c->object_clipper()->set_position(-1., false); - return true; - } - - return false; -} - -void GLGizmoSlaSupports::delete_selected_points(bool force) -{ - if (! m_editing_mode) { - std::cout << "DEBUGGING: delete_selected_points called out of editing mode!" << std::endl; - std::abort(); - } - - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Delete support point")); - - for (unsigned int idx=0; idx pos_and_normal; - if (! unproject_on_mesh(data.mouse_pos.cast(), pos_and_normal)) - return; - m_editing_cache[m_hover_id].support_point.pos = pos_and_normal.first; - m_editing_cache[m_hover_id].support_point.is_new_island = false; - m_editing_cache[m_hover_id].normal = pos_and_normal.second; - } - } -} - -std::vector GLGizmoSlaSupports::get_config_options(const std::vector& keys) const -{ - std::vector out; - const ModelObject* mo = m_c->selection_info()->model_object(); - - if (! mo) - return out; - - const DynamicPrintConfig& object_cfg = mo->config.get(); - const DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; - std::unique_ptr default_cfg = nullptr; - - for (const std::string& key : keys) { - if (object_cfg.has(key)) - out.push_back(object_cfg.option(key)); - else - if (print_cfg.has(key)) - out.push_back(print_cfg.option(key)); - else { // we must get it from defaults - if (default_cfg == nullptr) - default_cfg.reset(DynamicPrintConfig::new_from_defaults_keys(keys)); - out.push_back(default_cfg->option(key)); - } - } - - return out; -} - - - -/* -void GLGizmoSlaSupports::find_intersecting_facets(const igl::AABB* aabb, const Vec3f& normal, double offset, std::vector& idxs) const -{ - if (aabb->is_leaf()) { // this is a facet - // corner.dot(normal) - offset - idxs.push_back(aabb->m_primitive); - } - else { // not a leaf - using CornerType = Eigen::AlignedBox::CornerType; - bool sign = std::signbit(offset - normal.dot(aabb->m_box.corner(CornerType(0)))); - for (unsigned int i=1; i<8; ++i) - if (std::signbit(offset - normal.dot(aabb->m_box.corner(CornerType(i)))) != sign) { - find_intersecting_facets(aabb->m_left, normal, offset, idxs); - find_intersecting_facets(aabb->m_right, normal, offset, idxs); - } - } -} - - - -void GLGizmoSlaSupports::make_line_segments() const -{ - TriangleMeshSlicer tms(&m_c->m_model_object->volumes.front()->mesh); - Vec3f normal(0.f, 1.f, 1.f); - double d = 0.; - - std::vector lines; - find_intersections(&m_AABB, normal, d, lines); - ExPolygons expolys; - tms.make_expolygons_simple(lines, &expolys); - - SVG svg("slice_loops.svg", get_extents(expolys)); - svg.draw(expolys); - //for (const IntersectionLine &l : lines[i]) - // svg.draw(l, "red", 0); - //svg.draw_outline(expolygons, "black", "blue", 0); - svg.Close(); -} -*/ - - -void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_limit) -{ - static float last_y = 0.0f; - static float last_h = 0.0f; - - ModelObject* mo = m_c->selection_info()->model_object(); - - if (! mo) - return; - - bool first_run = true; // This is a hack to redraw the button when all points are removed, - // so it is not delayed until the background process finishes. -RENDER_AGAIN: - //m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); - //const ImVec2 window_size(m_imgui->scaled(18.f, 16.f)); - //ImGui::SetNextWindowPos(ImVec2(x, y - std::max(0.f, y+window_size.y-bottom_limit) )); - //ImGui::SetNextWindowSize(ImVec2(window_size)); - - m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); - - // adjust window position to avoid overlap the view toolbar - float win_h = ImGui::GetWindowHeight(); - y = std::min(y, bottom_limit - win_h); - ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always); - if ((last_h != win_h) || (last_y != y)) - { - // ask canvas for another frame to render the window in the correct position - m_imgui->set_requires_extra_frame(); - if (last_h != win_h) - last_h = win_h; - if (last_y != y) - last_y = y; - } - - // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: - - const float settings_sliders_left = std::max(m_imgui->calc_text_size(m_desc.at("minimal_distance")).x, m_imgui->calc_text_size(m_desc.at("points_density")).x) + m_imgui->scaled(1.f); - const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); - const float diameter_slider_left = m_imgui->calc_text_size(m_desc.at("head_diameter")).x + m_imgui->scaled(1.f); - const float minimal_slider_width = m_imgui->scaled(4.f); - const float buttons_width_approx = m_imgui->calc_text_size(m_desc.at("apply_changes")).x + m_imgui->calc_text_size(m_desc.at("discard_changes")).x + m_imgui->scaled(1.5f); - const float lock_supports_width_approx = m_imgui->calc_text_size(m_desc.at("lock_supports")).x + m_imgui->scaled(2.f); - - float window_width = minimal_slider_width + std::max(std::max(settings_sliders_left, clipping_slider_left), diameter_slider_left); - window_width = std::max(std::max(window_width, buttons_width_approx), lock_supports_width_approx); - - bool force_refresh = false; - bool remove_selected = false; - bool remove_all = false; - - if (m_editing_mode) { - - float diameter_upper_cap = static_cast(wxGetApp().preset_bundle->sla_prints.get_edited_preset().config.option("support_pillar_diameter"))->value; - if (m_new_point_head_diameter > diameter_upper_cap) - m_new_point_head_diameter = diameter_upper_cap; - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("head_diameter")); - ImGui::SameLine(diameter_slider_left); - ImGui::PushItemWidth(window_width - diameter_slider_left); - - // Following is a nasty way to: - // - save the initial value of the slider before one starts messing with it - // - keep updating the head radius during sliding so it is continuosly refreshed in 3D scene - // - take correct undo/redo snapshot after the user is done with moving the slider - float initial_value = m_new_point_head_diameter; - m_imgui->slider_float("##head_diameter", &m_new_point_head_diameter, 0.1f, diameter_upper_cap, "%.1f"); - if (m_imgui->get_last_slider_status().clicked) { - if (m_old_point_head_diameter == 0.f) - m_old_point_head_diameter = initial_value; - } - if (m_imgui->get_last_slider_status().edited) { - for (auto& cache_entry : m_editing_cache) - if (cache_entry.selected) - cache_entry.support_point.head_front_radius = m_new_point_head_diameter / 2.f; - } - if (m_imgui->get_last_slider_status().deactivated_after_edit) { - // momentarily restore the old value to take snapshot - for (auto& cache_entry : m_editing_cache) - if (cache_entry.selected) - cache_entry.support_point.head_front_radius = m_old_point_head_diameter / 2.f; - float backup = m_new_point_head_diameter; - m_new_point_head_diameter = m_old_point_head_diameter; - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Change point head diameter")); - m_new_point_head_diameter = backup; - for (auto& cache_entry : m_editing_cache) - if (cache_entry.selected) - cache_entry.support_point.head_front_radius = m_new_point_head_diameter / 2.f; - m_old_point_head_diameter = 0.f; - } - - bool changed = m_lock_unique_islands; - m_imgui->checkbox(m_desc.at("lock_supports"), m_lock_unique_islands); - force_refresh |= changed != m_lock_unique_islands; - - m_imgui->disabled_begin(m_selection_empty); - remove_selected = m_imgui->button(m_desc.at("remove_selected")); - m_imgui->disabled_end(); - - m_imgui->disabled_begin(m_editing_cache.empty()); - remove_all = m_imgui->button(m_desc.at("remove_all")); - m_imgui->disabled_end(); - - m_imgui->text(" "); // vertical gap - - if (m_imgui->button(m_desc.at("apply_changes"))) { - editing_mode_apply_changes(); - force_refresh = true; - } - ImGui::SameLine(); - bool discard_changes = m_imgui->button(m_desc.at("discard_changes")); - if (discard_changes) { - editing_mode_discard_changes(); - force_refresh = true; - } - } - else { // not in editing mode: - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("minimal_distance")); - ImGui::SameLine(settings_sliders_left); - ImGui::PushItemWidth(window_width - settings_sliders_left); - - std::vector opts = get_config_options({"support_points_density_relative", "support_points_minimal_distance"}); - float density = static_cast(opts[0])->value; - float minimal_point_distance = static_cast(opts[1])->value; - - m_imgui->slider_float("##minimal_point_distance", &minimal_point_distance, 0.f, 20.f, "%.f mm"); - bool slider_clicked = m_imgui->get_last_slider_status().clicked; // someone clicked the slider - bool slider_edited = m_imgui->get_last_slider_status().edited; // someone is dragging the slider - bool slider_released = m_imgui->get_last_slider_status().deactivated_after_edit; // someone has just released the slider - - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("points_density")); - ImGui::SameLine(settings_sliders_left); - - m_imgui->slider_float("##points_density", &density, 0.f, 200.f, "%.f %%"); - slider_clicked |= m_imgui->get_last_slider_status().clicked; - slider_edited |= m_imgui->get_last_slider_status().edited; - slider_released |= m_imgui->get_last_slider_status().deactivated_after_edit; - - if (slider_clicked) { // stash the values of the settings so we know what to revert to after undo - m_minimal_point_distance_stash = minimal_point_distance; - m_density_stash = density; - } - if (slider_edited) { - mo->config.set("support_points_minimal_distance", minimal_point_distance); - mo->config.set("support_points_density_relative", (int)density); - } - if (slider_released) { - mo->config.set("support_points_minimal_distance", m_minimal_point_distance_stash); - mo->config.set("support_points_density_relative", (int)m_density_stash); - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Support parameter change")); - mo->config.set("support_points_minimal_distance", minimal_point_distance); - mo->config.set("support_points_density_relative", (int)density); - wxGetApp().obj_list()->update_and_show_object_settings_item(); - } - - bool generate = m_imgui->button(m_desc.at("auto_generate")); - - if (generate) - auto_generate(); - - ImGui::Separator(); - if (m_imgui->button(m_desc.at("manual_editing"))) - switch_to_editing_mode(); - - m_imgui->disabled_begin(m_normal_cache.empty()); - remove_all = m_imgui->button(m_desc.at("remove_all")); - m_imgui->disabled_end(); - - // m_imgui->text(""); - // m_imgui->text(m_c->m_model_object->sla_points_status == sla::PointsStatus::NoPoints ? _(L("No points (will be autogenerated)")) : - // (m_c->m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated ? _(L("Autogenerated points (no modifications)")) : - // (m_c->m_model_object->sla_points_status == sla::PointsStatus::UserModified ? _(L("User-modified points")) : - // (m_c->m_model_object->sla_points_status == sla::PointsStatus::Generating ? _(L("Generation in progress...")) : "UNKNOWN STATUS")))); - } - - - // Following is rendered in both editing and non-editing mode: - ImGui::Separator(); - if (m_c->object_clipper()->get_position() == 0.f) { - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("clipping_of_view")); - } - else { - if (m_imgui->button(m_desc.at("reset_direction"))) { - wxGetApp().CallAfter([this](){ - m_c->object_clipper()->set_position(-1., false); - }); - } - } - - ImGui::SameLine(clipping_slider_left); - ImGui::PushItemWidth(window_width - clipping_slider_left); - float clp_dist = m_c->object_clipper()->get_position(); - if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f")) - m_c->object_clipper()->set_position(clp_dist, true); - - - if (m_imgui->button("?")) { - wxGetApp().CallAfter([]() { - SlaGizmoHelpDialog help_dlg; - help_dlg.ShowModal(); - }); - } - - m_imgui->end(); - - if (remove_selected || remove_all) { - force_refresh = false; - m_parent.set_as_dirty(); - bool was_in_editing = m_editing_mode; - if (! was_in_editing) - switch_to_editing_mode(); - if (remove_all) { - select_point(AllPoints); - delete_selected_points(true); // true - delete regardless of locked status - } - if (remove_selected) - delete_selected_points(false); // leave locked points - if (! was_in_editing) - editing_mode_apply_changes(); - - if (first_run) { - first_run = false; - goto RENDER_AGAIN; - } - } - - if (force_refresh) - m_parent.set_as_dirty(); -} - -bool GLGizmoSlaSupports::on_is_activable() const -{ - const Selection& selection = m_parent.get_selection(); - - if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA - || !selection.is_from_single_instance()) - return false; - - // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside. - const Selection::IndicesList& list = selection.get_volume_idxs(); - for (const auto& idx : list) - if (selection.get_volume(idx)->is_outside && selection.get_volume(idx)->composite_id.volume_id >= 0) - return false; - - return true; -} - -bool GLGizmoSlaSupports::on_is_selectable() const -{ - return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA); -} - -std::string GLGizmoSlaSupports::on_get_name() const -{ - return _u8L("SLA Support Points"); -} - -CommonGizmosDataID GLGizmoSlaSupports::on_get_requirements() const -{ - return CommonGizmosDataID( - int(CommonGizmosDataID::SelectionInfo) - | int(CommonGizmosDataID::InstancesHider) - | int(CommonGizmosDataID::Raycaster) - | int(CommonGizmosDataID::HollowedMesh) - | int(CommonGizmosDataID::ObjectClipper) - | int(CommonGizmosDataID::SupportsClipper)); -} - - - -void GLGizmoSlaSupports::ask_about_changes_call_after(std::function on_yes, std::function on_no) -{ - wxGetApp().CallAfter([on_yes, on_no]() { - // Following is called through CallAfter, because otherwise there was a problem - // on OSX with the wxMessageDialog being shown several times when clicked into. - MessageDialog dlg(GUI::wxGetApp().mainframe, _L("Do you want to save your manually " - "edited support points?") + "\n",_L("Save support points?"), wxICON_QUESTION | wxYES | wxNO | wxCANCEL ); - int ret = dlg.ShowModal(); - if (ret == wxID_YES) - on_yes(); - else if (ret == wxID_NO) - on_no(); - }); -} - - -void GLGizmoSlaSupports::on_set_state() -{ - if (m_state == m_old_state) - return; - - if (m_state == On && m_old_state != On) { // the gizmo was just turned on - // Set default head diameter from config. - const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; - m_new_point_head_diameter = static_cast(cfg.option("support_head_front_diameter"))->value; - } - if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off - bool will_ask = m_editing_mode && unsaved_changes() && on_is_activable(); - if (will_ask) { - ask_about_changes_call_after([this](){ editing_mode_apply_changes(); }, - [this](){ editing_mode_discard_changes(); }); - // refuse to be turned off so the gizmo is active when the CallAfter is executed - m_state = m_old_state; - } - else { - // we are actually shutting down - disable_editing_mode(); // so it is not active next time the gizmo opens - m_old_mo_id = -1; - } - } - m_old_state = m_state; -} - - - -void GLGizmoSlaSupports::on_start_dragging() -{ - if (m_hover_id != -1) { - select_point(NoPoints); - select_point(m_hover_id); - m_point_before_drag = m_editing_cache[m_hover_id]; - } - else - m_point_before_drag = CacheEntry(); -} - - -void GLGizmoSlaSupports::on_stop_dragging() -{ - if (m_hover_id != -1) { - CacheEntry backup = m_editing_cache[m_hover_id]; - - if (m_point_before_drag.support_point.pos != Vec3f::Zero() // some point was touched - && backup.support_point.pos != m_point_before_drag.support_point.pos) // and it was moved, not just selected - { - m_editing_cache[m_hover_id] = m_point_before_drag; - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move support point")); - m_editing_cache[m_hover_id] = backup; - } - } - m_point_before_drag = CacheEntry(); -} - - - -void GLGizmoSlaSupports::on_load(cereal::BinaryInputArchive& ar) -{ - ar(m_new_point_head_diameter, - m_normal_cache, - m_editing_cache, - m_selection_empty - ); -} - - - -void GLGizmoSlaSupports::on_save(cereal::BinaryOutputArchive& ar) const -{ - ar(m_new_point_head_diameter, - m_normal_cache, - m_editing_cache, - m_selection_empty - ); -} - - - -void GLGizmoSlaSupports::select_point(int i) -{ - if (! m_editing_mode) { - std::cout << "DEBUGGING: select_point called when out of editing mode!" << std::endl; - std::abort(); - } - - if (i == AllPoints || i == NoPoints) { - for (auto& point_and_selection : m_editing_cache) - point_and_selection.selected = ( i == AllPoints ); - m_selection_empty = (i == NoPoints); - - if (i == AllPoints && ! m_editing_cache.empty()) - m_new_point_head_diameter = m_editing_cache[0].support_point.head_front_radius * 2.f; - } - else { - m_editing_cache[i].selected = true; - m_selection_empty = false; - m_new_point_head_diameter = m_editing_cache[i].support_point.head_front_radius * 2.f; - } -} - - -void GLGizmoSlaSupports::unselect_point(int i) -{ - if (! m_editing_mode) { - std::cout << "DEBUGGING: unselect_point called when out of editing mode!" << std::endl; - std::abort(); - } - - m_editing_cache[i].selected = false; - m_selection_empty = true; - for (const CacheEntry& ce : m_editing_cache) { - if (ce.selected) { - m_selection_empty = false; - break; - } - } -} - - - - -void GLGizmoSlaSupports::editing_mode_discard_changes() -{ - if (! m_editing_mode) { - std::cout << "DEBUGGING: editing_mode_discard_changes called when out of editing mode!" << std::endl; - std::abort(); - } - select_point(NoPoints); - disable_editing_mode(); -} - - - -void GLGizmoSlaSupports::editing_mode_apply_changes() -{ - // If there are no changes, don't touch the front-end. The data in the cache could have been - // taken from the backend and copying them to ModelObject would needlessly invalidate them. - disable_editing_mode(); // this leaves the editing mode undo/redo stack and must be done before the snapshot is taken - - if (unsaved_changes()) { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Support points edit")); - - m_normal_cache.clear(); - for (const CacheEntry& ce : m_editing_cache) - m_normal_cache.push_back(ce.support_point); - - ModelObject* mo = m_c->selection_info()->model_object(); - mo->sla_points_status = sla::PointsStatus::UserModified; - mo->sla_support_points.clear(); - mo->sla_support_points = m_normal_cache; - - reslice_SLA_supports(); - } -} - - - -void GLGizmoSlaSupports::reload_cache() -{ - const ModelObject* mo = m_c->selection_info()->model_object(); - m_normal_cache.clear(); - if (mo->sla_points_status == sla::PointsStatus::AutoGenerated || mo->sla_points_status == sla::PointsStatus::Generating) - get_data_from_backend(); - else - for (const sla::SupportPoint& point : mo->sla_support_points) - m_normal_cache.emplace_back(point); -} - - -bool GLGizmoSlaSupports::has_backend_supports() const -{ - const ModelObject* mo = m_c->selection_info()->model_object(); - if (! mo) - return false; - - // find SlaPrintObject with this ID - for (const SLAPrintObject* po : m_parent.sla_print()->objects()) { - if (po->model_object()->id() == mo->id()) - return po->is_step_done(slaposSupportPoints); - } - return false; -} - -void GLGizmoSlaSupports::reslice_SLA_supports(bool postpone_error_messages) const -{ - wxGetApp().CallAfter([this, postpone_error_messages]() { - wxGetApp().plater()->reslice_SLA_supports( - *m_c->selection_info()->model_object(), postpone_error_messages); - }); -} - -void GLGizmoSlaSupports::get_data_from_backend() -{ - if (! has_backend_supports()) - return; - ModelObject* mo = m_c->selection_info()->model_object(); - - // find the respective SLAPrintObject, we need a pointer to it - for (const SLAPrintObject* po : m_parent.sla_print()->objects()) { - if (po->model_object()->id() == mo->id()) { - m_normal_cache.clear(); - const std::vector& points = po->get_support_points(); - auto mat = (po->trafo() * po->model_object()->volumes.front()->get_transformation().get_matrix()).inverse().cast(); - for (unsigned int i=0; isla_points_status = sla::PointsStatus::AutoGenerated; - break; - } - } - - // We don't copy the data into ModelObject, as this would stop the background processing. -} - - - -void GLGizmoSlaSupports::auto_generate() -{ - //wxMessageDialog dlg(GUI::wxGetApp().plater(), - MessageDialog dlg(GUI::wxGetApp().plater(), - _L("Autogeneration will erase all manually edited points.") + "\n\n" + - _L("Are you sure you want to do it?") + "\n", - _L("Warning"), wxICON_WARNING | wxYES | wxNO); - - ModelObject* mo = m_c->selection_info()->model_object(); - - if (mo->sla_points_status != sla::PointsStatus::UserModified || m_normal_cache.empty() || dlg.ShowModal() == wxID_YES) { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Autogenerate support points")); - wxGetApp().CallAfter([this]() { reslice_SLA_supports(); }); - mo->sla_points_status = sla::PointsStatus::Generating; - } -} - - - -void GLGizmoSlaSupports::switch_to_editing_mode() -{ - wxGetApp().plater()->enter_gizmos_stack(); - m_editing_mode = true; - m_editing_cache.clear(); - for (const sla::SupportPoint& sp : m_normal_cache) - m_editing_cache.emplace_back(sp); - select_point(NoPoints); - - m_c->instances_hider()->show_supports(false); - m_parent.set_as_dirty(); -} - - -void GLGizmoSlaSupports::disable_editing_mode() -{ - if (m_editing_mode) { - m_editing_mode = false; - wxGetApp().plater()->leave_gizmos_stack(); - m_c->instances_hider()->show_supports(true); - m_parent.set_as_dirty(); - } - wxGetApp().plater()->get_notification_manager()->close_notification_of_type(NotificationType::QuitSLAManualMode); -} - - - -bool GLGizmoSlaSupports::unsaved_changes() const -{ - if (m_editing_cache.size() != m_normal_cache.size()) - return true; - - for (size_t i=0; iSetFont(font); - - auto vsizer = new wxBoxSizer(wxVERTICAL); - auto gridsizer = new wxFlexGridSizer(2, 5, 15); - auto hsizer = new wxBoxSizer(wxHORIZONTAL); - - hsizer->AddSpacer(20); - hsizer->Add(vsizer); - hsizer->AddSpacer(20); - - vsizer->AddSpacer(20); - vsizer->Add(note_text, 1, wxALIGN_CENTRE_HORIZONTAL); - vsizer->AddSpacer(20); - vsizer->Add(gridsizer); - vsizer->AddSpacer(20); - - std::vector> shortcuts; - shortcuts.push_back(std::make_pair(_L("Left click"), _L("Add point"))); - shortcuts.push_back(std::make_pair(_L("Right click"), _L("Remove point"))); - shortcuts.push_back(std::make_pair(_L("Drag"), _L("Move point"))); - shortcuts.push_back(std::make_pair(ctrl+_L("Left click"), _L("Add point to selection"))); - shortcuts.push_back(std::make_pair(alt+_L("Left click"), _L("Remove point from selection"))); - shortcuts.push_back(std::make_pair(wxString("Shift+")+_L("Drag"), _L("Select by rectangle"))); - shortcuts.push_back(std::make_pair(alt+_(L("Drag")), _L("Deselect by rectangle"))); - shortcuts.push_back(std::make_pair(ctrl+"A", _L("Select all points"))); - shortcuts.push_back(std::make_pair("Delete", _L("Remove selected points"))); - shortcuts.push_back(std::make_pair(ctrl+_L("Mouse wheel"), _L("Move clipping plane"))); - shortcuts.push_back(std::make_pair("R", _L("Reset clipping plane"))); - shortcuts.push_back(std::make_pair("Enter", _L("Apply changes"))); - shortcuts.push_back(std::make_pair("Esc", _L("Discard changes"))); - shortcuts.push_back(std::make_pair("M", _L("Switch to editing mode"))); - shortcuts.push_back(std::make_pair("A", _L("Auto-generate points"))); - - for (const auto& pair : shortcuts) { - auto shortcut = new wxStaticText(this, wxID_ANY, pair.first); - auto desc = new wxStaticText(this, wxID_ANY, pair.second); - shortcut->SetFont(bold_font); - desc->SetFont(font); - gridsizer->Add(shortcut, -1, wxALIGN_CENTRE_VERTICAL); - gridsizer->Add(desc, -1, wxALIGN_CENTRE_VERTICAL); - } - - SetSizer(hsizer); - hsizer->SetSizeHints(this); -} - - - -} // namespace GUI -} // namespace Slic3r ->>>>>>> master_250 diff --git a/src/slic3r/Utils/FixModelByWin10.cpp b/src/slic3r/Utils/FixModelByWin10.cpp index feab32bea..296c58622 100644 --- a/src/slic3r/Utils/FixModelByWin10.cpp +++ b/src/slic3r/Utils/FixModelByWin10.cpp @@ -1,454 +1,3 @@ -<<<<<<< HEAD -#ifdef HAS_WIN10SDK - -#ifndef NOMINMAX -# define NOMINMAX -#endif - -// Windows Runtime -#include -// for ComPtr -#include - -// from C:/Program Files (x86)/Windows Kits/10/Include/10.0.17134.0/ -#include -#include -#include - -#include "FixModelByWin10.hpp" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "libslic3r/Model.hpp" -#include "libslic3r/Print.hpp" -#include "libslic3r/PresetBundle.hpp" -#include "libslic3r/Format/3mf.hpp" -#include "../GUI/GUI.hpp" -#include "../GUI/I18N.hpp" -#include "../GUI/MsgDialog.hpp" - -#include -#include - -extern "C"{ - // from rapi.h - typedef HRESULT (__stdcall* FunctionRoInitialize)(int); - typedef HRESULT (__stdcall* FunctionRoUninitialize)(); - typedef HRESULT (__stdcall* FunctionRoActivateInstance)(HSTRING activatableClassId, IInspectable **instance); - typedef HRESULT (__stdcall* FunctionRoGetActivationFactory)(HSTRING activatableClassId, REFIID iid, void **factory); - // from winstring.h - typedef HRESULT (__stdcall* FunctionWindowsCreateString)(LPCWSTR sourceString, UINT32 length, HSTRING *string); - typedef HRESULT (__stdcall* FunctionWindowsDelteString)(HSTRING string); -} - -namespace Slic3r { - -HMODULE s_hRuntimeObjectLibrary = nullptr; -FunctionRoInitialize s_RoInitialize = nullptr; -FunctionRoUninitialize s_RoUninitialize = nullptr; -FunctionRoActivateInstance s_RoActivateInstance = nullptr; -FunctionRoGetActivationFactory s_RoGetActivationFactory = nullptr; -FunctionWindowsCreateString s_WindowsCreateString = nullptr; -FunctionWindowsDelteString s_WindowsDeleteString = nullptr; - -bool winrt_load_runtime_object_library() -{ - if (s_hRuntimeObjectLibrary == nullptr) - s_hRuntimeObjectLibrary = LoadLibrary(L"ComBase.dll"); - if (s_hRuntimeObjectLibrary != nullptr) { - s_RoInitialize = (FunctionRoInitialize) GetProcAddress(s_hRuntimeObjectLibrary, "RoInitialize"); - s_RoUninitialize = (FunctionRoUninitialize) GetProcAddress(s_hRuntimeObjectLibrary, "RoUninitialize"); - s_RoActivateInstance = (FunctionRoActivateInstance) GetProcAddress(s_hRuntimeObjectLibrary, "RoActivateInstance"); - s_RoGetActivationFactory = (FunctionRoGetActivationFactory) GetProcAddress(s_hRuntimeObjectLibrary, "RoGetActivationFactory"); - s_WindowsCreateString = (FunctionWindowsCreateString) GetProcAddress(s_hRuntimeObjectLibrary, "WindowsCreateString"); - s_WindowsDeleteString = (FunctionWindowsDelteString) GetProcAddress(s_hRuntimeObjectLibrary, "WindowsDeleteString"); - } - return s_RoInitialize && s_RoUninitialize && s_RoActivateInstance && s_WindowsCreateString && s_WindowsDeleteString; -} - -static HRESULT winrt_activate_instance(const std::wstring &class_name, IInspectable **pinst) -{ - HSTRING hClassName; - HRESULT hr = (*s_WindowsCreateString)(class_name.c_str(), class_name.size(), &hClassName); - if (S_OK != hr) - return hr; - hr = (*s_RoActivateInstance)(hClassName, pinst); - (*s_WindowsDeleteString)(hClassName); - return hr; -} - -template -static HRESULT winrt_activate_instance(const std::wstring &class_name, TYPE **pinst) -{ - IInspectable *pinspectable = nullptr; - HRESULT hr = winrt_activate_instance(class_name, &pinspectable); - if (S_OK != hr) - return hr; - hr = pinspectable->QueryInterface(__uuidof(TYPE), (void**)pinst); - pinspectable->Release(); - return hr; -} - -static HRESULT winrt_get_activation_factory(const std::wstring &class_name, REFIID iid, void **pinst) -{ - HSTRING hClassName; - HRESULT hr = (*s_WindowsCreateString)(class_name.c_str(), class_name.size(), &hClassName); - if (S_OK != hr) - return hr; - hr = (*s_RoGetActivationFactory)(hClassName, iid, pinst); - (*s_WindowsDeleteString)(hClassName); - return hr; -} - -template -static HRESULT winrt_get_activation_factory(const std::wstring &class_name, TYPE **pinst) -{ - return winrt_get_activation_factory(class_name, __uuidof(TYPE), reinterpret_cast(pinst)); -} - -// To be called often to test whether to cancel the operation. -typedef std::function ThrowOnCancelFn; - -template -static AsyncStatus winrt_async_await(const Microsoft::WRL::ComPtr &asyncAction, ThrowOnCancelFn throw_on_cancel, int blocking_tick_ms = 100) -{ - Microsoft::WRL::ComPtr asyncInfo; - asyncAction.As(&asyncInfo); - AsyncStatus status; - // Ugly blocking loop until the RepairAsync call finishes. -//FIXME replace with a callback. -// https://social.msdn.microsoft.com/Forums/en-US/a5038fb4-b7b7-4504-969d-c102faa389fb/trying-to-block-an-async-operation-and-wait-for-a-particular-time?forum=vclanguage - for (;;) { - asyncInfo->get_Status(&status); - if (status != AsyncStatus::Started) - return status; - throw_on_cancel(); - ::Sleep(blocking_tick_ms); - } -} - -static HRESULT winrt_open_file_stream( - const std::wstring &path, - ABI::Windows::Storage::FileAccessMode mode, - ABI::Windows::Storage::Streams::IRandomAccessStream **fileStream, - ThrowOnCancelFn throw_on_cancel) -{ - // Get the file factory. - Microsoft::WRL::ComPtr fileFactory; - HRESULT hr = winrt_get_activation_factory(L"Windows.Storage.StorageFile", fileFactory.GetAddressOf()); - if (FAILED(hr)) return hr; - - // Open the file asynchronously. - HSTRING hstr_path; - hr = (*s_WindowsCreateString)(path.c_str(), path.size(), &hstr_path); - if (FAILED(hr)) return hr; - Microsoft::WRL::ComPtr> fileOpenAsync; - hr = fileFactory->GetFileFromPathAsync(hstr_path, fileOpenAsync.GetAddressOf()); - if (FAILED(hr)) return hr; - (*s_WindowsDeleteString)(hstr_path); - - // Wait until the file gets open, get the actual file. - AsyncStatus status = winrt_async_await(fileOpenAsync, throw_on_cancel); - Microsoft::WRL::ComPtr storageFile; - if (status == AsyncStatus::Completed) { - hr = fileOpenAsync->GetResults(storageFile.GetAddressOf()); - } else { - Microsoft::WRL::ComPtr asyncInfo; - hr = fileOpenAsync.As(&asyncInfo); - if (FAILED(hr)) return hr; - HRESULT err; - hr = asyncInfo->get_ErrorCode(&err); - return FAILED(hr) ? hr : err; - } - - Microsoft::WRL::ComPtr> fileStreamAsync; - hr = storageFile->OpenAsync(mode, fileStreamAsync.GetAddressOf()); - if (FAILED(hr)) return hr; - - status = winrt_async_await(fileStreamAsync, throw_on_cancel); - if (status == AsyncStatus::Completed) { - hr = fileStreamAsync->GetResults(fileStream); - } else { - Microsoft::WRL::ComPtr asyncInfo; - hr = fileStreamAsync.As(&asyncInfo); - if (FAILED(hr)) return hr; - HRESULT err; - hr = asyncInfo->get_ErrorCode(&err); - if (!FAILED(hr)) - hr = err; - } - return hr; -} - -bool is_windows10() -{ - HKEY hKey; - LONG lRes = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_READ, &hKey); - if (lRes == ERROR_SUCCESS) { - WCHAR szBuffer[512]; - DWORD dwBufferSize = sizeof(szBuffer); - lRes = RegQueryValueExW(hKey, L"ProductName", 0, nullptr, (LPBYTE)szBuffer, &dwBufferSize); - if (lRes == ERROR_SUCCESS) - return wcsncmp(szBuffer, L"Windows 10", 10) == 0; - RegCloseKey(hKey); - } - return false; -} - -// Progress function, to be called regularly to update the progress. -typedef std::function ProgressFn; - -void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path_dst, ProgressFn on_progress, ThrowOnCancelFn throw_on_cancel) -{ - if (! is_windows10()) - throw Slic3r::RuntimeError("fix_model_by_win10_sdk called on non Windows 10 system"); - - if (! winrt_load_runtime_object_library()) - throw Slic3r::RuntimeError("Failed to initialize the WinRT library."); - - HRESULT hr = (*s_RoInitialize)(RO_INIT_MULTITHREADED); - { - on_progress(L("Exporting source model"), 20); - - Microsoft::WRL::ComPtr fileStream; - hr = winrt_open_file_stream(boost::nowide::widen(path_src), ABI::Windows::Storage::FileAccessMode::FileAccessMode_Read, fileStream.GetAddressOf(), throw_on_cancel); - - Microsoft::WRL::ComPtr printing3d3mfpackage; - hr = winrt_activate_instance(L"Windows.Graphics.Printing3D.Printing3D3MFPackage", printing3d3mfpackage.GetAddressOf()); - - Microsoft::WRL::ComPtr> modelAsync; - hr = printing3d3mfpackage->LoadModelFromPackageAsync(fileStream.Get(), modelAsync.GetAddressOf()); - - AsyncStatus status = winrt_async_await(modelAsync, throw_on_cancel); - Microsoft::WRL::ComPtr model; - if (status == AsyncStatus::Completed) - hr = modelAsync->GetResults(model.GetAddressOf()); - else - throw Slic3r::RuntimeError(L("Failed loading the input model.")); - - Microsoft::WRL::ComPtr> meshes; - hr = model->get_Meshes(meshes.GetAddressOf()); - unsigned num_meshes = 0; - hr = meshes->get_Size(&num_meshes); - - on_progress(L("Repairing model by the Netfabb service"), 40); - - Microsoft::WRL::ComPtr repairAsync; - hr = model->RepairAsync(repairAsync.GetAddressOf()); - status = winrt_async_await(repairAsync, throw_on_cancel); - if (status != AsyncStatus::Completed) - throw Slic3r::RuntimeError(L("Mesh repair failed.")); - repairAsync->GetResults(); - - on_progress(L("Loading repaired model"), 60); - - // Verify the number of meshes returned after the repair action. - meshes.Reset(); - hr = model->get_Meshes(meshes.GetAddressOf()); - hr = meshes->get_Size(&num_meshes); - - // Save model to this class' Printing3D3MFPackage. - Microsoft::WRL::ComPtr saveToPackageAsync; - hr = printing3d3mfpackage->SaveModelToPackageAsync(model.Get(), saveToPackageAsync.GetAddressOf()); - status = winrt_async_await(saveToPackageAsync, throw_on_cancel); - if (status != AsyncStatus::Completed) - throw Slic3r::RuntimeError(L("Saving mesh into the 3MF container failed.")); - hr = saveToPackageAsync->GetResults(); - - Microsoft::WRL::ComPtr> generatorStreamAsync; - hr = printing3d3mfpackage->SaveAsync(generatorStreamAsync.GetAddressOf()); - status = winrt_async_await(generatorStreamAsync, throw_on_cancel); - if (status != AsyncStatus::Completed) - throw Slic3r::RuntimeError(L("Saving mesh into the 3MF container failed.")); - Microsoft::WRL::ComPtr generatorStream; - hr = generatorStreamAsync->GetResults(generatorStream.GetAddressOf()); - - // Go to the beginning of the stream. - generatorStream->Seek(0); - Microsoft::WRL::ComPtr inputStream; - hr = generatorStream.As(&inputStream); - - // Get the buffer factory. - Microsoft::WRL::ComPtr bufferFactory; - hr = winrt_get_activation_factory(L"Windows.Storage.Streams.Buffer", bufferFactory.GetAddressOf()); - - // Open the destination file. - FILE *fout = boost::nowide::fopen(path_dst.c_str(), "wb"); - try { - Microsoft::WRL::ComPtr buffer; - byte *buffer_ptr; - bufferFactory->Create(65536 * 2048, buffer.GetAddressOf()); - { - Microsoft::WRL::ComPtr bufferByteAccess; - buffer.As(&bufferByteAccess); - hr = bufferByteAccess->Buffer(&buffer_ptr); - } - uint32_t length; - hr = buffer->get_Length(&length); - Microsoft::WRL::ComPtr> asyncRead; - for (;;) { - hr = inputStream->ReadAsync(buffer.Get(), 65536 * 2048, ABI::Windows::Storage::Streams::InputStreamOptions_ReadAhead, asyncRead.GetAddressOf()); - status = winrt_async_await(asyncRead, throw_on_cancel); - if (status != AsyncStatus::Completed) - throw Slic3r::RuntimeError(L("Saving mesh into the 3MF container failed.")); - hr = buffer->get_Length(&length); - if (length == 0) - break; - fwrite(buffer_ptr, length, 1, fout); - } - } catch (...) { - fclose(fout); - throw; - } - fclose(fout); - // Here all the COM objects will be released through the ComPtr destructors. - } - (*s_RoUninitialize)(); -} - -class RepairCanceledException : public std::exception { -public: - const char* what() const throw() { return "Model repair has been canceled"; } -}; - -// returt FALSE, if fixing was canceled -// fix_result is empty, if fixing finished successfully -// fix_result containes a message if fixing failed -bool fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx, wxProgressDialog& progress_dialog, const wxString& msg_header, std::string& fix_result) -{ - std::mutex mutex; - std::condition_variable condition; - std::unique_lock lock(mutex); - struct Progress { - std::string message; - int percent = 0; - bool updated = false; - } progress; - std::atomic canceled = false; - std::atomic finished = false; - - std::vector volumes; - if (volume_idx == -1) - volumes = model_object.volumes; - else - volumes.emplace_back(model_object.volumes[volume_idx]); - - // Executing the calculation in a background thread, so that the COM context could be created with its own threading model. - // (It seems like wxWidgets initialize the COM contex as single threaded and we need a multi-threaded context). - bool success = false; - size_t ivolume = 0; - auto on_progress = [&mutex, &condition, &ivolume, &volumes, &progress](const char *msg, unsigned prcnt) { - std::lock_guard lk(mutex); - progress.message = msg; - progress.percent = (int)floor((float(prcnt) + float(ivolume) * 100.f) / float(volumes.size())); - progress.updated = true; - condition.notify_all(); - }; - auto worker_thread = boost::thread([&model_object, &volumes, &ivolume, on_progress, &success, &canceled, &finished]() { - try { - std::vector meshes_repaired; - meshes_repaired.reserve(volumes.size()); - for (; ivolume < volumes.size(); ++ ivolume) { - on_progress(L("Exporting source model"), 0); - boost::filesystem::path path_src = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); - path_src += ".3mf"; - Model model; - ModelObject *mo = model.add_object(); - mo->add_volume(*volumes[ivolume]); - - // We are about to save a 3mf, fix it by netfabb and load the fixed 3mf back. - // store_3mf currently bakes the volume transformation into the mesh itself. - // If we then loaded the repaired 3mf and pushed the mesh into the original ModelVolume - // (which remembers the matrix the whole time), the transformation would be used twice. - // We will therefore set the volume transform on the dummy ModelVolume to identity. - mo->volumes.back()->set_transformation(Geometry::Transformation()); - - mo->add_instance(); - if (!Slic3r::store_3mf(path_src.string().c_str(), &model, nullptr, false, nullptr, false)) { - boost::filesystem::remove(path_src); - throw Slic3r::RuntimeError(L("Export of a temporary 3mf file failed")); - } - model.clear_objects(); - model.clear_materials(); - boost::filesystem::path path_dst = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); - path_dst += ".3mf"; - fix_model_by_win10_sdk(path_src.string().c_str(), path_dst.string(), on_progress, - [&canceled]() { if (canceled) throw RepairCanceledException(); }); - boost::filesystem::remove(path_src); - // PresetBundle bundle; - on_progress(L("Loading repaired model"), 80); - DynamicPrintConfig config; - ConfigSubstitutionContext config_substitutions{ ForwardCompatibilitySubstitutionRule::EnableSilent }; - bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), config, config_substitutions, &model, false); - boost::filesystem::remove(path_dst); - if (! loaded) - throw Slic3r::RuntimeError(L("Import of the repaired 3mf file failed")); - if (model.objects.size() == 0) - throw Slic3r::RuntimeError(L("Repaired 3MF file does not contain any object")); - if (model.objects.size() > 1) - throw Slic3r::RuntimeError(L("Repaired 3MF file contains more than one object")); - if (model.objects.front()->volumes.size() == 0) - throw Slic3r::RuntimeError(L("Repaired 3MF file does not contain any volume")); - if (model.objects.front()->volumes.size() > 1) - throw Slic3r::RuntimeError(L("Repaired 3MF file contains more than one volume")); - meshes_repaired.emplace_back(std::move(model.objects.front()->volumes.front()->mesh())); - } - for (size_t i = 0; i < volumes.size(); ++ i) { - volumes[i]->set_mesh(std::move(meshes_repaired[i])); - volumes[i]->calculate_convex_hull(); - volumes[i]->set_new_unique_id(); - } - model_object.invalidate_bounding_box(); - -- ivolume; - on_progress(L("Model repair finished"), 100); - success = true; - finished = true; - } catch (RepairCanceledException & /* ex */) { - canceled = true; - finished = true; - on_progress(L("Model repair canceled"), 100); - } catch (std::exception &ex) { - success = false; - finished = true; - on_progress(ex.what(), 100); - } - }); - while (! finished) { - condition.wait_for(lock, std::chrono::milliseconds(250), [&progress]{ return progress.updated; }); - // decrease progress.percent value to avoid closing of the progress dialog - if (!progress_dialog.Update(progress.percent-1, msg_header + _(progress.message))) - canceled = true; - else - progress_dialog.Fit(); - progress.updated = false; - } - - if (canceled) { - // Nothing to show. - } else if (success) { - fix_result = ""; - } else { - fix_result = progress.message; - } - worker_thread.join(); - return !canceled; -} - -} // namespace Slic3r - -#endif /* HAS_WIN10SDK */ -======= #ifdef HAS_WIN10SDK #ifndef NOMINMAX @@ -777,8 +326,9 @@ public: // fix_result containes a message if fixing failed bool fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx, wxProgressDialog& progress_dialog, const wxString& msg_header, std::string& fix_result) { - std::mutex mtx; - std::condition_variable condition; + std::mutex mutex; + std::condition_variable condition; + std::unique_lock lock(mutex); struct Progress { std::string message; int percent = 0; @@ -797,8 +347,8 @@ bool fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx, wxPro // (It seems like wxWidgets initialize the COM contex as single threaded and we need a multi-threaded context). bool success = false; size_t ivolume = 0; - auto on_progress = [&mtx, &condition, &ivolume, &volumes, &progress](const char *msg, unsigned prcnt) { - std::unique_lock lock(mtx); + auto on_progress = [&mutex, &condition, &ivolume, &volumes, &progress](const char *msg, unsigned prcnt) { + std::lock_guard lk(mutex); progress.message = msg; progress.percent = (int)floor((float(prcnt) + float(ivolume) * 100.f) / float(volumes.size())); progress.updated = true; @@ -874,7 +424,6 @@ bool fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx, wxPro } }); while (! finished) { - std::unique_lock lock(mtx); condition.wait_for(lock, std::chrono::milliseconds(250), [&progress]{ return progress.updated; }); // decrease progress.percent value to avoid closing of the progress dialog if (!progress_dialog.Update(progress.percent-1, msg_header + _(progress.message))) @@ -898,4 +447,3 @@ bool fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx, wxPro } // namespace Slic3r #endif /* HAS_WIN10SDK */ ->>>>>>> master_250 From 6c0db58628ca2c26611ca9d427c56e02dcfe5fae Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 29 Jun 2022 16:12:52 +0200 Subject: [PATCH 4/4] Fixing conflicts part 4: See previous commit. This one applies changes from master_250 to the files mentioned --- src/libslic3r/CMakeLists.txt | 4 - src/libslic3r/GCode/GCodeProcessor.cpp | 86 ++++++++++++-------- src/libslic3r/GCode/GCodeProcessor.hpp | 17 ++-- src/libslic3r/Point.hpp | 10 +++ src/slic3r/GUI/ConfigWizard.cpp | 3 +- src/slic3r/GUI/GLCanvas3D.cpp | 4 + src/slic3r/GUI/GUI_App.cpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 2 +- src/slic3r/Utils/FixModelByWin10.cpp | 10 +-- 10 files changed, 88 insertions(+), 54 deletions(-) diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index b5bc8b7a6..e637b1402 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -450,11 +450,7 @@ endif() if (APPLE) # This flag prevents the need for minimum SDK version 10.14 -<<<<<<< HEAD - # currently, PS targets v10.10 -======= # currently, PS targets v10.12 ->>>>>>> master_250 target_compile_options(libslic3r PUBLIC "-fno-aligned-allocation") endif () diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 1611b2325..a12937bc7 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -679,13 +679,30 @@ void GCodeProcessor::UsedFilaments::reset() role_cache = 0.0; filaments_per_role.clear(); + + extruder_retracted_volume.clear(); } -void GCodeProcessor::UsedFilaments::increase_caches(double extruded_volume) +void GCodeProcessor::UsedFilaments::increase_caches(double extruded_volume, unsigned char extruder_id, double parking_volume, double extra_loading_volume) { - color_change_cache += extruded_volume; - tool_change_cache += extruded_volume; - role_cache += extruded_volume; + if (extruder_id >= extruder_retracted_volume.size()) + extruder_retracted_volume.resize(extruder_id + 1, parking_volume); + + if (recent_toolchange) { + extruded_volume -= extra_loading_volume; + recent_toolchange = false; + } + + extruder_retracted_volume[extruder_id] -= extruded_volume; + + if (extruder_retracted_volume[extruder_id] < 0.) { + extruded_volume = - extruder_retracted_volume[extruder_id]; + extruder_retracted_volume[extruder_id] = 0.; + + color_change_cache += extruded_volume; + tool_change_cache += extruded_volume; + role_cache += extruded_volume; + } } void GCodeProcessor::UsedFilaments::process_color_change_cache() @@ -696,19 +713,16 @@ void GCodeProcessor::UsedFilaments::process_color_change_cache() } } -void GCodeProcessor::UsedFilaments::process_extruder_cache(GCodeProcessor* processor) -{ - size_t active_extruder_id = processor->m_extruder_id; +void GCodeProcessor::UsedFilaments::process_extruder_cache(unsigned char extruder_id) + { if (tool_change_cache != 0.0) { - if (volumes_per_extruder.find(active_extruder_id) != volumes_per_extruder.end()) - volumes_per_extruder[active_extruder_id] += tool_change_cache; - else - volumes_per_extruder[active_extruder_id] = tool_change_cache; - tool_change_cache = 0.0; - } + volumes_per_extruder[extruder_id] += tool_change_cache; + tool_change_cache = 0.0; + } + recent_toolchange = true; } -void GCodeProcessor::UsedFilaments::process_role_cache(GCodeProcessor* processor) +void GCodeProcessor::UsedFilaments::process_role_cache(const GCodeProcessor* processor) { if (role_cache != 0.0) { std::pair filament = { 0.0f, 0.0f }; @@ -728,10 +742,10 @@ void GCodeProcessor::UsedFilaments::process_role_cache(GCodeProcessor* processor } } -void GCodeProcessor::UsedFilaments::process_caches(GCodeProcessor* processor) +void GCodeProcessor::UsedFilaments::process_caches(const GCodeProcessor* processor) { process_color_change_cache(); - process_extruder_cache(processor); + process_extruder_cache(processor->m_extruder_id); process_role_cache(processor); } @@ -901,7 +915,14 @@ void GCodeProcessor::apply_config(const PrintConfig& config) m_time_processor.filament_unload_times[i] = static_cast(config.filament_unload_time.values[i]); } - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + // With MM setups like Prusa MMU2, the filaments may be expected to be parked at the beginning. + // Remember the parking position so the initial load is not included in filament estimate. + if (config.single_extruder_multi_material && extruders_count > 1 && config.wipe_tower) { + m_parking_position = float(config.parking_pos_retraction.value); + m_extra_loading_move = float(config.extra_loading_move); + } + +for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i); m_time_processor.machines[i].max_acceleration = max_acceleration; m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION; @@ -1242,6 +1263,8 @@ void GCodeProcessor::reset() m_extruder_temps[i] = 0.0f; } + m_parking_position = 0.f; + m_extra_loading_move = 0.f; m_extruded_last_z = 0.0f; m_first_layer_height = 0.0f; m_g1_line_id = 0; @@ -2550,27 +2573,26 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) if (line.has_f()) m_feedrate = m_feed_multiply.current * line.f() * MMMIN_TO_MMSEC; - // calculates movement deltas - float max_abs_delta = 0.0f; - AxisCoords delta_pos; - for (unsigned char a = X; a <= E; ++a) { - delta_pos[a] = m_end_position[a] - m_start_position[a]; - max_abs_delta = std::max(max_abs_delta, std::abs(delta_pos[a])); - } + // calculates movement deltas + AxisCoords delta_pos; + for (unsigned char a = X; a <= E; ++a) + delta_pos[a] = m_end_position[a] - m_start_position[a]; + + if (std::all_of(delta_pos.begin(), delta_pos.end(), [](double d) { return d == 0.; })) + return; + + const float volume_extruded_filament = area_filament_cross_section * delta_pos[E]; - // no displacement, return - if (max_abs_delta == 0.0f) - return; + if (volume_extruded_filament != 0.) + m_used_filaments.increase_caches(volume_extruded_filament, + this->m_extruder_id, area_filament_cross_section * this->m_parking_position, + area_filament_cross_section * this->m_extra_loading_move); const EMoveType type = move_type(delta_pos); if (type == EMoveType::Extrude) { const float delta_xyz = std::sqrt(sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z])); - const float volume_extruded_filament = area_filament_cross_section * delta_pos[E]; const float area_toolpath_cross_section = volume_extruded_filament / delta_xyz; - // save extruded volume to the cache - m_used_filaments.increase_caches(volume_extruded_filament); - // volume extruded filament / tool displacement = area toolpath cross section m_mm3_per_mm = area_toolpath_cross_section; #if ENABLE_GCODE_VIEWER_DATA_CHECKING @@ -4090,7 +4112,7 @@ void GCodeProcessor::process_filaments(CustomGCode::Type code) m_used_filaments.process_color_change_cache(); if (code == CustomGCode::ToolChange) - m_used_filaments.process_extruder_cache(this); + m_used_filaments.process_extruder_cache(this->m_extruder_id); } void GCodeProcessor::simulate_st_synchronize(float additional_time) diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 08e70f2f5..71388866e 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -380,18 +380,19 @@ namespace Slic3r { std::map volumes_per_extruder; double role_cache; - std::map> filaments_per_role; + std::map> filaments_per_role; // ExtrusionRole -> (m, g) void reset(); - void increase_caches(double extruded_volume); + void increase_caches(double extruded_volume, unsigned char extruder_id, double parking_volume, double extra_loading_volume); void process_color_change_cache(); - void process_extruder_cache(GCodeProcessor* processor); - void process_role_cache(GCodeProcessor* processor); - void process_caches(GCodeProcessor* processor); - - friend class GCodeProcessor; + void process_extruder_cache(unsigned char extruder_id); + void process_role_cache(const GCodeProcessor* processor); + void process_caches(const GCodeProcessor* processor); + private: + std::vector extruder_retracted_volume; + bool recent_toolchange = false; }; public: @@ -562,6 +563,8 @@ namespace Slic3r { unsigned char m_extruder_id; ExtruderColors m_extruder_colors; ExtruderTemps m_extruder_temps; + float m_parking_position; + float m_extra_loading_move; float m_extruded_last_z; float m_first_layer_height; // mm unsigned int m_g1_line_id; diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index ec071673b..06cf34503 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -175,6 +175,7 @@ public: Point rotated(double angle) const { Point res(*this); res.rotate(angle); return res; } Point rotated(double cos_a, double sin_a) const { Point res(*this); res.rotate(cos_a, sin_a); return res; } Point rotated(double angle, const Point ¢er) const { Point res(*this); res.rotate(angle, center); return res; } + Point rotate_90_degree_ccw() const { return Point(-this->y(), this->x()); } int nearest_point_index(const Points &points) const; int nearest_point_index(const PointConstPtrs &points) const; int nearest_point_index(const PointPtrs &points) const; @@ -259,6 +260,15 @@ inline bool has_duplicate_successive_points_closed(const std::vector &pts return has_duplicate_successive_points(pts) || (pts.size() >= 2 && pts.front() == pts.back()); } +inline bool shorter_then(const Point& p0, const coord_t len) +{ + if (p0.x() > len || p0.x() < -len) + return false; + if (p0.y() > len || p0.y() < -len) + return false; + return p0.cast().squaredNorm() <= Slic3r::sqr(int64_t(len)); +} + namespace int128 { // Exact orientation predicate, // returns +1: CCW, 0: collinear, -1: CW. diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 7a24d3572..813804f0d 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -1,4 +1,3 @@ -<<<<<<< HEAD // FIXME: extract absolute units -> em #include "ConfigWizard_private.hpp" @@ -1485,7 +1484,7 @@ PageDiameters::PageDiameters(ConfigWizard *parent) auto *unit_filam = new wxStaticText(this, wxID_ANY, _L("mm")); sizer_filam->AddGrowableCol(0, 1); sizer_filam->Add(text_filam, 0, wxALIGN_CENTRE_VERTICAL); - sizer_filam->Add(diam_filam); + sizer_filam->Add(diam_filam, 0, wxALIGN_CENTRE_VERTICAL); sizer_filam->Add(unit_filam, 0, wxALIGN_CENTRE_VERTICAL); append(sizer_filam); } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index eb5883f19..53c80162c 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1840,6 +1840,10 @@ void GLCanvas3D::select_all() { m_selection.add_all(); m_dirty = true; + wxGetApp().obj_manipul()->set_dirty(); + m_gizmos.reset_all_states(); + m_gizmos.update_data(); + post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); } void GLCanvas3D::deselect_all() diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 933192c23..58498f371 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -269,9 +269,9 @@ private: credits = title + " " + _L("is based on Slic3r by Alessandro Ranellucci and the RepRap community.") + "\n" + _L("Developed by Prusa Research.")+ "\n\n" + - title + " " + _L("is licensed under the") + " " + _L("GNU Affero General Public License, version 3") + "\n\n" + + title + " " + _L("is licensed under the") + " " + _L("GNU Affero General Public License, version 3") + ".\n\n" + _L("Contributions by Vojtech Bubnik, Enrico Turri, Oleksandra Iushchenko, Tamas Meszaros, Lukas Matena, Vojtech Kral, David Kocik and numerous others.") + "\n\n" + - _L("Artwork model by M Boyer"); + _L("Artwork model by Leslie Ing"); title_font = version_font = credits_font = init_font; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 7272a5ef7..64ae13562 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -944,7 +944,7 @@ void GLGizmoHollow::select_point(int i) m_selected.assign(m_selected.size(), i == AllPoints); m_selection_empty = (i == NoPoints); - if (i == AllPoints) { + if (i == AllPoints && ! drain_holes.empty()) { m_new_hole_radius = drain_holes[0].radius; m_new_hole_height = drain_holes[0].height; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 987fd325f..edae75b80 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -1070,7 +1070,7 @@ void GLGizmoSlaSupports::select_point(int i) point_and_selection.selected = ( i == AllPoints ); m_selection_empty = (i == NoPoints); - if (i == AllPoints) + if (i == AllPoints && ! m_editing_cache.empty()) m_new_point_head_diameter = m_editing_cache[0].support_point.head_front_radius * 2.f; } else { diff --git a/src/slic3r/Utils/FixModelByWin10.cpp b/src/slic3r/Utils/FixModelByWin10.cpp index 296c58622..1381983ac 100644 --- a/src/slic3r/Utils/FixModelByWin10.cpp +++ b/src/slic3r/Utils/FixModelByWin10.cpp @@ -326,9 +326,8 @@ public: // fix_result containes a message if fixing failed bool fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx, wxProgressDialog& progress_dialog, const wxString& msg_header, std::string& fix_result) { - std::mutex mutex; - std::condition_variable condition; - std::unique_lock lock(mutex); + std::mutex mtx; + std::condition_variable condition; struct Progress { std::string message; int percent = 0; @@ -347,8 +346,8 @@ bool fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx, wxPro // (It seems like wxWidgets initialize the COM contex as single threaded and we need a multi-threaded context). bool success = false; size_t ivolume = 0; - auto on_progress = [&mutex, &condition, &ivolume, &volumes, &progress](const char *msg, unsigned prcnt) { - std::lock_guard lk(mutex); + auto on_progress = [&mtx, &condition, &ivolume, &volumes, &progress](const char *msg, unsigned prcnt) { + std::unique_lock lock(mtx); progress.message = msg; progress.percent = (int)floor((float(prcnt) + float(ivolume) * 100.f) / float(volumes.size())); progress.updated = true; @@ -424,6 +423,7 @@ bool fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx, wxPro } }); while (! finished) { + std::unique_lock lock(mtx); condition.wait_for(lock, std::chrono::milliseconds(250), [&progress]{ return progress.updated; }); // decrease progress.percent value to avoid closing of the progress dialog if (!progress_dialog.Update(progress.percent-1, msg_header + _(progress.message)))