diff --git a/.clang-format b/.clang-format index e0a41be5f..6b68254ec 100644 --- a/.clang-format +++ b/.clang-format @@ -46,7 +46,7 @@ BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeComma BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true -ColumnLimit: 75 +ColumnLimit: 78 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: true ConstructorInitializerAllOnOneLineOrOnePerLine: true diff --git a/CMakeLists.txt b/CMakeLists.txt index ba264c8c3..49717b68d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -169,8 +169,19 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" STRE # On GCC and Clang, no return from a non-void function is a warning only. Here, we make it an error. add_compile_options(-Werror=return-type) - #removes LOTS of extraneous Eigen warnings - # add_compile_options(-Wno-ignored-attributes) # Tamas: Eigen include dirs are marked as SYSTEM + #removes LOTS of extraneous Eigen warnings (GCC only supports it since 6.1) + #if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 6.1) + # add_compile_options(-Wno-ignored-attributes) # Tamas: Eigen include dirs are marked as SYSTEM + #endif() + + #GCC generates loads of -Wunknown-pragmas when compiling igl. The fix is not easy due to a bug in gcc, see + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66943 or + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53431 + # We will turn the warning of for GCC for now: + if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + add_compile_options(-Wno-unknown-pragmas) + endif() + if (SLIC3R_ASAN) add_compile_options(-fsanitize=address -fno-omit-frame-pointer) @@ -344,6 +355,10 @@ if (NOT GLEW_FOUND) endif () include_directories(${GLEW_INCLUDE_DIRS}) +# Find the Cereal serialization library +add_library(cereal INTERFACE) +target_include_directories(cereal INTERFACE include) + # l10n set(L10N_DIR "${SLIC3R_RESOURCES_DIR}/localization") add_custom_target(pot diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index d8e72370b..f6a80f3ca 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -89,6 +89,7 @@ if (MSVC) dep_libcurl dep_wxwidgets dep_gtest + dep_cereal dep_nlopt # dep_qhull # Experimental dep_zlib # on Windows we still need zlib @@ -103,9 +104,10 @@ else() dep_libcurl dep_wxwidgets dep_gtest + dep_cereal dep_nlopt dep_qhull - dep_libigl + # dep_libigl # Not working, static build has different Eigen ) endif() diff --git a/deps/deps-unix-common.cmake b/deps/deps-unix-common.cmake index 3614e9444..e323460a6 100644 --- a/deps/deps-unix-common.cmake +++ b/deps/deps-unix-common.cmake @@ -19,6 +19,16 @@ ExternalProject_Add(dep_gtest CMAKE_ARGS -DBUILD_GMOCK=OFF ${DEP_CMAKE_OPTS} -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local ) +ExternalProject_Add(dep_cereal + EXCLUDE_FROM_ALL 1 + URL "https://github.com/USCiLab/cereal/archive/v1.2.2.tar.gz" +# URL_HASH SHA256=c6dd7a5701fff8ad5ebb45a3dc8e757e61d52658de3918e38bab233e7fd3b4ae + CMAKE_ARGS + -DJUST_INSTALL_CEREAL=on + -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local + ${DEP_CMAKE_OPTS} +) + ExternalProject_Add(dep_nlopt EXCLUDE_FROM_ALL 1 URL "https://github.com/stevengj/nlopt/archive/v2.5.0.tar.gz" diff --git a/deps/deps-windows.cmake b/deps/deps-windows.cmake index 0b3fcb13c..9092f330b 100644 --- a/deps/deps-windows.cmake +++ b/deps/deps-windows.cmake @@ -115,6 +115,20 @@ if (${DEP_DEBUG}) endif () +ExternalProject_Add(dep_cereal + EXCLUDE_FROM_ALL 1 + URL "https://github.com/USCiLab/cereal/archive/v1.2.2.tar.gz" +# URL_HASH SHA256=c6dd7a5701fff8ad5ebb45a3dc8e757e61d52658de3918e38bab233e7fd3b4ae + CMAKE_GENERATOR "${DEP_MSVC_GEN}" + CMAKE_GENERATOR_PLATFORM "${DEP_PLATFORM}" + CMAKE_ARGS + -DJUST_INSTALL_CEREAL=on + "-DCMAKE_INSTALL_PREFIX:PATH=${DESTDIR}\\usr\\local" + BUILD_COMMAND msbuild /m /P:Configuration=Release INSTALL.vcxproj + INSTALL_COMMAND "" +) + + ExternalProject_Add(dep_nlopt EXCLUDE_FROM_ALL 1 URL "https://github.com/stevengj/nlopt/archive/v2.5.0.tar.gz" diff --git a/doc/How to build - Mac OS.md b/doc/How to build - Mac OS.md index 42a71e10d..b4196909d 100644 --- a/doc/How to build - Mac OS.md +++ b/doc/How to build - Mac OS.md @@ -20,6 +20,9 @@ You can also customize the bundle output path using the `-DDESTDIR=` **Warning**: Once the dependency bundle is installed in a destdir, the destdir cannot be moved elsewhere. (This is because wxWidgets hardcodes the installation path.) +FIXME The Cereal serialization library needs a tiny patch on some old OSX clang installations +https://github.com/USCiLab/cereal/issues/339#issuecomment-246166717 + ### Building PrusaSlicer diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 44100db8c..94f0b5658 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -50,7 +50,6 @@ use Slic3r::Point; use Slic3r::Polygon; use Slic3r::Polyline; use Slic3r::Print::Object; -use Slic3r::Print::Simple; use Slic3r::Surface; our $build = eval "use Slic3r::Build; 1"; diff --git a/lib/Slic3r/Print/Simple.pm b/lib/Slic3r/Print/Simple.pm deleted file mode 100644 index 2ab68f4d3..000000000 --- a/lib/Slic3r/Print/Simple.pm +++ /dev/null @@ -1,104 +0,0 @@ -# A simple wrapper to quickly print a single model without a GUI. -# Used by the command line slic3r.pl, by command line utilities pdf-slic3s.pl and view-toolpaths.pl, -# and by the quick slice menu of the Slic3r GUI. -# -# It creates and owns an instance of Slic3r::Print to perform the slicing -# and it accepts an instance of Slic3r::Model from the outside. - -package Slic3r::Print::Simple; -use Moo; - -use Slic3r::Geometry qw(X Y); - -has '_print' => ( - is => 'ro', - default => sub { Slic3r::Print->new }, - handles => [qw(apply_config_perl_tests_only extruders output_filepath - total_used_filament total_extruded_volume - placeholder_parser process)], -); - -has 'duplicate' => ( - is => 'rw', - default => sub { 1 }, -); - -has 'scale' => ( - is => 'rw', - default => sub { 1 }, -); - -has 'rotate' => ( - is => 'rw', - default => sub { 0 }, -); - -has 'duplicate_grid' => ( - is => 'rw', - default => sub { [1,1] }, -); - -has 'print_center' => ( - is => 'rw', - default => sub { Slic3r::Pointf->new(100,100) }, -); - -has 'dont_arrange' => ( - is => 'rw', - default => sub { 0 }, -); - -has 'output_file' => ( - is => 'rw', -); - -sub set_model { - # $model is of type Slic3r::Model - my ($self, $model) = @_; - - # make method idempotent so that the object is reusable - $self->_print->clear_objects; - - # make sure all objects have at least one defined instance - my $need_arrange = $model->add_default_instances && ! $self->dont_arrange; - - # apply scaling and rotation supplied from command line if any - foreach my $instance (map @{$_->instances}, @{$model->objects}) { - $instance->set_scaling_factor($instance->scaling_factor * $self->scale); - $instance->set_rotation($instance->rotation + $self->rotate); - } - - if ($self->duplicate_grid->[X] > 1 || $self->duplicate_grid->[Y] > 1) { - $model->duplicate_objects_grid($self->duplicate_grid->[X], $self->duplicate_grid->[Y], $self->_print->config->duplicate_distance); - } elsif ($need_arrange) { - $model->duplicate_objects($self->duplicate, $self->_print->config->min_object_distance); - } elsif ($self->duplicate > 1) { - # if all input objects have defined position(s) apply duplication to the whole model - $model->duplicate($self->duplicate, $self->_print->config->min_object_distance); - } - $_->translate(0,0,-$_->bounding_box->z_min) for @{$model->objects}; - $model->center_instances_around_point($self->print_center) if (! $self->dont_arrange); - - foreach my $model_object (@{$model->objects}) { - $self->_print->auto_assign_extruders($model_object); - $self->_print->add_model_object($model_object); - } -} - -sub export_gcode { - my ($self) = @_; - $self->_print->validate; - $self->_print->export_gcode($self->output_file // ''); -} - -sub export_png { - my ($self) = @_; - - $self->_before_export; - - $self->_print->export_png(output_file => $self->output_file); - - $self->_after_export; -} - -1; diff --git a/lib/Slic3r/Test.pm b/lib/Slic3r/Test.pm index d1b99e48c..570bca41b 100644 --- a/lib/Slic3r/Test.pm +++ b/lib/Slic3r/Test.pm @@ -146,60 +146,66 @@ sub mesh { } sub model { - my ($model_name, %params) = @_; + my ($model_names, %params) = @_; + $model_names = [ $model_names ] if ! ref($model_names); - my $input_file = "${model_name}.stl"; - my $mesh = mesh($model_name, %params); -# $mesh->write_ascii("out/$input_file"); - my $model = Slic3r::Model->new; - my $object = $model->add_object(input_file => $input_file); - $model->set_material($model_name); - $object->add_volume(mesh => $mesh, material_id => $model_name); - $object->add_instance( - offset => Slic3r::Pointf->new(0,0), - # 3D full transform - rotation => Slic3r::Pointf3->new(0, 0, $params{rotation} // 0), - scaling_factor => Slic3r::Pointf3->new($params{scale} // 1, $params{scale} // 1, $params{scale} // 1), - # old transform -# rotation => $params{rotation} // 0, -# scaling_factor => $params{scale} // 1, - ); + + for my $model_name (@$model_names) { + my $input_file = "${model_name}.stl"; + my $mesh = mesh($model_name, %params); + # $mesh->write_ascii("out/$input_file"); + + my $object = $model->add_object(input_file => $input_file); + $model->set_material($model_name); + $object->add_volume(mesh => $mesh, material_id => $model_name); + $object->add_instance( + offset => Slic3r::Pointf->new(0,0), + # 3D full transform + rotation => Slic3r::Pointf3->new(0, 0, $params{rotation} // 0), + scaling_factor => Slic3r::Pointf3->new($params{scale} // 1, $params{scale} // 1, $params{scale} // 1), + # old transform + # rotation => $params{rotation} // 0, + # scaling_factor => $params{scale} // 1, + ); + } return $model; } sub init_print { my ($models, %params) = @_; + my $model; + if (ref($models) eq 'ARRAY') { + $model = model($models, %params); + } elsif (ref($models)) { + $model = $models; + } else { + $model = model([$models], %params); + } my $config = Slic3r::Config->new; $config->apply($params{config}) if $params{config}; $config->set('gcode_comments', 1) if $ENV{SLIC3R_TESTS_GCODE}; my $print = Slic3r::Print->new; - $print->apply_config_perl_tests_only($config); - - $models = [$models] if ref($models) ne 'ARRAY'; - $models = [ map { ref($_) ? $_ : model($_, %params) } @$models ]; - for my $model (@$models) { - die "Unknown model in test" if !defined $model; - if (defined $params{duplicate} && $params{duplicate} > 1) { - $model->duplicate($params{duplicate} // 1, $print->config->min_object_distance); - } - $model->arrange_objects($print->config->min_object_distance); - $model->center_instances_around_point($params{print_center} ? Slic3r::Pointf->new(@{$params{print_center}}) : Slic3r::Pointf->new(100,100)); - foreach my $model_object (@{$model->objects}) { - $print->auto_assign_extruders($model_object); - $print->add_model_object($model_object); - } + die "Unknown model in test" if !defined $model; + if (defined $params{duplicate} && $params{duplicate} > 1) { + $model->duplicate($params{duplicate} // 1, $config->min_object_distance); } - # Call apply_config_perl_tests_only one more time, so that the layer height profiles are updated over all PrintObjects. - $print->apply_config_perl_tests_only($config); + $model->arrange_objects($config->min_object_distance); + $model->center_instances_around_point($params{print_center} ? Slic3r::Pointf->new(@{$params{print_center}}) : Slic3r::Pointf->new(100,100)); + foreach my $model_object (@{$model->objects}) { + $model_object->ensure_on_bed; + $print->auto_assign_extruders($model_object); + } + + $print->apply($model, $config); $print->validate; # We return a proxy object in order to keep $models alive as required by the Print API. return Slic3r::Test::Print->new( - print => $print, - models => $models, + print => $print, + model => $model, ); } @@ -250,7 +256,7 @@ sub add_facet { package Slic3r::Test::Print; use Moo; -has 'print' => (is => 'ro', required => 1, handles => [qw(process apply_config_perl_tests_only)]); -has 'models' => (is => 'ro', required => 1); +has 'print' => (is => 'ro', required => 1, handles => [qw(process apply)]); +has 'model' => (is => 'ro', required => 1); 1; diff --git a/resources/icons/drop_to_bed.png b/resources/icons/drop_to_bed.png new file mode 100644 index 000000000..b60e1b137 Binary files /dev/null and b/resources/icons/drop_to_bed.png differ diff --git a/resources/icons/redo.svg b/resources/icons/redo.svg new file mode 100644 index 000000000..9109779bb --- /dev/null +++ b/resources/icons/redo.svg @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/resources/icons/redo_toolbar.svg b/resources/icons/redo_toolbar.svg new file mode 100644 index 000000000..ad073244f --- /dev/null +++ b/resources/icons/redo_toolbar.svg @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/resources/icons/row.png b/resources/icons/row.png new file mode 100644 index 000000000..18a6034fd Binary files /dev/null and b/resources/icons/row.png differ diff --git a/resources/icons/table.png b/resources/icons/table.png new file mode 100644 index 000000000..3bc0bd32f Binary files /dev/null and b/resources/icons/table.png differ diff --git a/resources/icons/undo_toolbar.svg b/resources/icons/undo_toolbar.svg new file mode 100644 index 000000000..699ccd807 --- /dev/null +++ b/resources/icons/undo_toolbar.svg @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/sandboxes/slabasebed/slabasebed.cpp b/sandboxes/slabasebed/slabasebed.cpp index 0c34eb9f5..5393f61fd 100644 --- a/sandboxes/slabasebed/slabasebed.cpp +++ b/sandboxes/slabasebed/slabasebed.cpp @@ -15,7 +15,8 @@ const std::string USAGE_STR = { namespace Slic3r { namespace sla { -Contour3D create_base_pool(const ExPolygons &ground_layer, +Contour3D create_base_pool(const Polygons &ground_layer, + const Polygons &holes = {}, const PoolConfig& cfg = PoolConfig()); Contour3D walls(const Polygon& floor_plate, const Polygon& ceiling, @@ -42,37 +43,28 @@ int main(const int argc, const char *argv[]) { model.ReadSTLFile(argv[1]); model.align_to_origin(); - ExPolygons ground_slice; - sla::Contour3D mesh; -// TriangleMesh basepool; - + Polygons ground_slice; sla::base_plate(model, ground_slice, 0.1f); - if(ground_slice.empty()) return EXIT_FAILURE; -// ExPolygon bottom_plate = ground_slice.front(); -// ExPolygon top_plate = bottom_plate; -// sla::offset(top_plate, coord_t(3.0/SCALING_FACTOR)); -// sla::offset(bottom_plate, coord_t(1.0/SCALING_FACTOR)); + Polygon gndfirst; gndfirst = ground_slice.front(); + sla::offset_with_breakstick_holes(gndfirst, 0.5, 10, 0.3); + + sla::Contour3D mesh; + bench.start(); -// TriangleMesh pool; sla::PoolConfig cfg; cfg.min_wall_height_mm = 0; - cfg.edge_radius_mm = 0.2; - mesh = sla::create_base_pool(ground_slice, cfg); - -// mesh.merge(triangulate_expolygon_3d(top_plate, 3.0, false)); -// mesh.merge(triangulate_expolygon_3d(bottom_plate, 0.0, true)); -// mesh = sla::walls(bottom_plate.contour, top_plate.contour, 0, 3, 2.0, [](){}); - + cfg.edge_radius_mm = 0; + mesh = sla::create_base_pool(ground_slice, {}, cfg); + bench.stop(); cout << "Base pool creation time: " << std::setprecision(10) << bench.getElapsedSec() << " seconds." << endl; - -// auto point = []() + for(auto& trind : mesh.indices) { Vec3d p0 = mesh.points[size_t(trind[0])]; Vec3d p1 = mesh.points[size_t(trind[1])]; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 61faa0571..3ee46289a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -75,7 +75,7 @@ if (NOT MSVC) set_target_properties(PrusaSlicer PROPERTIES OUTPUT_NAME "prusa-slicer") endif () -target_link_libraries(PrusaSlicer libslic3r) +target_link_libraries(PrusaSlicer libslic3r cereal) if (APPLE) # add_compile_options(-stdlib=libc++) # add_definitions(-DBOOST_THREAD_DONT_USE_CHRONO -DBOOST_NO_CXX11_RVALUE_REFERENCES -DBOOST_THREAD_USES_MOVE) diff --git a/src/admesh/connect.cpp b/src/admesh/connect.cpp index e729c8922..b86ec5055 100644 --- a/src/admesh/connect.cpp +++ b/src/admesh/connect.cpp @@ -132,7 +132,7 @@ struct HashTableEdges { ~HashTableEdges() { #ifndef NDEBUG for (int i = 0; i < this->M; ++ i) - for (HashEdge *temp = this->heads[i]; this->heads[i] != this->tail; temp = this->heads[i]) + for (HashEdge *temp = this->heads[i]; temp != this->tail; temp = temp->next) ++ this->freed; this->tail = nullptr; #endif /* NDEBUG */ diff --git a/src/avrdude/arduino.c b/src/avrdude/arduino.c index 53e5ed822..e6008adeb 100644 --- a/src/avrdude/arduino.c +++ b/src/avrdude/arduino.c @@ -41,6 +41,7 @@ static int arduino_read_sig_bytes(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m) { unsigned char buf[32]; + (void)p; /* Signature byte reads are always 3 bytes. */ @@ -83,9 +84,9 @@ static int arduino_read_sig_bytes(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m) static int prusa_init_external_flash(PROGRAMMER * pgm) { // Note: send/receive as in _the firmare_ send & receives - const char entry_magic_send [] = "start\n"; - const char entry_magic_receive[] = "w25x20cl_enter\n"; - const char entry_magic_cfm [] = "w25x20cl_cfm\n"; + const char entry_magic_send[] = "start\n"; + const unsigned char entry_magic_receive[] = "w25x20cl_enter\n"; + const char entry_magic_cfm[] = "w25x20cl_cfm\n"; const size_t buffer_len = 32; // Should be large enough for the above messages int res; @@ -94,7 +95,7 @@ static int prusa_init_external_flash(PROGRAMMER * pgm) // 1. receive the "start" command recv_size = sizeof(entry_magic_send) - 1; - res = serial_recv(&pgm->fd, buffer, recv_size); + res = serial_recv(&pgm->fd, (unsigned char *)buffer, recv_size); if (res < 0) { avrdude_message(MSG_INFO, "%s: prusa_init_external_flash(): MK3 printer did not boot up on time or serial communication failed\n", progname); return -1; @@ -111,7 +112,7 @@ static int prusa_init_external_flash(PROGRAMMER * pgm) // 3. Receive the entry confirmation command recv_size = sizeof(entry_magic_cfm) - 1; - res = serial_recv(&pgm->fd, buffer, recv_size); + res = serial_recv(&pgm->fd, (unsigned char *)buffer, recv_size); if (res < 0) { avrdude_message(MSG_INFO, "%s: prusa_init_external_flash(): MK3 printer did not boot up on time or serial communication failed\n", progname); return -1; @@ -142,7 +143,7 @@ static int arduino_open(PROGRAMMER * pgm, char * port) // Sometimes there may be line noise generating input on the printer's USB-to-serial IC // Here we try to clean its input buffer with a sequence of newlines (a minimum of 9 is needed): - const char cleanup_newlines[] = "\n\n\n\n\n\n\n\n\n\n"; + const unsigned char cleanup_newlines[] = "\n\n\n\n\n\n\n\n\n\n"; if (serial_send(&pgm->fd, cleanup_newlines, sizeof(cleanup_newlines) - 1) < 0) { return -1; } diff --git a/src/avrdude/avr.c b/src/avrdude/avr.c index 73dcaf4ff..defae75d5 100644 --- a/src/avrdude/avr.c +++ b/src/avrdude/avr.c @@ -341,7 +341,7 @@ int avr_read(PROGRAMMER * pgm, AVRPART * p, char * memtype, avr_tpi_setup_rw(pgm, mem, 0, TPI_NVMCMD_NO_OPERATION); /* load bytes */ - for (lastaddr = i = 0; i < mem->size; i++) { + for (lastaddr = i = 0; i < (unsigned)mem->size; i++) { RETURN_IF_CANCEL(); if (vmem == NULL || (vmem->tags[i] & TAG_ALLOCATED) != 0) @@ -374,7 +374,7 @@ int avr_read(PROGRAMMER * pgm, AVRPART * p, char * memtype, /* quickly scan number of pages to be written to first */ for (pageaddr = 0, npages = 0; - pageaddr < mem->size; + pageaddr < (unsigned)mem->size; pageaddr += mem->page_size) { /* check whether this page must be read */ for (i = pageaddr; @@ -391,7 +391,7 @@ int avr_read(PROGRAMMER * pgm, AVRPART * p, char * memtype, } for (pageaddr = 0, failure = 0, nread = 0; - !failure && pageaddr < mem->size; + !failure && pageaddr < (unsigned)mem->size; pageaddr += mem->page_size) { RETURN_IF_CANCEL(); /* check whether this page must be read */ @@ -437,7 +437,7 @@ int avr_read(PROGRAMMER * pgm, AVRPART * p, char * memtype, } } - for (i=0; i < mem->size; i++) { + for (i = 0; i < (unsigned)mem->size; i++) { RETURN_IF_CANCEL(); if (vmem == NULL || (vmem->tags[i] & TAG_ALLOCATED) != 0) @@ -634,18 +634,18 @@ int avr_write_byte_default(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem, writeop = mem->op[AVR_OP_WRITE_HI]; else writeop = mem->op[AVR_OP_WRITE_LO]; - caddr = addr / 2; + caddr = (unsigned short)(addr / 2); } else if (mem->paged && mem->op[AVR_OP_LOADPAGE_LO]) { if (addr & 0x01) writeop = mem->op[AVR_OP_LOADPAGE_HI]; else writeop = mem->op[AVR_OP_LOADPAGE_LO]; - caddr = addr / 2; + caddr = (unsigned short)(addr / 2); } else { writeop = mem->op[AVR_OP_WRITE]; - caddr = addr; + caddr = (unsigned short)addr; } if (writeop == NULL) { @@ -723,7 +723,7 @@ int avr_write_byte_default(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem, gettimeofday (&tv, NULL); prog_time = (tv.tv_sec * 1000000) + tv.tv_usec; } while ((r != data) && - ((prog_time-start_time) < mem->max_write_delay)); + ((prog_time - start_time) < (unsigned long)mem->max_write_delay)); } /* @@ -878,7 +878,7 @@ int avr_write(PROGRAMMER * pgm, AVRPART * p, char * memtype, int size, } /* write words, low byte first */ - for (lastaddr = i = 0; i < wsize; i += 2) { + for (lastaddr = i = 0; i < (unsigned)wsize; i += 2) { RETURN_IF_CANCEL(); if ((m->tags[i] & TAG_ALLOCATED) != 0 || (m->tags[i + 1] & TAG_ALLOCATED) != 0) { @@ -915,7 +915,7 @@ int avr_write(PROGRAMMER * pgm, AVRPART * p, char * memtype, int size, /* quickly scan number of pages to be written to first */ for (pageaddr = 0, npages = 0; - pageaddr < wsize; + pageaddr < (unsigned)wsize; pageaddr += m->page_size) { /* check whether this page must be written to */ for (i = pageaddr; @@ -928,7 +928,7 @@ int avr_write(PROGRAMMER * pgm, AVRPART * p, char * memtype, int size, } for (pageaddr = 0, failure = 0, nwritten = 0; - !failure && pageaddr < wsize; + !failure && pageaddr < (unsigned)wsize; pageaddr += m->page_size) { RETURN_IF_CANCEL(); /* check whether this page must be written to */ @@ -968,7 +968,7 @@ int avr_write(PROGRAMMER * pgm, AVRPART * p, char * memtype, int size, page_tainted = 0; flush_page = 0; - for (i=0; ibuf[i]; report_progress(i, wsize, NULL); diff --git a/src/avrdude/avr910.c b/src/avrdude/avr910.c index aa5cc07a9..17a4ab69f 100644 --- a/src/avrdude/avr910.c +++ b/src/avrdude/avr910.c @@ -676,7 +676,7 @@ static int avr910_paged_load(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m, avr910_set_addr(pgm, addr / rd_size); while (addr < max_addr) { - if ((max_addr - addr) < blocksize) { + if ((max_addr - addr) < (unsigned)blocksize) { blocksize = max_addr - addr; } cmd[1] = (blocksize >> 8) & 0xff; diff --git a/src/avrdude/avrdude-slic3r.conf.h b/src/avrdude/avrdude-slic3r.conf.h index 7cc901336..905b14ee8 100644 --- a/src/avrdude/avrdude-slic3r.conf.h +++ b/src/avrdude/avrdude-slic3r.conf.h @@ -1,5 +1,5 @@ /* WARN: This file is auto-generated from `avrdude-slic3r.conf` */ -unsigned char avrdude_slic3r_conf[] = { +const unsigned char avrdude_slic3r_conf[] = { 0x0a, 0x23, 0x0a, 0x23, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x62, 0x61, 0x73, 0x69, 0x63, 0x20, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x61, 0x6c, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x20, @@ -1184,5 +1184,5 @@ unsigned char avrdude_slic3r_conf[] = { 0x20, 0x20, 0x3b, 0x0a, 0x0a, 0x0a, 0, 0 }; -size_t avrdude_slic3r_conf_size = 14178; -size_t avrdude_slic3r_conf_size_yy = 14180; +const size_t avrdude_slic3r_conf_size = 14178; +const size_t avrdude_slic3r_conf_size_yy = 14180; diff --git a/src/avrdude/avrdude-slic3r.cpp b/src/avrdude/avrdude-slic3r.cpp index 0140d93ed..7eff436e2 100644 --- a/src/avrdude/avrdude-slic3r.cpp +++ b/src/avrdude/avrdude-slic3r.cpp @@ -93,7 +93,7 @@ void AvrDude::priv::unset_handlers() int AvrDude::priv::run_one(const std::vector &args) { - std::vector c_args {{ const_cast(PACKAGE) }}; + std::vector c_args { const_cast(PACKAGE) }; std::string command_line { PACKAGE }; for (const auto &arg : args) { @@ -105,7 +105,7 @@ int AvrDude::priv::run_one(const std::vector &args) { HandlerGuard guard(*this); - message_fn(command_line.c_str(), command_line.size()); + message_fn(command_line.c_str(), (unsigned)command_line.size()); const auto res = ::avrdude_main(static_cast(c_args.size()), c_args.data()); @@ -200,7 +200,7 @@ AvrDude::Ptr AvrDude::run() auto &message_fn = self->p->message_fn; if (message_fn) { message_fn(msg, sizeof(msg)); - message_fn(what, std::strlen(what)); + message_fn(what, (unsigned)std::strlen(what)); message_fn("\n", 1); } diff --git a/src/avrdude/avrdude.h b/src/avrdude/avrdude.h index ff464f46f..bc784cec6 100644 --- a/src/avrdude/avrdude.h +++ b/src/avrdude/avrdude.h @@ -64,6 +64,8 @@ int avrdude_main(int argc, char * argv []); #include #include +#define strdup _strdup + #ifdef UNICODE #error "UNICODE should not be defined for avrdude bits on Windows" #endif diff --git a/src/avrdude/avrpart.c b/src/avrdude/avrpart.c index d0bb951ee..1c7d6af00 100644 --- a/src/avrdude/avrpart.c +++ b/src/avrdude/avrpart.c @@ -358,7 +358,7 @@ AVRMEM * avr_locate_mem(AVRPART * p, char * desc) int matches; int l; - l = strlen(desc); + l = (int)strlen(desc); matches = 0; match = NULL; for (ln=lfirst(p->mem); ln; ln=lnext(ln)) { @@ -662,7 +662,7 @@ void avr_display(FILE * f, AVRPART * p, const char * prefix, int verbose) prefix); px = prefix; - i = strlen(prefix) + 5; + i = (int)strlen(prefix) + 5; buf = (char *)malloc(i); if (buf == NULL) { /* ugh, this is not important enough to bail, just ignore it */ diff --git a/src/avrdude/buspirate.c b/src/avrdude/buspirate.c index 5875d4283..dc8c68fbe 100644 --- a/src/avrdude/buspirate.c +++ b/src/avrdude/buspirate.c @@ -128,7 +128,7 @@ static int buspirate_recv_bin(struct programmer_t *pgm, unsigned char *buf, size avrdude_message(MSG_DEBUG, "%s: buspirate_recv_bin():\n", progname); dump_mem(MSG_DEBUG, buf, len); - return len; + return (int)len; } static int buspirate_expect_bin(struct programmer_t *pgm, @@ -249,7 +249,7 @@ static int buspirate_send(struct programmer_t *pgm, const char *str) static int buspirate_is_prompt(const char *str) { - int strlen_str = strlen(str); + int strlen_str = (int)strlen(str); /* Prompt ends with '>' or '> ' * all other input probably ends with '\n' */ return (str[strlen_str - 1] == '>' || str[strlen_str - 2] == '>'); diff --git a/src/avrdude/butterfly.c b/src/avrdude/butterfly.c index beb5e04de..8f582e72a 100644 --- a/src/avrdude/butterfly.c +++ b/src/avrdude/butterfly.c @@ -675,7 +675,7 @@ static int butterfly_paged_load(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m, butterfly_set_addr(pgm, addr / rd_size); } while (addr < max_addr) { - if ((max_addr - addr) < blocksize) { + if ((max_addr - addr) < (unsigned)blocksize) { blocksize = max_addr - addr; }; cmd[1] = (blocksize >> 8) & 0xff; diff --git a/src/avrdude/conf-generate.cpp b/src/avrdude/conf-generate.cpp index f2761ba22..4aa80ae0a 100644 --- a/src/avrdude/conf-generate.cpp +++ b/src/avrdude/conf-generate.cpp @@ -21,7 +21,7 @@ int main(int argc, char const *argv[]) } std::cout << "/* WARN: This file is auto-generated from `" << filename << "` */" << std::endl; - std::cout << "unsigned char " << symbol << "[] = {"; + std::cout << "const unsigned char " << symbol << "[] = {"; char c; std::cout << std::hex; @@ -34,8 +34,8 @@ int main(int argc, char const *argv[]) std::cout << "\n 0, 0\n};\n"; std::cout << std::dec; - std::cout << "size_t " << symbol << "_size = " << size << ";" << std::endl; - std::cout << "size_t " << symbol << "_size_yy = " << size + 2 << ";" << std::endl; + std::cout << "const size_t " << symbol << "_size = " << size << ";" << std::endl; + std::cout << "const size_t " << symbol << "_size_yy = " << size + 2 << ";" << std::endl; return 0; } diff --git a/src/avrdude/config.c b/src/avrdude/config.c index 1c0ff5525..b82fb29cb 100644 --- a/src/avrdude/config.c +++ b/src/avrdude/config.c @@ -240,7 +240,7 @@ TOKEN * string(char * text) return NULL; /* yyerror already called */ } - len = strlen(text); + len = (int)strlen(text); tkn->value.type = V_STR; tkn->value.string = (char *) malloc(len+1); @@ -351,7 +351,7 @@ int read_config(const char * file) } typedef struct yy_buffer_state *YY_BUFFER_STATE; -extern YY_BUFFER_STATE yy_scan_bytes(char *base, size_t size); +extern YY_BUFFER_STATE yy_scan_bytes(const char *base, size_t size); extern void yy_delete_buffer(YY_BUFFER_STATE b); int read_config_builtin() @@ -363,7 +363,7 @@ int read_config_builtin() // Note: Can't use yy_scan_buffer, it's buggy (?), leads to fread from a null FILE* // and so unfortunatelly we have to use the copying variant here - YY_BUFFER_STATE buffer = yy_scan_bytes(avrdude_slic3r_conf, avrdude_slic3r_conf_size); + YY_BUFFER_STATE buffer = yy_scan_bytes((const char *)avrdude_slic3r_conf, avrdude_slic3r_conf_size); if (buffer == NULL) { avrdude_message(MSG_INFO, "%s: read_config_builtin: Failed to initialize parsing buffer\n", progname); return -1; diff --git a/src/avrdude/config_gram.c b/src/avrdude/config_gram.c index c1a65b13e..2d32fe739 100644 --- a/src/avrdude/config_gram.c +++ b/src/avrdude/config_gram.c @@ -3640,7 +3640,7 @@ static int parse_cmdbits(OPCODE * op) break; } - len = strlen(s); + len = (int)strlen(s); if (len == 0) { yyerror("invalid bit specifier \"\""); diff --git a/src/avrdude/config_gram.y b/src/avrdude/config_gram.y index 0aa95a8e8..6a062352b 100644 --- a/src/avrdude/config_gram.y +++ b/src/avrdude/config_gram.y @@ -1493,7 +1493,7 @@ static int parse_cmdbits(OPCODE * op) break; } - len = strlen(s); + len = (int)strlen(s); if (len == 0) { yyerror("invalid bit specifier \"\""); diff --git a/src/avrdude/fileio.c b/src/avrdude/fileio.c index 7803497a0..7f4c8edee 100644 --- a/src/avrdude/fileio.c +++ b/src/avrdude/fileio.c @@ -264,7 +264,7 @@ static int ihex_readrec(struct ihexrec * ihex, char * rec) unsigned char cksum; int rc; - len = strlen(rec); + len = (int)strlen(rec); offset = 1; cksum = 0; @@ -274,7 +274,7 @@ static int ihex_readrec(struct ihexrec * ihex, char * rec) for (i=0; i<2; i++) buf[i] = rec[offset++]; buf[i] = 0; - ihex->reclen = strtoul(buf, &e, 16); + ihex->reclen = (unsigned char)strtoul(buf, &e, 16); if (e == buf || *e != 0) return -1; @@ -294,7 +294,7 @@ static int ihex_readrec(struct ihexrec * ihex, char * rec) for (i=0; i<2; i++) buf[i] = rec[offset++]; buf[i] = 0; - ihex->rectyp = strtoul(buf, &e, 16); + ihex->rectyp = (unsigned char)strtoul(buf, &e, 16); if (e == buf || *e != 0) return -1; @@ -308,7 +308,7 @@ static int ihex_readrec(struct ihexrec * ihex, char * rec) for (i=0; i<2; i++) buf[i] = rec[offset++]; buf[i] = 0; - ihex->data[j] = strtoul(buf, &e, 16); + ihex->data[j] = (char)strtoul(buf, &e, 16); if (e == buf || *e != 0) return -1; cksum += ihex->data[j]; @@ -320,7 +320,7 @@ static int ihex_readrec(struct ihexrec * ihex, char * rec) for (i=0; i<2; i++) buf[i] = rec[offset++]; buf[i] = 0; - ihex->cksum = strtoul(buf, &e, 16); + ihex->cksum = (char)strtoul(buf, &e, 16); if (e == buf || *e != 0) return -1; @@ -361,7 +361,7 @@ static int ihex2b(char * infile, FILE * inf, while (fgets((char *)buffer,MAX_LINE_LEN,inf)!=NULL) { lineno++; - len = strlen(buffer); + len = (int)strlen(buffer); if (buffer[len-1] == '\n') buffer[--len] = 0; if (buffer[0] != ':') @@ -388,7 +388,7 @@ static int ihex2b(char * infile, FILE * inf, return -1; } nextaddr = ihex.loadofs + baseaddr - fileoffset; - if (nextaddr + ihex.reclen > bufsize) { + if (nextaddr + ihex.reclen > (unsigned)bufsize) { avrdude_message(MSG_INFO, "%s: ERROR: address 0x%04x out of range at line %d of %s\n", progname, nextaddr+ihex.reclen, lineno, infile); return -1; @@ -502,10 +502,11 @@ static int b2srec(unsigned char * inbuf, int bufsize, cksum += n + addr_width + 1; - for (i=addr_width; i>0; i--) + for (i = addr_width; i>0; i--) { cksum += (nextaddr >> (i-1) * 8) & 0xff; + } - for (i=nextaddr; ireclen = strtoul(buf, &e, 16); + srec->reclen = (char)strtoul(buf, &e, 16); cksum += srec->reclen; srec->reclen -= (addr_width+1); if (e == buf || *e != 0) @@ -594,7 +595,7 @@ static int srec_readrec(struct ihexrec * srec, char * rec) for (i=0; iloadofs = strtoull(buf, &e, 16); + srec->loadofs = strtoul(buf, &e, 16); if (e == buf || *e != 0) return -1; @@ -608,7 +609,7 @@ static int srec_readrec(struct ihexrec * srec, char * rec) for (i=0; i<2; i++) buf[i] = rec[offset++]; buf[i] = 0; - srec->data[j] = strtoul(buf, &e, 16); + srec->data[j] = (char)strtoul(buf, &e, 16); if (e == buf || *e != 0) return -1; cksum += srec->data[j]; @@ -620,7 +621,7 @@ static int srec_readrec(struct ihexrec * srec, char * rec) for (i=0; i<2; i++) buf[i] = rec[offset++]; buf[i] = 0; - srec->cksum = strtoul(buf, &e, 16); + srec->cksum = (char)strtoul(buf, &e, 16); if (e == buf || *e != 0) return -1; @@ -650,7 +651,7 @@ static int srec2b(char * infile, FILE * inf, while (fgets((char *)buffer,MAX_LINE_LEN,inf)!=NULL) { lineno++; - len = strlen(buffer); + len = (int)strlen(buffer); if (buffer[len-1] == '\n') buffer[--len] = 0; if (buffer[0] != 0x53) @@ -729,7 +730,7 @@ static int srec2b(char * infile, FILE * inf, return -1; } nextaddr -= fileoffset; - if (nextaddr + srec.reclen > bufsize) { + if (nextaddr + srec.reclen > (unsigned)bufsize) { avrdude_message(MSG_INFO, msg, progname, nextaddr+srec.reclen, "", lineno, infile); return -1; @@ -740,7 +741,7 @@ static int srec2b(char * infile, FILE * inf, } if (nextaddr+srec.reclen > maxaddr) maxaddr = nextaddr+srec.reclen; - reccount++; + reccount++; } } @@ -1143,12 +1144,12 @@ static int fileio_rbin(struct fioparms * fio, switch (fio->op) { case FIO_READ: - rc = fread(buf, 1, size, f); + rc = (int)fread(buf, 1, size, f); if (rc > 0) memset(mem->tags, TAG_ALLOCATED, rc); break; case FIO_WRITE: - rc = fwrite(buf, 1, size, f); + rc = (int)fwrite(buf, 1, size, f); break; default: avrdude_message(MSG_INFO, "%s: fileio: invalid operation=%d\n", @@ -1190,7 +1191,7 @@ static int fileio_imm(struct fioparms * fio, progname, p); return -1; } - mem->buf[loc] = b; + mem->buf[loc] = (char)b; mem->tags[loc++] = TAG_ALLOCATED; p = strtok(NULL, " ,"); rc = loc; @@ -1452,7 +1453,7 @@ static int fmt_autodetect(char * fname, unsigned section) } buf[MAX_LINE_LEN-1] = 0; - len = strlen((char *)buf); + len = (int)strlen((char *)buf); if (buf[len-1] == '\n') buf[--len] = 0; diff --git a/src/avrdude/lists.c b/src/avrdude/lists.c index cab88364e..063507ed3 100644 --- a/src/avrdude/lists.c +++ b/src/avrdude/lists.c @@ -444,7 +444,7 @@ lcreat ( void * liststruct, int elements ) l->poolsize = DEFAULT_POOLSIZE; } else { - l->poolsize = elements*sizeof(LISTNODE)+sizeof(NODEPOOL); + l->poolsize = (short)(elements*sizeof(LISTNODE)+sizeof(NODEPOOL)); } l->n_ln_pool = (l->poolsize-sizeof(NODEPOOL))/sizeof(LISTNODE); @@ -803,7 +803,7 @@ lget_n ( LISTID lid, unsigned int n ) CKLMAGIC(l); - if ((n<1)||(n>lsize(l))) { + if ((n < 1) || (n > (unsigned)lsize(l))) { return NULL; } @@ -844,7 +844,7 @@ lget_ln ( LISTID lid, unsigned int n ) CKLMAGIC(l); - if ((n<1)||(n>lsize(l))) { + if ((n < 1) || (n > (unsigned)lsize(l))) { return NULL; } @@ -941,7 +941,7 @@ insert_ln ( LIST * l, LISTNODE * ln, void * data_ptr ) | | Insert data before the nth item in the list. -----------------------------------------------------------------*/ -int +int lins_n ( LISTID lid, void * data_ptr, unsigned int n ) { int i; @@ -952,7 +952,7 @@ lins_n ( LISTID lid, void * data_ptr, unsigned int n ) CKLMAGIC(l); - if ((n<1)||(n>(l->num+1))) { + if ((n < 1) || (n > (unsigned)(l->num+1))) { return -1; } @@ -1193,7 +1193,7 @@ lrmv_n ( LISTID lid, unsigned int n ) CKLMAGIC(l); - if ((n<1)||(n>l->num)) { + if ((n < 1) || (n > (unsigned)l->num)) { return NULL; } diff --git a/src/avrdude/main.c b/src/avrdude/main.c index 8f9040349..60be7ec3a 100644 --- a/src/avrdude/main.c +++ b/src/avrdude/main.c @@ -107,7 +107,7 @@ int avrdude_message(const int msglvl, const char *format, ...) if (rc > 0 && rc < MSGBUFFER_SIZE) { avrdude_message_handler(msgbuffer, rc, avrdude_message_handler_user_p); } else { - avrdude_message_handler(format_error, strlen(format_error), avrdude_message_handler_user_p); + avrdude_message_handler(format_error, (unsigned)strlen(format_error), avrdude_message_handler_user_p); } } @@ -567,7 +567,7 @@ int avrdude_main(int argc, char * argv []) // #endif - len = strlen(progname) + 2; + len = (int)strlen(progname) + 2; for (i=0; ifd.pfd = (void *)hComPort; @@ -326,8 +326,8 @@ static void serbb_close(PROGRAMMER *pgm) pgm->setpin(pgm, PIN_AVR_RESET, 1); CloseHandle (hComPort); } - avrdude_message(MSG_DEBUG, "%s: ser_close(): closed comm port handle 0x%x\n", - progname, (int)hComPort); + avrdude_message(MSG_DEBUG, "%s: ser_close(): closed comm port handle %p\n", + progname, (void *)hComPort); hComPort = INVALID_HANDLE_VALUE; } diff --git a/src/avrdude/stk500.c b/src/avrdude/stk500.c index efb7078bc..256076cc6 100644 --- a/src/avrdude/stk500.c +++ b/src/avrdude/stk500.c @@ -504,7 +504,7 @@ static int stk500_initialize(PROGRAMMER * pgm, AVRPART * p) } else { buf[9] = 0xff; - buf[10] = 0xff; + buf[10] = 0xff; buf[13] = 0; buf[14] = 0; buf[17] = 0; @@ -821,7 +821,7 @@ static int stk500_paged_write(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m, break; } - for (prusa3d_semicolon_workaround_round = 0; prusa3d_semicolon_workaround_round < (has_semicolon ? 2 : 1); ++ prusa3d_semicolon_workaround_round) { + for (prusa3d_semicolon_workaround_round = 0; prusa3d_semicolon_workaround_round < (has_semicolon ? 2u : 1u); prusa3d_semicolon_workaround_round++) { /* build command block and avoid multiple send commands as it leads to a crash of the silabs usb serial driver on mac os x */ i = 0; @@ -834,7 +834,7 @@ static int stk500_paged_write(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m, buf[i++] = block_size & 0x0f; buf[i++] = memtype; if (has_semicolon) { - for (j = 0; j < block_size; ++i, ++ j) { + for (j = 0; j < (unsigned)block_size; ++i, ++ j) { buf[i] = m->buf[addr + j]; if (buf[i] == ';') buf[i] |= (prusa3d_semicolon_workaround_round ? 0xf0 : 0x0f); @@ -1088,8 +1088,8 @@ static int stk500_set_sck_period(PROGRAMMER * pgm, double v) min = 8.0 / STK500_XTAL; max = 255 * min; - dur = v / min + 0.5; - + dur = (int)(v / min + 0.5); + if (v < min) { dur = 1; avrdude_message(MSG_INFO, "%s: stk500_set_sck_period(): p = %.1f us too small, using %.1f us\n", @@ -1099,7 +1099,7 @@ static int stk500_set_sck_period(PROGRAMMER * pgm, double v) avrdude_message(MSG_INFO, "%s: stk500_set_sck_period(): p = %.1f us too large, using %.1f us\n", progname, v / 1e-6, dur * min / 1e-6); } - + return stk500_setparm(pgm, Parm_STK_SCK_DURATION, dur); } diff --git a/src/avrdude/stk500v2.c b/src/avrdude/stk500v2.c index 9bc629ba4..33bdf8eea 100644 --- a/src/avrdude/stk500v2.c +++ b/src/avrdude/stk500v2.c @@ -130,58 +130,58 @@ struct jtagispentry #define SZ_SPI_MULTI (USHRT_MAX - 1) }; -static const struct jtagispentry jtagispcmds[] = { - /* generic */ - { CMD_SET_PARAMETER, 2 }, - { CMD_GET_PARAMETER, 3 }, - { CMD_OSCCAL, 2 }, - { CMD_LOAD_ADDRESS, 2 }, - /* ISP mode */ - { CMD_ENTER_PROGMODE_ISP, 2 }, - { CMD_LEAVE_PROGMODE_ISP, 2 }, - { CMD_CHIP_ERASE_ISP, 2 }, - { CMD_PROGRAM_FLASH_ISP, 2 }, - { CMD_READ_FLASH_ISP, SZ_READ_FLASH_EE }, - { CMD_PROGRAM_EEPROM_ISP, 2 }, - { CMD_READ_EEPROM_ISP, SZ_READ_FLASH_EE }, - { CMD_PROGRAM_FUSE_ISP, 3 }, - { CMD_READ_FUSE_ISP, 4 }, - { CMD_PROGRAM_LOCK_ISP, 3 }, - { CMD_READ_LOCK_ISP, 4 }, - { CMD_READ_SIGNATURE_ISP, 4 }, - { CMD_READ_OSCCAL_ISP, 4 }, - { CMD_SPI_MULTI, SZ_SPI_MULTI }, - /* all HV modes */ - { CMD_SET_CONTROL_STACK, 2 }, - /* HVSP mode */ - { CMD_ENTER_PROGMODE_HVSP, 2 }, - { CMD_LEAVE_PROGMODE_HVSP, 2 }, - { CMD_CHIP_ERASE_HVSP, 2 }, - { CMD_PROGRAM_FLASH_HVSP, 2 }, - { CMD_READ_FLASH_HVSP, SZ_READ_FLASH_EE }, - { CMD_PROGRAM_EEPROM_HVSP, 2 }, - { CMD_READ_EEPROM_HVSP, SZ_READ_FLASH_EE }, - { CMD_PROGRAM_FUSE_HVSP, 2 }, - { CMD_READ_FUSE_HVSP, 3 }, - { CMD_PROGRAM_LOCK_HVSP, 2 }, - { CMD_READ_LOCK_HVSP, 3 }, - { CMD_READ_SIGNATURE_HVSP, 3 }, - { CMD_READ_OSCCAL_HVSP, 3 }, - /* PP mode */ - { CMD_ENTER_PROGMODE_PP, 2 }, - { CMD_LEAVE_PROGMODE_PP, 2 }, - { CMD_CHIP_ERASE_PP, 2 }, - { CMD_PROGRAM_FLASH_PP, 2 }, - { CMD_READ_FLASH_PP, SZ_READ_FLASH_EE }, - { CMD_PROGRAM_EEPROM_PP, 2 }, - { CMD_READ_EEPROM_PP, SZ_READ_FLASH_EE }, - { CMD_PROGRAM_FUSE_PP, 2 }, - { CMD_READ_FUSE_PP, 3 }, - { CMD_PROGRAM_LOCK_PP, 2 }, - { CMD_READ_LOCK_PP, 3 }, - { CMD_READ_SIGNATURE_PP, 3 }, - { CMD_READ_OSCCAL_PP, 3 }, -}; +// static const struct jtagispentry jtagispcmds[] = { +// /* generic */ +// { CMD_SET_PARAMETER, 2 }, +// { CMD_GET_PARAMETER, 3 }, +// { CMD_OSCCAL, 2 }, +// { CMD_LOAD_ADDRESS, 2 }, +// /* ISP mode */ +// { CMD_ENTER_PROGMODE_ISP, 2 }, +// { CMD_LEAVE_PROGMODE_ISP, 2 }, +// { CMD_CHIP_ERASE_ISP, 2 }, +// { CMD_PROGRAM_FLASH_ISP, 2 }, +// { CMD_READ_FLASH_ISP, SZ_READ_FLASH_EE }, +// { CMD_PROGRAM_EEPROM_ISP, 2 }, +// { CMD_READ_EEPROM_ISP, SZ_READ_FLASH_EE }, +// { CMD_PROGRAM_FUSE_ISP, 3 }, +// { CMD_READ_FUSE_ISP, 4 }, +// { CMD_PROGRAM_LOCK_ISP, 3 }, +// { CMD_READ_LOCK_ISP, 4 }, +// { CMD_READ_SIGNATURE_ISP, 4 }, +// { CMD_READ_OSCCAL_ISP, 4 }, +// { CMD_SPI_MULTI, SZ_SPI_MULTI }, +// /* all HV modes */ +// { CMD_SET_CONTROL_STACK, 2 }, +// /* HVSP mode */ +// { CMD_ENTER_PROGMODE_HVSP, 2 }, +// { CMD_LEAVE_PROGMODE_HVSP, 2 }, +// { CMD_CHIP_ERASE_HVSP, 2 }, +// { CMD_PROGRAM_FLASH_HVSP, 2 }, +// { CMD_READ_FLASH_HVSP, SZ_READ_FLASH_EE }, +// { CMD_PROGRAM_EEPROM_HVSP, 2 }, +// { CMD_READ_EEPROM_HVSP, SZ_READ_FLASH_EE }, +// { CMD_PROGRAM_FUSE_HVSP, 2 }, +// { CMD_READ_FUSE_HVSP, 3 }, +// { CMD_PROGRAM_LOCK_HVSP, 2 }, +// { CMD_READ_LOCK_HVSP, 3 }, +// { CMD_READ_SIGNATURE_HVSP, 3 }, +// { CMD_READ_OSCCAL_HVSP, 3 }, +// /* PP mode */ +// { CMD_ENTER_PROGMODE_PP, 2 }, +// { CMD_LEAVE_PROGMODE_PP, 2 }, +// { CMD_CHIP_ERASE_PP, 2 }, +// { CMD_PROGRAM_FLASH_PP, 2 }, +// { CMD_READ_FLASH_PP, SZ_READ_FLASH_EE }, +// { CMD_PROGRAM_EEPROM_PP, 2 }, +// { CMD_READ_EEPROM_PP, SZ_READ_FLASH_EE }, +// { CMD_PROGRAM_FUSE_PP, 2 }, +// { CMD_READ_FUSE_PP, 3 }, +// { CMD_PROGRAM_LOCK_PP, 2 }, +// { CMD_READ_LOCK_PP, 3 }, +// { CMD_READ_SIGNATURE_PP, 3 }, +// { CMD_READ_OSCCAL_PP, 3 }, +// }; /* * From XML file: @@ -379,15 +379,15 @@ static void stk500v2_jtag3_teardown(PROGRAMMER * pgm) } -static unsigned short -b2_to_u16(unsigned char *b) -{ - unsigned short l; - l = b[0]; - l += (unsigned)b[1] << 8; +// static unsigned short +// b2_to_u16(unsigned char *b) +// { +// unsigned short l; +// l = b[0]; +// l += (unsigned)b[1] << 8; - return l; -} +// return l; +// } static int stk500v2_send_mk2(PROGRAMMER * pgm, unsigned char * data, size_t len) { @@ -399,16 +399,16 @@ static int stk500v2_send_mk2(PROGRAMMER * pgm, unsigned char * data, size_t len) return 0; } -static unsigned short get_jtagisp_return_size(unsigned char cmd) -{ - int i; +// static unsigned short get_jtagisp_return_size(unsigned char cmd) +// { +// int i; - for (i = 0; i < sizeof jtagispcmds / sizeof jtagispcmds[0]; i++) - if (jtagispcmds[i].cmd == cmd) - return jtagispcmds[i].size; +// for (i = 0; i < sizeof jtagispcmds / sizeof jtagispcmds[0]; i++) +// if (jtagispcmds[i].cmd == cmd) +// return jtagispcmds[i].size; - return 0; -} +// return 0; +// } /* * Send the data as a JTAG ICE mkII encapsulated ISP packet. @@ -504,7 +504,7 @@ static int stk500v2_send(PROGRAMMER * pgm, unsigned char * data, size_t len) buf[0] = MESSAGE_START; buf[1] = PDATA(pgm)->command_sequence; - buf[2] = len / 256; + buf[2] = (char)(len / 256); buf[3] = len % 256; buf[4] = TOKEN; memcpy(buf+5, data, len); @@ -1128,7 +1128,8 @@ static int stk500v2_program_enable(PROGRAMMER * pgm, AVRPART * p) { unsigned char buf[16]; char msg[100]; /* see remarks above about size needed */ - int rv, tries; + int rv; + // int tries; PDATA(pgm)->lastpart = p; @@ -1143,7 +1144,7 @@ static int stk500v2_program_enable(PROGRAMMER * pgm, AVRPART * p) /* Activate AVR-style (low active) RESET */ stk500v2_setparm_real(pgm, PARAM_RESET_POLARITY, 0x01); - tries = 0; + // tries = 0; // retry: buf[0] = CMD_ENTER_PROGMODE_ISP; buf[1] = p->timeout; @@ -1882,7 +1883,7 @@ static int stk500hv_read_byte(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem, if (stk500v2_loadaddr(pgm, use_ext_addr | (paddr >> addrshift)) < 0) return -1; } else { - buf[1] = addr; + buf[1] = (char)addr; } avrdude_message(MSG_NOTICE2, "%s: stk500hv_read_byte(): Sending read memory command: ", @@ -2137,7 +2138,7 @@ static int stk500hv_write_byte(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem, if (stk500v2_loadaddr(pgm, use_ext_addr | (paddr >> addrshift)) < 0) return -1; } else { - buf[1] = addr; + buf[1] = (char)addr; buf[2] = data; if (mode == PPMODE) { buf[3] = pulsewidth; @@ -2298,7 +2299,7 @@ static int stk500v2_paged_write(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m, unsigned int page_size, unsigned int addr, unsigned int n_bytes) { -static int page = 0; +// static int page = 0; unsigned int block_size, last_addr, addrshift, use_ext_addr; unsigned int maxaddr = addr + n_bytes; unsigned char commandbuf[10]; @@ -2833,10 +2834,10 @@ static int stk500v2_set_fosc(PROGRAMMER * pgm, double v) progname, v, unit, STK500V2_XTAL / 2e6); fosc = STK500V2_XTAL / 2; } else - fosc = (unsigned)v; + fosc = (int)v; for (idx = 0; idx < sizeof(ps) / sizeof(ps[0]); idx++) { - if (fosc >= STK500V2_XTAL / (256 * ps[idx] * 2)) { + if (fosc >= (int)(STK500V2_XTAL / (256 * ps[idx] * 2))) { /* this prescaler value can handle our frequency */ prescale = idx + 1; cmatch = (unsigned)(STK500V2_XTAL / (2 * fosc * ps[idx])) - 1; @@ -3065,8 +3066,8 @@ static int stk600_set_fosc(PROGRAMMER * pgm, double v) { unsigned int oct, dac; - oct = 1.443 * log(v / 1039.0); - dac = 2048 - (2078.0 * pow(2, (double)(10 + oct))) / v; + oct = (unsigned)(1.443 * log(v / 1039.0)); + dac = (unsigned)(2048.0 - (2078.0 * pow(2, (double)(10 + oct))) / v); return stk500v2_setparm2(pgm, PARAM2_CLOCK_CONF, (oct << 12) | (dac << 2)); } @@ -3075,7 +3076,7 @@ static int stk600_set_sck_period(PROGRAMMER * pgm, double v) { unsigned int sck; - sck = ceil((16e6 / (2 * 1.0 / v)) - 1); + sck = (unsigned)ceil((16e6 / (2 * 1.0 / v)) - 1); if (sck >= 4096) sck = 4095; @@ -3093,7 +3094,7 @@ static int stk500v2_jtag3_set_sck_period(PROGRAMMER * pgm, double v) else if (v > 1E-3) sck = 1; else - sck = 1.0 / (1000.0 * v); + sck = (unsigned)(1.0 / (1000.0 * v)); value[0] = CMD_SET_SCK; value[1] = sck & 0xff; @@ -3143,7 +3144,7 @@ static int stk500v2_setparm_real(PROGRAMMER * pgm, unsigned char parm, unsigned static int stk500v2_setparm(PROGRAMMER * pgm, unsigned char parm, unsigned char value) { - unsigned char current_value; + unsigned char current_value = 0; int res; res = stk500v2_getparm(pgm, parm, ¤t_value); @@ -3214,8 +3215,15 @@ static const char *stk600_get_cardname(const struct carddata *table, static void stk500v2_display(PROGRAMMER * pgm, const char * p) { - unsigned char maj, min, hdw, topcard, maj_s1, min_s1, maj_s2, min_s2; - unsigned int rev; + unsigned char maj = 0; + unsigned char min = 0; + unsigned char hdw = 0; + unsigned char topcard = 0; + unsigned char maj_s1 = 0; + unsigned char min_s1 = 0; + unsigned char maj_s2 = 0; + unsigned char min_s2 = 0; + unsigned int rev = 0; const char *topcard_name, *pgmname; switch (PDATA(pgm)->pgmtype) { @@ -3294,13 +3302,20 @@ f_to_kHz_MHz(double f, const char **unit) static void stk500v2_print_parms1(PROGRAMMER * pgm, const char * p) { - unsigned char vtarget, vadjust, osc_pscale, osc_cmatch, sck_duration =0; //XXX 0 is not correct, check caller - unsigned int sck_stk600, clock_conf, dac, oct, varef; - unsigned char vtarget_jtag[4]; + unsigned char vtarget = 0; + unsigned char vadjust = 0; + unsigned char sck_duration = 0; + unsigned char osc_pscale = 0; + unsigned char osc_cmatch = 0; + unsigned varef = 0; + unsigned sck_stk600 = 0; + unsigned clock_conf = 0; + unsigned dac, oct; + // unsigned char vtarget_jtag[4]; int prescale; double f; const char *unit; - void *mycookie; + // void *mycookie; if (PDATA(pgm)->pgmtype == PGMTYPE_JTAGICE_MKII) { return; @@ -3963,10 +3978,10 @@ static int stk600_xprog_write_byte(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem, b[0] = XPRG_CMD_WRITE_MEM; b[1] = memcode; b[2] = 0; /* pagemode: non-paged write */ - b[3] = addr >> 24; - b[4] = addr >> 16; - b[5] = addr >> 8; - b[6] = addr; + b[3] = (char)(addr >> 24); + b[4] = (char)(addr >> 16); + b[5] = (char)(addr >> 8); + b[6] = (char)addr; b[7] = 0; b[8] = write_size; b[9] = data; @@ -4011,10 +4026,10 @@ static int stk600_xprog_read_byte(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem, addr += mem->offset; b[0] = XPRG_CMD_READ_MEM; - b[2] = addr >> 24; - b[3] = addr >> 16; - b[4] = addr >> 8; - b[5] = addr; + b[2] = (char)(addr >> 24); + b[3] = (char)(addr >> 16); + b[4] = (char)(addr >> 8); + b[5] = (char)addr; b[6] = 0; b[7] = 1; if (stk600_xprog_command(pgm, b, 8, 3) < 0) { diff --git a/src/avrdude/term.c b/src/avrdude/term.c index 012f6f1a5..182367cf2 100644 --- a/src/avrdude/term.c +++ b/src/avrdude/term.c @@ -281,7 +281,7 @@ static int cmd_dump(PROGRAMMER * pgm, struct avrpart * p, maxsize = mem->size; - if (addr >= maxsize) { + if (addr >= (unsigned long)maxsize) { if (argc == 2) { /* wrap around */ addr = 0; @@ -294,7 +294,7 @@ static int cmd_dump(PROGRAMMER * pgm, struct avrpart * p, } /* trim len if nessary to not read past the end of memory */ - if ((addr + len) > maxsize) + if ((addr + len) > (unsigned long)maxsize) len = maxsize - addr; buf = malloc(len); @@ -303,7 +303,7 @@ static int cmd_dump(PROGRAMMER * pgm, struct avrpart * p, return -1; } - for (i=0; iread_byte(pgm, p, mem, addr+i, &buf[i]); if (rc != 0) { avrdude_message(MSG_INFO, "error reading %s address 0x%05lx of part %s\n", @@ -364,7 +364,7 @@ static int cmd_write(PROGRAMMER * pgm, struct avrpart * p, return -1; } - if (addr > maxsize) { + if (addr > (unsigned long)maxsize) { avrdude_message(MSG_INFO, "%s (write): address 0x%05lx is out of range for %s memory\n", progname, addr, memtype); return -1; @@ -373,7 +373,7 @@ static int cmd_write(PROGRAMMER * pgm, struct avrpart * p, /* number of bytes to write at the specified address */ len = argc - 3; - if ((addr + len) > maxsize) { + if ((addr + len) > (unsigned long)maxsize) { avrdude_message(MSG_INFO, "%s (write): selected address and # bytes exceed " "range for %s memory\n", progname, memtype); @@ -386,8 +386,8 @@ static int cmd_write(PROGRAMMER * pgm, struct avrpart * p, return -1; } - for (i=3; ierr_led(pgm, OFF); - for (werror=0, i=0; i &bb) } // namespace Slic3r +// Serialization through the Cereal library +namespace cereal { + template void serialize(Archive& archive, Slic3r::BoundingBox &bb) { archive(bb.min, bb.max, bb.defined); } + template void serialize(Archive& archive, Slic3r::BoundingBox3 &bb) { archive(bb.min, bb.max, bb.defined); } + template void serialize(Archive& archive, Slic3r::BoundingBoxf &bb) { archive(bb.min, bb.max, bb.defined); } + template void serialize(Archive& archive, Slic3r::BoundingBoxf3 &bb) { archive(bb.min, bb.max, bb.defined); } +} + #endif diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 736290489..ac40e99bc 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -81,9 +81,8 @@ add_library(libslic3r STATIC GCode/SpiralVase.hpp GCode/ToolOrdering.cpp GCode/ToolOrdering.hpp + GCode/WipeTower.cpp GCode/WipeTower.hpp - GCode/WipeTowerPrusaMM.cpp - GCode/WipeTowerPrusaMM.hpp GCode.cpp GCode.hpp GCodeReader.cpp @@ -114,6 +113,8 @@ add_library(libslic3r STATIC MultiPoint.cpp MultiPoint.hpp MutablePriorityQueue.hpp + ObjectID.cpp + ObjectID.hpp PerimeterGenerator.cpp PerimeterGenerator.hpp PlaceholderParser.cpp @@ -189,6 +190,7 @@ target_include_directories(libslic3r PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${LIBNE target_link_libraries(libslic3r libnest2d admesh + cereal libigl miniz boost_libs diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index 0738b77c6..9d0649a1f 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -209,6 +209,51 @@ std::vector ConfigOptionDef::cli_args(const std::string &key) const return args; } +ConfigOption* ConfigOptionDef::create_empty_option() const +{ + switch (this->type) { + case coFloat: return new ConfigOptionFloat(); + case coFloats: return new ConfigOptionFloats(); + case coInt: return new ConfigOptionInt(); + case coInts: return new ConfigOptionInts(); + case coString: return new ConfigOptionString(); + case coStrings: return new ConfigOptionStrings(); + case coPercent: return new ConfigOptionPercent(); + case coPercents: return new ConfigOptionPercents(); + case coFloatOrPercent: return new ConfigOptionFloatOrPercent(); + case coPoint: return new ConfigOptionPoint(); + case coPoints: return new ConfigOptionPoints(); + case coPoint3: return new ConfigOptionPoint3(); +// case coPoint3s: return new ConfigOptionPoint3s(); + case coBool: return new ConfigOptionBool(); + case coBools: return new ConfigOptionBools(); + case coEnum: return new ConfigOptionEnumGeneric(this->enum_keys_map); + default: throw std::runtime_error(std::string("Unknown option type for option ") + this->label); + } +} + +ConfigOption* ConfigOptionDef::create_default_option() const +{ + if (this->default_value) + return (this->default_value->type() == coEnum) ? + // Special case: For a DynamicConfig, convert a templated enum to a generic enum. + new ConfigOptionEnumGeneric(this->enum_keys_map, this->default_value->getInt()) : + this->default_value->clone(); + return this->create_empty_option(); +} + +// Assignment of the serialization IDs is not thread safe. The Defs shall be initialized from the main thread! +ConfigOptionDef* ConfigDef::add(const t_config_option_key &opt_key, ConfigOptionType type) +{ + static size_t serialization_key_ordinal_last = 0; + ConfigOptionDef *opt = &this->options[opt_key]; + opt->opt_key = opt_key; + opt->type = type; + opt->serialization_key_ordinal = ++ serialization_key_ordinal_last; + this->by_serialization_key_ordinal[opt->serialization_key_ordinal] = opt; + return opt; +} + std::string ConfigOptionDef::nocli = "~~~noCLI"; std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults, std::function filter) const @@ -358,7 +403,7 @@ t_config_option_keys ConfigBase::equal(const ConfigBase &other) const return equal; } -std::string ConfigBase::serialize(const t_config_option_key &opt_key) const +std::string ConfigBase::opt_serialize(const t_config_option_key &opt_key) const { const ConfigOption* opt = this->option(opt_key); assert(opt != nullptr); @@ -469,7 +514,7 @@ void ConfigBase::setenv_() const for (size_t i = 0; i < envname.size(); ++i) envname[i] = (envname[i] <= 'z' && envname[i] >= 'a') ? envname[i]-('a'-'A') : envname[i]; - boost::nowide::setenv(envname.c_str(), this->serialize(*it).c_str(), 1); + boost::nowide::setenv(envname.c_str(), this->opt_serialize(*it).c_str(), 1); } } @@ -593,16 +638,16 @@ void ConfigBase::save(const std::string &file) const c.open(file, std::ios::out | std::ios::trunc); c << "# " << Slic3r::header_slic3r_generated() << std::endl; for (const std::string &opt_key : this->keys()) - c << opt_key << " = " << this->serialize(opt_key) << std::endl; + c << opt_key << " = " << this->opt_serialize(opt_key) << std::endl; c.close(); } bool DynamicConfig::operator==(const DynamicConfig &rhs) const { - t_options_map::const_iterator it1 = this->options.begin(); - t_options_map::const_iterator it1_end = this->options.end(); - t_options_map::const_iterator it2 = rhs.options.begin(); - t_options_map::const_iterator it2_end = rhs.options.end(); + auto it1 = this->options.begin(); + auto it1_end = this->options.end(); + auto it2 = rhs.options.begin(); + auto it2_end = rhs.options.end(); for (; it1 != it1_end && it2 != it2_end; ++ it1, ++ it2) if (it1->first != it2->first || *it1->second != *it2->second) // key or value differ @@ -612,10 +657,10 @@ bool DynamicConfig::operator==(const DynamicConfig &rhs) const ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool create) { - t_options_map::iterator it = options.find(opt_key); + auto it = options.find(opt_key); if (it != options.end()) // Option was found. - return it->second; + return it->second.get(); if (! create) // Option was not found and a new option shall not be created. return nullptr; @@ -628,34 +673,8 @@ ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool cre // throw std::runtime_error(std::string("Invalid option name: ") + opt_key); // Let the parent decide what to do if the opt_key is not defined by this->def(). return nullptr; - ConfigOption *opt = nullptr; - if (optdef->default_value) { - opt = (optdef->default_value->type() == coEnum) ? - // Special case: For a DynamicConfig, convert a templated enum to a generic enum. - new ConfigOptionEnumGeneric(optdef->enum_keys_map, optdef->default_value->getInt()) : - optdef->default_value->clone(); - } else { - switch (optdef->type) { - case coFloat: opt = new ConfigOptionFloat(); break; - case coFloats: opt = new ConfigOptionFloats(); break; - case coInt: opt = new ConfigOptionInt(); break; - case coInts: opt = new ConfigOptionInts(); break; - case coString: opt = new ConfigOptionString(); break; - case coStrings: opt = new ConfigOptionStrings(); break; - case coPercent: opt = new ConfigOptionPercent(); break; - case coPercents: opt = new ConfigOptionPercents(); break; - case coFloatOrPercent: opt = new ConfigOptionFloatOrPercent(); break; - case coPoint: opt = new ConfigOptionPoint(); break; - case coPoints: opt = new ConfigOptionPoints(); break; - case coPoint3: opt = new ConfigOptionPoint3(); break; - // case coPoint3s: opt = new ConfigOptionPoint3s(); break; - case coBool: opt = new ConfigOptionBool(); break; - case coBools: opt = new ConfigOptionBools(); break; - case coEnum: opt = new ConfigOptionEnumGeneric(optdef->enum_keys_map); break; - default: throw std::runtime_error(std::string("Unknown option type for option ") + opt_key); - } - } - this->options[opt_key] = opt; + ConfigOption *opt = optdef->create_default_option(); + this->options.emplace_hint(it, opt_key, std::unique_ptr(opt)); return opt; } @@ -732,18 +751,18 @@ bool DynamicConfig::read_cli(int argc, char** argv, t_config_option_keys* extra, } // Store the option value. const bool existing = this->has(opt_key); - if (keys != nullptr && !existing) { + if (keys != nullptr && ! existing) { // Save the order of detected keys. keys->push_back(opt_key); } ConfigOption *opt_base = this->option(opt_key, true); ConfigOptionVectorBase *opt_vector = opt_base->is_vector() ? static_cast(opt_base) : nullptr; if (opt_vector) { + if (! existing) + // remove the default values + opt_vector->clear(); // Vector values will be chained. Repeated use of a parameter will append the parameter or parameters // to the end of the value. - if (!existing) - // remove the default values - opt_vector->deserialize("", true); if (opt_base->type() == coBools) static_cast(opt_base)->values.push_back(!no); else @@ -802,3 +821,64 @@ t_config_option_keys StaticConfig::keys() const } } + +#include +CEREAL_REGISTER_TYPE(Slic3r::ConfigOption) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVectorBase) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloat) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloats) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionInt) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionInts) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionString) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionStrings) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPercent) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPercents) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloatOrPercent) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPoint) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPoints) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPoint3) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionBool) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionBools) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionEnumGeneric) +CEREAL_REGISTER_TYPE(Slic3r::ConfigBase) +CEREAL_REGISTER_TYPE(Slic3r::DynamicConfig) + +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionVectorBase) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle, Slic3r::ConfigOptionFloat) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector, Slic3r::ConfigOptionFloats) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle, Slic3r::ConfigOptionInt) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector, Slic3r::ConfigOptionInts) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle, Slic3r::ConfigOptionString) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector, Slic3r::ConfigOptionStrings) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionFloat, Slic3r::ConfigOptionPercent) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionFloats, Slic3r::ConfigOptionPercents) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionPercent, Slic3r::ConfigOptionFloatOrPercent) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle, Slic3r::ConfigOptionPoint) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector, Slic3r::ConfigOptionPoints) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle, Slic3r::ConfigOptionPoint3) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle, Slic3r::ConfigOptionBool) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector, Slic3r::ConfigOptionBools) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionInt, Slic3r::ConfigOptionEnumGeneric) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigBase, Slic3r::DynamicConfig) diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index ee4bc4e46..844287efb 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -18,6 +18,9 @@ #include #include +#include +#include + namespace Slic3r { // Name of the configuration option. @@ -152,6 +155,10 @@ public: bool operator==(const T &rhs) const { return this->value == rhs; } bool operator!=(const T &rhs) const { return this->value != rhs; } + +private: + friend class cereal::access; + template void serialize(Archive & ar) { ar(this->value); } }; // Value of a vector valued option (bools, ints, floats, strings, points) @@ -167,8 +174,10 @@ public: // Set a single vector item from either a scalar option or the first value of a vector option.vector of ConfigOptions. // This function is useful to split values from multiple extrder / filament settings into separate configurations. virtual void set_at(const ConfigOption *rhs, size_t i, size_t j) = 0; - + // Resize the vector of values, copy the newly added values from opt_default if provided. virtual void resize(size_t n, const ConfigOption *opt_default = nullptr) = 0; + // Clear the values vector. + virtual void clear() = 0; // Get size of this vector. virtual size_t size() const = 0; @@ -277,6 +286,8 @@ public: } } + // Clear the values vector. + void clear() override { this->values.clear(); } size_t size() const override { return this->values.size(); } bool empty() const override { return this->values.empty(); } @@ -290,6 +301,10 @@ public: bool operator==(const std::vector &rhs) const { return this->values == rhs; } bool operator!=(const std::vector &rhs) const { return this->values != rhs; } + +private: + friend class cereal::access; + template void serialize(Archive & ar) { ar(this->values); } }; class ConfigOptionFloat : public ConfigOptionSingle @@ -324,6 +339,10 @@ public: this->set(opt); return *this; } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; class ConfigOptionFloats : public ConfigOptionVector @@ -382,6 +401,10 @@ public: this->set(opt); return *this; } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; class ConfigOptionInt : public ConfigOptionSingle @@ -418,6 +441,10 @@ public: this->set(opt); return *this; } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; class ConfigOptionInts : public ConfigOptionVector @@ -468,6 +495,10 @@ public: } return true; } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; class ConfigOptionString : public ConfigOptionSingle @@ -492,6 +523,10 @@ public: UNUSED(append); return unescape_string_cstyle(str, this->value); } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; // semicolon-separated strings @@ -526,6 +561,10 @@ public: this->values.clear(); return unescape_strings_cstyle(str, this->values); } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; class ConfigOptionPercent : public ConfigOptionFloat @@ -558,6 +597,10 @@ public: iss >> this->value; return !iss.fail(); } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class(this)); } }; class ConfigOptionPercents : public ConfigOptionFloats @@ -612,6 +655,10 @@ public: } return true; } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class(this)); } }; class ConfigOptionFloatOrPercent : public ConfigOptionPercent @@ -661,6 +708,10 @@ public: iss >> this->value; return !iss.fail(); } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class(this), percent); } }; class ConfigOptionPoint : public ConfigOptionSingle @@ -691,6 +742,10 @@ public: return sscanf(str.data(), " %lf , %lf %c", &this->value(0), &this->value(1), &dummy) == 2 || sscanf(str.data(), " %lf x %lf %c", &this->value(0), &this->value(1), &dummy) == 2; } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; class ConfigOptionPoints : public ConfigOptionVector @@ -750,8 +805,21 @@ public: } return true; } -}; +private: + friend class cereal::access; + template void save(Archive& archive) const { + size_t cnt = this->values.size(); + archive(cnt); + archive.saveBinary((const char*)this->values.data(), sizeof(Vec2d) * cnt); + } + template void load(Archive& archive) { + size_t cnt; + archive(cnt); + this->values.assign(cnt, Vec2d()); + archive.loadBinary((char*)this->values.data(), sizeof(Vec2d) * cnt); + } +}; class ConfigOptionPoint3 : public ConfigOptionSingle { @@ -783,6 +851,10 @@ public: return sscanf(str.data(), " %lf , %lf , %lf %c", &this->value(0), &this->value(1), &this->value(2), &dummy) == 2 || sscanf(str.data(), " %lf x %lf x %lf %c", &this->value(0), &this->value(1), &this->value(2), &dummy) == 2; } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; class ConfigOptionBool : public ConfigOptionSingle @@ -809,6 +881,10 @@ public: this->value = (str.compare("1") == 0); return true; } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; class ConfigOptionBools : public ConfigOptionVector @@ -864,6 +940,10 @@ public: } return true; } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; // Map from an enum integer value to an enum name. @@ -1002,19 +1082,73 @@ public: this->value = it->second; return true; } + +private: + friend class cereal::access; + template void serialize(Archive& ar) { ar(cereal::base_class(this)); } }; // Definition of a configuration value for the purpose of GUI presentation, editing, value mapping and config file handling. class ConfigOptionDef { public: + // Identifier of this option. It is stored here so that it is accessible through the by_serialization_key_ordinal map. + t_config_option_key opt_key; // What type? bool, int, string etc. ConfigOptionType type = coNone; // Default value of this option. The default value object is owned by ConfigDef, it is released in its destructor. Slic3r::clonable_ptr default_value; - void set_default_value(const ConfigOption* ptr) { this->default_value = Slic3r::clonable_ptr(ptr); } - template - const T* get_default_value() const { return static_cast(this->default_value.get()); } + void set_default_value(const ConfigOption* ptr) { this->default_value = Slic3r::clonable_ptr(ptr); } + template const T* get_default_value() const { return static_cast(this->default_value.get()); } + + // Create an empty option to be used as a base for deserialization of DynamicConfig. + ConfigOption* create_empty_option() const; + // Create a default option to be inserted into a DynamicConfig. + ConfigOption* create_default_option() const; + + template ConfigOption* load_option_from_archive(Archive &archive) const { + switch (this->type) { + case coFloat: { auto opt = new ConfigOptionFloat(); archive(*opt); return opt; } + case coFloats: { auto opt = new ConfigOptionFloats(); archive(*opt); return opt; } + case coInt: { auto opt = new ConfigOptionInt(); archive(*opt); return opt; } + case coInts: { auto opt = new ConfigOptionInts(); archive(*opt); return opt; } + case coString: { auto opt = new ConfigOptionString(); archive(*opt); return opt; } + case coStrings: { auto opt = new ConfigOptionStrings(); archive(*opt); return opt; } + case coPercent: { auto opt = new ConfigOptionPercent(); archive(*opt); return opt; } + case coPercents: { auto opt = new ConfigOptionPercents(); archive(*opt); return opt; } + case coFloatOrPercent: { auto opt = new ConfigOptionFloatOrPercent(); archive(*opt); return opt; } + case coPoint: { auto opt = new ConfigOptionPoint(); archive(*opt); return opt; } + case coPoints: { auto opt = new ConfigOptionPoints(); archive(*opt); return opt; } + case coPoint3: { auto opt = new ConfigOptionPoint3(); archive(*opt); return opt; } + case coBool: { auto opt = new ConfigOptionBool(); archive(*opt); return opt; } + case coBools: { auto opt = new ConfigOptionBools(); archive(*opt); return opt; } + case coEnum: { auto opt = new ConfigOptionEnumGeneric(this->enum_keys_map); archive(*opt); return opt; } + default: throw std::runtime_error(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key); + } + } + + template ConfigOption* save_option_to_archive(Archive &archive, const ConfigOption *opt) const { + switch (this->type) { + case coFloat: archive(*static_cast(opt)); break; + case coFloats: archive(*static_cast(opt)); break; + case coInt: archive(*static_cast(opt)); break; + case coInts: archive(*static_cast(opt)); break; + case coString: archive(*static_cast(opt)); break; + case coStrings: archive(*static_cast(opt)); break; + case coPercent: archive(*static_cast(opt)); break; + case coPercents: archive(*static_cast(opt)); break; + case coFloatOrPercent: archive(*static_cast(opt)); break; + case coPoint: archive(*static_cast(opt)); break; + case coPoints: archive(*static_cast(opt)); break; + case coPoint3: archive(*static_cast(opt)); break; + case coBool: archive(*static_cast(opt)); break; + case coBools: archive(*static_cast(opt)); break; + case coEnum: archive(*static_cast(opt)); break; + default: throw std::runtime_error(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key); + } + // Make the compiler happy, shut up the warnings. + return nullptr; + } // Usually empty. // Special values - "i_enum_open", "f_enum_open" to provide combo box for int or float selection, @@ -1084,6 +1218,9 @@ public: return false; } + // 0 is an invalid key. + size_t serialization_key_ordinal = 0; + // Returns the alternative CLI arguments for the given option. // If there are no cli arguments defined, use the key and replace underscores with dashes. std::vector cli_args(const std::string &key) const; @@ -1103,7 +1240,8 @@ typedef std::map t_optiondef_map; class ConfigDef { public: - t_optiondef_map options; + t_optiondef_map options; + std::map by_serialization_key_ordinal; bool has(const t_config_option_key &opt_key) const { return this->options.count(opt_key) > 0; } const ConfigOptionDef* get(const t_config_option_key &opt_key) const { @@ -1124,11 +1262,7 @@ public: std::function filter = [](const ConfigOptionDef &){ return true; }) const; protected: - ConfigOptionDef* add(const t_config_option_key &opt_key, ConfigOptionType type) { - ConfigOptionDef* opt = &this->options[opt_key]; - opt->type = type; - return opt; - } + ConfigOptionDef* add(const t_config_option_key &opt_key, ConfigOptionType type); }; // An abstract configuration store. @@ -1197,7 +1331,7 @@ public: bool equals(const ConfigBase &other) const { return this->diff(other).empty(); } t_config_option_keys diff(const ConfigBase &other) const; t_config_option_keys equal(const ConfigBase &other) const; - std::string serialize(const t_config_option_key &opt_key) const; + std::string opt_serialize(const t_config_option_key &opt_key) const; // Set a configuration value from a string, it will call an overridable handle_legacy() // to resolve renamed and removed configuration keys. bool set_deserialize(const t_config_option_key &opt_key, const std::string &str, bool append = false); @@ -1235,7 +1369,7 @@ public: assert(this->def() == nullptr || this->def() == rhs.def()); this->clear(); for (const auto &kvp : rhs.options) - this->options[kvp.first] = kvp.second->clone(); + this->options[kvp.first].reset(kvp.second->clone()); return *this; } @@ -1258,15 +1392,13 @@ public: for (const auto &kvp : rhs.options) { auto it = this->options.find(kvp.first); if (it == this->options.end()) - this->options[kvp.first] = kvp.second->clone(); + this->options[kvp.first].reset(kvp.second->clone()); else { assert(it->second->type() == kvp.second->type()); if (it->second->type() == kvp.second->type()) *it->second = *kvp.second; - else { - delete it->second; - it->second = kvp.second->clone(); - } + else + it->second.reset(kvp.second->clone()); } } return *this; @@ -1277,14 +1409,13 @@ public: DynamicConfig& operator+=(DynamicConfig &&rhs) { assert(this->def() == nullptr || this->def() == rhs.def()); - for (const auto &kvp : rhs.options) { + for (auto &kvp : rhs.options) { auto it = this->options.find(kvp.first); if (it == this->options.end()) { - this->options[kvp.first] = kvp.second; + this->options.insert(std::make_pair(kvp.first, std::move(kvp.second))); } else { assert(it->second->type() == kvp.second->type()); - delete it->second; - it->second = kvp.second; + it->second = std::move(kvp.second); } } rhs.options.clear(); @@ -1301,8 +1432,6 @@ public: void clear() { - for (auto &opt : this->options) - delete opt.second; this->options.clear(); } @@ -1311,7 +1440,6 @@ public: auto it = this->options.find(opt_key); if (it == this->options.end()) return false; - delete it->second; this->options.erase(it); return true; } @@ -1336,11 +1464,10 @@ public: { auto it = this->options.find(opt_key); if (it == this->options.end()) { - this->options[opt_key] = opt; + this->options[opt_key].reset(opt); return true; } else { - delete it->second; - it->second = opt; + it->second.reset(opt); return false; } } @@ -1370,12 +1497,15 @@ public: void read_cli(const std::vector &tokens, t_config_option_keys* extra, t_config_option_keys* keys = nullptr); bool read_cli(int argc, char** argv, t_config_option_keys* extra, t_config_option_keys* keys = nullptr); - typedef std::map t_options_map; - t_options_map::const_iterator cbegin() const { return options.cbegin(); } - t_options_map::const_iterator cend() const { return options.cend(); } + std::map>::const_iterator cbegin() const { return options.cbegin(); } + std::map>::const_iterator cend() const { return options.cend(); } + size_t size() const { return options.size(); } private: - t_options_map options; + std::map> options; + + friend class cereal::access; + template void serialize(Archive &ar) { ar(options); } }; /// Configuration store with a static definition of configuration values. diff --git a/src/libslic3r/EdgeGrid.cpp b/src/libslic3r/EdgeGrid.cpp index c34aca8f2..9d02ef09b 100644 --- a/src/libslic3r/EdgeGrid.cpp +++ b/src/libslic3r/EdgeGrid.cpp @@ -146,10 +146,10 @@ void EdgeGrid::Grid::create_from_m_contours(coord_t resolution) coord_t iy = p1(1) / m_resolution; coord_t ixb = p2(0) / m_resolution; coord_t iyb = p2(1) / m_resolution; - assert(ix >= 0 && ix < m_cols); - assert(iy >= 0 && iy < m_rows); - assert(ixb >= 0 && ixb < m_cols); - assert(iyb >= 0 && iyb < m_rows); + assert(ix >= 0 && size_t(ix) < m_cols); + assert(iy >= 0 && size_t(iy) < m_rows); + assert(ixb >= 0 && size_t(ixb) < m_cols); + assert(iyb >= 0 && size_t(iyb) < m_rows); // Account for the end points. ++ m_cells[iy*m_cols+ix].end; if (ix == ixb && iy == iyb) @@ -290,10 +290,10 @@ void EdgeGrid::Grid::create_from_m_contours(coord_t resolution) coord_t iy = p1(1) / m_resolution; coord_t ixb = p2(0) / m_resolution; coord_t iyb = p2(1) / m_resolution; - assert(ix >= 0 && ix < m_cols); - assert(iy >= 0 && iy < m_rows); - assert(ixb >= 0 && ixb < m_cols); - assert(iyb >= 0 && iyb < m_rows); + assert(ix >= 0 && size_t(ix) < m_cols); + assert(iy >= 0 && size_t(iy) < m_rows); + assert(ixb >= 0 && size_t(ixb) < m_cols); + assert(iyb >= 0 && size_t(iyb) < m_rows); // Account for the end points. m_cell_data[m_cells[iy*m_cols + ix].end++] = std::pair(i, j); if (ix == ixb && iy == iyb) @@ -775,11 +775,11 @@ void EdgeGrid::Grid::calculate_sdf() // For each corner of this cell and its 1 ring neighbours: for (int corner_y = -1; corner_y < 3; ++ corner_y) { coord_t corner_r = r + corner_y; - if (corner_r < 0 || corner_r >= nrows) + if (corner_r < 0 || (size_t)corner_r >= nrows) continue; for (int corner_x = -1; corner_x < 3; ++ corner_x) { coord_t corner_c = c + corner_x; - if (corner_c < 0 || corner_c >= ncols) + if (corner_c < 0 || (size_t)corner_c >= ncols) continue; float &d_min = m_signed_distance_field[corner_r * ncols + corner_c]; Slic3r::Point pt(m_bbox.min(0) + corner_c * m_resolution, m_bbox.min(1) + corner_r * m_resolution); @@ -1137,9 +1137,9 @@ bool EdgeGrid::Grid::signed_distance_edges(const Point &pt, coord_t search_radiu return false; bbox.max(0) /= m_resolution; bbox.max(1) /= m_resolution; - if (bbox.max(0) >= m_cols) + if ((size_t)bbox.max(0) >= m_cols) bbox.max(0) = m_cols - 1; - if (bbox.max(1) >= m_rows) + if ((size_t)bbox.max(1) >= m_rows) bbox.max(1) = m_rows - 1; // Lower boundary, round to grid and test validity. bbox.min(0) -= search_radius; diff --git a/src/libslic3r/EdgeGrid.hpp b/src/libslic3r/EdgeGrid.hpp index f99ab30c4..7faafdb3e 100644 --- a/src/libslic3r/EdgeGrid.hpp +++ b/src/libslic3r/EdgeGrid.hpp @@ -78,8 +78,8 @@ protected: #endif bool cell_inside_or_crossing(int r, int c) const { - if (r < 0 || r >= m_rows || - c < 0 || c >= m_cols) + if (r < 0 || (size_t)r >= m_rows || + c < 0 || (size_t)c >= m_cols) // The cell is outside the domain. Hoping that the contours were correctly oriented, so // there is a CCW outmost contour so the out of domain cells are outside. return false; diff --git a/src/libslic3r/ExtrusionSimulator.cpp b/src/libslic3r/ExtrusionSimulator.cpp index fcb2fe825..8c2ab037f 100644 --- a/src/libslic3r/ExtrusionSimulator.cpp +++ b/src/libslic3r/ExtrusionSimulator.cpp @@ -660,7 +660,7 @@ void gcode_spread_points( for (ExtrusionPoints::const_iterator it = points.begin(); it != points.end(); ++ it) { const V2f ¢er = it->center; const float radius = it->radius; - const float radius2 = radius * radius; + //const float radius2 = radius * radius; const float height_target = it->height; B2f bbox(center - V2f(radius, radius), center + V2f(radius, radius)); B2i bboxi( @@ -774,8 +774,8 @@ void gcode_spread_points( } } #endif - float area_circle_total2 = float(M_PI) * sqr(radius); - float area_err = fabs(area_circle_total2 - area_circle_total) / area_circle_total2; +// float area_circle_total2 = float(M_PI) * sqr(radius); +// float area_err = fabs(area_circle_total2 - area_circle_total) / area_circle_total2; // printf("area_circle_total: %f, %f, %f\n", area_circle_total, area_circle_total2, area_err); float volume_full = float(M_PI) * sqr(radius) * height_target; // if (true) { @@ -905,8 +905,8 @@ void ExtrusionSimulator::set_image_size(const Point &image_size) // printf("Allocating image data, allocated\n"); //FIXME fill the image with red vertical lines. - for (size_t r = 0; r < image_size.y(); ++ r) { - for (size_t c = 0; c < image_size.x(); c += 2) { + for (size_t r = 0; r < size_t(image_size.y()); ++ r) { + for (size_t c = 0; c < size_t(image_size.x()); c += 2) { // Color red pimpl->image_data[r * image_size.x() * 4 + c * 4] = 255; // Opacity full @@ -958,7 +958,7 @@ void ExtrusionSimulator::extrude_to_accumulator(const ExtrusionPath &path, const float scalex = float(viewport.size().x()) / float(bbox.size().x()); float scaley = float(viewport.size().y()) / float(bbox.size().y()); float w = scale_(path.width) * scalex; - float h = scale_(path.height) * scalex; + //float h = scale_(path.height) * scalex; w = scale_(path.mm3_per_mm / path.height) * scalex; // printf("scalex: %f, scaley: %f\n", scalex, scaley); // printf("bbox: %d,%d %d,%d\n", bbox.min.x(), bbox.min.y, bbox.max.x(), bbox.max.y); @@ -993,8 +993,8 @@ void ExtrusionSimulator::evaluate_accumulator(ExtrusionSimulationType simulation for (int r = 0; r < sz.y(); ++r) { for (int c = 0; c < sz.x(); ++c) { float p = 0; - for (int j = 0; j < pimpl->bitmap_oversampled; ++ j) { - for (int i = 0; i < pimpl->bitmap_oversampled; ++ i) { + for (unsigned int j = 0; j < pimpl->bitmap_oversampled; ++ j) { + for (unsigned int i = 0; i < pimpl->bitmap_oversampled; ++ i) { if (pimpl->bitmap[r * pimpl->bitmap_oversampled + j][c * pimpl->bitmap_oversampled + i]) p += 1.f; } diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 75952e4c2..fbdef29b9 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -126,7 +126,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) Polygons surfaces_polygons = to_polygons(surfaces); Polygons collapsed = diff( surfaces_polygons, - offset2(surfaces_polygons, -distance_between_surfaces/2, +distance_between_surfaces/2), + offset2(surfaces_polygons, (float)-distance_between_surfaces/2, (float)+distance_between_surfaces/2), true); Polygons to_subtract; to_subtract.reserve(collapsed.size() + number_polygons(surfaces)); @@ -137,7 +137,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) surfaces_append( surfaces, intersection_ex( - offset(collapsed, distance_between_surfaces), + offset(collapsed, (float)distance_between_surfaces), to_subtract, true), stInternalSolid); @@ -219,14 +219,14 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) f->z = layerm.layer()->print_z; f->angle = float(Geometry::deg2rad(layerm.region()->config().fill_angle.value)); // Maximum length of the perimeter segment linking two infill lines. - f->link_max_length = scale_(link_max_length); + f->link_max_length = (coord_t)scale_(link_max_length); // Used by the concentric infill pattern to clip the loops to create extrusion paths. - f->loop_clipping = scale_(flow.nozzle_diameter) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER; + f->loop_clipping = coord_t(scale_(flow.nozzle_diameter) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER); // f->layer_height = h; // apply half spacing using this flow's own spacing and generate infill FillParams params; - params.density = 0.01 * density; + params.density = float(0.01 * density); // params.dont_adjust = true; params.dont_adjust = false; Polylines polylines = f->fill_surface(&surface, params); @@ -240,7 +240,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) // so we can safely ignore the slight variation that might have // been applied to $f->flow_spacing } else { - flow = Flow::new_from_spacing(f->spacing, flow.nozzle_diameter, h, is_bridge || f->use_bridge_flow()); + flow = Flow::new_from_spacing(f->spacing, flow.nozzle_diameter, (float)h, is_bridge || f->use_bridge_flow()); } // Save into layer. diff --git a/src/libslic3r/Fill/FillPlanePath.cpp b/src/libslic3r/Fill/FillPlanePath.cpp index c52353b02..3b9266a0f 100644 --- a/src/libslic3r/Fill/FillPlanePath.cpp +++ b/src/libslic3r/Fill/FillPlanePath.cpp @@ -130,14 +130,14 @@ static inline Point hilbert_n_to_xy(const size_t n) } } int state = (ndigits & 1) ? 4 : 0; - int dirstate = (ndigits & 1) ? 0 : 4; +// int dirstate = (ndigits & 1) ? 0 : 4; coord_t x = 0; coord_t y = 0; for (int i = (int)ndigits - 1; i >= 0; -- i) { int digit = (n >> (i * 2)) & 3; state += digit; - if (digit != 3) - dirstate = state; // lowest non-3 digit +// if (digit != 3) +// dirstate = state; // lowest non-3 digit x |= digit_to_x[state] << i; y |= digit_to_y[state] << i; state = next_state[state]; diff --git a/src/libslic3r/Fill/FillRectilinear2.cpp b/src/libslic3r/Fill/FillRectilinear2.cpp index 65440d0ef..8aea75886 100644 --- a/src/libslic3r/Fill/FillRectilinear2.cpp +++ b/src/libslic3r/Fill/FillRectilinear2.cpp @@ -287,7 +287,8 @@ public: assert(aoffset1 < 0); assert(aoffset2 < 0); assert(aoffset2 < aoffset1); - bool sticks_removed = remove_sticks(polygons_src); +// bool sticks_removed = + remove_sticks(polygons_src); // if (sticks_removed) printf("Sticks removed!\n"); polygons_outer = offset(polygons_src, aoffset1, ClipperLib::jtMiter, @@ -481,7 +482,7 @@ static inline IntersectionTypeOtherVLine intersection_type_on_prev_next_vertical { // This routine will propose a connecting line even if the connecting perimeter segment intersects // iVertical line multiple times before reaching iIntersectionOther. - if (iIntersectionOther == -1) + if (iIntersectionOther == size_t(-1)) return INTERSECTION_TYPE_OTHER_VLINE_UNDEFINED; assert(dir_is_next ? (iVerticalLine + 1 < segs.size()) : (iVerticalLine > 0)); const SegmentedIntersectionLine &il_this = segs[iVerticalLine]; @@ -858,8 +859,8 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP if (il > ir) // No vertical line intersects this segment. continue; - assert(il >= 0 && il < segs.size()); - assert(ir >= 0 && ir < segs.size()); + assert(il >= 0 && size_t(il) < segs.size()); + assert(ir >= 0 && size_t(ir) < segs.size()); for (int i = il; i <= ir; ++ i) { coord_t this_x = segs[i].pos; assert(this_x == i * line_spacing + x0); @@ -1159,8 +1160,8 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP int iSegAbove = -1; int iSegBelow = -1; { - SegmentIntersection::SegmentIntersectionType type_crossing = (intrsctn->type == SegmentIntersection::INNER_LOW) ? - SegmentIntersection::INNER_HIGH : SegmentIntersection::INNER_LOW; +// SegmentIntersection::SegmentIntersectionType type_crossing = (intrsctn->type == SegmentIntersection::INNER_LOW) ? +// SegmentIntersection::INNER_HIGH : SegmentIntersection::INNER_LOW; // Does the perimeter intersect the current vertical line above intrsctn? for (size_t i = i_intersection + 1; i + 1 < seg.intersections.size(); ++ i) // if (seg.intersections[i].iContour == intrsctn->iContour && seg.intersections[i].type == type_crossing) { diff --git a/src/libslic3r/Fill/FillRectilinear3.cpp b/src/libslic3r/Fill/FillRectilinear3.cpp index dab584298..078feeae9 100644 --- a/src/libslic3r/Fill/FillRectilinear3.cpp +++ b/src/libslic3r/Fill/FillRectilinear3.cpp @@ -849,7 +849,7 @@ static inline IntersectionTypeOtherVLine intersection_type_on_prev_next_vertical { // This routine will propose a connecting line even if the connecting perimeter segment intersects // iVertical line multiple times before reaching iIntersectionOther. - if (iIntersectionOther == -1) + if (iIntersectionOther == size_t(-1)) return INTERSECTION_TYPE_OTHER_VLINE_UNDEFINED; assert(dir_is_next ? (iVerticalLine + 1 < segs.size()) : (iVerticalLine > 0)); const SegmentedIntersectionLine &il_this = segs[iVerticalLine]; @@ -1284,8 +1284,8 @@ static bool fill_hatching_segments_legacy( int iSegAbove = -1; int iSegBelow = -1; { - SegmentIntersection::SegmentIntersectionType type_crossing = (intrsctn->type == SegmentIntersection::INNER_LOW) ? - SegmentIntersection::INNER_HIGH : SegmentIntersection::INNER_LOW; +// SegmentIntersection::SegmentIntersectionType type_crossing = (intrsctn->type == SegmentIntersection::INNER_LOW) ? +// SegmentIntersection::INNER_HIGH : SegmentIntersection::INNER_LOW; // Does the perimeter intersect the current vertical line above intrsctn? for (size_t i = i_intersection + 1; i + 1 < seg.intersections.size(); ++ i) // if (seg.intersections[i].iContour == intrsctn->iContour && seg.intersections[i].type == type_crossing) { diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 4793586e3..0cc0a9d58 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -11,10 +11,16 @@ #include #include #include +#include #include #include #include +#include +#include +#include +namespace pt = boost::property_tree; + #include #include #include "miniz_extension.hpp" @@ -33,6 +39,7 @@ const std::string RELATIONSHIPS_FILE = "_rels/.rels"; const std::string PRINT_CONFIG_FILE = "Metadata/Slic3r_PE.config"; const std::string MODEL_CONFIG_FILE = "Metadata/Slic3r_PE_model.config"; const std::string LAYER_HEIGHTS_PROFILE_FILE = "Metadata/Slic3r_PE_layer_heights_profile.txt"; +const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/Prusa_Slicer_layer_config_ranges.xml"; const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt"; const char* MODEL_TAG = "model"; @@ -331,6 +338,7 @@ namespace Slic3r { typedef std::map IdToMetadataMap; typedef std::map IdToGeometryMap; typedef std::map> IdToLayerHeightsProfileMap; + typedef std::map IdToLayerConfigRangesMap; typedef std::map> IdToSlaSupportPointsMap; // Version of the 3mf file @@ -347,6 +355,7 @@ namespace Slic3r { CurrentConfig m_curr_config; IdToMetadataMap m_objects_metadata; IdToLayerHeightsProfileMap m_layer_heights_profiles; + IdToLayerConfigRangesMap m_layer_config_ranges; IdToSlaSupportPointsMap m_sla_support_points; std::string m_curr_metadata_name; std::string m_curr_characters; @@ -365,6 +374,7 @@ namespace Slic3r { bool _load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config); bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); + void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, const std::string& archive_filename); @@ -476,6 +486,7 @@ namespace Slic3r { m_curr_config.volume_id = -1; m_objects_metadata.clear(); m_layer_heights_profiles.clear(); + m_layer_config_ranges.clear(); m_sla_support_points.clear(); m_curr_metadata_name.clear(); m_curr_characters.clear(); @@ -546,9 +557,14 @@ namespace Slic3r { if (boost::algorithm::iequals(name, LAYER_HEIGHTS_PROFILE_FILE)) { - // extract slic3r lazer heights profile file + // extract slic3r layer heights profile file _extract_layer_heights_profile_config_from_archive(archive, stat); } + if (boost::algorithm::iequals(name, LAYER_CONFIG_RANGES_FILE)) + { + // extract slic3r layer config ranges file + _extract_layer_config_ranges_from_archive(archive, stat); + } else if (boost::algorithm::iequals(name, SLA_SUPPORT_POINTS_FILE)) { // extract sla support points file @@ -592,6 +608,11 @@ namespace Slic3r { if (obj_layer_heights_profile != m_layer_heights_profiles.end()) model_object->layer_height_profile = obj_layer_heights_profile->second; + // m_layer_config_ranges are indexed by a 1 based model object index. + IdToLayerConfigRangesMap::iterator obj_layer_config_ranges = m_layer_config_ranges.find(object.second + 1); + if (obj_layer_config_ranges != m_layer_config_ranges.end()) + model_object->layer_config_ranges = obj_layer_config_ranges->second; + // m_sla_support_points are indexed by a 1 based model object index. IdToSlaSupportPointsMap::iterator obj_sla_support_points = m_sla_support_points.find(object.second + 1); if (obj_sla_support_points != m_sla_support_points.end() && !obj_sla_support_points->second.empty()) { @@ -675,7 +696,7 @@ namespace Slic3r { if (!XML_ParseBuffer(m_xml_parser, (int)stat.m_uncomp_size, 1)) { char error_buf[1024]; - ::sprintf(error_buf, "Error (%s) while parsing xml file at line %d", XML_ErrorString(XML_GetErrorCode(m_xml_parser)), XML_GetCurrentLineNumber(m_xml_parser)); + ::sprintf(error_buf, "Error (%s) while parsing xml file at line %d", XML_ErrorString(XML_GetErrorCode(m_xml_parser)), (int)XML_GetCurrentLineNumber(m_xml_parser)); add_error(error_buf); return false; } @@ -769,6 +790,66 @@ namespace Slic3r { } } + void _3MF_Importer::_extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) + { + if (stat.m_uncomp_size > 0) + { + std::string buffer((size_t)stat.m_uncomp_size, 0); + mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); + if (res == 0) { + add_error("Error while reading layer config ranges data to buffer"); + return; + } + + std::istringstream iss(buffer); // wrap returned xml to istringstream + pt::ptree objects_tree; + pt::read_xml(iss, objects_tree); + + for (const auto& object : objects_tree.get_child("objects")) + { + pt::ptree object_tree = object.second; + int obj_idx = object_tree.get(".id", -1); + if (obj_idx <= 0) { + add_error("Found invalid object id"); + continue; + } + + IdToLayerConfigRangesMap::iterator object_item = m_layer_config_ranges.find(obj_idx); + if (object_item != m_layer_config_ranges.end()) { + add_error("Found duplicated layer config range"); + continue; + } + + t_layer_config_ranges config_ranges; + + for (const auto& range : object_tree) + { + if (range.first != "range") + continue; + pt::ptree range_tree = range.second; + double min_z = range_tree.get(".min_z"); + double max_z = range_tree.get(".max_z"); + + // get Z range information + DynamicPrintConfig& config = config_ranges[{ min_z, max_z }]; + + for (const auto& option : range_tree) + { + if (option.first != "option") + continue; + std::string opt_key = option.second.get(".opt_key"); + std::string value = option.second.data(); + + config.set_deserialize(opt_key, value); + } + } + + if (!config_ranges.empty()) + m_layer_config_ranges.insert(IdToLayerConfigRangesMap::value_type(obj_idx, config_ranges)); + } + } + } + void _3MF_Importer::_extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) { if (stat.m_uncomp_size > 0) @@ -895,7 +976,7 @@ namespace Slic3r { if (!XML_ParseBuffer(m_xml_parser, (int)stat.m_uncomp_size, 1)) { char error_buf[1024]; - ::sprintf(error_buf, "Error (%s) while parsing xml file at line %d", XML_ErrorString(XML_GetErrorCode(m_xml_parser)), XML_GetCurrentLineNumber(m_xml_parser)); + ::sprintf(error_buf, "Error (%s) while parsing xml file at line %d", XML_ErrorString(XML_GetErrorCode(m_xml_parser)), (int)XML_GetCurrentLineNumber(m_xml_parser)); add_error(error_buf); return false; } @@ -1452,7 +1533,7 @@ namespace Slic3r { object->second.metadata.emplace_back(key, value); else if (type == VOLUME_TYPE) { - if (m_curr_config.volume_id < object->second.volumes.size()) + if (size_t(m_curr_config.volume_id) < object->second.volumes.size()) object->second.volumes[m_curr_config.volume_id].metadata.emplace_back(key, value); } else @@ -1624,6 +1705,7 @@ namespace Slic3r { bool _add_mesh_to_object_stream(std::stringstream& stream, ModelObject& object, VolumeToOffsetsMap& volumes_offsets); bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items); bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model); + bool _add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config); bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, const IdToObjectDataMap &objects_data); @@ -1684,6 +1766,16 @@ namespace Slic3r { return false; } + // Adds layer config ranges file ("Metadata/Slic3r_PE_layer_config_ranges.txt"). + // All layer height profiles of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model. + // The index differes from the index of an object ID of an object instance of a 3MF file! + if (!_add_layer_config_ranges_file_to_archive(archive, model)) + { + close_zip_writer(&archive); + boost::filesystem::remove(filename); + return false; + } + // Adds sla support points file ("Metadata/Slic3r_PE_sla_support_points.txt"). // All sla support points of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model. // The index differes from the index of an object ID of an object instance of a 3MF file! @@ -1895,7 +1987,7 @@ namespace Slic3r { return false; } - vertices_count += its.vertices.size(); + vertices_count += (int)its.vertices.size(); const Transform3d& matrix = volume->get_matrix(); @@ -1925,7 +2017,7 @@ namespace Slic3r { // updates triangle offsets volume_it->second.first_triangle_id = triangles_count; - triangles_count += its.indices.size(); + triangles_count += (int)its.indices.size(); volume_it->second.last_triangle_id = triangles_count - 1; for (size_t i = 0; i < its.indices.size(); ++ i) @@ -2013,6 +2105,70 @@ namespace Slic3r { return true; } + bool _3MF_Exporter::_add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model) + { + std::string out = ""; + pt::ptree tree; + + unsigned int object_cnt = 0; + for (const ModelObject* object : model.objects) + { + object_cnt++; + const t_layer_config_ranges& ranges = object->layer_config_ranges; + if (!ranges.empty()) + { + pt::ptree& obj_tree = tree.add("objects.object",""); + + obj_tree.put(".id", object_cnt); + + // Store the layer config ranges. + for (const auto& range : ranges) + { + pt::ptree& range_tree = obj_tree.add("range", ""); + + // store minX and maxZ + range_tree.put(".min_z", range.first.first); + range_tree.put(".max_z", range.first.second); + + // store range configuration + const DynamicPrintConfig& config = range.second; + for (const std::string& opt_key : config.keys()) + { + pt::ptree& opt_tree = range_tree.add("option", config.opt_serialize(opt_key)); + opt_tree.put(".opt_key", opt_key); + } + } + } + } + + if (!tree.empty()) + { + std::ostringstream oss; + boost::property_tree::write_xml(oss, tree); + out = oss.str(); + + // Post processing("beautification") of the output string for a better preview + boost::replace_all(out, ">\n \n \n ", ">\n "); + boost::replace_all(out, ">", ">\n "); + // OR just + boost::replace_all(out, "><", ">\n<"); + } + + if (!out.empty()) + { + if (!mz_zip_writer_add_mem(&archive, LAYER_CONFIG_RANGES_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) + { + add_error("Unable to add layer heights profile file to archive"); + return false; + } + } + + return true; + } + bool _3MF_Exporter::_add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model) { std::string out = ""; @@ -2060,7 +2216,7 @@ namespace Slic3r { for (const std::string &key : config.keys()) if (key != "compatible_printers") - out += "; " + key + " = " + config.serialize(key) + "\n"; + out += "; " + key + " = " + config.opt_serialize(key) + "\n"; if (!out.empty()) { @@ -2094,7 +2250,7 @@ namespace Slic3r { // stores object's config data for (const std::string& key : obj->config.keys()) { - stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << OBJECT_TYPE << "\" " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << obj->config.serialize(key) << "\"/>\n"; + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << OBJECT_TYPE << "\" " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << obj->config.opt_serialize(key) << "\"/>\n"; } for (const ModelVolume* volume : obj_metadata.second.object->volumes) @@ -2124,7 +2280,7 @@ namespace Slic3r { // stores volume's config data for (const std::string& key : volume->config.keys()) { - stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << volume->config.serialize(key) << "\"/>\n"; + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << volume->config.opt_serialize(key) << "\"/>\n"; } stream << " \n"; diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index a33d21c9f..4370a2e1c 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -106,6 +106,9 @@ struct AMFParserContext // amf/material/metadata NODE_TYPE_OBJECT, // amf/object // amf/object/metadata + NODE_TYPE_LAYER_CONFIG, // amf/object/layer_config_ranges + NODE_TYPE_RANGE, // amf/object/layer_config_ranges/range + // amf/object/layer_config_ranges/range/metadata NODE_TYPE_MESH, // amf/object/mesh NODE_TYPE_VERTICES, // amf/object/mesh/vertices NODE_TYPE_VERTEX, // amf/object/mesh/vertices/vertex @@ -189,7 +192,7 @@ struct AMFParserContext }; // Version of the amf file - unsigned int m_version; + unsigned int m_version; // Current Expat XML parser instance. XML_Parser m_parser; // Model to receive objects extracted from an AMF file. @@ -260,7 +263,9 @@ void AMFParserContext::startElement(const char *name, const char **atts) m_value[0] = get_attribute(atts, "type"); node_type_new = NODE_TYPE_METADATA; } - } else if (strcmp(name, "mesh") == 0) { + } else if (strcmp(name, "layer_config_ranges") == 0 && m_path[1] == NODE_TYPE_OBJECT) + node_type_new = NODE_TYPE_LAYER_CONFIG; + else if (strcmp(name, "mesh") == 0) { if (m_path[1] == NODE_TYPE_OBJECT) node_type_new = NODE_TYPE_MESH; } else if (strcmp(name, "instance") == 0) { @@ -317,6 +322,10 @@ void AMFParserContext::startElement(const char *name, const char **atts) else if (strcmp(name, "mirrorz") == 0) node_type_new = NODE_TYPE_MIRRORZ; } + else if (m_path[2] == NODE_TYPE_LAYER_CONFIG && strcmp(name, "range") == 0) { + assert(m_object); + node_type_new = NODE_TYPE_RANGE; + } break; case 4: if (m_path[3] == NODE_TYPE_VERTICES) { @@ -334,6 +343,10 @@ void AMFParserContext::startElement(const char *name, const char **atts) } else if (strcmp(name, "triangle") == 0) node_type_new = NODE_TYPE_TRIANGLE; } + else if (m_path[3] == NODE_TYPE_RANGE && strcmp(name, "metadata") == 0) { + m_value[0] = get_attribute(atts, "type"); + node_type_new = NODE_TYPE_METADATA; + } break; case 5: if (strcmp(name, "coordinates") == 0) { @@ -571,8 +584,13 @@ void AMFParserContext::endElement(const char * /* name */) config = &m_material->config; else if (m_path[1] == NODE_TYPE_OBJECT && m_object) config = &m_object->config; - } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume) + } + else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume) config = &m_volume->config; + else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_RANGE && m_object && !m_object->layer_config_ranges.empty()) { + auto it = --m_object->layer_config_ranges.end(); + config = &it->second; + } if (config) config->set_deserialize(opt_key, m_value[1]); } else if (m_path.size() == 3 && m_path[1] == NODE_TYPE_OBJECT && m_object && strcmp(opt_key, "layer_height_profile") == 0) { @@ -598,7 +616,7 @@ void AMFParserContext::endElement(const char * /* name */) if (end != nullptr) *end = 0; - point(coord_idx) = atof(p); + point(coord_idx) = float(atof(p)); if (++coord_idx == 5) { m_object->sla_support_points.push_back(sla::SupportPoint(point)); coord_idx = 0; @@ -609,6 +627,16 @@ void AMFParserContext::endElement(const char * /* name */) } m_object->sla_points_status = sla::PointsStatus::UserModified; } + else if (m_path.size() == 5 && m_path[1] == NODE_TYPE_OBJECT && m_path[3] == NODE_TYPE_RANGE && + m_object && strcmp(opt_key, "layer_height_range") == 0) { + // Parse object's layer_height_range, a semicolon separated doubles. + char* p = const_cast(m_value[1].c_str()); + char* end = strchr(p, ';'); + *end = 0; + + const t_layer_height_range range = {double(atof(p)), double(atof(end + 1))}; + m_object->layer_config_ranges[range]; + } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume) { if (strcmp(opt_key, "modifier") == 0) { // Is this volume a modifier volume? @@ -694,8 +722,8 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, Model *model) } int done = feof(pFile); if (XML_Parse(parser, buff, len, done) == XML_STATUS_ERROR) { - printf("AMF parser: Parse error at line %ul:\n%s\n", - XML_GetCurrentLineNumber(parser), + printf("AMF parser: Parse error at line %d:\n%s\n", + (int)XML_GetCurrentLineNumber(parser), XML_ErrorString(XML_GetErrorCode(parser))); break; } @@ -753,7 +781,7 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi if (!XML_ParseBuffer(parser, (int)stat.m_uncomp_size, 1)) { - printf("Error (%s) while parsing xml file at line %d\n", XML_ErrorString(XML_GetErrorCode(parser)), XML_GetCurrentLineNumber(parser)); + printf("Error (%s) while parsing xml file at line %d\n", XML_ErrorString(XML_GetErrorCode(parser)), (int)XML_GetCurrentLineNumber(parser)); close_zip_reader(&archive); return false; } @@ -873,7 +901,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) std::string str_config = "\n"; for (const std::string &key : config->keys()) if (key != "compatible_printers") - str_config += "; " + key + " = " + config->serialize(key) + "\n"; + str_config += "; " + key + " = " + config->opt_serialize(key) + "\n"; stream << "" << xml_escape(str_config) << "\n"; } @@ -885,7 +913,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) for (const auto &attr : material.second->attributes) stream << " " << attr.second << "\n"; for (const std::string &key : material.second->config.keys()) - stream << " " << material.second->config.serialize(key) << "\n"; + stream << " " << material.second->config.opt_serialize(key) << "\n"; stream << " \n"; } std::string instances; @@ -893,7 +921,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) ModelObject *object = model->objects[object_id]; stream << " \n"; for (const std::string &key : object->config.keys()) - stream << " " << object->config.serialize(key) << "\n"; + stream << " " << object->config.opt_serialize(key) << "\n"; if (!object->name.empty()) stream << " " << xml_escape(object->name) << "\n"; const std::vector &layer_height_profile = object->layer_height_profile; @@ -905,7 +933,30 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) stream << ";" << layer_height_profile[i]; stream << "\n \n"; } - //FIXME Store the layer height ranges (ModelObject::layer_height_ranges) + + // Export layer height ranges including the layer range specific config overrides. + const t_layer_config_ranges& config_ranges = object->layer_config_ranges; + if (!config_ranges.empty()) + { + // Store the layer config range as a single semicolon separated list. + stream << " \n"; + size_t layer_counter = 0; + for (auto range : config_ranges) { + stream << " \n"; + + stream << " "; + stream << range.first.first << ";" << range.first.second << "\n"; + + for (const std::string& key : range.second.keys()) + stream << " " << range.second.opt_serialize(key) << "\n"; + + stream << " \n"; + layer_counter++; + } + + stream << " \n"; + } + const std::vector& sla_support_points = object->sla_support_points; if (!sla_support_points.empty()) { @@ -941,7 +992,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) stream << " \n"; stream << " \n"; } - num_vertices += its.vertices.size(); + num_vertices += (int)its.vertices.size(); } stream << " \n"; for (size_t i_volume = 0; i_volume < object->volumes.size(); ++i_volume) { @@ -952,14 +1003,14 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) else stream << " material_id() << "\">\n"; for (const std::string &key : volume->config.keys()) - stream << " " << volume->config.serialize(key) << "\n"; + stream << " " << volume->config.opt_serialize(key) << "\n"; if (!volume->name.empty()) stream << " " << xml_escape(volume->name) << "\n"; if (volume->is_modifier()) stream << " 1\n"; stream << " " << ModelVolume::type_to_string(volume->type()) << "\n"; const indexed_triangle_set &its = volume->mesh().its; - for (size_t i = 0; i < (int)its.indices.size(); ++i) { + for (size_t i = 0; i < its.indices.size(); ++i) { stream << " \n"; for (int j = 0; j < 3; ++j) stream << " " << its.indices[i][j] + vertices_offset << "\n"; diff --git a/src/libslic3r/Format/objparser.cpp b/src/libslic3r/Format/objparser.cpp index 88dfae695..62bcd306e 100644 --- a/src/libslic3r/Format/objparser.cpp +++ b/src/libslic3r/Format/objparser.cpp @@ -176,8 +176,7 @@ static bool obj_parseline(const char *line, ObjData &data) EATWS(); if (*line == 0) return false; - // number of vertices of this face - int n = 0; + // current vertex to be parsed ObjVertex vertex; char *endptr = 0; @@ -266,7 +265,6 @@ static bool obj_parseline(const char *line, ObjData &data) { // o [object name] EATWS(); - const char *name = line; while (*line != ' ' && *line != '\t' && *line != 0) ++ line; // copy name to line. diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index c42669de0..37a14d42c 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -4,7 +4,7 @@ #include "EdgeGrid.hpp" #include "Geometry.hpp" #include "GCode/PrintExtents.hpp" -#include "GCode/WipeTowerPrusaMM.hpp" +#include "GCode/WipeTower.hpp" #include "Utils.hpp" #include @@ -162,9 +162,9 @@ std::string Wipe::wipe(GCode &gcodegen, bool toolchange) return gcode; } -static inline Point wipe_tower_point_to_object_point(GCode &gcodegen, const WipeTower::xy &wipe_tower_pt) +static inline Point wipe_tower_point_to_object_point(GCode &gcodegen, const Vec2f &wipe_tower_pt) { - return Point(scale_(wipe_tower_pt.x - gcodegen.origin()(0)), scale_(wipe_tower_pt.y - gcodegen.origin()(1))); + return Point(scale_(wipe_tower_pt.x() - gcodegen.origin()(0)), scale_(wipe_tower_pt.y() - gcodegen.origin()(1))); } std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const @@ -174,47 +174,97 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T // Toolchangeresult.gcode assumes the wipe tower corner is at the origin // We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position float alpha = m_wipe_tower_rotation/180.f * float(M_PI); - WipeTower::xy start_pos = tcr.start_pos; - WipeTower::xy end_pos = tcr.end_pos; - start_pos.rotate(alpha); - start_pos.translate(m_wipe_tower_pos); - end_pos.rotate(alpha); - end_pos.translate(m_wipe_tower_pos); - std::string tcr_rotated_gcode = rotate_wipe_tower_moves(tcr.gcode, tcr.start_pos, m_wipe_tower_pos, alpha); + Vec2f start_pos = tcr.start_pos; + Vec2f end_pos = tcr.end_pos; + if (!tcr.priming) { + start_pos = Eigen::Rotation2Df(alpha) * start_pos; + start_pos += m_wipe_tower_pos; + end_pos = Eigen::Rotation2Df(alpha) * end_pos; + end_pos += m_wipe_tower_pos; + } + std::string tcr_rotated_gcode = tcr.priming ? tcr.gcode : rotate_wipe_tower_moves(tcr.gcode, tcr.start_pos, m_wipe_tower_pos, alpha); // Disable linear advance for the wipe tower operations. gcode += (gcodegen.config().gcode_flavor == gcfRepRap ? std::string("M572 D0 S0\n") : std::string("M900 K0\n")); - // Move over the wipe tower. - // Retract for a tool change, using the toolchange retract value and setting the priming extra length. - gcode += gcodegen.retract(true); - gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; - gcode += gcodegen.travel_to( - wipe_tower_point_to_object_point(gcodegen, start_pos), - erMixed, - "Travel to a Wipe Tower"); - gcode += gcodegen.unretract(); - // Let the tool change be executed by the wipe tower class. - // Inform the G-code writer about the changes done behind its back. - gcode += tcr_rotated_gcode; - // Let the m_writer know the current extruder_id, but ignore the generated G-code. - if (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id)) - gcodegen.writer().toolchange(new_extruder_id); + if (!tcr.priming) { + // Move over the wipe tower. + // Retract for a tool change, using the toolchange retract value and setting the priming extra length. + gcode += gcodegen.retract(true); + gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; + gcode += gcodegen.travel_to( + wipe_tower_point_to_object_point(gcodegen, start_pos), + erMixed, + "Travel to a Wipe Tower"); + gcode += gcodegen.unretract(); + } + + + // Process the end filament gcode. + std::string end_filament_gcode_str; + if (gcodegen.writer().extruder() != nullptr) { + // Process the custom end_filament_gcode in case of single_extruder_multi_material. + unsigned int old_extruder_id = gcodegen.writer().extruder()->id(); + const std::string &end_filament_gcode = gcodegen.config().end_filament_gcode.get_at(old_extruder_id); + if (gcodegen.writer().extruder() != nullptr && ! end_filament_gcode.empty()) { + end_filament_gcode_str = gcodegen.placeholder_parser_process("end_filament_gcode", end_filament_gcode, old_extruder_id); + check_add_eol(end_filament_gcode_str); + } + } + + // Process the custom toolchange_gcode. If it is empty, provide a simple Tn command to change the filament. + // Otherwise, leave control to the user completely. + std::string toolchange_gcode_str; + if (true /*gcodegen.writer().extruder() != nullptr*/) { + const std::string& toolchange_gcode = gcodegen.config().toolchange_gcode.value; + if (!toolchange_gcode.empty()) { + DynamicConfig config; + int previous_extruder_id = gcodegen.writer().extruder() ? (int)gcodegen.writer().extruder()->id() : -1; + config.set_key_value("previous_extruder", new ConfigOptionInt(previous_extruder_id)); + config.set_key_value("next_extruder", new ConfigOptionInt((int)new_extruder_id)); + config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index)); + config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z)); + toolchange_gcode_str = gcodegen.placeholder_parser_process("toolchange_gcode", toolchange_gcode, new_extruder_id, &config); + check_add_eol(toolchange_gcode_str); + } + + std::string toolchange_command; + if (tcr.priming || (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id))) + toolchange_command = gcodegen.writer().toolchange(new_extruder_id); + if (toolchange_gcode.empty()) + toolchange_gcode_str = toolchange_command; + else { + // We have informed the m_writer about the current extruder_id, we can ignore the generated G-code. + } + } + gcodegen.placeholder_parser().set("current_extruder", new_extruder_id); - // Always append the filament start G-code even if the extruder did not switch, - // because the wipe tower resets the linear advance and we want it to be re-enabled. + // Process the start filament gcode. + std::string start_filament_gcode_str; const std::string &start_filament_gcode = gcodegen.config().start_filament_gcode.get_at(new_extruder_id); if (! start_filament_gcode.empty()) { // Process the start_filament_gcode for the active filament only. DynamicConfig config; config.set_key_value("filament_extruder_id", new ConfigOptionInt(new_extruder_id)); - gcode += gcodegen.placeholder_parser_process("start_filament_gcode", start_filament_gcode, new_extruder_id, &config); - check_add_eol(gcode); + start_filament_gcode_str = gcodegen.placeholder_parser_process("start_filament_gcode", start_filament_gcode, new_extruder_id, &config); + check_add_eol(start_filament_gcode_str); } + + // Insert the end filament, toolchange, and start filament gcode into the generated gcode. + DynamicConfig config; + config.set_key_value("end_filament_gcode", new ConfigOptionString(end_filament_gcode_str)); + config.set_key_value("toolchange_gcode", new ConfigOptionString(toolchange_gcode_str)); + config.set_key_value("start_filament_gcode", new ConfigOptionString(start_filament_gcode_str)); + std::string tcr_gcode, tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_extruder_id, &config); + unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode); + gcode += tcr_gcode; + check_add_eol(toolchange_gcode_str); + + // A phony move to the end position at the wipe tower. - gcodegen.writer().travel_to_xy(Vec2d(end_pos.x, end_pos.y)); + gcodegen.writer().travel_to_xy(end_pos.cast()); gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos)); // Prepare a future wipe. @@ -224,8 +274,8 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, end_pos)); // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge. gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, - WipeTower::xy((std::abs(m_left - end_pos.x) < std::abs(m_right - end_pos.x)) ? m_right : m_left, - end_pos.y))); + Vec2f((std::abs(m_left - end_pos.x()) < std::abs(m_right - end_pos.x())) ? m_right : m_left, + end_pos.y()))); } // Let the planner know we are traveling between objects. @@ -235,14 +285,14 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T // This function postprocesses gcode_original, rotates and moves all G1 extrusions and returns resulting gcode // Starting position has to be supplied explicitely (otherwise it would fail in case first G1 command only contained one coordinate) -std::string WipeTowerIntegration::rotate_wipe_tower_moves(const std::string& gcode_original, const WipeTower::xy& start_pos, const WipeTower::xy& translation, float angle) const +std::string WipeTowerIntegration::rotate_wipe_tower_moves(const std::string& gcode_original, const Vec2f& start_pos, const Vec2f& translation, float angle) const { std::istringstream gcode_str(gcode_original); std::string gcode_out; std::string line; - WipeTower::xy pos = start_pos; - WipeTower::xy transformed_pos; - WipeTower::xy old_pos(-1000.1f, -1000.1f); + Vec2f pos = start_pos; + Vec2f transformed_pos; + Vec2f old_pos(-1000.1f, -1000.1f); while (gcode_str) { std::getline(gcode_str, line); // we read the gcode line by line @@ -253,25 +303,25 @@ std::string WipeTowerIntegration::rotate_wipe_tower_moves(const std::string& gco char ch = 0; while (line_str >> ch) { if (ch == 'X') - line_str >> pos.x; + line_str >> pos.x(); else if (ch == 'Y') - line_str >> pos.y; + line_str >> pos.y(); else line_out << ch; } transformed_pos = pos; - transformed_pos.rotate(angle); - transformed_pos.translate(translation); + transformed_pos = Eigen::Rotation2Df(angle) * transformed_pos; + transformed_pos += translation; if (transformed_pos != old_pos) { line = line_out.str(); char buf[2048] = "G1"; - if (transformed_pos.x != old_pos.x) - sprintf(buf + strlen(buf), " X%.3f", transformed_pos.x); - if (transformed_pos.y != old_pos.y) - sprintf(buf + strlen(buf), " Y%.3f", transformed_pos.y); + if (transformed_pos.x() != old_pos.x()) + sprintf(buf + strlen(buf), " X%.3f", transformed_pos.x()); + if (transformed_pos.y() != old_pos.y()) + sprintf(buf + strlen(buf), " Y%.3f", transformed_pos.y()); line.replace(line.find("G1 "), 3, buf); old_pos = transformed_pos; @@ -288,27 +338,36 @@ std::string WipeTowerIntegration::prime(GCode &gcodegen) assert(m_layer_idx == 0); std::string gcode; - if (&m_priming != nullptr && ! m_priming.extrusions.empty()) { + if (&m_priming != nullptr) { // Disable linear advance for the wipe tower operations. - gcode += (gcodegen.config().gcode_flavor == gcfRepRap ? std::string("M572 D0 S0\n") : std::string("M900 K0\n")); - // Let the tool change be executed by the wipe tower class. - // Inform the G-code writer about the changes done behind its back. - gcode += m_priming.gcode; - // Let the m_writer know the current extruder_id, but ignore the generated G-code. - unsigned int current_extruder_id = m_priming.extrusions.back().tool; - gcodegen.writer().toolchange(current_extruder_id); - gcodegen.placeholder_parser().set("current_extruder", current_extruder_id); + //gcode += (gcodegen.config().gcode_flavor == gcfRepRap ? std::string("M572 D0 S0\n") : std::string("M900 K0\n")); + + for (const WipeTower::ToolChangeResult& tcr : m_priming) { + if (!tcr.extrusions.empty()) + gcode += append_tcr(gcodegen, tcr, tcr.new_tool); + + + // Let the tool change be executed by the wipe tower class. + // Inform the G-code writer about the changes done behind its back. + //gcode += tcr.gcode; + // Let the m_writer know the current extruder_id, but ignore the generated G-code. + // unsigned int current_extruder_id = tcr.extrusions.back().tool; + // gcodegen.writer().toolchange(current_extruder_id); + // gcodegen.placeholder_parser().set("current_extruder", current_extruder_id); + + } + // A phony move to the end position at the wipe tower. - gcodegen.writer().travel_to_xy(Vec2d(m_priming.end_pos.x, m_priming.end_pos.y)); - gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, m_priming.end_pos)); + /* gcodegen.writer().travel_to_xy(Vec2d(m_priming.back().end_pos.x, m_priming.back().end_pos.y)); + gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, m_priming.back().end_pos)); // Prepare a future wipe. gcodegen.m_wipe.path.points.clear(); // Start the wipe at the current position. - gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, m_priming.end_pos)); + gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, m_priming.back().end_pos)); // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge. - gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, - WipeTower::xy((std::abs(m_left - m_priming.end_pos.x) < std::abs(m_right - m_priming.end_pos.x)) ? m_right : m_left, - m_priming.end_pos.y))); + gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, + WipeTower::xy((std::abs(m_left - m_priming.back().end_pos.x) < std::abs(m_right - m_priming.back().end_pos.x)) ? m_right : m_left, + m_priming.back().end_pos.y)));*/ } return gcode; } @@ -316,10 +375,10 @@ std::string WipeTowerIntegration::prime(GCode &gcodegen) std::string WipeTowerIntegration::tool_change(GCode &gcodegen, int extruder_id, bool finish_layer) { std::string gcode; - assert(m_layer_idx >= 0 && m_layer_idx <= m_tool_changes.size()); + assert(m_layer_idx >= 0 && size_t(m_layer_idx) <= m_tool_changes.size()); if (! m_brim_done || gcodegen.writer().need_toolchange(extruder_id) || finish_layer) { - if (m_layer_idx < m_tool_changes.size()) { - assert(m_tool_change_idx < m_tool_changes[m_layer_idx].size()); + if (m_layer_idx < (int)m_tool_changes.size()) { + assert(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size()); gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id); } m_brim_done = true; @@ -586,6 +645,9 @@ void GCode::_do_export(Print &print, FILE *file) } m_analyzer.set_extruder_offsets(extruder_offsets); + // tell analyzer about the gcode flavor + m_analyzer.set_gcode_flavor(print.config().gcode_flavor); + // resets analyzer's tracking data m_last_mm3_per_mm = GCodeAnalyzer::Default_mm3_per_mm; m_last_width = GCodeAnalyzer::Default_Width; @@ -804,24 +866,16 @@ void GCode::_do_export(Print &print, FILE *file) // Write the custom start G-code _writeln(file, start_gcode); - // Process filament-specific gcode in extruder order. - if (print.config().single_extruder_multi_material) { - if (has_wipe_tower) { - // Wipe tower will control the extruder switching, it will call the start_filament_gcode. - } else { - // Only initialize the initial extruder. + + // Process filament-specific gcode. + /* if (has_wipe_tower) { + // Wipe tower will control the extruder switching, it will call the start_filament_gcode. + } else { DynamicConfig config; config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(initial_extruder_id))); - _writeln(file, this->placeholder_parser_process("start_filament_gcode", print.config().start_filament_gcode.values[initial_extruder_id], initial_extruder_id, &config)); - } - } else { - DynamicConfig config; - for (const std::string &start_gcode : print.config().start_filament_gcode.values) { - int extruder_id = (unsigned int)(&start_gcode - &print.config().start_filament_gcode.values.front()); - config.set_key_value("filament_extruder_id", new ConfigOptionInt(extruder_id)); - _writeln(file, this->placeholder_parser_process("start_filament_gcode", start_gcode, extruder_id, &config)); - } + _writeln(file, this->placeholder_parser_process("start_filament_gcode", print.config().start_filament_gcode.values[initial_extruder_id], initial_extruder_id, &config)); } +*/ this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, true); print.throw_if_canceled(); @@ -1057,6 +1111,10 @@ void GCode::_do_export(Print &print, FILE *file) print.m_print_statistics.clear(); print.m_print_statistics.estimated_normal_print_time = m_normal_time_estimator.get_time_dhms(); print.m_print_statistics.estimated_silent_print_time = m_silent_time_estimator_enabled ? m_silent_time_estimator.get_time_dhms() : "N/A"; + print.m_print_statistics.estimated_normal_color_print_times = m_normal_time_estimator.get_color_times_dhms(); + if (m_silent_time_estimator_enabled) + print.m_print_statistics.estimated_silent_color_print_times = m_silent_time_estimator.get_color_times_dhms(); + std::vector extruders = m_writer.extruders(); if (! extruders.empty()) { std::pair out_filament_used_mm ("; filament used [mm] = ", 0); @@ -1253,7 +1311,7 @@ void GCode::_print_first_layer_extruder_temperatures(FILE *file, Print &print, c int temp = print.config().first_layer_temperature.get_at(first_printing_extruder_id); if (temp_by_gcode >= 0 && temp_by_gcode < 1000) temp = temp_by_gcode; - m_writer.set_temperature(temp_by_gcode, wait, first_printing_extruder_id); + m_writer.set_temperature(temp, wait, first_printing_extruder_id); } else { // Custom G-code does not set the extruder temperature. Do it now. if (print.config().single_extruder_multi_material.value) { @@ -1344,11 +1402,11 @@ void GCode::process_layer( // Check whether it is possible to apply the spiral vase logic for this layer. // Just a reminder: A spiral vase mode is allowed for a single object, single material print only. if (m_spiral_vase && layers.size() == 1 && support_layer == nullptr) { - bool enable = (layer.id() > 0 || print.config().brim_width.value == 0.) && (layer.id() >= print.config().skirt_height.value && ! print.has_infinite_skirt()); + bool enable = (layer.id() > 0 || print.config().brim_width.value == 0.) && (layer.id() >= (size_t)print.config().skirt_height.value && ! print.has_infinite_skirt()); if (enable) { for (const LayerRegion *layer_region : layer.regions()) - if (layer_region->region()->config().bottom_solid_layers.value > layer.id() || - layer_region->perimeters.items_count() > 1 || + if (size_t(layer_region->region()->config().bottom_solid_layers.value) > layer.id() || + layer_region->perimeters.items_count() > 1u || layer_region->fills.items_count() > 0) { enable = false; break; @@ -1405,7 +1463,9 @@ void GCode::process_layer( m_colorprint_heights.erase(m_colorprint_heights.begin()); colorprint_change = true; } - if (colorprint_change && print.extruders().size()==1) + + // we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count + if (colorprint_change && print./*extruders()*/config().nozzle_diameter.size()==1) gcode += "M600\n"; @@ -1414,11 +1474,11 @@ void GCode::process_layer( bool extrude_skirt = ! print.skirt().entities.empty() && // Not enough skirt layers printed yet. - (m_skirt_done.size() < print.config().skirt_height.value || print.has_infinite_skirt()) && + (m_skirt_done.size() < (size_t)print.config().skirt_height.value || print.has_infinite_skirt()) && // This print_z has not been extruded yet (m_skirt_done.empty() ? 0. : m_skirt_done.back()) < print_z - EPSILON && // and this layer is the 1st layer, or it is an object layer, or it is a raft layer. - (first_layer || object_layer != nullptr || support_layer->id() < m_config.raft_layers.value); + (first_layer || object_layer != nullptr || support_layer->id() < (size_t)m_config.raft_layers.value); std::map> skirt_loops_per_extruder; coordf_t skirt_height = 0.; if (extrude_skirt) { @@ -1749,7 +1809,7 @@ void GCode::append_full_config(const Print& print, std::string& str) const StaticPrintConfig *cfg = configs[i]; for (const std::string &key : cfg->keys()) if (key != "compatible_printers") - str += "; " + key + " = " + cfg->serialize(key) + "\n"; + str += "; " + key + " = " + cfg->opt_serialize(key) + "\n"; } const DynamicConfig &full_config = print.placeholder_parser().config(); for (const char *key : { @@ -2067,19 +2127,18 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // Retrieve the last start position for this object. float last_pos_weight = 1.f; - switch (seam_position) { - case spAligned: + + if (seam_position == spAligned) { // Seam is aligned to the seam at the preceding layer. if (m_layer != NULL && m_seam_position.count(m_layer->object()) > 0) { last_pos = m_seam_position[m_layer->object()]; last_pos_weight = 1.f; } - break; - case spRear: + } + else if (seam_position == spRear) { last_pos = m_layer->object()->bounding_box().center(); last_pos(1) += coord_t(3. * m_layer->object()->bounding_box().radius()); last_pos_weight = 5.f; - break; } // Insert a projection of last_pos into the polygon. @@ -2088,7 +2147,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou Points::iterator it = project_point_to_polygon_and_insert(polygon, last_pos, 0.1 * nozzle_r); last_pos_proj_idx = it - polygon.points.begin(); } - Point last_pos_proj = polygon.points[last_pos_proj_idx]; + // Parametrize the polygon by its length. std::vector lengths = polygon_parameter_by_length(polygon); @@ -2098,7 +2157,6 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // No penalty for reflex points, slight penalty for convex points, high penalty for flat surfaces. const float penaltyConvexVertex = 1.f; const float penaltyFlatSurface = 5.f; - const float penaltySeam = 1.3f; const float penaltyOverhangHalf = 10.f; // Penalty for visible seams. for (size_t i = 0; i < polygon.points.size(); ++ i) { @@ -2143,10 +2201,14 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // Signed distance is positive outside the object, negative inside the object. // The point is considered at an overhang, if it is more than nozzle radius // outside of the lower layer contour. - bool found = (*lower_layer_edge_grid)->signed_distance(p, search_r, dist); + #ifdef NDEBUG // to suppress unused variable warning in release mode + (*lower_layer_edge_grid)->signed_distance(p, search_r, dist); + #else + bool found = (*lower_layer_edge_grid)->signed_distance(p, search_r, dist); + #endif // If the approximate Signed Distance Field was initialized over lower_layer_edge_grid, // then the signed distnace shall always be known. - assert(found); + assert(found); penalties[i] += extrudate_overlap_penalty(float(nozzle_r), penaltyOverhangHalf, float(dist)); } } @@ -2554,7 +2616,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, gcode += buf; } - if (m_last_mm3_per_mm != path.mm3_per_mm) + if (last_was_wipe_tower || (m_last_mm3_per_mm != path.mm3_per_mm)) { m_last_mm3_per_mm = path.mm3_per_mm; @@ -2732,38 +2794,57 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z) m_wipe.reset_path(); if (m_writer.extruder() != nullptr) { - // Process the custom end_filament_gcode in case of single_extruder_multi_material. + // Process the custom end_filament_gcode. set_extruder() is only called if there is no wipe tower + // so it should not be injected twice. unsigned int old_extruder_id = m_writer.extruder()->id(); const std::string &end_filament_gcode = m_config.end_filament_gcode.get_at(old_extruder_id); - if (m_config.single_extruder_multi_material && ! end_filament_gcode.empty()) { + if (! end_filament_gcode.empty()) { gcode += placeholder_parser_process("end_filament_gcode", end_filament_gcode, old_extruder_id); check_add_eol(gcode); } } - m_placeholder_parser.set("current_extruder", extruder_id); - - if (m_writer.extruder() != nullptr && ! m_config.toolchange_gcode.value.empty()) { - // Process the custom toolchange_gcode. - DynamicConfig config; - config.set_key_value("previous_extruder", new ConfigOptionInt((int)m_writer.extruder()->id())); - config.set_key_value("next_extruder", new ConfigOptionInt((int)extruder_id)); - config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); - config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); - gcode += placeholder_parser_process("toolchange_gcode", m_config.toolchange_gcode.value, extruder_id, &config); - check_add_eol(gcode); - } // If ooze prevention is enabled, park current extruder in the nearest // standby point and set it to the standby temperature. if (m_ooze_prevention.enable && m_writer.extruder() != nullptr) gcode += m_ooze_prevention.pre_toolchange(*this); - // Append the toolchange command. - gcode += m_writer.toolchange(extruder_id); - // Append the filament start G-code for single_extruder_multi_material. + + const std::string& toolchange_gcode = m_config.toolchange_gcode.value; + + // Process the custom toolchange_gcode. If it is empty, insert just a Tn command. + if (!toolchange_gcode.empty()) { + DynamicConfig config; + config.set_key_value("previous_extruder", new ConfigOptionInt((int)(m_writer.extruder() != nullptr ? m_writer.extruder()->id() : -1 ))); + config.set_key_value("next_extruder", new ConfigOptionInt((int)extruder_id)); + config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); + config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); + gcode += placeholder_parser_process("toolchange_gcode", toolchange_gcode, extruder_id, &config); + check_add_eol(gcode); + } + + // We inform the writer about what is happening, but we may not use the resulting gcode. + std::string toolchange_command = m_writer.toolchange(extruder_id); + if (toolchange_gcode.empty()) + gcode += toolchange_command; + else { + // user provided his own toolchange gcode, no need to do anything + } + + // Set the temperature if the wipe tower didn't (not needed for non-single extruder MM) + if (m_config.single_extruder_multi_material && !m_config.wipe_tower) { + int temp = (m_layer_index == 0 ? m_config.first_layer_temperature.get_at(extruder_id) : + m_config.temperature.get_at(extruder_id)); + + gcode += m_writer.set_temperature(temp, false); + } + + m_placeholder_parser.set("current_extruder", extruder_id); + + // Append the filament start G-code. const std::string &start_filament_gcode = m_config.start_filament_gcode.get_at(extruder_id); - if (m_config.single_extruder_multi_material && ! start_filament_gcode.empty()) { - // Process the start_filament_gcode for the active filament only. + if (! start_filament_gcode.empty()) { + // Process the start_filament_gcode for the new filament. gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id); check_add_eol(gcode); } diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 21957d32c..4b81b42aa 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -83,7 +83,7 @@ class WipeTowerIntegration { public: WipeTowerIntegration( const PrintConfig &print_config, - const WipeTower::ToolChangeResult &priming, + const std::vector &priming, const std::vector> &tool_changes, const WipeTower::ToolChangeResult &final_purge) : m_left(/*float(print_config.wipe_tower_x.value)*/ 0.f), @@ -108,15 +108,15 @@ private: std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const; // Postprocesses gcode: rotates and moves all G1 extrusions and returns result - std::string rotate_wipe_tower_moves(const std::string& gcode_original, const WipeTower::xy& start_pos, const WipeTower::xy& translation, float angle) const; + std::string rotate_wipe_tower_moves(const std::string& gcode_original, const Vec2f& start_pos, const Vec2f& translation, float angle) const; // Left / right edges of the wipe tower, for the planning of wipe moves. const float m_left; const float m_right; - const WipeTower::xy m_wipe_tower_pos; + const Vec2f m_wipe_tower_pos; const float m_wipe_tower_rotation; // Reference to cached values at the Printer class. - const WipeTower::ToolChangeResult &m_priming; + const std::vector &m_priming; const std::vector> &m_tool_changes; const WipeTower::ToolChangeResult &m_final_purge; // Current layer index. diff --git a/src/libslic3r/GCode/Analyzer.cpp b/src/libslic3r/GCode/Analyzer.cpp index 321d9a342..6cf118cc2 100644 --- a/src/libslic3r/GCode/Analyzer.cpp +++ b/src/libslic3r/GCode/Analyzer.cpp @@ -106,6 +106,11 @@ void GCodeAnalyzer::set_extruder_offsets(const GCodeAnalyzer::ExtruderOffsetsMap m_extruder_offsets = extruder_offsets; } +void GCodeAnalyzer::set_gcode_flavor(const GCodeFlavor& flavor) +{ + m_gcode_flavor = flavor; +} + void GCodeAnalyzer::reset() { _set_units(Millimeters); @@ -249,6 +254,14 @@ void GCodeAnalyzer::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLi _processM83(line); break; } + case 108: + case 135: + { + // these are used by MakerWare and Sailfish firmwares + // for tool changing - we can process it in one place + _processM108orM135(line); + break; + } } break; @@ -426,9 +439,27 @@ void GCodeAnalyzer::_processM600(const GCodeReader::GCodeLine& line) _set_cp_color_id(m_state.cur_cp_color_id); } -void GCodeAnalyzer::_processT(const GCodeReader::GCodeLine& line) +void GCodeAnalyzer::_processM108orM135(const GCodeReader::GCodeLine& line) +{ + // These M-codes are used by MakerWare and 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 + + size_t code = ::atoi(&(line.cmd()[1])); + if ((code == 108 && m_gcode_flavor == gcfSailfish) + || (code == 135 && m_gcode_flavor == gcfMakerWare)) { + + std::string cmd = line.raw(); + size_t T_pos = cmd.find("T"); + if (T_pos != std::string::npos) { + cmd = cmd.substr(T_pos); + _processT(cmd); + } + } +} + +void GCodeAnalyzer::_processT(const std::string& cmd) { - std::string cmd = line.cmd(); if (cmd.length() > 1) { unsigned int id = (unsigned int)::strtol(cmd.substr(1).c_str(), nullptr, 10); @@ -442,6 +473,11 @@ void GCodeAnalyzer::_processT(const GCodeReader::GCodeLine& line) } } +void GCodeAnalyzer::_processT(const GCodeReader::GCodeLine& line) +{ + _processT(line.cmd()); +} + bool GCodeAnalyzer::_process_tags(const GCodeReader::GCodeLine& line) { std::string comment = line.comment(); diff --git a/src/libslic3r/GCode/Analyzer.hpp b/src/libslic3r/GCode/Analyzer.hpp index 4c201c640..ab8bade71 100644 --- a/src/libslic3r/GCode/Analyzer.hpp +++ b/src/libslic3r/GCode/Analyzer.hpp @@ -106,6 +106,7 @@ private: GCodeReader m_parser; TypeToMovesMap m_moves_map; ExtruderOffsetsMap m_extruder_offsets; + GCodeFlavor m_gcode_flavor; // The output of process_layer() std::string m_process_output; @@ -115,6 +116,8 @@ public: void set_extruder_offsets(const ExtruderOffsetsMap& extruder_offsets); + void set_gcode_flavor(const GCodeFlavor& flavor); + // Reinitialize the analyzer void reset(); @@ -164,10 +167,14 @@ private: // Set extruder to relative mode void _processM83(const GCodeReader::GCodeLine& line); + // Set tool (MakerWare and Sailfish flavor) + void _processM108orM135(const GCodeReader::GCodeLine& line); + // Set color change void _processM600(const GCodeReader::GCodeLine& line); // Processes T line (Select Tool) + void _processT(const std::string& command); void _processT(const GCodeReader::GCodeLine& line); // Processes the tags diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp index 552fbf88c..1aa15ef33 100644 --- a/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/src/libslic3r/GCode/CoolingBuffer.cpp @@ -672,7 +672,7 @@ std::string CoolingBuffer::apply_layer_cooldown( #define EXTRUDER_CONFIG(OPT) config.OPT.get_at(m_current_extruder) int min_fan_speed = EXTRUDER_CONFIG(min_fan_speed); int fan_speed_new = EXTRUDER_CONFIG(fan_always_on) ? min_fan_speed : 0; - if (layer_id >= EXTRUDER_CONFIG(disable_fan_first_layers)) { + if (layer_id >= (size_t)EXTRUDER_CONFIG(disable_fan_first_layers)) { int max_fan_speed = EXTRUDER_CONFIG(max_fan_speed); float slowdown_below_layer_time = float(EXTRUDER_CONFIG(slowdown_below_layer_time)); float fan_below_layer_time = float(EXTRUDER_CONFIG(fan_below_layer_time)); diff --git a/src/libslic3r/GCode/PreviewData.cpp b/src/libslic3r/GCode/PreviewData.cpp index 8eba6801e..55f07998b 100644 --- a/src/libslic3r/GCode/PreviewData.cpp +++ b/src/libslic3r/GCode/PreviewData.cpp @@ -404,6 +404,8 @@ std::string GCodePreviewData::get_legend_title() const return L("Tool"); case Extrusion::ColorPrint: return L("Color Print"); + case Extrusion::Num_View_Types: + break; // just to supress warning about non-handled value } return ""; @@ -466,7 +468,7 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std:: } case Extrusion::Tool: { - unsigned int tools_colors_count = tool_colors.size() / 4; + unsigned int tools_colors_count = (unsigned int)tool_colors.size() / 4; items.reserve(tools_colors_count); for (unsigned int i = 0; i < tools_colors_count; ++i) { @@ -491,20 +493,25 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std:: items.emplace_back(Slic3r::I18N::translate(L("Default print color")), color); break; } + + std::string id_str = std::to_string(i + 1) + ": "; + if (i == 0) { - items.emplace_back((boost::format(Slic3r::I18N::translate(L("up to %.2f mm"))) % cp_values[0].first).str(), color); + items.emplace_back(id_str + (boost::format(Slic3r::I18N::translate(L("up to %.2f mm"))) % cp_values[0].first).str(), color); break; } if (i == color_print_cnt) { - items.emplace_back((boost::format(Slic3r::I18N::translate(L("above %.2f mm"))) % cp_values[i-1].second).str(), color); + items.emplace_back(id_str + (boost::format(Slic3r::I18N::translate(L("above %.2f mm"))) % cp_values[i - 1].second).str(), color); continue; } // items.emplace_back((boost::format(Slic3r::I18N::translate(L("%.2f - %.2f mm"))) % cp_values[i-1] % cp_values[i]).str(), color); - items.emplace_back((boost::format(Slic3r::I18N::translate(L("%.2f - %.2f mm"))) % cp_values[i-1].second % cp_values[i].first).str(), color); + items.emplace_back(id_str + (boost::format(Slic3r::I18N::translate(L("%.2f - %.2f mm"))) % cp_values[i - 1].second% cp_values[i].first).str(), color); } break; } + case Extrusion::Num_View_Types: + break; // just to supress warning about non-handled value } return items; diff --git a/src/libslic3r/GCode/PrintExtents.cpp b/src/libslic3r/GCode/PrintExtents.cpp index 92a58fdf0..07a71a0ea 100644 --- a/src/libslic3r/GCode/PrintExtents.cpp +++ b/src/libslic3r/GCode/PrintExtents.cpp @@ -149,8 +149,8 @@ BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_ const WipeTower::Extrusion &e = tcr.extrusions[i]; if (e.width > 0) { Vec2d delta = 0.5 * Vec2d(e.width, e.width); - Vec2d p1 = trafo * Vec2d((&e - 1)->pos.x, (&e - 1)->pos.y); - Vec2d p2 = trafo * Vec2d(e.pos.x, e.pos.y); + Vec2d p1 = trafo * (&e - 1)->pos.cast(); + Vec2d p2 = trafo * e.pos.cast(); bbox.merge(p1.cwiseMin(p2) - delta); bbox.merge(p1.cwiseMax(p2) + delta); } @@ -165,18 +165,19 @@ BoundingBoxf get_wipe_tower_priming_extrusions_extents(const Print &print) { BoundingBoxf bbox; if (print.wipe_tower_data().priming != nullptr) { - const WipeTower::ToolChangeResult &tcr = *print.wipe_tower_data().priming; - for (size_t i = 1; i < tcr.extrusions.size(); ++ i) { - const WipeTower::Extrusion &e = tcr.extrusions[i]; - if (e.width > 0) { - Vec2d p1((&e - 1)->pos.x, (&e - 1)->pos.y); - Vec2d p2(e.pos.x, e.pos.y); - bbox.merge(p1); - coordf_t radius = 0.5 * e.width; - bbox.min(0) = std::min(bbox.min(0), std::min(p1(0), p2(0)) - radius); - bbox.min(1) = std::min(bbox.min(1), std::min(p1(1), p2(1)) - radius); - bbox.max(0) = std::max(bbox.max(0), std::max(p1(0), p2(0)) + radius); - bbox.max(1) = std::max(bbox.max(1), std::max(p1(1), p2(1)) + radius); + for (const WipeTower::ToolChangeResult &tcr : *print.wipe_tower_data().priming) { + for (size_t i = 1; i < tcr.extrusions.size(); ++ i) { + const WipeTower::Extrusion &e = tcr.extrusions[i]; + if (e.width > 0) { + const Vec2d& p1 = (&e - 1)->pos.cast(); + const Vec2d& p2 = e.pos.cast(); + bbox.merge(p1); + coordf_t radius = 0.5 * e.width; + bbox.min(0) = std::min(bbox.min(0), std::min(p1(0), p2(0)) - radius); + bbox.min(1) = std::min(bbox.min(1), std::min(p1(1), p2(1)) - radius); + bbox.max(0) = std::max(bbox.max(0), std::max(p1(0), p2(0)) + radius); + bbox.max(1) = std::max(bbox.max(1), std::max(p1(1), p2(1)) + radius); + } } } } diff --git a/src/libslic3r/GCode/SpiralVase.cpp b/src/libslic3r/GCode/SpiralVase.cpp index 8e8ae3075..a4ae42b31 100644 --- a/src/libslic3r/GCode/SpiralVase.cpp +++ b/src/libslic3r/GCode/SpiralVase.cpp @@ -24,7 +24,7 @@ std::string SpiralVase::process_layer(const std::string &gcode) // Get total XY length for this layer by summing all extrusion moves. float total_layer_length = 0; float layer_height = 0; - float z; + float z = 0.f; bool set_z = false; { diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index e25ad91fe..f10b45723 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -324,9 +324,8 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_ m_layer_tools[j].has_wipe_tower = true; } else { LayerTools <_extra = *m_layer_tools.insert(m_layer_tools.begin() + j, lt_new); - LayerTools <_prev = m_layer_tools[j - 1]; LayerTools <_next = m_layer_tools[j + 1]; - assert(! lt_prev.extruders.empty() && ! lt_next.extruders.empty()); + assert(! m_layer_tools[j - 1].extruders.empty() && ! lt_next.extruders.empty()); // FIXME: Following assert tripped when running combine_infill.t. I decided to comment it out for now. // If it is a bug, it's likely not critical, because this code is unchanged for a long time. It might // still be worth looking into it more and decide if it is a bug or an obsolete assert. @@ -495,9 +494,6 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int if (!is_overriddable(*fill, print.config(), *object, region)) continue; - // What extruder would this normally be printed with? - unsigned int correct_extruder = Print::get_extruder(*fill, region); - if (volume_to_wipe<=0) continue; diff --git a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/src/libslic3r/GCode/WipeTower.cpp similarity index 66% rename from src/libslic3r/GCode/WipeTowerPrusaMM.cpp rename to src/libslic3r/GCode/WipeTower.cpp index edfe475b5..37e4040d1 100644 --- a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -13,7 +13,7 @@ TODO LIST */ -#include "WipeTowerPrusaMM.hpp" +#include "WipeTower.hpp" #include #include @@ -35,12 +35,23 @@ TODO LIST namespace Slic3r { -namespace PrusaMultiMaterial { +// Rotate the point around center of the wipe tower about given angle (in degrees) +static Vec2f rotate(const Vec2f& pt, float width, float depth, float angle) +{ + Vec2f out(0,0); + float temp_x = pt(0) - width / 2.f; + float temp_y = pt(1) - depth / 2.f; + angle *= float(M_PI/180.); + out.x() += temp_x * cos(angle) - temp_y * sin(angle) + width / 2.f; + out.y() += temp_x * sin(angle) + temp_y * cos(angle) + depth / 2.f; + return out; +} -class Writer + +class WipeTowerWriter { public: - Writer(float layer_height, float line_width, GCodeFlavor flavor) : + WipeTowerWriter(float layer_height, float line_width, GCodeFlavor flavor, const std::vector& filament_parameters) : m_current_pos(std::numeric_limits::max(), std::numeric_limits::max()), m_current_z(0.f), m_current_feedrate(0.f), @@ -49,7 +60,8 @@ public: m_preview_suppressed(false), m_elapsed_time(0.f), m_default_analyzer_line_width(line_width), - m_gcode_flavor(flavor) + m_gcode_flavor(flavor), + m_filpar(filament_parameters) { // adds tag for analyzer: char buf[64]; @@ -60,7 +72,7 @@ public: change_analyzer_line_width(line_width); } - Writer& change_analyzer_line_width(float line_width) { + WipeTowerWriter& change_analyzer_line_width(float line_width) { // adds tag for analyzer: char buf[64]; sprintf(buf, ";%s%f\n", GCodeAnalyzer::Width_Tag.c_str(), line_width); @@ -68,25 +80,35 @@ public: return *this; } - Writer& set_initial_position(const WipeTower::xy &pos, float width = 0.f, float depth = 0.f, float internal_angle = 0.f) { + WipeTowerWriter& change_analyzer_mm3_per_mm(float len, float e) { + static const float area = M_PI * 1.75f * 1.75f / 4.f; + float mm3_per_mm = (len == 0.f ? 0.f : area * e / len); + // adds tag for analyzer: + char buf[64]; + sprintf(buf, ";%s%f\n", GCodeAnalyzer::Mm3_Per_Mm_Tag.c_str(), mm3_per_mm); + m_gcode += buf; + return *this; + } + + WipeTowerWriter& set_initial_position(const Vec2f &pos, float width = 0.f, float depth = 0.f, float internal_angle = 0.f) { m_wipe_tower_width = width; m_wipe_tower_depth = depth; m_internal_angle = internal_angle; - m_start_pos = WipeTower::xy(pos,0.f,m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle); + m_start_pos = rotate(pos + Vec2f(0.f,m_y_shift), m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle); m_current_pos = pos; return *this; } - Writer& set_initial_tool(const unsigned int tool) { m_current_tool = tool; return *this; } + WipeTowerWriter& set_initial_tool(const unsigned int tool) { m_current_tool = tool; return *this; } - Writer& set_z(float z) + WipeTowerWriter& set_z(float z) { m_current_z = z; return *this; } - Writer& set_extrusion_flow(float flow) + WipeTowerWriter& set_extrusion_flow(float flow) { m_extrusion_flow = flow; return *this; } - Writer& set_y_shift(float shift) { - m_current_pos.y -= shift-m_y_shift; + WipeTowerWriter& set_y_shift(float shift) { + m_current_pos.y() -= shift-m_y_shift; m_y_shift = shift; return (*this); } @@ -94,10 +116,10 @@ public: // Suppress / resume G-code preview in Slic3r. Slic3r will have difficulty to differentiate the various // filament loading and cooling moves from normal extrusion moves. Therefore the writer // is asked to suppres output of some lines, which look like extrusions. - Writer& suppress_preview() { change_analyzer_line_width(0.f); m_preview_suppressed = true; return *this; } - Writer& resume_preview() { change_analyzer_line_width(m_default_analyzer_line_width); m_preview_suppressed = false; return *this; } + WipeTowerWriter& suppress_preview() { change_analyzer_line_width(0.f); m_preview_suppressed = true; return *this; } + WipeTowerWriter& resume_preview() { change_analyzer_line_width(m_default_analyzer_line_width); m_preview_suppressed = false; return *this; } - Writer& feedrate(float f) + WipeTowerWriter& feedrate(float f) { if (f != m_current_feedrate) m_gcode += "G1" + set_format_F(f) + "\n"; @@ -106,33 +128,33 @@ public: const std::string& gcode() const { return m_gcode; } const std::vector& extrusions() const { return m_extrusions; } - float x() const { return m_current_pos.x; } - float y() const { return m_current_pos.y; } - const WipeTower::xy& pos() const { return m_current_pos; } - const WipeTower::xy start_pos_rotated() const { return m_start_pos; } - const WipeTower::xy pos_rotated() const { return WipeTower::xy(m_current_pos, 0.f, m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle); } + float x() const { return m_current_pos.x(); } + float y() const { return m_current_pos.y(); } + const Vec2f& pos() const { return m_current_pos; } + const Vec2f start_pos_rotated() const { return m_start_pos; } + const Vec2f pos_rotated() const { return rotate(m_current_pos + Vec2f(0.f, m_y_shift), m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle); } float elapsed_time() const { return m_elapsed_time; } float get_and_reset_used_filament_length() { float temp = m_used_filament_length; m_used_filament_length = 0.f; return temp; } // Extrude with an explicitely provided amount of extrusion. - Writer& extrude_explicit(float x, float y, float e, float f = 0.f, bool record_length = false) + WipeTowerWriter& extrude_explicit(float x, float y, float e, float f = 0.f, bool record_length = false, bool limit_volumetric_flow = true) { - if (x == m_current_pos.x && y == m_current_pos.y && e == 0.f && (f == 0.f || f == m_current_feedrate)) + if (x == m_current_pos.x() && y == m_current_pos.y() && e == 0.f && (f == 0.f || f == m_current_feedrate)) // Neither extrusion nor a travel move. return *this; - float dx = x - m_current_pos.x; - float dy = y - m_current_pos.y; + float dx = x - m_current_pos.x(); + float dy = y - m_current_pos.y(); double len = sqrt(dx*dx+dy*dy); if (record_length) m_used_filament_length += e; - // Now do the "internal rotation" with respect to the wipe tower center - WipeTower::xy rotated_current_pos(WipeTower::xy(m_current_pos,0.f,m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle)); // this is where we are - WipeTower::xy rot(WipeTower::xy(x,y+m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle)); // this is where we want to go + Vec2f rotated_current_pos(rotate(m_current_pos + Vec2f(0.f,m_y_shift), m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle)); // this is where we are + Vec2f rot(rotate(Vec2f(x,y+m_y_shift), m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle)); // this is where we want to go if (! m_preview_suppressed && e > 0.f && len > 0.) { + change_analyzer_mm3_per_mm(len, e); // Width of a squished extrusion, corrected for the roundings of the squished extrusions. // This is left zero if it is a travel move. float width = float(double(e) * /*Filament_Area*/2.40528 / (len * m_layer_height)); @@ -140,25 +162,30 @@ public: width += m_layer_height * float(1. - M_PI / 4.); if (m_extrusions.empty() || m_extrusions.back().pos != rotated_current_pos) m_extrusions.emplace_back(WipeTower::Extrusion(rotated_current_pos, 0, m_current_tool)); - m_extrusions.emplace_back(WipeTower::Extrusion(WipeTower::xy(rot.x, rot.y), width, m_current_tool)); + m_extrusions.emplace_back(WipeTower::Extrusion(rot, width, m_current_tool)); } m_gcode += "G1"; - if (std::abs(rot.x - rotated_current_pos.x) > EPSILON) - m_gcode += set_format_X(rot.x); + if (std::abs(rot.x() - rotated_current_pos.x()) > EPSILON) + m_gcode += set_format_X(rot.x()); - if (std::abs(rot.y - rotated_current_pos.y) > EPSILON) - m_gcode += set_format_Y(rot.y); + if (std::abs(rot.y() - rotated_current_pos.y()) > EPSILON) + m_gcode += set_format_Y(rot.y()); if (e != 0.f) m_gcode += set_format_E(e); - if (f != 0.f && f != m_current_feedrate) + if (f != 0.f && f != m_current_feedrate) { + if (limit_volumetric_flow) { + float e_speed = e / (((len == 0) ? std::abs(e) : len) / f * 60.f); + f /= std::max(1.f, e_speed / m_filpar[m_current_tool].max_e_speed); + } m_gcode += set_format_F(f); + } - m_current_pos.x = x; - m_current_pos.y = y; + m_current_pos.x() = x; + m_current_pos.y() = y; // Update the elapsed time with a rough estimate. m_elapsed_time += ((len == 0) ? std::abs(e) : len) / m_current_feedrate * 60.f; @@ -166,42 +193,42 @@ public: return *this; } - Writer& extrude_explicit(const WipeTower::xy &dest, float e, float f = 0.f, bool record_length = false) - { return extrude_explicit(dest.x, dest.y, e, f, record_length); } + WipeTowerWriter& extrude_explicit(const Vec2f &dest, float e, float f = 0.f, bool record_length = false, bool limit_volumetric_flow = true) + { return extrude_explicit(dest.x(), dest.y(), e, f, record_length); } // Travel to a new XY position. f=0 means use the current value. - Writer& travel(float x, float y, float f = 0.f) + WipeTowerWriter& travel(float x, float y, float f = 0.f) { return extrude_explicit(x, y, 0.f, f); } - Writer& travel(const WipeTower::xy &dest, float f = 0.f) - { return extrude_explicit(dest.x, dest.y, 0.f, f); } + WipeTowerWriter& travel(const Vec2f &dest, float f = 0.f) + { return extrude_explicit(dest.x(), dest.y(), 0.f, f); } // Extrude a line from current position to x, y with the extrusion amount given by m_extrusion_flow. - Writer& extrude(float x, float y, float f = 0.f) + WipeTowerWriter& extrude(float x, float y, float f = 0.f) { - float dx = x - m_current_pos.x; - float dy = y - m_current_pos.y; + float dx = x - m_current_pos.x(); + float dy = y - m_current_pos.y(); return extrude_explicit(x, y, sqrt(dx*dx+dy*dy) * m_extrusion_flow, f, true); } - Writer& extrude(const WipeTower::xy &dest, const float f = 0.f) - { return extrude(dest.x, dest.y, f); } + WipeTowerWriter& extrude(const Vec2f &dest, const float f = 0.f) + { return extrude(dest.x(), dest.y(), f); } - Writer& rectangle(const WipeTower::xy& ld,float width,float height,const float f = 0.f) + WipeTowerWriter& rectangle(const Vec2f& ld,float width,float height,const float f = 0.f) { - WipeTower::xy corners[4]; + Vec2f corners[4]; corners[0] = ld; - corners[1] = WipeTower::xy(ld,width,0.f); - corners[2] = WipeTower::xy(ld,width,height); - corners[3] = WipeTower::xy(ld,0.f,height); + corners[1] = ld + Vec2f(width,0.f); + corners[2] = ld + Vec2f(width,height); + corners[3] = ld + Vec2f(0.f,height); int index_of_closest = 0; - if (x()-ld.x > ld.x+width-x()) // closer to the right + if (x()-ld.x() > ld.x()+width-x()) // closer to the right index_of_closest = 1; - if (y()-ld.y > ld.y+height-y()) // closer to the top + if (y()-ld.y() > ld.y()+height-y()) // closer to the top index_of_closest = (index_of_closest==0 ? 3 : 2); - travel(corners[index_of_closest].x, y()); // travel to the closest corner - travel(x(),corners[index_of_closest].y); + travel(corners[index_of_closest].x(), y()); // travel to the closest corner + travel(x(),corners[index_of_closest].y()); int i = index_of_closest; do { @@ -212,7 +239,7 @@ public: return (*this); } - Writer& load(float e, float f = 0.f) + WipeTowerWriter& load(float e, float f = 0.f) { if (e == 0.f && (f == 0.f || f == m_current_feedrate)) return *this; @@ -224,29 +251,29 @@ public: m_gcode += "\n"; return *this; } - - // Derectract while moving in the X direction. - // If |x| > 0, the feed rate relates to the x distance, - // otherwise the feed rate relates to the e distance. - Writer& load_move_x(float x, float e, float f = 0.f) - { return extrude_explicit(x, m_current_pos.y, e, f); } - Writer& retract(float e, float f = 0.f) + WipeTowerWriter& retract(float e, float f = 0.f) { return load(-e, f); } // Loads filament while also moving towards given points in x-axis (x feedrate is limited by cutting the distance short if necessary) - Writer& load_move_x_advanced(float farthest_x, float loading_dist, float loading_speed, float max_x_speed = 50.f) + WipeTowerWriter& load_move_x_advanced(float farthest_x, float loading_dist, float loading_speed, float max_x_speed = 50.f) { - float time = std::abs(loading_dist / loading_speed); - float x_speed = std::min(max_x_speed, std::abs(farthest_x - x()) / time); - float feedrate = 60.f * std::hypot(x_speed, loading_speed); + float time = std::abs(loading_dist / loading_speed); // time that the move must take + float x_distance = std::abs(farthest_x - x()); // max x-distance that we can travel + float x_speed = x_distance / time; // x-speed to do it in that time - float end_point = x() + (farthest_x > x() ? 1.f : -1.f) * x_speed * time; - return extrude_explicit(end_point, y(), loading_dist, feedrate); + if (x_speed > max_x_speed) { + // Necessary x_speed is too high - we must shorten the distance to achieve max_x_speed and still respect the time. + x_distance = max_x_speed * time; + x_speed = max_x_speed; + } + + float end_point = x() + (farthest_x > x() ? 1.f : -1.f) * x_distance; + return extrude_explicit(end_point, y(), loading_dist, x_speed * 60.f, false, false); } // Elevate the extruder head above the current print_z position. - Writer& z_hop(float hop, float f = 0.f) + WipeTowerWriter& z_hop(float hop, float f = 0.f) { m_gcode += std::string("G1") + set_format_Z(m_current_z + hop); if (f != 0 && f != m_current_feedrate) @@ -256,39 +283,36 @@ public: } // Lower the extruder head back to the current print_z position. - Writer& z_hop_reset(float f = 0.f) + WipeTowerWriter& z_hop_reset(float f = 0.f) { return z_hop(0, f); } // Move to x1, +y_increment, // extrude quickly amount e to x2 with feed f. - Writer& ram(float x1, float x2, float dy, float e0, float e, float f) + WipeTowerWriter& ram(float x1, float x2, float dy, float e0, float e, float f) { - extrude_explicit(x1, m_current_pos.y + dy, e0, f, true); - extrude_explicit(x2, m_current_pos.y, e, 0.f, true); + extrude_explicit(x1, m_current_pos.y() + dy, e0, f, true, false); + extrude_explicit(x2, m_current_pos.y(), e, 0.f, true, false); return *this; } // Let the end of the pulled out filament cool down in the cooling tube // by moving up and down and moving the print head left / right // at the current Y position to spread the leaking material. - Writer& cool(float x1, float x2, float e1, float e2, float f) + WipeTowerWriter& cool(float x1, float x2, float e1, float e2, float f) { - extrude_explicit(x1, m_current_pos.y, e1, f); - extrude_explicit(x2, m_current_pos.y, e2); + extrude_explicit(x1, m_current_pos.y(), e1, f, false, false); + extrude_explicit(x2, m_current_pos.y(), e2, false, false); return *this; } - Writer& set_tool(int tool) + WipeTowerWriter& set_tool(int tool) { - char buf[64]; - sprintf(buf, "T%d\n", tool); - m_gcode += buf; m_current_tool = tool; return *this; } // Set extruder temperature, don't wait by default. - Writer& set_extruder_temp(int temperature, bool wait = false) + WipeTowerWriter& set_extruder_temp(int temperature, bool wait = false) { char buf[128]; sprintf(buf, "M%d S%d\n", wait ? 109 : 104, temperature); @@ -297,7 +321,7 @@ public: }; // Wait for a period of time (seconds). - Writer& wait(float time) + WipeTowerWriter& wait(float time) { if (time==0) return *this; @@ -308,7 +332,7 @@ public: }; // Set speed factor override percentage. - Writer& speed_override(int speed) + WipeTowerWriter& speed_override(int speed) { char buf[128]; sprintf(buf, "M220 S%d\n", speed); @@ -317,21 +341,21 @@ public: }; // Let the firmware back up the active speed override value. - Writer& speed_override_backup() + WipeTowerWriter& speed_override_backup() { m_gcode += "M220 B\n"; return *this; }; // Let the firmware restore the active speed override value. - Writer& speed_override_restore() + WipeTowerWriter& speed_override_restore() { m_gcode += "M220 R\n"; return *this; }; // Set digital trimpot motor - Writer& set_extruder_trimpot(int current) + WipeTowerWriter& set_extruder_trimpot(int current) { char buf[128]; if (m_gcode_flavor == gcfRepRap) @@ -342,20 +366,20 @@ public: return *this; }; - Writer& flush_planner_queue() + WipeTowerWriter& flush_planner_queue() { m_gcode += "G4 S0\n"; return *this; } // Reset internal extruder counter. - Writer& reset_extruder() + WipeTowerWriter& reset_extruder() { m_gcode += "G92 E0\n"; return *this; } - Writer& comment_with_value(const char *comment, int value) + WipeTowerWriter& comment_with_value(const char *comment, int value) { char strvalue[64]; sprintf(strvalue, "%d", value); @@ -364,7 +388,7 @@ public: }; - Writer& set_fan(unsigned int speed) + WipeTowerWriter& set_fan(unsigned int speed) { if (speed == m_last_fan_speed) return *this; @@ -382,18 +406,11 @@ public: return *this; } - Writer& comment_material(WipeTowerPrusaMM::material_type material) - { - m_gcode += "; material : "; - m_gcode += WipeTowerPrusaMM::to_string(material) + "\n"; - return *this; - }; - - Writer& append(const char *text) { m_gcode += text; return *this; } + WipeTowerWriter& append(const std::string& text) { m_gcode += text; return *this; } private: - WipeTower::xy m_start_pos; - WipeTower::xy m_current_pos; + Vec2f m_start_pos; + Vec2f m_current_pos; float m_current_z; float m_current_feedrate; unsigned int m_current_tool; @@ -412,19 +429,20 @@ private: const float m_default_analyzer_line_width; float m_used_filament_length = 0.f; GCodeFlavor m_gcode_flavor; + const std::vector& m_filpar; std::string set_format_X(float x) { char buf[64]; sprintf(buf, " X%.3f", x); - m_current_pos.x = x; + m_current_pos.x() = x; return buf; } std::string set_format_Y(float y) { char buf[64]; sprintf(buf, " Y%.3f", y); - m_current_pos.y = y; + m_current_pos.y() = y; return buf; } @@ -447,58 +465,13 @@ private: return buf; } - Writer& operator=(const Writer &rhs); -}; // class Writer - -}; // namespace PrusaMultiMaterial + WipeTowerWriter& operator=(const WipeTowerWriter &rhs); +}; // class WipeTowerWriter -WipeTowerPrusaMM::material_type WipeTowerPrusaMM::parse_material(const char *name) -{ - if (strcasecmp(name, "PLA") == 0) - return PLA; - if (strcasecmp(name, "ABS") == 0) - return ABS; - if (strcasecmp(name, "PET") == 0) - return PET; - if (strcasecmp(name, "HIPS") == 0) - return HIPS; - if (strcasecmp(name, "FLEX") == 0) - return FLEX; - if (strcasecmp(name, "SCAFF") == 0) - return SCAFF; - if (strcasecmp(name, "EDGE") == 0) - return EDGE; - if (strcasecmp(name, "NGEN") == 0) - return NGEN; - if (strcasecmp(name, "PVA") == 0) - return PVA; - if (strcasecmp(name, "PC") == 0) - return PC; - return INVALID; -} - -std::string WipeTowerPrusaMM::to_string(material_type material) -{ - switch (material) { - case PLA: return "PLA"; - case ABS: return "ABS"; - case PET: return "PET"; - case HIPS: return "HIPS"; - case FLEX: return "FLEX"; - case SCAFF: return "SCAFF"; - case EDGE: return "EDGE"; - case NGEN: return "NGEN"; - case PVA: return "PVA"; - case PC: return "PC"; - case INVALID: - default: return "INVALID"; - } -} - // Returns gcode to prime the nozzles at the front edge of the print bed. -WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( +std::vector WipeTower::prime( // print_z of the first layer. float first_layer_height, // Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object. @@ -516,25 +489,36 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( // box_coordinates cleaning_box(xy(0.5f, - 1.5f), m_wipe_tower_width, wipe_area); const float prime_section_width = std::min(240.f / tools.size(), 60.f); - box_coordinates cleaning_box(xy(5.f, 0.01f + m_perimeter_width/2.f), prime_section_width, 100.f); + box_coordinates cleaning_box(Vec2f(5.f, 0.01f + m_perimeter_width/2.f), prime_section_width, 100.f); - PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width, m_gcode_flavor); - writer.set_extrusion_flow(m_extrusion_flow) - .set_z(m_z_pos) - .set_initial_tool(m_current_tool) - .append(";--------------------\n" - "; CP PRIMING START\n") - .append(";--------------------\n"); - if (m_retain_speed_override) - writer.speed_override_backup(); - writer.speed_override(100); - writer.set_initial_position(xy(0.f, 0.f)) // Always move to the starting position - .travel(cleaning_box.ld, 7200); - if (m_set_extruder_trimpot) - writer.set_extruder_trimpot(750); // Increase the extruder driver current to allow fast ramming. + std::vector results; + // Iterate over all priming toolchanges and push respective ToolChangeResults into results vector. for (size_t idx_tool = 0; idx_tool < tools.size(); ++ idx_tool) { + int old_tool = m_current_tool; + + WipeTowerWriter writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); + writer.set_extrusion_flow(m_extrusion_flow) + .set_z(m_z_pos) + .set_initial_tool(m_current_tool); + + // This is the first toolchange - initiate priming + if (idx_tool == 0) { + writer.append(";--------------------\n" + "; CP PRIMING START\n") + .append(";--------------------\n") + .speed_override_backup() + .speed_override(100) + .set_initial_position(Vec2f::Zero()) // Always move to the starting position + .travel(cleaning_box.ld, 7200); + if (m_set_extruder_trimpot) + writer.set_extruder_trimpot(750); // Increase the extruder driver current to allow fast ramming. + } + else + writer.set_initial_position(results.back().end_pos); + + unsigned int tool = tools[idx_tool]; m_left_to_right = true; toolchange_Change(writer, tool, m_filpar[tool].material); // Select the tool, set a speed override for soluble and flex materials. @@ -547,53 +531,63 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( //writer.travel(writer.x(), writer.y() + m_perimeter_width, 7200); toolchange_Wipe(writer, cleaning_box , 20.f); box_coordinates box = cleaning_box; - box.translate(0.f, writer.y() - cleaning_box.ld.y + m_perimeter_width); + box.translate(0.f, writer.y() - cleaning_box.ld.y() + m_perimeter_width); toolchange_Unload(writer, box , m_filpar[m_current_tool].material, m_filpar[tools[idx_tool + 1]].first_layer_temperature); cleaning_box.translate(prime_section_width, 0.f); writer.travel(cleaning_box.ld, 7200); } ++ m_num_tool_changes; + + + // Ask our writer about how much material was consumed: + if (m_current_tool < m_used_filament_length.size()) + m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); + + ToolChangeResult result; + result.priming = true; + result.initial_tool = old_tool; + result.new_tool = m_current_tool; + result.print_z = this->m_z_pos; + result.layer_height = this->m_layer_height; + result.gcode = writer.gcode(); + result.elapsed_time = writer.elapsed_time(); + result.extrusions = writer.extrusions(); + result.start_pos = writer.start_pos_rotated(); + result.end_pos = writer.pos_rotated(); + + results.push_back(std::move(result)); + + // This is the last priming toolchange - finish priming + if (idx_tool+1 == tools.size()) { + // Reset the extruder current to a normal value. + if (m_set_extruder_trimpot) + writer.set_extruder_trimpot(550); + writer.speed_override_restore() + .feedrate(6000) + .flush_planner_queue() + .reset_extruder() + .append("; CP PRIMING END\n" + ";------------------\n" + "\n\n"); + } } m_old_temperature = -1; // If the priming is turned off in config, the temperature changing commands will not actually appear // in the output gcode - we should not remember emitting them (we will output them twice in the worst case) - // Reset the extruder current to a normal value. - if (m_set_extruder_trimpot) - writer.set_extruder_trimpot(550); - if (m_retain_speed_override) - writer.speed_override_restore(); - writer.feedrate(6000) - .flush_planner_queue() - .reset_extruder() - .append("; CP PRIMING END\n" - ";------------------\n" - "\n\n"); - // so that tool_change() will know to extrude the wipe tower brim: m_print_brim = true; - // Ask our writer about how much material was consumed: - if (m_current_tool < m_used_filament_length.size()) - m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); - - ToolChangeResult result; - result.priming = true; - result.print_z = this->m_z_pos; - result.layer_height = this->m_layer_height; - result.gcode = writer.gcode(); - result.elapsed_time = writer.elapsed_time(); - result.extrusions = writer.extrusions(); - result.start_pos = writer.start_pos_rotated(); - result.end_pos = writer.pos_rotated(); - return result; + return results; } -WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, bool last_in_layer) +WipeTower::ToolChangeResult WipeTower::tool_change(unsigned int tool, bool last_in_layer) { if ( m_print_brim ) return toolchange_Brim(); + int old_tool = m_current_tool; + float wipe_area = 0.f; bool last_change_in_layer = false; float wipe_volume = 0.f; @@ -615,26 +609,28 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo } box_coordinates cleaning_box( - xy(m_perimeter_width / 2.f, m_perimeter_width / 2.f), + Vec2f(m_perimeter_width / 2.f, m_perimeter_width / 2.f), m_wipe_tower_width - m_perimeter_width, (tool != (unsigned int)(-1) ? /*m_layer_info->depth*/wipe_area+m_depth_traversed-0.5*m_perimeter_width : m_wipe_tower_depth-m_perimeter_width)); - PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width, m_gcode_flavor); + WipeTowerWriter writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); writer.set_extrusion_flow(m_extrusion_flow) .set_z(m_z_pos) .set_initial_tool(m_current_tool) .set_y_shift(m_y_shift + (tool!=(unsigned int)(-1) && (m_current_shape == SHAPE_REVERSED && !m_peters_wipe_tower) ? m_layer_info->depth - m_layer_info->toolchanges_depth(): 0.f)) .append(";--------------------\n" "; CP TOOLCHANGE START\n") - .comment_with_value(" toolchange #", m_num_tool_changes + 1) // the number is zero-based - .comment_material(m_filpar[m_current_tool].material) - .append(";--------------------\n"); - if (m_retain_speed_override) - writer.speed_override_backup(); + .comment_with_value(" toolchange #", m_num_tool_changes + 1); // the number is zero-based + + if (tool != (unsigned)(-1)) + writer.append(std::string("; material : " + (m_current_tool < m_filpar.size() ? m_filpar[m_current_tool].material : "(NONE)") + " -> " + m_filpar[tool].material + "\n").c_str()) + .append(";--------------------\n"); + + writer.speed_override_backup(); writer.speed_override(100); - xy initial_position = cleaning_box.ld + WipeTower::xy(0.f,m_depth_traversed); + Vec2f initial_position = cleaning_box.ld + Vec2f(0.f, m_depth_traversed); writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); // Increase the extruder driver current to allow fast ramming. @@ -658,9 +654,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo if (last_change_in_layer) {// draw perimeter line writer.set_y_shift(m_y_shift); if (m_peters_wipe_tower) - writer.rectangle(WipeTower::xy(0.f, 0.f),m_layer_info->depth + 3*m_perimeter_width,m_wipe_tower_depth); + writer.rectangle(Vec2f::Zero(), m_layer_info->depth + 3*m_perimeter_width, m_wipe_tower_depth); else { - writer.rectangle(WipeTower::xy(0.f, 0.f),m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); + writer.rectangle(Vec2f::Zero(), m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); if (layer_finished()) { // no finish_layer will be called, we must wipe the nozzle writer.travel(writer.x()> m_wipe_tower_width / 2.f ? 0.f : m_wipe_tower_width, writer.y()); } @@ -669,8 +665,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo if (m_set_extruder_trimpot) writer.set_extruder_trimpot(550); // Reset the extruder current to a normal value. - if (m_retain_speed_override) - writer.speed_override_restore(); + writer.speed_override_restore(); writer.feedrate(6000) .flush_planner_queue() .reset_extruder() @@ -684,6 +679,8 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo ToolChangeResult result; result.priming = false; + result.initial_tool = old_tool; + result.new_tool = m_current_tool; result.print_z = this->m_z_pos; result.layer_height = this->m_layer_height; result.gcode = writer.gcode(); @@ -694,25 +691,27 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo return result; } -WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, float y_offset) +WipeTower::ToolChangeResult WipeTower::toolchange_Brim(bool sideOnly, float y_offset) { + int old_tool = m_current_tool; + const box_coordinates wipeTower_box( - WipeTower::xy(0.f, 0.f), + Vec2f::Zero(), m_wipe_tower_width, m_wipe_tower_depth); - PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width, m_gcode_flavor); + WipeTowerWriter writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); writer.set_extrusion_flow(m_extrusion_flow * 1.1f) .set_z(m_z_pos) // Let the writer know the current Z position as a base for Z-hop. .set_initial_tool(m_current_tool) .append(";-------------------------------------\n" "; CP WIPE TOWER FIRST LAYER BRIM START\n"); - xy initial_position = wipeTower_box.lu - xy(m_perimeter_width * 6.f, 0); + Vec2f initial_position = wipeTower_box.lu - Vec2f(m_perimeter_width * 6.f, 0); writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); - writer.extrude_explicit(wipeTower_box.ld - xy(m_perimeter_width * 6.f, 0), // Prime the extruder left of the wipe tower. - 1.5f * m_extrusion_flow * (wipeTower_box.lu.y - wipeTower_box.ld.y), 2400); + writer.extrude_explicit(wipeTower_box.ld - Vec2f(m_perimeter_width * 6.f, 0), // Prime the extruder left of the wipe tower. + 1.5f * m_extrusion_flow * (wipeTower_box.lu.y() - wipeTower_box.ld.y()), 2400); // The tool is supposed to be active and primed at the time when the wipe tower brim is extruded. // Extrude 4 rounds of a brim around the future wipe tower. @@ -738,6 +737,8 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, flo ToolChangeResult result; result.priming = false; + result.initial_tool = old_tool; + result.new_tool = m_current_tool; result.print_z = this->m_z_pos; result.layer_height = this->m_layer_height; result.gcode = writer.gcode(); @@ -751,14 +752,14 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, flo // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. -void WipeTowerPrusaMM::toolchange_Unload( - PrusaMultiMaterial::Writer &writer, +void WipeTower::toolchange_Unload( + WipeTowerWriter &writer, const box_coordinates &cleaning_box, - const material_type current_material, + const std::string& current_material, const int new_temperature) { - float xl = cleaning_box.ld.x + 1.f * m_perimeter_width; - float xr = cleaning_box.rd.x - 1.f * m_perimeter_width; + float xl = cleaning_box.ld.x() + 1.f * m_perimeter_width; + float xr = cleaning_box.rd.x() - 1.f * m_perimeter_width; const float line_width = m_perimeter_width * m_filpar[m_current_tool].ramming_line_width_multiplicator; // desired ramming line thickness const float y_step = line_width * m_filpar[m_current_tool].ramming_step_multiplicator * m_extra_spacing; // spacing between lines in mm @@ -771,7 +772,7 @@ void WipeTowerPrusaMM::toolchange_Unload( float remaining = xr - xl ; // keeps track of distance to the next turnaround float e_done = 0; // measures E move done from each segment - writer.travel(xl, cleaning_box.ld.y + m_depth_traversed + y_step/2.f ); // move to starting position + writer.travel(xl, cleaning_box.ld.y() + m_depth_traversed + y_step/2.f ); // move to starting position // if the ending point of the ram would end up in mid air, align it with the end of the wipe tower: if (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion )) { @@ -824,7 +825,7 @@ void WipeTowerPrusaMM::toolchange_Unload( const float e = m_filpar[m_current_tool].ramming_speed[i] * 0.25f / Filament_Area; // transform volume per sec to E move; const float dist = std::min(x - e_done, remaining); // distance to travel for either the next 0.25s, or to the next turnaround const float actual_time = dist/x * 0.25; - writer.ram(writer.x(), writer.x() + (m_left_to_right ? 1.f : -1.f) * dist, 0, 0, e * (dist / x), std::hypot(dist, e * (dist / x)) / (actual_time / 60.)); + writer.ram(writer.x(), writer.x() + (m_left_to_right ? 1.f : -1.f) * dist, 0, 0, e * (dist / x), dist / (actual_time / 60.)); remaining -= dist; if (remaining < WT_EPSILON) { // we reached a turning point @@ -838,25 +839,27 @@ void WipeTowerPrusaMM::toolchange_Unload( e_done = 0; } } - WipeTower::xy end_of_ramming(writer.x(),writer.y()); + Vec2f end_of_ramming(writer.x(),writer.y()); writer.change_analyzer_line_width(m_perimeter_width); // so the next lines are not affected by ramming_line_width_multiplier // Retraction: float old_x = writer.x(); float turning_point = (!m_left_to_right ? xl : xr ); - float total_retraction_distance = m_cooling_tube_retraction + m_cooling_tube_length/2.f - 15.f; // the 15mm is reserved for the first part after ramming - writer.suppress_preview() - .retract(15.f, m_filpar[m_current_tool].unloading_speed_start * 60.f) // feedrate 5000mm/min = 83mm/s - .retract(0.70f * total_retraction_distance, 1.0f * m_filpar[m_current_tool].unloading_speed * 60.f) - .retract(0.20f * total_retraction_distance, 0.5f * m_filpar[m_current_tool].unloading_speed * 60.f) - .retract(0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed * 60.f) - - /*.load_move_x_advanced(turning_point, -15.f, 83.f, 50.f) // this is done at fixed speed - .load_move_x_advanced(old_x, -0.70f * total_retraction_distance, 1.0f * m_filpar[m_current_tool].unloading_speed) - .load_move_x_advanced(turning_point, -0.20f * total_retraction_distance, 0.5f * m_filpar[m_current_tool].unloading_speed) - .load_move_x_advanced(old_x, -0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed) - .travel(old_x, writer.y()) // in case previous move was shortened to limit feedrate*/ - .resume_preview(); + if (m_semm && (m_cooling_tube_retraction != 0 || m_cooling_tube_length != 0)) { + float total_retraction_distance = m_cooling_tube_retraction + m_cooling_tube_length/2.f - 15.f; // the 15mm is reserved for the first part after ramming + writer.suppress_preview() + .retract(15.f, m_filpar[m_current_tool].unloading_speed_start * 60.f) // feedrate 5000mm/min = 83mm/s + .retract(0.70f * total_retraction_distance, 1.0f * m_filpar[m_current_tool].unloading_speed * 60.f) + .retract(0.20f * total_retraction_distance, 0.5f * m_filpar[m_current_tool].unloading_speed * 60.f) + .retract(0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed * 60.f) + + /*.load_move_x_advanced(turning_point, -15.f, 83.f, 50.f) // this is done at fixed speed + .load_move_x_advanced(old_x, -0.70f * total_retraction_distance, 1.0f * m_filpar[m_current_tool].unloading_speed) + .load_move_x_advanced(turning_point, -0.20f * total_retraction_distance, 0.5f * m_filpar[m_current_tool].unloading_speed) + .load_move_x_advanced(old_x, -0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed) + .travel(old_x, writer.y()) // in case previous move was shortened to limit feedrate*/ + .resume_preview(); + } if (new_temperature != 0 && (new_temperature != m_old_temperature || m_is_first_layer) ) { // Set the extruder temperature, but don't wait. // If the required temperature is the same as last time, don't emit the M104 again (if user adjusted the value, it would be reset) // However, always change temperatures on the first layer (this is to avoid issues with priming lines turned off). @@ -891,73 +894,72 @@ void WipeTowerPrusaMM::toolchange_Unload( // this is to align ramming and future wiping extrusions, so the future y-steps can be uniform from the start: // the perimeter_width will later be subtracted, it is there to not load while moving over just extruded material - writer.travel(end_of_ramming.x, end_of_ramming.y + (y_step/m_extra_spacing-m_perimeter_width) / 2.f + m_perimeter_width, 2400.f); + writer.travel(end_of_ramming.x(), end_of_ramming.y() + (y_step/m_extra_spacing-m_perimeter_width) / 2.f + m_perimeter_width, 2400.f); writer.resume_preview() .flush_planner_queue(); } // Change the tool, set a speed override for soluble and flex materials. -void WipeTowerPrusaMM::toolchange_Change( - PrusaMultiMaterial::Writer &writer, +void WipeTower::toolchange_Change( + WipeTowerWriter &writer, const unsigned int new_tool, - material_type new_material) + const std::string& new_material) { // Ask the writer about how much of the old filament we consumed: if (m_current_tool < m_used_filament_length.size()) m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); - // Speed override for the material. Go slow for flex and soluble materials. - int speed_override; - switch (new_material) { - case PVA: speed_override = (m_z_pos < 0.80f) ? 60 : 80; break; - case SCAFF: speed_override = 35; break; - case FLEX: speed_override = 35; break; - default: speed_override = 100; - } - writer.set_tool(new_tool); - if (m_retain_speed_override) - assert(speed_override == 100); - else - writer.speed_override(speed_override); + // This is where we want to place the custom gcodes. We will use placeholders for this. + // These will be substituted by the actual gcodes when the gcode is generated. + writer.append("[end_filament_gcode]\n"); + writer.append("[toolchange_gcode]\n"); + + // The toolchange Tn command will be inserted later, only in case that the user does + // not provide a custom toolchange gcode. + writer.set_tool(new_tool); // This outputs nothing, the writer just needs to know the tool has changed. + writer.append("[start_filament_gcode]\n"); + writer.flush_planner_queue(); m_current_tool = new_tool; } -void WipeTowerPrusaMM::toolchange_Load( - PrusaMultiMaterial::Writer &writer, +void WipeTower::toolchange_Load( + WipeTowerWriter &writer, const box_coordinates &cleaning_box) -{ - float xl = cleaning_box.ld.x + m_perimeter_width * 0.75f; - float xr = cleaning_box.rd.x - m_perimeter_width * 0.75f; - float oldx = writer.x(); // the nozzle is in place to do the first wiping moves, we will remember the position +{ + if (m_semm && (m_parking_pos_retraction != 0 || m_extra_loading_move != 0)) { + float xl = cleaning_box.ld.x() + m_perimeter_width * 0.75f; + float xr = cleaning_box.rd.x() - m_perimeter_width * 0.75f; + float oldx = writer.x(); // the nozzle is in place to do the first wiping moves, we will remember the position - // Load the filament while moving left / right, so the excess material will not create a blob at a single position. - float turning_point = ( oldx-xl < xr-oldx ? xr : xl ); - float edist = m_parking_pos_retraction+m_extra_loading_move; + // Load the filament while moving left / right, so the excess material will not create a blob at a single position. + float turning_point = ( oldx-xl < xr-oldx ? xr : xl ); + float edist = m_parking_pos_retraction+m_extra_loading_move; - writer.append("; CP TOOLCHANGE LOAD\n") - .suppress_preview() - /*.load_move_x_advanced(turning_point, 0.2f * edist, 0.3f * m_filpar[m_current_tool].loading_speed) // Acceleration - .load_move_x_advanced(oldx, 0.5f * edist, m_filpar[m_current_tool].loading_speed) // Fast phase - .load_move_x_advanced(turning_point, 0.2f * edist, 0.3f * m_filpar[m_current_tool].loading_speed) // Slowing down - .load_move_x_advanced(oldx, 0.1f * edist, 0.1f * m_filpar[m_current_tool].loading_speed) // Super slow*/ + writer.append("; CP TOOLCHANGE LOAD\n") + .suppress_preview() + /*.load_move_x_advanced(turning_point, 0.2f * edist, 0.3f * m_filpar[m_current_tool].loading_speed) // Acceleration + .load_move_x_advanced(oldx, 0.5f * edist, m_filpar[m_current_tool].loading_speed) // Fast phase + .load_move_x_advanced(turning_point, 0.2f * edist, 0.3f * m_filpar[m_current_tool].loading_speed) // Slowing down + .load_move_x_advanced(oldx, 0.1f * edist, 0.1f * m_filpar[m_current_tool].loading_speed) // Super slow*/ - .load(0.2f * edist, 60.f * m_filpar[m_current_tool].loading_speed_start) - .load_move_x_advanced(turning_point, 0.7f * edist, m_filpar[m_current_tool].loading_speed) // Fast phase - .load_move_x_advanced(oldx, 0.1f * edist, 0.1f * m_filpar[m_current_tool].loading_speed) // Super slow*/ + .load(0.2f * edist, 60.f * m_filpar[m_current_tool].loading_speed_start) + .load_move_x_advanced(turning_point, 0.7f * edist, m_filpar[m_current_tool].loading_speed) // Fast phase + .load_move_x_advanced(oldx, 0.1f * edist, 0.1f * m_filpar[m_current_tool].loading_speed) // Super slow*/ - .travel(oldx, writer.y()) // in case last move was shortened to limit x feedrate - .resume_preview(); + .travel(oldx, writer.y()) // in case last move was shortened to limit x feedrate + .resume_preview(); - // Reset the extruder current to the normal value. - if (m_set_extruder_trimpot) - writer.set_extruder_trimpot(550); + // Reset the extruder current to the normal value. + if (m_set_extruder_trimpot) + writer.set_extruder_trimpot(550); + } } // Wipe the newly loaded filament until the end of the assigned wipe area. -void WipeTowerPrusaMM::toolchange_Wipe( - PrusaMultiMaterial::Writer &writer, +void WipeTower::toolchange_Wipe( + WipeTowerWriter &writer, const box_coordinates &cleaning_box, float wipe_volume) { @@ -965,8 +967,8 @@ void WipeTowerPrusaMM::toolchange_Wipe( writer.set_extrusion_flow(m_extrusion_flow * (m_is_first_layer ? 1.18f : 1.f)) .append("; CP TOOLCHANGE WIPE\n"); float wipe_coeff = m_is_first_layer ? 0.5f : 1.f; - const float& xl = cleaning_box.ld.x; - const float& xr = cleaning_box.rd.x; + const float& xl = cleaning_box.ld.x(); + const float& xr = cleaning_box.rd.x(); // Variables x_to_wipe and traversed_x are here to be able to make sure it always wipes at least // the ordered volume, even if it means violating the box. This can later be removed and simply @@ -997,7 +999,7 @@ void WipeTowerPrusaMM::toolchange_Wipe( else writer.extrude(xl + (i % 4 == 1 ? 0 : 1.5*m_perimeter_width), writer.y(), wipe_speed * wipe_coeff); - if (writer.y()+EPSILON > cleaning_box.lu.y-0.5f*m_perimeter_width) + if (writer.y()+EPSILON > cleaning_box.lu.y()-0.5f*m_perimeter_width) break; // in case next line would not fit traversed_x -= writer.x(); @@ -1024,13 +1026,15 @@ void WipeTowerPrusaMM::toolchange_Wipe( -WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer() +WipeTower::ToolChangeResult WipeTower::finish_layer() { // This should only be called if the layer is not finished yet. // Otherwise the caller would likely travel to the wipe tower in vain. assert(! this->layer_finished()); - PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width, m_gcode_flavor); + int old_tool = m_current_tool; + + WipeTowerWriter writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); writer.set_extrusion_flow(m_extrusion_flow) .set_z(m_z_pos) .set_initial_tool(m_current_tool) @@ -1042,7 +1046,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer() // Slow down on the 1st layer. float speed_factor = m_is_first_layer ? 0.5f : 1.f; float current_depth = m_layer_info->depth - m_layer_info->toolchanges_depth(); - box_coordinates fill_box(xy(m_perimeter_width, m_depth_traversed + m_perimeter_width), + box_coordinates fill_box(Vec2f(m_perimeter_width, m_depth_traversed + m_perimeter_width), m_wipe_tower_width - 2 * m_perimeter_width, current_depth-m_perimeter_width); @@ -1056,44 +1060,44 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer() else box.expand(-m_perimeter_width); } else i=2; // only draw the inner perimeter, outer has been already drawn by tool_change(...) - writer.rectangle(box.ld,box.rd.x-box.ld.x,box.ru.y-box.rd.y,2900*speed_factor); + writer.rectangle(box.ld, box.rd.x()-box.ld.x(), box.ru.y()-box.rd.y(), 2900*speed_factor); } // we are in one of the corners, travel to ld along the perimeter: - if (writer.x() > fill_box.ld.x+EPSILON) writer.travel(fill_box.ld.x,writer.y()); - if (writer.y() > fill_box.ld.y+EPSILON) writer.travel(writer.x(),fill_box.ld.y); + if (writer.x() > fill_box.ld.x()+EPSILON) writer.travel(fill_box.ld.x(),writer.y()); + if (writer.y() > fill_box.ld.y()+EPSILON) writer.travel(writer.x(),fill_box.ld.y()); if (m_is_first_layer && m_adhesion) { // Extrude a dense infill at the 1st layer to improve 1st layer adhesion of the wipe tower. box.expand(-m_perimeter_width/2.f); - int nsteps = int(floor((box.lu.y - box.ld.y) / (2*m_perimeter_width))); - float step = (box.lu.y - box.ld.y) / nsteps; - writer.travel(box.ld-xy(m_perimeter_width/2.f,m_perimeter_width/2.f)); + int nsteps = int(floor((box.lu.y() - box.ld.y()) / (2*m_perimeter_width))); + float step = (box.lu.y() - box.ld.y()) / nsteps; + writer.travel(box.ld - Vec2f(m_perimeter_width/2.f, m_perimeter_width/2.f)); if (nsteps >= 0) for (int i = 0; i < nsteps; ++i) { - writer.extrude(box.ld.x+m_perimeter_width/2.f, writer.y() + 0.5f * step); - writer.extrude(box.rd.x - m_perimeter_width / 2.f, writer.y()); - writer.extrude(box.rd.x - m_perimeter_width / 2.f, writer.y() + 0.5f * step); - writer.extrude(box.ld.x + m_perimeter_width / 2.f, writer.y()); + writer.extrude(box.ld.x()+m_perimeter_width/2.f, writer.y() + 0.5f * step); + writer.extrude(box.rd.x() - m_perimeter_width / 2.f, writer.y()); + writer.extrude(box.rd.x() - m_perimeter_width / 2.f, writer.y() + 0.5f * step); + writer.extrude(box.ld.x() + m_perimeter_width / 2.f, writer.y()); } - writer.travel(box.rd.x-m_perimeter_width/2.f,writer.y()); // wipe the nozzle + writer.travel(box.rd.x()-m_perimeter_width/2.f,writer.y()); // wipe the nozzle } else { // Extrude a sparse infill to support the material to be printed above. - const float dy = (fill_box.lu.y - fill_box.ld.y - m_perimeter_width); - const float left = fill_box.lu.x+2*m_perimeter_width; - const float right = fill_box.ru.x - 2 * m_perimeter_width; + const float dy = (fill_box.lu.y() - fill_box.ld.y() - m_perimeter_width); + const float left = fill_box.lu.x() + 2*m_perimeter_width; + const float right = fill_box.ru.x() - 2 * m_perimeter_width; if (dy > m_perimeter_width) { // Extrude an inverse U at the left of the region. - writer.travel(fill_box.ld + xy(m_perimeter_width * 2, 0.f)) - .extrude(fill_box.lu + xy(m_perimeter_width * 2, 0.f), 2900 * speed_factor); + writer.travel(fill_box.ld + Vec2f(m_perimeter_width * 2, 0.f)) + .extrude(fill_box.lu + Vec2f(m_perimeter_width * 2, 0.f), 2900 * speed_factor); const int n = 1+(right-left)/(m_bridging); const float dx = (right-left)/n; for (int i=1;i<=n;++i) { float x=left+dx*i; writer.travel(x,writer.y()); - writer.extrude(x,i%2 ? fill_box.rd.y : fill_box.ru.y); + writer.extrude(x,i%2 ? fill_box.rd.y() : fill_box.ru.y()); } writer.travel(left,writer.y(),7200); // wipes the nozzle before moving away from the wipe tower } @@ -1111,6 +1115,8 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer() ToolChangeResult result; result.priming = false; + result.initial_tool = old_tool; + result.new_tool = m_current_tool; result.print_z = this->m_z_pos; result.layer_height = this->m_layer_height; result.gcode = writer.gcode(); @@ -1122,7 +1128,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer() } // Appends a toolchange into m_plan and calculates neccessary depth of the corresponding box -void WipeTowerPrusaMM::plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, bool brim, float wipe_volume) +void WipeTower::plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, bool brim, float wipe_volume) { assert(m_plan.empty() || m_plan.back().z <= z_par + WT_EPSILON); // refuses to add a layer below the last one @@ -1158,7 +1164,7 @@ void WipeTowerPrusaMM::plan_toolchange(float z_par, float layer_height_par, unsi -void WipeTowerPrusaMM::plan_tower() +void WipeTower::plan_tower() { // Calculate m_wipe_tower_depth (maximum depth for all the layers) and propagate depths downwards m_wipe_tower_depth = 0.f; @@ -1181,7 +1187,7 @@ void WipeTowerPrusaMM::plan_tower() } } -void WipeTowerPrusaMM::save_on_last_wipe() +void WipeTower::save_on_last_wipe() { for (m_layer_info=m_plan.begin();m_layer_infoz, m_layer_info->height, 0, m_layer_info->z == m_plan.front().z, m_layer_info->z == m_plan.back().z); @@ -1206,7 +1212,7 @@ void WipeTowerPrusaMM::save_on_last_wipe() // Processes vector m_plan and calls respective functions to generate G-code for the wipe tower // Resulting ToolChangeResults are appended into vector "result" -void WipeTowerPrusaMM::generate(std::vector> &result) +void WipeTower::generate(std::vector> &result) { if (m_plan.empty()) @@ -1224,7 +1230,15 @@ void WipeTowerPrusaMM::generate(std::vectordepth < m_wipe_tower_depth - m_perimeter_width) m_y_shift = (m_wipe_tower_depth-m_layer_info->depth-m_perimeter_width)/2.f; - for (const auto &toolchange : layer.tool_changes) { - if (m_current_tool == (unsigned int)(-2)) - m_current_tool = toolchange.old_tool; + for (const auto &toolchange : layer.tool_changes) layer_result.emplace_back(tool_change(toolchange.new_tool, false)); - } if (! layer_finished()) { auto finish_layer_toolchange = finish_layer(); @@ -1252,7 +1263,7 @@ void WipeTowerPrusaMM::generate(std::vector -#include +#include #include -#include +#include +#include +#include + +#include "libslic3r/PrintConfig.hpp" + namespace Slic3r { -// A pure virtual WipeTower definition. +class WipeTowerWriter; + + + class WipeTower { public: - // Internal point class, to make the wipe tower independent from other slic3r modules. - // This is important for Prusa Research as we want to build the wipe tower post-processor independently from slic3r. - struct xy + struct Extrusion { - xy(float x = 0.f, float y = 0.f) : x(x), y(y) {} - xy(const xy& pos,float xp,float yp) : x(pos.x+xp), y(pos.y+yp) {} - xy operator+(const xy &rhs) const { xy out(*this); out.x += rhs.x; out.y += rhs.y; return out; } - xy operator-(const xy &rhs) const { xy out(*this); out.x -= rhs.x; out.y -= rhs.y; return out; } - xy& operator+=(const xy &rhs) { x += rhs.x; y += rhs.y; return *this; } - xy& operator-=(const xy &rhs) { x -= rhs.x; y -= rhs.y; return *this; } - bool operator==(const xy &rhs) const { return x == rhs.x && y == rhs.y; } - bool operator!=(const xy &rhs) const { return x != rhs.x || y != rhs.y; } - - // Rotate the point around center of the wipe tower about given angle (in degrees) - xy rotate(float width, float depth, float angle) const { - xy out(0,0); - float temp_x = x - width / 2.f; - float temp_y = y - depth / 2.f; - angle *= float(M_PI/180.); - out.x += temp_x * cos(angle) - temp_y * sin(angle) + width / 2.f; - out.y += temp_x * sin(angle) + temp_y * cos(angle) + depth / 2.f; - return out; - } - - // Rotate the point around origin about given angle in degrees - void rotate(float angle) { - float temp_x = x * cos(angle) - y * sin(angle); - y = x * sin(angle) + y * cos(angle); - x = temp_x; - } - - void translate(const xy& vect) { - x += vect.x; - y += vect.y; - } - - float x; - float y; - }; - - WipeTower() {} - virtual ~WipeTower() {} - - // Return the wipe tower position. - virtual const xy& position() const = 0; - - // Return the wipe tower width. - virtual float width() const = 0; - - // The wipe tower is finished, there should be no more tool changes or wipe tower prints. - virtual bool finished() const = 0; - - // Switch to a next layer. - virtual void set_layer( - // Print height of this layer. - float print_z, - // Layer height, used to calculate extrusion the rate. - float layer_height, - // Maximum number of tool changes on this layer or the layers below. - size_t max_tool_changes, - // Is this the first layer of the print? In that case print the brim first. - bool is_first_layer, - // Is this the last layer of the wipe tower? - bool is_last_layer) = 0; - - enum Purpose { - PURPOSE_MOVE_TO_TOWER, - PURPOSE_EXTRUDE, - PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE, - }; - - // Extrusion path of the wipe tower, for 3D preview of the generated tool paths. - struct Extrusion - { - Extrusion(const xy &pos, float width, unsigned int tool) : pos(pos), width(width), tool(tool) {} + Extrusion(const Vec2f &pos, float width, unsigned int tool) : pos(pos), width(width), tool(tool) {} // End position of this extrusion. - xy pos; + Vec2f pos; // Width of a squished extrusion, corrected for the roundings of the squished extrusions. // This is left zero if it is a travel move. float width; @@ -108,10 +43,10 @@ public: std::vector extrusions; // Initial position, at which the wipe tower starts its action. // At this position the extruder is loaded and there is no Z-hop applied. - xy start_pos; + Vec2f start_pos; // Last point, at which the normal G-code generator of Slic3r shall continue. // At this position the extruder is loaded and there is no Z-hop applied. - xy end_pos; + Vec2f end_pos; // Time elapsed over this tool change. // This is useful not only for the print time estimation, but also for the control of layer cooling. float elapsed_time; @@ -119,50 +54,375 @@ public: // Is this a priming extrusion? (If so, the wipe tower rotation & translation will not be applied later) bool priming; + // Initial tool + int initial_tool; + + // New tool + int new_tool; + // Sum the total length of the extrusion. float total_extrusion_length_in_plane() { float e_length = 0.f; for (size_t i = 1; i < this->extrusions.size(); ++ i) { const Extrusion &e = this->extrusions[i]; if (e.width > 0) { - xy v = e.pos - (&e - 1)->pos; - e_length += sqrt(v.x*v.x+v.y*v.y); + Vec2f v = e.pos - (&e - 1)->pos; + e_length += v.norm(); } } return e_length; } }; + // x -- x coordinates of wipe tower in mm ( left bottom corner ) + // y -- y coordinates of wipe tower in mm ( left bottom corner ) + // width -- width of wipe tower in mm ( default 60 mm - leave as it is ) + // wipe_area -- space available for one toolchange in mm + WipeTower(bool semm, float x, float y, float width, float rotation_angle, float cooling_tube_retraction, + float cooling_tube_length, float parking_pos_retraction, float extra_loading_move, + float bridging, bool set_extruder_trimpot, GCodeFlavor flavor, + const std::vector>& wiping_matrix, unsigned int initial_tool) : + m_semm(semm), + m_wipe_tower_pos(x, y), + m_wipe_tower_width(width), + m_wipe_tower_rotation_angle(rotation_angle), + m_y_shift(0.f), + m_z_pos(0.f), + m_is_first_layer(false), + m_gcode_flavor(flavor), + m_bridging(bridging), + m_current_tool(initial_tool), + wipe_volumes(wiping_matrix) + { + // If this is a single extruder MM printer, we will use all the SE-specific config values. + // Otherwise, the defaults will be used to turn off the SE stuff. + if (m_semm) { + m_cooling_tube_retraction = cooling_tube_retraction; + m_cooling_tube_length = cooling_tube_length; + m_parking_pos_retraction = parking_pos_retraction; + m_extra_loading_move = extra_loading_move; + m_set_extruder_trimpot = set_extruder_trimpot; + } + } + + virtual ~WipeTower() {} + + + // Set the extruder properties. + void set_extruder(size_t idx, std::string material, int temp, int first_layer_temp, float loading_speed, float loading_speed_start, + float unloading_speed, float unloading_speed_start, float delay, int cooling_moves, + float cooling_initial_speed, float cooling_final_speed, std::string ramming_parameters, float max_volumetric_speed, float nozzle_diameter) + { + //while (m_filpar.size() < idx+1) // makes sure the required element is in the vector + m_filpar.push_back(FilamentParameters()); + + m_filpar[idx].material = material; + m_filpar[idx].temperature = temp; + m_filpar[idx].first_layer_temperature = first_layer_temp; + + // If this is a single extruder MM printer, we will use all the SE-specific config values. + // Otherwise, the defaults will be used to turn off the SE stuff. + if (m_semm) { + m_filpar[idx].loading_speed = loading_speed; + m_filpar[idx].loading_speed_start = loading_speed_start; + m_filpar[idx].unloading_speed = unloading_speed; + m_filpar[idx].unloading_speed_start = unloading_speed_start; + m_filpar[idx].delay = delay; + m_filpar[idx].cooling_moves = cooling_moves; + m_filpar[idx].cooling_initial_speed = cooling_initial_speed; + m_filpar[idx].cooling_final_speed = cooling_final_speed; + } + + if (max_volumetric_speed != 0.f) + m_filpar[idx].max_e_speed = (max_volumetric_speed / Filament_Area); + m_filpar[idx].nozzle_diameter = nozzle_diameter; // to be used in future with (non-single) multiextruder MM + + m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter + + std::stringstream stream{m_semm ? ramming_parameters : std::string()}; + float speed = 0.f; + stream >> m_filpar[idx].ramming_line_width_multiplicator >> m_filpar[idx].ramming_step_multiplicator; + m_filpar[idx].ramming_line_width_multiplicator /= 100; + m_filpar[idx].ramming_step_multiplicator /= 100; + while (stream >> speed) + m_filpar[idx].ramming_speed.push_back(speed); + + m_used_filament_length.resize(std::max(m_used_filament_length.size(), idx + 1)); // makes sure that the vector is big enough so we don't have to check later + } + + + // Appends into internal structure m_plan containing info about the future wipe tower + // to be used before building begins. The entries must be added ordered in z. + void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, bool brim, float wipe_volume = 0.f); + + // Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result" + void generate(std::vector> &result); + + float get_depth() const { return m_wipe_tower_depth; } + + + + // Switch to a next layer. + void set_layer( + // Print height of this layer. + float print_z, + // Layer height, used to calculate extrusion the rate. + float layer_height, + // Maximum number of tool changes on this layer or the layers below. + size_t max_tool_changes, + // Is this the first layer of the print? In that case print the brim first. + bool is_first_layer, + // Is this the last layer of the waste tower? + bool is_last_layer) + { + m_z_pos = print_z; + m_layer_height = layer_height; + m_is_first_layer = is_first_layer; + m_print_brim = is_first_layer; + m_depth_traversed = 0.f; + m_current_shape = (! is_first_layer && m_current_shape == SHAPE_NORMAL) ? SHAPE_REVERSED : SHAPE_NORMAL; + if (is_first_layer) { + this->m_num_layer_changes = 0; + this->m_num_tool_changes = 0; + } + else + ++ m_num_layer_changes; + + // Calculate extrusion flow from desired line width, nozzle diameter, filament diameter and layer_height: + m_extrusion_flow = extrusion_flow(layer_height); + + // Advance m_layer_info iterator, making sure we got it right + while (!m_plan.empty() && m_layer_info->z < print_z - WT_EPSILON && m_layer_info+1 != m_plan.end()) + ++m_layer_info; + } + + // Return the wipe tower position. + const Vec2f& position() const { return m_wipe_tower_pos; } + // Return the wipe tower width. + float width() const { return m_wipe_tower_width; } + // The wipe tower is finished, there should be no more tool changes or wipe tower prints. + bool finished() const { return m_max_color_changes == 0; } + // Returns gcode to prime the nozzles at the front edge of the print bed. - virtual ToolChangeResult prime( + std::vector prime( // print_z of the first layer. float first_layer_height, // Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object. const std::vector &tools, // If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower. // If false, the last priming are will be large enough to wipe the last extruder sufficiently. - bool last_wipe_inside_wipe_tower) = 0; + bool last_wipe_inside_wipe_tower); - // Returns gcode for toolchange and the end position. - // if new_tool == -1, just unload the current filament over the wipe tower. - virtual ToolChangeResult tool_change(unsigned int new_tool, bool last_in_layer) = 0; + // Returns gcode for a toolchange and a final print head position. + // On the first layer, extrude a brim around the future wipe tower first. + ToolChangeResult tool_change(unsigned int new_tool, bool last_in_layer); - // Close the current wipe tower layer with a perimeter and possibly fill the unfilled space with a zig-zag. + // Fill the unfilled space with a sparse infill. // Call this method only if layer_finished() is false. - virtual ToolChangeResult finish_layer() = 0; + ToolChangeResult finish_layer(); - // Is the current layer finished? A layer is finished if either the wipe tower is finished, or - // the wipe tower has been completely covered by the tool change extrusions, - // or the rest of the tower has been filled by a sparse infill with the finish_layer() method. - virtual bool layer_finished() const = 0; + // Is the current layer finished? + bool layer_finished() const { + return ( (m_is_first_layer ? m_wipe_tower_depth - m_perimeter_width : m_layer_info->depth) - WT_EPSILON < m_depth_traversed); + } - // Returns used filament length per extruder: - virtual std::vector get_used_filament() const = 0; + std::vector get_used_filament() const { return m_used_filament_length; } + int get_number_of_toolchanges() const { return m_num_tool_changes; } - // Returns total number of toolchanges: - virtual int get_number_of_toolchanges() const = 0; + struct FilamentParameters { + std::string material = "PLA"; + int temperature = 0; + int first_layer_temperature = 0; + float loading_speed = 0.f; + float loading_speed_start = 0.f; + float unloading_speed = 0.f; + float unloading_speed_start = 0.f; + float delay = 0.f ; + int cooling_moves = 0; + float cooling_initial_speed = 0.f; + float cooling_final_speed = 0.f; + float ramming_line_width_multiplicator = 0.f; + float ramming_step_multiplicator = 0.f; + float max_e_speed = std::numeric_limits::max(); + std::vector ramming_speed; + float nozzle_diameter; + }; + +private: + WipeTower(); + + enum wipe_shape // A fill-in direction + { + SHAPE_NORMAL = 1, + SHAPE_REVERSED = -1 + }; + + + const bool m_peters_wipe_tower = false; // sparse wipe tower inspired by Peter's post processor - not finished yet + const float Filament_Area = float(M_PI * 1.75f * 1.75f / 4.f); // filament area in mm^2 + const float Width_To_Nozzle_Ratio = 1.25f; // desired line width (oval) in multiples of nozzle diameter - may not be actually neccessary to adjust + const float WT_EPSILON = 1e-3f; + + + bool m_semm = true; // Are we using a single extruder multimaterial printer? + Vec2f m_wipe_tower_pos; // Left front corner of the wipe tower in mm. + float m_wipe_tower_width; // Width of the wipe tower. + float m_wipe_tower_depth = 0.f; // Depth of the wipe tower + float m_wipe_tower_rotation_angle = 0.f; // Wipe tower rotation angle in degrees (with respect to x axis) + float m_internal_rotation = 0.f; + float m_y_shift = 0.f; // y shift passed to writer + float m_z_pos = 0.f; // Current Z position. + float m_layer_height = 0.f; // Current layer height. + size_t m_max_color_changes = 0; // Maximum number of color changes per layer. + bool m_is_first_layer = false;// Is this the 1st layer of the print? If so, print the brim around the waste tower. + int m_old_temperature = -1; // To keep track of what was the last temp that we set (so we don't issue the command when not neccessary) + + // G-code generator parameters. + float m_cooling_tube_retraction = 0.f; + float m_cooling_tube_length = 0.f; + float m_parking_pos_retraction = 0.f; + float m_extra_loading_move = 0.f; + float m_bridging = 0.f; + bool m_set_extruder_trimpot = false; + bool m_adhesion = true; + GCodeFlavor m_gcode_flavor; + + float m_perimeter_width = 0.4f * Width_To_Nozzle_Ratio; // Width of an extrusion line, also a perimeter spacing for 100% infill. + float m_extrusion_flow = 0.038f; //0.029f;// Extrusion flow is derived from m_perimeter_width, layer height and filament diameter. + + // Extruder specific parameters. + std::vector m_filpar; + + + // State of the wipe tower generator. + unsigned int m_num_layer_changes = 0; // Layer change counter for the output statistics. + unsigned int m_num_tool_changes = 0; // Tool change change counter for the output statistics. + ///unsigned int m_idx_tool_change_in_layer = 0; // Layer change counter in this layer. Counting up to m_max_color_changes. + bool m_print_brim = true; + // A fill-in direction (positive Y, negative Y) alternates with each layer. + wipe_shape m_current_shape = SHAPE_NORMAL; + unsigned int m_current_tool = 0; + const std::vector> wipe_volumes; + + float m_depth_traversed = 0.f; // Current y position at the wipe tower. + bool m_left_to_right = true; + float m_extra_spacing = 1.f; + + // Calculates extrusion flow needed to produce required line width for given layer height + float extrusion_flow(float layer_height = -1.f) const // negative layer_height - return current m_extrusion_flow + { + if ( layer_height < 0 ) + return m_extrusion_flow; + return layer_height * ( m_perimeter_width - layer_height * (1.f-float(M_PI)/4.f)) / Filament_Area; + } + + // Calculates length of extrusion line to extrude given volume + float volume_to_length(float volume, float line_width, float layer_height) const { + return std::max(0.f, volume / (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f)))); + } + + // Calculates depth for all layers and propagates them downwards + void plan_tower(); + + // Goes through m_plan and recalculates depths and width of the WT to make it exactly square - experimental + void make_wipe_tower_square(); + + // Goes through m_plan, calculates border and finish_layer extrusions and subtracts them from last wipe + void save_on_last_wipe(); + + + struct box_coordinates + { + box_coordinates(float left, float bottom, float width, float height) : + ld(left , bottom ), + lu(left , bottom + height), + rd(left + width, bottom ), + ru(left + width, bottom + height) {} + box_coordinates(const Vec2f &pos, float width, float height) : box_coordinates(pos(0), pos(1), width, height) {} + void translate(const Vec2f &shift) { + ld += shift; lu += shift; + rd += shift; ru += shift; + } + void translate(const float dx, const float dy) { translate(Vec2f(dx, dy)); } + void expand(const float offset) { + ld += Vec2f(- offset, - offset); + lu += Vec2f(- offset, offset); + rd += Vec2f( offset, - offset); + ru += Vec2f( offset, offset); + } + void expand(const float offset_x, const float offset_y) { + ld += Vec2f(- offset_x, - offset_y); + lu += Vec2f(- offset_x, offset_y); + rd += Vec2f( offset_x, - offset_y); + ru += Vec2f( offset_x, offset_y); + } + Vec2f ld; // left down + Vec2f lu; // left upper + Vec2f rd; // right lower + Vec2f ru; // right upper + }; + + + // to store information about tool changes for a given layer + struct WipeTowerInfo{ + struct ToolChange { + unsigned int old_tool; + unsigned int new_tool; + float required_depth; + float ramming_depth; + float first_wipe_line; + float wipe_volume; + ToolChange(unsigned int old, unsigned int newtool, float depth=0.f, float ramming_depth=0.f, float fwl=0.f, float wv=0.f) + : old_tool{old}, new_tool{newtool}, required_depth{depth}, ramming_depth{ramming_depth}, first_wipe_line{fwl}, wipe_volume{wv} {} + }; + float z; // z position of the layer + float height; // layer height + float depth; // depth of the layer based on all layers above + float extra_spacing; + float toolchanges_depth() const { float sum = 0.f; for (const auto &a : tool_changes) sum += a.required_depth; return sum; } + + std::vector tool_changes; + + WipeTowerInfo(float z_par, float layer_height_par) + : z{z_par}, height{layer_height_par}, depth{0}, extra_spacing{1.f} {} + }; + + std::vector m_plan; // Stores information about all layers and toolchanges for the future wipe tower (filled by plan_toolchange(...)) + std::vector::iterator m_layer_info = m_plan.end(); + + // Stores information about used filament length per extruder: + std::vector m_used_filament_length; + + + // Returns gcode for wipe tower brim + // sideOnly -- set to false -- experimental, draw brim on sides of wipe tower + // offset -- set to 0 -- experimental, offset to replace brim in front / rear of wipe tower + ToolChangeResult toolchange_Brim(bool sideOnly = false, float y_offset = 0.f); + + void toolchange_Unload( + WipeTowerWriter &writer, + const box_coordinates &cleaning_box, + const std::string& current_material, + const int new_temperature); + + void toolchange_Change( + WipeTowerWriter &writer, + const unsigned int new_tool, + const std::string& new_material); + + void toolchange_Load( + WipeTowerWriter &writer, + const box_coordinates &cleaning_box); + + void toolchange_Wipe( + WipeTowerWriter &writer, + const box_coordinates &cleaning_box, + float wipe_volume); }; + + + }; // namespace Slic3r -#endif /* slic3r_WipeTower_hpp_ */ +#endif // WipeTowerPrusaMM_hpp_ diff --git a/src/libslic3r/GCode/WipeTowerPrusaMM.hpp b/src/libslic3r/GCode/WipeTowerPrusaMM.hpp deleted file mode 100644 index f8adf4c5f..000000000 --- a/src/libslic3r/GCode/WipeTowerPrusaMM.hpp +++ /dev/null @@ -1,388 +0,0 @@ -#ifndef WipeTowerPrusaMM_hpp_ -#define WipeTowerPrusaMM_hpp_ - -#include -#include -#include -#include -#include - -#include "WipeTower.hpp" -#include "PrintConfig.hpp" - - -namespace Slic3r -{ - -namespace PrusaMultiMaterial { - class Writer; -}; - - - -class WipeTowerPrusaMM : public WipeTower -{ -public: - enum material_type - { - INVALID = -1, - PLA = 0, // E:210C B:55C - ABS = 1, // E:255C B:100C - PET = 2, // E:240C B:90C - HIPS = 3, // E:220C B:100C - FLEX = 4, // E:245C B:80C - SCAFF = 5, // E:215C B:55C - EDGE = 6, // E:240C B:80C - NGEN = 7, // E:230C B:80C - PVA = 8, // E:210C B:80C - PC = 9 - }; - - // Parse material name into material_type. - static material_type parse_material(const char *name); - static std::string to_string(material_type material); - - // x -- x coordinates of wipe tower in mm ( left bottom corner ) - // y -- y coordinates of wipe tower in mm ( left bottom corner ) - // width -- width of wipe tower in mm ( default 60 mm - leave as it is ) - // wipe_area -- space available for one toolchange in mm - WipeTowerPrusaMM(float x, float y, float width, float rotation_angle, float cooling_tube_retraction, - float cooling_tube_length, float parking_pos_retraction, float extra_loading_move, - float bridging, bool set_extruder_trimpot, GCodeFlavor flavor, - const std::vector>& wiping_matrix, unsigned int initial_tool) : - m_wipe_tower_pos(x, y), - m_wipe_tower_width(width), - m_wipe_tower_rotation_angle(rotation_angle), - m_y_shift(0.f), - m_z_pos(0.f), - m_is_first_layer(false), - m_cooling_tube_retraction(cooling_tube_retraction), - m_cooling_tube_length(cooling_tube_length), - m_parking_pos_retraction(parking_pos_retraction), - m_extra_loading_move(extra_loading_move), - m_bridging(bridging), - m_set_extruder_trimpot(set_extruder_trimpot), - m_gcode_flavor(flavor), - m_current_tool(initial_tool), - wipe_volumes(wiping_matrix) - {} - - virtual ~WipeTowerPrusaMM() {} - - - // Set the extruder properties. - void set_extruder(size_t idx, material_type material, int temp, int first_layer_temp, float loading_speed, float loading_speed_start, - float unloading_speed, float unloading_speed_start, float delay, int cooling_moves, - float cooling_initial_speed, float cooling_final_speed, std::string ramming_parameters, float nozzle_diameter) - { - //while (m_filpar.size() < idx+1) // makes sure the required element is in the vector - m_filpar.push_back(FilamentParameters()); - - m_filpar[idx].material = material; - if (material == FLEX || material == SCAFF || material == PVA) { - // MMU2 lowers the print speed using the speed override (M220) for printing of soluble PVA/BVOH and flex materials. - // Therefore it does not make sense to use the new M220 B and M220 R (backup / restore). - m_retain_speed_override = false; - } - m_filpar[idx].temperature = temp; - m_filpar[idx].first_layer_temperature = first_layer_temp; - m_filpar[idx].loading_speed = loading_speed; - m_filpar[idx].loading_speed_start = loading_speed_start; - m_filpar[idx].unloading_speed = unloading_speed; - m_filpar[idx].unloading_speed_start = unloading_speed_start; - m_filpar[idx].delay = delay; - m_filpar[idx].cooling_moves = cooling_moves; - m_filpar[idx].cooling_initial_speed = cooling_initial_speed; - m_filpar[idx].cooling_final_speed = cooling_final_speed; - m_filpar[idx].nozzle_diameter = nozzle_diameter; // to be used in future with (non-single) multiextruder MM - - m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter - - std::stringstream stream{ramming_parameters}; - float speed = 0.f; - stream >> m_filpar[idx].ramming_line_width_multiplicator >> m_filpar[idx].ramming_step_multiplicator; - m_filpar[idx].ramming_line_width_multiplicator /= 100; - m_filpar[idx].ramming_step_multiplicator /= 100; - while (stream >> speed) - m_filpar[idx].ramming_speed.push_back(speed); - - m_used_filament_length.resize(std::max(m_used_filament_length.size(), idx + 1)); // makes sure that the vector is big enough so we don't have to check later - } - - - // Appends into internal structure m_plan containing info about the future wipe tower - // to be used before building begins. The entries must be added ordered in z. - void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, bool brim, float wipe_volume = 0.f); - - // Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result" - void generate(std::vector> &result); - - float get_depth() const { return m_wipe_tower_depth; } - - - - // Switch to a next layer. - virtual void set_layer( - // Print height of this layer. - float print_z, - // Layer height, used to calculate extrusion the rate. - float layer_height, - // Maximum number of tool changes on this layer or the layers below. - size_t max_tool_changes, - // Is this the first layer of the print? In that case print the brim first. - bool is_first_layer, - // Is this the last layer of the waste tower? - bool is_last_layer) - { - m_z_pos = print_z; - m_layer_height = layer_height; - m_is_first_layer = is_first_layer; - m_print_brim = is_first_layer; - m_depth_traversed = 0.f; - m_current_shape = (! is_first_layer && m_current_shape == SHAPE_NORMAL) ? SHAPE_REVERSED : SHAPE_NORMAL; - if (is_first_layer) { - this->m_num_layer_changes = 0; - this->m_num_tool_changes = 0; - } - else - ++ m_num_layer_changes; - - // Calculate extrusion flow from desired line width, nozzle diameter, filament diameter and layer_height: - m_extrusion_flow = extrusion_flow(layer_height); - - // Advance m_layer_info iterator, making sure we got it right - while (!m_plan.empty() && m_layer_info->z < print_z - WT_EPSILON && m_layer_info+1 != m_plan.end()) - ++m_layer_info; - } - - // Return the wipe tower position. - virtual const xy& position() const { return m_wipe_tower_pos; } - // Return the wipe tower width. - virtual float width() const { return m_wipe_tower_width; } - // The wipe tower is finished, there should be no more tool changes or wipe tower prints. - virtual bool finished() const { return m_max_color_changes == 0; } - - // Returns gcode to prime the nozzles at the front edge of the print bed. - virtual ToolChangeResult prime( - // print_z of the first layer. - float first_layer_height, - // Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object. - const std::vector &tools, - // If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower. - // If false, the last priming are will be large enough to wipe the last extruder sufficiently. - bool last_wipe_inside_wipe_tower); - - // Returns gcode for a toolchange and a final print head position. - // On the first layer, extrude a brim around the future wipe tower first. - virtual ToolChangeResult tool_change(unsigned int new_tool, bool last_in_layer); - - // Fill the unfilled space with a sparse infill. - // Call this method only if layer_finished() is false. - virtual ToolChangeResult finish_layer(); - - // Is the current layer finished? - virtual bool layer_finished() const { - return ( (m_is_first_layer ? m_wipe_tower_depth - m_perimeter_width : m_layer_info->depth) - WT_EPSILON < m_depth_traversed); - } - - virtual std::vector get_used_filament() const override { return m_used_filament_length; } - virtual int get_number_of_toolchanges() const override { return m_num_tool_changes; } - - -private: - WipeTowerPrusaMM(); - - enum wipe_shape // A fill-in direction - { - SHAPE_NORMAL = 1, - SHAPE_REVERSED = -1 - }; - - - const bool m_peters_wipe_tower = false; // sparse wipe tower inspired by Peter's post processor - not finished yet - const float Filament_Area = float(M_PI * 1.75f * 1.75f / 4.f); // filament area in mm^2 - const float Width_To_Nozzle_Ratio = 1.25f; // desired line width (oval) in multiples of nozzle diameter - may not be actually neccessary to adjust - const float WT_EPSILON = 1e-3f; - - - xy m_wipe_tower_pos; // Left front corner of the wipe tower in mm. - float m_wipe_tower_width; // Width of the wipe tower. - float m_wipe_tower_depth = 0.f; // Depth of the wipe tower - float m_wipe_tower_rotation_angle = 0.f; // Wipe tower rotation angle in degrees (with respect to x axis) - float m_internal_rotation = 0.f; - float m_y_shift = 0.f; // y shift passed to writer - float m_z_pos = 0.f; // Current Z position. - float m_layer_height = 0.f; // Current layer height. - size_t m_max_color_changes = 0; // Maximum number of color changes per layer. - bool m_is_first_layer = false;// Is this the 1st layer of the print? If so, print the brim around the waste tower. - int m_old_temperature = -1; // To keep track of what was the last temp that we set (so we don't issue the command when not neccessary) - - // G-code generator parameters. - float m_cooling_tube_retraction = 0.f; - float m_cooling_tube_length = 0.f; - float m_parking_pos_retraction = 0.f; - float m_extra_loading_move = 0.f; - float m_bridging = 0.f; - bool m_set_extruder_trimpot = false; - bool m_retain_speed_override = true; - bool m_adhesion = true; - GCodeFlavor m_gcode_flavor; - - float m_perimeter_width = 0.4f * Width_To_Nozzle_Ratio; // Width of an extrusion line, also a perimeter spacing for 100% infill. - float m_extrusion_flow = 0.038f; //0.029f;// Extrusion flow is derived from m_perimeter_width, layer height and filament diameter. - - - struct FilamentParameters { - material_type material = PLA; - int temperature = 0; - int first_layer_temperature = 0; - float loading_speed = 0.f; - float loading_speed_start = 0.f; - float unloading_speed = 0.f; - float unloading_speed_start = 0.f; - float delay = 0.f ; - int cooling_moves = 0; - float cooling_initial_speed = 0.f; - float cooling_final_speed = 0.f; - float ramming_line_width_multiplicator = 0.f; - float ramming_step_multiplicator = 0.f; - std::vector ramming_speed; - float nozzle_diameter; - }; - - // Extruder specific parameters. - std::vector m_filpar; - - - // State of the wipe tower generator. - unsigned int m_num_layer_changes = 0; // Layer change counter for the output statistics. - unsigned int m_num_tool_changes = 0; // Tool change change counter for the output statistics. - ///unsigned int m_idx_tool_change_in_layer = 0; // Layer change counter in this layer. Counting up to m_max_color_changes. - bool m_print_brim = true; - // A fill-in direction (positive Y, negative Y) alternates with each layer. - wipe_shape m_current_shape = SHAPE_NORMAL; - unsigned int m_current_tool = 0; - const std::vector> wipe_volumes; - - float m_depth_traversed = 0.f; // Current y position at the wipe tower. - bool m_left_to_right = true; - float m_extra_spacing = 1.f; - - // Calculates extrusion flow needed to produce required line width for given layer height - float extrusion_flow(float layer_height = -1.f) const // negative layer_height - return current m_extrusion_flow - { - if ( layer_height < 0 ) - return m_extrusion_flow; - return layer_height * ( m_perimeter_width - layer_height * (1.f-float(M_PI)/4.f)) / Filament_Area; - } - - // Calculates length of extrusion line to extrude given volume - float volume_to_length(float volume, float line_width, float layer_height) const { - return std::max(0.f, volume / (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f)))); - } - - // Calculates depth for all layers and propagates them downwards - void plan_tower(); - - // Goes through m_plan and recalculates depths and width of the WT to make it exactly square - experimental - void make_wipe_tower_square(); - - // Goes through m_plan, calculates border and finish_layer extrusions and subtracts them from last wipe - void save_on_last_wipe(); - - - struct box_coordinates - { - box_coordinates(float left, float bottom, float width, float height) : - ld(left , bottom ), - lu(left , bottom + height), - rd(left + width, bottom ), - ru(left + width, bottom + height) {} - box_coordinates(const xy &pos, float width, float height) : box_coordinates(pos.x, pos.y, width, height) {} - void translate(const xy &shift) { - ld += shift; lu += shift; - rd += shift; ru += shift; - } - void translate(const float dx, const float dy) { translate(xy(dx, dy)); } - void expand(const float offset) { - ld += xy(- offset, - offset); - lu += xy(- offset, offset); - rd += xy( offset, - offset); - ru += xy( offset, offset); - } - void expand(const float offset_x, const float offset_y) { - ld += xy(- offset_x, - offset_y); - lu += xy(- offset_x, offset_y); - rd += xy( offset_x, - offset_y); - ru += xy( offset_x, offset_y); - } - xy ld; // left down - xy lu; // left upper - xy rd; // right lower - xy ru; // right upper - }; - - - // to store information about tool changes for a given layer - struct WipeTowerInfo{ - struct ToolChange { - unsigned int old_tool; - unsigned int new_tool; - float required_depth; - float ramming_depth; - float first_wipe_line; - float wipe_volume; - ToolChange(unsigned int old, unsigned int newtool, float depth=0.f, float ramming_depth=0.f, float fwl=0.f, float wv=0.f) - : old_tool{old}, new_tool{newtool}, required_depth{depth}, ramming_depth{ramming_depth}, first_wipe_line{fwl}, wipe_volume{wv} {} - }; - float z; // z position of the layer - float height; // layer height - float depth; // depth of the layer based on all layers above - float extra_spacing; - float toolchanges_depth() const { float sum = 0.f; for (const auto &a : tool_changes) sum += a.required_depth; return sum; } - - std::vector tool_changes; - - WipeTowerInfo(float z_par, float layer_height_par) - : z{z_par}, height{layer_height_par}, depth{0}, extra_spacing{1.f} {} - }; - - std::vector m_plan; // Stores information about all layers and toolchanges for the future wipe tower (filled by plan_toolchange(...)) - std::vector::iterator m_layer_info = m_plan.end(); - - // Stores information about used filament length per extruder: - std::vector m_used_filament_length; - - - // Returns gcode for wipe tower brim - // sideOnly -- set to false -- experimental, draw brim on sides of wipe tower - // offset -- set to 0 -- experimental, offset to replace brim in front / rear of wipe tower - ToolChangeResult toolchange_Brim(bool sideOnly = false, float y_offset = 0.f); - - void toolchange_Unload( - PrusaMultiMaterial::Writer &writer, - const box_coordinates &cleaning_box, - const material_type current_material, - const int new_temperature); - - void toolchange_Change( - PrusaMultiMaterial::Writer &writer, - const unsigned int new_tool, - material_type new_material); - - void toolchange_Load( - PrusaMultiMaterial::Writer &writer, - const box_coordinates &cleaning_box); - - void toolchange_Wipe( - PrusaMultiMaterial::Writer &writer, - const box_coordinates &cleaning_box, - float wipe_volume); -}; - - - - -}; // namespace Slic3r - -#endif /* WipeTowerPrusaMM_hpp_ */ diff --git a/src/libslic3r/GCodeTimeEstimator.cpp b/src/libslic3r/GCodeTimeEstimator.cpp index b87305da8..33dc9f4b7 100644 --- a/src/libslic3r/GCodeTimeEstimator.cpp +++ b/src/libslic3r/GCodeTimeEstimator.cpp @@ -174,7 +174,7 @@ namespace Slic3r { const std::string GCodeTimeEstimator::Silent_Last_M73_Output_Placeholder_Tag = "; SILENT_LAST_M73_OUTPUT_PLACEHOLDER"; GCodeTimeEstimator::GCodeTimeEstimator(EMode mode) - : _mode(mode) + : m_mode(mode) { reset(); set_default(); @@ -183,7 +183,7 @@ namespace Slic3r { void GCodeTimeEstimator::add_gcode_line(const std::string& gcode_line) { PROFILE_FUNC(); - _parser.parse_line(gcode_line, + m_parser.parse_line(gcode_line, [this](GCodeReader &reader, const GCodeReader::GCodeLine &line) { this->_process_gcode_line(reader, line); }); } @@ -196,7 +196,7 @@ namespace Slic3r { { this->_process_gcode_line(reader, line); }; for (; *ptr != 0;) { gline.reset(); - ptr = _parser.parse_line(ptr, gline, action); + ptr = m_parser.parse_line(ptr, gline, action); } } @@ -206,10 +206,13 @@ namespace Slic3r { if (start_from_beginning) { _reset_time(); - _last_st_synchronized_block_id = -1; + m_last_st_synchronized_block_id = -1; } _calculate_time(); + if (m_needs_color_times && (m_color_time_cache != 0.0f)) + m_color_times.push_back(m_color_time_cache); + #if ENABLE_MOVE_STATS _log_moves_stats(); #endif // ENABLE_MOVE_STATS @@ -219,12 +222,15 @@ namespace Slic3r { { reset(); - _parser.parse_buffer(gcode, + m_parser.parse_buffer(gcode, [this](GCodeReader &reader, const GCodeReader::GCodeLine &line) { this->_process_gcode_line(reader, line); }); _calculate_time(); + if (m_needs_color_times && (m_color_time_cache != 0.0f)) + m_color_times.push_back(m_color_time_cache); + #if ENABLE_MOVE_STATS _log_moves_stats(); #endif // ENABLE_MOVE_STATS @@ -234,9 +240,12 @@ namespace Slic3r { { reset(); - _parser.parse_file(file, boost::bind(&GCodeTimeEstimator::_process_gcode_line, this, _1, _2)); + m_parser.parse_file(file, boost::bind(&GCodeTimeEstimator::_process_gcode_line, this, _1, _2)); _calculate_time(); + if (m_needs_color_times && (m_color_time_cache != 0.0f)) + m_color_times.push_back(m_color_time_cache); + #if ENABLE_MOVE_STATS _log_moves_stats(); #endif // ENABLE_MOVE_STATS @@ -249,9 +258,12 @@ namespace Slic3r { auto action = [this](GCodeReader &reader, const GCodeReader::GCodeLine &line) { this->_process_gcode_line(reader, line); }; for (const std::string& line : gcode_lines) - _parser.parse_line(line, action); + m_parser.parse_line(line, action); _calculate_time(); + if (m_needs_color_times && (m_color_time_cache != 0.0f)) + m_color_times.push_back(m_color_time_cache); + #if ENABLE_MOVE_STATS _log_moves_stats(); #endif // ENABLE_MOVE_STATS @@ -270,7 +282,7 @@ namespace Slic3r { throw std::runtime_error(std::string("Remaining times export failed.\nCannot open file for writing.\n")); std::string time_mask; - switch (_mode) + switch (m_mode) { default: case Normal: @@ -291,7 +303,7 @@ namespace Slic3r { // buffer line to export only when greater than 64K to reduce writing calls std::string export_line; char time_line[64]; - G1LineIdToBlockIdMap::const_iterator it_line_id = _g1_line_ids.begin(); + G1LineIdToBlockIdMap::const_iterator it_line_id = m_g1_line_ids.begin(); while (std::getline(in, gcode_line)) { if (!in.good()) @@ -301,15 +313,15 @@ namespace Slic3r { } // replaces placeholders for initial line M73 with the real lines - if (((_mode == Normal) && (gcode_line == Normal_First_M73_Output_Placeholder_Tag)) || - ((_mode == Silent) && (gcode_line == Silent_First_M73_Output_Placeholder_Tag))) + if (((m_mode == Normal) && (gcode_line == Normal_First_M73_Output_Placeholder_Tag)) || + ((m_mode == Silent) && (gcode_line == Silent_First_M73_Output_Placeholder_Tag))) { - sprintf(time_line, time_mask.c_str(), "0", _get_time_minutes(_time).c_str()); + sprintf(time_line, time_mask.c_str(), "0", _get_time_minutes(m_time).c_str()); gcode_line = time_line; } // replaces placeholders for final line M73 with the real lines - else if (((_mode == Normal) && (gcode_line == Normal_Last_M73_Output_Placeholder_Tag)) || - ((_mode == Silent) && (gcode_line == Silent_Last_M73_Output_Placeholder_Tag))) + else if (((m_mode == Normal) && (gcode_line == Normal_Last_M73_Output_Placeholder_Tag)) || + ((m_mode == Silent) && (gcode_line == Silent_Last_M73_Output_Placeholder_Tag))) { sprintf(time_line, time_mask.c_str(), "100", "0"); gcode_line = time_line; @@ -319,27 +331,27 @@ namespace Slic3r { // add remaining time lines where needed - _parser.parse_line(gcode_line, + m_parser.parse_line(gcode_line, [this, &it_line_id, &g1_lines_count, &last_recorded_time, &time_line, &gcode_line, time_mask, interval](GCodeReader& reader, const GCodeReader::GCodeLine& line) { if (line.cmd_is("G1")) { ++g1_lines_count; - assert(it_line_id == _g1_line_ids.end() || it_line_id->first >= g1_lines_count); + assert(it_line_id == m_g1_line_ids.end() || it_line_id->first >= g1_lines_count); const Block *block = nullptr; - if (it_line_id != _g1_line_ids.end() && it_line_id->first == g1_lines_count) { - if (line.has_e() && it_line_id->second < (unsigned int)_blocks.size()) - block = &_blocks[it_line_id->second]; + if (it_line_id != m_g1_line_ids.end() && it_line_id->first == g1_lines_count) { + if (line.has_e() && it_line_id->second < (unsigned int)m_blocks.size()) + block = &m_blocks[it_line_id->second]; ++it_line_id; } if (block != nullptr && block->elapsed_time != -1.0f) { - float block_remaining_time = _time - block->elapsed_time; + float block_remaining_time = m_time - block->elapsed_time; if (std::abs(last_recorded_time - block_remaining_time) > interval) { - sprintf(time_line, time_mask.c_str(), std::to_string((int)(100.0f * block->elapsed_time / _time)).c_str(), _get_time_minutes(block_remaining_time).c_str()); + sprintf(time_line, time_mask.c_str(), std::to_string((int)(100.0f * block->elapsed_time / m_time)).c_str(), _get_time_minutes(block_remaining_time).c_str()); gcode_line += time_line; last_recorded_time = block_remaining_time; @@ -387,240 +399,240 @@ namespace Slic3r { void GCodeTimeEstimator::set_axis_position(EAxis axis, float position) { - _state.axis[axis].position = position; + m_state.axis[axis].position = position; } void GCodeTimeEstimator::set_axis_max_feedrate(EAxis axis, float feedrate_mm_sec) { - _state.axis[axis].max_feedrate = feedrate_mm_sec; + m_state.axis[axis].max_feedrate = feedrate_mm_sec; } void GCodeTimeEstimator::set_axis_max_acceleration(EAxis axis, float acceleration) { - _state.axis[axis].max_acceleration = acceleration; + m_state.axis[axis].max_acceleration = acceleration; } void GCodeTimeEstimator::set_axis_max_jerk(EAxis axis, float jerk) { - _state.axis[axis].max_jerk = jerk; + m_state.axis[axis].max_jerk = jerk; } float GCodeTimeEstimator::get_axis_position(EAxis axis) const { - return _state.axis[axis].position; + return m_state.axis[axis].position; } float GCodeTimeEstimator::get_axis_max_feedrate(EAxis axis) const { - return _state.axis[axis].max_feedrate; + return m_state.axis[axis].max_feedrate; } float GCodeTimeEstimator::get_axis_max_acceleration(EAxis axis) const { - return _state.axis[axis].max_acceleration; + return m_state.axis[axis].max_acceleration; } float GCodeTimeEstimator::get_axis_max_jerk(EAxis axis) const { - return _state.axis[axis].max_jerk; + return m_state.axis[axis].max_jerk; } void GCodeTimeEstimator::set_feedrate(float feedrate_mm_sec) { - _state.feedrate = feedrate_mm_sec; + m_state.feedrate = feedrate_mm_sec; } float GCodeTimeEstimator::get_feedrate() const { - return _state.feedrate; + return m_state.feedrate; } void GCodeTimeEstimator::set_acceleration(float acceleration_mm_sec2) { - _state.acceleration = (_state.max_acceleration == 0) ? + m_state.acceleration = (m_state.max_acceleration == 0) ? acceleration_mm_sec2 : // Clamp the acceleration with the maximum. - std::min(_state.max_acceleration, acceleration_mm_sec2); + std::min(m_state.max_acceleration, acceleration_mm_sec2); } float GCodeTimeEstimator::get_acceleration() const { - return _state.acceleration; + return m_state.acceleration; } void GCodeTimeEstimator::set_max_acceleration(float acceleration_mm_sec2) { - _state.max_acceleration = acceleration_mm_sec2; + m_state.max_acceleration = acceleration_mm_sec2; if (acceleration_mm_sec2 > 0) - _state.acceleration = acceleration_mm_sec2; + m_state.acceleration = acceleration_mm_sec2; } float GCodeTimeEstimator::get_max_acceleration() const { - return _state.max_acceleration; + return m_state.max_acceleration; } void GCodeTimeEstimator::set_retract_acceleration(float acceleration_mm_sec2) { - _state.retract_acceleration = acceleration_mm_sec2; + m_state.retract_acceleration = acceleration_mm_sec2; } float GCodeTimeEstimator::get_retract_acceleration() const { - return _state.retract_acceleration; + return m_state.retract_acceleration; } void GCodeTimeEstimator::set_minimum_feedrate(float feedrate_mm_sec) { - _state.minimum_feedrate = feedrate_mm_sec; + m_state.minimum_feedrate = feedrate_mm_sec; } float GCodeTimeEstimator::get_minimum_feedrate() const { - return _state.minimum_feedrate; + return m_state.minimum_feedrate; } void GCodeTimeEstimator::set_minimum_travel_feedrate(float feedrate_mm_sec) { - _state.minimum_travel_feedrate = feedrate_mm_sec; + m_state.minimum_travel_feedrate = feedrate_mm_sec; } float GCodeTimeEstimator::get_minimum_travel_feedrate() const { - return _state.minimum_travel_feedrate; + return m_state.minimum_travel_feedrate; } void GCodeTimeEstimator::set_filament_load_times(const std::vector &filament_load_times) { - _state.filament_load_times.clear(); + m_state.filament_load_times.clear(); for (double t : filament_load_times) - _state.filament_load_times.push_back((float)t); + m_state.filament_load_times.push_back((float)t); } void GCodeTimeEstimator::set_filament_unload_times(const std::vector &filament_unload_times) { - _state.filament_unload_times.clear(); + m_state.filament_unload_times.clear(); for (double t : filament_unload_times) - _state.filament_unload_times.push_back((float)t); + m_state.filament_unload_times.push_back((float)t); } float GCodeTimeEstimator::get_filament_load_time(unsigned int id_extruder) { return - (_state.filament_load_times.empty() || id_extruder == _state.extruder_id_unloaded) ? + (m_state.filament_load_times.empty() || id_extruder == m_state.extruder_id_unloaded) ? 0 : - (_state.filament_load_times.size() <= id_extruder) ? - _state.filament_load_times.front() : - _state.filament_load_times[id_extruder]; + (m_state.filament_load_times.size() <= id_extruder) ? + m_state.filament_load_times.front() : + m_state.filament_load_times[id_extruder]; } float GCodeTimeEstimator::get_filament_unload_time(unsigned int id_extruder) { return - (_state.filament_unload_times.empty() || id_extruder == _state.extruder_id_unloaded) ? + (m_state.filament_unload_times.empty() || id_extruder == m_state.extruder_id_unloaded) ? 0 : - (_state.filament_unload_times.size() <= id_extruder) ? - _state.filament_unload_times.front() : - _state.filament_unload_times[id_extruder]; + (m_state.filament_unload_times.size() <= id_extruder) ? + m_state.filament_unload_times.front() : + m_state.filament_unload_times[id_extruder]; } void GCodeTimeEstimator::set_extrude_factor_override_percentage(float percentage) { - _state.extrude_factor_override_percentage = percentage; + m_state.extrude_factor_override_percentage = percentage; } float GCodeTimeEstimator::get_extrude_factor_override_percentage() const { - return _state.extrude_factor_override_percentage; + return m_state.extrude_factor_override_percentage; } void GCodeTimeEstimator::set_dialect(GCodeFlavor dialect) { - _state.dialect = dialect; + m_state.dialect = dialect; } GCodeFlavor GCodeTimeEstimator::get_dialect() const { PROFILE_FUNC(); - return _state.dialect; + return m_state.dialect; } void GCodeTimeEstimator::set_units(GCodeTimeEstimator::EUnits units) { - _state.units = units; + m_state.units = units; } GCodeTimeEstimator::EUnits GCodeTimeEstimator::get_units() const { - return _state.units; + return m_state.units; } void GCodeTimeEstimator::set_global_positioning_type(GCodeTimeEstimator::EPositioningType type) { - _state.global_positioning_type = type; + m_state.global_positioning_type = type; } GCodeTimeEstimator::EPositioningType GCodeTimeEstimator::get_global_positioning_type() const { - return _state.global_positioning_type; + return m_state.global_positioning_type; } void GCodeTimeEstimator::set_e_local_positioning_type(GCodeTimeEstimator::EPositioningType type) { - _state.e_local_positioning_type = type; + m_state.e_local_positioning_type = type; } GCodeTimeEstimator::EPositioningType GCodeTimeEstimator::get_e_local_positioning_type() const { - return _state.e_local_positioning_type; + return m_state.e_local_positioning_type; } int GCodeTimeEstimator::get_g1_line_id() const { - return _state.g1_line_id; + return m_state.g1_line_id; } void GCodeTimeEstimator::increment_g1_line_id() { - ++_state.g1_line_id; + ++m_state.g1_line_id; } void GCodeTimeEstimator::reset_g1_line_id() { - _state.g1_line_id = 0; + m_state.g1_line_id = 0; } void GCodeTimeEstimator::set_extruder_id(unsigned int id) { - _state.extruder_id = id; + m_state.extruder_id = id; } unsigned int GCodeTimeEstimator::get_extruder_id() const { - return _state.extruder_id; + return m_state.extruder_id; } void GCodeTimeEstimator::reset_extruder_id() { // Set the initial extruder ID to unknown. For the multi-material setup it means // that all the filaments are parked in the MMU and no filament is loaded yet. - _state.extruder_id = _state.extruder_id_unloaded; + m_state.extruder_id = m_state.extruder_id_unloaded; } void GCodeTimeEstimator::add_additional_time(float timeSec) { PROFILE_FUNC(); - _state.additional_time += timeSec; + m_state.additional_time += timeSec; } void GCodeTimeEstimator::set_additional_time(float timeSec) { - _state.additional_time = timeSec; + m_state.additional_time = timeSec; } float GCodeTimeEstimator::get_additional_time() const { - return _state.additional_time; + return m_state.additional_time; } void GCodeTimeEstimator::set_default() @@ -648,8 +660,8 @@ namespace Slic3r { set_axis_max_jerk(axis, DEFAULT_AXIS_MAX_JERK[a]); } - _state.filament_load_times.clear(); - _state.filament_unload_times.clear(); + m_state.filament_load_times.clear(); + m_state.filament_unload_times.clear(); } void GCodeTimeEstimator::reset() @@ -664,7 +676,7 @@ namespace Slic3r { float GCodeTimeEstimator::get_time() const { - return _time; + return m_time; } std::string GCodeTimeEstimator::get_time_dhms() const @@ -677,19 +689,44 @@ namespace Slic3r { return _get_time_minutes(get_time()); } + std::vector GCodeTimeEstimator::get_color_times() const + { + return m_color_times; + } + + std::vector GCodeTimeEstimator::get_color_times_dhms() const + { + std::vector ret; + for (float t : m_color_times) + { + ret.push_back(_get_time_dhms(t)); + } + return ret; + } + + std::vector GCodeTimeEstimator::get_color_times_minutes() const + { + std::vector ret; + for (float t : m_color_times) + { + ret.push_back(_get_time_minutes(t)); + } + return ret; + } + // Return an estimate of the memory consumed by the time estimator. size_t GCodeTimeEstimator::memory_used() const { size_t out = sizeof(*this); - out += SLIC3R_STDVEC_MEMSIZE(this->_blocks, Block); - out += SLIC3R_STDVEC_MEMSIZE(this->_g1_line_ids, G1LineIdToBlockId); + out += SLIC3R_STDVEC_MEMSIZE(this->m_blocks, Block); + out += SLIC3R_STDVEC_MEMSIZE(this->m_g1_line_ids, G1LineIdToBlockId); return out; } void GCodeTimeEstimator::_reset() { - _curr.reset(); - _prev.reset(); + m_curr.reset(); + m_prev.reset(); set_axis_position(X, 0.0f); set_axis_position(Y, 0.0f); @@ -701,19 +738,23 @@ namespace Slic3r { reset_extruder_id(); reset_g1_line_id(); - _g1_line_ids.clear(); + m_g1_line_ids.clear(); - _last_st_synchronized_block_id = -1; + m_last_st_synchronized_block_id = -1; + + m_needs_color_times = false; + m_color_times.clear(); + m_color_time_cache = 0.0f; } void GCodeTimeEstimator::_reset_time() { - _time = 0.0f; + m_time = 0.0f; } void GCodeTimeEstimator::_reset_blocks() { - _blocks.clear(); + m_blocks.clear(); } void GCodeTimeEstimator::_calculate_time() @@ -723,35 +764,32 @@ namespace Slic3r { _reverse_pass(); _recalculate_trapezoids(); - _time += get_additional_time(); + m_time += get_additional_time(); + m_color_time_cache += get_additional_time(); - for (int i = _last_st_synchronized_block_id + 1; i < (int)_blocks.size(); ++i) + for (int i = m_last_st_synchronized_block_id + 1; i < (int)m_blocks.size(); ++i) { - Block& block = _blocks[i]; - -#if ENABLE_MOVE_STATS + Block& block = m_blocks[i]; float block_time = 0.0f; block_time += block.acceleration_time(); block_time += block.cruise_time(); block_time += block.deceleration_time(); - _time += block_time; - block.elapsed_time = _time; + m_time += block_time; + block.elapsed_time = m_time; +#if ENABLE_MOVE_STATS MovesStatsMap::iterator it = _moves_stats.find(block.move_type); if (it == _moves_stats.end()) it = _moves_stats.insert(MovesStatsMap::value_type(block.move_type, MoveStats())).first; it->second.count += 1; it->second.time += block_time; -#else - _time += block.acceleration_time(); - _time += block.cruise_time(); - _time += block.deceleration_time(); - block.elapsed_time = _time; #endif // ENABLE_MOVE_STATS + + m_color_time_cache += block_time; } - _last_st_synchronized_block_id = (int)_blocks.size() - 1; + m_last_st_synchronized_block_id = (int)m_blocks.size() - 1; // The additional time has been consumed (added to the total time), reset it to zero. set_additional_time(0.); } @@ -866,6 +904,11 @@ namespace Slic3r { _processM566(line); break; } + case 600: // Set color change + { + _processM600(line); + break; + } case 702: // MK3 MMU2: Process the final filament unload. { _processM702(line); @@ -934,7 +977,7 @@ namespace Slic3r { return; // calculates block feedrate - _curr.feedrate = std::max(get_feedrate(), block.is_travel_move() ? get_minimum_travel_feedrate() : get_minimum_feedrate()); + m_curr.feedrate = std::max(get_feedrate(), block.is_travel_move() ? get_minimum_travel_feedrate() : get_minimum_feedrate()); float distance = block.move_length(); float invDistance = 1.0f / distance; @@ -942,23 +985,23 @@ namespace Slic3r { float min_feedrate_factor = 1.0f; for (unsigned char a = X; a < Num_Axis; ++a) { - _curr.axis_feedrate[a] = _curr.feedrate * block.delta_pos[a] * invDistance; + m_curr.axis_feedrate[a] = m_curr.feedrate * block.delta_pos[a] * invDistance; if (a == E) - _curr.axis_feedrate[a] *= get_extrude_factor_override_percentage(); + m_curr.axis_feedrate[a] *= get_extrude_factor_override_percentage(); - _curr.abs_axis_feedrate[a] = std::abs(_curr.axis_feedrate[a]); - if (_curr.abs_axis_feedrate[a] > 0.0f) - min_feedrate_factor = std::min(min_feedrate_factor, get_axis_max_feedrate((EAxis)a) / _curr.abs_axis_feedrate[a]); + m_curr.abs_axis_feedrate[a] = std::abs(m_curr.axis_feedrate[a]); + if (m_curr.abs_axis_feedrate[a] > 0.0f) + min_feedrate_factor = std::min(min_feedrate_factor, get_axis_max_feedrate((EAxis)a) / m_curr.abs_axis_feedrate[a]); } - block.feedrate.cruise = min_feedrate_factor * _curr.feedrate; + block.feedrate.cruise = min_feedrate_factor * m_curr.feedrate; if (min_feedrate_factor < 1.0f) { for (unsigned char a = X; a < Num_Axis; ++a) { - _curr.axis_feedrate[a] *= min_feedrate_factor; - _curr.abs_axis_feedrate[a] *= min_feedrate_factor; + m_curr.axis_feedrate[a] *= min_feedrate_factor; + m_curr.abs_axis_feedrate[a] *= min_feedrate_factor; } } @@ -975,25 +1018,25 @@ namespace Slic3r { block.acceleration = acceleration; // calculates block exit feedrate - _curr.safe_feedrate = block.feedrate.cruise; + m_curr.safe_feedrate = block.feedrate.cruise; for (unsigned char a = X; a < Num_Axis; ++a) { float axis_max_jerk = get_axis_max_jerk((EAxis)a); - if (_curr.abs_axis_feedrate[a] > axis_max_jerk) - _curr.safe_feedrate = std::min(_curr.safe_feedrate, axis_max_jerk); + if (m_curr.abs_axis_feedrate[a] > axis_max_jerk) + m_curr.safe_feedrate = std::min(m_curr.safe_feedrate, axis_max_jerk); } - block.feedrate.exit = _curr.safe_feedrate; + block.feedrate.exit = m_curr.safe_feedrate; // calculates block entry feedrate - float vmax_junction = _curr.safe_feedrate; - if (!_blocks.empty() && (_prev.feedrate > PREVIOUS_FEEDRATE_THRESHOLD)) + float vmax_junction = m_curr.safe_feedrate; + if (!m_blocks.empty() && (m_prev.feedrate > PREVIOUS_FEEDRATE_THRESHOLD)) { - bool prev_speed_larger = _prev.feedrate > block.feedrate.cruise; - float smaller_speed_factor = prev_speed_larger ? (block.feedrate.cruise / _prev.feedrate) : (_prev.feedrate / block.feedrate.cruise); + bool prev_speed_larger = m_prev.feedrate > block.feedrate.cruise; + float smaller_speed_factor = prev_speed_larger ? (block.feedrate.cruise / m_prev.feedrate) : (m_prev.feedrate / block.feedrate.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.cruise : _prev.feedrate; + vmax_junction = prev_speed_larger ? block.feedrate.cruise : m_prev.feedrate; float v_factor = 1.0f; bool limited = false; @@ -1001,8 +1044,8 @@ namespace Slic3r { for (unsigned char a = X; a < Num_Axis; ++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]; + float v_exit = m_prev.axis_feedrate[a]; + float v_entry = m_curr.axis_feedrate[a]; if (prev_speed_larger) v_exit *= smaller_speed_factor; @@ -1044,23 +1087,23 @@ namespace Slic3r { 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; + if ((m_prev.safe_feedrate > vmax_junction_threshold) && (m_curr.safe_feedrate > vmax_junction_threshold)) + vmax_junction = m_curr.safe_feedrate; } - float v_allowable = Block::max_allowable_speed(-acceleration, _curr.safe_feedrate, distance); + float v_allowable = Block::max_allowable_speed(-acceleration, m_curr.safe_feedrate, distance); block.feedrate.entry = std::min(vmax_junction, v_allowable); block.max_entry_speed = vmax_junction; block.flags.nominal_length = (block.feedrate.cruise <= v_allowable); block.flags.recalculate = true; - block.safe_feedrate = _curr.safe_feedrate; + block.safe_feedrate = m_curr.safe_feedrate; // calculates block trapezoid block.calculate_trapezoid(); // updates previous - _prev = _curr; + m_prev = m_curr; // updates axis positions for (unsigned char a = X; a < Num_Axis; ++a) @@ -1091,8 +1134,8 @@ namespace Slic3r { #endif // ENABLE_MOVE_STATS // adds block to blocks list - _blocks.emplace_back(block); - _g1_line_ids.emplace_back(G1LineIdToBlockIdMap::value_type(get_g1_line_id(), (unsigned int)_blocks.size() - 1)); + m_blocks.emplace_back(block); + m_g1_line_ids.emplace_back(G1LineIdToBlockIdMap::value_type(get_g1_line_id(), (unsigned int)m_blocks.size() - 1)); } void GCodeTimeEstimator::_processG4(const GCodeReader::GCodeLine& line) @@ -1336,6 +1379,18 @@ namespace Slic3r { set_axis_max_jerk(E, line.e() * MMMIN_TO_MMSEC); } + void GCodeTimeEstimator::_processM600(const GCodeReader::GCodeLine& line) + { + PROFILE_FUNC(); + m_needs_color_times = true; + _calculate_time(); + if (m_color_time_cache != 0.0f) + { + m_color_times.push_back(m_color_time_cache); + m_color_time_cache = 0.0f; + } + } + void GCodeTimeEstimator::_processM702(const GCodeReader::GCodeLine& line) { PROFILE_FUNC(); @@ -1376,11 +1431,11 @@ namespace Slic3r { void GCodeTimeEstimator::_forward_pass() { PROFILE_FUNC(); - if (_blocks.size() > 1) + if (m_blocks.size() > 1) { - for (int i = _last_st_synchronized_block_id + 1; i < (int)_blocks.size() - 1; ++i) + for (int i = m_last_st_synchronized_block_id + 1; i < (int)m_blocks.size() - 1; ++i) { - _planner_forward_pass_kernel(_blocks[i], _blocks[i + 1]); + _planner_forward_pass_kernel(m_blocks[i], m_blocks[i + 1]); } } } @@ -1388,11 +1443,11 @@ namespace Slic3r { void GCodeTimeEstimator::_reverse_pass() { PROFILE_FUNC(); - if (_blocks.size() > 1) + if (m_blocks.size() > 1) { - for (int i = (int)_blocks.size() - 1; i >= _last_st_synchronized_block_id + 2; --i) + for (int i = (int)m_blocks.size() - 1; i >= m_last_st_synchronized_block_id + 2; --i) { - _planner_reverse_pass_kernel(_blocks[i - 1], _blocks[i]); + _planner_reverse_pass_kernel(m_blocks[i - 1], m_blocks[i]); } } } @@ -1444,9 +1499,9 @@ namespace Slic3r { Block* curr = nullptr; Block* next = nullptr; - for (int i = _last_st_synchronized_block_id + 1; i < (int)_blocks.size(); ++i) + for (int i = m_last_st_synchronized_block_id + 1; i < (int)m_blocks.size(); ++i) { - Block& b = _blocks[i]; + Block& b = m_blocks[i]; curr = next; next = &b; @@ -1517,7 +1572,7 @@ namespace Slic3r { { std::cout << MOVE_TYPE_STR[move.first]; std::cout << ": count " << move.second.count << " (" << 100.0f * (float)move.second.count / moves_count << "%)"; - std::cout << " - time: " << move.second.time << "s (" << 100.0f * move.second.time / _time << "%)"; + std::cout << " - time: " << move.second.time << "s (" << 100.0f * move.second.time / m_time << "%)"; std::cout << std::endl; } std::cout << std::endl; diff --git a/src/libslic3r/GCodeTimeEstimator.hpp b/src/libslic3r/GCodeTimeEstimator.hpp index 1fbc1c14b..840d58778 100644 --- a/src/libslic3r/GCodeTimeEstimator.hpp +++ b/src/libslic3r/GCodeTimeEstimator.hpp @@ -215,17 +215,22 @@ namespace Slic3r { typedef std::vector G1LineIdToBlockIdMap; private: - EMode _mode; - GCodeReader _parser; - State _state; - Feedrates _curr; - Feedrates _prev; - BlocksList _blocks; + EMode m_mode; + GCodeReader m_parser; + State m_state; + Feedrates m_curr; + Feedrates m_prev; + BlocksList m_blocks; // Map between g1 line id and blocks id, used to speed up export of remaining times - G1LineIdToBlockIdMap _g1_line_ids; + G1LineIdToBlockIdMap m_g1_line_ids; // Index of the last block already st_synchronized - int _last_st_synchronized_block_id; - float _time; // s + int m_last_st_synchronized_block_id; + float m_time; // s + + // data to calculate color print times + bool m_needs_color_times; + std::vector m_color_times; + float m_color_time_cache; #if ENABLE_MOVE_STATS MovesStatsMap _moves_stats; @@ -341,6 +346,15 @@ namespace Slic3r { // Returns the estimated time, in minutes (integer) std::string get_time_minutes() const; + // Returns the estimated time, in seconds, for each color + std::vector get_color_times() const; + + // Returns the estimated time, in format DDd HHh MMm SSs, for each color + std::vector get_color_times_dhms() const; + + // Returns the estimated time, in minutes (integer), for each color + std::vector get_color_times_minutes() const; + // Return an estimate of the memory consumed by the time estimator. size_t memory_used() const; @@ -409,6 +423,9 @@ namespace Slic3r { // Set allowable instantaneous speed change void _processM566(const GCodeReader::GCodeLine& line); + // Set color change + void _processM600(const GCodeReader::GCodeLine& line); + // Unload the current filament into the MK3 MMU2 unit at the end of print. void _processM702(const GCodeReader::GCodeLine& line); diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index accb4e286..cc8a86a96 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -912,7 +912,7 @@ MedialAxis::build(ThickPolylines* polylines) } */ - typedef const VD::vertex_type vert_t; + //typedef const VD::vertex_type vert_t; typedef const VD::edge_type edge_t; // collect valid edges (i.e. prune those not belonging to MAT) diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index bcbe80a0d..dca1872d8 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -7,6 +7,9 @@ #include "Polygon.hpp" #include "Polyline.hpp" +// Serialization through the Cereal library +#include + #include "boost/polygon/voronoi.hpp" using boost::polygon::voronoi_builder; using boost::polygon::voronoi_diagram; @@ -263,6 +266,17 @@ public: // as possible in least squares norm in regard to the 8 corners of bbox. // Bounding box is expected to be centered around zero in all axes. static Transformation volume_to_bed_transformation(const Transformation& instance_transformation, const BoundingBoxf3& bbox); + +private: + friend class cereal::access; + template void serialize(Archive & ar) { ar(m_offset, m_rotation, m_scaling_factor, m_mirror); } + explicit Transformation(int) : m_dirty(true) {} + template static void load_and_construct(Archive &ar, cereal::construct &construct) + { + // Calling a private constructor with special "int" parameter to indicate that no construction is necessary. + construct(1); + ar(construct.ptr()->m_offset, construct.ptr()->m_rotation, construct.ptr()->m_scaling_factor, construct.ptr()->m_mirror); + } }; // Rotation when going from the first coordinate system with rotation rot_xyz_from applied diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index c1d92c6bb..a8160867a 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -128,7 +128,7 @@ void Layer::make_perimeters() && config.external_perimeter_speed == other_config.external_perimeter_speed && config.gap_fill_speed == other_config.gap_fill_speed && config.overhangs == other_config.overhangs - && config.serialize("perimeter_extrusion_width").compare(other_config.serialize("perimeter_extrusion_width")) == 0 + && config.opt_serialize("perimeter_extrusion_width") == other_config.opt_serialize("perimeter_extrusion_width") && config.thin_walls == other_config.thin_walls && config.external_perimeters_first == other_config.external_perimeters_first) { layerms.push_back(other_layerm); diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 19fbb3bb6..caf8dc20f 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -201,7 +201,7 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer) size_t n_groups = 0; for (size_t i = 0; i < bridges.size(); ++ i) { // A grup id for this bridge. - size_t group_id = (bridge_group[i] == -1) ? (n_groups ++) : bridge_group[i]; + size_t group_id = (bridge_group[i] == size_t(-1)) ? (n_groups ++) : bridge_group[i]; bridge_group[i] = group_id; // For all possibly overlaping bridges: for (size_t j = i + 1; j < bridges.size(); ++ j) { @@ -210,7 +210,7 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer) if (intersection(bridges_grown[i], bridges_grown[j], false).empty()) continue; // The two bridge regions intersect. Give them the same group id. - if (bridge_group[j] != -1) { + if (bridge_group[j] != size_t(-1)) { // The j'th bridge has been merged with some other bridge before. size_t group_id_new = bridge_group[j]; for (size_t k = 0; k < j; ++ k) diff --git a/src/libslic3r/Line.cpp b/src/libslic3r/Line.cpp index 02f1cb7c2..baa04795a 100644 --- a/src/libslic3r/Line.cpp +++ b/src/libslic3r/Line.cpp @@ -22,7 +22,6 @@ Linef3 transform(const Linef3& line, const Transform3d& t) bool Line::intersection_infinite(const Line &other, Point* point) const { Vec2d a1 = this->a.cast(); - Vec2d a2 = other.a.cast(); Vec2d v12 = (other.a - this->a).cast(); Vec2d v1 = (this->b - this->a).cast(); Vec2d v2 = (other.b - other.a).cast(); diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index a9f2c0274..1fef4b475 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -1,11 +1,13 @@ #ifndef MTUTILS_HPP #define MTUTILS_HPP -#include // for std::atomic_flag and memory orders -#include // for std::lock_guard -#include // for std::function -#include // for std::forward +#include // for std::atomic_flag and memory orders +#include // for std::lock_guard +#include // for std::function +#include // for std::forward +#include #include +#include #include "libslic3r.h" #include "Point.hpp" @@ -242,6 +244,58 @@ template bool all_of(const C &container) }); } +template struct remove_cvref +{ + using type = + typename std::remove_cv::type>::type; +}; + +template using remove_cvref_t = typename remove_cvref::type; + +template class C, class T> +class Container : public C> +{ +public: + explicit Container(size_t count, T &&initval) + : C>(count, initval) + {} +}; + +template using DefaultContainer = std::vector; + +/// Exactly like Matlab https://www.mathworks.com/help/matlab/ref/linspace.html +template class C = DefaultContainer> +inline C> linspace(const T &start, const T &stop, const I &n) +{ + Container vals(n, T()); + + T stride = (stop - start) / n; + size_t i = 0; + std::generate(vals.begin(), vals.end(), [&i, start, stride] { + return start + i++ * stride; + }); + + return vals; +} + +/// A set of equidistant values starting from 'start' (inclusive), ending +/// in the closest multiple of 'stride' less than or equal to 'end' and +/// leaving 'stride' space between each value. +/// Very similar to Matlab [start:stride:end] notation. +template class C = DefaultContainer> +inline C> grid(const T &start, const T &stop, const T &stride) +{ + Container vals(size_t(std::ceil((stop - start) / stride)), T()); + + int i = 0; + std::generate(vals.begin(), vals.end(), [&i, start, stride] { + return start + i++ * stride; + }); + + return vals; +} + + // A shorter C++14 style form of the enable_if metafunction template using enable_if_t = typename std::enable_if::type; diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index abe8af16a..ed1c1bc00 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -23,21 +23,6 @@ namespace Slic3r { unsigned int Model::s_auto_extruder_id = 1; -size_t ModelBase::s_last_id = 0; - -// Unique object / instance ID for the wipe tower. -ModelID wipe_tower_object_id() -{ - static ModelBase mine; - return mine.id(); -} - -ModelID wipe_tower_instance_id() -{ - static ModelBase mine; - return mine.id(); -} - Model& Model::assign_copy(const Model &rhs) { this->copy_id(rhs); @@ -88,6 +73,19 @@ void Model::assign_new_unique_ids_recursive() model_object->assign_new_unique_ids_recursive(); } +void Model::update_links_bottom_up_recursive() +{ + for (std::pair &kvp : this->materials) + kvp.second->set_model(this); + for (ModelObject *model_object : this->objects) { + model_object->set_model(this); + for (ModelInstance *model_instance : model_object->instances) + model_instance->set_model_object(model_object); + for (ModelVolume *model_volume : model_object->volumes) + model_volume->set_model_object(model_object); + } +} + Model Model::read_from_file(const std::string &input_file, DynamicPrintConfig *config, bool add_default_instances) { Model model; @@ -222,7 +220,7 @@ bool Model::delete_object(ModelObject* object) return false; } -bool Model::delete_object(ModelID id) +bool Model::delete_object(ObjectID id) { if (id.id != 0) { size_t idx = 0; @@ -633,14 +631,18 @@ ModelObject::~ModelObject() // maintains the m_model pointer ModelObject& ModelObject::assign_copy(const ModelObject &rhs) { - this->copy_id(rhs); + assert(this->id().invalid() || this->id() == rhs.id()); + assert(this->config.id().invalid() || this->config.id() == rhs.config.id()); + this->copy_id(rhs); this->name = rhs.name; this->input_file = rhs.input_file; + // Copies the config's ID this->config = rhs.config; + assert(this->config.id() == rhs.config.id()); this->sla_support_points = rhs.sla_support_points; this->sla_points_status = rhs.sla_points_status; - this->layer_height_ranges = rhs.layer_height_ranges; + this->layer_config_ranges = rhs.layer_config_ranges; // #ys_FIXME_experiment this->layer_height_profile = rhs.layer_height_profile; this->origin_translation = rhs.origin_translation; m_bounding_box = rhs.m_bounding_box; @@ -669,14 +671,17 @@ ModelObject& ModelObject::assign_copy(const ModelObject &rhs) // maintains the m_model pointer ModelObject& ModelObject::assign_copy(ModelObject &&rhs) { + assert(this->id().invalid()); this->copy_id(rhs); this->name = std::move(rhs.name); this->input_file = std::move(rhs.input_file); + // Moves the config's ID this->config = std::move(rhs.config); + assert(this->config.id() == rhs.config.id()); this->sla_support_points = std::move(rhs.sla_support_points); this->sla_points_status = std::move(rhs.sla_points_status); - this->layer_height_ranges = std::move(rhs.layer_height_ranges); + this->layer_config_ranges = std::move(rhs.layer_config_ranges); // #ys_FIXME_experiment this->layer_height_profile = std::move(rhs.layer_height_profile); this->origin_translation = std::move(rhs.origin_translation); m_bounding_box = std::move(rhs.m_bounding_box); @@ -1081,11 +1086,11 @@ void ModelObject::mirror(Axis axis) } // This method could only be called before the meshes of this ModelVolumes are not shared! -void ModelObject::scale_mesh(const Vec3d &versor) +void ModelObject::scale_mesh_after_creation(const Vec3d &versor) { for (ModelVolume *v : this->volumes) { - v->scale_geometry(versor); + v->scale_geometry_after_creation(versor); v->set_offset(versor.cwiseProduct(v->get_offset())); } this->invalidate_bounding_box(); @@ -1202,13 +1207,19 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b if (keep_upper && upper_mesh.facets_count() > 0) { ModelVolume* vol = upper->add_volume(upper_mesh); vol->name = volume->name; - vol->config = volume->config; + // Don't copy the config's ID. + static_cast(vol->config) = static_cast(volume->config); + assert(vol->config.id().valid()); + assert(vol->config.id() != volume->config.id()); vol->set_material(volume->material_id(), *volume->material()); } if (keep_lower && lower_mesh.facets_count() > 0) { ModelVolume* vol = lower->add_volume(lower_mesh); vol->name = volume->name; - vol->config = volume->config; + // Don't copy the config's ID. + static_cast(vol->config) = static_cast(volume->config); + assert(vol->config.id().valid()); + assert(vol->config.id() != volume->config.id()); vol->set_material(volume->material_id(), *volume->material()); // Compute the lower part instances' bounding boxes to figure out where to place @@ -1283,7 +1294,10 @@ void ModelObject::split(ModelObjectPtrs* new_objects) // XXX: this seems to be the only real usage of m_model, maybe refactor this so that it's not needed? ModelObject* new_object = m_model->add_object(); new_object->name = this->name; - new_object->config = this->config; + // Don't copy the config's ID. + static_cast(new_object->config) = static_cast(this->config); + assert(new_object->config.id().valid()); + assert(new_object->config.id() != this->config.id()); new_object->instances.reserve(this->instances.size()); for (const ModelInstance *model_instance : this->instances) new_object->add_instance(*model_instance); @@ -1576,9 +1590,9 @@ void ModelVolume::center_geometry_after_creation() if (!shift.isApprox(Vec3d::Zero())) { if (m_mesh) - m_mesh->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); + const_cast(m_mesh.get())->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); if (m_convex_hull) - m_convex_hull->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); + const_cast(m_convex_hull.get())->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); translate(shift); } } @@ -1731,10 +1745,10 @@ void ModelVolume::mirror(Axis axis) } // This method could only be called before the meshes of this ModelVolumes are not shared! -void ModelVolume::scale_geometry(const Vec3d& versor) +void ModelVolume::scale_geometry_after_creation(const Vec3d& versor) { - m_mesh->scale(versor); - m_convex_hull->scale(versor); + const_cast(m_mesh.get())->scale(versor); + const_cast(m_convex_hull.get())->scale(versor); } void ModelVolume::transform_this_mesh(const Transform3d &mesh_trafo, bool fix_left_handed) @@ -1891,7 +1905,7 @@ bool model_volume_list_changed(const ModelObject &model_object_old, const ModelO if (!mv_old.get_matrix().isApprox(mv_new.get_matrix())) return true; - ++i_old; + ++ i_old; ++ i_new; } for (; i_old < model_object_old.volumes.size(); ++ i_old) { @@ -1913,21 +1927,26 @@ bool model_volume_list_changed(const ModelObject &model_object_old, const ModelO // Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique. void check_model_ids_validity(const Model &model) { - std::set ids; - auto check = [&ids](ModelID id) { - assert(id.id > 0); + std::set ids; + auto check = [&ids](ObjectID id) { + assert(id.valid()); assert(ids.find(id) == ids.end()); ids.insert(id); }; for (const ModelObject *model_object : model.objects) { check(model_object->id()); - for (const ModelVolume *model_volume : model_object->volumes) + check(model_object->config.id()); + for (const ModelVolume *model_volume : model_object->volumes) { check(model_volume->id()); + check(model_volume->config.id()); + } for (const ModelInstance *model_instance : model_object->instances) check(model_instance->id()); } - for (const auto mm : model.materials) + for (const auto mm : model.materials) { check(mm.second->id()); + check(mm.second->config.id()); + } } void check_model_ids_equal(const Model &model1, const Model &model2) @@ -1938,10 +1957,13 @@ void check_model_ids_equal(const Model &model1, const Model &model2) const ModelObject &model_object1 = *model1.objects[idx_model]; const ModelObject &model_object2 = * model2.objects[idx_model]; assert(model_object1.id() == model_object2.id()); + assert(model_object1.config.id() == model_object2.config.id()); assert(model_object1.volumes.size() == model_object2.volumes.size()); assert(model_object1.instances.size() == model_object2.instances.size()); - for (size_t i = 0; i < model_object1.volumes.size(); ++ i) + for (size_t i = 0; i < model_object1.volumes.size(); ++ i) { assert(model_object1.volumes[i]->id() == model_object2.volumes[i]->id()); + assert(model_object1.volumes[i]->config.id() == model_object2.volumes[i]->config.id()); + } for (size_t i = 0; i < model_object1.instances.size(); ++ i) assert(model_object1.instances[i]->id() == model_object2.instances[i]->id()); } @@ -1952,9 +1974,22 @@ void check_model_ids_equal(const Model &model1, const Model &model2) for (; it1 != model1.materials.end(); ++ it1, ++ it2) { assert(it1->first == it2->first); // compare keys assert(it1->second->id() == it2->second->id()); + assert(it1->second->config.id() == it2->second->config.id()); } } } #endif /* NDEBUG */ } + +#if 0 +CEREAL_REGISTER_TYPE(Slic3r::ModelObject) +CEREAL_REGISTER_TYPE(Slic3r::ModelVolume) +CEREAL_REGISTER_TYPE(Slic3r::ModelInstance) +CEREAL_REGISTER_TYPE(Slic3r::Model) + +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ObjectBase, Slic3r::ModelObject) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ObjectBase, Slic3r::ModelVolume) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ObjectBase, Slic3r::ModelInstance) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ObjectBase, Slic3r::Model) +#endif \ No newline at end of file diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 5a0dc9863..463d7b58e 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -2,11 +2,14 @@ #define slic3r_Model_hpp_ #include "libslic3r.h" -#include "PrintConfig.hpp" +#include "Geometry.hpp" #include "Layer.hpp" +#include "ObjectID.hpp" #include "Point.hpp" -#include "TriangleMesh.hpp" +#include "PrintConfig.hpp" #include "Slicing.hpp" +#include "SLA/SLACommon.hpp" +#include "TriangleMesh.hpp" #include "Arrange.hpp" #include @@ -14,8 +17,6 @@ #include #include #include -#include "Geometry.hpp" -#include namespace Slic3r { @@ -27,6 +28,38 @@ class ModelVolume; class Print; class SLAPrint; +namespace UndoRedo { + class StackImpl; +} + +class ModelConfig : public ObjectBase, public DynamicPrintConfig +{ +private: + friend class cereal::access; + friend class UndoRedo::StackImpl; + friend class ModelObject; + friend class ModelVolume; + friend class ModelMaterial; + + // Constructors to be only called by derived classes. + // Default constructor to assign a unique ID. + explicit ModelConfig() {} + // Constructor with ignored int parameter to assign an invalid ID, to be replaced + // by an existing ID copied from elsewhere. + explicit ModelConfig(int) : ObjectBase(-1) {} + // Copy constructor copies the ID. + explicit ModelConfig(const ModelConfig &cfg) : ObjectBase(-1), DynamicPrintConfig(cfg) { this->copy_id(cfg); } + // Move constructor copies the ID. + explicit ModelConfig(ModelConfig &&cfg) : ObjectBase(-1), DynamicPrintConfig(std::move(cfg)) { this->copy_id(cfg); } + + ModelConfig& operator=(const ModelConfig &rhs) = default; + ModelConfig& operator=(ModelConfig &&rhs) = default; + + template void serialize(Archive &ar) { + ar(cereal::base_class(this)); + } +}; + typedef std::string t_model_material_id; typedef std::string t_model_material_attribute; typedef std::map t_model_material_attributes; @@ -36,74 +69,13 @@ typedef std::vector ModelObjectPtrs; typedef std::vector ModelVolumePtrs; typedef std::vector ModelInstancePtrs; -// Unique identifier of a Model, ModelObject, ModelVolume, ModelInstance or ModelMaterial. -// Used to synchronize the front end (UI) with the back end (BackgroundSlicingProcess / Print / PrintObject) -// Valid IDs are strictly positive (non zero). -// It is declared as an object, as some compilers (notably msvcc) consider a typedef size_t equivalent to size_t -// for parameter overload. -struct ModelID -{ - ModelID(size_t id) : id(id) {} - - bool operator==(const ModelID &rhs) const { return this->id == rhs.id; } - bool operator!=(const ModelID &rhs) const { return this->id != rhs.id; } - bool operator< (const ModelID &rhs) const { return this->id < rhs.id; } - bool operator> (const ModelID &rhs) const { return this->id > rhs.id; } - bool operator<=(const ModelID &rhs) const { return this->id <= rhs.id; } - bool operator>=(const ModelID &rhs) const { return this->id >= rhs.id; } - - bool valid() const { return id != 0; } - - size_t id; -}; - -// Unique object / instance ID for the wipe tower. -extern ModelID wipe_tower_object_id(); -extern ModelID wipe_tower_instance_id(); - -// Base for Model, ModelObject, ModelVolume, ModelInstance or ModelMaterial to provide a unique ID -// to synchronize the front end (UI) with the back end (BackgroundSlicingProcess / Print / PrintObject). -// Achtung! The s_last_id counter is not thread safe, so it is expected, that the ModelBase derived instances -// are only instantiated from the main thread. -class ModelBase -{ -public: - ModelID id() const { return m_id; } - -protected: - // Constructors to be only called by derived classes. - // Default constructor to assign a unique ID. - ModelBase() : m_id(generate_new_id()) {} - // Constructor with ignored int parameter to assign an invalid ID, to be replaced - // by an existing ID copied from elsewhere. - ModelBase(int) : m_id(ModelID(0)) {} - - // Use with caution! - void set_new_unique_id() { m_id = generate_new_id(); } - void set_invalid_id() { m_id = 0; } - // Use with caution! - void copy_id(const ModelBase &rhs) { m_id = rhs.id(); } - - // Override this method if a ModelBase derived class owns other ModelBase derived instances. - void assign_new_unique_ids_recursive() { this->set_new_unique_id(); } - -private: - ModelID m_id; - - static inline ModelID generate_new_id() { return ModelID(++ s_last_id); } - static size_t s_last_id; - - friend ModelID wipe_tower_object_id(); - friend ModelID wipe_tower_instance_id(); -}; - -#define MODELBASE_DERIVED_COPY_MOVE_CLONE(TYPE) \ +#define OBJECTBASE_DERIVED_COPY_MOVE_CLONE(TYPE) \ /* Copy a model, copy the IDs. The Print::apply() will call the TYPE::copy() method */ \ /* to make a private copy for background processing. */ \ - static TYPE* new_copy(const TYPE &rhs) { return new TYPE(rhs); } \ - static TYPE* new_copy(TYPE &&rhs) { return new TYPE(std::move(rhs)); } \ - static TYPE make_copy(const TYPE &rhs) { return TYPE(rhs); } \ - static TYPE make_copy(TYPE &&rhs) { return TYPE(std::move(rhs)); } \ + static TYPE* new_copy(const TYPE &rhs) { auto *ret = new TYPE(rhs); assert(ret->id() == rhs.id()); return ret; } \ + static TYPE* new_copy(TYPE &&rhs) { auto *ret = new TYPE(std::move(rhs)); assert(ret->id() == rhs.id()); return ret; } \ + static TYPE make_copy(const TYPE &rhs) { TYPE ret(rhs); assert(ret.id() == rhs.id()); return ret; } \ + static TYPE make_copy(TYPE &&rhs) { TYPE ret(std::move(rhs)); assert(ret.id() == rhs.id()); return ret; } \ TYPE& assign_copy(const TYPE &rhs); \ TYPE& assign_copy(TYPE &&rhs); \ /* Copy a TYPE, generate new IDs. The front end will use this call. */ \ @@ -111,52 +83,62 @@ private: /* Default constructor assigning an invalid ID. */ \ auto obj = new TYPE(-1); \ obj->assign_clone(rhs); \ + assert(obj->id().valid() && obj->id() != rhs.id()); \ return obj; \ } \ TYPE make_clone(const TYPE &rhs) { \ /* Default constructor assigning an invalid ID. */ \ TYPE obj(-1); \ obj.assign_clone(rhs); \ + assert(obj.id().valid() && obj.id() != rhs.id()); \ return obj; \ } \ TYPE& assign_clone(const TYPE &rhs) { \ this->assign_copy(rhs); \ + assert(this->id().valid() && this->id() == rhs.id()); \ this->assign_new_unique_ids_recursive(); \ + assert(this->id().valid() && this->id() != rhs.id()); \ return *this; \ } -#define MODELBASE_DERIVED_PRIVATE_COPY_MOVE(TYPE) \ -private: \ - /* Private constructor with an unused int parameter will create a TYPE instance with an invalid ID. */ \ - explicit TYPE(int) : ModelBase(-1) {}; \ - void assign_new_unique_ids_recursive(); - // Material, which may be shared across multiple ModelObjects of a single Model. -class ModelMaterial : public ModelBase +class ModelMaterial final : public ObjectBase { public: // Attributes are defined by the AMF file format, but they don't seem to be used by Slic3r for any purpose. t_model_material_attributes attributes; // Dynamic configuration storage for the object specific configuration values, overriding the global configuration. - DynamicPrintConfig config; + ModelConfig config; Model* get_model() const { return m_model; } void apply(const t_model_material_attributes &attributes) { this->attributes.insert(attributes.begin(), attributes.end()); } -protected: - friend class Model; - // Constructor, which assigns a new unique ID. - ModelMaterial(Model *model) : m_model(model) {} - // Copy constructor copies the ID and m_model! - ModelMaterial(const ModelMaterial &rhs) = default; - void set_model(Model *model) { m_model = model; } - private: // Parent, owning this material. Model *m_model; - - ModelMaterial() = delete; + + // To be accessed by the Model. + friend class Model; + // Constructor, which assigns a new unique ID to the material and to its config. + ModelMaterial(Model *model) : m_model(model) { assert(this->id().valid()); } + // Copy constructor copies the IDs of the ModelMaterial and its config, and m_model! + ModelMaterial(const ModelMaterial &rhs) = default; + void set_model(Model *model) { m_model = model; } + void set_new_unique_id() { ObjectBase::set_new_unique_id(); this->config.set_new_unique_id(); } + + // To be accessed by the serialization and Undo/Redo code. + friend class cereal::access; + friend class UndoRedo::StackImpl; + // Create an object for deserialization, don't allocate IDs for ModelMaterial and its config. + ModelMaterial() : ObjectBase(-1), config(-1), m_model(nullptr) { assert(this->id().invalid()); assert(this->config.id().invalid()); } + template void serialize(Archive &ar) { + assert(this->id().invalid()); assert(this->config.id().invalid()); + ar(attributes, config); + // assert(this->id().valid()); assert(this->config.id().valid()); + } + + // Disabled methods. ModelMaterial(ModelMaterial &&rhs) = delete; ModelMaterial& operator=(const ModelMaterial &rhs) = delete; ModelMaterial& operator=(ModelMaterial &&rhs) = delete; @@ -166,9 +148,8 @@ private: // and possibly having multiple modifier volumes, each modifier volume with its set of parameters and materials. // Each ModelObject may be instantiated mutliple times, each instance having different placement on the print bed, // different rotation and different uniform scaling. -class ModelObject : public ModelBase +class ModelObject final : public ObjectBase { - friend class Model; public: std::string name; std::string input_file; // XXX: consider fs::path @@ -179,9 +160,9 @@ public: // ModelVolumes are owned by this ModelObject. ModelVolumePtrs volumes; // Configuration parameters specific to a single ModelObject, overriding the global Slic3r settings. - DynamicPrintConfig config; - // Variation of a layer thickness for spans of Z coordinates. - t_layer_height_ranges layer_height_ranges; + ModelConfig config; + // Variation of a layer thickness for spans of Z coordinates + optional parameter overrides. + t_layer_config_ranges layer_config_ranges; // Profile of increasing z to a layer height, to be linearly interpolated when calculating the layers. // The pairs of are packed into a 1D array. std::vector layer_height_profile; @@ -265,7 +246,7 @@ public: void mirror(Axis axis); // This method could only be called before the meshes of this ModelVolumes are not shared! - void scale_mesh(const Vec3d& versor); + void scale_mesh_after_creation(const Vec3d& versor); size_t materials_count() const; size_t facets_count() const; @@ -289,31 +270,53 @@ public: std::string get_export_filename() const; - // Get full stl statistics for all object's meshes + // Get full stl statistics for all object's meshes stl_stats get_object_stl_stats() const; - // Get count of errors in the mesh( or all object's meshes, if volume index isn't defined) + // Get count of errors in the mesh( or all object's meshes, if volume index isn't defined) int get_mesh_errors_count(const int vol_idx = -1) const; -protected: - friend class Print; - friend class SLAPrint; - // Called by Print::apply() to set the model pointer after making a copy. - void set_model(Model *model) { m_model = model; } - private: - ModelObject(Model *model) : m_model(model), origin_translation(Vec3d::Zero()), - m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) {} - ~ModelObject(); + friend class Model; + // This constructor assigns new ID to this ModelObject and its config. + explicit ModelObject(Model *model) : m_model(model), origin_translation(Vec3d::Zero()), + m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) + { assert(this->id().valid()); } + explicit ModelObject(int) : ObjectBase(-1), config(-1), m_model(nullptr), origin_translation(Vec3d::Zero()), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) + { assert(this->id().invalid()); assert(this->config.id().invalid()); } + ~ModelObject(); + void assign_new_unique_ids_recursive() override; - /* To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" */ - /* (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics). */ - ModelObject(const ModelObject &rhs) : ModelBase(-1), m_model(rhs.m_model) { this->assign_copy(rhs); } - explicit ModelObject(ModelObject &&rhs) : ModelBase(-1) { this->assign_copy(std::move(rhs)); } - ModelObject& operator=(const ModelObject &rhs) { this->assign_copy(rhs); m_model = rhs.m_model; return *this; } - ModelObject& operator=(ModelObject &&rhs) { this->assign_copy(std::move(rhs)); m_model = rhs.m_model; return *this; } + // To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" + // (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics). + ModelObject(const ModelObject &rhs) : ObjectBase(-1), config(-1), m_model(rhs.m_model) { + assert(this->id().invalid()); assert(this->config.id().invalid()); assert(rhs.id() != rhs.config.id()); + this->assign_copy(rhs); + assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); + assert(this->id() == rhs.id()); assert(this->config.id() == rhs.config.id()); + } + explicit ModelObject(ModelObject &&rhs) : ObjectBase(-1), config(-1) { + assert(this->id().invalid()); assert(this->config.id().invalid()); assert(rhs.id() != rhs.config.id()); + this->assign_copy(std::move(rhs)); + assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); + assert(this->id() == rhs.id()); assert(this->config.id() == rhs.config.id()); + } + ModelObject& operator=(const ModelObject &rhs) { + this->assign_copy(rhs); + m_model = rhs.m_model; + assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); + assert(this->id() == rhs.id()); assert(this->config.id() == rhs.config.id()); + return *this; + } + ModelObject& operator=(ModelObject &&rhs) { + this->assign_copy(std::move(rhs)); + m_model = rhs.m_model; + assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); + assert(this->id() == rhs.id()); assert(this->config.id() == rhs.config.id()); + return *this; + } + void set_new_unique_id() { ObjectBase::set_new_unique_id(); this->config.set_new_unique_id(); } - MODELBASE_DERIVED_COPY_MOVE_CLONE(ModelObject) - MODELBASE_DERIVED_PRIVATE_COPY_MOVE(ModelObject) + OBJECTBASE_DERIVED_COPY_MOVE_CLONE(ModelObject) // Parent object, owning this ModelObject. Set to nullptr here, so the macros above will have it initialized. Model *m_model = nullptr; @@ -324,7 +327,25 @@ private: mutable BoundingBoxf3 m_raw_bounding_box; mutable bool m_raw_bounding_box_valid; mutable BoundingBoxf3 m_raw_mesh_bounding_box; - mutable bool m_raw_mesh_bounding_box_valid; + mutable bool m_raw_mesh_bounding_box_valid; + + // Called by Print::apply() to set the model pointer after making a copy. + friend class Print; + friend class SLAPrint; + void set_model(Model *model) { m_model = model; } + + // Undo / Redo through the cereal serialization library + friend class cereal::access; + friend class UndoRedo::StackImpl; + // Used for deserialization -> Don't allocate any IDs for the ModelObject or its config. + ModelObject() : ObjectBase(-1), config(-1), m_model(nullptr), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) { + assert(this->id().invalid()); assert(this->config.id().invalid()); + } + template void serialize(Archive &ar) { + ar(cereal::base_class(this)); + ar(name, input_file, instances, volumes, config, layer_config_ranges, layer_height_profile, sla_support_points, sla_points_status, origin_translation, + m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid); + } }; // Declared outside of ModelVolume, so it could be forward declared. @@ -338,20 +359,20 @@ enum class ModelVolumeType : int { // An object STL, or a modifier volume, over which a different set of parameters shall be applied. // ModelVolume instances are owned by a ModelObject. -class ModelVolume : public ModelBase +class ModelVolume final : public ObjectBase { public: std::string name; // The triangular model. const TriangleMesh& mesh() const { return *m_mesh.get(); } - void set_mesh(const TriangleMesh &mesh) { m_mesh = std::make_shared(mesh); } - void set_mesh(TriangleMesh &&mesh) { m_mesh = std::make_shared(std::move(mesh)); } - void set_mesh(std::shared_ptr &mesh) { m_mesh = mesh; } - void set_mesh(std::unique_ptr &&mesh) { m_mesh = std::move(mesh); } - void reset_mesh() { m_mesh = std::make_shared(); } + void set_mesh(const TriangleMesh &mesh) { m_mesh = std::make_shared(mesh); } + void set_mesh(TriangleMesh &&mesh) { m_mesh = std::make_shared(std::move(mesh)); } + void set_mesh(std::shared_ptr &mesh) { m_mesh = mesh; } + void set_mesh(std::unique_ptr &&mesh) { m_mesh = std::move(mesh); } + void reset_mesh() { m_mesh = std::make_shared(); } // Configuration parameters specific to an object model geometry or a modifier volume, // overriding the global Slic3r settings and the ModelObject settings. - DynamicPrintConfig config; + ModelConfig config; // A parent object owning this modifier volume. ModelObject* get_object() const { return this->object; }; @@ -386,7 +407,7 @@ public: void mirror(Axis axis); // This method could only be called before the meshes of this ModelVolumes are not shared! - void scale_geometry(const Vec3d& versor); + void scale_geometry_after_creation(const Vec3d& versor); // Translates the mesh and the convex hull so that the origin of their vertices is in the center of this volume's bounding box. // Attention! This method may only be called just after ModelVolume creation! It must not be called once the TriangleMesh of this ModelVolume is shared! @@ -432,66 +453,88 @@ public: const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); } - using ModelBase::set_new_unique_id; + void set_new_unique_id() { ObjectBase::set_new_unique_id(); this->config.set_new_unique_id(); } protected: friend class Print; friend class SLAPrint; + friend class Model; friend class ModelObject; + // Copies IDs of both the ModelVolume and its config. explicit ModelVolume(const ModelVolume &rhs) = default; void set_model_object(ModelObject *model_object) { object = model_object; } + void assign_new_unique_ids_recursive() override { ObjectBase::set_new_unique_id(); config.set_new_unique_id(); } void transform_this_mesh(const Transform3d& t, bool fix_left_handed); void transform_this_mesh(const Matrix3d& m, bool fix_left_handed); private: // Parent object owning this ModelVolume. - ModelObject* object; + ModelObject* object; // The triangular model. - std::shared_ptr m_mesh; + std::shared_ptr m_mesh; // Is it an object to be printed, or a modifier volume? - ModelVolumeType m_type; - t_model_material_id m_material_id; + ModelVolumeType m_type; + t_model_material_id m_material_id; // The convex hull of this model's mesh. - std::shared_ptr m_convex_hull; - Geometry::Transformation m_transformation; + std::shared_ptr m_convex_hull; + Geometry::Transformation m_transformation; // flag to optimize the checking if the volume is splittable // -1 -> is unknown value (before first cheking) // 0 -> is not splittable // 1 -> is splittable - mutable int m_is_splittable{ -1 }; + mutable int m_is_splittable{ -1 }; ModelVolume(ModelObject *object, const TriangleMesh &mesh) : m_mesh(new TriangleMesh(mesh)), m_type(ModelVolumeType::MODEL_PART), object(object) { + assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); if (mesh.stl.stats.number_of_facets > 1) calculate_convex_hull(); } ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) : - m_mesh(new TriangleMesh(std::move(mesh))), m_convex_hull(new TriangleMesh(std::move(convex_hull))), m_type(ModelVolumeType::MODEL_PART), object(object) {} + m_mesh(new TriangleMesh(std::move(mesh))), m_convex_hull(new TriangleMesh(std::move(convex_hull))), m_type(ModelVolumeType::MODEL_PART), object(object) { + assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); + } // Copying an existing volume, therefore this volume will get a copy of the ID assigned. ModelVolume(ModelObject *object, const ModelVolume &other) : - ModelBase(other), // copy the ID + ObjectBase(other), name(other.name), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation) { + assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); + assert(this->id() == other.id() && this->config.id() == other.config.id()); this->set_material_id(other.material_id()); } // Providing a new mesh, therefore this volume will get a new unique ID assigned. ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) : name(other.name), m_mesh(new TriangleMesh(std::move(mesh))), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation) { + assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); + assert(this->id() != other.id() && this->config.id() == other.config.id()); this->set_material_id(other.material_id()); + this->config.set_new_unique_id(); if (mesh.stl.stats.number_of_facets > 1) calculate_convex_hull(); + assert(this->config.id().valid()); assert(this->config.id() != other.config.id()); assert(this->id() != this->config.id()); } ModelVolume& operator=(ModelVolume &rhs) = delete; + + friend class cereal::access; + friend class UndoRedo::StackImpl; + // Used for deserialization, therefore no IDs are allocated. + ModelVolume() : ObjectBase(-1), config(-1), object(nullptr) { + assert(this->id().invalid()); assert(this->config.id().invalid()); + } + template void serialize(Archive &ar) { + ar(name, config, m_mesh, m_type, m_material_id, m_convex_hull, m_transformation, m_is_splittable); + } }; // A single instance of a ModelObject. // Knows the affine transformation of an object. -class ModelInstance : public ModelBase +class ModelInstance final : public ObjectBase { public: enum EPrintVolumeState : unsigned char @@ -569,6 +612,7 @@ public: protected: friend class Print; friend class SLAPrint; + friend class Model; friend class ModelObject; explicit ModelInstance(const ModelInstance &rhs) = default; @@ -579,27 +623,28 @@ private: ModelObject* object; // Constructor, which assigns a new unique ID. - explicit ModelInstance(ModelObject *object) : object(object), print_volume_state(PVS_Inside) - { - get_arrange_polygon(); // initialize the arrange cache - } + explicit ModelInstance(ModelObject *object) : object(object), print_volume_state(PVS_Inside) { assert(this->id().valid()); } // Constructor, which assigns a new unique ID. explicit ModelInstance(ModelObject *object, const ModelInstance &other) : - m_transformation(other.m_transformation), object(object), print_volume_state(PVS_Inside) - { - get_arrange_polygon(); // initialize the arrange cache - } + m_transformation(other.m_transformation), object(object), print_volume_state(PVS_Inside) { assert(this->id().valid() && this->id() != other.id()); } - ModelInstance() = delete; explicit ModelInstance(ModelInstance &&rhs) = delete; ModelInstance& operator=(const ModelInstance &rhs) = delete; ModelInstance& operator=(ModelInstance &&rhs) = delete; + + friend class cereal::access; + friend class UndoRedo::StackImpl; + // Used for deserialization, therefore no IDs are allocated. + ModelInstance() : ObjectBase(-1), object(nullptr) { assert(this->id().invalid()); } + template void serialize(Archive &ar) { + ar(m_transformation, print_volume_state); + } // Warning! This object is not guarded against concurrency. - mutable struct ArrangeCache { - bool valid = false; - ExPolygon poly; - } m_arrange_cache; + // mutable struct ArrangeCache { + // bool valid = false; + // ExPolygon poly; + // } m_arrange_cache; }; // The print bed content. @@ -607,7 +652,7 @@ private: // and with multiple modifier meshes. // A model groups multiple objects, each object having possibly multiple instances, // all objects may share mutliple materials. -class Model : public ModelBase +class Model final : public ObjectBase { static unsigned int s_auto_extruder_id; @@ -619,17 +664,17 @@ public: ModelObjectPtrs objects; // Default constructor assigns a new ID to the model. - Model() {} + Model() { assert(this->id().valid()); } ~Model() { this->clear_objects(); this->clear_materials(); } /* To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" */ /* (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics). */ - Model(const Model &rhs) : ModelBase(-1) { this->assign_copy(rhs); } - explicit Model(Model &&rhs) : ModelBase(-1) { this->assign_copy(std::move(rhs)); } - Model& operator=(const Model &rhs) { this->assign_copy(rhs); return *this; } - Model& operator=(Model &&rhs) { this->assign_copy(std::move(rhs)); return *this; } + Model(const Model &rhs) : ObjectBase(-1) { assert(this->id().invalid()); this->assign_copy(rhs); assert(this->id().valid()); assert(this->id() == rhs.id()); } + explicit Model(Model &&rhs) : ObjectBase(-1) { assert(this->id().invalid()); this->assign_copy(std::move(rhs)); assert(this->id().valid()); assert(this->id() == rhs.id()); } + Model& operator=(const Model &rhs) { this->assign_copy(rhs); assert(this->id().valid()); assert(this->id() == rhs.id()); return *this; } + Model& operator=(Model &&rhs) { this->assign_copy(std::move(rhs)); assert(this->id().valid()); assert(this->id() == rhs.id()); return *this; } - MODELBASE_DERIVED_COPY_MOVE_CLONE(Model) + OBJECTBASE_DERIVED_COPY_MOVE_CLONE(Model) static Model read_from_file(const std::string &input_file, DynamicPrintConfig *config = nullptr, bool add_default_instances = true); static Model read_from_archive(const std::string &input_file, DynamicPrintConfig *config, bool add_default_instances = true); @@ -640,7 +685,7 @@ public: ModelObject* add_object(const char *name, const char *path, TriangleMesh &&mesh); ModelObject* add_object(const ModelObject &other); void delete_object(size_t idx); - bool delete_object(ModelID id); + bool delete_object(ObjectID id); bool delete_object(ModelObject* object); void clear_objects(); @@ -658,24 +703,24 @@ public: BoundingBoxf3 bounding_box() const; // Set the print_volume_state of PrintObject::instances, // return total number of printable objects. - unsigned int update_print_volume_state(const BoundingBoxf3 &print_volume); + unsigned int update_print_volume_state(const BoundingBoxf3 &print_volume); // Returns true if any ModelObject was modified. - bool center_instances_around_point(const Vec2d &point); - void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); } - TriangleMesh mesh() const; - bool arrange_objects(coordf_t dist, const BoundingBoxf* bb = NULL); + bool center_instances_around_point(const Vec2d &point); + void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); } + TriangleMesh mesh() const; + bool arrange_objects(coordf_t dist, const BoundingBoxf* bb = NULL); // Croaks if the duplicated objects do not fit the print bed. - void duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL); - void duplicate_objects(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL); - void duplicate_objects_grid(size_t x, size_t y, coordf_t dist); + void duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL); + void duplicate_objects(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL); + void duplicate_objects_grid(size_t x, size_t y, coordf_t dist); - bool looks_like_multipart_object() const; - void convert_multipart_object(unsigned int max_extruders); + bool looks_like_multipart_object() const; + void convert_multipart_object(unsigned int max_extruders); // Ensures that the min z of the model is not negative - void adjust_min_z(); + void adjust_min_z(); - void print_info() const { for (const ModelObject *o : this->objects) o->print_info(); } + void print_info() const { for (const ModelObject *o : this->objects) o->print_info(); } static unsigned int get_auto_extruder_id(unsigned int max_extruders); static std::string get_auto_extruder_id_as_string(unsigned int max_extruders); @@ -687,11 +732,19 @@ public: std::string propose_export_file_name_and_path(const std::string &new_extension) const; private: - MODELBASE_DERIVED_PRIVATE_COPY_MOVE(Model) + explicit Model(int) : ObjectBase(-1) { assert(this->id().invalid()); }; + void assign_new_unique_ids_recursive(); + void update_links_bottom_up_recursive(); + + friend class cereal::access; + friend class UndoRedo::StackImpl; + template void serialize(Archive &ar) { + ar(materials, objects); + } }; -#undef MODELBASE_DERIVED_COPY_MOVE_CLONE -#undef MODELBASE_DERIVED_PRIVATE_COPY_MOVE +#undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE +#undef OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE // Test whether the two models contain the same number of ModelObjects with the same set of IDs // ordered in the same order. In that case it is not necessary to kill the background processing. @@ -711,6 +764,6 @@ void check_model_ids_validity(const Model &model); void check_model_ids_equal(const Model &model1, const Model &model2); #endif /* NDEBUG */ -} +} // namespace Slic3r -#endif +#endif /* slic3r_Model_hpp_ */ diff --git a/src/libslic3r/MotionPlanner.cpp b/src/libslic3r/MotionPlanner.cpp index 198c39f31..dd3443879 100644 --- a/src/libslic3r/MotionPlanner.cpp +++ b/src/libslic3r/MotionPlanner.cpp @@ -332,7 +332,7 @@ Polyline MotionPlannerGraph::shortest_path(size_t node_start, size_t node_end) c queue.pop(); map_node_to_queue_id[u] = size_t(-1); // Stop searching if we reached our destination. - if (u == node_end) + if (size_t(u) == node_end) break; // Visit each edge starting at node u. for (const Neighbor& neighbor : m_adjacency_list[u]) diff --git a/src/libslic3r/ObjectID.cpp b/src/libslic3r/ObjectID.cpp new file mode 100644 index 000000000..b188d84c0 --- /dev/null +++ b/src/libslic3r/ObjectID.cpp @@ -0,0 +1,22 @@ +#include "ObjectID.hpp" + +namespace Slic3r { + +size_t ObjectBase::s_last_id = 0; + +// Unique object / instance ID for the wipe tower. +ObjectID wipe_tower_object_id() +{ + static ObjectBase mine; + return mine.id(); +} + +ObjectID wipe_tower_instance_id() +{ + static ObjectBase mine; + return mine.id(); +} + +} // namespace Slic3r + +// CEREAL_REGISTER_TYPE(Slic3r::ObjectBase) diff --git a/src/libslic3r/ObjectID.hpp b/src/libslic3r/ObjectID.hpp new file mode 100644 index 000000000..484d1173b --- /dev/null +++ b/src/libslic3r/ObjectID.hpp @@ -0,0 +1,93 @@ +#ifndef slic3r_ObjectID_hpp_ +#define slic3r_ObjectID_hpp_ + +#include + +namespace Slic3r { + +namespace UndoRedo { + class StackImpl; +}; + +// Unique identifier of a mutable object accross the application. +// Used to synchronize the front end (UI) with the back end (BackgroundSlicingProcess / Print / PrintObject) +// (for Model, ModelObject, ModelVolume, ModelInstance or ModelMaterial classes) +// and to serialize / deserialize an object onto the Undo / Redo stack. +// Valid IDs are strictly positive (non zero). +// It is declared as an object, as some compilers (notably msvcc) consider a typedef size_t equivalent to size_t +// for parameter overload. +class ObjectID +{ +public: + ObjectID(size_t id) : id(id) {} + // Default constructor constructs an invalid ObjectID. + ObjectID() : id(0) {} + + bool operator==(const ObjectID &rhs) const { return this->id == rhs.id; } + bool operator!=(const ObjectID &rhs) const { return this->id != rhs.id; } + bool operator< (const ObjectID &rhs) const { return this->id < rhs.id; } + bool operator> (const ObjectID &rhs) const { return this->id > rhs.id; } + bool operator<=(const ObjectID &rhs) const { return this->id <= rhs.id; } + bool operator>=(const ObjectID &rhs) const { return this->id >= rhs.id; } + + bool valid() const { return id != 0; } + bool invalid() const { return id == 0; } + + size_t id; + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(id); } +}; + +// Base for Model, ModelObject, ModelVolume, ModelInstance or ModelMaterial to provide a unique ID +// to synchronize the front end (UI) with the back end (BackgroundSlicingProcess / Print / PrintObject). +// Achtung! The s_last_id counter is not thread safe, so it is expected, that the ObjectBase derived instances +// are only instantiated from the main thread. +class ObjectBase +{ +public: + ObjectID id() const { return m_id; } + +protected: + // Constructors to be only called by derived classes. + // Default constructor to assign a unique ID. + ObjectBase() : m_id(generate_new_id()) {} + // Constructor with ignored int parameter to assign an invalid ID, to be replaced + // by an existing ID copied from elsewhere. + ObjectBase(int) : m_id(ObjectID(0)) {} + // The class tree will have virtual tables and type information. + virtual ~ObjectBase() {} + + // Use with caution! + void set_new_unique_id() { m_id = generate_new_id(); } + void set_invalid_id() { m_id = 0; } + // Use with caution! + void copy_id(const ObjectBase &rhs) { m_id = rhs.id(); } + + // Override this method if a ObjectBase derived class owns other ObjectBase derived instances. + virtual void assign_new_unique_ids_recursive() { this->set_new_unique_id(); } + +private: + ObjectID m_id; + + static inline ObjectID generate_new_id() { return ObjectID(++ s_last_id); } + static size_t s_last_id; + + friend ObjectID wipe_tower_object_id(); + friend ObjectID wipe_tower_instance_id(); + + friend class cereal::access; + friend class Slic3r::UndoRedo::StackImpl; + template void serialize(Archive &ar) { ar(m_id); } + ObjectBase(const ObjectID id) : m_id(id) {} + template static void load_and_construct(Archive & ar, cereal::construct &construct) { ObjectID id; ar(id); construct(id); } +}; + +// Unique object / instance ID for the wipe tower. +extern ObjectID wipe_tower_object_id(); +extern ObjectID wipe_tower_instance_id(); + +} // namespace Slic3r + +#endif /* slic3r_ObjectID_hpp_ */ diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index de8aeeb2a..4dac192ad 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -175,7 +175,7 @@ void PerimeterGenerator::process() const PerimeterGeneratorLoop &loop = contours_d[i]; // find the contour loop that contains it for (int t = d - 1; t >= 0; -- t) { - for (int j = 0; j < contours[t].size(); ++ j) { + for (size_t j = 0; j < contours[t].size(); ++ j) { PerimeterGeneratorLoop &candidate_parent = contours[t][j]; if (candidate_parent.polygon.contains(loop.polygon.first_point())) { candidate_parent.children.push_back(loop); @@ -397,7 +397,7 @@ static inline ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyli pp.push_back(line.b); width.push_back(line.b_width); - assert(pp.size() == segments + 1); + assert(pp.size() == segments + 1u); assert(width.size() == segments*2); } diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index ae66178aa..2bfe9b745 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -521,7 +521,6 @@ namespace client static void regex_op(expr &lhs, boost::iterator_range &rhs, char op) { const std::string *subject = nullptr; - const std::string *mask = nullptr; if (lhs.type == TYPE_STRING) { // One type is string, the other could be converted to string. subject = &lhs.s(); @@ -563,7 +562,6 @@ namespace client static void ternary_op(expr &lhs, expr &rhs1, expr &rhs2) { - bool value = false; if (lhs.type != TYPE_BOOL) lhs.throw_exception("Not a boolean expression"); if (lhs.b()) @@ -975,7 +973,7 @@ namespace client // depending on the context->just_boolean_expression flag. This way a single static expression parser // could serve both purposes. start = eps[px::bind(&MyContext::evaluate_full_macro, _r1, _a)] > - ( eps(_a==true) > text_block(_r1) [_val=_1] + ( (eps(_a==true) > text_block(_r1) [_val=_1]) | conditional_expression(_r1) [ px::bind(&expr::evaluate_boolean_to_string, _1, _val) ] ) > eoi; start.name("start"); @@ -1245,7 +1243,7 @@ static std::string process_macro(const std::string &templ, client::MyContext &co std::string::const_iterator end = templ.end(); // Accumulator for the processed template. std::string output; - bool res = phrase_parse(iter, end, macro_processor_instance(&context), space, output); + phrase_parse(iter, end, macro_processor_instance(&context), space, output); if (!context.error_message.empty()) { if (context.error_message.back() != '\n' && context.error_message.back() != '\r') context.error_message += '\n'; diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index b02ead299..994f45e59 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -62,8 +62,8 @@ inline Vec2i64 to_2d(const Vec3i64 &pt3) { return Vec2i64(pt3(0), pt3(1)); } inline Vec2f to_2d(const Vec3f &pt3) { return Vec2f (pt3(0), pt3(1)); } inline Vec2d to_2d(const Vec3d &pt3) { return Vec2d (pt3(0), pt3(1)); } -inline Vec3d to_3d(const Vec2d &v, double z) { return Vec3d(v(0), v(1), z); } -inline Vec3f to_3d(const Vec2f &v, float z) { return Vec3f(v(0), v(1), z); } +inline Vec3d to_3d(const Vec2d &v, double z) { return Vec3d(v(0), v(1), z); } +inline Vec3f to_3d(const Vec2f &v, float z) { return Vec3f(v(0), v(1), z); } inline Vec3i64 to_3d(const Vec2i64 &v, float z) { return Vec3i64(int64_t(v(0)), int64_t(v(1)), int64_t(z)); } inline Vec3crd to_3d(const Vec3crd &p, coord_t z) { return Vec3crd(p(0), p(1), z); } @@ -291,4 +291,21 @@ namespace boost { namespace polygon { } } // 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); } +} + #endif diff --git a/src/libslic3r/PolylineCollection.cpp b/src/libslic3r/PolylineCollection.cpp index 1304161c3..0c66c371a 100644 --- a/src/libslic3r/PolylineCollection.cpp +++ b/src/libslic3r/PolylineCollection.cpp @@ -61,7 +61,7 @@ Polylines PolylineCollection::_chained_path_from( while (! endpoints.empty()) { // find nearest point int endpoint_index = nearest_point_index(endpoints, start_near, no_reverse); - assert(endpoint_index >= 0 && endpoint_index < endpoints.size() * 2); + assert(endpoint_index >= 0 && size_t(endpoint_index) < endpoints.size() * 2); if (move_from_src) { retval.push_back(std::move(src[endpoints[endpoint_index/2].idx])); } else { diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index f9129f15a..a9b8a827f 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -7,18 +7,20 @@ #include "I18N.hpp" #include "SupportMaterial.hpp" #include "GCode.hpp" -#include "GCode/WipeTowerPrusaMM.hpp" +#include "GCode/WipeTower.hpp" #include "Utils.hpp" //#include "PrintExport.hpp" +#include + #include #include #include #include #include -//! macro used to mark string used at localization, +//! macro used to mark string used at localization, //! return same string #define L(s) Slic3r::I18N::translate(s) @@ -41,36 +43,6 @@ void Print::clear() m_model.clear_objects(); } -// Only used by the Perl test cases. -void Print::reload_object(size_t /* idx */) -{ - ModelObjectPtrs model_objects; - { - tbb::mutex::scoped_lock lock(this->state_mutex()); - // The following call should stop background processing if it is running. - this->invalidate_all_steps(); - /* TODO: this method should check whether the per-object config and per-material configs - have changed in such a way that regions need to be rearranged or we can just apply - the diff and invalidate something. Same logic as apply() - For now we just re-add all objects since we haven't implemented this incremental logic yet. - This should also check whether object volumes (parts) have changed. */ - // collect all current model objects - model_objects.reserve(m_objects.size()); - for (PrintObject *object : m_objects) - model_objects.push_back(object->model_object()); - // remove our print objects - for (PrintObject *object : m_objects) - delete object; - m_objects.clear(); - for (PrintRegion *region : m_regions) - delete region; - m_regions.clear(); - } - // re-add model objects - for (ModelObject *mo : model_objects) - this->add_model_object(mo); -} - PrintRegion* Print::add_region() { m_regions.emplace_back(new PrintRegion(this)); @@ -121,7 +93,6 @@ bool Print::invalidate_state_by_config_options(const std::vectorcopies().size(); + instances += (unsigned int)print_object->copies().size(); return instances; } @@ -358,198 +330,6 @@ double Print::max_allowed_layer_height() const return nozzle_diameter_max; } -// Caller is responsible for supplying models whose objects don't collide -// and have explicit instance positions. -void Print::add_model_object(ModelObject* model_object, int idx) -{ - tbb::mutex::scoped_lock lock(this->state_mutex()); - // Add a copy of this ModelObject to this Print. - m_model.objects.emplace_back(ModelObject::new_copy(*model_object)); - m_model.objects.back()->set_model(&m_model); - // Initialize a new print object and store it at the given position. - PrintObject *object = new PrintObject(this, model_object, true); - if (idx != -1) { - delete m_objects[idx]; - m_objects[idx] = object; - } else - m_objects.emplace_back(object); - // Invalidate all print steps. - this->invalidate_all_steps(); - - // Set the transformation matrix without translation from the first instance. - if (! model_object->instances.empty()) { - // Trafo and bounding box, both in world coordinate system. - Transform3d trafo = model_object->instances.front()->get_matrix(); - BoundingBoxf3 bbox = model_object->instance_bounding_box(0); - // Now shift the object up to align it with the print bed. - trafo.data()[14] -= bbox.min(2); - // and reset the XY translation. - trafo.data()[12] = 0; - trafo.data()[13] = 0; - object->set_trafo(trafo); - } - - size_t volume_id = 0; - for (const ModelVolume *volume : model_object->volumes) { - if (! volume->is_model_part() && ! volume->is_modifier()) - continue; - // Get the config applied to this volume. - PrintRegionConfig config = PrintObject::region_config_from_model_volume(m_default_region_config, *volume, 99999); - // Find an existing print region with the same config. - size_t region_id = size_t(-1); - for (size_t i = 0; i < m_regions.size(); ++ i) - if (config.equals(m_regions[i]->config())) { - region_id = i; - break; - } - // If no region exists with the same config, create a new one. - if (region_id == size_t(-1)) { - region_id = m_regions.size(); - this->add_region(config); - } - // Assign volume to a region. - object->add_region_volume(region_id, volume_id); - ++ volume_id; - } - - // Apply config to print object. - object->config_apply(this->default_object_config()); - { - //normalize_and_apply_config(object->config(), model_object->config); - DynamicPrintConfig src_normalized(model_object->config); - src_normalized.normalize(); - object->config_apply(src_normalized, true); - } -} - -// This function is only called through the Perl-C++ binding from the unit tests, should be -// removed when unit tests are rewritten to C++. -bool Print::apply_config_perl_tests_only(DynamicPrintConfig config) -{ - tbb::mutex::scoped_lock lock(this->state_mutex()); - - - // Perl unit tests were failing in case the preset was not normalized (e.g. https://github.com/prusa3d/PrusaSlicer/issues/2288 was caused - // by too short max_layer_height vector. Calling the necessary function Preset::normalize(...) is not currently possible because there is no - // access to preset. This should be solved when the unit tests are rewritten to C++. For now we just copy-pasted code from Preset.cpp - // to make sure the unit tests pass (functions set_num_extruders and nozzle_options()). - auto *nozzle_diameter = dynamic_cast(config.option("nozzle_diameter", true)); - assert(nozzle_diameter != nullptr); - const auto &defaults = FullPrintConfig::defaults(); - for (const std::string &key : { "nozzle_diameter", "min_layer_height", "max_layer_height", "extruder_offset", - "retract_length", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_speed", "deretract_speed", - "retract_before_wipe", "retract_restart_extra", "retract_before_travel", "wipe", - "retract_layer_change", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour" }) - { - auto *opt = config.option(key, true); - assert(opt != nullptr); - assert(opt->is_vector()); - unsigned int num_extruders = (unsigned int)nozzle_diameter->values.size(); - static_cast(opt)->resize(num_extruders, defaults.option(key)); - } - - // we get a copy of the config object so we can modify it safely - config.normalize(); - - // apply variables to placeholder parser - this->placeholder_parser().apply_config(config); - - // handle changes to print config - t_config_option_keys print_diff = m_config.diff(config); - m_config.apply_only(config, print_diff, true); - bool invalidated = this->invalidate_state_by_config_options(print_diff); - - // handle changes to object config defaults - m_default_object_config.apply(config, true); - for (PrintObject *object : m_objects) { - // we don't assume that config contains a full ObjectConfig, - // so we base it on the current print-wise default - PrintObjectConfig new_config = this->default_object_config(); - // we override the new config with object-specific options - normalize_and_apply_config(new_config, object->model_object()->config); - // check whether the new config is different from the current one - t_config_option_keys diff = object->config().diff(new_config); - object->config_apply_only(new_config, diff, true); - invalidated |= object->invalidate_state_by_config_options(diff); - } - - // handle changes to regions config defaults - m_default_region_config.apply(config, true); - - // All regions now have distinct settings. - // Check whether applying the new region config defaults we'd get different regions. - bool rearrange_regions = false; - { - // Collect the already visited region configs into other_region_configs, - // so one may check for duplicates. - std::vector other_region_configs; - for (size_t region_id = 0; region_id < m_regions.size(); ++ region_id) { - PrintRegion ®ion = *m_regions[region_id]; - PrintRegionConfig this_region_config; - bool this_region_config_set = false; - for (PrintObject *object : m_objects) { - if (region_id < object->region_volumes.size()) { - for (int volume_id : object->region_volumes[region_id]) { - const ModelVolume &volume = *object->model_object()->volumes[volume_id]; - if (this_region_config_set) { - // If the new config for this volume differs from the other - // volume configs currently associated to this region, it means - // the region subdivision does not make sense anymore. - if (! this_region_config.equals(PrintObject::region_config_from_model_volume(m_default_region_config, volume, 99999))) { - rearrange_regions = true; - goto exit_for_rearrange_regions; - } - } else { - this_region_config = PrintObject::region_config_from_model_volume(m_default_region_config, volume, 99999); - this_region_config_set = true; - } - for (const PrintRegionConfig &cfg : other_region_configs) { - // If the new config for this volume equals any of the other - // volume configs that are not currently associated to this - // region, it means the region subdivision does not make - // sense anymore. - if (cfg.equals(this_region_config)) { - rearrange_regions = true; - goto exit_for_rearrange_regions; - } - } - } - } - } - if (this_region_config_set) { - t_config_option_keys diff = region.config().diff(this_region_config); - if (! diff.empty()) { - region.config_apply_only(this_region_config, diff, false); - for (PrintObject *object : m_objects) - if (region_id < object->region_volumes.size() && ! object->region_volumes[region_id].empty()) - invalidated |= object->invalidate_state_by_config_options(diff); - } - other_region_configs.emplace_back(std::move(this_region_config)); - } - } - } - -exit_for_rearrange_regions: - - if (rearrange_regions) { - // The current subdivision of regions does not make sense anymore. - // We need to remove all objects and re-add them. - ModelObjectPtrs model_objects; - model_objects.reserve(m_objects.size()); - for (PrintObject *object : m_objects) - model_objects.push_back(object->model_object()); - this->clear(); - for (ModelObject *mo : model_objects) - this->add_model_object(mo); - invalidated = true; - } - - for (PrintObject *object : m_objects) - object->update_slicing_parameters(); - - return invalidated; -} - // Add or remove support modifier ModelVolumes from model_object_dst to match the ModelVolumes of model_object_new // in the exact order and with the same IDs. // It is expected, that the model_object_dst already contains the non-support volumes of model_object_new in the correct order. @@ -612,7 +392,7 @@ static inline void model_volume_list_copy_configs(ModelObject &model_object_dst, assert(mv_src.id() == mv_dst.id()); // Copy the ModelVolume data. mv_dst.name = mv_src.name; - mv_dst.config = mv_src.config; + static_cast(mv_dst.config) = static_cast(mv_src.config); //FIXME what to do with the materials? // mv_dst.m_material_id = mv_src.m_material_id; ++ i_src; @@ -620,6 +400,20 @@ static inline void model_volume_list_copy_configs(ModelObject &model_object_dst, } } +static inline void layer_height_ranges_copy_configs(t_layer_config_ranges &lr_dst, const t_layer_config_ranges &lr_src) +{ + assert(lr_dst.size() == lr_src.size()); + auto it_src = lr_src.cbegin(); + for (auto &kvp_dst : lr_dst) { + const auto &kvp_src = *it_src ++; + assert(std::abs(kvp_dst.first.first - kvp_src.first.first ) <= EPSILON); + assert(std::abs(kvp_dst.first.second - kvp_src.first.second) <= EPSILON); + // Layer heights are allowed do differ in case the layer height table is being overriden by the smooth profile. + // assert(std::abs(kvp_dst.second.option("layer_height")->getFloat() - kvp_src.second.option("layer_height")->getFloat()) <= EPSILON); + kvp_dst.second = kvp_src.second; + } +} + static inline bool transform3d_lower(const Transform3d &lhs, const Transform3d &rhs) { typedef Transform3d::Scalar T; @@ -674,6 +468,23 @@ static std::vector print_objects_from_model_object(const ModelOb return std::vector(trafos.begin(), trafos.end()); } +// Compare just the layer ranges and their layer heights, not the associated configs. +// Ignore the layer heights if check_layer_heights is false. +bool layer_height_ranges_equal(const t_layer_config_ranges &lr1, const t_layer_config_ranges &lr2, bool check_layer_height) +{ + if (lr1.size() != lr2.size()) + return false; + auto it2 = lr2.begin(); + for (const auto &kvp1 : lr1) { + const auto &kvp2 = *it2 ++; + if (std::abs(kvp1.first.first - kvp2.first.first ) > EPSILON || + std::abs(kvp1.first.second - kvp2.first.second) > EPSILON || + (check_layer_height && std::abs(kvp1.second.option("layer_height")->getFloat() - kvp2.second.option("layer_height")->getFloat()) > EPSILON)) + return false; + } + return true; +} + Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &config_in) { #ifdef _DEBUG @@ -724,6 +535,50 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co // Handle changes to regions config defaults m_default_region_config.apply_only(config, region_diff, true); + class LayerRanges + { + public: + LayerRanges() {} + // Convert input config ranges into continuous non-overlapping sorted vector of intervals and their configs. + void assign(const t_layer_config_ranges &in) { + m_ranges.clear(); + m_ranges.reserve(in.size()); + // Input ranges are sorted lexicographically. First range trims the other ranges. + coordf_t last_z = 0; + for (const std::pair &range : in) { +// for (auto &range : in) { + if (range.first.second > last_z) { + coordf_t min_z = std::max(range.first.first, 0.); + if (min_z > last_z + EPSILON) { + m_ranges.emplace_back(t_layer_height_range(last_z, min_z), nullptr); + last_z = min_z; + } + if (range.first.second > last_z + EPSILON) { + const DynamicPrintConfig* cfg = &range.second; + m_ranges.emplace_back(t_layer_height_range(last_z, range.first.second), cfg); + last_z = range.first.second; + } + } + } + if (m_ranges.empty()) + m_ranges.emplace_back(t_layer_height_range(0, DBL_MAX), nullptr); + else if (m_ranges.back().second == nullptr) + m_ranges.back().first.second = DBL_MAX; + else + m_ranges.emplace_back(t_layer_height_range(m_ranges.back().first.second, DBL_MAX), nullptr); + } + const DynamicPrintConfig* config(const t_layer_height_range &range) const { + auto it = std::lower_bound(m_ranges.begin(), m_ranges.end(), std::make_pair< t_layer_height_range, const DynamicPrintConfig*>(t_layer_height_range(range.first - EPSILON, range.second - EPSILON), nullptr)); + assert(it != m_ranges.end()); + assert(it == m_ranges.end() || std::abs(it->first.first - range.first ) < EPSILON); + assert(it == m_ranges.end() || std::abs(it->first.second - range.second) < EPSILON); + return (it == m_ranges.end()) ? nullptr : it->second; + } + std::vector>::const_iterator begin() const { return m_ranges.cbegin(); } + std::vector>::const_iterator end() const { return m_ranges.cend(); } + private: + std::vector> m_ranges; + }; struct ModelObjectStatus { enum Status { Unknown, @@ -732,9 +587,10 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co Moved, Deleted, }; - ModelObjectStatus(ModelID id, Status status = Unknown) : id(id), status(status) {} - ModelID id; - Status status; + ModelObjectStatus(ObjectID id, Status status = Unknown) : id(id), status(status) {} + ObjectID id; + Status status; + LayerRanges layer_ranges; // Search by id. bool operator<(const ModelObjectStatus &rhs) const { return id < rhs.id; } }; @@ -839,9 +695,9 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co print_object(print_object), trafo(print_object->trafo()), status(status) {} - PrintObjectStatus(ModelID id) : id(id), print_object(nullptr), trafo(Transform3d::Identity()), status(Unknown) {} + PrintObjectStatus(ObjectID id) : id(id), print_object(nullptr), trafo(Transform3d::Identity()), status(Unknown) {} // ID of the ModelObject & PrintObject - ModelID id; + ObjectID id; // Pointer to the old PrintObject PrintObject *print_object; // Trafo generated with model_object->world_matrix(true) @@ -861,21 +717,23 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co auto it_status = model_object_status.find(ModelObjectStatus(model_object.id())); assert(it_status != model_object_status.end()); assert(it_status->status != ModelObjectStatus::Deleted); + const ModelObject& model_object_new = *model.objects[idx_model_object]; + const_cast(*it_status).layer_ranges.assign(model_object_new.layer_config_ranges); if (it_status->status == ModelObjectStatus::New) // PrintObject instances will be added in the next loop. continue; // Update the ModelObject instance, possibly invalidate the linked PrintObjects. assert(it_status->status == ModelObjectStatus::Old || it_status->status == ModelObjectStatus::Moved); - const ModelObject &model_object_new = *model.objects[idx_model_object]; // Check whether a model part volume was added or removed, their transformations or order changed. + // Only volume IDs, volume types and their order are checked, configuration and other parameters are NOT checked. bool model_parts_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::MODEL_PART); bool modifiers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::PARAMETER_MODIFIER); bool support_blockers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_BLOCKER); bool support_enforcers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_ENFORCER); if (model_parts_differ || modifiers_differ || model_object.origin_translation != model_object_new.origin_translation || - model_object.layer_height_ranges != model_object_new.layer_height_ranges || - model_object.layer_height_profile != model_object_new.layer_height_profile) { + model_object.layer_height_profile != model_object_new.layer_height_profile || + ! layer_height_ranges_equal(model_object.layer_config_ranges, model_object_new.layer_config_ranges, model_object_new.layer_height_profile.empty())) { // The very first step (the slicing step) is invalidated. One may freely remove all associated PrintObjects. auto range = print_object_status.equal_range(PrintObjectStatus(model_object.id())); for (auto it = range.first; it != range.second; ++ it) { @@ -899,7 +757,7 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co // Synchronize Object's config. bool object_config_changed = model_object.config != model_object_new.config; if (object_config_changed) - model_object.config = model_object_new.config; + static_cast(model_object.config) = static_cast(model_object_new.config); if (! object_diff.empty() || object_config_changed) { PrintObjectConfig new_config = PrintObject::object_config_from_model_object(m_default_object_config, model_object, num_extruders); auto range = print_object_status.equal_range(PrintObjectStatus(model_object.id())); @@ -915,7 +773,8 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co //FIXME What to do with m_material_id? model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::MODEL_PART); model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::PARAMETER_MODIFIER); - // Copy the ModelObject name, input_file and instances. The instances will compared against PrintObject instances in the next step. + layer_height_ranges_copy_configs(model_object.layer_config_ranges /* dst */, model_object_new.layer_config_ranges /* src */); + // Copy the ModelObject name, input_file and instances. The instances will be compared against PrintObject instances in the next step. model_object.name = model_object_new.name; model_object.input_file = model_object_new.input_file; model_object.clear_instances(); @@ -1027,19 +886,27 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co PrintRegionConfig this_region_config; bool this_region_config_set = false; for (PrintObject *print_object : m_objects) { + const LayerRanges *layer_ranges; + { + auto it_status = model_object_status.find(ModelObjectStatus(print_object->model_object()->id())); + assert(it_status != model_object_status.end()); + assert(it_status->status != ModelObjectStatus::Deleted); + layer_ranges = &it_status->layer_ranges; + } if (region_id < print_object->region_volumes.size()) { - for (int volume_id : print_object->region_volumes[region_id]) { - const ModelVolume &volume = *print_object->model_object()->volumes[volume_id]; + for (const std::pair &volume_and_range : print_object->region_volumes[region_id]) { + const ModelVolume &volume = *print_object->model_object()->volumes[volume_and_range.second]; + const DynamicPrintConfig *layer_range_config = layer_ranges->config(volume_and_range.first); if (this_region_config_set) { // If the new config for this volume differs from the other // volume configs currently associated to this region, it means // the region subdivision does not make sense anymore. - if (! this_region_config.equals(PrintObject::region_config_from_model_volume(m_default_region_config, volume, num_extruders))) + if (! this_region_config.equals(PrintObject::region_config_from_model_volume(m_default_region_config, layer_range_config, volume, num_extruders))) // Regions were split. Reset this print_object. goto print_object_end; } else { - this_region_config = PrintObject::region_config_from_model_volume(m_default_region_config, volume, num_extruders); - for (size_t i = 0; i < region_id; ++i) { + this_region_config = PrintObject::region_config_from_model_volume(m_default_region_config, layer_range_config, volume, num_extruders); + for (size_t i = 0; i < region_id; ++ i) { const PrintRegion ®ion_other = *m_regions[i]; if (region_other.m_refcnt != 0 && region_other.config().equals(this_region_config)) // Regions were merged. Reset this print_object. @@ -1054,7 +921,7 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co update_apply_status(print_object->invalidate_all_steps()); // Decrease the references to regions from this volume. int ireg = 0; - for (const std::vector &volumes : print_object->region_volumes) { + for (const std::vector> &volumes : print_object->region_volumes) { if (! volumes.empty()) -- m_regions[ireg]->m_refcnt; ++ ireg; @@ -1076,52 +943,65 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co for (size_t idx_print_object = 0; idx_print_object < m_objects.size(); ++ idx_print_object) { PrintObject &print_object0 = *m_objects[idx_print_object]; const ModelObject &model_object = *print_object0.model_object(); - std::vector map_volume_to_region(model_object.volumes.size(), -1); + const LayerRanges *layer_ranges; + { + auto it_status = model_object_status.find(ModelObjectStatus(model_object.id())); + assert(it_status != model_object_status.end()); + assert(it_status->status != ModelObjectStatus::Deleted); + layer_ranges = &it_status->layer_ranges; + } + std::vector regions_in_object; + regions_in_object.reserve(64); for (size_t i = idx_print_object; i < m_objects.size() && m_objects[i]->model_object() == &model_object; ++ i) { PrintObject &print_object = *m_objects[i]; bool fresh = print_object.region_volumes.empty(); unsigned int volume_id = 0; + unsigned int idx_region_in_object = 0; for (const ModelVolume *volume : model_object.volumes) { if (! volume->is_model_part() && ! volume->is_modifier()) { ++ volume_id; continue; } - int region_id = -1; - if (&print_object == &print_object0) { - // Get the config applied to this volume. - PrintRegionConfig config = PrintObject::region_config_from_model_volume(m_default_region_config, *volume, num_extruders); - // Find an existing print region with the same config. - int idx_empty_slot = -1; - for (int i = 0; i < (int)m_regions.size(); ++ i) { - if (m_regions[i]->m_refcnt == 0) { - if (idx_empty_slot == -1) - idx_empty_slot = i; - } else if (config.equals(m_regions[i]->config())) { - region_id = i; - break; + // Filter the layer ranges, so they do not overlap and they contain at least a single layer. + // Now insert a volume with a layer range to its own region. + for (auto it_range = layer_ranges->begin(); it_range != layer_ranges->end(); ++ it_range) { + int region_id = -1; + if (&print_object == &print_object0) { + // Get the config applied to this volume. + PrintRegionConfig config = PrintObject::region_config_from_model_volume(m_default_region_config, it_range->second, *volume, num_extruders); + // Find an existing print region with the same config. + int idx_empty_slot = -1; + for (int i = 0; i < (int)m_regions.size(); ++ i) { + if (m_regions[i]->m_refcnt == 0) { + if (idx_empty_slot == -1) + idx_empty_slot = i; + } else if (config.equals(m_regions[i]->config())) { + region_id = i; + break; + } + } + // If no region exists with the same config, create a new one. + if (region_id == -1) { + if (idx_empty_slot == -1) { + region_id = (int)m_regions.size(); + this->add_region(config); + } else { + region_id = idx_empty_slot; + m_regions[region_id]->set_config(std::move(config)); + } } - } - // If no region exists with the same config, create a new one. - if (region_id == -1) { - if (idx_empty_slot == -1) { - region_id = (int)m_regions.size(); - this->add_region(config); - } else { - region_id = idx_empty_slot; - m_regions[region_id]->set_config(std::move(config)); - } - } - map_volume_to_region[volume_id] = region_id; - } else - region_id = map_volume_to_region[volume_id]; - // Assign volume to a region. - if (fresh) { - if (region_id >= print_object.region_volumes.size() || print_object.region_volumes[region_id].empty()) - ++ m_regions[region_id]->m_refcnt; - print_object.add_region_volume(region_id, volume_id); - } - ++ volume_id; - } + regions_in_object.emplace_back(region_id); + } else + region_id = regions_in_object[idx_region_in_object ++]; + // Assign volume to a region. + if (fresh) { + if ((size_t)region_id >= print_object.region_volumes.size() || print_object.region_volumes[region_id].empty()) + ++ m_regions[region_id]->m_refcnt; + print_object.add_region_volume(region_id, volume_id, it_range->first); + } + } + ++ volume_id; + } } } @@ -1175,9 +1055,9 @@ std::string Print::validate() const Polygon convex_hull0 = offset( print_object->model_object()->convex_hull_2d( Geometry::assemble_transform(Vec3d::Zero(), rotation, model_instance0->get_scaling_factor(), model_instance0->get_mirror())), - scale_(m_config.extruder_clearance_radius.value) / 2., jtRound, scale_(0.1)).front(); + float(scale_(0.5 * m_config.extruder_clearance_radius.value)), jtRound, float(scale_(0.1))).front(); // Now we check that no instance of convex_hull intersects any of the previously checked object instances. - for (const Point © : print_object->m_copies) { + for (const Point © : print_object->copies()) { Polygon convex_hull = convex_hull0; convex_hull.translate(copy); if (! intersection(convex_hulls_other, convex_hull).empty()) @@ -1211,23 +1091,21 @@ std::string Print::validate() const return L("The Spiral Vase option can only be used when printing single material objects."); } - if (m_config.single_extruder_multi_material) { - for (size_t i=1; ihas_wipe_tower() && ! m_objects.empty()) { if (m_config.gcode_flavor != gcfRepRap && m_config.gcode_flavor != gcfRepetier && m_config.gcode_flavor != gcfMarlin) return L("The Wipe Tower is currently only supported for the Marlin, RepRap/Sprinter and Repetier G-code flavors."); if (! m_config.use_relative_e_distances) return L("The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1)."); + + for (size_t i=1; i 1) { bool has_custom_layering = false; std::vector> layer_height_profiles; for (const PrintObject *object : m_objects) { - has_custom_layering = ! object->model_object()->layer_height_ranges.empty() || ! object->model_object()->layer_height_profile.empty(); + has_custom_layering = ! object->model_object()->layer_config_ranges.empty() || ! object->model_object()->layer_height_profile.empty(); // #ys_FIXME_experiment if (has_custom_layering) { layer_height_profiles.assign(m_objects.size(), std::vector()); break; @@ -1259,11 +1137,10 @@ std::string Print::validate() const if (has_custom_layering) { const std::vector &layer_height_profile_tallest = layer_height_profiles[tallest_object_idx]; for (size_t idx_object = 0; idx_object < m_objects.size(); ++ idx_object) { - const PrintObject *object = m_objects[idx_object]; const std::vector &layer_height_profile = layer_height_profiles[idx_object]; bool failed = false; if (layer_height_profile_tallest.size() >= layer_height_profile.size()) { - int i = 0; + size_t i = 0; while (i < layer_height_profile.size() && i < layer_height_profile_tallest.size()) { if (std::abs(layer_height_profile_tallest[i] - layer_height_profile[i])) { failed = true; @@ -1435,9 +1312,9 @@ Flow Print::brim_flow() const generation as well. */ return Flow::new_from_config_width( frPerimeter, - width, - m_config.nozzle_diameter.get_at(m_regions.front()->config().perimeter_extruder-1), - this->skirt_first_layer_height(), + width, + (float)m_config.nozzle_diameter.get_at(m_regions.front()->config().perimeter_extruder-1), + (float)this->skirt_first_layer_height(), 0 ); } @@ -1457,9 +1334,9 @@ Flow Print::skirt_flow() const generation as well. */ return Flow::new_from_config_width( frPerimeter, - width, - m_config.nozzle_diameter.get_at(m_objects.front()->config().support_material_extruder-1), - this->skirt_first_layer_height(), + width, + (float)m_config.nozzle_diameter.get_at(m_objects.front()->config().support_material_extruder-1), + (float)this->skirt_first_layer_height(), 0 ); } @@ -1628,26 +1505,26 @@ void Print::_make_skirt() } // Number of skirt loops per skirt layer. - int n_skirts = m_config.skirts.value; + size_t n_skirts = m_config.skirts.value; if (this->has_infinite_skirt() && n_skirts == 0) n_skirts = 1; // Initial offset of the brim inner edge from the object (possible with a support & raft). // The skirt will touch the brim if the brim is extruded. - Flow brim_flow = this->brim_flow(); + Flow brim_flow = this->brim_flow(); double actual_brim_width = brim_flow.spacing() * floor(m_config.brim_width.value / brim_flow.spacing()); - coord_t distance = scale_(std::max(m_config.skirt_distance.value, actual_brim_width) - spacing/2.); + auto distance = float(scale_(std::max(m_config.skirt_distance.value, actual_brim_width) - spacing/2.)); // Draw outlines from outside to inside. // Loop while we have less skirts than required or any extruder hasn't reached the min length if any. std::vector extruded_length(extruders.size(), 0.); - for (int i = n_skirts, extruder_idx = 0; i > 0; -- i) { + for (size_t i = n_skirts, extruder_idx = 0; i > 0; -- i) { this->throw_if_canceled(); // Offset the skirt outside. - distance += coord_t(scale_(spacing)); + distance += float(scale_(spacing)); // Generate the skirt centerline. Polygon loop; { - Polygons loops = offset(convex_hull, distance, ClipperLib::jtRound, scale_(0.1)); + Polygons loops = offset(convex_hull, distance, ClipperLib::jtRound, float(scale_(0.1))); Geometry::simplify_polygons(loops, scale_(0.05), &loops); if (loops.empty()) break; @@ -1658,9 +1535,9 @@ void Print::_make_skirt() eloop.paths.emplace_back(ExtrusionPath( ExtrusionPath( erSkirt, - mm3_per_mm, // this will be overridden at G-code export time + (float)mm3_per_mm, // this will be overridden at G-code export time flow.width, - first_layer_height // this will be overridden at G-code export time + (float)first_layer_height // this will be overridden at G-code export time ))); eloop.paths.back().polyline = loop.split_at_first_point(); m_skirt.append(eloop); @@ -1730,7 +1607,6 @@ void Print::_make_brim() bool Print::has_wipe_tower() const { return - m_config.single_extruder_multi_material.value && ! m_config.spiral_vase.value && m_config.wipe_tower.value && m_config.nozzle_diameter.values.size() > 1; @@ -1786,7 +1662,7 @@ void Print::_make_wipe_tower() // Insert the new support layer. double height = lt.print_z - m_wipe_tower_data.tool_ordering.layer_tools()[i-1].print_z; //FIXME the support layer ID is set to -1, as Vojtech hopes it is not being used anyway. - it_layer = m_objects.front()->insert_support_layer(it_layer, size_t(-1), height, lt.print_z, lt.print_z - 0.5 * height); + it_layer = m_objects.front()->insert_support_layer(it_layer, -1, height, lt.print_z, lt.print_z - 0.5 * height); ++ it_layer; } } @@ -1794,12 +1670,13 @@ void Print::_make_wipe_tower() this->throw_if_canceled(); // Initialize the wipe tower. - WipeTowerPrusaMM wipe_tower( + WipeTower wipe_tower( + m_config.single_extruder_multi_material.value, float(m_config.wipe_tower_x.value), float(m_config.wipe_tower_y.value), float(m_config.wipe_tower_width.value), float(m_config.wipe_tower_rotation_angle.value), float(m_config.cooling_tube_retraction.value), float(m_config.cooling_tube_length.value), float(m_config.parking_pos_retraction.value), - float(m_config.extra_loading_move.value), float(m_config.wipe_tower_bridging), + float(m_config.extra_loading_move.value), float(m_config.wipe_tower_bridging), m_config.high_current_on_filament_swap.value, m_config.gcode_flavor, wipe_volumes, m_wipe_tower_data.tool_ordering.first_extruder()); @@ -1808,24 +1685,26 @@ void Print::_make_wipe_tower() // Set the extruder & material properties at the wipe tower object. for (size_t i = 0; i < number_of_extruders; ++ i) + wipe_tower.set_extruder( i, - WipeTowerPrusaMM::parse_material(m_config.filament_type.get_at(i).c_str()), + m_config.filament_type.get_at(i), m_config.temperature.get_at(i), m_config.first_layer_temperature.get_at(i), - m_config.filament_loading_speed.get_at(i), - m_config.filament_loading_speed_start.get_at(i), - m_config.filament_unloading_speed.get_at(i), - m_config.filament_unloading_speed_start.get_at(i), - m_config.filament_toolchange_delay.get_at(i), + (float)m_config.filament_loading_speed.get_at(i), + (float)m_config.filament_loading_speed_start.get_at(i), + (float)m_config.filament_unloading_speed.get_at(i), + (float)m_config.filament_unloading_speed_start.get_at(i), + (float)m_config.filament_toolchange_delay.get_at(i), m_config.filament_cooling_moves.get_at(i), - m_config.filament_cooling_initial_speed.get_at(i), - m_config.filament_cooling_final_speed.get_at(i), + (float)m_config.filament_cooling_initial_speed.get_at(i), + (float)m_config.filament_cooling_final_speed.get_at(i), m_config.filament_ramming_parameters.get_at(i), + m_config.filament_max_volumetric_speed.get_at(i), m_config.nozzle_diameter.get_at(i)); - m_wipe_tower_data.priming = Slic3r::make_unique( - wipe_tower.prime(this->skirt_first_layer_height(), m_wipe_tower_data.tool_ordering.all_extruders(), false)); + m_wipe_tower_data.priming = Slic3r::make_unique>( + wipe_tower.prime((float)this->skirt_first_layer_height(), m_wipe_tower_data.tool_ordering.all_extruders(), false)); // Lets go through the wipe tower layers and determine pairs of extruder changes for each // to pass to wipe_tower (so that it can use it for planning the layout of the tower) @@ -1834,21 +1713,21 @@ void Print::_make_wipe_tower() for (auto &layer_tools : m_wipe_tower_data.tool_ordering.layer_tools()) { // for all layers if (!layer_tools.has_wipe_tower) continue; bool first_layer = &layer_tools == &m_wipe_tower_data.tool_ordering.front(); - wipe_tower.plan_toolchange(layer_tools.print_z, layer_tools.wipe_tower_layer_height, current_extruder_id, current_extruder_id,false); + wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_extruder_id, current_extruder_id, false); for (const auto extruder_id : layer_tools.extruders) { if ((first_layer && extruder_id == m_wipe_tower_data.tool_ordering.all_extruders().back()) || extruder_id != current_extruder_id) { float volume_to_wipe = wipe_volumes[current_extruder_id][extruder_id]; // total volume to wipe after this toolchange // Not all of that can be used for infill purging: - volume_to_wipe -= m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); + volume_to_wipe -= (float)m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); // try to assign some infills/objects for the wiping: volume_to_wipe = layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, current_extruder_id, extruder_id, volume_to_wipe); // add back the minimal amount toforce on the wipe tower: - volume_to_wipe += m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); + volume_to_wipe += (float)m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); // request a toolchange at the wipe tower with at least volume_to_wipe purging amount - wipe_tower.plan_toolchange(layer_tools.print_z, layer_tools.wipe_tower_layer_height, current_extruder_id, extruder_id, + wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_extruder_id, extruder_id, first_layer && extruder_id == m_wipe_tower_data.tool_ordering.all_extruders().back(), volume_to_wipe); current_extruder_id = extruder_id; } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 53d6d692d..368bf4ee8 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -80,8 +80,8 @@ private: // Prevents erroneous use by other classes. typedef PrintObjectBaseWithState Inherited; public: - // vector of (vectors of volume ids), indexed by region_id - std::vector> region_volumes; + // vector of (layer height ranges and vectors of volume ids), indexed by region_id + std::vector>> region_volumes; // this is set to true when LayerRegion->slices is split in top/internal/bottom // so that next call to make_perimeters() performs a union() before computing loops @@ -99,10 +99,10 @@ public: BoundingBox bounding_box() const { return BoundingBox(Point(0,0), to_2d(this->size)); } // adds region_id, too, if necessary - void add_region_volume(unsigned int region_id, int volume_id) { + void add_region_volume(unsigned int region_id, int volume_id, const t_layer_height_range &layer_range) { if (region_id >= region_volumes.size()) region_volumes.resize(region_id + 1); - region_volumes[region_id].emplace_back(volume_id); + region_volumes[region_id].emplace_back(layer_range, volume_id); } // This is the *total* layer count (including support layers) // this value is not supposed to be compared with Layer::id @@ -120,7 +120,7 @@ public: void clear_support_layers(); SupportLayer* get_support_layer(int idx) { return m_support_layers[idx]; } SupportLayer* add_support_layer(int id, coordf_t height, coordf_t print_z); - SupportLayerPtrs::const_iterator insert_support_layer(SupportLayerPtrs::const_iterator pos, int id, coordf_t height, coordf_t print_z, coordf_t slice_z); + SupportLayerPtrs::const_iterator insert_support_layer(SupportLayerPtrs::const_iterator pos, size_t id, coordf_t height, coordf_t print_z, coordf_t slice_z); void delete_support_layer(int idx); // Initialize the layer_height_profile from the model_object's layer_height_profile, from model_object's layer height table, or from slicing parameters. @@ -141,8 +141,9 @@ public: void slice(); // Helpers to slice support enforcer / blocker meshes by the support generator. - std::vector slice_support_enforcers() const; - std::vector slice_support_blockers() const; + std::vector slice_support_volumes(const ModelVolumeType &model_volume_type) const; + std::vector slice_support_blockers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_BLOCKER); } + std::vector slice_support_enforcers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_ENFORCER); } protected: // to be called from Print only. @@ -165,7 +166,7 @@ protected: void update_slicing_parameters(); static PrintObjectConfig object_config_from_model_object(const PrintObjectConfig &default_object_config, const ModelObject &object, size_t num_extruders); - static PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &default_region_config, const ModelVolume &volume, size_t num_extruders); + static PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &default_region_config, const DynamicPrintConfig *layer_range_config, const ModelVolume &volume, size_t num_extruders); private: void make_perimeters(); @@ -201,9 +202,11 @@ private: LayerPtrs m_layers; SupportLayerPtrs m_support_layers; - std::vector _slice_region(size_t region_id, const std::vector &z, bool modifier); - std::vector _slice_volumes(const std::vector &z, const std::vector &volumes) const; - std::vector _slice_volume(const std::vector &z, const ModelVolume &volume) const; + std::vector slice_region(size_t region_id, const std::vector &z) const; + std::vector slice_modifiers(size_t region_id, const std::vector &z) const; + std::vector slice_volumes(const std::vector &z, const std::vector &volumes) const; + std::vector slice_volume(const std::vector &z, const ModelVolume &volume) const; + std::vector slice_volume(const std::vector &z, const std::vector &ranges, const ModelVolume &volume) const; }; struct WipeTowerData @@ -213,7 +216,7 @@ struct WipeTowerData // Cache it here, so it does not need to be recalculated during the G-code generation. ToolOrdering tool_ordering; // Cache of tool changes per print layer. - std::unique_ptr priming; + std::unique_ptr> priming; std::vector> tool_changes; std::unique_ptr final_purge; std::vector used_filament; @@ -238,6 +241,8 @@ struct PrintStatistics PrintStatistics() { clear(); } std::string estimated_normal_print_time; std::string estimated_silent_print_time; + std::vector estimated_normal_color_print_times; + std::vector estimated_silent_color_print_times; double total_used_filament; double total_extruded_volume; double total_cost; @@ -256,6 +261,8 @@ struct PrintStatistics void clear() { estimated_normal_print_time.clear(); estimated_silent_print_time.clear(); + estimated_normal_color_print_times.clear(); + estimated_silent_color_print_times.clear(); total_used_filament = 0.; total_extruded_volume = 0.; total_cost = 0.; @@ -291,11 +298,6 @@ public: ApplyStatus apply(const Model &model, const DynamicPrintConfig &config) override; - // The following three methods are used by the Perl tests only. Get rid of them! - void reload_object(size_t idx); - void add_model_object(ModelObject* model_object, int idx = -1); - bool apply_config_perl_tests_only(DynamicPrintConfig config); - void process() override; // Exports G-code into a file name based on the path_template, returns the file path of the generated G-code file. // If preview_data is not null, the preview_data is filled in for the G-code visualization (not used by the command line Slic3r). diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index d4c39499c..d84d492a0 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -246,7 +246,7 @@ public: struct TaskParams { TaskParams() : single_model_object(0), single_model_instance_only(false), to_object_step(-1), to_print_step(-1) {} // If non-empty, limit the processing to this ModelObject. - ModelID single_model_object; + ObjectID single_model_object; // If set, only process single_model_object. Otherwise process everything, but single_model_object first. bool single_model_instance_only; // If non-negative, stop processing at the successive object step. diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 89e21934a..7d451a4cb 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -36,7 +36,6 @@ PrintConfigDef::PrintConfigDef() void PrintConfigDef::init_common_params() { - t_optiondef_map &Options = this->options; ConfigOptionDef* def; def = this->add("printer_technology", coEnum); @@ -102,7 +101,6 @@ void PrintConfigDef::init_common_params() void PrintConfigDef::init_fff_params() { - t_optiondef_map &Options = this->options; ConfigOptionDef* def; // Maximum extruder temperature, bumped to 1500 to support printing of glass. @@ -368,7 +366,8 @@ void PrintConfigDef::init_fff_params() def = this->add("end_filament_gcode", coStrings); def->label = L("End G-code"); - def->tooltip = L("This end procedure is inserted at the end of the output file, before the printer end gcode. " + def->tooltip = L("This end procedure is inserted at the end of the output file, before the printer end gcode (and " + "before any toolchange from this filament in case of multimaterial printers). " "Note that you can use placeholder variables for all Slic3r settings. " "If you have multiple extruders, the gcode is processed in extruder order."); def->multiline = true; @@ -406,10 +405,13 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionEnum(ipRectilinear)); def = this->add("bottom_fill_pattern", coEnum); - *def = *def_top_fill_pattern; def->label = L("Bottom fill pattern"); + def->category = L("Infill"); def->tooltip = L("Fill pattern for bottom infill. This only affects the bottom external visible layer, and not its adjacent solid shells."); def->cli = "bottom-fill-pattern|external-fill-pattern|solid-fill-pattern"; + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values = def_top_fill_pattern->enum_values; + def->aliases = def_top_fill_pattern->aliases; def->set_default_value(new ConfigOptionEnum(ipRectilinear)); def = this->add("external_perimeter_extrusion_width", coFloatOrPercent); @@ -1085,16 +1087,16 @@ void PrintConfigDef::init_fff_params() // Add the machine feedrate limits for XYZE axes. (M203) def = this->add("machine_max_feedrate_" + axis.name, coFloats); def->full_label = (boost::format("Maximum feedrate %1%") % axis_upper).str(); - L("Maximum feedrate X"); - L("Maximum feedrate Y"); - L("Maximum feedrate Z"); - L("Maximum feedrate E"); + (void)L("Maximum feedrate X"); + (void)L("Maximum feedrate Y"); + (void)L("Maximum feedrate Z"); + (void)L("Maximum feedrate E"); def->category = L("Machine limits"); def->tooltip = (boost::format("Maximum feedrate of the %1% axis") % axis_upper).str(); - L("Maximum feedrate of the X axis"); - L("Maximum feedrate of the Y axis"); - L("Maximum feedrate of the Z axis"); - L("Maximum feedrate of the E axis"); + (void)L("Maximum feedrate of the X axis"); + (void)L("Maximum feedrate of the Y axis"); + (void)L("Maximum feedrate of the Z axis"); + (void)L("Maximum feedrate of the E axis"); def->sidetext = L("mm/s"); def->min = 0; def->width = machine_limits_opt_width; @@ -1103,16 +1105,16 @@ void PrintConfigDef::init_fff_params() // Add the machine acceleration limits for XYZE axes (M201) def = this->add("machine_max_acceleration_" + axis.name, coFloats); def->full_label = (boost::format("Maximum acceleration %1%") % axis_upper).str(); - L("Maximum acceleration X"); - L("Maximum acceleration Y"); - L("Maximum acceleration Z"); - L("Maximum acceleration E"); + (void)L("Maximum acceleration X"); + (void)L("Maximum acceleration Y"); + (void)L("Maximum acceleration Z"); + (void)L("Maximum acceleration E"); def->category = L("Machine limits"); def->tooltip = (boost::format("Maximum acceleration of the %1% axis") % axis_upper).str(); - L("Maximum acceleration of the X axis"); - L("Maximum acceleration of the Y axis"); - L("Maximum acceleration of the Z axis"); - L("Maximum acceleration of the E axis"); + (void)L("Maximum acceleration of the X axis"); + (void)L("Maximum acceleration of the Y axis"); + (void)L("Maximum acceleration of the Z axis"); + (void)L("Maximum acceleration of the E axis"); def->sidetext = L("mm/s²"); def->min = 0; def->width = machine_limits_opt_width; @@ -1121,16 +1123,16 @@ void PrintConfigDef::init_fff_params() // Add the machine jerk limits for XYZE axes (M205) def = this->add("machine_max_jerk_" + axis.name, coFloats); def->full_label = (boost::format("Maximum jerk %1%") % axis_upper).str(); - L("Maximum jerk X"); - L("Maximum jerk Y"); - L("Maximum jerk Z"); - L("Maximum jerk E"); + (void)L("Maximum jerk X"); + (void)L("Maximum jerk Y"); + (void)L("Maximum jerk Z"); + (void)L("Maximum jerk E"); def->category = L("Machine limits"); def->tooltip = (boost::format("Maximum jerk of the %1% axis") % axis_upper).str(); - L("Maximum jerk of the X axis"); - L("Maximum jerk of the Y axis"); - L("Maximum jerk of the Z axis"); - L("Maximum jerk of the E axis"); + (void)L("Maximum jerk of the X axis"); + (void)L("Maximum jerk of the Y axis"); + (void)L("Maximum jerk of the Z axis"); + (void)L("Maximum jerk of the E axis"); def->sidetext = L("mm/s"); def->min = 0; def->width = machine_limits_opt_width; @@ -1784,7 +1786,8 @@ void PrintConfigDef::init_fff_params() def = this->add("start_filament_gcode", coStrings); def->label = L("Start G-code"); - def->tooltip = L("This start procedure is inserted at the beginning, after any printer start gcode. " + def->tooltip = L("This start procedure is inserted at the beginning, after any printer start gcode (and " + "after any toolchange to this filament in case of multi-material printers). " "This is used to override settings for a specific filament. If Slic3r detects " "M104, M109, M140 or M190 in your custom codes, such commands will " "not be prepended automatically so you're free to customize the order " @@ -2039,9 +2042,10 @@ void PrintConfigDef::init_fff_params() def = this->add("toolchange_gcode", coString); def->label = L("Tool change G-code"); - def->tooltip = L("This custom code is inserted right before every extruder change. " - "Note that you can use placeholder variables for all Slic3r settings as well " - "as [previous_extruder] and [next_extruder]."); + def->tooltip = L("This custom code is inserted at every extruder change. If you don't leave this empty, you are " + "expected to take care of the toolchange yourself - PrusaSlicer will not output any other G-code to " + "change the filament. You can use placeholder variables for all Slic3r settings as well as [previous_extruder] " + "and [next_extruder], so e.g. the standard toolchange command can be scripted as T[next_extruder]."); def->multiline = true; def->full_width = true; def->height = 5; @@ -2228,7 +2232,6 @@ void PrintConfigDef::init_fff_params() void PrintConfigDef::init_sla_params() { - t_optiondef_map &Options = this->options; ConfigOptionDef* def; // SLA Printer settings @@ -2504,6 +2507,19 @@ void PrintConfigDef::init_sla_params() def->min = 0; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(1.0)); + + def = this->add("support_base_safety_distance", coFloat); + def->label = L("Support base safety distance"); + def->category = L("Supports"); + def->tooltip = L( + "The minimum distance of the pillar base from the model in mm. " + "Makes sense in zero elevation mode where a gap according " + "to this parameter is inserted between the model and the pad."); + def->sidetext = L("mm"); + def->min = 0; + def->max = 10; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(1)); def = this->add("support_critical_angle", coFloat); def->label = L("Critical angle"); @@ -2537,7 +2553,9 @@ void PrintConfigDef::init_sla_params() def = this->add("support_object_elevation", coFloat); def->label = L("Object elevation"); def->category = L("Supports"); - def->tooltip = L("How much the supports should lift up the supported object."); + def->tooltip = L("How much the supports should lift up the supported object. " + "If this value is zero, the bottom of the model geometry " + "will be considered as part of the pad."); def->sidetext = L("mm"); def->min = 0; def->max = 150; // This is the max height of print on SL1 @@ -2623,6 +2641,47 @@ void PrintConfigDef::init_sla_params() def->max = 90; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(45.0)); + + def = this->add("pad_object_gap", coFloat); + def->label = L("Pad object gap"); + def->category = L("Pad"); + def->tooltip = L("The gap between the object bottom and the generated " + "pad in zero elevation mode."); + def->sidetext = L("mm"); + def->min = 0; + def->max = 10; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(1)); + + def = this->add("pad_object_connector_stride", coFloat); + def->label = L("Pad object connector stride"); + def->category = L("Pad"); + def->tooltip = L("Distance between two connector sticks between " + "the object pad and the generated pad."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(10)); + + def = this->add("pad_object_connector_width", coFloat); + def->label = L("Pad object connector width"); + def->category = L("Pad"); + def->tooltip = L("The width of the connectors sticks which connect the " + "object pad and the generated pad."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(0.5)); + + def = this->add("pad_object_connector_penetration", coFloat); + def->label = L("Pad object connector penetration"); + def->category = L("Pad"); + def->tooltip = L( + "How much should the tiny connectors penetrate into the model body."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(0.3)); } void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value) @@ -3194,3 +3253,7 @@ void DynamicPrintAndCLIConfig::handle_legacy(t_config_option_key &opt_key, std:: } } + +#include +CEREAL_REGISTER_TYPE(Slic3r::DynamicPrintConfig) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::DynamicConfig, Slic3r::DynamicPrintConfig) diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 248b89e32..5731bef00 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -983,6 +983,9 @@ public: // The height of the pillar base cone in mm. ConfigOptionFloat support_base_height /*= 1.0*/; + + // The minimum distance of the pillar base from the model in mm. + ConfigOptionFloat support_base_safety_distance; /*= 1.0*/; // The default angle for connecting support sticks and junctions. ConfigOptionFloat support_critical_angle /*= 45*/; @@ -996,7 +999,7 @@ public: // The elevation in Z direction upwards. This is the space between the pad // and the model object's bounding box bottom. Units in mm. ConfigOptionFloat support_object_elevation /*= 5.0*/; - + /////// Following options influence automatic support points placement: ConfigOptionInt support_points_density_relative; ConfigOptionFloat support_points_minimal_distance; @@ -1021,6 +1024,26 @@ public: // The slope of the pad wall... ConfigOptionFloat pad_wall_slope; + + // ///////////////////////////////////////////////////////////////////////// + // Zero elevation mode parameters: + // - The object pad will be derived from the the model geometry. + // - There will be a gap between the object pad and the generated pad + // according to the support_base_safety_distance parameter. + // - The two pads will be connected with tiny connector sticks + // ///////////////////////////////////////////////////////////////////////// + + // This is the gap between the object bottom and the generated pad + ConfigOptionFloat pad_object_gap; + + // How far to place the connector sticks on the object pad perimeter + ConfigOptionFloat pad_object_connector_stride; + + // The width of the connectors sticks + ConfigOptionFloat pad_object_connector_width; + + // How much should the tiny connectors penetrate into the model body + ConfigOptionFloat pad_object_connector_penetration; protected: void initialize(StaticCacheBase &cache, const char *base_ptr) @@ -1038,6 +1061,7 @@ protected: OPT_PTR(support_pillar_widening_factor); OPT_PTR(support_base_diameter); OPT_PTR(support_base_height); + OPT_PTR(support_base_safety_distance); OPT_PTR(support_critical_angle); OPT_PTR(support_max_bridge_length); OPT_PTR(support_max_pillar_link_distance); @@ -1050,6 +1074,10 @@ protected: OPT_PTR(pad_max_merge_distance); OPT_PTR(pad_edge_radius); OPT_PTR(pad_wall_slope); + OPT_PTR(pad_object_gap); + OPT_PTR(pad_object_connector_stride); + OPT_PTR(pad_object_connector_width); + OPT_PTR(pad_object_connector_penetration); } }; @@ -1190,6 +1218,8 @@ private: this->options.insert(cli_actions_config_def.options.begin(), cli_actions_config_def.options.end()); this->options.insert(cli_transform_config_def.options.begin(), cli_transform_config_def.options.end()); this->options.insert(cli_misc_config_def.options.begin(), cli_misc_config_def.options.end()); + for (const auto &kvp : this->options) + this->by_serialization_key_ordinal[kvp.second.serialization_key_ordinal] = &kvp.second; } // Do not release the default values, they are handled by print_config_def & cli_actions_config_def / cli_transform_config_def / cli_misc_config_def. ~PrintAndCLIConfigDef() { this->options.clear(); } @@ -1199,4 +1229,38 @@ private: } // namespace Slic3r +// Serialization through the Cereal library +namespace cereal { + // Let cereal know that there are load / save non-member functions declared for DynamicPrintConfig, ignore serialize / load / save from parent class DynamicConfig. + template struct specialize {}; + + template void load(Archive& archive, Slic3r::DynamicPrintConfig &config) + { + size_t cnt; + archive(cnt); + config.clear(); + for (size_t i = 0; i < cnt; ++ i) { + size_t serialization_key_ordinal; + archive(serialization_key_ordinal); + assert(serialization_key_ordinal > 0); + auto it = Slic3r::print_config_def.by_serialization_key_ordinal.find(serialization_key_ordinal); + assert(it != Slic3r::print_config_def.by_serialization_key_ordinal.end()); + config.set_key_value(it->second->opt_key, it->second->load_option_from_archive(archive)); + } + } + + template void save(Archive& archive, const Slic3r::DynamicPrintConfig &config) + { + size_t cnt = config.size(); + archive(cnt); + for (auto it = config.cbegin(); it != config.cend(); ++it) { + const Slic3r::ConfigOptionDef* optdef = Slic3r::print_config_def.get(it->first); + assert(optdef != nullptr); + assert(optdef->serialization_key_ordinal > 0); + archive(optdef->serialization_key_ordinal); + optdef->save_option_to_archive(archive, it->second.get()); + } + } +} + #endif diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index d99aceabf..37cf0cccc 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -18,7 +18,7 @@ #include -//! macro used to mark string used at localization, +//! macro used to mark string used at localization, //! return same string #define L(s) Slic3r::I18N::translate(s) @@ -49,7 +49,7 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, bool add_insta { // Translate meshes so that our toolpath generation algorithms work with smaller // XY coordinates; this translation is an optimization and not strictly required. - // A cloned mesh will be aligned to 0 before slicing in _slice_region() since we + // A cloned mesh will be aligned to 0 before slicing in slice_region() since we // don't assume it's already aligned and we don't alter the original position in model. // We store the XY translation so that we can place copies correctly in the output G-code // (copies are expressed in G-code coordinates and this translation is not publicly exposed). @@ -430,7 +430,7 @@ SupportLayer* PrintObject::add_support_layer(int id, coordf_t height, coordf_t p return m_support_layers.back(); } -SupportLayerPtrs::const_iterator PrintObject::insert_support_layer(SupportLayerPtrs::const_iterator pos, int id, coordf_t height, coordf_t print_z, coordf_t slice_z) +SupportLayerPtrs::const_iterator PrintObject::insert_support_layer(SupportLayerPtrs::const_iterator pos, size_t id, coordf_t height, coordf_t print_z, coordf_t slice_z) { return m_support_layers.insert(pos, new SupportLayer(id, this, height, print_z, slice_z)); } @@ -590,7 +590,12 @@ bool PrintObject::invalidate_step(PrintObjectStep step) bool PrintObject::invalidate_all_steps() { - return Inherited::invalidate_all_steps() | m_print->invalidate_all_steps(); + // First call the "invalidate" functions, which may cancel background processing. + bool result = Inherited::invalidate_all_steps() | m_print->invalidate_all_steps(); + // Then reset some of the depending values. + this->m_slicing_params.valid = false; + this->region_volumes.clear(); + return result; } bool PrintObject::has_support_material() const @@ -620,7 +625,7 @@ void PrintObject::detect_surfaces_type() // should be visible. bool interface_shells = m_config.interface_shells.value; - for (int idx_region = 0; idx_region < this->region_volumes.size(); ++ idx_region) { + for (size_t idx_region = 0; idx_region < this->region_volumes.size(); ++ idx_region) { BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << idx_region << " in parallel - start"; #ifdef SLIC3R_DEBUG_SLICE_PROCESSING for (Layer *layer : m_layers) @@ -806,8 +811,6 @@ void PrintObject::process_external_surfaces() BOOST_LOG_TRIVIAL(info) << "Processing external surfaces..." << log_memory_info(); for (size_t region_id = 0; region_id < this->region_volumes.size(); ++region_id) { - const PrintRegion ®ion = *m_print->regions()[region_id]; - BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces for region " << region_id << " in parallel - start"; tbb::parallel_for( tbb::blocked_range(0, m_layers.size()), @@ -1028,7 +1031,6 @@ void PrintObject::discover_vertical_shells() bool hole_first = true; for (int n = (int)idx_layer - n_extra_bottom_layers; n <= (int)idx_layer + n_extra_top_layers; ++ n) if (n >= 0 && n < (int)m_layers.size()) { - Layer &neighbor_layer = *m_layers[n]; const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[n]; if (hole_first) { hole_first = false; @@ -1354,10 +1356,12 @@ PrintObjectConfig PrintObject::object_config_from_model_object(const PrintObject return config; } -PrintRegionConfig PrintObject::region_config_from_model_volume(const PrintRegionConfig &default_region_config, const ModelVolume &volume, size_t num_extruders) +PrintRegionConfig PrintObject::region_config_from_model_volume(const PrintRegionConfig &default_region_config, const DynamicPrintConfig *layer_range_config, const ModelVolume &volume, size_t num_extruders) { PrintRegionConfig config = default_region_config; normalize_and_apply_config(config, volume.get_object()->config); + if (layer_range_config != nullptr) + normalize_and_apply_config(config, *layer_range_config); normalize_and_apply_config(config, volume.config); if (! volume.material_id().empty()) normalize_and_apply_config(config, volume.material()->config); @@ -1375,28 +1379,37 @@ void PrintObject::update_slicing_parameters() this->print()->config(), m_config, unscale(this->size(2)), this->object_extruders()); } -SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full_config, const ModelObject &model_object, float object_max_z) +SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig& full_config, const ModelObject& model_object, float object_max_z) { - PrintConfig print_config; - PrintObjectConfig object_config; - PrintRegionConfig default_region_config; - print_config .apply(full_config, true); - object_config.apply(full_config, true); - default_region_config.apply(full_config, true); - size_t num_extruders = print_config.nozzle_diameter.size(); - object_config = object_config_from_model_object(object_config, model_object, num_extruders); + PrintConfig print_config; + PrintObjectConfig object_config; + PrintRegionConfig default_region_config; + print_config.apply(full_config, true); + object_config.apply(full_config, true); + default_region_config.apply(full_config, true); + size_t num_extruders = print_config.nozzle_diameter.size(); + object_config = object_config_from_model_object(object_config, model_object, num_extruders); - std::vector object_extruders; - for (const ModelVolume *model_volume : model_object.volumes) - if (model_volume->is_model_part()) - PrintRegion::collect_object_printing_extruders( - print_config, - region_config_from_model_volume(default_region_config, *model_volume, num_extruders), - object_extruders); + std::vector object_extruders; + for (const ModelVolume* model_volume : model_object.volumes) + if (model_volume->is_model_part()) { + PrintRegion::collect_object_printing_extruders( + print_config, + region_config_from_model_volume(default_region_config, nullptr, *model_volume, num_extruders), + object_extruders); + for (const std::pair &range_and_config : model_object.layer_config_ranges) + if (range_and_config.second.has("perimeter_extruder") || + range_and_config.second.has("infill_extruder") || + range_and_config.second.has("solid_infill_extruder")) + PrintRegion::collect_object_printing_extruders( + print_config, + region_config_from_model_volume(default_region_config, &range_and_config.second, *model_volume, num_extruders), + object_extruders); + } sort_remove_duplicates(object_extruders); if (object_max_z <= 0.f) - object_max_z = model_object.raw_bounding_box().size().z(); + object_max_z = (float)model_object.raw_bounding_box().size().z(); return SlicingParameters::create_from_config(print_config, object_config, object_max_z, object_extruders); } @@ -1430,12 +1443,12 @@ bool PrintObject::update_layer_height_profile(const ModelObject &model_object, c layer_height_profile.clear(); if (layer_height_profile.empty()) { - if (0) + if (0) // if (this->layer_height_profile.empty()) - layer_height_profile = layer_height_profile_adaptive(slicing_parameters, model_object.layer_height_ranges, model_object.volumes); + layer_height_profile = layer_height_profile_adaptive(slicing_parameters, model_object.layer_config_ranges, model_object.volumes); else - layer_height_profile = layer_height_profile_from_ranges(slicing_parameters, model_object.layer_height_ranges); - updated = true; + layer_height_profile = layer_height_profile_from_ranges(slicing_parameters, model_object.layer_config_ranges); // #ys_FIXME_experiment + updated = true; } return updated; } @@ -1489,22 +1502,28 @@ void PrintObject::_slice(const std::vector &layer_height_profile) } // Count model parts and modifier meshes, check whether the model parts are of the same region. - int single_volume_region = -2; // not set yet + int all_volumes_single_region = -2; // not set yet + bool has_z_ranges = false; size_t num_volumes = 0; size_t num_modifiers = 0; - std::vector map_volume_to_region(this->model_object()->volumes.size()); for (int region_id = 0; region_id < (int)this->region_volumes.size(); ++ region_id) { - for (int volume_id : this->region_volumes[region_id]) { + int last_volume_id = -1; + for (const std::pair &volume_and_range : this->region_volumes[region_id]) { + const int volume_id = volume_and_range.second; const ModelVolume *model_volume = this->model_object()->volumes[volume_id]; if (model_volume->is_model_part()) { - map_volume_to_region[volume_id] = region_id; - if (single_volume_region == -2) - // first model volume met - single_volume_region = region_id; - else if (single_volume_region != region_id) - // multiple volumes met and they are not equal - single_volume_region = -1; - ++ num_volumes; + if (last_volume_id == volume_id) { + has_z_ranges = true; + } else { + last_volume_id = volume_id; + if (all_volumes_single_region == -2) + // first model volume met + all_volumes_single_region = region_id; + else if (all_volumes_single_region != region_id) + // multiple volumes met and they are not equal + all_volumes_single_region = -1; + ++ num_volumes; + } } else if (model_volume->is_modifier()) ++ num_modifiers; } @@ -1514,13 +1533,13 @@ void PrintObject::_slice(const std::vector &layer_height_profile) // Slice all non-modifier volumes. bool clipped = false; bool upscaled = false; - if (! m_config.clip_multipart_objects.value || single_volume_region >= 0) { + if (! has_z_ranges && (! m_config.clip_multipart_objects.value || all_volumes_single_region >= 0)) { // Cheap path: Slice regions without mutual clipping. // The cheap path is possible if no clipping is allowed or if slicing volumes of just a single region. for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { BOOST_LOG_TRIVIAL(debug) << "Slicing objects - region " << region_id; // slicing in parallel - std::vector expolygons_by_layer = this->_slice_region(region_id, slice_zs, false); + std::vector expolygons_by_layer = this->slice_region(region_id, slice_zs); m_print->throw_if_canceled(); BOOST_LOG_TRIVIAL(debug) << "Slicing objects - append slices " << region_id << " start"; for (size_t layer_id = 0; layer_id < expolygons_by_layer.size(); ++ layer_id) @@ -1541,15 +1560,29 @@ void PrintObject::_slice(const std::vector &layer_height_profile) }; std::vector sliced_volumes; sliced_volumes.reserve(num_volumes); - for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) - for (int volume_id : this->region_volumes[region_id]) { + for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { + const std::vector> &volumes_and_ranges = this->region_volumes[region_id]; + for (size_t i = 0; i < volumes_and_ranges.size(); ) { + int volume_id = volumes_and_ranges[i].second; const ModelVolume *model_volume = this->model_object()->volumes[volume_id]; if (model_volume->is_model_part()) { BOOST_LOG_TRIVIAL(debug) << "Slicing objects - volume " << volume_id; + // Find the ranges of this volume. Ranges in volumes_and_ranges must not overlap for a single volume. + std::vector ranges; + ranges.emplace_back(volumes_and_ranges[i].first); + size_t j = i + 1; + for (; j < volumes_and_ranges.size() && volume_id == volumes_and_ranges[j].second; ++ j) + if (! ranges.empty() && std::abs(ranges.back().second - volumes_and_ranges[j].first.first) < EPSILON) + ranges.back().second = volumes_and_ranges[j].first.second; + else + ranges.emplace_back(volumes_and_ranges[j].first); // slicing in parallel - sliced_volumes.emplace_back(volume_id, map_volume_to_region[volume_id], this->_slice_volume(slice_zs, *model_volume)); - } + sliced_volumes.emplace_back(volume_id, (int)region_id, this->slice_volume(slice_zs, ranges, *model_volume)); + i = j; + } else + ++ i; } + } // Second clip the volumes in the order they are presented at the user interface. BOOST_LOG_TRIVIAL(debug) << "Slicing objects - parallel clipping - start"; tbb::parallel_for( @@ -1603,7 +1636,7 @@ void PrintObject::_slice(const std::vector &layer_height_profile) for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { BOOST_LOG_TRIVIAL(debug) << "Slicing modifier volumes - region " << region_id; // slicing in parallel - std::vector expolygons_by_layer = this->_slice_region(region_id, slice_zs, true); + std::vector expolygons_by_layer = this->slice_modifiers(region_id, slice_zs); m_print->throw_if_canceled(); if (expolygons_by_layer.empty()) continue; @@ -1619,7 +1652,7 @@ void PrintObject::_slice(const std::vector &layer_height_profile) Layer *layer = m_layers[layer_id]; LayerRegion *layerm = layer->m_regions[region_id]; LayerRegion *other_layerm = layer->m_regions[other_region_id]; - if (layerm == nullptr || other_layerm == nullptr) + if (layerm == nullptr || other_layerm == nullptr || other_layerm->slices.empty() || expolygons_by_layer[layer_id].empty()) continue; Polygons other_slices = to_polygons(other_layerm->slices); ExPolygons my_parts = intersection_ex(other_slices, to_polygons(expolygons_by_layer[layer_id])); @@ -1752,46 +1785,127 @@ end: BOOST_LOG_TRIVIAL(debug) << "Slicing objects - make_slices in parallel - end"; } -std::vector PrintObject::_slice_region(size_t region_id, const std::vector &z, bool modifier) +// To be used only if there are no layer span specific configurations applied, which would lead to z ranges being generated for this region. +std::vector PrintObject::slice_region(size_t region_id, const std::vector &z) const { - std::vector volumes; + std::vector volumes; if (region_id < this->region_volumes.size()) { - for (int volume_id : this->region_volumes[region_id]) { - const ModelVolume *volume = this->model_object()->volumes[volume_id]; - if (modifier ? volume->is_modifier() : volume->is_model_part()) - volumes.emplace_back(volume); - } + for (const std::pair &volume_and_range : this->region_volumes[region_id]) { + const ModelVolume *volume = this->model_object()->volumes[volume_and_range.second]; + if (volume->is_model_part()) + volumes.emplace_back(volume); + } } - return this->_slice_volumes(z, volumes); + return this->slice_volumes(z, volumes); } -std::vector PrintObject::slice_support_enforcers() const +// Z ranges are not applicable to modifier meshes, therefore a sinle volume will be found in volume_and_range at most once. +std::vector PrintObject::slice_modifiers(size_t region_id, const std::vector &slice_zs) const +{ + std::vector out; + if (region_id < this->region_volumes.size()) + { + std::vector> volume_ranges; + const std::vector> &volumes_and_ranges = this->region_volumes[region_id]; + volume_ranges.reserve(volumes_and_ranges.size()); + for (size_t i = 0; i < volumes_and_ranges.size(); ) { + int volume_id = volumes_and_ranges[i].second; + const ModelVolume *model_volume = this->model_object()->volumes[volume_id]; + if (model_volume->is_modifier()) { + std::vector ranges; + ranges.emplace_back(volumes_and_ranges[i].first); + size_t j = i + 1; + for (; j < volumes_and_ranges.size() && volume_id == volumes_and_ranges[j].second; ++ j) { + if (! ranges.empty() && std::abs(ranges.back().second - volumes_and_ranges[j].first.first) < EPSILON) + ranges.back().second = volumes_and_ranges[j].first.second; + else + ranges.emplace_back(volumes_and_ranges[j].first); + } + volume_ranges.emplace_back(std::move(ranges)); + i = j; + } else + ++ i; + } + + if (! volume_ranges.empty()) + { + bool equal_ranges = true; + for (size_t i = 1; i < volume_ranges.size(); ++ i) { + assert(! volume_ranges[i].empty()); + if (volume_ranges.front() != volume_ranges[i]) { + equal_ranges = false; + break; + } + } + + if (equal_ranges && volume_ranges.front().size() == 1 && volume_ranges.front().front() == t_layer_height_range(0, DBL_MAX)) { + // No modifier in this region was split to layer spans. + std::vector volumes; + for (const std::pair &volume_and_range : this->region_volumes[region_id]) { + const ModelVolume *volume = this->model_object()->volumes[volume_and_range.second]; + if (volume->is_modifier()) + volumes.emplace_back(volume); + } + out = this->slice_volumes(slice_zs, volumes); + } else { + // Some modifier in this region was split to layer spans. + std::vector merge; + for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { + const std::vector> &volumes_and_ranges = this->region_volumes[region_id]; + for (size_t i = 0; i < volumes_and_ranges.size(); ) { + int volume_id = volumes_and_ranges[i].second; + const ModelVolume *model_volume = this->model_object()->volumes[volume_id]; + if (model_volume->is_modifier()) { + BOOST_LOG_TRIVIAL(debug) << "Slicing modifiers - volume " << volume_id; + // Find the ranges of this volume. Ranges in volumes_and_ranges must not overlap for a single volume. + std::vector ranges; + ranges.emplace_back(volumes_and_ranges[i].first); + size_t j = i + 1; + for (; j < volumes_and_ranges.size() && volume_id == volumes_and_ranges[j].second; ++ j) + ranges.emplace_back(volumes_and_ranges[j].first); + // slicing in parallel + std::vector this_slices = this->slice_volume(slice_zs, ranges, *model_volume); + if (out.empty()) { + out = std::move(this_slices); + merge.assign(out.size(), false); + } else { + for (size_t i = 0; i < out.size(); ++ i) + if (! this_slices[i].empty()) + if (! out[i].empty()) { + append(out[i], this_slices[i]); + merge[i] = true; + } else + out[i] = std::move(this_slices[i]); + } + i = j; + } else + ++ i; + } + } + for (size_t i = 0; i < merge.size(); ++ i) + if (merge[i]) + out[i] = union_ex(out[i]); + } + } + } + + return out; +} + +std::vector PrintObject::slice_support_volumes(const ModelVolumeType &model_volume_type) const { std::vector volumes; for (const ModelVolume *volume : this->model_object()->volumes) - if (volume->is_support_enforcer()) + if (volume->type() == model_volume_type) volumes.emplace_back(volume); std::vector zs; zs.reserve(this->layers().size()); for (const Layer *l : this->layers()) zs.emplace_back((float)l->slice_z); - return this->_slice_volumes(zs, volumes); + return this->slice_volumes(zs, volumes); } -std::vector PrintObject::slice_support_blockers() const -{ - std::vector volumes; - for (const ModelVolume *volume : this->model_object()->volumes) - if (volume->is_support_blocker()) - volumes.emplace_back(volume); - std::vector zs; - zs.reserve(this->layers().size()); - for (const Layer *l : this->layers()) - zs.emplace_back((float)l->slice_z); - return this->_slice_volumes(zs, volumes); -} - -std::vector PrintObject::_slice_volumes(const std::vector &z, const std::vector &volumes) const +std::vector PrintObject::slice_volumes(const std::vector &z, const std::vector &volumes) const { std::vector layers; if (! volumes.empty()) { @@ -1828,34 +1942,71 @@ std::vector PrintObject::_slice_volumes(const std::vector &z, return layers; } -std::vector PrintObject::_slice_volume(const std::vector &z, const ModelVolume &volume) const +std::vector PrintObject::slice_volume(const std::vector &z, const ModelVolume &volume) const { std::vector layers; - // Compose mesh. - //FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them. - TriangleMesh mesh(volume.mesh()); - mesh.transform(volume.get_matrix(), true); - if (mesh.repaired) { - //FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it. - stl_check_facets_exact(&mesh.stl); + if (! z.empty()) { + // Compose mesh. + //FIXME better to split the mesh into separate shells, perform slicing over each shell separately and then to use a Boolean operation to merge them. + TriangleMesh mesh(volume.mesh()); + mesh.transform(volume.get_matrix(), true); + if (mesh.repaired) { + //FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it. + stl_check_facets_exact(&mesh.stl); + } + if (mesh.stl.stats.number_of_facets > 0) { + mesh.transform(m_trafo, true); + // apply XY shift + mesh.translate(- unscale(m_copies_shift(0)), - unscale(m_copies_shift(1)), 0); + // perform actual slicing + TriangleMeshSlicer mslicer; + const Print *print = this->print(); + auto callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();}); + // TriangleMeshSlicer needs the shared vertices. + mesh.require_shared_vertices(); + mslicer.init(&mesh, callback); + mslicer.slice(z, float(m_config.slice_closing_radius.value), &layers, callback); + m_print->throw_if_canceled(); + } } - if (mesh.stl.stats.number_of_facets > 0) { - mesh.transform(m_trafo, true); - // apply XY shift - mesh.translate(- unscale(m_copies_shift(0)), - unscale(m_copies_shift(1)), 0); - // perform actual slicing - TriangleMeshSlicer mslicer; - const Print *print = this->print(); - auto callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();}); - // TriangleMeshSlicer needs the shared vertices. - mesh.require_shared_vertices(); - mslicer.init(&mesh, callback); - mslicer.slice(z, float(m_config.slice_closing_radius.value), &layers, callback); - m_print->throw_if_canceled(); - } return layers; } +// Filter the zs not inside the ranges. The ranges are closed at the botton and open at the top, they are sorted lexicographically and non overlapping. +std::vector PrintObject::slice_volume(const std::vector &z, const std::vector &ranges, const ModelVolume &volume) const +{ + std::vector out; + if (! z.empty() && ! ranges.empty()) { + if (ranges.size() == 1 && z.front() >= ranges.front().first && z.back() < ranges.front().second) { + // All layers fit into a single range. + out = this->slice_volume(z, volume); + } else { + std::vector z_filtered; + std::vector> n_filtered; + z_filtered.reserve(z.size()); + n_filtered.reserve(2 * ranges.size()); + size_t i = 0; + for (const t_layer_height_range &range : ranges) { + for (; i < z.size() && z[i] < range.first; ++ i) ; + size_t first = i; + for (; i < z.size() && z[i] < range.second; ++ i) + z_filtered.emplace_back(z[i]); + if (i > first) + n_filtered.emplace_back(std::make_pair(first, i)); + } + if (! n_filtered.empty()) { + std::vector layers = this->slice_volume(z_filtered, volume); + out.assign(z.size(), ExPolygons()); + i = 0; + for (const std::pair &span : n_filtered) + for (size_t j = span.first; j < span.second; ++ j) + out[j] = std::move(layers[i ++]); + } + } + } + return out; +} + std::string PrintObject::_fix_slicing_errors() { // Collect layers with slicing errors. @@ -2119,7 +2270,7 @@ void PrintObject::clip_fill_surfaces() //Should the pw not be half of the current value? float pw = FLT_MAX; for (const LayerRegion *layerm : layer->m_regions) - pw = std::min(pw, layerm->flow(frPerimeter).scaled_width()); + pw = std::min(pw, (float)layerm->flow(frPerimeter).scaled_width()); // Append such thick perimeters to the areas that need support polygons_append(overhangs, offset2(perimeters, -pw, +pw)); } @@ -2154,7 +2305,7 @@ void PrintObject::discover_horizontal_shells() BOOST_LOG_TRIVIAL(trace) << "discover_horizontal_shells()"; for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { - for (int i = 0; i < int(m_layers.size()); ++ i) { + for (size_t i = 0; i < m_layers.size(); ++ i) { m_print->throw_if_canceled(); LayerRegion *layerm = m_layers[i]->regions()[region_id]; const PrintRegionConfig ®ion_config = layerm->region()->config(); @@ -2171,7 +2322,7 @@ void PrintObject::discover_horizontal_shells() if (region_config.ensure_vertical_shell_thickness.value) continue; - for (int idx_surface_type = 0; idx_surface_type < 3; ++ idx_surface_type) { + for (size_t idx_surface_type = 0; idx_surface_type < 3; ++ idx_surface_type) { m_print->throw_if_canceled(); SurfaceType type = (idx_surface_type == 0) ? stTop : (idx_surface_type == 1) ? stBottom : stBottomBridge; // Find slices of current type for current layer. @@ -2200,7 +2351,7 @@ void PrintObject::discover_horizontal_shells() // Slic3r::debugf "Layer %d has %s surfaces\n", $i, ($type == S_TYPE_TOP) ? 'top' : 'bottom'; size_t solid_layers = (type == stTop) ? region_config.top_solid_layers.value : region_config.bottom_solid_layers.value; - for (int n = (type == stTop) ? i-1 : i+1; std::abs(n - i) < solid_layers; (type == stTop) ? -- n : ++ n) { + for (int n = (type == stTop) ? i-1 : i+1; std::abs(n - (int)i) < solid_layers; (type == stTop) ? -- n : ++ n) { if (n < 0 || n >= int(m_layers.size())) continue; // Slic3r::debugf " looking for neighbors on layer %d...\n", $n; @@ -2345,7 +2496,7 @@ void PrintObject::combine_infill() // Work on each region separately. for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { const PrintRegion *region = this->print()->regions()[region_id]; - const int every = region->config().infill_every_layers.value; + const size_t every = region->config().infill_every_layers.value; if (every < 2 || region->config().fill_density == 0.) continue; // Limit the number of combined layers to the maximum height allowed by this regions' nozzle. diff --git a/src/libslic3r/SLA/SLAAutoSupports.cpp b/src/libslic3r/SLA/SLAAutoSupports.cpp index 0a2537b91..91b7dfe5d 100644 --- a/src/libslic3r/SLA/SLAAutoSupports.cpp +++ b/src/libslic3r/SLA/SLAAutoSupports.cpp @@ -59,8 +59,6 @@ SLAAutoSupports::SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3 void SLAAutoSupports::project_onto_mesh(std::vector& points) const { // The function makes sure that all the points are really exactly placed on the mesh. - igl::Hit hit_up{0, 0, 0.f, 0.f, 0.f}; - igl::Hit hit_down{0, 0, 0.f, 0.f, 0.f}; // Use a reasonable granularity to account for the worker thread synchronization cost. tbb::parallel_for(tbb::blocked_range(0, points.size(), 64), @@ -140,7 +138,6 @@ static std::vector make_layers( SLAAutoSupports::MyLayer &layer_above = layers[layer_id]; SLAAutoSupports::MyLayer &layer_below = layers[layer_id - 1]; //FIXME WTF? - const float height = (layer_id>2 ? heights[layer_id-3] : heights[0]-(heights[1]-heights[0])); const float layer_height = (layer_id!=0 ? heights[layer_id]-heights[layer_id-1] : heights[0]); const float safe_angle = 5.f * (float(M_PI)/180.f); // smaller number - less supports const float between_layers_offset = float(scale_(layer_height / std::tan(safe_angle))); @@ -212,7 +209,7 @@ void SLAAutoSupports::process(const std::vector& slices, const std:: for (Structure &top : layer_top->islands) for (Structure::Link &bottom_link : top.islands_below) { Structure &bottom = *bottom_link.island; - float centroids_dist = (bottom.centroid - top.centroid).norm(); + //float centroids_dist = (bottom.centroid - top.centroid).norm(); // Penalization resulting from centroid offset: // bottom.supports_force *= std::min(1.f, 1.f - std::min(1.f, (1600.f * layer_height) * centroids_dist * centroids_dist / bottom.area)); float &support_force = support_force_bottom[&bottom - layer_bottom->islands.data()]; @@ -239,7 +236,7 @@ void SLAAutoSupports::process(const std::vector& slices, const std:: // s.supports_force_inherited /= std::max(1.f, (layer_height / 0.3f) * e_area / s.area); s.supports_force_inherited /= std::max(1.f, 0.17f * (s.overhangs_area) / s.area); - float force_deficit = s.support_force_deficit(m_config.tear_pressure()); + //float force_deficit = s.support_force_deficit(m_config.tear_pressure()); if (s.islands_below.empty()) { // completely new island - needs support no doubt uniformly_cover({ *s.polygon }, s, point_grid, true); } else if (! s.dangling_areas.empty()) { @@ -380,7 +377,7 @@ static inline std::vector poisson_disk_from_samples(const std::vector -//#include -//#include "SVG.hpp" +// #include +// #include +// #include "SVG.hpp" namespace Slic3r { namespace sla { @@ -184,9 +184,10 @@ Contour3D walls(const Polygon& lower, const Polygon& upper, } /// Offsetting with clipper and smoothing the edges into a curvature. -void offset(ExPolygon& sh, coord_t distance) { +void offset(ExPolygon& sh, coord_t distance, bool edgerounding = true) { using ClipperLib::ClipperOffset; using ClipperLib::jtRound; + using ClipperLib::jtMiter; using ClipperLib::etClosedPolygon; using ClipperLib::Paths; using ClipperLib::Path; @@ -203,11 +204,13 @@ void offset(ExPolygon& sh, coord_t distance) { return; } + auto jointype = edgerounding? jtRound : jtMiter; + ClipperOffset offs; offs.ArcTolerance = scaled(0.01); Paths result; - offs.AddPath(ctour, jtRound, etClosedPolygon); - offs.AddPaths(holes, jtRound, etClosedPolygon); + offs.AddPath(ctour, jointype, etClosedPolygon); + offs.AddPaths(holes, jointype, etClosedPolygon); offs.Execute(result, static_cast(distance)); // Offsetting reverts the orientation and also removes the last vertex @@ -237,6 +240,50 @@ void offset(ExPolygon& sh, coord_t distance) { } } +void offset(Polygon &sh, coord_t distance, bool edgerounding = true) +{ + using ClipperLib::ClipperOffset; + using ClipperLib::jtRound; + using ClipperLib::jtMiter; + using ClipperLib::etClosedPolygon; + using ClipperLib::Paths; + using ClipperLib::Path; + + auto &&ctour = Slic3rMultiPoint_to_ClipperPath(sh); + + // If the input is not at least a triangle, we can not do this algorithm + if (ctour.size() < 3) { + BOOST_LOG_TRIVIAL(error) << "Invalid geometry for offsetting!"; + return; + } + + ClipperOffset offs; + offs.ArcTolerance = 0.01 * scaled(1.); + Paths result; + offs.AddPath(ctour, edgerounding ? jtRound : jtMiter, etClosedPolygon); + offs.Execute(result, static_cast(distance)); + + // Offsetting reverts the orientation and also removes the last vertex + // so boost will not have a closed polygon. + + bool found_the_contour = false; + for (auto &r : result) { + if (ClipperLib::Orientation(r)) { + // We don't like if the offsetting generates more than one contour + // but throwing would be an overkill. Instead, we should warn the + // caller about the inability to create correct geometries + if (!found_the_contour) { + auto rr = ClipperPath_to_Slic3rPolygon(r); + sh.points.swap(rr.points); + found_the_contour = true; + } else { + BOOST_LOG_TRIVIAL(warning) + << "Warning: offsetting result is invalid!"; + } + } + } +} + /// Unification of polygons (with clipper) preserving holes as well. ExPolygons unify(const ExPolygons& shapes) { using ClipperLib::ptSubject; @@ -307,6 +354,116 @@ ExPolygons unify(const ExPolygons& shapes) { return retv; } +Polygons unify(const Polygons& shapes) { + using ClipperLib::ptSubject; + + bool closed = true; + bool valid = true; + + ClipperLib::Clipper clipper; + + for(auto& path : shapes) { + auto clipperpath = Slic3rMultiPoint_to_ClipperPath(path); + + if(!clipperpath.empty()) + valid &= clipper.AddPath(clipperpath, ptSubject, closed); + } + + if(!valid) BOOST_LOG_TRIVIAL(warning) << "Unification of invalid shapes!"; + + ClipperLib::Paths result; + clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNonZero); + + Polygons ret; + for (ClipperLib::Path &p : result) { + Polygon pp = ClipperPath_to_Slic3rPolygon(p); + if (!pp.is_clockwise()) ret.emplace_back(std::move(pp)); + } + + return ret; +} + +// Function to cut tiny connector cavities for a given polygon. The input poly +// will be offsetted by "padding" and small rectangle shaped cavities will be +// inserted along the perimeter in every "stride" distance. The stick rectangles +// will have a with about "stick_width". The input dimensions are in world +// measure, not the scaled clipper units. +void breakstick_holes(ExPolygon& poly, + double padding, + double stride, + double stick_width, + double penetration) +{ + // SVG svg("bridgestick_plate.svg"); + // svg.draw(poly); + + auto transf = [stick_width, penetration, padding, stride](Points &pts) { + // The connector stick will be a small rectangle with dimensions + // stick_width x (penetration + padding) to have some penetration + // into the input polygon. + + Points out; + out.reserve(2 * pts.size()); // output polygon points + + // stick bottom and right edge dimensions + double sbottom = scaled(stick_width); + double sright = scaled(penetration + padding); + + // scaled stride distance + double sstride = scaled(stride); + double t = 0; + + // process pairs of vertices as an edge, start with the last and + // first point + for (size_t i = pts.size() - 1, j = 0; j < pts.size(); i = j, ++j) { + // Get vertices and the direction vectors + const Point &a = pts[i], &b = pts[j]; + Vec2d dir = b.cast() - a.cast(); + double nrm = dir.norm(); + dir /= nrm; + Vec2d dirp(-dir(Y), dir(X)); + + // Insert start point + out.emplace_back(a); + + // dodge the start point, do not make sticks on the joins + while (t < sbottom) t += sbottom; + double tend = nrm - sbottom; + + while (t < tend) { // insert the stick on the polygon perimeter + + // calculate the stick rectangle vertices and insert them + // into the output. + Point p1 = a + (t * dir).cast(); + Point p2 = p1 + (sright * dirp).cast(); + Point p3 = p2 + (sbottom * dir).cast(); + Point p4 = p3 + (sright * -dirp).cast(); + out.insert(out.end(), {p1, p2, p3, p4}); + + // continue along the perimeter + t += sstride; + } + + t = t - nrm; + + // Insert edge endpoint + out.emplace_back(b); + } + + // move the new points + out.shrink_to_fit(); + pts.swap(out); + }; + + if(stride > 0.0 && stick_width > 0.0 && padding > 0.0) { + transf(poly.contour.points); + for (auto &h : poly.holes) transf(h.points); + } + + // svg.draw(poly); + // svg.Close(); +} + /// This method will create a rounded edge around a flat polygon in 3d space. /// 'base_plate' parameter is the target plate. /// 'radius' is the radius of the edges. @@ -426,41 +583,38 @@ inline Point centroid(Points& pp) { return c; } -inline Point centroid(const ExPolygon& poly) { - return poly.contour.centroid(); +inline Point centroid(const Polygon& poly) { + return poly.centroid(); } /// A fake concave hull that is constructed by connecting separate shapes /// with explicit bridges. Bridges are generated from each shape's centroid /// to the center of the "scene" which is the centroid calculated from the shape /// centroids (a star is created...) -ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50, - ThrowOnCancel throw_on_cancel = [](){}) +Polygons concave_hull(const Polygons& polys, double max_dist_mm = 50, + ThrowOnCancel throw_on_cancel = [](){}) { namespace bgi = boost::geometry::index; - using SpatElement = std::pair; + using SpatElement = std::pair; using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; - if(polys.empty()) return ExPolygons(); + if(polys.empty()) return Polygons(); + + const double max_dist = scaled(max_dist_mm); - ExPolygons punion = unify(polys); // could be redundant + Polygons punion = unify(polys); // could be redundant if(punion.size() == 1) return punion; // We get the centroids of all the islands in the 2D slice Points centroids; centroids.reserve(punion.size()); std::transform(punion.begin(), punion.end(), std::back_inserter(centroids), - [](const ExPolygon& poly) { return centroid(poly); }); - - - SpatIndex boxindex; unsigned idx = 0; - std::for_each(punion.begin(), punion.end(), - [&boxindex, &idx](const ExPolygon& expo) { - BoundingBox bb(expo); - boxindex.insert(std::make_pair(bb, idx++)); - }); - + [](const Polygon& poly) { return centroid(poly); }); + SpatIndex ctrindex; + unsigned idx = 0; + for(const Point &ct : centroids) ctrindex.insert(std::make_pair(ct, idx++)); + // Centroid of the centroids of islands. This is where the additional // connector sticks are routed. Point cc = centroid(centroids); @@ -470,25 +624,32 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50, idx = 0; std::transform(centroids.begin(), centroids.end(), std::back_inserter(punion), - [&punion, &boxindex, cc, max_dist_mm, &idx, throw_on_cancel] + [¢roids, &ctrindex, cc, max_dist, &idx, throw_on_cancel] (const Point& c) { throw_on_cancel(); double dx = x(c) - x(cc), dy = y(c) - y(cc); double l = std::sqrt(dx * dx + dy * dy); double nx = dx / l, ny = dy / l; - double max_dist = scaled(max_dist_mm); - - ExPolygon& expo = punion[idx++]; - BoundingBox querybb(expo); - - querybb.offset(max_dist); + + Point& ct = centroids[idx]; + std::vector result; - boxindex.query(bgi::intersects(querybb), std::back_inserter(result)); - if(result.size() <= 1) return ExPolygon(); + ctrindex.query(bgi::nearest(ct, 2), std::back_inserter(result)); - ExPolygon r; - auto& ctour = r.contour.points; + double dist = max_dist; + for (const SpatElement &el : result) + if (el.second != idx) { + dist = Line(el.first, ct).length(); + break; + } + + idx++; + + if (dist >= max_dist) return Polygon(); + + Polygon r; + auto& ctour = r.points; ctour.reserve(3); ctour.emplace_back(cc); @@ -507,24 +668,20 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50, return punion; } -void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h, - float layerh, ThrowOnCancel thrfn) +void base_plate(const TriangleMesh & mesh, + ExPolygons & output, + const std::vector &heights, + ThrowOnCancel thrfn) { - TriangleMesh m = mesh; - m.require_shared_vertices(); // TriangleMeshSlicer needs this - TriangleMeshSlicer slicer(&m); - - auto bb = mesh.bounding_box(); - float gnd = float(bb.min(Z)); - std::vector heights = {float(bb.min(Z))}; - for(float hi = gnd + layerh; hi <= gnd + h; hi += layerh) - heights.emplace_back(hi); - - std::vector out; out.reserve(size_t(std::ceil(h/layerh))); + if (mesh.empty()) return; + // m.require_shared_vertices(); // TriangleMeshSlicer needs this + TriangleMeshSlicer slicer(&mesh); + + std::vector out; out.reserve(heights.size()); slicer.slice(heights, 0.f, &out, thrfn); - + size_t count = 0; for(auto& o : out) count += o.size(); - + // Now we have to unify all slice layers which can be an expensive operation // so we will try to simplify the polygons ExPolygons tmp; tmp.reserve(count); @@ -533,16 +690,33 @@ void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h, auto&& exss = e.simplify(scaled(0.1)); for(ExPolygon& ep : exss) tmp.emplace_back(std::move(ep)); } - + ExPolygons utmp = unify(tmp); - + for(auto& o : utmp) { auto&& smp = o.simplify(scaled(0.1)); output.insert(output.end(), smp.begin(), smp.end()); } } -Contour3D create_base_pool(const ExPolygons &ground_layer, +void base_plate(const TriangleMesh &mesh, + ExPolygons & output, + float h, + float layerh, + ThrowOnCancel thrfn) +{ + auto bb = mesh.bounding_box(); + float gnd = float(bb.min(Z)); + std::vector heights = {float(bb.min(Z))}; + + for(float hi = gnd + layerh; hi <= gnd + h; hi += layerh) + heights.emplace_back(hi); + + base_plate(mesh, output, heights, thrfn); +} + +Contour3D create_base_pool(const Polygons &ground_layer, + const ExPolygons &obj_self_pad = {}, const PoolConfig& cfg = PoolConfig()) { // for debugging: @@ -557,7 +731,7 @@ Contour3D create_base_pool(const ExPolygons &ground_layer, // serve as the bottom plate of the pad. We will offset this concave hull // and then offset back the result with clipper with rounding edges ON. This // trick will create a nice rounded pad shape. - ExPolygons concavehs = concave_hull(ground_layer, mergedist, cfg.throw_on_cancel); + Polygons concavehs = concave_hull(ground_layer, mergedist, cfg.throw_on_cancel); const double thickness = cfg.min_wall_thickness_mm; const double wingheight = cfg.min_wall_height_mm; @@ -577,42 +751,37 @@ Contour3D create_base_pool(const ExPolygons &ground_layer, Contour3D pool; - for(ExPolygon& concaveh : concavehs) { - if(concaveh.contour.points.empty()) return pool; - - // Get rid of any holes in the concave hull output. - concaveh.holes.clear(); + for(Polygon& concaveh : concavehs) { + if(concaveh.points.empty()) return pool; // Here lies the trick that does the smoothing only with clipper offset // calls. The offset is configured to round edges. Inner edges will // be rounded because we offset twice: ones to get the outer (top) plate // and again to get the inner (bottom) plate auto outer_base = concaveh; - outer_base.holes.clear(); offset(outer_base, s_safety_dist + s_wingdist + s_thickness); - ExPolygon bottom_poly = outer_base; - bottom_poly.holes.clear(); + ExPolygon bottom_poly; bottom_poly.contour = outer_base; offset(bottom_poly, -s_bottom_offs); // Punching a hole in the top plate for the cavity ExPolygon top_poly; ExPolygon middle_base; ExPolygon inner_base; - top_poly.contour = outer_base.contour; + top_poly.contour = outer_base; if(wingheight > 0) { - inner_base = outer_base; + inner_base.contour = outer_base; offset(inner_base, -(s_thickness + s_wingdist + s_eradius)); - middle_base = outer_base; + middle_base.contour = outer_base; offset(middle_base, -s_thickness); top_poly.holes.emplace_back(middle_base.contour); auto& tph = top_poly.holes.back().points; std::reverse(tph.begin(), tph.end()); } - ExPolygon ob = outer_base; double wh = 0; + ExPolygon ob; ob.contour = outer_base; double wh = 0; // now we will calculate the angle or portion of the circle from // pi/2 that will connect perfectly with the bottom plate. @@ -659,6 +828,7 @@ Contour3D create_base_pool(const ExPolygons &ground_layer, if(wingheight > 0) { // Generate the smoothed edge geometry wh = 0; + ob = middle_base; if(s_eradius) pool.merge(round_edges(middle_base, r, phi - 90, // from tangent lines @@ -673,11 +843,59 @@ Contour3D create_base_pool(const ExPolygons &ground_layer, wh, -wingdist, thrcl)); } - // Now we need to triangulate the top and bottom plates as well as the - // cavity bottom plate which is the same as the bottom plate but it is - // elevated by the thickness. + if (cfg.embed_object) { + ExPolygons bttms = diff_ex(to_polygons(bottom_poly), + to_polygons(obj_self_pad)); + + assert(!bttms.empty()); + + std::sort(bttms.begin(), bttms.end(), + [](const ExPolygon& e1, const ExPolygon& e2) { + return e1.contour.area() > e2.contour.area(); + }); + + if(wingheight > 0) inner_base.holes = bttms.front().holes; + else top_poly.holes = bttms.front().holes; + + auto straight_walls = + [&pool](const Polygon &cntr, coord_t z_low, coord_t z_high) { + + auto lines = cntr.lines(); + + for (auto &l : lines) { + auto s = coord_t(pool.points.size()); + auto& pts = pool.points; + pts.emplace_back(unscale(l.a.x(), l.a.y(), z_low)); + pts.emplace_back(unscale(l.b.x(), l.b.y(), z_low)); + pts.emplace_back(unscale(l.a.x(), l.a.y(), z_high)); + pts.emplace_back(unscale(l.b.x(), l.b.y(), z_high)); + + pool.indices.emplace_back(s, s + 1, s + 3); + pool.indices.emplace_back(s, s + 3, s + 2); + } + }; + + coord_t z_lo = -scaled(fullheight), z_hi = -scaled(wingheight); + for (ExPolygon &ep : bttms) { + pool.merge(triangulate_expolygon_3d(ep, -fullheight, true)); + for (auto &h : ep.holes) straight_walls(h, z_lo, z_hi); + } + + // Skip the outer contour, triangulate the holes + for (auto it = std::next(bttms.begin()); it != bttms.end(); ++it) { + pool.merge(triangulate_expolygon_3d(*it, -wingheight)); + straight_walls(it->contour, z_lo, z_hi); + } + + } else { + // Now we need to triangulate the top and bottom plates as well as + // the cavity bottom plate which is the same as the bottom plate + // but it is elevated by the thickness. + + pool.merge(triangulate_expolygon_3d(bottom_poly, -fullheight, true)); + } + pool.merge(triangulate_expolygon_3d(top_poly)); - pool.merge(triangulate_expolygon_3d(bottom_poly, -fullheight, true)); if(wingheight > 0) pool.merge(triangulate_expolygon_3d(inner_base, -wingheight)); @@ -687,8 +905,8 @@ Contour3D create_base_pool(const ExPolygons &ground_layer, return pool; } -void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out, - const PoolConfig& cfg) +void create_base_pool(const Polygons &ground_layer, TriangleMesh& out, + const ExPolygons &holes, const PoolConfig& cfg) { @@ -698,7 +916,7 @@ void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out, // std::fstream fout("pad_debug.obj", std::fstream::out); // if(fout.good()) pool.to_obj(fout); - out.merge(mesh(create_base_pool(ground_layer, cfg))); + out.merge(mesh(create_base_pool(ground_layer, holes, cfg))); } } diff --git a/src/libslic3r/SLA/SLABasePool.hpp b/src/libslic3r/SLA/SLABasePool.hpp index 3c88e58c8..67b9ccdcb 100644 --- a/src/libslic3r/SLA/SLABasePool.hpp +++ b/src/libslic3r/SLA/SLABasePool.hpp @@ -8,7 +8,9 @@ namespace Slic3r { class ExPolygon; +class Polygon; using ExPolygons = std::vector; +using Polygons = std::vector; class TriangleMesh; @@ -19,16 +21,40 @@ using ThrowOnCancel = std::function; /// Calculate the polygon representing the silhouette from the specified height void base_plate(const TriangleMesh& mesh, // input mesh ExPolygons& output, // Output will be merged with - float zlevel = 0.1f, // Plate creation level + float samplingheight = 0.1f, // The height range to sample float layerheight = 0.05f, // The sampling height ThrowOnCancel thrfn = [](){}); // Will be called frequently +void base_plate(const TriangleMesh& mesh, // input mesh + ExPolygons& output, // Output will be merged with + const std::vector&, // Exact Z levels to sample + ThrowOnCancel thrfn = [](){}); // Will be called frequently + +// Function to cut tiny connector cavities for a given polygon. The input poly +// will be offsetted by "padding" and small rectangle shaped cavities will be +// inserted along the perimeter in every "stride" distance. The stick rectangles +// will have a with about "stick_width". The input dimensions are in world +// measure, not the scaled clipper units. +void breakstick_holes(ExPolygon &poly, + double padding, + double stride, + double stick_width, + double penetration = 0.0); + struct PoolConfig { double min_wall_thickness_mm = 2; double min_wall_height_mm = 5; double max_merge_distance_mm = 50; double edge_radius_mm = 1; double wall_slope = std::atan(1.0); // Universal constant for Pi/4 + struct EmbedObject { + double object_gap_mm = 0.5; + double stick_stride_mm = 10; + double stick_width_mm = 0.3; + double stick_penetration_mm = 0.1; + bool enabled = false; + operator bool() const { return enabled; } + } embed_object; ThrowOnCancel throw_on_cancel = [](){}; @@ -42,15 +68,12 @@ struct PoolConfig { }; /// Calculate the pool for the mesh for SLA printing -void create_base_pool(const ExPolygons& base_plate, +void create_base_pool(const Polygons& base_plate, TriangleMesh& output_mesh, + const ExPolygons& holes, const PoolConfig& = PoolConfig()); -/// TODO: Currently the base plate of the pool will have half the height of the -/// whole pool. So the carved out space has also half the height. This is not -/// a particularly elegant solution, the thickness should be exactly -/// min_wall_thickness and it should be corrected in the future. This method -/// will return the correct value for further processing. +/// Returns the elevation needed for compensating the pad. inline double get_pad_elevation(const PoolConfig& cfg) { return cfg.min_wall_thickness_mm; } diff --git a/src/libslic3r/SLA/SLACommon.hpp b/src/libslic3r/SLA/SLACommon.hpp index 855802759..874388e05 100644 --- a/src/libslic3r/SLA/SLACommon.hpp +++ b/src/libslic3r/SLA/SLACommon.hpp @@ -43,6 +43,8 @@ struct SupportPoint { bool operator==(const SupportPoint& sp) const { return (pos==sp.pos) && head_front_radius==sp.head_front_radius && is_new_island==sp.is_new_island; } bool operator!=(const SupportPoint& sp) const { return !(sp == (*this)); } + + template void serialize(Archive &ar) { ar(pos, head_front_radius, is_new_island); } }; /// An index-triangle structure for libIGL functions. Also serves as an @@ -60,7 +62,7 @@ class EigenMesh3D { Eigen::MatrixXd m_V; Eigen::MatrixXi m_F; - double m_ground_level = 0; + double m_ground_level = 0, m_gnd_offset = 0; std::unique_ptr m_aabb; public: @@ -71,7 +73,9 @@ public: ~EigenMesh3D(); - inline double ground_level() const { return m_ground_level; } + inline double ground_level() const { return m_ground_level + m_gnd_offset; } + inline void ground_level_offset(double o) { m_gnd_offset = o; } + inline double ground_level_offset() const { return m_gnd_offset; } inline const Eigen::MatrixXd& V() const { return m_V; } inline const Eigen::MatrixXi& F() const { return m_F; } @@ -149,6 +153,12 @@ public: #endif /* SLIC3R_SLA_NEEDS_WINDTREE */ double squared_distance(const Vec3d& p, int& i, Vec3d& c) const; + inline double squared_distance(const Vec3d &p) const + { + int i; + Vec3d c; + return squared_distance(p, i, c); + } }; diff --git a/src/libslic3r/SLA/SLASpatIndex.hpp b/src/libslic3r/SLA/SLASpatIndex.hpp index e5fbfa7d4..90dcdc362 100644 --- a/src/libslic3r/SLA/SLASpatIndex.hpp +++ b/src/libslic3r/SLA/SLASpatIndex.hpp @@ -7,13 +7,15 @@ #include +#include + namespace Slic3r { namespace sla { typedef Eigen::Matrix Vec3d; -using SpatElement = std::pair; +using PointIndexEl = std::pair; -class SpatIndex { +class PointIndex { class Impl; // We use Pimpl because it takes a long time to compile boost headers which @@ -21,30 +23,67 @@ class SpatIndex { std::unique_ptr m_impl; public: - SpatIndex(); - ~SpatIndex(); + PointIndex(); + ~PointIndex(); - SpatIndex(const SpatIndex&); - SpatIndex(SpatIndex&&); - SpatIndex& operator=(const SpatIndex&); - SpatIndex& operator=(SpatIndex&&); + PointIndex(const PointIndex&); + PointIndex(PointIndex&&); + PointIndex& operator=(const PointIndex&); + PointIndex& operator=(PointIndex&&); - void insert(const SpatElement&); - bool remove(const SpatElement&); + void insert(const PointIndexEl&); + bool remove(const PointIndexEl&); inline void insert(const Vec3d& v, unsigned idx) { insert(std::make_pair(v, unsigned(idx))); } - std::vector query(std::function); - std::vector nearest(const Vec3d&, unsigned k); + std::vector query(std::function); + std::vector nearest(const Vec3d&, unsigned k); // For testing size_t size() const; bool empty() const { return size() == 0; } - void foreach(std::function fn); + void foreach(std::function fn); +}; + +using BoxIndexEl = std::pair; + +class BoxIndex { + class Impl; + + // We use Pimpl because it takes a long time to compile boost headers which + // is the engine of this class. We include it only in the cpp file. + std::unique_ptr m_impl; +public: + + BoxIndex(); + ~BoxIndex(); + + BoxIndex(const BoxIndex&); + BoxIndex(BoxIndex&&); + BoxIndex& operator=(const BoxIndex&); + BoxIndex& operator=(BoxIndex&&); + + void insert(const BoxIndexEl&); + inline void insert(const BoundingBox& bb, unsigned idx) + { + insert(std::make_pair(bb, unsigned(idx))); + } + + bool remove(const BoxIndexEl&); + + enum QueryType { qtIntersects, qtWithin }; + + std::vector query(const BoundingBox&, QueryType qt); + + // For testing + size_t size() const; + bool empty() const { return size() == 0; } + + void foreach(std::function fn); }; } diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index ae033c62f..4751dad6c 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -9,10 +9,12 @@ #include "SLASpatIndex.hpp" #include "SLABasePool.hpp" +#include #include #include #include +#include #include #include #include @@ -413,7 +415,7 @@ struct Pillar { assert(steps > 0); height = jp(Z) - endp(Z); - if(height > 0) { // Endpoint is below the starting point + if(height > EPSILON) { // Endpoint is below the starting point // We just create a bridge geometry with the pillar parameters and // move the data. @@ -528,6 +530,7 @@ struct CompactBridge { const Vec3d& ep, const Vec3d& n, double r, + bool endball = true, size_t steps = 45) { Vec3d startp = sp + r * n; @@ -541,12 +544,14 @@ struct CompactBridge { double fa = 2*PI/steps; auto upperball = sphere(r, Portion{PI / 2 - fa, PI}, fa); for(auto& p : upperball.points) p += startp; - - auto lowerball = sphere(r, Portion{0, PI/2 + 2*fa}, fa); - for(auto& p : lowerball.points) p += endp; - + + if(endball) { + auto lowerball = sphere(r, Portion{0, PI/2 + 2*fa}, fa); + for(auto& p : lowerball.points) p += endp; + mesh.merge(lowerball); + } + mesh.merge(upperball); - mesh.merge(lowerball); } }; @@ -556,28 +561,111 @@ struct Pad { PoolConfig cfg; double zlevel = 0; - Pad() {} + Pad() = default; - Pad(const TriangleMesh& object_support_mesh, - const ExPolygons& baseplate, + Pad(const TriangleMesh& support_mesh, + const ExPolygons& modelbase, double ground_level, const PoolConfig& pcfg) : cfg(pcfg), - zlevel(ground_level + - (sla::get_pad_fullheight(pcfg) - sla::get_pad_elevation(pcfg)) ) + zlevel(ground_level + + sla::get_pad_fullheight(pcfg) - + sla::get_pad_elevation(pcfg)) { - ExPolygons basep; - cfg.throw_on_cancel(); + Polygons basep; + auto &thr = cfg.throw_on_cancel; + + thr(); + + // Get a sample for the pad from the support mesh + { + ExPolygons platetmp; - // The 0.1f is the layer height with which the mesh is sampled and then - // the layers are unified into one vector of polygons. - base_plate(object_support_mesh, basep, - float(cfg.min_wall_height_mm + cfg.min_wall_thickness_mm), - 0.1f, pcfg.throw_on_cancel); + float zstart = float(zlevel); + float zend = zstart + float(get_pad_fullheight(pcfg) + EPSILON); - for(auto& bp : baseplate) basep.emplace_back(bp); + base_plate(support_mesh, platetmp, grid(zstart, zend, 0.1f), thr); + + // We don't need no... holes control... + for (const ExPolygon &bp : platetmp) + basep.emplace_back(std::move(bp.contour)); + } + + if(pcfg.embed_object) { + + // If the zero elevation mode is ON, we need to process the model + // base silhouette. Create the offsetted version and punch the + // breaksticks across its perimeter. + + ExPolygons modelbase_offs = modelbase; + + if (pcfg.embed_object.object_gap_mm > 0.0) + modelbase_offs + = offset_ex(modelbase_offs, + float(scaled(pcfg.embed_object.object_gap_mm))); + + // Create a spatial index of the support silhouette polygons. + // This will be used to check for intersections with the model + // silhouette polygons. If there is no intersection, then a certain + // part of the pad is redundant as it does not host any supports. + BoxIndex bindex; + { + unsigned idx = 0; + for(auto &bp : basep) { + auto bb = bp.bounding_box(); + bb.offset(float(scaled(pcfg.min_wall_thickness_mm))); + bindex.insert(bb, idx++); + } + } + + // Punching the breaksticks across the offsetted polygon perimeters + ExPolygons pad_stickholes; pad_stickholes.reserve(modelbase.size()); + for(auto& poly : modelbase_offs) { + + std::vector qres = + bindex.query(poly.contour.bounding_box(), + BoxIndex::qtIntersects); + + if (!qres.empty()) { + + // The model silhouette polygon 'poly' HAS an intersection + // with the support silhouettes. Include this polygon + // in the pad holes with the breaksticks and merge the + // original (offsetted) version with the rest of the pad + // base plate. + + basep.emplace_back(poly.contour); + + // The holes of 'poly' will become positive parts of the + // pad, so they has to be checked for intersections as well + // and erased if there is no intersection with the supports + auto it = poly.holes.begin(); + while(it != poly.holes.end()) { + if (bindex.query(it->bounding_box(), + BoxIndex::qtIntersects).empty()) + it = poly.holes.erase(it); + else + ++it; + } + + // Punch the breaksticks + sla::breakstick_holes( + poly, + pcfg.embed_object.object_gap_mm, // padding + pcfg.embed_object.stick_stride_mm, + pcfg.embed_object.stick_width_mm, + pcfg.embed_object.stick_penetration_mm); + + pad_stickholes.emplace_back(poly); + } + } + + create_base_pool(basep, tmesh, pad_stickholes, cfg); + } else { + for (const ExPolygon &bp : modelbase) basep.emplace_back(bp.contour); + create_base_pool(basep, tmesh, {}, cfg); + } - create_base_pool(basep, tmesh, cfg); tmesh.translate(0, 0, float(zlevel)); } @@ -603,7 +691,7 @@ inline Vec2d to_vec2(const Vec3d& v3) { return {v3(X), v3(Y)}; } -bool operator==(const SpatElement& e1, const SpatElement& e2) { +bool operator==(const PointIndexEl& e1, const PointIndexEl& e2) { return e1.second == e2.second; } @@ -620,7 +708,7 @@ ClusteredPoints cluster(const PointSet& points, ClusteredPoints cluster( const std::vector& indices, std::function pointfn, - std::function predicate, + std::function predicate, unsigned max_points); // This class will hold the support tree meshes with some additional bookkeeping @@ -763,9 +851,9 @@ public: } const Pad& create_pad(const TriangleMesh& object_supports, - const ExPolygons& baseplate, + const ExPolygons& modelbase, const PoolConfig& cfg) { - m_pad = Pad(object_supports, baseplate, ground_level, cfg); + m_pad = Pad(object_supports, modelbase, ground_level, cfg); return m_pad; } @@ -808,7 +896,6 @@ public: merged.merge(bs.mesh); } - if(m_ctl.stopcondition()) { // In case of failure we have to return an empty mesh meshcache = TriangleMesh(); @@ -819,7 +906,7 @@ public: // The mesh will be passed by const-pointer to TriangleMeshSlicer, // which will need this. - meshcache.require_shared_vertices(); + if (!meshcache.empty()) meshcache.require_shared_vertices(); // TODO: Is this necessary? //meshcache.repair(); @@ -947,7 +1034,7 @@ class SLASupportTree::Algorithm { ThrowOnCancel m_thr; // A spatial index to easily find strong pillars to connect to. - SpatIndex m_pillar_index; + PointIndex m_pillar_index; inline double ray_mesh_intersect(const Vec3d& s, const Vec3d& dir) @@ -1149,7 +1236,7 @@ class SLASupportTree::Algorithm { auto hr = m.query_ray_hit(p + sd*dir, dir); if(ins_check && hr.is_inside()) { - if(hr.distance() > r + sd) hits[i] = HitResult(0.0); + if(hr.distance() > 2 * r + sd) hits[i] = HitResult(0.0); else { // re-cast the ray from the outside of the object auto hr2 = @@ -1264,9 +1351,12 @@ class SLASupportTree::Algorithm { // For connecting a head to a nearby pillar. bool connect_to_nearpillar(const Head& head, long nearpillar_id) { - - auto nearpillar = [this, nearpillar_id]() { return m_result.pillar(nearpillar_id); }; - if(nearpillar().bridges > m_cfg.max_bridges_on_pillar) return false; + + auto nearpillar = [this, nearpillar_id]() { + return m_result.pillar(nearpillar_id); + }; + + if (nearpillar().bridges > m_cfg.max_bridges_on_pillar) return false; Vec3d headjp = head.junction_point(); Vec3d nearjp_u = nearpillar().startpoint(); @@ -1337,7 +1427,7 @@ class SLASupportTree::Algorithm { } bool search_pillar_and_connect(const Head& head) { - SpatIndex spindex = m_pillar_index; + PointIndex spindex = m_pillar_index; long nearest_id = -1; @@ -1369,6 +1459,120 @@ class SLASupportTree::Algorithm { return nearest_id >= 0; } + + // This is a proxy function for pillar creation which will mind the gap + // between the pad and the model bottom in zero elevation mode. + void create_ground_pillar(const Vec3d &jp, + const Vec3d &sourcedir, + double radius, + int head_id = -1) + { + // People were killed for this number (seriously) + static const double SQR2 = std::sqrt(2.0); + static const Vec3d DOWN = {0.0, 0.0, -1.0}; + + double gndlvl = m_result.ground_level; + Vec3d endp = {jp(X), jp(Y), gndlvl}; + double sd = m_cfg.pillar_base_safety_distance_mm; + int pillar_id = -1; + double min_dist = sd + m_cfg.base_radius_mm + EPSILON; + double dist = 0; + bool can_add_base = true; + bool normal_mode = true; + + if (m_cfg.object_elevation_mm < EPSILON + && (dist = std::sqrt(m_mesh.squared_distance(endp))) < min_dist) { + // Get the distance from the mesh. This can be later optimized + // to get the distance in 2D plane because we are dealing with + // the ground level only. + + normal_mode = false; + double mv = min_dist - dist; + double azimuth = std::atan2(sourcedir(Y), sourcedir(X)); + double sinpolar = std::sin(PI - m_cfg.bridge_slope); + double cospolar = std::cos(PI - m_cfg.bridge_slope); + double cosazm = std::cos(azimuth); + double sinazm = std::sin(azimuth); + + auto dir = Vec3d(cosazm * sinpolar, sinazm * sinpolar, cospolar) + .normalized(); + + using namespace libnest2d::opt; + StopCriteria scr; + scr.stop_score = min_dist; + SubplexOptimizer solver(scr); + + auto result = solver.optimize_max( + [this, dir, jp, gndlvl](double mv) { + Vec3d endp = jp + SQR2 * mv * dir; + endp(Z) = gndlvl; + return std::sqrt(m_mesh.squared_distance(endp)); + }, + initvals(mv), bound(0.0, 2 * min_dist)); + + mv = std::get<0>(result.optimum); + endp = jp + SQR2 * mv * dir; + Vec3d pgnd = {endp(X), endp(Y), gndlvl}; + can_add_base = result.score > min_dist; + + double gnd_offs = m_mesh.ground_level_offset(); + auto abort_in_shame = + [gnd_offs, &normal_mode, &can_add_base, &endp, jp, gndlvl]() + { + normal_mode = true; + can_add_base = false; // Nothing left to do, hope for the best + endp = {jp(X), jp(Y), gndlvl - gnd_offs }; + }; + + // We have to check if the bridge is feasible. + if (bridge_mesh_intersect(jp, dir, radius) < (endp - jp).norm()) + abort_in_shame(); + else { + // If the new endpoint is below ground, do not make a pillar + if (endp(Z) < gndlvl) + endp = endp - SQR2 * (gndlvl - endp(Z)) * dir; // back off + else { + + auto hit = bridge_mesh_intersect(endp, DOWN, radius); + if (!std::isinf(hit.distance())) abort_in_shame(); + + Pillar &plr = m_result.add_pillar(endp, pgnd, radius); + + if (can_add_base) + plr.add_base(m_cfg.base_height_mm, + m_cfg.base_radius_mm); + + pillar_id = plr.id; + } + + m_result.add_bridge(jp, endp, radius); + m_result.add_junction(endp, radius); + + // Add a degenerated pillar and the bridge. + // The degenerate pillar will have zero length and it will + // prevent from queries of head_pillar() to have non-existing + // pillar when the head should have one. + if (head_id >= 0) + m_result.add_pillar(unsigned(head_id), jp, radius); + } + } + + if (normal_mode) { + Pillar &plr = head_id >= 0 + ? m_result.add_pillar(unsigned(head_id), + endp, + radius) + : m_result.add_pillar(jp, endp, radius); + + if (can_add_base) + plr.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); + + pillar_id = plr.id; + } + + if(pillar_id >= 0) // Save the pillar endpoint in the spatial index + m_pillar_index.insert(endp, pillar_id); + } public: @@ -1447,9 +1651,9 @@ public: // (Quaternion::FromTwoVectors) and apply the rotation to the // arrow head. - double z = n(2); - double r = 1.0; // for normalized vector - double polar = std::acos(z / r); + double z = n(2); + double r = 1.0; // for normalized vector + double polar = std::acos(z / r); double azimuth = std::atan2(n(1), n(0)); // skip if the tilt is not sane @@ -1473,14 +1677,14 @@ public: std::cos(polar)).normalized(); // check available distance - double t = pinhead_mesh_intersect( - hp, // touching point - nn, // normal - pin_r, - m_cfg.head_back_radius_mm, - w); + EigenMesh3D::hit_result t + = pinhead_mesh_intersect(hp, // touching point + nn, // normal + pin_r, + m_cfg.head_back_radius_mm, + w); - if(t <= w) { + if(t.distance() <= w) { // Let's try to optimize this angle, there might be a // viable normal that doesn't collide with the model @@ -1523,12 +1727,17 @@ public: // save the verified and corrected normal m_support_nmls.row(fidx) = nn; - if(t > w) { - // mark the point for needing a head. - m_iheads.emplace_back(fidx); - } else if( polar >= 3*PI/4 ) { - // Headless supports do not tilt like the headed ones so - // the normal should point almost to the ground. + if (t.distance() > w) { + // Check distance from ground, we might have zero elevation. + if (hp(Z) + w * nn(Z) < m_result.ground_level) { + m_iheadless.emplace_back(fidx); + } else { + // mark the point for needing a head. + m_iheads.emplace_back(fidx); + } + } else if (polar >= 3 * PI / 4) { + // Headless supports do not tilt like the headed ones + // so the normal should point almost to the ground. m_iheadless.emplace_back(fidx); } } @@ -1594,16 +1803,22 @@ public: // from each other in the XY plane to not cross their pillar bases // These clusters of support points will join in one pillar, // possibly in their centroid support point. + auto pointfn = [this](unsigned i) { return m_result.head(i).junction_point(); }; - auto predicate = [this](const SpatElement& e1, const SpatElement& e2) { + + auto predicate = [this](const PointIndexEl &e1, + const PointIndexEl &e2) { double d2d = distance(to_2d(e1.first), to_2d(e2.first)); double d3d = distance(e1.first, e2.first); - return d2d < 2 * m_cfg.base_radius_mm && - d3d < m_cfg.max_bridge_length_mm; + return d2d < 2 * m_cfg.base_radius_mm + && d3d < m_cfg.max_bridge_length_mm; }; - m_pillar_clusters = cluster(ground_head_indices, pointfn, predicate, + + m_pillar_clusters = cluster(ground_head_indices, + pointfn, + predicate, m_cfg.max_bridges_on_pillar); } @@ -1615,7 +1830,7 @@ public: void routing_to_ground() { const double pradius = m_cfg.head_back_radius_mm; - const double gndlvl = m_result.ground_level; + // const double gndlvl = m_result.ground_level; ClusterEl cl_centroids; cl_centroids.reserve(m_pillar_clusters.size()); @@ -1648,13 +1863,8 @@ public: Head& h = m_result.head(hid); h.transform(); - Vec3d p = h.junction_point(); p(Z) = gndlvl; - auto& plr = m_result.add_pillar(hid, p, h.r_back_mm) - .add_base(m_cfg.base_height_mm, - m_cfg.base_radius_mm); - // Save the pillar endpoint and the pillar id in the spatial index - m_pillar_index.insert(plr.endpoint(), unsigned(plr.id)); + create_ground_pillar(h.junction_point(), h.dir, h.r_back_mm, h.id); } // now we will go through the clusters ones again and connect the @@ -1681,15 +1891,12 @@ public: !search_pillar_and_connect(sidehead)) { Vec3d pstart = sidehead.junction_point(); - Vec3d pend = Vec3d{pstart(X), pstart(Y), gndlvl}; + //Vec3d pend = Vec3d{pstart(X), pstart(Y), gndlvl}; // Could not find a pillar, create one - auto& pillar = m_result.add_pillar(unsigned(sidehead.id), - pend, pradius) - .add_base(m_cfg.base_height_mm, - m_cfg.base_radius_mm); - - // connects to ground, eligible for bridging - m_pillar_index.insert(pend, unsigned(pillar.id)); + create_ground_pillar(pstart, + sidehead.dir, + pradius, + sidehead.id); } } } @@ -1718,12 +1925,7 @@ public: m_result.add_bridge(hjp, endp, head.r_back_mm); m_result.add_junction(endp, head.r_back_mm); - auto groundp = endp; - groundp(Z) = m_result.ground_level; - auto& newpillar = m_result.add_pillar(endp, groundp, head.r_back_mm) - .add_base(m_cfg.base_height_mm, - m_cfg.base_radius_mm); - m_pillar_index.insert(groundp, unsigned(newpillar.id)); + this->create_ground_pillar(endp, dir, head.r_back_mm); }; std::vector modelpillars; @@ -1883,6 +2085,28 @@ public: m_pillar_index.insert(pillar.endpoint(), pillid); } } + + // Helper function for interconnect_pillars where pairs of already connected + // pillars should be checked for not to be processed again. This can be done + // in O(log) or even constant time with a set or an unordered set of hash + // values uniquely representing a pair of integers. The order of numbers + // within the pair should not matter, it has the same unique hash. + template static I pairhash(I a, I b) + { + using std::ceil; using std::log2; using std::max; using std::min; + + static_assert(std::is_integral::value, + "This function works only for integral types."); + + I g = min(a, b), l = max(a, b); + + auto bits_g = g ? int(ceil(log2(g))) : 0; + + // Assume the hash will fit into the output variable + assert((l ? (ceil(log2(l))) : 0) + bits_g < int(sizeof(I) * CHAR_BIT)); + + return (l << bits_g) + g; + } void interconnect_pillars() { // Now comes the algorithm that connects pillars with each other. @@ -1900,45 +2124,51 @@ public: double min_height_ratio = 0.5; std::set pairs; - + + // A function to connect one pillar with its neighbors. THe number of + // neighbors is given in the configuration. This function if called + // for every pillar in the pillar index. A pair of pillar will not + // be connected multiple times this is ensured by the 'pairs' set which + // remembers the processed pillar pairs auto cascadefn = - [this, d, &pairs, min_height_ratio, H1] (const SpatElement& el) + [this, d, &pairs, min_height_ratio, H1] (const PointIndexEl& el) { - Vec3d qp = el.first; - - const Pillar& pillar = m_result.pillar(el.second); + Vec3d qp = el.first; // endpoint of the pillar + const Pillar& pillar = m_result.pillar(el.second); // actual pillar + + // Get the max number of neighbors a pillar should connect to unsigned neighbors = m_cfg.pillar_cascade_neighbors; - // connections are enough for one pillar + // connections are already enough for the pillar if(pillar.links >= neighbors) return; // Query all remaining points within reach - auto qres = m_pillar_index.query([qp, d](const SpatElement& e){ + auto qres = m_pillar_index.query([qp, d](const PointIndexEl& e){ return distance(e.first, qp) < d; }); // sort the result by distance (have to check if this is needed) std::sort(qres.begin(), qres.end(), - [qp](const SpatElement& e1, const SpatElement& e2){ + [qp](const PointIndexEl& e1, const PointIndexEl& e2){ return distance(e1.first, qp) < distance(e2.first, qp); }); - for(auto& re : qres) { + for(auto& re : qres) { // process the queried neighbors - if(re.second == el.second) continue; + if(re.second == el.second) continue; // Skip self auto a = el.second, b = re.second; - // I hope that the area of a square is never equal to its - // circumference - auto hashval = 2 * (a + b) + a * b; - + // Get unique hash for the given pair (order doesn't matter) + auto hashval = pairhash(a, b); + + // Search for the pair amongst the remembered pairs if(pairs.find(hashval) != pairs.end()) continue; const Pillar& neighborpillar = m_result.pillars()[re.second]; - // this neighbor is occupied + // this neighbor is occupied, skip if(neighborpillar.links >= neighbors) continue; if(interconnect(pillar, neighborpillar)) { @@ -1960,47 +2190,79 @@ public: if(pillar.links >= neighbors) break; } }; - + + // Run the cascade for the pillars in the index m_pillar_index.foreach(cascadefn); - + + // We would be done here if we could allow some pillars to not be + // connected with any neighbors. But this might leave the support tree + // unprintable. + // + // The current solution is to insert additional pillars next to these + // lonely pillars. One or even two additional pillar might get inserted + // depending on the length of the lonely pillar. + size_t pillarcount = m_result.pillars().size(); - + + // Again, go through all pillars, this time in the whole support tree + // not just the index. for(size_t pid = 0; pid < pillarcount; pid++) { auto pillar = [this, pid]() { return m_result.pillar(pid); }; - + + // Decide how many additional pillars will be needed: + unsigned needpillars = 0; - if(pillar().bridges > m_cfg.max_bridges_on_pillar) needpillars = 3; - else if(pillar().links < 2 && pillar().height > H2) { + if (pillar().bridges > m_cfg.max_bridges_on_pillar) + needpillars = 3; + else if (pillar().links < 2 && pillar().height > H2) { // Not enough neighbors to support this pillar needpillars = 2 - pillar().links; - } - else if(pillar().links < 1 && pillar().height > H1) { + } else if (pillar().links < 1 && pillar().height > H1) { // No neighbors could be found and the pillar is too long. needpillars = 1; } - // Search for new pillar locations - bool found = false; - double alpha = 0; // goes to 2Pi - double r = 2 * m_cfg.base_radius_mm; - Vec3d pillarsp = pillar().startpoint(); + // Search for new pillar locations: + + bool found = false; + double alpha = 0; // goes to 2Pi + double r = 2 * m_cfg.base_radius_mm; + Vec3d pillarsp = pillar().startpoint(); + + // temp value for starting point detection Vec3d sp(pillarsp(X), pillarsp(Y), pillarsp(Z) - r); - std::vector tv(needpillars, false); - std::vector spts(needpillars); + // A vector of bool for placement feasbility + std::vector canplace(needpillars, false); + std::vector spts(needpillars); // vector of starting points + + double gnd = m_result.ground_level; + double min_dist = m_cfg.pillar_base_safety_distance_mm + + m_cfg.base_radius_mm + EPSILON; + while(!found && alpha < 2*PI) { - - for(unsigned n = 0; n < needpillars; n++) { - double a = alpha + n * PI/3; - Vec3d s = sp; + for (unsigned n = 0; + n < needpillars && (!n || canplace[n - 1]); + n++) + { + double a = alpha + n * PI / 3; + Vec3d s = sp; s(X) += std::cos(a) * r; s(Y) += std::sin(a) * r; spts[n] = s; + + // Check the path vertically down auto hr = bridge_mesh_intersect(s, {0, 0, -1}, pillar().r); - tv[n] = std::isinf(hr.distance()); + Vec3d gndsp{s(X), s(Y), gnd}; + + // If the path is clear, check for pillar base collisions + canplace[n] = std::isinf(hr.distance()) && + std::sqrt(m_mesh.squared_distance(gndsp)) > + min_dist; } - found = std::all_of(tv.begin(), tv.end(), [](bool v){return v;}); + found = std::all_of(canplace.begin(), canplace.end(), + [](bool v) { return v; }); // 20 angles will be tried... alpha += 0.1 * PI; @@ -2010,7 +2272,7 @@ public: newpills.reserve(needpillars); if(found) for(unsigned n = 0; n < needpillars; n++) { - Vec3d s = spts[n]; double gnd = m_result.ground_level; + Vec3d s = spts[n]; Pillar p(s, Vec3d(s(X), s(Y), gnd), pillar().r); p.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); @@ -2075,9 +2337,13 @@ public: // This is only for checking double idist = bridge_mesh_intersect(sph, dir, R, true); double dist = ray_mesh_intersect(sj, dir); + if (std::isinf(dist)) + dist = sph(Z) - m_mesh.ground_level() + + m_mesh.ground_level_offset(); - if(std::isinf(idist) || std::isnan(idist) || idist < 2*R || - std::isinf(dist) || std::isnan(dist) || dist < 2*R) { + if(std::isnan(idist) || idist < 2*R || + std::isnan(dist) || dist < 2*R) + { BOOST_LOG_TRIVIAL(warning) << "Can not find route for headless" << " support stick at: " << sj.transpose(); @@ -2085,7 +2351,7 @@ public: } Vec3d ej = sj + (dist + HWIDTH_MM)* dir; - m_result.add_compact_bridge(sp, ej, n, R); + m_result.add_compact_bridge(sp, ej, n, R, !std::isinf(dist)); } } }; @@ -2214,7 +2480,9 @@ bool SLASupportTree::generate(const std::vector &support_points, return pc == ABORT; } -SLASupportTree::SLASupportTree(): m_impl(new Impl()) {} +SLASupportTree::SLASupportTree(double gnd_lvl): m_impl(new Impl()) { + m_impl->ground_level = gnd_lvl; +} const TriangleMesh &SLASupportTree::merged_mesh() const { @@ -2226,7 +2494,7 @@ void SLASupportTree::merged_mesh_with_pad(TriangleMesh &outmesh) const { outmesh.merge(get_pad()); } -SlicedSupports SLASupportTree::slice(float layerh, float init_layerh) const +std::vector SLASupportTree::slice(float layerh, float init_layerh) const { if(init_layerh < 0) init_layerh = layerh; auto& stree = get(); @@ -2245,36 +2513,31 @@ SlicedSupports SLASupportTree::slice(float layerh, float init_layerh) const TriangleMesh fullmesh = m_impl->merged_mesh(); fullmesh.merge(get_pad()); - fullmesh.require_shared_vertices(); // TriangleMeshSlicer needs this + if (!fullmesh.empty()) fullmesh.require_shared_vertices(); TriangleMeshSlicer slicer(&fullmesh); - SlicedSupports ret; + std::vector ret; slicer.slice(heights, 0.f, &ret, get().ctl().cancelfn); return ret; } -SlicedSupports SLASupportTree::slice(const std::vector &heights, +std::vector SLASupportTree::slice(const std::vector &heights, float cr) const { TriangleMesh fullmesh = m_impl->merged_mesh(); fullmesh.merge(get_pad()); - fullmesh.require_shared_vertices(); // TriangleMeshSlicer needs this + if (!fullmesh.empty()) fullmesh.require_shared_vertices(); TriangleMeshSlicer slicer(&fullmesh); - SlicedSupports ret; + std::vector ret; slicer.slice(heights, cr, &ret, get().ctl().cancelfn); return ret; } -const TriangleMesh &SLASupportTree::add_pad(const SliceLayer& baseplate, +const TriangleMesh &SLASupportTree::add_pad(const ExPolygons& modelbase, const PoolConfig& pcfg) const { -// PoolConfig pcfg; -// pcfg.min_wall_thickness_mm = min_wall_thickness_mm; -// pcfg.min_wall_height_mm = min_wall_height_mm; -// pcfg.max_merge_distance_mm = max_merge_distance_mm; -// pcfg.edge_radius_mm = edge_radius_mm; - return m_impl->create_pad(merged_mesh(), baseplate, pcfg).tmesh; + return m_impl->create_pad(merged_mesh(), modelbase, pcfg).tmesh; } const TriangleMesh &SLASupportTree::get_pad() const diff --git a/src/libslic3r/SLA/SLASupportTree.hpp b/src/libslic3r/SLA/SLASupportTree.hpp index 66677e4d7..8602d8a46 100644 --- a/src/libslic3r/SLA/SLASupportTree.hpp +++ b/src/libslic3r/SLA/SLASupportTree.hpp @@ -24,10 +24,11 @@ class TriangleMesh; class Model; class ModelInstance; class ModelObject; +class Polygon; class ExPolygon; -using SliceLayer = std::vector; -using SlicedSupports = std::vector; +using Polygons = std::vector; +using ExPolygons = std::vector; namespace sla { @@ -80,6 +81,10 @@ struct SupportConfig { // The elevation in Z direction upwards. This is the space between the pad // and the model object's bounding box bottom. double object_elevation_mm = 10; + + // The shortest distance between a pillar base perimeter from the model + // body. This is only useful when elevation is set to zero. + double pillar_base_safety_distance_mm = 0.5; // ///////////////////////////////////////////////////////////////////////// // Compile time configuration values (candidates for runtime) @@ -160,7 +165,7 @@ class SLASupportTree { public: - SLASupportTree(); + SLASupportTree(double ground_level = 0.0); SLASupportTree(const std::vector& pts, const EigenMesh3D& em, @@ -179,12 +184,17 @@ public: void merged_mesh_with_pad(TriangleMesh&) const; /// Get the sliced 2d layers of the support geometry. - SlicedSupports slice(float layerh, float init_layerh = -1.0) const; + std::vector slice(float layerh, float init_layerh = -1.0) const; - SlicedSupports slice(const std::vector&, float closing_radius) const; + std::vector slice(const std::vector &, + float closing_radius) const; /// Adding the "pad" (base pool) under the supports - const TriangleMesh& add_pad(const SliceLayer& baseplate, + /// modelbase will be used according to the embed_object flag in PoolConfig. + /// If set, the plate will interpreted as the model's intrinsic pad. + /// Otherwise, the modelbase will be unified with the base plate calculated + /// from the supports. + const TriangleMesh& add_pad(const ExPolygons& modelbase, const PoolConfig& pcfg) const; /// Get the pad geometry diff --git a/src/libslic3r/SLA/SLASupportTreeIGL.cpp b/src/libslic3r/SLA/SLASupportTreeIGL.cpp index 1609b9ac4..4b6498a20 100644 --- a/src/libslic3r/SLA/SLASupportTreeIGL.cpp +++ b/src/libslic3r/SLA/SLASupportTreeIGL.cpp @@ -29,69 +29,137 @@ namespace sla { using igl::PI; /* ************************************************************************** - * SpatIndex implementation + * PointIndex implementation * ************************************************************************** */ -class SpatIndex::Impl { +class PointIndex::Impl { public: - using BoostIndex = boost::geometry::index::rtree< SpatElement, + using BoostIndex = boost::geometry::index::rtree< PointIndexEl, boost::geometry::index::rstar<16, 4> /* ? */ >; BoostIndex m_store; }; -SpatIndex::SpatIndex(): m_impl(new Impl()) {} -SpatIndex::~SpatIndex() {} +PointIndex::PointIndex(): m_impl(new Impl()) {} +PointIndex::~PointIndex() {} -SpatIndex::SpatIndex(const SpatIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} -SpatIndex::SpatIndex(SpatIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} +PointIndex::PointIndex(const PointIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} +PointIndex::PointIndex(PointIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} -SpatIndex& SpatIndex::operator=(const SpatIndex &cpy) +PointIndex& PointIndex::operator=(const PointIndex &cpy) { m_impl.reset(new Impl(*cpy.m_impl)); return *this; } -SpatIndex& SpatIndex::operator=(SpatIndex &&cpy) +PointIndex& PointIndex::operator=(PointIndex &&cpy) { m_impl.swap(cpy.m_impl); return *this; } -void SpatIndex::insert(const SpatElement &el) +void PointIndex::insert(const PointIndexEl &el) { m_impl->m_store.insert(el); } -bool SpatIndex::remove(const SpatElement& el) +bool PointIndex::remove(const PointIndexEl& el) { return m_impl->m_store.remove(el) == 1; } -std::vector -SpatIndex::query(std::function fn) +std::vector +PointIndex::query(std::function fn) { namespace bgi = boost::geometry::index; - std::vector ret; + std::vector ret; m_impl->m_store.query(bgi::satisfies(fn), std::back_inserter(ret)); return ret; } -std::vector SpatIndex::nearest(const Vec3d &el, unsigned k = 1) +std::vector PointIndex::nearest(const Vec3d &el, unsigned k = 1) { namespace bgi = boost::geometry::index; - std::vector ret; ret.reserve(k); + std::vector ret; ret.reserve(k); m_impl->m_store.query(bgi::nearest(el, k), std::back_inserter(ret)); return ret; } -size_t SpatIndex::size() const +size_t PointIndex::size() const { return m_impl->m_store.size(); } -void SpatIndex::foreach(std::function fn) +void PointIndex::foreach(std::function fn) +{ + for(auto& el : m_impl->m_store) fn(el); +} + +/* ************************************************************************** + * BoxIndex implementation + * ************************************************************************** */ + +class BoxIndex::Impl { +public: + using BoostIndex = boost::geometry::index:: + rtree /* ? */>; + + BoostIndex m_store; +}; + +BoxIndex::BoxIndex(): m_impl(new Impl()) {} +BoxIndex::~BoxIndex() {} + +BoxIndex::BoxIndex(const BoxIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} +BoxIndex::BoxIndex(BoxIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} + +BoxIndex& BoxIndex::operator=(const BoxIndex &cpy) +{ + m_impl.reset(new Impl(*cpy.m_impl)); + return *this; +} + +BoxIndex& BoxIndex::operator=(BoxIndex &&cpy) +{ + m_impl.swap(cpy.m_impl); + return *this; +} + +void BoxIndex::insert(const BoxIndexEl &el) +{ + m_impl->m_store.insert(el); +} + +bool BoxIndex::remove(const BoxIndexEl& el) +{ + return m_impl->m_store.remove(el) == 1; +} + +std::vector BoxIndex::query(const BoundingBox &qrbb, + BoxIndex::QueryType qt) +{ + namespace bgi = boost::geometry::index; + + std::vector ret; ret.reserve(m_impl->m_store.size()); + + switch (qt) { + case qtIntersects: + m_impl->m_store.query(bgi::intersects(qrbb), std::back_inserter(ret)); + break; + case qtWithin: + m_impl->m_store.query(bgi::within(qrbb), std::back_inserter(ret)); + } + + return ret; +} + +size_t BoxIndex::size() const +{ + return m_impl->m_store.size(); +} + +void BoxIndex::foreach(std::function fn) { for(auto& el : m_impl->m_store) fn(el); } @@ -343,12 +411,14 @@ PointSet normals(const PointSet& points, return ret; } namespace bgi = boost::geometry::index; -using Index3D = bgi::rtree< SpatElement, bgi::rstar<16, 4> /* ? */ >; +using Index3D = bgi::rtree< PointIndexEl, bgi::rstar<16, 4> /* ? */ >; -ClusteredPoints cluster(Index3D& sindex, unsigned max_points, - std::function(const Index3D&, const SpatElement&)> qfn) +ClusteredPoints cluster(Index3D &sindex, + unsigned max_points, + std::function( + const Index3D &, const PointIndexEl &)> qfn) { - using Elems = std::vector; + using Elems = std::vector; // Recursive function for visiting all the points in a given distance to // each other @@ -356,8 +426,8 @@ ClusteredPoints cluster(Index3D& sindex, unsigned max_points, [&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster) { for(auto& p : pts) { - std::vector tmp = qfn(sindex, p); - auto cmp = [](const SpatElement& e1, const SpatElement& e2){ + std::vector tmp = qfn(sindex, p); + auto cmp = [](const PointIndexEl& e1, const PointIndexEl& e2){ return e1.second < e2.second; }; @@ -401,12 +471,12 @@ ClusteredPoints cluster(Index3D& sindex, unsigned max_points, } namespace { -std::vector distance_queryfn(const Index3D& sindex, - const SpatElement& p, +std::vector distance_queryfn(const Index3D& sindex, + const PointIndexEl& p, double dist, unsigned max_points) { - std::vector tmp; tmp.reserve(max_points); + std::vector tmp; tmp.reserve(max_points); sindex.query( bgi::nearest(p.first, max_points), std::back_inserter(tmp) @@ -433,7 +503,7 @@ ClusteredPoints cluster( for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx)); return cluster(sindex, max_points, - [dist, max_points](const Index3D& sidx, const SpatElement& p) + [dist, max_points](const Index3D& sidx, const PointIndexEl& p) { return distance_queryfn(sidx, p, dist, max_points); }); @@ -443,7 +513,7 @@ ClusteredPoints cluster( ClusteredPoints cluster( const std::vector& indices, std::function pointfn, - std::function predicate, + std::function predicate, unsigned max_points) { // A spatial index for querying the nearest points @@ -453,10 +523,10 @@ ClusteredPoints cluster( for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx)); return cluster(sindex, max_points, - [max_points, predicate](const Index3D& sidx, const SpatElement& p) + [max_points, predicate](const Index3D& sidx, const PointIndexEl& p) { - std::vector tmp; tmp.reserve(max_points); - sidx.query(bgi::satisfies([p, predicate](const SpatElement& e){ + std::vector tmp; tmp.reserve(max_points); + sidx.query(bgi::satisfies([p, predicate](const PointIndexEl& e){ return predicate(p, e); }), std::back_inserter(tmp)); return tmp; @@ -473,7 +543,7 @@ ClusteredPoints cluster(const PointSet& pts, double dist, unsigned max_points) sindex.insert(std::make_pair(Vec3d(pts.row(i)), unsigned(i))); return cluster(sindex, max_points, - [dist, max_points](const Index3D& sidx, const SpatElement& p) + [dist, max_points](const Index3D& sidx, const PointIndexEl& p) { return distance_queryfn(sidx, p, dist, max_points); }); diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 1902e74ae..fb2a5d7df 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -31,11 +31,10 @@ using SupportTreePtr = std::unique_ptr; class SLAPrintObject::SupportData { public: - sla::EigenMesh3D emesh; // index-triangle representation - std::vector - support_points; // all the support points (manual/auto) - SupportTreePtr support_tree_ptr; // the supports - SlicedSupports support_slices; // sliced supports + sla::EigenMesh3D emesh; // index-triangle representation + std::vector support_points; // all the support points (manual/auto) + SupportTreePtr support_tree_ptr; // the supports + std::vector support_slices; // sliced supports inline SupportData(const TriangleMesh &trmesh) : emesh(trmesh) {} }; @@ -212,8 +211,8 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf Moved, Deleted, }; - ModelObjectStatus(ModelID id, Status status = Unknown) : id(id), status(status) {} - ModelID id; + ModelObjectStatus(ObjectID id, Status status = Unknown) : id(id), status(status) {} + ObjectID id; Status status; // Search by id. bool operator<(const ModelObjectStatus &rhs) const { return id < rhs.id; } @@ -316,9 +315,9 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf print_object(print_object), trafo(print_object->trafo()), status(status) {} - PrintObjectStatus(ModelID id) : id(id), print_object(nullptr), trafo(Transform3d::Identity()), status(Unknown) {} + PrintObjectStatus(ObjectID id) : id(id), print_object(nullptr), trafo(Transform3d::Identity()), status(Unknown) {} // ID of the ModelObject & PrintObject - ModelID id; + ObjectID id; // Pointer to the old PrintObject SLAPrintObject *print_object; // Trafo generated with model_object->world_matrix(true) @@ -368,7 +367,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf // Synchronize Object's config. bool object_config_changed = model_object.config != model_object_new.config; if (object_config_changed) - model_object.config = model_object_new.config; + static_cast(model_object.config) = static_cast(model_object_new.config); if (! object_diff.empty() || object_config_changed) { SLAPrintObjectConfig new_config = m_default_object_config; normalize_and_apply_config(new_config, model_object.config); @@ -441,12 +440,10 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf update_apply_status(this->invalidate_all_steps()); m_objects = print_objects_new; // Delete the PrintObjects marked as Unknown or Deleted. - bool deleted_objects = false; for (auto &pos : print_object_status) if (pos.status == PrintObjectStatus::Unknown || pos.status == PrintObjectStatus::Deleted) { update_apply_status(pos.print_object->invalidate_all_steps()); delete pos.print_object; - deleted_objects = true; } if (new_objects) update_apply_status(false); @@ -473,7 +470,7 @@ void SLAPrint::set_task(const TaskParams ¶ms) int n_object_steps = int(params.to_object_step) + 1; if (n_object_steps == 0) - n_object_steps = (int)slaposCount; + n_object_steps = int(slaposCount); if (params.single_model_object.valid()) { // Find the print object to be processed with priority. @@ -488,7 +485,7 @@ void SLAPrint::set_task(const TaskParams ¶ms) // Find out whether the priority print object is being currently processed. bool running = false; for (int istep = 0; istep < n_object_steps; ++ istep) { - if (! print_object->m_stepmask[istep]) + if (! print_object->m_stepmask[size_t(istep)]) // Step was skipped, cancel. break; if (print_object->is_step_started_unguarded(SLAPrintObjectStep(istep))) { @@ -504,7 +501,7 @@ void SLAPrint::set_task(const TaskParams ¶ms) if (params.single_model_instance_only) { // Suppress all the steps of other instances. for (SLAPrintObject *po : m_objects) - for (int istep = 0; istep < (int)slaposCount; ++ istep) + for (size_t istep = 0; istep < slaposCount; ++ istep) po->m_stepmask[istep] = false; } else if (! running) { // Swap the print objects, so that the selected print_object is first in the row. @@ -514,15 +511,15 @@ void SLAPrint::set_task(const TaskParams ¶ms) } // and set the steps for the current object. for (int istep = 0; istep < n_object_steps; ++ istep) - print_object->m_stepmask[istep] = true; - for (int istep = n_object_steps; istep < (int)slaposCount; ++ istep) - print_object->m_stepmask[istep] = false; + print_object->m_stepmask[size_t(istep)] = true; + for (int istep = n_object_steps; istep < int(slaposCount); ++ istep) + print_object->m_stepmask[size_t(istep)] = false; } else { // Slicing all objects. bool running = false; for (SLAPrintObject *print_object : m_objects) for (int istep = 0; istep < n_object_steps; ++ istep) { - if (! print_object->m_stepmask[istep]) { + if (! print_object->m_stepmask[size_t(istep)]) { // Step may have been skipped. Restart. goto loop_end; } @@ -538,8 +535,8 @@ void SLAPrint::set_task(const TaskParams ¶ms) this->call_cancel_callback(); for (SLAPrintObject *po : m_objects) { for (int istep = 0; istep < n_object_steps; ++ istep) - po->m_stepmask[istep] = true; - for (int istep = n_object_steps; istep < (int)slaposCount; ++ istep) + po->m_stepmask[size_t(istep)] = true; + for (auto istep = size_t(n_object_steps); istep < slaposCount; ++ istep) po->m_stepmask[istep] = false; } } @@ -557,9 +554,9 @@ void SLAPrint::set_task(const TaskParams ¶ms) void SLAPrint::finalize() { for (SLAPrintObject *po : m_objects) - for (int istep = 0; istep < (int)slaposCount; ++ istep) + for (size_t istep = 0; istep < slaposCount; ++ istep) po->m_stepmask[istep] = true; - for (int istep = 0; istep < (int)slapsCount; ++ istep) + for (size_t istep = 0; istep < slapsCount; ++ istep) m_stepmask[istep] = true; } @@ -597,21 +594,48 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { scfg.pillar_widening_factor = c.support_pillar_widening_factor.getFloat(); scfg.base_radius_mm = 0.5*c.support_base_diameter.getFloat(); scfg.base_height_mm = c.support_base_height.getFloat(); - + scfg.pillar_base_safety_distance_mm = + c.support_base_safety_distance.getFloat() < EPSILON ? + scfg.safety_distance_mm : c.support_base_safety_distance.getFloat(); + return scfg; } +sla::PoolConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c) { + sla::PoolConfig::EmbedObject ret; + + ret.enabled = c.support_object_elevation.getFloat() <= EPSILON && + c.pad_enable.getBool() && c.supports_enable.getBool(); + + if(ret.enabled) { + ret.object_gap_mm = c.pad_object_gap.getFloat(); + ret.stick_width_mm = c.pad_object_connector_width.getFloat(); + ret.stick_stride_mm = c.pad_object_connector_stride.getFloat(); + ret.stick_penetration_mm = c.pad_object_connector_penetration + .getFloat(); + } + + return ret; +} + sla::PoolConfig make_pool_config(const SLAPrintObjectConfig& c) { sla::PoolConfig pcfg; pcfg.min_wall_thickness_mm = c.pad_wall_thickness.getFloat(); - pcfg.wall_slope = c.pad_wall_slope.getFloat(); - pcfg.edge_radius_mm = c.pad_edge_radius.getFloat(); + pcfg.wall_slope = c.pad_wall_slope.getFloat() * PI / 180.0; + + // We do not support radius for now + pcfg.edge_radius_mm = 0.0; //c.pad_edge_radius.getFloat(); + pcfg.max_merge_distance_mm = c.pad_max_merge_distance.getFloat(); pcfg.min_wall_height_mm = c.pad_wall_height.getFloat(); + // set builtin pad implicitly ON + pcfg.embed_object = builtin_pad_cfg(c); + return pcfg; } + } std::string SLAPrint::validate() const @@ -634,9 +658,21 @@ std::string SLAPrint::validate() const cfg.head_width_mm + 2 * cfg.head_back_radius_mm - cfg.head_penetration_mm; + + double elv = cfg.object_elevation_mm; - if(supports_en && pinhead_width > cfg.object_elevation_mm) + if(supports_en && elv > EPSILON && elv < pinhead_width ) return L("Elevation is too low for object."); + + sla::PoolConfig::EmbedObject builtinpad = builtin_pad_cfg(po->config()); + if(supports_en && builtinpad.enabled && + cfg.pillar_base_safety_distance_mm < builtinpad.object_gap_mm) { + return L( + "The endings of the support pillars will be deployed on the " + "gap between the object and the pad. 'Support base safety " + "distance' has to be greater than the 'Pad object gap' " + "parameter to avoid this."); + } } return ""; @@ -751,18 +787,27 @@ void SLAPrint::process() mit->set_model_slice_idx(po, id); ++mit; } + + if(po.m_config.supports_enable.getBool() || + po.m_config.pad_enable.getBool()) + { + po.m_supportdata.reset( + new SLAPrintObject::SupportData(po.transformed_mesh()) ); + } }; // In this step we check the slices, identify island and cover them with // support points. Then we sprinkle the rest of the mesh. auto support_points = [this, ostepd](SLAPrintObject& po) { - const ModelObject& mo = *po.m_model_object; - po.m_supportdata.reset( - new SLAPrintObject::SupportData(po.transformed_mesh()) ); - // If supports are disabled, we can skip the model scan. if(!po.m_config.supports_enable.getBool()) return; + if (!po.m_supportdata) + po.m_supportdata.reset( + new SLAPrintObject::SupportData(po.transformed_mesh())); + + const ModelObject& mo = *po.m_model_object; + BOOST_LOG_TRIVIAL(debug) << "Support point count " << mo.sla_support_points.size(); @@ -771,7 +816,7 @@ void SLAPrint::process() // into the backend cache. if (mo.sla_points_status != sla::PointsStatus::UserModified) { - // Hypotetical use of the slice index: + // Hypothetical use of the slice index: // auto bb = po.transformed_mesh().bounding_box(); // auto range = po.get_slice_records(bb.min(Z)); // std::vector heights; heights.reserve(range.size()); @@ -820,23 +865,59 @@ void SLAPrint::process() BOOST_LOG_TRIVIAL(debug) << "Automatic support points: " << po.m_supportdata->support_points.size(); - // Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass the update status to GLGizmoSlaSupports - m_report_status(*this, -1, L("Generating support points"), SlicingStatus::RELOAD_SLA_SUPPORT_POINTS); + // Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass + // the update status to GLGizmoSlaSupports + m_report_status(*this, + -1, + L("Generating support points"), + SlicingStatus::RELOAD_SLA_SUPPORT_POINTS); } else { - // There are either some points on the front-end, or the user removed them on purpose. No calculation will be done. + // There are either some points on the front-end, or the user + // removed them on purpose. No calculation will be done. po.m_supportdata->support_points = po.transformed_support_points(); } + + // If the zero elevation mode is engaged, we have to filter out all the + // points that are on the bottom of the object + if (po.config().support_object_elevation.getFloat() <= EPSILON) { + double gnd = po.m_supportdata->emesh.ground_level(); + auto & pts = po.m_supportdata->support_points; + double tolerance = po.config().pad_enable.getBool() + ? po.m_config.pad_wall_thickness.getFloat() + : po.m_config.support_base_height.getFloat(); + + // get iterator to the reorganized vector end + auto endit = std::remove_if( + pts.begin(), + pts.end(), + [tolerance, gnd](const sla::SupportPoint &sp) { + double diff = std::abs(gnd - double(sp.pos(Z))); + return diff <= tolerance; + }); + + // erase all elements after the new end + pts.erase(endit, pts.end()); + } }; // In this step we create the supports auto support_tree = [this, ostepd](SLAPrintObject& po) { if(!po.m_supportdata) return; + + sla::PoolConfig pcfg = make_pool_config(po.m_config); + + if (pcfg.embed_object) + po.m_supportdata->emesh.ground_level_offset( + pcfg.min_wall_thickness_mm); if(!po.m_config.supports_enable.getBool()) { + // Generate empty support tree. It can still host a pad - po.m_supportdata->support_tree_ptr.reset(new SLASupportTree()); + po.m_supportdata->support_tree_ptr.reset( + new SLASupportTree(po.m_supportdata->emesh.ground_level())); + return; } @@ -858,7 +939,7 @@ void SLAPrint::process() ctl.stopcondition = [this](){ return canceled(); }; ctl.cancelfn = [this]() { throw_if_canceled(); }; - + po.m_supportdata->support_tree_ptr.reset( new SLASupportTree(po.m_supportdata->support_points, po.m_supportdata->emesh, scfg, ctl)); @@ -888,40 +969,33 @@ void SLAPrint::process() // and before the supports had been sliced. (or the slicing has to be // repeated) - if(!po.m_supportdata || !po.m_supportdata->support_tree_ptr) { - BOOST_LOG_TRIVIAL(error) << "Uninitialized support data at " - << "pad creation."; - return; - } - if(po.m_config.pad_enable.getBool()) { - double wt = po.m_config.pad_wall_thickness.getFloat(); - double h = po.m_config.pad_wall_height.getFloat(); - double md = po.m_config.pad_max_merge_distance.getFloat(); - // Radius is disabled for now... - double er = 0; // po.m_config.pad_edge_radius.getFloat(); - double tilt = po.m_config.pad_wall_slope.getFloat() * PI / 180.0; - double lh = po.m_config.layer_height.getFloat(); - double elevation = po.m_config.support_object_elevation.getFloat(); - if(!po.m_config.supports_enable.getBool()) elevation = 0; - sla::PoolConfig pcfg(wt, h, md, er, tilt); + // Get the distilled pad configuration from the config + sla::PoolConfig pcfg = make_pool_config(po.m_config); - ExPolygons bp; - double pad_h = sla::get_pad_fullheight(pcfg); - auto&& trmesh = po.transformed_mesh(); + ExPolygons bp; // This will store the base plate of the pad. + double pad_h = sla::get_pad_fullheight(pcfg); + const TriangleMesh &trmesh = po.transformed_mesh(); // This call can get pretty time consuming auto thrfn = [this](){ throw_if_canceled(); }; - if(elevation < pad_h) { - // we have to count with the model geometry for the base plate - sla::base_plate(trmesh, bp, float(pad_h), float(lh), thrfn); + if (!po.m_config.supports_enable.getBool() || pcfg.embed_object) { + // No support (thus no elevation) or zero elevation mode + // we sometimes call it "builtin pad" is enabled so we will + // get a sample from the bottom of the mesh and use it for pad + // creation. + sla::base_plate(trmesh, + bp, + float(pad_h), + float(po.m_config.layer_height.getFloat()), + thrfn); } pcfg.throw_on_cancel = thrfn; po.m_supportdata->support_tree_ptr->add_pad(bp, pcfg); - } else { + } else if(po.m_supportdata && po.m_supportdata->support_tree_ptr) { po.m_supportdata->support_tree_ptr->remove_pad(); } @@ -938,6 +1012,11 @@ void SLAPrint::process() if(sd) sd->support_slices.clear(); + // Don't bother if no supports and no pad is present. + if (!po.m_config.supports_enable.getBool() && + !po.m_config.pad_enable.getBool()) + return; + if(sd && sd->support_tree_ptr) { std::vector heights; heights.reserve(po.m_slice_index.size()); @@ -964,7 +1043,8 @@ void SLAPrint::process() po.m_slice_index[i].set_support_slice_idx(po, i); } - // Using RELOAD_SLA_PREVIEW to tell the Plater to pass the update status to the 3D preview to load the SLA slices. + // Using RELOAD_SLA_PREVIEW to tell the Plater to pass the update + // status to the 3D preview to load the SLA slices. m_report_status(*this, -2, "", SlicingStatus::RELOAD_SLA_PREVIEW); }; @@ -1536,14 +1616,17 @@ bool SLAPrint::is_step_done(SLAPrintObjectStep step) const return true; } -SLAPrintObject::SLAPrintObject(SLAPrint *print, ModelObject *model_object): - Inherited(print, model_object), - m_stepmask(slaposCount, true), - m_transformed_rmesh( [this](TriangleMesh& obj){ - obj = m_model_object->raw_mesh(); obj.transform(m_trafo); obj.require_shared_vertices(); - }) -{ -} +SLAPrintObject::SLAPrintObject(SLAPrint *print, ModelObject *model_object) + : Inherited(print, model_object) + , m_stepmask(slaposCount, true) + , m_transformed_rmesh([this](TriangleMesh &obj) { + obj = m_model_object->raw_mesh(); + if (!obj.empty()) { + obj.transform(m_trafo); + obj.require_shared_vertices(); + } + }) +{} SLAPrintObject::~SLAPrintObject() {} @@ -1582,13 +1665,19 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector EMPTY_SLICES; const TriangleMesh EMPTY_MESH; const ExPolygons EMPTY_SLICE; +const std::vector EMPTY_SUPPORT_POINTS; } const SliceRecord SliceRecord::EMPTY(0, std::nanf(""), 0.f); const std::vector& SLAPrintObject::get_support_points() const { - return m_supportdata->support_points; + return m_supportdata? m_supportdata->support_points : EMPTY_SUPPORT_POINTS; } const std::vector &SLAPrintObject::get_support_slices() const diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index b0c5a8fdc..9620e9b68 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -54,10 +54,10 @@ public: bool is_left_handed() const { return m_left_handed; } struct Instance { - Instance(ModelID instance_id, const Point &shift, float rotation) : instance_id(instance_id), shift(shift), rotation(rotation) {} + Instance(ObjectID instance_id, const Point &shift, float rotation) : instance_id(instance_id), shift(shift), rotation(rotation) {} bool operator==(const Instance &rhs) const { return this->instance_id == rhs.instance_id && this->shift == rhs.shift && this->rotation == rhs.rotation; } // ID of the corresponding ModelInstance. - ModelID instance_id; + ObjectID instance_id; // Slic3r::Point objects in scaled G-code coordinates Point shift; // Rotation along the Z axis, in radians. diff --git a/src/libslic3r/Slicing.cpp b/src/libslic3r/Slicing.cpp index e1bb4b313..c62736ffe 100644 --- a/src/libslic3r/Slicing.cpp +++ b/src/libslic3r/Slicing.cpp @@ -153,24 +153,33 @@ SlicingParameters SlicingParameters::create_from_config( return params; } -// Convert layer_height_ranges to layer_height_profile. Both are referenced to z=0, meaning the raft layers are not accounted for +std::vector> layer_height_ranges(const t_layer_config_ranges &config_ranges) +{ + std::vector> out; + out.reserve(config_ranges.size()); + for (const auto &kvp : config_ranges) + out.emplace_back(kvp.first, kvp.second.option("layer_height")->getFloat()); + return out; +} + +// Convert layer_config_ranges to layer_height_profile. Both are referenced to z=0, meaning the raft layers are not accounted for // in the height profile and the printed object may be lifted by the raft thickness at the time of the G-code generation. std::vector layer_height_profile_from_ranges( const SlicingParameters &slicing_params, - const t_layer_height_ranges &layer_height_ranges) + const t_layer_config_ranges &layer_config_ranges) // #ys_FIXME_experiment { // 1) If there are any height ranges, trim one by the other to make them non-overlapping. Insert the 1st layer if fixed. std::vector> ranges_non_overlapping; - ranges_non_overlapping.reserve(layer_height_ranges.size() * 4); + ranges_non_overlapping.reserve(layer_config_ranges.size() * 4); // #ys_FIXME_experiment if (slicing_params.first_object_layer_height_fixed()) ranges_non_overlapping.push_back(std::pair( t_layer_height_range(0., slicing_params.first_object_layer_height), slicing_params.first_object_layer_height)); // The height ranges are sorted lexicographically by low / high layer boundaries. - for (t_layer_height_ranges::const_iterator it_range = layer_height_ranges.begin(); it_range != layer_height_ranges.end(); ++ it_range) { + for (t_layer_config_ranges::const_iterator it_range = layer_config_ranges.begin(); it_range != layer_config_ranges.end(); ++ it_range) { coordf_t lo = it_range->first.first; coordf_t hi = std::min(it_range->first.second, slicing_params.object_print_z_height()); - coordf_t height = it_range->second; + coordf_t height = it_range->second.option("layer_height")->getFloat(); if (! ranges_non_overlapping.empty()) // Trim current low with the last high. lo = std::max(lo, ranges_non_overlapping.back().first.second); @@ -187,7 +196,6 @@ std::vector layer_height_profile_from_ranges( coordf_t hi = it_range->first.second; coordf_t height = it_range->second; coordf_t last_z = layer_height_profile.empty() ? 0. : layer_height_profile[layer_height_profile.size() - 2]; - coordf_t last_height = layer_height_profile.empty() ? 0. : layer_height_profile[layer_height_profile.size() - 1]; if (lo > last_z + EPSILON) { // Insert a step of normal layer height. layer_height_profile.push_back(last_z); @@ -203,7 +211,6 @@ std::vector layer_height_profile_from_ranges( } coordf_t last_z = layer_height_profile.empty() ? 0. : layer_height_profile[layer_height_profile.size() - 2]; - coordf_t last_height = layer_height_profile.empty() ? 0. : layer_height_profile[layer_height_profile.size() - 1]; if (last_z < slicing_params.object_print_z_height()) { // Insert a step of normal layer height up to the object top. layer_height_profile.push_back(last_z); @@ -219,7 +226,7 @@ std::vector layer_height_profile_from_ranges( // Fill layer_height_profile by heights ensuring a prescribed maximum cusp height. std::vector layer_height_profile_adaptive( const SlicingParameters &slicing_params, - const t_layer_height_ranges &layer_height_ranges, + const t_layer_config_ranges & /* layer_config_ranges */, const ModelVolumePtrs &volumes) { // 1) Initialize the SlicingAdaptive class with the object meshes. @@ -245,7 +252,6 @@ std::vector layer_height_profile_adaptive( } coordf_t slice_z = slicing_params.first_object_layer_height; coordf_t height = slicing_params.first_object_layer_height; - coordf_t cusp_height = 0.; int current_facet = 0; while ((slice_z - height) <= slicing_params.object_print_z_height()) { height = 999; @@ -401,7 +407,6 @@ void adjust_layer_height_profile( } // Adjust height by layer_thickness_delta. coordf_t weight = std::abs(zz - z) < 0.5 * band_width ? (0.5 + 0.5 * cos(2. * M_PI * (zz - z) / band_width)) : 0.; - coordf_t height_new = height; switch (action) { case LAYER_HEIGHT_EDIT_ACTION_INCREASE: case LAYER_HEIGHT_EDIT_ACTION_DECREASE: diff --git a/src/libslic3r/Slicing.hpp b/src/libslic3r/Slicing.hpp index fa5a12f9c..064363ec2 100644 --- a/src/libslic3r/Slicing.hpp +++ b/src/libslic3r/Slicing.hpp @@ -11,6 +11,8 @@ #include "libslic3r.h" #include "Utils.hpp" +#include "PrintConfig.hpp" + namespace Slic3r { @@ -128,15 +130,17 @@ inline bool equal_layering(const SlicingParameters &sp1, const SlicingParameters } typedef std::pair t_layer_height_range; -typedef std::map t_layer_height_ranges; +typedef std::map t_layer_config_ranges; + +extern std::vector> layer_height_ranges(const t_layer_config_ranges &config_ranges); extern std::vector layer_height_profile_from_ranges( const SlicingParameters &slicing_params, - const t_layer_height_ranges &layer_height_ranges); + const t_layer_config_ranges &layer_config_ranges); extern std::vector layer_height_profile_adaptive( const SlicingParameters &slicing_params, - const t_layer_height_ranges &layer_height_ranges, + const t_layer_config_ranges &layer_config_ranges, const ModelVolumePtrs &volumes); @@ -171,4 +175,9 @@ extern int generate_layer_height_texture( }; // namespace Slic3r +namespace cereal +{ + template void serialize(Archive& archive, Slic3r::t_layer_height_range &lhr) { archive(lhr.first, lhr.second); } +} + #endif /* slic3r_Slicing_hpp_ */ diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index fde35ca1e..505fdcaf5 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -361,17 +361,17 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) std::sort(layers_sorted.begin(), layers_sorted.end(), MyLayersPtrCompare()); int layer_id = 0; assert(object.support_layers().empty()); - for (int i = 0; i < int(layers_sorted.size());) { + for (size_t i = 0; i < layers_sorted.size();) { // Find the last layer with roughly the same print_z, find the minimum layer height of all. // Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should. - int j = i + 1; + size_t j = i + 1; coordf_t zmax = layers_sorted[i]->print_z + EPSILON; for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) ; // Assign an average print_z to the set of layers with nearly equal print_z. coordf_t zavg = 0.5 * (layers_sorted[i]->print_z + layers_sorted[j - 1]->print_z); coordf_t height_min = layers_sorted[i]->height; bool empty = true; - for (int u = i; u < j; ++u) { + for (size_t u = i; u < j; ++u) { MyLayer &layer = *layers_sorted[u]; if (! layer.polygons.empty()) empty = false; @@ -829,7 +829,7 @@ namespace SupportMaterialInternal { assert(expansion_scaled >= 0.f); for (const ExtrusionPath &ep : loop.paths) if (ep.role() == erOverhangPerimeter && ! ep.polyline.empty()) { - float exp = 0.5f * scale_(ep.width) + expansion_scaled; + float exp = 0.5f * (float)scale_(ep.width) + expansion_scaled; if (ep.is_closed()) { if (ep.size() >= 3) { // This is a complete loop. @@ -1042,7 +1042,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ float fw = float(layerm->flow(frExternalPerimeter).scaled_width()); no_interface_offset = (no_interface_offset == 0.f) ? fw : std::min(no_interface_offset, fw); float lower_layer_offset = - (layer_id < m_object_config->support_material_enforce_layers.value) ? + (layer_id < (size_t)m_object_config->support_material_enforce_layers.value) ? // Enforce a full possible support, ignore the overhang angle. 0.f : (threshold_rad > 0. ? @@ -1352,7 +1352,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ { // Find the span of layers, which are to be printed at the first layer height. int j = 0; - for (; j < contact_out.size() && contact_out[j]->print_z < m_slicing_params.first_print_layer_height + this->m_support_layer_height_min - EPSILON; ++ j); + for (; j < (int)contact_out.size() && contact_out[j]->print_z < m_slicing_params.first_print_layer_height + this->m_support_layer_height_min - EPSILON; ++ j); if (j > 0) { // Merge the contact_out layers (0) to (j - 1) into the contact_out[0]. MyLayer &dst = *contact_out.front(); @@ -1377,7 +1377,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // Find the span of layers closer than m_support_layer_height_min. int j = i + 1; coordf_t zmax = contact_out[i]->print_z + m_support_layer_height_min + EPSILON; - for (; j < contact_out.size() && contact_out[j]->print_z < zmax; ++ j) ; + for (; j < (int)contact_out.size() && contact_out[j]->print_z < zmax; ++ j) ; if (i + 1 < j) { // Merge the contact_out layers (i + 1) to (j - 1) into the contact_out[i]. MyLayer &dst = *contact_out[i]; @@ -1395,7 +1395,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ contact_out[k] = contact_out[i]; i = j; } - if (k < contact_out.size()) + if (k < (int)contact_out.size()) contact_out.erase(contact_out.begin() + k, contact_out.end()); } @@ -2214,7 +2214,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf // Expand the bases of the support columns in the 1st layer. columns_base->polygons = diff( offset(columns_base->polygons, inflate_factor_1st_layer), - offset(m_object->layers().front()->slices.expolygons, scale_(m_gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS)); + offset(m_object->layers().front()->slices.expolygons, (float)scale_(m_gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS)); if (contacts != nullptr) columns_base->polygons = diff(columns_base->polygons, interface_polygons); } @@ -2566,11 +2566,11 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const { // make more loops Polygons loop_polygons = loops0; - for (size_t i = 1; i < n_contact_loops; ++ i) + for (int i = 1; i < n_contact_loops; ++ i) polygons_append(loop_polygons, offset2( loops0, - - int(i) * flow.scaled_spacing() - 0.5f * flow.scaled_spacing(), + - i * flow.scaled_spacing() - 0.5f * flow.scaled_spacing(), 0.5f * flow.scaled_spacing())); // Clip such loops to the side oriented towards the object. // Collect split points, so they will be recognized after the clipping. @@ -3226,7 +3226,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( // TODO: use brim ordering algorithm Polygons to_infill_polygons = to_polygons(to_infill); // TODO: use offset2_ex() - to_infill = offset_ex(to_infill, - 0.4 * float(flow.scaled_spacing())); + to_infill = offset_ex(to_infill, - 0.4f * float(flow.scaled_spacing())); extrusion_entities_append_paths( base_layer.extrusions, to_polylines(std::move(to_infill_polygons)), diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index a5f93b24f..ae43369d2 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -15,6 +15,8 @@ #define ENABLE_RENDER_STATISTICS 0 // Shows an imgui dialog with camera related data #define ENABLE_CAMERA_STATISTICS 0 +// Render the picking pass instead of the main scene (use [T] key to toggle between regular rendering and picking pass only rendering) +#define ENABLE_RENDER_PICKING_PASS 0 //==================== @@ -39,13 +41,4 @@ #define ENABLE_TEXTURES_FROM_SVG (1 && ENABLE_1_42_0_ALPHA7) -//==================== -// 1.42.0.alpha8 techs -//==================== -#define ENABLE_1_42_0_ALPHA8 1 - -// Toolbars and Gizmos use icons imported from svg files -#define ENABLE_SVG_ICONS (1 && ENABLE_1_42_0_ALPHA8 && ENABLE_TEXTURES_FROM_SVG) - - #endif // _technologies_h_ diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 56accfefa..fbfff90fb 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -547,9 +547,9 @@ TriangleMesh TriangleMesh::convex_hull_3d() const #if REALfloat qhull.runQhull("", 3, (int)this->its.vertices.size(), (const realT*)(this->its.vertices.front().data()), "Qt"); #else - src_vertices.reserve(this->its.vertices() * 3); + src_vertices.reserve(this->its.vertices.size() * 3); // We will now fill the vector with input points for computation: - for (const stl_vertex &v : ths->its.vertices.size()) + for (const stl_vertex &v : this->its.vertices) for (int i = 0; i < 3; ++ i) src_vertices.emplace_back(v(i)); qhull.runQhull("", 3, (int)src_vertices.size() / 3, src_vertices.data(), "Qt"); diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 054a98935..5dd2597a5 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -195,4 +195,24 @@ TriangleMesh make_sphere(double rho, double fa=(2*PI/360)); } +// Serialization through the Cereal library +#include +namespace cereal { + template struct specialize {}; + template void load(Archive &archive, Slic3r::TriangleMesh &mesh) { + stl_file &stl = mesh.stl; + stl.stats.type = inmemory; + archive(stl.stats.number_of_facets, stl.stats.original_num_facets); + stl_allocate(&stl); + archive.loadBinary((char*)stl.facet_start.data(), stl.facet_start.size() * 50); + stl_get_size(&stl); + mesh.repair(); + } + template void save(Archive &archive, const Slic3r::TriangleMesh &mesh) { + const stl_file& stl = mesh.stl; + archive(stl.stats.number_of_facets, stl.stats.original_num_facets); + archive.saveBinary((char*)stl.facet_start.data(), stl.facet_start.size() * 50); + } +} + #endif diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index e76fccdb8..3b30e981c 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -167,7 +167,7 @@ template size_t next_highest_power_of_2(T v, extern std::string xml_escape(std::string text); -#if defined __GNUC__ & __GNUC__ < 5 +#if defined __GNUC__ && __GNUC__ < 5 && !defined __clang__ // Older GCCs don't have std::is_trivially_copyable // cf. https://gcc.gnu.org/onlinedocs/gcc-4.9.4/libstdc++/manual/manual/status.html#status.iso.2011 #warning "GCC version < 5, faking std::is_trivially_copyable" @@ -182,7 +182,7 @@ class ScopeGuard public: typedef std::function Closure; private: - bool committed; +// bool committed; Closure closure; public: diff --git a/src/libslic3r/pchheader.hpp b/src/libslic3r/pchheader.hpp index b27dfe6a2..c0ffe2108 100644 --- a/src/libslic3r/pchheader.hpp +++ b/src/libslic3r/pchheader.hpp @@ -100,7 +100,10 @@ #include #include -#include +#include + +#include +#include #include "BoundingBox.hpp" #include "ClipperUtils.hpp" diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 3482f708a..519763731 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -414,13 +414,13 @@ std::string format_memsize_MB(size_t n) scale *= 1000; } char buf[8]; - sprintf(buf, "%d", n); + sprintf(buf, "%d", (int)n); out = buf; while (scale != 1) { scale /= 1000; n = n2 / scale; n2 = n2 % scale; - sprintf(buf, ",%03d", n); + sprintf(buf, ",%03d", (int)n); out += buf; } return out + "MB"; diff --git a/src/semver/semver.c b/src/semver/semver.c index 527738644..e8bd6edcf 100644 --- a/src/semver/semver.c +++ b/src/semver/semver.c @@ -22,6 +22,10 @@ static const size_t MAX_SIZE = sizeof(char) * 255; static const int MAX_SAFE_INT = (unsigned int) -1 >> 1; +#ifdef _WIN32 + #define strdup _strdup +#endif + /** * Define comparison operators, storing the * ASCII code per each symbol in hexadecimal notation. @@ -50,8 +54,8 @@ strcut (char *str, int begin, int len) { if((int)l < 0 || (int)l > MAX_SAFE_INT) return -1; - if (len < 0) len = l - begin + 1; - if (begin + len > (int)l) len = l - begin; + if (len < 0) len = (int)l - begin + 1; + if (begin + len > (int)l) len = (int)l - begin; memmove(str + begin, str + begin + len, l - len + 1 - begin); return len; @@ -104,7 +108,7 @@ parse_int (const char *s) { static char * parse_slice (char *buf, char sep) { char *pr, *part; - int plen; + size_t plen; /* Find separator in buf */ pr = strchr(buf, sep); @@ -210,8 +214,9 @@ semver_parse_version (const char *str, semver_t *ver) { static int compare_prerelease (char *x, char *y) { char *lastx, *lasty, *xptr, *yptr, *endptr; - int xlen, ylen, xisnum, yisnum, xnum, ynum; - int xn, yn, min, res; + size_t xlen, ylen, xn, yn, min; + int xisnum, yisnum, xnum, ynum; + int res; if (x == NULL && y == NULL) return 0; if (y == NULL && x) return -1; if (x == NULL && y) return 1; @@ -572,7 +577,7 @@ semver_clean (char *s) { for (i = 0; i < len; i++) { if (contains(s[i], VALID_CHARS, mlen) == 0) { - res = strcut(s, i, 1); + res = strcut(s, (int)i, 1); if(res == -1) return -1; --len; --i; } diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 570e23baa..e3a910d6d 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -81,6 +81,8 @@ set(SLIC3R_GUI_SOURCES GUI/GUI_ObjectManipulation.hpp GUI/GUI_ObjectSettings.cpp GUI/GUI_ObjectSettings.hpp + GUI/GUI_ObjectLayers.cpp + GUI/GUI_ObjectLayers.hpp GUI/LambdaObjectDialog.cpp GUI/LambdaObjectDialog.hpp GUI/Tab.cpp @@ -146,6 +148,8 @@ set(SLIC3R_GUI_SOURCES Utils/PresetUpdater.hpp Utils/Time.cpp Utils/Time.hpp + Utils/UndoRedo.cpp + Utils/UndoRedo.hpp Utils/HexFile.cpp Utils/HexFile.hpp ) @@ -159,7 +163,7 @@ endif () add_library(libslic3r_gui STATIC ${SLIC3R_GUI_SOURCES}) -target_link_libraries(libslic3r_gui libslic3r avrdude imgui ${GLEW_LIBRARIES}) +target_link_libraries(libslic3r_gui libslic3r avrdude cereal imgui ${GLEW_LIBRARIES}) if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY) add_precompiled_header(libslic3r_gui pchheader.hpp FORCEINCLUDE) endif () diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index d73e423e0..482cd58ad 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -274,8 +274,8 @@ void Bed3D::Axes::render_axis(double length) const Bed3D::Bed3D() : m_type(Custom) - , m_requires_canvas_update(false) #if ENABLE_TEXTURES_FROM_SVG + , m_requires_canvas_update(false) , m_vbo_id(0) #endif // ENABLE_TEXTURES_FROM_SVG , m_scale_factor(1.0f) @@ -330,12 +330,11 @@ Point Bed3D::point_projection(const Point& point) const } #if ENABLE_TEXTURES_FROM_SVG -void Bed3D::render(GLCanvas3D* canvas, float theta, bool useVBOs, float scale_factor) const +void Bed3D::render(GLCanvas3D* canvas, float theta, float scale_factor) const { m_scale_factor = scale_factor; - EType type = useVBOs ? m_type : Custom; - switch (type) + switch (m_type) { case MK2: { @@ -361,7 +360,7 @@ void Bed3D::render(GLCanvas3D* canvas, float theta, bool useVBOs, float scale_fa } } #else -void Bed3D::render(GLCanvas3D* canvas, float theta, bool useVBOs, float scale_factor) const +void Bed3D::render(float theta, float scale_factor) const { m_scale_factor = scale_factor; @@ -372,17 +371,17 @@ void Bed3D::render(GLCanvas3D* canvas, float theta, bool useVBOs, float scale_fa { case MK2: { - render_prusa(canvas, "mk2", theta, useVBOs); + render_prusa("mk2", theta); break; } case MK3: { - render_prusa(canvas, "mk3", theta, useVBOs); + render_prusa("mk3", theta); break; } case SL1: { - render_prusa(canvas, "sl1", theta, useVBOs); + render_prusa("sl1", theta); break; } default: @@ -546,7 +545,7 @@ void Bed3D::render_prusa(GLCanvas3D* canvas, const std::string &key, bool bottom if (!bottom) { filename = model_path + "_bed.stl"; - if ((m_model.get_filename() != filename) && m_model.init_from_file(filename, true)) { + if ((m_model.get_filename() != filename) && m_model.init_from_file(filename)) { Vec3d offset = m_bounding_box.center() - Vec3d(0.0, 0.0, 0.5 * m_model.get_bounding_box().size()(2)); if (key == "mk2") // hardcoded value to match the stl model @@ -628,12 +627,12 @@ void Bed3D::render_prusa_shader(bool transparent) const if (position_id != -1) { glsafe(::glEnableVertexAttribArray(position_id)); - glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)m_triangles.get_position_offset())); + glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)m_triangles.get_position_offset())); } if (tex_coords_id != -1) { glsafe(::glEnableVertexAttribArray(tex_coords_id)); - glsafe(::glVertexAttribPointer(tex_coords_id, 2, GL_FLOAT, GL_FALSE, stride, (GLvoid*)m_triangles.get_tex_coords_offset())); + glsafe(::glVertexAttribPointer(tex_coords_id, 2, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)m_triangles.get_tex_coords_offset())); } glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)m_triangles.get_vertices_count())); @@ -651,7 +650,7 @@ void Bed3D::render_prusa_shader(bool transparent) const } } #else -void Bed3D::render_prusa(const std::string &key, float theta, bool useVBOs) const +void Bed3D::render_prusa(const std::string& key, float theta) const { std::string tex_path = resources_dir() + "/icons/bed/" + key; @@ -677,7 +676,7 @@ void Bed3D::render_prusa(const std::string &key, float theta, bool useVBOs) cons std::string filename = tex_path + "_top.png"; if ((m_top_texture.get_id() == 0) || (m_top_texture.get_source() != filename)) { - if (!m_top_texture.load_from_file(filename, true)) + if (!m_top_texture.load_from_file(filename, true, true)) { render_custom(); return; @@ -694,7 +693,7 @@ void Bed3D::render_prusa(const std::string &key, float theta, bool useVBOs) cons filename = tex_path + "_bottom.png"; if ((m_bottom_texture.get_id() == 0) || (m_bottom_texture.get_source() != filename)) { - if (!m_bottom_texture.load_from_file(filename, true)) + if (!m_bottom_texture.load_from_file(filename, true, true)) { render_custom(); return; @@ -711,7 +710,7 @@ void Bed3D::render_prusa(const std::string &key, float theta, bool useVBOs) cons if (theta <= 90.0f) { filename = model_path + "_bed.stl"; - if ((m_model.get_filename() != filename) && m_model.init_from_file(filename, useVBOs)) { + if ((m_model.get_filename() != filename) && m_model.init_from_file(filename)) { Vec3d offset = m_bounding_box.center() - Vec3d(0.0, 0.0, 0.5 * m_model.get_bounding_box().size()(2)); if (key == "mk2") // hardcoded value to match the stl model diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index 68a74f61c..6c8913a9b 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -44,8 +44,8 @@ public: const float* get_vertices_data() const; unsigned int get_vertices_data_size() const { return (unsigned int)m_vertices.size() * get_vertex_data_size(); } unsigned int get_vertex_data_size() const { return (unsigned int)(5 * sizeof(float)); } - unsigned int get_position_offset() const { return 0; } - unsigned int get_tex_coords_offset() const { return (unsigned int)(3 * sizeof(float)); } + size_t get_position_offset() const { return 0; } + size_t get_tex_coords_offset() const { return (size_t)(3 * sizeof(float)); } unsigned int get_vertices_count() const { return (unsigned int)m_vertices.size(); } #else const float* get_vertices() const { return m_vertices.data(); } @@ -128,7 +128,11 @@ public: bool contains(const Point& point) const; Point point_projection(const Point& point) const; - void render(GLCanvas3D* canvas, float theta, bool useVBOs, float scale_factor) const; +#if ENABLE_TEXTURES_FROM_SVG + void render(GLCanvas3D* canvas, float theta, float scale_factor) const; +#else + void render(float theta, float scale_factor) const; +#endif // ENABLE_TEXTURES_FROM_SVG void render_axes() const; private: @@ -140,7 +144,7 @@ private: void render_prusa(GLCanvas3D* canvas, const std::string& key, bool bottom) const; void render_prusa_shader(bool transparent) const; #else - void render_prusa(const std::string &key, float theta, bool useVBOs) const; + void render_prusa(const std::string& key, float theta) const; #endif // ENABLE_TEXTURES_FROM_SVG void render_custom() const; #if ENABLE_TEXTURES_FROM_SVG diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index f9a79f2d8..9e3eaf41d 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -55,21 +55,6 @@ void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char namespace Slic3r { -void GLIndexedVertexArray::load_mesh_flat_shading(const TriangleMesh &mesh) -{ - assert(triangle_indices.empty() && vertices_and_normals_interleaved_size == 0); - assert(quad_indices.empty() && triangle_indices_size == 0); - assert(vertices_and_normals_interleaved.size() % 6 == 0 && quad_indices_size == vertices_and_normals_interleaved.size()); - - this->vertices_and_normals_interleaved.reserve(this->vertices_and_normals_interleaved.size() + 3 * 3 * 2 * mesh.facets_count()); - - for (int i = 0; i < (int)mesh.stl.stats.number_of_facets; ++i) { - const stl_facet &facet = mesh.stl.facet_start[i]; - for (int j = 0; j < 3; ++ j) - this->push_geometry(facet.vertex[j](0), facet.vertex[j](1), facet.vertex[j](2), facet.normal(0), facet.normal(1), facet.normal(2)); - } -} - void GLIndexedVertexArray::load_mesh_full_shading(const TriangleMesh &mesh) { assert(triangle_indices.empty() && vertices_and_normals_interleaved_size == 0); @@ -89,37 +74,35 @@ void GLIndexedVertexArray::load_mesh_full_shading(const TriangleMesh &mesh) } } -void GLIndexedVertexArray::finalize_geometry(bool use_VBOs) +void GLIndexedVertexArray::finalize_geometry() const { assert(this->vertices_and_normals_interleaved_VBO_id == 0); assert(this->triangle_indices_VBO_id == 0); assert(this->quad_indices_VBO_id == 0); - this->setup_sizes(); - - if (use_VBOs) { - if (! empty()) { - glsafe(::glGenBuffers(1, &this->vertices_and_normals_interleaved_VBO_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved_VBO_id)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved.size() * 4, this->vertices_and_normals_interleaved.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - this->vertices_and_normals_interleaved.clear(); - } - if (! this->triangle_indices.empty()) { - glsafe(::glGenBuffers(1, &this->triangle_indices_VBO_id)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_id)); - glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices.size() * 4, this->triangle_indices.data(), GL_STATIC_DRAW)); - this->triangle_indices.clear(); - } - if (! this->quad_indices.empty()) { - glsafe(::glGenBuffers(1, &this->quad_indices_VBO_id)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->quad_indices_VBO_id)); - glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->quad_indices.size() * 4, this->quad_indices.data(), GL_STATIC_DRAW)); - this->quad_indices.clear(); - } - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - } this->shrink_to_fit(); + + if (! empty()) { + glsafe(::glGenBuffers(1, &this->vertices_and_normals_interleaved_VBO_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved_VBO_id)); + glsafe(::glBufferData(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved.size() * 4, this->vertices_and_normals_interleaved.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + this->vertices_and_normals_interleaved.clear(); + } + if (! this->triangle_indices.empty()) { + glsafe(::glGenBuffers(1, &this->triangle_indices_VBO_id)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_id)); + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices.size() * 4, this->triangle_indices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + this->triangle_indices.clear(); + } + if (! this->quad_indices.empty()) { + glsafe(::glGenBuffers(1, &this->quad_indices_VBO_id)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->quad_indices_VBO_id)); + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->quad_indices.size() * 4, this->quad_indices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + this->quad_indices.clear(); + } } void GLIndexedVertexArray::release_geometry() @@ -137,89 +120,78 @@ void GLIndexedVertexArray::release_geometry() this->quad_indices_VBO_id = 0; } this->clear(); - this->shrink_to_fit(); } void GLIndexedVertexArray::render() const { - if (this->vertices_and_normals_interleaved_VBO_id) { - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved_VBO_id)); - glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float)))); - glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullptr)); - } else { - glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), this->vertices_and_normals_interleaved.data() + 3)); - glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), this->vertices_and_normals_interleaved.data())); + if (this->vertices_and_normals_interleaved_VBO_id == 0) + { + // sends data to gpu, if not done yet + finalize_geometry(); + if (this->vertices_and_normals_interleaved_VBO_id == 0) + return; } + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved_VBO_id)); + glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float)))); + glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullptr)); + glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - if (this->indexed()) { - if (this->vertices_and_normals_interleaved_VBO_id) { - // Render using the Vertex Buffer Objects. - if (this->triangle_indices_size > 0) { - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_id)); - glsafe(::glDrawElements(GL_TRIANGLES, GLsizei(this->triangle_indices_size), GL_UNSIGNED_INT, nullptr)); - } - if (this->quad_indices_size > 0) { - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->quad_indices_VBO_id)); - glsafe(::glDrawElements(GL_QUADS, GLsizei(this->quad_indices_size), GL_UNSIGNED_INT, nullptr)); - } - glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - } else { - // Render in an immediate mode. - if (! this->triangle_indices.empty()) - glsafe(::glDrawElements(GL_TRIANGLES, GLsizei(this->triangle_indices_size), GL_UNSIGNED_INT, this->triangle_indices.data())); - if (! this->quad_indices.empty()) - glsafe(::glDrawElements(GL_QUADS, GLsizei(this->quad_indices_size), GL_UNSIGNED_INT, this->quad_indices.data())); - } - } else - glsafe(::glDrawArrays(GL_TRIANGLES, 0, GLsizei(this->vertices_and_normals_interleaved_size / 6))); - - if (this->vertices_and_normals_interleaved_VBO_id) - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); -} - -void GLIndexedVertexArray::render( - const std::pair &tverts_range, - const std::pair &qverts_range) const -{ - assert(this->indexed()); - if (! this->indexed()) - return; - - if (this->vertices_and_normals_interleaved_VBO_id) { - // Render using the Vertex Buffer Objects. - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved_VBO_id)); - glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float)))); - glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullptr)); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - if (this->triangle_indices_size > 0) { - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_id)); - glsafe(::glDrawElements(GL_TRIANGLES, GLsizei(std::min(this->triangle_indices_size, tverts_range.second - tverts_range.first)), GL_UNSIGNED_INT, (const void*)(tverts_range.first * 4))); - } - if (this->quad_indices_size > 0) { - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->quad_indices_VBO_id)); - glsafe(::glDrawElements(GL_QUADS, GLsizei(std::min(this->quad_indices_size, qverts_range.second - qverts_range.first)), GL_UNSIGNED_INT, (const void*)(qverts_range.first * 4))); - } - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - } else { - // Render in an immediate mode. - glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), this->vertices_and_normals_interleaved.data() + 3)); - glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), this->vertices_and_normals_interleaved.data())); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - if (! this->triangle_indices.empty()) - glsafe(::glDrawElements(GL_TRIANGLES, GLsizei(std::min(this->triangle_indices_size, tverts_range.second - tverts_range.first)), GL_UNSIGNED_INT, (const void*)(this->triangle_indices.data() + tverts_range.first))); - if (! this->quad_indices.empty()) - glsafe(::glDrawElements(GL_QUADS, GLsizei(std::min(this->quad_indices_size, qverts_range.second - qverts_range.first)), GL_UNSIGNED_INT, (const void*)(this->quad_indices.data() + qverts_range.first))); + // Render using the Vertex Buffer Objects. + if (this->triangle_indices_size > 0) { + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_id)); + glsafe(::glDrawElements(GL_TRIANGLES, GLsizei(this->triangle_indices_size), GL_UNSIGNED_INT, nullptr)); + glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } + if (this->quad_indices_size > 0) { + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->quad_indices_VBO_id)); + glsafe(::glDrawElements(GL_QUADS, GLsizei(this->quad_indices_size), GL_UNSIGNED_INT, nullptr)); + glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); } glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); +} + +void GLIndexedVertexArray::render( + const std::pair& tverts_range, + const std::pair& qverts_range) const +{ + if (this->vertices_and_normals_interleaved_VBO_id == 0) + { + // sends data to gpu, if not done yet + finalize_geometry(); + if (this->vertices_and_normals_interleaved_VBO_id == 0) + return; + } + + // Render using the Vertex Buffer Objects. + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved_VBO_id)); + glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float)))); + glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullptr)); + + glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); + glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); + + if (this->triangle_indices_size > 0) { + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_id)); + glsafe(::glDrawElements(GL_TRIANGLES, GLsizei(std::min(this->triangle_indices_size, tverts_range.second - tverts_range.first)), GL_UNSIGNED_INT, (const void*)(tverts_range.first * 4))); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } + if (this->quad_indices_size > 0) { + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->quad_indices_VBO_id)); + glsafe(::glDrawElements(GL_QUADS, GLsizei(std::min(this->quad_indices_size, qverts_range.second - qverts_range.first)), GL_UNSIGNED_INT, (const void*)(qverts_range.first * 4))); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } + + glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); + glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); } const float GLVolume::SELECTED_COLOR[4] = { 0.0f, 1.0f, 0.0f, 1.0f }; @@ -343,11 +315,12 @@ bool GLVolume::is_left_handed() const const BoundingBoxf3& GLVolume::transformed_bounding_box() const { - assert(bounding_box.defined || bounding_box.min(0) >= bounding_box.max(0) || bounding_box.min(1) >= bounding_box.max(1) || bounding_box.min(2) >= bounding_box.max(2)); + const BoundingBoxf3& box = bounding_box(); + assert(box.defined || box.min(0) >= box.max(0) || box.min(1) >= box.max(1) || box.min(2) >= box.max(2)); if (m_transformed_bounding_box_dirty) { - m_transformed_bounding_box = bounding_box.transformed(world_matrix()); + m_transformed_bounding_box = box.transformed(world_matrix()); m_transformed_bounding_box_dirty = false; } @@ -365,14 +338,15 @@ BoundingBoxf3 GLVolume::transformed_convex_hull_bounding_box(const Transform3d & { return (m_convex_hull && m_convex_hull->stl.stats.number_of_facets > 0) ? m_convex_hull->transformed_bounding_box(trafo) : - bounding_box.transformed(trafo); + bounding_box().transformed(trafo); } + void GLVolume::set_range(double min_z, double max_z) { - this->qverts_range.first = 0; + this->qverts_range.first = 0; this->qverts_range.second = this->indexed_vertex_array.quad_indices_size; - this->tverts_range.first = 0; + this->tverts_range.first = 0; this->tverts_range.second = this->indexed_vertex_array.triangle_indices_size; if (! this->print_zs.empty()) { // The Z layer range is specified. @@ -412,58 +386,17 @@ void GLVolume::render() const glFrontFace(GL_CW); glsafe(::glCullFace(GL_BACK)); glsafe(::glPushMatrix()); - glsafe(::glMultMatrixd(world_matrix().data())); - if (this->indexed_vertex_array.indexed()) - this->indexed_vertex_array.render(this->tverts_range, this->qverts_range); - else - this->indexed_vertex_array.render(); + + this->indexed_vertex_array.render(this->tverts_range, this->qverts_range); + glsafe(::glPopMatrix()); if (this->is_left_handed()) glFrontFace(GL_CCW); } -void GLVolume::render_VBOs(int color_id, int detection_id, int worldmatrix_id) const +void GLVolume::render(int color_id, int detection_id, int worldmatrix_id) const { - if (!is_active) - return; - - if (!indexed_vertex_array.vertices_and_normals_interleaved_VBO_id) - return; - - if (this->is_left_handed()) - glFrontFace(GL_CW); - - GLsizei n_triangles = GLsizei(std::min(indexed_vertex_array.triangle_indices_size, tverts_range.second - tverts_range.first)); - GLsizei n_quads = GLsizei(std::min(indexed_vertex_array.quad_indices_size, qverts_range.second - qverts_range.first)); - if (n_triangles + n_quads == 0) - { - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - - if (color_id >= 0) - { - float color[4]; - ::memcpy((void*)color, (const void*)render_color, 4 * sizeof(float)); - glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)color)); - } - else - glsafe(::glColor4fv(render_color)); - - if (detection_id != -1) - glsafe(::glUniform1i(detection_id, shader_outside_printer_detection_enabled ? 1 : 0)); - - if (worldmatrix_id != -1) - glsafe(::glUniformMatrix4fv(worldmatrix_id, 1, GL_FALSE, (const GLfloat*)world_matrix().cast().data())); - - render(); - - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - - return; - } - if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)render_color)); else @@ -475,187 +408,110 @@ void GLVolume::render_VBOs(int color_id, int detection_id, int worldmatrix_id) c if (worldmatrix_id != -1) glsafe(::glUniformMatrix4fv(worldmatrix_id, 1, GL_FALSE, (const GLfloat*)world_matrix().cast().data())); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, indexed_vertex_array.vertices_and_normals_interleaved_VBO_id)); - glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float)))); - glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullptr)); - - glsafe(::glPushMatrix()); - - glsafe(::glMultMatrixd(world_matrix().data())); - - if (n_triangles > 0) - { - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexed_vertex_array.triangle_indices_VBO_id)); - glsafe(::glDrawElements(GL_TRIANGLES, n_triangles, GL_UNSIGNED_INT, (const void*)(tverts_range.first * 4))); - } - if (n_quads > 0) - { - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexed_vertex_array.quad_indices_VBO_id)); - glsafe(::glDrawElements(GL_QUADS, n_quads, GL_UNSIGNED_INT, (const void*)(qverts_range.first * 4))); - } - - glsafe(::glPopMatrix()); - - if (this->is_left_handed()) - glFrontFace(GL_CCW); -} - -void GLVolume::render_legacy() const -{ - assert(!indexed_vertex_array.vertices_and_normals_interleaved_VBO_id); - if (!is_active) - return; - - if (this->is_left_handed()) - glFrontFace(GL_CW); - - GLsizei n_triangles = GLsizei(std::min(indexed_vertex_array.triangle_indices_size, tverts_range.second - tverts_range.first)); - GLsizei n_quads = GLsizei(std::min(indexed_vertex_array.quad_indices_size, qverts_range.second - qverts_range.first)); - if (n_triangles + n_quads == 0) - { - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - - glsafe(::glColor4fv(render_color)); - render(); - - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - - return; - } - - glsafe(::glColor4fv(render_color)); - glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), indexed_vertex_array.vertices_and_normals_interleaved.data() + 3)); - glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), indexed_vertex_array.vertices_and_normals_interleaved.data())); - - glsafe(::glPushMatrix()); - - glsafe(::glMultMatrixd(world_matrix().data())); - - if (n_triangles > 0) - glsafe(::glDrawElements(GL_TRIANGLES, n_triangles, GL_UNSIGNED_INT, indexed_vertex_array.triangle_indices.data() + tverts_range.first)); - - if (n_quads > 0) - glsafe(::glDrawElements(GL_QUADS, n_quads, GL_UNSIGNED_INT, indexed_vertex_array.quad_indices.data() + qverts_range.first)); - - glsafe(::glPopMatrix()); - - if (this->is_left_handed()) - glFrontFace(GL_CCW); + render(); } bool GLVolume::is_sla_support() const { return this->composite_id.volume_id == -int(slaposSupportTree); } bool GLVolume::is_sla_pad() const { return this->composite_id.volume_id == -int(slaposBasePool); } std::vector GLVolumeCollection::load_object( - const ModelObject *model_object, + const ModelObject* model_object, int obj_idx, - const std::vector &instance_idxs, - const std::string &color_by, - bool use_VBOs) + const std::vector& instance_idxs, + const std::string& color_by) { std::vector volumes_idx; - for (int volume_idx = 0; volume_idx < int(model_object->volumes.size()); ++ volume_idx) + for (int volume_idx = 0; volume_idx < int(model_object->volumes.size()); ++volume_idx) for (int instance_idx : instance_idxs) - volumes_idx.emplace_back(this->GLVolumeCollection::load_object_volume(model_object, obj_idx, volume_idx, instance_idx, color_by, use_VBOs)); - return volumes_idx; + volumes_idx.emplace_back(this->GLVolumeCollection::load_object_volume(model_object, obj_idx, volume_idx, instance_idx, color_by)); + return volumes_idx; } int GLVolumeCollection::load_object_volume( - const ModelObject *model_object, + const ModelObject* model_object, int obj_idx, int volume_idx, int instance_idx, - const std::string &color_by, - bool use_VBOs) + const std::string& color_by) { - const ModelVolume *model_volume = model_object->volumes[volume_idx]; - const int extruder_id = model_volume->extruder_id(); - const ModelInstance *instance = model_object->instances[instance_idx]; + const ModelVolume* model_volume = model_object->volumes[volume_idx]; + const int extruder_id = model_volume->extruder_id(); + const ModelInstance* instance = model_object->instances[instance_idx]; const TriangleMesh& mesh = model_volume->mesh(); float color[4]; memcpy(color, GLVolume::MODEL_COLOR[((color_by == "volume") ? volume_idx : obj_idx) % 4], sizeof(float) * 3); -/* if (model_volume->is_support_blocker()) { - color[0] = 1.0f; - color[1] = 0.2f; - color[2] = 0.2f; - } else if (model_volume->is_support_enforcer()) { - color[0] = 0.2f; - color[1] = 0.2f; - color[2] = 1.0f; - } - color[3] = model_volume->is_model_part() ? 1.f : 0.5f; */ + /* if (model_volume->is_support_blocker()) { + color[0] = 1.0f; + color[1] = 0.2f; + color[2] = 0.2f; + } else if (model_volume->is_support_enforcer()) { + color[0] = 0.2f; + color[1] = 0.2f; + color[2] = 1.0f; + } + color[3] = model_volume->is_model_part() ? 1.f : 0.5f; */ color[3] = model_volume->is_model_part() ? 1.f : 0.5f; this->volumes.emplace_back(new GLVolume(color)); - GLVolume &v = *this->volumes.back(); + GLVolume& v = *this->volumes.back(); v.set_color_from_model_volume(model_volume); - v.indexed_vertex_array.load_mesh(mesh, use_VBOs); - - // finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry(). - v.bounding_box = v.indexed_vertex_array.bounding_box(); - v.indexed_vertex_array.finalize_geometry(use_VBOs); - v.composite_id = GLVolume::CompositeID(obj_idx, volume_idx, instance_idx); + v.indexed_vertex_array.load_mesh(mesh); + v.composite_id = GLVolume::CompositeID(obj_idx, volume_idx, instance_idx); if (model_volume->is_model_part()) { - // GLVolume will reference a convex hull from model_volume! + // GLVolume will reference a convex hull from model_volume! v.set_convex_hull(model_volume->get_convex_hull_shared_ptr()); if (extruder_id != -1) v.extruder_id = extruder_id; } - v.is_modifier = ! model_volume->is_model_part(); + v.is_modifier = !model_volume->is_model_part(); v.shader_outside_printer_detection_enabled = model_volume->is_model_part(); v.set_instance_transformation(instance->get_transformation()); v.set_volume_transformation(model_volume->get_transformation()); - return int(this->volumes.size() - 1); + return int(this->volumes.size() - 1); } // Load SLA auxiliary GLVolumes (for support trees or pad). // This function produces volumes for multiple instances in a single shot, // as some object specific mesh conversions may be expensive. void GLVolumeCollection::load_object_auxiliary( - const SLAPrintObject *print_object, + const SLAPrintObject* print_object, int obj_idx, // pairs of - const std::vector> &instances, + const std::vector>& instances, SLAPrintObjectStep milestone, // Timestamp of the last change of the milestone - size_t timestamp, - bool use_VBOs) + size_t timestamp) { assert(print_object->is_step_done(milestone)); Transform3d mesh_trafo_inv = print_object->trafo().inverse(); // Get the support mesh. TriangleMesh mesh = print_object->get_mesh(milestone); mesh.transform(mesh_trafo_inv); - // Convex hull is required for out of print bed detection. - TriangleMesh convex_hull = mesh.convex_hull_3d(); - for (const std::pair &instance_idx : instances) { - const ModelInstance &model_instance = *print_object->model_object()->instances[instance_idx.first]; + // Convex hull is required for out of print bed detection. + TriangleMesh convex_hull = mesh.convex_hull_3d(); + for (const std::pair& instance_idx : instances) { + const ModelInstance& model_instance = *print_object->model_object()->instances[instance_idx.first]; this->volumes.emplace_back(new GLVolume((milestone == slaposBasePool) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR)); - GLVolume &v = *this->volumes.back(); - v.indexed_vertex_array.load_mesh(mesh, use_VBOs); - // finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry(). - v.bounding_box = v.indexed_vertex_array.bounding_box(); - v.indexed_vertex_array.finalize_geometry(use_VBOs); - v.composite_id = GLVolume::CompositeID(obj_idx, - int(milestone), (int)instance_idx.first); + GLVolume& v = *this->volumes.back(); + v.indexed_vertex_array.load_mesh(mesh); + v.composite_id = GLVolume::CompositeID(obj_idx, -int(milestone), (int)instance_idx.first); v.geometry_id = std::pair(timestamp, model_instance.id().id); - // Create a copy of the convex hull mesh for each instance. Use a move operator on the last instance. + // Create a copy of the convex hull mesh for each instance. Use a move operator on the last instance. if (&instance_idx == &instances.back()) v.set_convex_hull(std::move(convex_hull)); else v.set_convex_hull(convex_hull); - v.is_modifier = false; + v.is_modifier = false; v.shader_outside_printer_detection_enabled = (milestone == slaposSupportTree); v.set_instance_transformation(model_instance.get_transformation()); - // Leave the volume transformation at identity. + // Leave the volume transformation at identity. // v.set_volume_transformation(model_volume->get_transformation()); } } int GLVolumeCollection::load_wipe_tower_preview( - int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs, bool size_unknown, float brim_width) + int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width) { if (depth < 0.01f) return int(this->volumes.size() - 1); @@ -680,45 +536,41 @@ int GLVolumeCollection::load_wipe_tower_preview( { 38.453f, 0, 1 }, { 0, 0, 1 }, { 0, -depth, 1 }, { 100.0f, -depth, 1 }, { 100.0f, 0, 1 }, { 61.547f, 0, 1 }, { 55.7735f, -10.0f, 1 }, { 44.2265f, 10.0f, 1 } }; int out_facets_idx[][3] = { { 0, 1, 2 }, { 3, 4, 5 }, { 6, 5, 0 }, { 3, 5, 6 }, { 6, 2, 7 }, { 6, 0, 2 }, { 8, 9, 10 }, { 11, 12, 13 }, { 10, 11, 14 }, { 14, 11, 13 }, { 15, 8, 14 }, {8, 10, 14}, {3, 12, 4}, {3, 13, 12}, {6, 13, 3}, {6, 14, 13}, {7, 14, 6}, {7, 15, 14}, {2, 15, 7}, {2, 8, 15}, {1, 8, 2}, {1, 9, 8}, - {0, 9, 1}, {0, 10, 9}, {5, 10, 0}, {5, 11, 10}, {4, 11, 5}, {4, 12, 11}}; - for (int i=0;i<16;++i) - points.push_back(Vec3d(out_points_idx[i][0] / (100.f/min_width), out_points_idx[i][1] + depth, out_points_idx[i][2])); - for (int i=0;i<28;++i) + {0, 9, 1}, {0, 10, 9}, {5, 10, 0}, {5, 11, 10}, {4, 11, 5}, {4, 12, 11} }; + for (int i = 0; i < 16; ++i) + points.push_back(Vec3d(out_points_idx[i][0] / (100.f / min_width), out_points_idx[i][1] + depth, out_points_idx[i][2])); + for (int i = 0; i < 28; ++i) facets.push_back(Vec3crd(out_facets_idx[i][0], out_facets_idx[i][1], out_facets_idx[i][2])); TriangleMesh tooth_mesh(points, facets); // We have the mesh ready. It has one tooth and width of min_width. We will now append several of these together until we are close to // the required width of the block. Than we can scale it precisely. - size_t n = std::max(1, int(width/min_width)); // How many shall be merged? - for (size_t i=0;ivolumes.emplace_back(new GLVolume(color)); - GLVolume &v = *this->volumes.back(); - v.indexed_vertex_array.load_mesh(mesh, use_VBOs); + GLVolume& v = *this->volumes.back(); + v.indexed_vertex_array.load_mesh(mesh); v.set_volume_offset(Vec3d(pos_x, pos_y, 0.0)); - v.set_volume_rotation(Vec3d(0., 0., (M_PI/180.) * rotation_angle)); - - // finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry(). - v.bounding_box = v.indexed_vertex_array.bounding_box(); - v.indexed_vertex_array.finalize_geometry(use_VBOs); - v.composite_id = GLVolume::CompositeID(obj_idx, 0, 0); + v.set_volume_rotation(Vec3d(0., 0., (M_PI / 180.) * rotation_angle)); + v.composite_id = GLVolume::CompositeID(obj_idx, 0, 0); v.geometry_id.first = 0; v.geometry_id.second = wipe_tower_instance_id().id; v.is_wipe_tower = true; - v.shader_outside_printer_detection_enabled = ! size_unknown; + v.shader_outside_printer_detection_enabled = !size_unknown; return int(this->volumes.size() - 1); } @@ -742,7 +594,7 @@ GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCo { for (GLVolumeWithIdAndZ& volume : list) { - volume.second.second = volume.first->bounding_box.transformed(view_matrix * volume.first->world_matrix()).max(2); + volume.second.second = volume.first->bounding_box().transformed(view_matrix * volume.first->world_matrix()).max(2); } std::sort(list.begin(), list.end(), @@ -759,7 +611,7 @@ GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCo return list; } -void GLVolumeCollection::render_VBOs(GLVolumeCollection::ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function filter_func) const +void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function filter_func) const { glsafe(::glEnable(GL_BLEND)); glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); @@ -797,7 +649,7 @@ void GLVolumeCollection::render_VBOs(GLVolumeCollection::ERenderType type, bool GLVolumeWithIdAndZList to_render = volumes_to_render(this->volumes, type, view_matrix, filter_func); for (GLVolumeWithIdAndZ& volume : to_render) { volume.first->set_render_color(); - volume.first->render_VBOs(color_id, print_box_detection_id, print_box_worldmatrix_id); + volume.first->render(color_id, print_box_detection_id, print_box_worldmatrix_id); } glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); @@ -812,34 +664,6 @@ void GLVolumeCollection::render_VBOs(GLVolumeCollection::ERenderType type, bool glsafe(::glDisable(GL_BLEND)); } -void GLVolumeCollection::render_legacy(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function filter_func) const -{ - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - - glsafe(::glCullFace(GL_BACK)); - if (disable_cullface) - glsafe(::glDisable(GL_CULL_FACE)); - - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - - GLVolumeWithIdAndZList to_render = volumes_to_render(this->volumes, type, view_matrix, filter_func); - for (GLVolumeWithIdAndZ& volume : to_render) - { - volume.first->set_render_color(); - volume.first->render_legacy(); - } - - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - - if (disable_cullface) - glsafe(::glEnable(GL_CULL_FACE)); - - glsafe(::glDisable(GL_BLEND)); -} - bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, ModelInstance::EPrintVolumeState* out_state) { if (config == nullptr) @@ -1020,19 +844,18 @@ static void thick_lines_to_indexed_vertex_array( // right, left, top, bottom int idx_prev[4] = { -1, -1, -1, -1 }; double bottom_z_prev = 0.; - Vec2d b1_prev(Vec2d::Zero()); - Vec2d v_prev(Vec2d::Zero()); + Vec2d b1_prev(Vec2d::Zero()); + Vec2d v_prev(Vec2d::Zero()); int idx_initial[4] = { -1, -1, -1, -1 }; double width_initial = 0.; double bottom_z_initial = 0.0; + double len_prev = 0.0; // loop once more in case of closed loops size_t lines_end = closed ? (lines.size() + 1) : lines.size(); for (size_t ii = 0; ii < lines_end; ++ ii) { size_t i = (ii == lines.size()) ? 0 : ii; const Line &line = lines[i]; - double len = unscale(line.length()); - double inv_len = 1.0 / len; double bottom_z = top_z - heights[i]; double middle_z = 0.5 * (top_z + bottom_z); double width = widths[i]; @@ -1041,8 +864,8 @@ static void thick_lines_to_indexed_vertex_array( bool is_last = (ii == lines_end - 1); bool is_closing = closed && is_last; - Vec2d v = unscale(line.vector()); - v *= inv_len; + Vec2d v = unscale(line.vector()).normalized(); + double len = unscale(line.length()); Vec2d a = unscale(line.a); Vec2d b = unscale(line.b); @@ -1061,9 +884,7 @@ static void thick_lines_to_indexed_vertex_array( } // calculate new XY normals - Vector n = line.normal(); - Vec3d xy_right_normal = unscale(n(0), n(1), 0); - xy_right_normal *= inv_len; + Vec2d xy_right_normal = unscale(line.normal()).normalized(); int idx_a[4]; int idx_b[4]; @@ -1091,9 +912,9 @@ static void thick_lines_to_indexed_vertex_array( idx_a[BOTTOM] = idx_last ++; volume.push_geometry(a(0), a(1), bottom_z, 0., 0., -1.); idx_a[LEFT ] = idx_last ++; - volume.push_geometry(a2(0), a2(1), middle_z, -xy_right_normal(0), -xy_right_normal(1), -xy_right_normal(2)); + volume.push_geometry(a2(0), a2(1), middle_z, -xy_right_normal(0), -xy_right_normal(1), 0.0); idx_a[RIGHT] = idx_last ++; - volume.push_geometry(a1(0), a1(1), middle_z, xy_right_normal(0), xy_right_normal(1), xy_right_normal(2)); + volume.push_geometry(a1(0), a1(1), middle_z, xy_right_normal(0), xy_right_normal(1), 0.0); } else { idx_a[BOTTOM] = idx_prev[BOTTOM]; @@ -1108,18 +929,36 @@ static void thick_lines_to_indexed_vertex_array( // Continuing a previous segment. // Share left / right vertices if possible. double v_dot = v_prev.dot(v); - bool sharp = v_dot < 0.707; // sin(45 degrees) + // To reduce gpu memory usage, we try to reuse vertices + // To reduce the visual artifacts, due to averaged normals, we allow to reuse vertices only when any of two adjacent edges + // is longer than a fixed threshold. + // The following value is arbitrary, it comes from tests made on a bunch of models showing the visual artifacts + double len_threshold = 2.5; + + // Generate new vertices if the angle between adjacent edges is greater than 45 degrees or thresholds conditions are met + bool sharp = (v_dot < 0.707) || (len_prev > len_threshold) || (len > len_threshold); if (sharp) { if (!bottom_z_different) { // Allocate new left / right points for the start of this segment as these points will receive their own normals to indicate a sharp turn. idx_a[RIGHT] = idx_last++; - volume.push_geometry(a1(0), a1(1), middle_z, xy_right_normal(0), xy_right_normal(1), xy_right_normal(2)); + volume.push_geometry(a1(0), a1(1), middle_z, xy_right_normal(0), xy_right_normal(1), 0.0); idx_a[LEFT] = idx_last++; - volume.push_geometry(a2(0), a2(1), middle_z, -xy_right_normal(0), -xy_right_normal(1), -xy_right_normal(2)); + volume.push_geometry(a2(0), a2(1), middle_z, -xy_right_normal(0), -xy_right_normal(1), 0.0); + if (cross2(v_prev, v) > 0.) { + // Right turn. Fill in the right turn wedge. + volume.push_triangle(idx_prev[RIGHT], idx_a[RIGHT], idx_prev[TOP]); + volume.push_triangle(idx_prev[RIGHT], idx_prev[BOTTOM], idx_a[RIGHT]); + } + else { + // Left turn. Fill in the left turn wedge. + volume.push_triangle(idx_prev[LEFT], idx_prev[TOP], idx_a[LEFT]); + volume.push_triangle(idx_prev[LEFT], idx_a[LEFT], idx_prev[BOTTOM]); + } } } - if (v_dot > 0.9) { + else + { if (!bottom_z_different) { // The two successive segments are nearly collinear. @@ -1127,45 +966,6 @@ static void thick_lines_to_indexed_vertex_array( idx_a[RIGHT] = idx_prev[RIGHT]; } } - else if (!sharp) { - if (!bottom_z_different) - { - // Create a sharp corner with an overshot and average the left / right normals. - // At the crease angle of 45 degrees, the overshot at the corner will be less than (1-1/cos(PI/8)) = 8.2% over an arc. - Vec2d intersection(Vec2d::Zero()); - Geometry::ray_ray_intersection(b1_prev, v_prev, a1, v, intersection); - a1 = intersection; - a2 = 2. * a - intersection; - assert((a - a1).norm() < width); - assert((a - a2).norm() < width); - float *n_left_prev = volume.vertices_and_normals_interleaved.data() + idx_prev[LEFT ] * 6; - float *p_left_prev = n_left_prev + 3; - float *n_right_prev = volume.vertices_and_normals_interleaved.data() + idx_prev[RIGHT] * 6; - float *p_right_prev = n_right_prev + 3; - p_left_prev [0] = float(a2(0)); - p_left_prev [1] = float(a2(1)); - p_right_prev[0] = float(a1(0)); - p_right_prev[1] = float(a1(1)); - xy_right_normal(0) += n_right_prev[0]; - xy_right_normal(1) += n_right_prev[1]; - xy_right_normal *= 1. / xy_right_normal.norm(); - n_left_prev [0] = float(-xy_right_normal(0)); - n_left_prev [1] = float(-xy_right_normal(1)); - n_right_prev[0] = float( xy_right_normal(0)); - n_right_prev[1] = float( xy_right_normal(1)); - idx_a[LEFT ] = idx_prev[LEFT ]; - idx_a[RIGHT] = idx_prev[RIGHT]; - } - } - else if (cross2(v_prev, v) > 0.) { - // Right turn. Fill in the right turn wedge. - volume.push_triangle(idx_prev[RIGHT], idx_a [RIGHT], idx_prev[TOP] ); - volume.push_triangle(idx_prev[RIGHT], idx_prev[BOTTOM], idx_a [RIGHT] ); - } else { - // Left turn. Fill in the left turn wedge. - volume.push_triangle(idx_prev[LEFT], idx_prev[TOP], idx_a [LEFT] ); - volume.push_triangle(idx_prev[LEFT], idx_a [LEFT], idx_prev[BOTTOM]); - } if (is_closing) { if (!sharp) { if (!bottom_z_different) @@ -1204,14 +1004,15 @@ static void thick_lines_to_indexed_vertex_array( } // Generate new vertices for the end of this line segment. idx_b[LEFT ] = idx_last ++; - volume.push_geometry(b2(0), b2(1), middle_z, -xy_right_normal(0), -xy_right_normal(1), -xy_right_normal(2)); + volume.push_geometry(b2(0), b2(1), middle_z, -xy_right_normal(0), -xy_right_normal(1), 0.0); idx_b[RIGHT ] = idx_last ++; - volume.push_geometry(b1(0), b1(1), middle_z, xy_right_normal(0), xy_right_normal(1), xy_right_normal(2)); + volume.push_geometry(b1(0), b1(1), middle_z, xy_right_normal(0), xy_right_normal(1), 0.0); memcpy(idx_prev, idx_b, 4 * sizeof(int)); bottom_z_prev = bottom_z; b1_prev = b1; v_prev = v; + len_prev = len; if (bottom_z_different && (closed || (!is_first && !is_last))) { @@ -1265,9 +1066,10 @@ static void thick_lines_to_indexed_vertex_array(const Lines3& lines, int idx_initial[4] = { -1, -1, -1, -1 }; int idx_prev[4] = { -1, -1, -1, -1 }; double z_prev = 0.0; - Vec3d n_right_prev = Vec3d::Zero(); - Vec3d n_top_prev = Vec3d::Zero(); - Vec3d unit_v_prev = Vec3d::Zero(); + double len_prev = 0.0; + Vec3d n_right_prev = Vec3d::Zero(); + Vec3d n_top_prev = Vec3d::Zero(); + Vec3d unit_v_prev = Vec3d::Zero(); double width_initial = 0.0; // new vertices around the line endpoints @@ -1286,21 +1088,23 @@ static void thick_lines_to_indexed_vertex_array(const Lines3& lines, double width = widths[i]; Vec3d unit_v = unscale(line.vector()).normalized(); + double len = unscale(line.length()); Vec3d n_top = Vec3d::Zero(); Vec3d n_right = Vec3d::Zero(); - Vec3d unit_positive_z(0.0, 0.0, 1.0); - + if ((line.a(0) == line.b(0)) && (line.a(1) == line.b(1))) { // vertical segment - n_right = (line.a(2) < line.b(2)) ? Vec3d(-1.0, 0.0, 0.0) : Vec3d(1.0, 0.0, 0.0); - n_top = Vec3d(0.0, 1.0, 0.0); + n_top = Vec3d::UnitY(); + n_right = Vec3d::UnitX(); + if (line.a(2) < line.b(2)) + n_right = -n_right; } else { - // generic segment - n_right = unit_v.cross(unit_positive_z).normalized(); + // horizontal segment + n_right = unit_v.cross(Vec3d::UnitZ()).normalized(); n_top = n_right.cross(unit_v).normalized(); } @@ -1361,9 +1165,16 @@ static void thick_lines_to_indexed_vertex_array(const Lines3& lines, // Continuing a previous segment. // Share left / right vertices if possible. double v_dot = unit_v_prev.dot(unit_v); - bool is_sharp = v_dot < 0.707; // sin(45 degrees) bool is_right_turn = n_top_prev.dot(unit_v_prev.cross(unit_v)) > 0.0; + // To reduce gpu memory usage, we try to reuse vertices + // To reduce the visual artifacts, due to averaged normals, we allow to reuse vertices only when any of two adjacent edges + // is longer than a fixed threshold. + // The following value is arbitrary, it comes from tests made on a bunch of models showing the visual artifacts + double len_threshold = 2.5; + + // Generate new vertices if the angle between adjacent edges is greater than 45 degrees or thresholds conditions are met + bool is_sharp = (v_dot < 0.707) || (len_prev > len_threshold) || (len > len_threshold); if (is_sharp) { // Allocate new left / right points for the start of this segment as these points will receive their own normals to indicate a sharp turn. @@ -1371,65 +1182,26 @@ static void thick_lines_to_indexed_vertex_array(const Lines3& lines, volume.push_geometry(a[RIGHT], n_right); idx_a[LEFT] = idx_last++; volume.push_geometry(a[LEFT], n_left); - } - if (v_dot > 0.9) + if (is_right_turn) + { + // Right turn. Fill in the right turn wedge. + volume.push_triangle(idx_prev[RIGHT], idx_a[RIGHT], idx_prev[TOP]); + volume.push_triangle(idx_prev[RIGHT], idx_prev[BOTTOM], idx_a[RIGHT]); + } + else + { + // Left turn. Fill in the left turn wedge. + volume.push_triangle(idx_prev[LEFT], idx_prev[TOP], idx_a[LEFT]); + volume.push_triangle(idx_prev[LEFT], idx_a[LEFT], idx_prev[BOTTOM]); + } + } + else { // The two successive segments are nearly collinear. idx_a[LEFT] = idx_prev[LEFT]; idx_a[RIGHT] = idx_prev[RIGHT]; } - else if (!is_sharp) - { - // Create a sharp corner with an overshot and average the left / right normals. - // At the crease angle of 45 degrees, the overshot at the corner will be less than (1-1/cos(PI/8)) = 8.2% over an arc. - - // averages normals - Vec3d average_n_right = 0.5 * (n_right + n_right_prev).normalized(); - Vec3d average_n_left = -average_n_right; - Vec3d average_rl_displacement = 0.5 * width * average_n_right; - - // updates vertices around a - a[RIGHT] = l_a + average_rl_displacement; - a[LEFT] = l_a - average_rl_displacement; - - // updates previous line normals - float* normal_left_prev = volume.vertices_and_normals_interleaved.data() + idx_prev[LEFT] * 6; - normal_left_prev[0] = float(average_n_left(0)); - normal_left_prev[1] = float(average_n_left(1)); - normal_left_prev[2] = float(average_n_left(2)); - - float* normal_right_prev = volume.vertices_and_normals_interleaved.data() + idx_prev[RIGHT] * 6; - normal_right_prev[0] = float(average_n_right(0)); - normal_right_prev[1] = float(average_n_right(1)); - normal_right_prev[2] = float(average_n_right(2)); - - // updates previous line's vertices around b - float* b_left_prev = normal_left_prev + 3; - b_left_prev[0] = float(a[LEFT](0)); - b_left_prev[1] = float(a[LEFT](1)); - b_left_prev[2] = float(a[LEFT](2)); - - float* b_right_prev = normal_right_prev + 3; - b_right_prev[0] = float(a[RIGHT](0)); - b_right_prev[1] = float(a[RIGHT](1)); - b_right_prev[2] = float(a[RIGHT](2)); - - idx_a[LEFT] = idx_prev[LEFT]; - idx_a[RIGHT] = idx_prev[RIGHT]; - } - else if (is_right_turn) - { - // Right turn. Fill in the right turn wedge. - volume.push_triangle(idx_prev[RIGHT], idx_a[RIGHT], idx_prev[TOP]); - volume.push_triangle(idx_prev[RIGHT], idx_prev[BOTTOM], idx_a[RIGHT]); - } - else - { - // Left turn. Fill in the left turn wedge. - volume.push_triangle(idx_prev[LEFT], idx_prev[TOP], idx_a[LEFT]); - volume.push_triangle(idx_prev[LEFT], idx_a[LEFT], idx_prev[BOTTOM]); - } if (ii == lines.size()) { @@ -1481,6 +1253,7 @@ static void thick_lines_to_indexed_vertex_array(const Lines3& lines, n_right_prev = n_right; n_top_prev = n_top; unit_v_prev = unit_v; + len_prev = len; if (!closed) { @@ -1694,8 +1467,7 @@ void _3DScene::point3_to_verts(const Vec3crd& point, double width, double height GUI::GLCanvas3DManager _3DScene::s_canvas_mgr; GLModel::GLModel() - : m_useVBOs(false) - , m_filename("") + : m_filename("") { m_volume.shader_outside_printer_detection_enabled = false; } @@ -1743,19 +1515,11 @@ void GLModel::set_scale(const Vec3d& scale) void GLModel::reset() { - m_volume.release_geometry(); + m_volume.indexed_vertex_array.release_geometry(); m_filename = ""; } void GLModel::render() const -{ - if (m_useVBOs) - render_VBOs(); - else - render_legacy(); -} - -void GLModel::render_VBOs() const { glsafe(::glEnable(GL_BLEND)); glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); @@ -1768,7 +1532,8 @@ void GLModel::render_VBOs() const glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, ¤t_program_id)); GLint color_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "uniform_color") : -1; glcheck(); - m_volume.render_VBOs(color_id, -1, -1); + + m_volume.render(color_id, -1, -1); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); @@ -1779,26 +1544,7 @@ void GLModel::render_VBOs() const glsafe(::glDisable(GL_BLEND)); } -void GLModel::render_legacy() const -{ - glsafe(::glEnable(GL_LIGHTING)); - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - - glsafe(::glCullFace(GL_BACK)); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - - m_volume.render_legacy(); - - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - - glsafe(::glDisable(GL_BLEND)); - glsafe(::glDisable(GL_LIGHTING)); -} - -bool GLArrow::on_init(bool useVBOs) +bool GLArrow::on_init() { Pointf3s vertices; std::vector triangles; @@ -1851,9 +1597,7 @@ bool GLArrow::on_init(bool useVBOs) triangles.emplace_back(6, 0, 7); triangles.emplace_back(7, 13, 6); - m_useVBOs = useVBOs; - m_volume.indexed_vertex_array.load_mesh(TriangleMesh(vertices, triangles), useVBOs); - m_volume.finalize_geometry(m_useVBOs); + m_volume.indexed_vertex_array.load_mesh(TriangleMesh(vertices, triangles)); return true; } @@ -1865,7 +1609,7 @@ GLCurvedArrow::GLCurvedArrow(unsigned int resolution) m_resolution = 1; } -bool GLCurvedArrow::on_init(bool useVBOs) +bool GLCurvedArrow::on_init() { Pointf3s vertices; std::vector triangles; @@ -1966,14 +1710,11 @@ bool GLCurvedArrow::on_init(bool useVBOs) triangles.emplace_back(vertices_per_level, vertices_per_level + 1, 0); triangles.emplace_back(vertices_per_level, 2 * vertices_per_level + 1, vertices_per_level + 1); - m_useVBOs = useVBOs; - m_volume.indexed_vertex_array.load_mesh(TriangleMesh(vertices, triangles), useVBOs); - m_volume.bounding_box = m_volume.indexed_vertex_array.bounding_box(); - m_volume.finalize_geometry(m_useVBOs); + m_volume.indexed_vertex_array.load_mesh(TriangleMesh(vertices, triangles)); return true; } -bool GLBed::on_init_from_file(const std::string& filename, bool useVBOs) +bool GLBed::on_init_from_file(const std::string& filename) { reset(); @@ -1994,7 +1735,6 @@ bool GLBed::on_init_from_file(const std::string& filename, bool useVBOs) } m_filename = filename; - m_useVBOs = useVBOs; ModelObject* model_object = model.objects.front(); model_object->center_around_origin(); @@ -2002,14 +1742,11 @@ bool GLBed::on_init_from_file(const std::string& filename, bool useVBOs) TriangleMesh mesh = model.mesh(); mesh.repair(); - m_volume.indexed_vertex_array.load_mesh(mesh, useVBOs); + m_volume.indexed_vertex_array.load_mesh(mesh); float color[4] = { 0.235f, 0.235f, 0.235f, 1.0f }; set_color(color, 4); - m_volume.bounding_box = m_volume.indexed_vertex_array.bounding_box(); - m_volume.finalize_geometry(m_useVBOs); - return true; } diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index d197372ab..8ae57eeae 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -56,7 +56,7 @@ public: vertices_and_normals_interleaved_VBO_id(0), triangle_indices_VBO_id(0), quad_indices_VBO_id(0) - { this->setup_sizes(); } + {} GLIndexedVertexArray(const GLIndexedVertexArray &rhs) : vertices_and_normals_interleaved(rhs.vertices_and_normals_interleaved), triangle_indices(rhs.triangle_indices), @@ -64,7 +64,7 @@ public: vertices_and_normals_interleaved_VBO_id(0), triangle_indices_VBO_id(0), quad_indices_VBO_id(0) - { this->setup_sizes(); } + {} GLIndexedVertexArray(GLIndexedVertexArray &&rhs) : vertices_and_normals_interleaved(std::move(rhs.vertices_and_normals_interleaved)), triangle_indices(std::move(rhs.triangle_indices)), @@ -72,7 +72,9 @@ public: vertices_and_normals_interleaved_VBO_id(0), triangle_indices_VBO_id(0), quad_indices_VBO_id(0) - { this->setup_sizes(); } + {} + + ~GLIndexedVertexArray() { release_geometry(); } GLIndexedVertexArray& operator=(const GLIndexedVertexArray &rhs) { @@ -82,7 +84,10 @@ public: this->vertices_and_normals_interleaved = rhs.vertices_and_normals_interleaved; this->triangle_indices = rhs.triangle_indices; this->quad_indices = rhs.quad_indices; - this->setup_sizes(); + this->m_bounding_box = rhs.m_bounding_box; + vertices_and_normals_interleaved_size = rhs.vertices_and_normals_interleaved_size; + triangle_indices_size = rhs.triangle_indices_size; + quad_indices_size = rhs.quad_indices_size; return *this; } @@ -94,30 +99,32 @@ public: this->vertices_and_normals_interleaved = std::move(rhs.vertices_and_normals_interleaved); this->triangle_indices = std::move(rhs.triangle_indices); this->quad_indices = std::move(rhs.quad_indices); - this->setup_sizes(); + this->m_bounding_box = std::move(rhs.m_bounding_box); + vertices_and_normals_interleaved_size = rhs.vertices_and_normals_interleaved_size; + triangle_indices_size = rhs.triangle_indices_size; + quad_indices_size = rhs.quad_indices_size; return *this; } // Vertices and their normals, interleaved to be used by void glInterleavedArrays(GL_N3F_V3F, 0, x) - std::vector vertices_and_normals_interleaved; - std::vector triangle_indices; - std::vector quad_indices; + mutable std::vector vertices_and_normals_interleaved; + mutable std::vector triangle_indices; + mutable std::vector quad_indices; // When the geometry data is loaded into the graphics card as Vertex Buffer Objects, // the above mentioned std::vectors are cleared and the following variables keep their original length. - size_t vertices_and_normals_interleaved_size; - size_t triangle_indices_size; - size_t quad_indices_size; + size_t vertices_and_normals_interleaved_size{ 0 }; + size_t triangle_indices_size{ 0 }; + size_t quad_indices_size{ 0 }; // IDs of the Vertex Array Objects, into which the geometry has been loaded. - // Zero if the VBOs are not used. - unsigned int vertices_and_normals_interleaved_VBO_id; - unsigned int triangle_indices_VBO_id; - unsigned int quad_indices_VBO_id; + // Zero if the VBOs are not sent to GPU yet. + mutable unsigned int vertices_and_normals_interleaved_VBO_id{ 0 }; + mutable unsigned int triangle_indices_VBO_id{ 0 }; + mutable unsigned int quad_indices_VBO_id{ 0 }; - void load_mesh_flat_shading(const TriangleMesh &mesh); void load_mesh_full_shading(const TriangleMesh &mesh); - void load_mesh(const TriangleMesh &mesh, bool use_VBOs) { use_VBOs ? this->load_mesh_full_shading(mesh) : this->load_mesh_flat_shading(mesh); } + void load_mesh(const TriangleMesh& mesh) { this->load_mesh_full_shading(mesh); } inline bool has_VBOs() const { return vertices_and_normals_interleaved_VBO_id != 0; } @@ -128,6 +135,10 @@ public: } inline void push_geometry(float x, float y, float z, float nx, float ny, float nz) { + assert(this->vertices_and_normals_interleaved_VBO_id == 0); + if (this->vertices_and_normals_interleaved_VBO_id != 0) + return; + if (this->vertices_and_normals_interleaved.size() + 6 > this->vertices_and_normals_interleaved.capacity()) this->vertices_and_normals_interleaved.reserve(next_highest_power_of_2(this->vertices_and_normals_interleaved.size() + 6)); this->vertices_and_normals_interleaved.push_back(nx); @@ -136,6 +147,9 @@ public: this->vertices_and_normals_interleaved.push_back(x); this->vertices_and_normals_interleaved.push_back(y); this->vertices_and_normals_interleaved.push_back(z); + + this->vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size(); + m_bounding_box.merge(Vec3f(x, y, z).cast()); }; inline void push_geometry(double x, double y, double z, double nx, double ny, double nz) { @@ -147,80 +161,66 @@ public: } inline void push_triangle(int idx1, int idx2, int idx3) { + assert(this->vertices_and_normals_interleaved_VBO_id == 0); + if (this->vertices_and_normals_interleaved_VBO_id != 0) + return; + if (this->triangle_indices.size() + 3 > this->vertices_and_normals_interleaved.capacity()) this->triangle_indices.reserve(next_highest_power_of_2(this->triangle_indices.size() + 3)); this->triangle_indices.push_back(idx1); this->triangle_indices.push_back(idx2); this->triangle_indices.push_back(idx3); + this->triangle_indices_size = this->triangle_indices.size(); }; inline void push_quad(int idx1, int idx2, int idx3, int idx4) { + assert(this->vertices_and_normals_interleaved_VBO_id == 0); + if (this->vertices_and_normals_interleaved_VBO_id != 0) + return; + if (this->quad_indices.size() + 4 > this->vertices_and_normals_interleaved.capacity()) this->quad_indices.reserve(next_highest_power_of_2(this->quad_indices.size() + 4)); this->quad_indices.push_back(idx1); this->quad_indices.push_back(idx2); this->quad_indices.push_back(idx3); this->quad_indices.push_back(idx4); + this->quad_indices_size = this->quad_indices.size(); }; // Finalize the initialization of the geometry & indices, // upload the geometry and indices to OpenGL VBO objects // and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs. - void finalize_geometry(bool use_VBOs); + void finalize_geometry() const; // Release the geometry data, release OpenGL VBOs. void release_geometry(); - // Render either using an immediate mode, or the VBOs. + void render() const; - void render(const std::pair &tverts_range, const std::pair &qverts_range) const; + void render(const std::pair& tverts_range, const std::pair& qverts_range) const; // Is there any geometry data stored? bool empty() const { return vertices_and_normals_interleaved_size == 0; } - // Is this object indexed, or is it just a set of triangles? - bool indexed() const { return ! this->empty() && this->triangle_indices_size + this->quad_indices_size > 0; } - void clear() { this->vertices_and_normals_interleaved.clear(); this->triangle_indices.clear(); this->quad_indices.clear(); - this->setup_sizes(); + this->m_bounding_box.reset(); + vertices_and_normals_interleaved_size = 0; + triangle_indices_size = 0; + quad_indices_size = 0; } // Shrink the internal storage to tighly fit the data stored. - void shrink_to_fit() { - if (! this->has_VBOs()) - this->setup_sizes(); + void shrink_to_fit() const { this->vertices_and_normals_interleaved.shrink_to_fit(); this->triangle_indices.shrink_to_fit(); this->quad_indices.shrink_to_fit(); } - BoundingBoxf3 bounding_box() const { - BoundingBoxf3 bbox; - if (! this->vertices_and_normals_interleaved.empty()) { - bbox.defined = true; - bbox.min(0) = bbox.max(0) = this->vertices_and_normals_interleaved[3]; - bbox.min(1) = bbox.max(1) = this->vertices_and_normals_interleaved[4]; - bbox.min(2) = bbox.max(2) = this->vertices_and_normals_interleaved[5]; - for (size_t i = 9; i < this->vertices_and_normals_interleaved.size(); i += 6) { - const float *verts = this->vertices_and_normals_interleaved.data() + i; - bbox.min(0) = std::min(bbox.min(0), verts[0]); - bbox.min(1) = std::min(bbox.min(1), verts[1]); - bbox.min(2) = std::min(bbox.min(2), verts[2]); - bbox.max(0) = std::max(bbox.max(0), verts[0]); - bbox.max(1) = std::max(bbox.max(1), verts[1]); - bbox.max(2) = std::max(bbox.max(2), verts[2]); - } - } - return bbox; - } + const BoundingBoxf3& bounding_box() const { return m_bounding_box; } private: - inline void setup_sizes() { - vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size(); - triangle_indices_size = this->triangle_indices.size(); - quad_indices_size = this->quad_indices.size(); - } + BoundingBoxf3 m_bounding_box; }; class GLVolume { @@ -263,8 +263,6 @@ private: mutable bool m_transformed_convex_hull_bounding_box_dirty; public: - // Bounding box of this volume, in unscaled coordinates. - BoundingBoxf3 bounding_box; // Color of the triangles / quads held by this volume. float color[4]; // Color used to render this volume. @@ -329,6 +327,9 @@ public: // Offset into qverts & tverts, or offsets into indices stored into an OpenGL name_index_buffer. std::vector offsets; + // Bounding box of this volume, in unscaled coordinates. + const BoundingBoxf3& bounding_box() const { return this->indexed_vertex_array.bounding_box(); } + void set_render_color(float r, float g, float b, float a); void set_render_color(const float* rgba, unsigned int size); // Sets render color in dependence of current state @@ -409,16 +410,17 @@ public: BoundingBoxf3 transformed_convex_hull_bounding_box(const Transform3d &trafo) const; // caching variant const BoundingBoxf3& transformed_convex_hull_bounding_box() const; + // convex hull + const TriangleMesh* convex_hull() const { return m_convex_hull.get(); } bool empty() const { return this->indexed_vertex_array.empty(); } - bool indexed() const { return this->indexed_vertex_array.indexed(); } void set_range(coordf_t low, coordf_t high); - void render() const; - void render_VBOs(int color_id, int detection_id, int worldmatrix_id) const; - void render_legacy() const; - void finalize_geometry(bool use_VBOs) { this->indexed_vertex_array.finalize_geometry(use_VBOs); } + void render() const; + void render(int color_id, int detection_id, int worldmatrix_id) const; + + void finalize_geometry() { this->indexed_vertex_array.finalize_geometry(); } void release_geometry() { this->indexed_vertex_array.release_geometry(); } void set_bounding_boxes_as_dirty() { m_transformed_bounding_box_dirty = true; m_transformed_convex_hull_bounding_box_dirty = true; } @@ -459,42 +461,38 @@ public: ~GLVolumeCollection() { clear(); }; std::vector load_object( - const ModelObject *model_object, + const ModelObject* model_object, int obj_idx, - const std::vector &instance_idxs, - const std::string &color_by, - bool use_VBOs); + const std::vector& instance_idxs, + const std::string& color_by); int load_object_volume( - const ModelObject *model_object, + const ModelObject* model_object, int obj_idx, int volume_idx, int instance_idx, - const std::string &color_by, - bool use_VBOs); + const std::string& color_by); // Load SLA auxiliary GLVolumes (for support trees or pad). void load_object_auxiliary( - const SLAPrintObject *print_object, + const SLAPrintObject* print_object, int obj_idx, // pairs of - const std::vector> &instances, + const std::vector>& instances, SLAPrintObjectStep milestone, // Timestamp of the last change of the milestone - size_t timestamp, - bool use_VBOs); + size_t timestamp); int load_wipe_tower_preview( - int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs, bool size_unknown, float brim_width); + int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width); // Render the volumes by OpenGL. - void render_VBOs(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function filter_func = std::function()) const; - void render_legacy(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function filter_func = std::function()) const; + void render(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function filter_func = std::function()) const; // Finalize the initialization of the geometry & indices, // upload the geometry and indices to OpenGL VBO objects // and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs. - void finalize_geometry(bool use_VBOs) { for (auto *v : volumes) v->finalize_geometry(use_VBOs); } + void finalize_geometry() { for (auto* v : volumes) v->finalize_geometry(); } // Release the geometry data assigned to the volumes. // If OpenGL VBOs were allocated, an OpenGL context has to be active to release them. void release_geometry() { for (auto *v : volumes) v->release_geometry(); } @@ -533,17 +531,16 @@ class GLModel { protected: GLVolume m_volume; - bool m_useVBOs; std::string m_filename; public: GLModel(); virtual ~GLModel(); - bool init(bool useVBOs) { return on_init(useVBOs); } - bool init_from_file(const std::string& filename, bool useVBOs) { return on_init_from_file(filename, useVBOs); } + bool init() { return on_init(); } + bool init_from_file(const std::string& filename) { return on_init_from_file(filename); } - void center_around(const Vec3d& center) { m_volume.set_volume_offset(center - m_volume.bounding_box.center()); } + void center_around(const Vec3d& center) { m_volume.set_volume_offset(center - m_volume.bounding_box().center()); } void set_color(const float* color, unsigned int size); const Vec3d& get_offset() const; @@ -554,7 +551,7 @@ public: void set_scale(const Vec3d& scale); const std::string& get_filename() const { return m_filename; } - const BoundingBoxf3& get_bounding_box() const { return m_volume.bounding_box; } + const BoundingBoxf3& get_bounding_box() const { return m_volume.bounding_box(); } const BoundingBoxf3& get_transformed_bounding_box() const { return m_volume.transformed_bounding_box(); } void reset(); @@ -562,18 +559,14 @@ public: void render() const; protected: - virtual bool on_init(bool useVBOs) { return false; } - virtual bool on_init_from_file(const std::string& filename, bool useVBOs) { return false; } - -private: - void render_VBOs() const; - void render_legacy() const; + virtual bool on_init() { return false; } + virtual bool on_init_from_file(const std::string& filename) { return false; } }; class GLArrow : public GLModel { protected: - virtual bool on_init(bool useVBOs); + virtual bool on_init(); }; class GLCurvedArrow : public GLModel @@ -584,13 +577,13 @@ public: explicit GLCurvedArrow(unsigned int resolution); protected: - virtual bool on_init(bool useVBOs); + virtual bool on_init(); }; class GLBed : public GLModel { protected: - virtual bool on_init_from_file(const std::string& filename, bool useVBOs); + virtual bool on_init_from_file(const std::string& filename); }; class _3DScene diff --git a/src/slic3r/GUI/AppConfig.cpp b/src/slic3r/GUI/AppConfig.cpp index 3b6210058..dfdc79677 100644 --- a/src/slic3r/GUI/AppConfig.cpp +++ b/src/slic3r/GUI/AppConfig.cpp @@ -229,6 +229,33 @@ std::string AppConfig::get_last_dir() const return std::string(); } +std::vector AppConfig::get_recent_projects() const +{ + std::vector ret; + const auto it = m_storage.find("recent_projects"); + if (it != m_storage.end()) + { + for (const std::map::value_type& item : it->second) + { + ret.push_back(item.second); + } + } + return ret; +} + +void AppConfig::set_recent_projects(const std::vector& recent_projects) +{ + auto it = m_storage.find("recent_projects"); + if (it == m_storage.end()) + it = m_storage.insert(std::map>::value_type("recent_projects", std::map())).first; + + it->second.clear(); + for (unsigned int i = 0; i < (unsigned int)recent_projects.size(); ++i) + { + it->second[std::to_string(i + 1)] = recent_projects[i]; + } +} + void AppConfig::update_config_dir(const std::string &dir) { this->set("recent", "config_directory", dir); diff --git a/src/slic3r/GUI/AppConfig.hpp b/src/slic3r/GUI/AppConfig.hpp index 5af635a12..230a92294 100644 --- a/src/slic3r/GUI/AppConfig.hpp +++ b/src/slic3r/GUI/AppConfig.hpp @@ -122,6 +122,9 @@ public: // Does the config file exist? static bool exists(); + std::vector get_recent_projects() const; + void set_recent_projects(const std::vector& recent_projects); + private: // Map of section, name -> value std::map> m_storage; diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index cec0f5067..d52204d4a 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -53,15 +53,12 @@ void BedShapeDialog::on_dpi_changed(const wxRect &suggested_rect) void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) { -// on_change(nullptr); - - auto box = new wxStaticBox(this, wxID_ANY, _(L("Shape"))); - auto sbsizer = new wxStaticBoxSizer(box, wxVERTICAL); + auto sbsizer = new wxStaticBoxSizer(wxVERTICAL, this, _(L("Shape"))); // shape options m_shape_options_book = new wxChoicebook(this, wxID_ANY, wxDefaultPosition, wxSize(25*wxGetApp().em_unit(), -1), wxCHB_TOP); - sbsizer->Add(m_shape_options_book); + sbsizer->Add(m_shape_options_book); auto optgroup = init_shape_options_page(_(L("Rectangular"))); ConfigOptionDef def; @@ -92,13 +89,15 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) Line line{ "", "" }; line.full_width = 1; line.widget = [this](wxWindow* parent) { - auto btn = new wxButton(parent, wxID_ANY, _(L("Load shape from STL...")), wxDefaultPosition, wxDefaultSize); - - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(btn); + auto shape_btn = new wxButton(parent, wxID_ANY, _(L("Load shape from STL..."))); + wxSizer* shape_sizer = new wxBoxSizer(wxHORIZONTAL); + shape_sizer->Add(shape_btn, 1, wxEXPAND); - btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) - { + wxSizer* sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(shape_sizer, 1, wxEXPAND); + + shape_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) + { load_stl(); })); @@ -106,8 +105,8 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) }; optgroup->append_line(line); - Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent e) - { + Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent& e) + { update_shape(); })); @@ -117,8 +116,8 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) // main sizer auto top_sizer = new wxBoxSizer(wxHORIZONTAL); - top_sizer->Add(sbsizer, 0, wxEXPAND | wxLeft | wxTOP | wxBOTTOM, 10); - if (m_canvas) + top_sizer->Add(sbsizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 10); + if (m_canvas) top_sizer->Add(m_canvas, 1, wxEXPAND | wxALL, 10) ; SetSizerAndFit(top_sizer); @@ -135,8 +134,7 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) // Create a panel for a rectangular / circular / custom bed shape. ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(const wxString& title) { - - auto panel = new wxPanel(m_shape_options_book); + auto panel = new wxPanel(m_shape_options_book); ConfigOptionsGroupShp optgroup; optgroup = std::make_shared(panel, _(L("Settings"))); @@ -234,8 +232,8 @@ void BedShapePanel::set_shape(ConfigOptionPoints* points) // This is a custom bed shape, use the polygon provided. m_shape_options_book->SetSelection(SHAPE_CUSTOM); // Copy the polygon to the canvas, make a copy of the array. - m_canvas->m_bed_shape = points->values; - update_shape(); + m_loaded_bed_shape = points->values; + update_shape(); } void BedShapePanel::update_preview() diff --git a/src/slic3r/GUI/BonjourDialog.cpp b/src/slic3r/GUI/BonjourDialog.cpp index 1885dda7b..0e05a517c 100644 --- a/src/slic3r/GUI/BonjourDialog.cpp +++ b/src/slic3r/GUI/BonjourDialog.cpp @@ -171,7 +171,7 @@ void BonjourDialog::on_reply(BonjourReplyEvent &e) // Filter replies based on selected technology const auto model = e.reply.txt_data.find("model"); const bool sl1 = model != e.reply.txt_data.end() && model->second == "SL1"; - if (tech == ptFFF && sl1 || tech == ptSLA && !sl1) { + if ((tech == ptFFF && sl1) || (tech == ptSLA && !sl1)) { return; } diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index aacbfdc52..8b08f6f7f 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -330,8 +330,8 @@ PagePrinters::PagePrinters(ConfigWizard *parent, wxString title, wxString shortn 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) + return ((model.technology == ptFFF && technology & T_FFF) + || (model.technology == ptSLA && technology & T_SLA)) && model.family == family; }; @@ -810,7 +810,7 @@ void ConfigWizardIndex::on_paint(wxPaintEvent & evt) 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) { + 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); } diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 7f42db4d7..e84e9637f 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -113,11 +113,19 @@ wxString Field::get_tooltip_text(const wxString& default_string) wxString tooltip_text(""); wxString tooltip = _(m_opt.tooltip); edit_tooltip(tooltip); + + std::string opt_id = m_opt_id; + auto hash_pos = opt_id.find("#"); + if (hash_pos != std::string::npos) { + opt_id.replace(hash_pos, 1,"["); + opt_id += "]"; + } + if (tooltip.length() > 0) tooltip_text = tooltip + "\n" + _(L("default value")) + "\t: " + - (boost::iends_with(m_opt_id, "_gcode") ? "\n" : "") + default_string + - (boost::iends_with(m_opt_id, "_gcode") ? "" : "\n") + - _(L("parameter name")) + "\t: " + m_opt_id; + (boost::iends_with(opt_id, "_gcode") ? "\n" : "") + default_string + + (boost::iends_with(opt_id, "_gcode") ? "" : "\n") + + _(L("parameter name")) + "\t: " + opt_id; return tooltip_text; } diff --git a/src/slic3r/GUI/FirmwareDialog.cpp b/src/slic3r/GUI/FirmwareDialog.cpp index 15a09aa71..7865aecf2 100644 --- a/src/slic3r/GUI/FirmwareDialog.cpp +++ b/src/slic3r/GUI/FirmwareDialog.cpp @@ -442,8 +442,7 @@ void FirmwareDialog::priv::avr109_lookup_port(Avr109Pid usb_pid) auto ports = Utils::scan_serial_ports_extended(); ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) { return port.id_vendor != USB_VID_PRUSA || - port.id_product != usb_pid.boot && - port.id_product != usb_pid.app; + (port.id_product != usb_pid.boot && port.id_product != usb_pid.app); }), ports.end()); if (ports.size() == 0) { diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index c674172e3..77a727108 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -524,7 +524,7 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G 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) { + 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; @@ -543,8 +543,8 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G { // Something went wrong. Just render the object. assert(false); - for (const GLVolume *glvolume : volumes.volumes) { - // Render the object using the layer editing shader and texture. + 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; glsafe(::glUniformMatrix4fv(world_matrix_id, 1, GL_FALSE, (const GLfloat*)glvolume->world_matrix().cast().data())); @@ -604,6 +604,7 @@ void GLCanvas3D::LayersEditing::accept_changes(GLCanvas3D& canvas) { if (last_object_id >= 0) { if (m_layer_height_profile_modified) { + wxGetApp().plater()->take_snapshot(_(L("Layers heights"))); const_cast(m_model_object)->layer_height_profile = m_layer_height_profile; canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); } @@ -912,7 +913,8 @@ GLCanvas3D::LegendTexture::LegendTexture() void GLCanvas3D::LegendTexture::fill_color_print_legend_values(const GCodePreviewData& preview_data, const GLCanvas3D& canvas, std::vector>& cp_legend_values) { - if (preview_data.extrusion.view_type == GCodePreviewData::Extrusion::ColorPrint) + if (preview_data.extrusion.view_type == GCodePreviewData::Extrusion::ColorPrint && + wxGetApp().extruders_edited_cnt() == 1) // show color change legend only for single-material presets { auto& config = wxGetApp().preset_bundle->project_config; const std::vector& color_print_values = config.option("colorprint_heights")->values; @@ -1187,6 +1189,8 @@ wxDEFINE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, wxKeyEvent); wxDEFINE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent); GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar) : m_canvas(canvas) @@ -1198,11 +1202,8 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar , m_bed(bed) , m_camera(camera) , m_view_toolbar(view_toolbar) -#if ENABLE_SVG_ICONS , m_toolbar(GLToolbar::Normal, "Top") -#else - , m_toolbar(GLToolbar::Normal) -#endif // ENABLE_SVG_ICONS + , m_gizmos(*this) , m_use_clipping_planes(false) , m_sidebar_field("") , m_keep_dirty(false) @@ -1211,7 +1212,6 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar , m_model(nullptr) , m_dirty(true) , m_initialized(false) - , m_use_VBOs(false) , m_apply_zoom_to_volumes_filter(false) , m_legend_texture_enabled(false) , m_picking_enabled(false) @@ -1224,6 +1224,9 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar , 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) { if (m_canvas != nullptr) { @@ -1249,7 +1252,7 @@ void GLCanvas3D::post_event(wxEvent &&event) wxPostEvent(m_canvas, event); } -bool GLCanvas3D::init(bool useVBOs) +bool GLCanvas3D::init() { if (m_initialized) return true; @@ -1300,30 +1303,30 @@ bool GLCanvas3D::init(bool useVBOs) if (m_multisample_allowed) glsafe(::glEnable(GL_MULTISAMPLE)); - if (useVBOs && !m_shader.init("gouraud.vs", "gouraud.fs")) + if (!m_shader.init("gouraud.vs", "gouraud.fs")) + { + std::cout << "Unable to initialize gouraud shader: please, check that the files gouraud.vs and gouraud.fs are available" << std::endl; return false; - - if (m_toolbar.is_enabled() && useVBOs && !m_layers_editing.init("variable_layer_height.vs", "variable_layer_height.fs")) - return false; - - m_use_VBOs = useVBOs; - - // 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() - if (!m_volumes.empty()) - m_volumes.finalize_geometry(m_use_VBOs); - - if (m_gizmos.is_enabled()) { - if (! m_gizmos.init(*this)) { - std::cout << "Unable to initialize gizmos: please, check that all the required textures are available" << std::endl; - return false; - } } + if (m_toolbar.is_enabled() && !m_layers_editing.init("variable_layer_height.vs", "variable_layer_height.fs")) + { + std::cout << "Unable to initialize variable_layer_height shader: please, check that the files variable_layer_height.vs and variable_layer_height.fs are available" << std::endl; + return false; + } + +// // 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() +// if (!m_volumes.empty()) +// m_volumes.finalize_geometry(); + + 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_toolbar()) return false; - if (m_selection.is_enabled() && !m_selection.init(m_use_VBOs)) + if (m_selection.is_enabled() && !m_selection.init()) return false; post_event(SimpleEvent(EVT_GLCANVAS_INIT)); @@ -1353,7 +1356,6 @@ void GLCanvas3D::reset_volumes() if (!m_volumes.empty()) { m_selection.clear(); - m_volumes.release_geometry(); m_volumes.clear(); m_dirty = true; } @@ -1627,6 +1629,10 @@ void GLCanvas3D::render() _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(); @@ -1656,6 +1662,9 @@ void GLCanvas3D::render() _render_current_gizmo(); _render_selection_sidebar_hints(); +#if ENABLE_RENDER_PICKING_PASS + } +#endif // ENABLE_RENDER_PICKING_PASS #if ENABLE_SHOW_CAMERA_TARGET _render_camera_target(); @@ -1712,7 +1721,7 @@ void GLCanvas3D::deselect_all() m_selection.set_mode(Selection::Instance); wxGetApp().obj_manipul()->set_dirty(); m_gizmos.reset_all_states(); - m_gizmos.update_data(*this); + m_gizmos.update_data(); post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); } @@ -1768,7 +1777,7 @@ std::vector GLCanvas3D::load_object(const ModelObject& model_object, int ob instance_idxs.push_back(i); } } - return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_color_by, m_use_VBOs && m_initialized); + return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_color_by); } std::vector GLCanvas3D::load_object(const Model& model, int obj_idx) @@ -1786,7 +1795,7 @@ std::vector GLCanvas3D::load_object(const Model& model, int obj_idx) void GLCanvas3D::mirror_selection(Axis axis) { m_selection.mirror(axis); - do_mirror(); + do_mirror(L("Mirror Object")); wxGetApp().obj_manipul()->set_dirty(); } @@ -1807,14 +1816,14 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re struct ModelVolumeState { ModelVolumeState(const GLVolume *volume) : model_volume(nullptr), geometry_id(volume->geometry_id), volume_idx(-1) {} - ModelVolumeState(const ModelVolume *model_volume, const ModelID &instance_id, const GLVolume::CompositeID &composite_id) : + 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 ModelID &volume_id, const ModelID &instance_id) : + 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; - // ModelID of ModelVolume + ModelID of ModelInstance - // or timestamp of an SLAPrintObjectStep + ModelID of ModelInstance + // 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. @@ -1908,7 +1917,6 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re assert(volume_idx_wipe_tower_old == -1); volume_idx_wipe_tower_old = (int)volume_id; } - volume->release_geometry(); if (! m_reload_delayed) delete volume; } else { @@ -1956,8 +1964,8 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id); if (it->new_geometry()) { // New volume. - m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, m_color_by, m_use_VBOs && m_initialized); - m_volumes.volumes.back()->geometry_id = key.geometry_id; + m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, m_color_by); + m_volumes.volumes.back()->geometry_id = key.geometry_id; update_object_list = true; } else { // Recycling an old GLVolume. @@ -2010,9 +2018,11 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re assert(it != aux_volume_state.end() && it->geometry_id == key.geometry_id); if (it->new_geometry()) instances[istep].emplace_back(std::pair(instance_idx, print_instance_idx)); - 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); + 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()); + } } } @@ -2021,7 +2031,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re 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_use_VBOs && m_initialized); + m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp); } // Shift-up all volumes of the object so that it has the right elevation with respect to the print bed @@ -2034,11 +2044,10 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re // Should the wipe tower be visualized ? unsigned int extruders_count = (unsigned int)dynamic_cast(m_config->option("nozzle_diameter"))->values.size(); - bool semm = dynamic_cast(m_config->option("single_extruder_multi_material"))->value; bool wt = dynamic_cast(m_config->option("wipe_tower"))->value; bool co = dynamic_cast(m_config->option("complete_objects"))->value; - if ((extruders_count > 1) && semm && wt && !co) + 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); @@ -2058,9 +2067,9 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re float brim_spacing = print->config().nozzle_diameter.values[0] * 1.25f - first_layer_height * (1. - M_PI_4); if (!print->is_step_done(psWipeTower)) - depth = (900.f/w) * (float)(extruders_count - 1) ; + depth = (900.f/w) * (float)(extruders_count - 1); int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( - 1000, x, y, w, depth, (float)height, a, m_use_VBOs && m_initialized, !print->is_step_done(psWipeTower), + 1000, x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower), brim_spacing * 4.5f); if (volume_idx_wipe_tower_old != -1) map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new; @@ -2075,8 +2084,8 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re m_selection.volumes_changed(map_glvolume_old_to_new); } - m_gizmos.update_data(*this); - m_gizmos.refresh_on_off_state(m_selection); + m_gizmos.update_data(); + m_gizmos.refresh_on_off_state(); // Update the toolbar if (update_object_list) @@ -2313,7 +2322,10 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) return; } - if (m_gizmos.on_char(evt, *this)) + if ((keyCode == WXK_ESCAPE) && _deactivate_undo_redo_toolbar_items()) + return; + + if (m_gizmos.on_char(evt)) return; //#ifdef __APPLE__ @@ -2345,6 +2357,25 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) #endif /* __APPLE__ */ post_event(SimpleEvent(EVT_GLTOOLBAR_PASTE)); 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; + #ifdef __APPLE__ case WXK_BACK: // the low cost Apple solutions are not equipped with a Delete key, use Backspace instead. #else /* __APPLE__ */ @@ -2365,7 +2396,6 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) #endif /* __APPLE__ */ post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE)); break; - case WXK_ESCAPE: { deselect_all(); break; } case '0': { select_view("iso"); break; } case '1': { select_view("top"); break; } @@ -2397,6 +2427,14 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) case 'k': { m_camera.select_next_type(); m_dirty = true; break; } case 'O': case 'o': { set_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': { m_selection.is_empty() ? zoom_to_volumes() : zoom_to_selection(); break; } default: { evt.Skip(); break; } @@ -2414,7 +2452,7 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) } else { - if (!m_gizmos.on_key(evt, *this)) + if (!m_gizmos.on_key(evt)) { if (evt.GetEventType() == wxEVT_KEY_UP) { if (m_tab_down && keyCode == WXK_TAB && !evt.HasAnyModifiers()) { @@ -2524,7 +2562,7 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt) } // Inform gizmos about the event so they have the opportunity to react. - if (m_gizmos.on_mouse_wheel(evt, *this)) + if (m_gizmos.on_mouse_wheel(evt)) return; // Calculate the zoom delta and apply it to the current zoom factor @@ -2652,7 +2690,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) return; } - if (m_gizmos.on_mouse(evt, *this)) + if (m_gizmos.on_mouse(evt)) { if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) mouse_up_cleanup(); @@ -2704,12 +2742,17 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } 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()) + else if (evt.LeftDown() || evt.RightDown() || evt.MiddleDown()) { + if (_deactivate_undo_redo_toolbar_items()) + 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; @@ -2774,9 +2817,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) if (m_selection.is_empty()) m_gizmos.reset_all_states(); else - m_gizmos.refresh_on_off_state(m_selection); + m_gizmos.refresh_on_off_state(); - m_gizmos.update_data(*this); + m_gizmos.update_data(); post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); m_dirty = true; } @@ -2907,9 +2950,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) else if ((m_mouse.drag.move_volume_idx != -1) && m_mouse.dragging) { m_regenerate_volumes = false; - do_move(); + do_move(L("Move Object")); wxGetApp().obj_manipul()->set_dirty(); - // Let the platter know that the dragging finished, so a delayed refresh + // 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)); } @@ -2944,9 +2987,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) { // forces the selection of the volume m_selection.add(volume_idx); - m_gizmos.refresh_on_off_state(m_selection); + m_gizmos.refresh_on_off_state(); post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); - m_gizmos.update_data(*this); + m_gizmos.update_data(); wxGetApp().obj_manipul()->set_dirty(); // forces a frame render to update the view before the context menu is shown render(); @@ -3066,11 +3109,14 @@ void GLCanvas3D::set_tooltip(const std::string& tooltip) const } -void GLCanvas3D::do_move() +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(); @@ -3121,13 +3167,18 @@ void GLCanvas3D::do_move() if (wipe_tower_origin != Vec3d::Zero()) post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_MOVED, std::move(wipe_tower_origin))); + + m_dirty = true; } -void GLCanvas3D::do_rotate() +void GLCanvas3D::do_rotate(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 Selection::EMode selection_mode = m_selection.get_mode(); @@ -3176,13 +3227,18 @@ void GLCanvas3D::do_rotate() if (!done.empty()) post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED)); + + m_dirty = true; } -void GLCanvas3D::do_scale() +void GLCanvas3D::do_scale(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 Selection::EMode selection_mode = m_selection.get_mode(); @@ -3228,18 +3284,27 @@ void GLCanvas3D::do_scale() if (!done.empty()) post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED)); + + m_dirty = true; } -void GLCanvas3D::do_flatten() +void GLCanvas3D::do_flatten(const Vec3d& normal, const std::string& snapshot_type) { - do_rotate(); + if (!snapshot_type.empty()) + wxGetApp().plater()->take_snapshot(_(snapshot_type)); + + m_selection.flattening_rotate(normal); + do_rotate(L("")); // avoid taking another snapshot } -void GLCanvas3D::do_mirror() +void GLCanvas3D::do_mirror(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 Selection::EMode selection_mode = m_selection.get_mode(); @@ -3278,6 +3343,8 @@ void GLCanvas3D::do_mirror() } post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + + m_dirty = true; } void GLCanvas3D::set_camera_zoom(double zoom) @@ -3290,8 +3357,8 @@ void GLCanvas3D::set_camera_zoom(double zoom) void GLCanvas3D::update_gizmos_on_off_state() { set_as_dirty(); - m_gizmos.update_data(*this); - m_gizmos.refresh_on_off_state(get_selection()); + m_gizmos.update_data(); + m_gizmos.refresh_on_off_state(); } void GLCanvas3D::handle_sidebar_focus_event(const std::string& opt_key, bool focus_on) @@ -3305,6 +3372,12 @@ void GLCanvas3D::handle_sidebar_focus_event(const std::string& opt_key, bool foc } } +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_camera.set_type(wxGetApp().app_config->get("use_perspective_camera")); @@ -3337,9 +3410,9 @@ GLCanvas3D::WipeTowerInfo GLCanvas3D::get_wipe_tower_info() const 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_size = Vec2d(bb.size().x(), bb.size().y()); + wti.rotation = (M_PI/180.) * m_config->opt_float("wipe_tower_rotation_angle"); + const BoundingBoxf3& bb = vol->bounding_box(); + wti.bb_size = Vec2d(bb.size()(0), bb.size()(1)); break; } } @@ -3383,17 +3456,45 @@ 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); +} + +void GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x) +{ + const wxString& stack_name = _(is_undo ? L("Undo") : L("Redo")); + ImGuiWrapper* imgui = wxGetApp().imgui(); + + const float x = pos_x * (float)get_camera().get_zoom() + 0.5f * (float)get_canvas_size().get_width(); + imgui->set_next_window_pos(x, m_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); + imgui->set_next_window_bg_alpha(0.5f); + imgui->begin(wxString::Format(_(L("%s Stack")), stack_name), + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + + int hovered = m_imgui_undo_redo_hovered_pos; + int selected = -1; + const float em = static_cast(wxGetApp().em_unit()); + + if (imgui->undo_redo_list(ImVec2(12 * em, 20 * em), is_undo, &string_getter, hovered, selected)) + 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); + + imgui->text(wxString::Format(_(L("%s %d Action")), stack_name, hovered + 1)); + + imgui->end(); +} + bool GLCanvas3D::_init_toolbar() { if (!m_toolbar.is_enabled()) return true; -#if !ENABLE_SVG_ICONS - ItemsIconsTexture::Metadata icons_data; - icons_data.filename = "toolbar.png"; - icons_data.icon_size = 37; -#endif // !ENABLE_SVG_ICONS - BackgroundTexture::Metadata background_data; background_data.filename = "toolbar_background.png"; background_data.left = 16; @@ -3401,11 +3502,7 @@ bool GLCanvas3D::_init_toolbar() background_data.right = 16; background_data.bottom = 16; -#if ENABLE_SVG_ICONS if (!m_toolbar.init(background_data)) -#else - if (!m_toolbar.init(icons_data, background_data)) -#endif // ENABLE_SVG_ICONS { // unable to init the toolbar texture, disable it m_toolbar.set_enabled(false); @@ -3422,45 +3519,37 @@ bool GLCanvas3D::_init_toolbar() GLToolbarItem::Data item; item.name = "add"; -#if ENABLE_SVG_ICONS item.icon_filename = "add.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Add...")) + " [" + GUI::shortkey_ctrl_prefix() + "I]"; item.sprite_id = 0; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ADD)); }; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ADD)); }; if (!m_toolbar.add_item(item)) return false; item.name = "delete"; -#if ENABLE_SVG_ICONS item.icon_filename = "remove.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Delete")) + " [Del]"; item.sprite_id = 1; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE)); }; - item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_delete(); }; + 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_toolbar.add_item(item)) return false; item.name = "deleteall"; -#if ENABLE_SVG_ICONS item.icon_filename = "delete_all.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Delete all")) + " [" + GUI::shortkey_ctrl_prefix() + "Del]"; item.sprite_id = 2; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); }; - item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_delete_all(); }; + 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_toolbar.add_item(item)) return false; item.name = "arrange"; -#if ENABLE_SVG_ICONS item.icon_filename = "arrange.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Arrange")) + " [A]"; item.sprite_id = 3; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ARRANGE)); }; - item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_arrange(); }; + 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(); }; if (!m_toolbar.add_item(item)) return false; @@ -3468,24 +3557,20 @@ bool GLCanvas3D::_init_toolbar() return false; item.name = "copy"; -#if ENABLE_SVG_ICONS item.icon_filename = "copy.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Copy")) + " [" + GUI::shortkey_ctrl_prefix() + "C]"; item.sprite_id = 4; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_COPY)); }; - item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_copy_to_clipboard(); }; + 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_toolbar.add_item(item)) return false; item.name = "paste"; -#if ENABLE_SVG_ICONS item.icon_filename = "paste.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Paste")) + " [" + GUI::shortkey_ctrl_prefix() + "V]"; item.sprite_id = 5; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_PASTE)); }; - item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_paste_from_clipboard(); }; + 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_toolbar.add_item(item)) return false; @@ -3493,26 +3578,23 @@ bool GLCanvas3D::_init_toolbar() return false; item.name = "more"; -#if ENABLE_SVG_ICONS item.icon_filename = "instance_add.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Add instance")) + " [+]"; item.sprite_id = 6; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_MORE)); }; + 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.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_increase_instances(); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_increase_instances(); }; + if (!m_toolbar.add_item(item)) return false; item.name = "fewer"; -#if ENABLE_SVG_ICONS item.icon_filename = "instance_remove.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Remove instance")) + " [-]"; item.sprite_id = 7; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_FEWER)); }; + 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.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_decrease_instances(); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_decrease_instances(); }; if (!m_toolbar.add_item(item)) return false; @@ -3520,26 +3602,22 @@ bool GLCanvas3D::_init_toolbar() return false; item.name = "splitobjects"; -#if ENABLE_SVG_ICONS item.icon_filename = "split_objects.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Split to objects")); item.sprite_id = 8; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_OBJECTS)); }; + 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.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_split_to_objects(); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_split_to_objects(); }; if (!m_toolbar.add_item(item)) return false; item.name = "splitvolumes"; -#if ENABLE_SVG_ICONS item.icon_filename = "split_parts.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Split to parts")); item.sprite_id = 9; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_VOLUMES)); }; + 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.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_split_to_volumes(); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_split_to_volumes(); }; if (!m_toolbar.add_item(item)) return false; @@ -3547,15 +3625,41 @@ bool GLCanvas3D::_init_toolbar() return false; item.name = "layersediting"; -#if ENABLE_SVG_ICONS item.icon_filename = "layers_white.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Layers editing")); item.sprite_id = 10; - item.is_toggable = true; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); }; + item.left.toggable = true; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); }; item.visibility_callback = [this]()->bool { return m_process->current_printer_technology() == ptFFF; }; - item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_layers_editing(); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_layers_editing(); }; + if (!m_toolbar.add_item(item)) + return false; + + if (!m_toolbar.add_separator()) + return false; + + item.name = "undo"; + item.icon_filename = "undo_toolbar.svg"; + item.tooltip = _utf8(L("Undo")) + " [" + GUI::shortkey_ctrl_prefix() + "Z]"; + item.sprite_id = 11; + item.left.toggable = false; + 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) _render_undo_redo_stack(true, 0.5f * (left + right)); }; + item.visibility_callback = []()->bool { return true; }; + item.enabling_callback = [this]()->bool { return wxGetApp().plater()->can_undo(); }; + if (!m_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]"; + item.sprite_id = 12; + 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) _render_undo_redo_stack(false, 0.5f * (left + right)); }; + item.enabling_callback = [this]()->bool { return wxGetApp().plater()->can_redo(); }; if (!m_toolbar.add_item(item)) return false; @@ -3648,7 +3752,7 @@ void GLCanvas3D::_picking_pass() const if (m_camera_clipping_plane.is_active()) ::glDisable(GL_CLIP_PLANE0); - m_gizmos.render_current_gizmo_for_picking_pass(m_selection); + m_gizmos.render_current_gizmo_for_picking_pass(); if (m_multisample_allowed) glsafe(::glEnable(GL_MULTISAMPLE)); @@ -3791,7 +3895,11 @@ void GLCanvas3D::_render_bed(float theta) const #if ENABLE_RETINA_GL scale_factor = m_retina_helper->get_scale_factor(); #endif // ENABLE_RETINA_GL - m_bed.render(const_cast(this), theta, m_use_VBOs, scale_factor); +#if ENABLE_TEXTURES_FROM_SVG + m_bed.render(const_cast(this), theta, scale_factor); +#else + m_bed.render(theta, scale_factor); +#endif // ENABLE_TEXTURES_FROM_SVG } void GLCanvas3D::_render_axes() const @@ -3799,8 +3907,6 @@ void GLCanvas3D::_render_axes() const m_bed.render_axes(); } - - void GLCanvas3D::_render_objects() const { if (m_volumes.empty()) @@ -3811,75 +3917,44 @@ void GLCanvas3D::_render_objects() const m_camera_clipping_plane = m_gizmos.get_sla_clipping_plane(); - if (m_use_VBOs) + if (m_picking_enabled) { - if (m_picking_enabled) + // Update the layer editing selection to the first object selected, update the current object maximum Z. + const_cast(m_layers_editing).select_object(*m_model, this->is_layers_editing_enabled() ? m_selection.get_object_idx() : -1); + + if (m_config != nullptr) { - // Update the layer editing selection to the first object selected, update the current object maximum Z. - const_cast(m_layers_editing).select_object(*m_model, this->is_layers_editing_enabled() ? m_selection.get_object_idx() : -1); - - if (m_config != nullptr) - { - const BoundingBoxf3& bed_bb = m_bed.get_bounding_box(false); - m_volumes.set_print_box((float)bed_bb.min(0), (float)bed_bb.min(1), 0.0f, (float)bed_bb.max(0), (float)bed_bb.max(1), (float)m_config->opt_float("max_print_height")); - m_volumes.check_outside_state(m_config, nullptr); - } + const BoundingBoxf3& bed_bb = m_bed.get_bounding_box(false); + m_volumes.set_print_box((float)bed_bb.min(0), (float)bed_bb.min(1), 0.0f, (float)bed_bb.max(0), (float)bed_bb.max(1), (float)m_config->opt_float("max_print_height")); + m_volumes.check_outside_state(m_config, nullptr); } - - 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_shader.start_using(); - 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_VBOs(GLVolumeCollection::Opaque, false, m_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, this->m_volumes); - } else { - // do not cull backfaces to show broken geometry, if any - m_volumes.render_VBOs(GLVolumeCollection::Opaque, m_picking_enabled, m_camera.get_view_matrix(), [this](const GLVolume& volume) { - return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0); - }); - } - m_volumes.render_VBOs(GLVolumeCollection::Transparent, false, m_camera.get_view_matrix()); - m_shader.stop_using(); } + + 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 - { - ::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_camera_clipping_plane.get_data()); - ::glEnable(GL_CLIP_PLANE0); + m_volumes.set_z_range(-FLT_MAX, FLT_MAX); - if (m_use_clipping_planes) - { - glsafe(::glClipPlane(GL_CLIP_PLANE1, (GLdouble*)m_clipping_planes[0].get_data())); - glsafe(::glEnable(GL_CLIP_PLANE1)); - glsafe(::glClipPlane(GL_CLIP_PLANE2, (GLdouble*)m_clipping_planes[1].get_data())); - glsafe(::glEnable(GL_CLIP_PLANE2)); - } - + m_volumes.set_clipping_plane(m_camera_clipping_plane.get_data()); + m_shader.start_using(); + 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(GLVolumeCollection::Opaque, false, m_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, this->m_volumes); + } else { // do not cull backfaces to show broken geometry, if any - m_volumes.render_legacy(GLVolumeCollection::Opaque, m_picking_enabled, m_camera.get_view_matrix(), [this](const GLVolume& volume) { + m_volumes.render(GLVolumeCollection::Opaque, m_picking_enabled, m_camera.get_view_matrix(), [this](const GLVolume& volume) { return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0); }); - m_volumes.render_legacy(GLVolumeCollection::Transparent, false, m_camera.get_view_matrix()); - - ::glDisable(GL_CLIP_PLANE0); - - if (m_use_clipping_planes) - { - glsafe(::glDisable(GL_CLIP_PLANE1)); - glsafe(::glDisable(GL_CLIP_PLANE2)); - } } - + m_volumes.render(GLVolumeCollection::Transparent, false, m_camera.get_view_matrix()); + m_shader.stop_using(); + m_camera_clipping_plane = ClippingPlane::ClipsNothing(); glsafe(::glDisable(GL_LIGHTING)); } @@ -3916,9 +3991,6 @@ void GLCanvas3D::_render_overlays() const _render_gizmos_overlay(); _render_warning_texture(); _render_legend_texture(); -#if !ENABLE_SVG_ICONS - _resize_toolbars(); -#endif // !ENABLE_SVG_ICONS _render_toolbar(); _render_view_toolbar(); @@ -3990,7 +4062,7 @@ void GLCanvas3D::_render_volumes_for_picking() const void GLCanvas3D::_render_current_gizmo() const { - m_gizmos.render_current_gizmo(m_selection); + m_gizmos.render_current_gizmo(); } void GLCanvas3D::_render_gizmos_overlay() const @@ -4006,12 +4078,11 @@ void GLCanvas3D::_render_gizmos_overlay() const m_gizmos.set_overlay_icon_size(size); //! #ys_FIXME_experiment #endif /* __WXMSW__ */ - m_gizmos.render_overlay(*this, m_selection); + m_gizmos.render_overlay(); } void GLCanvas3D::_render_toolbar() const { -#if ENABLE_SVG_ICONS #if ENABLE_RETINA_GL // m_toolbar.set_scale(m_retina_helper->get_scale_factor()); const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(true); @@ -4066,20 +4137,12 @@ void GLCanvas3D::_render_toolbar() const } } m_toolbar.set_position(top, left); -#else -#if ENABLE_RETINA_GL - m_toolbar.set_icons_scale(m_retina_helper->get_scale_factor()); -#else - m_toolbar.set_icons_scale(m_canvas->GetContentScaleFactor()); -#endif /* __WXMSW__ */ -#endif // ENABLE_SVG_ICONS m_toolbar.render(*this); } void GLCanvas3D::_render_view_toolbar() const { -#if ENABLE_SVG_ICONS #if ENABLE_RETINA_GL // m_view_toolbar.set_scale(m_retina_helper->get_scale_factor()); const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(); @@ -4099,13 +4162,6 @@ void GLCanvas3D::_render_view_toolbar() const float top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar.get_height()) * inv_zoom; float left = -0.5f * (float)cnv_size.get_width() * inv_zoom; m_view_toolbar.set_position(top, left); -#else -#if ENABLE_RETINA_GL - m_view_toolbar.set_icons_scale(m_retina_helper->get_scale_factor()); -#else - m_view_toolbar.set_icons_scale(m_canvas->GetContentScaleFactor()); -#endif /* __WXMSW__ */ -#endif // ENABLE_SVG_ICONS m_view_toolbar.render(*this); } @@ -4258,16 +4314,9 @@ void GLCanvas3D::_render_sla_slices() const void GLCanvas3D::_render_selection_sidebar_hints() const { - if (m_use_VBOs) - m_shader.start_using(); - - m_selection.render_sidebar_hints(m_sidebar_field); - - if (m_use_VBOs) - m_shader.stop_using(); + m_selection.render_sidebar_hints(m_sidebar_field, m_shader); } - void GLCanvas3D::_update_volumes_hover_state() const { for (GLVolume* v : m_volumes.volumes) @@ -4476,8 +4525,6 @@ void GLCanvas3D::_load_print_toolpaths() _3DScene::extrusionentity_to_verts(print->skirt(), print_zs[i], Point(0, 0), volume); } - volume.bounding_box = volume.indexed_vertex_array.bounding_box(); - volume.indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); } void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, const std::vector& str_tool_colors, const std::vector& color_print_values) @@ -4642,19 +4689,13 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array); // Copy the content back to the old GLVolume. vol.indexed_vertex_array = vol_new.indexed_vertex_array; - // Finalize a bounding box of the old GLVolume. - vol.bounding_box = vol.indexed_vertex_array.bounding_box(); - // Clear the buffers, but keep them pre-allocated. + // 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. vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); } } } - for (GLVolume *vol : vols) { - vol->bounding_box = vol->indexed_vertex_array.bounding_box(); - vol->indexed_vertex_array.shrink_to_fit(); - } }); BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - finalizing results"; @@ -4663,8 +4704,6 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c 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) - m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end"; } @@ -4684,7 +4723,7 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_ { const Print *print; const std::vector *tool_colors; - WipeTower::xy wipe_tower_pos; + Vec2f wipe_tower_pos; float wipe_tower_angle; // Number of vertices (each vertex is 6x4=24 bytes long) @@ -4715,12 +4754,13 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_ 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) - ctxt.priming.emplace_back(*print->wipe_tower_data().priming.get()); + for (int i=0; iwipe_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 = WipeTower::xy(ctxt.print->config().wipe_tower_x.value, ctxt.print->config().wipe_tower_y.value); + 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"; @@ -4782,19 +4822,19 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_ 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.rotate(ctxt.wipe_tower_angle); - e_prev.pos.translate(ctxt.wipe_tower_pos); + 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.rotate(ctxt.wipe_tower_angle); - e.pos.translate(ctxt.wipe_tower_pos); + 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)); + 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; @@ -4814,18 +4854,12 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_ vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array); // Copy the content back to the old GLVolume. vol.indexed_vertex_array = vol_new.indexed_vertex_array; - // Finalize a bounding box of the old GLVolume. - vol.bounding_box = vol.indexed_vertex_array.bounding_box(); // 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. vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); } } - for (GLVolume *vol : vols) { - vol->bounding_box = vol->indexed_vertex_array.bounding_box(); - vol->indexed_vertex_array.shrink_to_fit(); - } }); BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - finalizing results"; @@ -4834,8 +4868,6 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_ 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) - m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end"; } @@ -5012,17 +5044,6 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat } } } - - // finalize volumes and sends geometry to gpu - if (m_volumes.volumes.size() > initial_volumes_count) - { - for (size_t i = initial_volumes_count; i < m_volumes.volumes.size(); ++i) - { - GLVolume* volume = m_volumes.volumes[i]; - volume->bounding_box = volume->indexed_vertex_array.bounding_box(); - volume->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); - } - } } void GLCanvas3D::_load_gcode_travel_paths(const GCodePreviewData& preview_data, const std::vector& tool_colors) @@ -5067,17 +5088,6 @@ void GLCanvas3D::_load_gcode_travel_paths(const GCodePreviewData& preview_data, return; } - - // finalize volumes and sends geometry to gpu - if (m_volumes.volumes.size() > initial_volumes_count) - { - for (size_t i = initial_volumes_count; i < m_volumes.volumes.size(); ++i) - { - GLVolume* volume = m_volumes.volumes[i]; - volume->bounding_box = volume->indexed_vertex_array.bounding_box(); - volume->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); - } - } } bool GLCanvas3D::_travel_paths_by_type(const GCodePreviewData& preview_data) @@ -5306,10 +5316,6 @@ void GLCanvas3D::_load_gcode_retractions(const GCodePreviewData& preview_data) _3DScene::point3_to_verts(position.position, position.width, position.height, *volume); } - - // finalize volumes and sends geometry to gpu - volume->bounding_box = volume->indexed_vertex_array.bounding_box(); - volume->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); } } @@ -5337,10 +5343,6 @@ void GLCanvas3D::_load_gcode_unretractions(const GCodePreviewData& preview_data) _3DScene::point3_to_verts(position.position, position.width, position.height, *volume); } - - // finalize volumes and sends geometry to gpu - volume->bounding_box = volume->indexed_vertex_array.bounding_box(); - volume->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); } } @@ -5366,7 +5368,7 @@ void GLCanvas3D::_load_fff_shells() instance_ids[i] = i; } - m_volumes.load_object(model_obj, object_id, instance_ids, "object", m_use_VBOs && m_initialized); + m_volumes.load_object(model_obj, object_id, instance_ids, "object"); ++object_id; } @@ -5376,7 +5378,7 @@ void GLCanvas3D::_load_fff_shells() double max_z = print->objects()[0]->model_object()->get_model()->bounding_box().max(2); const PrintConfig& config = print->config(); unsigned int extruders_count = config.nozzle_diameter.size(); - if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) { + if ((extruders_count > 1) && config.wipe_tower && !config.complete_objects) { float depth = print->get_wipe_tower_depth(); // Calculate wipe tower brim spacing. @@ -5386,9 +5388,9 @@ void GLCanvas3D::_load_fff_shells() float brim_spacing = print->config().nozzle_diameter.values[0] * 1.25f - first_layer_height * (1. - M_PI_4); if (!print->is_step_done(psWipeTower)) - depth = (900.f/config.wipe_tower_width) * (float)(extruders_count - 1) ; + depth = (900.f/config.wipe_tower_width) * (float)(extruders_count - 1); m_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, - m_use_VBOs && m_initialized, !print->is_step_done(psWipeTower), brim_spacing * 4.5f); + !print->is_step_done(psWipeTower), brim_spacing * 4.5f); } } } @@ -5406,8 +5408,8 @@ void GLCanvas3D::_load_sla_shells() const TriangleMesh &mesh, const float color[4], bool outside_printer_detection_enabled) { m_volumes.volumes.emplace_back(new GLVolume(color)); GLVolume& v = *m_volumes.volumes.back(); - v.indexed_vertex_array.load_mesh(mesh, m_use_VBOs); - v.shader_outside_printer_detection_enabled = outside_printer_detection_enabled; + v.indexed_vertex_array.load_mesh(mesh); + v.shader_outside_printer_detection_enabled = outside_printer_detection_enabled; v.composite_id.volume_id = volume_id; v.set_instance_offset(unscale(instance.shift(0), instance.shift(1), 0)); v.set_instance_rotation(Vec3d(0.0, 0.0, (double)instance.rotation)); @@ -5432,9 +5434,6 @@ void GLCanvas3D::_load_sla_shells() double shift_z = obj->get_current_elevation(); for (unsigned int i = initial_volumes_count; i < m_volumes.volumes.size(); ++ i) { GLVolume& v = *m_volumes.volumes[i]; - // finalize volumes and sends geometry to gpu - v.bounding_box = v.indexed_vertex_array.bounding_box(); - v.indexed_vertex_array.finalize_geometry(m_use_VBOs); // apply shift z v.set_sla_shift_z(shift_z); } @@ -5525,7 +5524,7 @@ void GLCanvas3D::_update_toolpath_volumes_outside_state() for (GLVolume* volume : m_volumes.volumes) { - volume->is_outside = ((print_volume.radius() > 0.0) && volume->is_extrusion_path) ? !print_volume.contains(volume->bounding_box) : false; + volume->is_outside = ((print_volume.radius() > 0.0) && volume->is_extrusion_path) ? !print_volume.contains(volume->bounding_box()) : false; } } @@ -5606,76 +5605,6 @@ bool GLCanvas3D::_is_any_volume_outside() const return false; } -#if !ENABLE_SVG_ICONS -void GLCanvas3D::_resize_toolbars() const -{ - Size cnv_size = get_canvas_size(); - float zoom = get_camera_zoom(); - float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; - -#if ENABLE_RETINA_GL - m_toolbar.set_icons_scale(m_retina_helper->get_scale_factor()); -#else - m_toolbar.set_icons_scale(m_canvas->GetContentScaleFactor()); -#endif /* __WXMSW__ */ - - GLToolbar::Layout::EOrientation orientation = m_toolbar.get_layout_orientation(); - - switch (m_toolbar.get_layout_type()) - { - default: - case GLToolbar::Layout::Horizontal: - { - // centers the toolbar on the top edge of the 3d scene - float top, left; - if (orientation == GLToolbar::Layout::Top) - { - top = 0.5f * (float)cnv_size.get_height() * inv_zoom; - left = -0.5f * m_toolbar.get_width() * inv_zoom; - } - else - { - top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar.get_height()) * inv_zoom; - left = -0.5f * m_toolbar.get_width() * inv_zoom; - } - m_toolbar.set_position(top, left); - break; - } - case GLToolbar::Layout::Vertical: - { - // centers the toolbar on the right edge of the 3d scene - float top, left; - if (orientation == GLToolbar::Layout::Left) - { - top = 0.5f * m_toolbar.get_height() * inv_zoom; - left = (-0.5f * (float)cnv_size.get_width()) * inv_zoom; - } - else - { - top = 0.5f * m_toolbar.get_height() * inv_zoom; - left = (0.5f * (float)cnv_size.get_width() - m_toolbar.get_width()) * inv_zoom; - } - m_toolbar.set_position(top, left); - break; - } - } - - if (m_view_toolbar != nullptr) - { -#if ENABLE_RETINA_GL - m_view_toolbar.set_icons_scale(m_retina_helper->get_scale_factor()); -#else - m_view_toolbar.set_icons_scale(m_canvas->GetContentScaleFactor()); -#endif /* __WXMSW__ */ - - // places the toolbar on the bottom-left corner of the 3d scene - float top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar.get_height()) * inv_zoom; - float left = -0.5f * (float)cnv_size.get_width() * inv_zoom; - m_view_toolbar.set_position(top, left); - } -} -#endif // !ENABLE_SVG_ICONS - void GLCanvas3D::_update_selection_from_hover() { bool ctrl_pressed = wxGetKeyState(WXK_CONTROL); @@ -5722,13 +5651,29 @@ void GLCanvas3D::_update_selection_from_hover() if (m_selection.is_empty()) m_gizmos.reset_all_states(); else - m_gizmos.refresh_on_off_state(m_selection); + m_gizmos.refresh_on_off_state(); - m_gizmos.update_data(*this); + m_gizmos.update_data(); post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); m_dirty = true; } +bool GLCanvas3D::_deactivate_undo_redo_toolbar_items() +{ + if (m_toolbar.is_item_pressed("undo")) + { + m_toolbar.force_right_action(m_toolbar.get_item_id("undo"), *this); + return true; + } + else if (m_toolbar.is_item_pressed("redo")) + { + m_toolbar.force_right_action(m_toolbar.get_item_id("redo"), *this); + return true; + } + + return false; +} + const Print* GLCanvas3D::fff_print() const { return (m_process == nullptr) ? nullptr : m_process->fff_print(); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 36d16035e..fdf7cf38e 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -11,6 +11,7 @@ #include "Camera.hpp" #include "Selection.hpp" #include "Gizmos/GLGizmosManager.hpp" +#include "GUI_ObjectLayers.hpp" #include @@ -125,6 +126,8 @@ wxDECLARE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, wxKeyEvent); wxDECLARE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent); +wxDECLARE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent); +wxDECLARE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent); class GLCanvas3D { @@ -451,7 +454,6 @@ private: // Screen is only refreshed from the OnIdle handler if it is dirty. bool m_dirty; bool m_initialized; - bool m_use_VBOs; bool m_apply_zoom_to_volumes_filter; mutable std::vector m_hover_volume_idxs; bool m_warning_texture_enabled; @@ -476,10 +478,16 @@ private: GCodePreviewVolumeIndex m_gcode_preview_volume_index; +#if ENABLE_RENDER_PICKING_PASS + bool m_show_picking_texture; +#endif // ENABLE_RENDER_PICKING_PASS + #if ENABLE_RENDER_STATISTICS RenderStats m_render_stats; #endif // ENABLE_RENDER_STATISTICS + int m_imgui_undo_redo_hovered_pos{ -1 }; + public: GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar); ~GLCanvas3D(); @@ -489,7 +497,7 @@ public: wxGLCanvas* get_wxglcanvas() { return m_canvas; } const wxGLCanvas* get_wxglcanvas() const { return m_canvas; } - bool init(bool useVBOs); + bool init(); void post_event(wxEvent &&event); void set_as_dirty(); @@ -508,6 +516,9 @@ public: const Selection& get_selection() const { return m_selection; } Selection& get_selection() { return m_selection; } + const GLGizmosManager& get_gizmos_manager() const { return m_gizmos; } + GLGizmosManager& get_gizmos_manager() { return m_gizmos; } + void bed_shape_changed(); void set_clipping_plane(unsigned int id, const ClippingPlane& plane) @@ -591,11 +602,12 @@ public: void set_tooltip(const std::string& tooltip) const; - void do_move(); - void do_rotate(); - void do_scale(); - void do_flatten(); - void do_mirror(); + // the following methods add a snapshot to the undo/redo stack, unless the given string is empty + void do_move(const std::string& snapshot_type); + void do_rotate(const std::string& snapshot_type); + void do_scale(const std::string& snapshot_type); + void do_flatten(const Vec3d& normal, const std::string& snapshot_type); + void do_mirror(const std::string& snapshot_type); void set_camera_zoom(double zoom); @@ -603,6 +615,7 @@ public: void reset_all_gizmos() { m_gizmos.reset_all_states(); } void handle_sidebar_focus_event(const std::string& opt_key, bool focus_on); + void handle_layers_data_focus_event(const t_layer_height_range range, const EditorType type); void update_ui_from_settings(); @@ -686,6 +699,7 @@ private: #endif // ENABLE_SHOW_CAMERA_TARGET void _render_sla_slices() const; void _render_selection_sidebar_hints() const; + void _render_undo_redo_stack(const bool is_undo, float pos_x); void _update_volumes_hover_state() const; @@ -741,13 +755,11 @@ private: bool _is_any_volume_outside() const; -#if !ENABLE_SVG_ICONS - void _resize_toolbars() const; -#endif // !ENABLE_SVG_ICONS - // updates the selection from the content of m_hover_volume_idxs void _update_selection_from_hover(); + bool _deactivate_undo_redo_toolbar_items(); + static std::vector _parse_colors(const std::vector& colors); public: diff --git a/src/slic3r/GUI/GLCanvas3DManager.cpp b/src/slic3r/GUI/GLCanvas3DManager.cpp index a1430ef22..b2a3161e8 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.cpp +++ b/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -192,7 +192,6 @@ GLCanvas3DManager::GLInfo GLCanvas3DManager::s_gl_info; GLCanvas3DManager::GLCanvas3DManager() : m_context(nullptr) , m_gl_initialized(false) - , m_use_VBOs(false) { } @@ -266,8 +265,6 @@ void GLCanvas3DManager::init_gl() if (!m_gl_initialized) { glewInit(); - const AppConfig* config = GUI::get_app_config(); - m_use_VBOs = s_gl_info.is_version_greater_or_equal_to(2, 0); m_gl_initialized = true; if (GLEW_EXT_texture_compression_s3tc) s_compressed_textures_supported = true; @@ -323,7 +320,7 @@ bool GLCanvas3DManager::init(GLCanvas3D& canvas) if (!m_gl_initialized) init_gl(); - return canvas.init(m_use_VBOs); + return canvas.init(); } void GLCanvas3DManager::detect_multisample(int* attribList) diff --git a/src/slic3r/GUI/GLCanvas3DManager.hpp b/src/slic3r/GUI/GLCanvas3DManager.hpp index 7a600dcbd..c0e0df622 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.hpp +++ b/src/slic3r/GUI/GLCanvas3DManager.hpp @@ -75,7 +75,6 @@ private: wxGLContext* m_context; static GLInfo s_gl_info; bool m_gl_initialized; - bool m_use_VBOs; static EMultisampleState s_multisample; static bool s_compressed_textures_supported; diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index f8082ad7e..0002eda2d 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -34,20 +34,24 @@ wxDEFINE_EVENT(EVT_GLVIEWTOOLBAR_PREVIEW, SimpleEvent); const GLToolbarItem::ActionCallback GLToolbarItem::Default_Action_Callback = [](){}; const GLToolbarItem::VisibilityCallback GLToolbarItem::Default_Visibility_Callback = []()->bool { return true; }; -const GLToolbarItem::EnabledStateCallback GLToolbarItem::Default_Enabled_State_Callback = []()->bool { return true; }; +const GLToolbarItem::EnablingCallback GLToolbarItem::Default_Enabling_Callback = []()->bool { return true; }; +const GLToolbarItem::RenderCallback GLToolbarItem::Default_Render_Callback = [](float, float, float, float){}; + +GLToolbarItem::Data::Option::Option() + : toggable(false) + , action_callback(Default_Action_Callback) + , render_callback(nullptr) +{ +} GLToolbarItem::Data::Data() : name("") -#if ENABLE_SVG_ICONS , icon_filename("") -#endif // ENABLE_SVG_ICONS , tooltip("") , sprite_id(-1) - , is_toggable(false) , visible(true) - , action_callback(Default_Action_Callback) , visibility_callback(Default_Visibility_Callback) - , enabled_state_callback(Default_Enabled_State_Callback) + , enabling_callback(Default_Enabling_Callback) { } @@ -55,6 +59,7 @@ GLToolbarItem::GLToolbarItem(GLToolbarItem::EType type, const GLToolbarItem::Dat : m_type(type) , m_state(Normal) , m_data(data) + , m_last_action_type(Undefined) { } @@ -70,7 +75,7 @@ bool GLToolbarItem::update_visibility() bool GLToolbarItem::update_enabled_state() { - bool enabled = m_data.enabled_state_callback(); + bool enabled = m_data.enabling_callback(); bool ret = (is_enabled() != enabled); if (ret) m_state = enabled ? GLToolbarItem::Normal : GLToolbarItem::Disabled; @@ -81,6 +86,14 @@ bool GLToolbarItem::update_enabled_state() void GLToolbarItem::render(unsigned int tex_id, float left, float right, float bottom, float top, unsigned int tex_width, unsigned int tex_height, unsigned int icon_size) const { GLTexture::render_sub_texture(tex_id, left, right, bottom, top, get_uvs(tex_width, tex_height, icon_size)); + + if (is_pressed()) + { + if ((m_last_action_type == Left) && m_data.left.can_render()) + m_data.left.render_callback(left, right, bottom, top); + else if ((m_last_action_type == Right) && m_data.right.can_render()) + m_data.right.render_callback(left, right, bottom, top); + } } GLTexture::Quad_UVs GLToolbarItem::get_uvs(unsigned int tex_width, unsigned int tex_height, unsigned int icon_size) const @@ -105,14 +118,6 @@ GLTexture::Quad_UVs GLToolbarItem::get_uvs(unsigned int tex_width, unsigned int return uvs; } -#if !ENABLE_SVG_ICONS -ItemsIconsTexture::Metadata::Metadata() - : filename("") - , icon_size(0) -{ -} -#endif // !ENABLE_SVG_ICONS - BackgroundTexture::Metadata::Metadata() : filename("") , left(0) @@ -122,9 +127,7 @@ BackgroundTexture::Metadata::Metadata() { } -#if ENABLE_SVG_ICONS const float GLToolbar::Default_Icons_Size = 40.0f; -#endif // ENABLE_SVG_ICONS GLToolbar::Layout::Layout() : type(Horizontal) @@ -134,32 +137,21 @@ GLToolbar::Layout::Layout() , border(0.0f) , separator_size(0.0f) , gap_size(0.0f) -#if ENABLE_SVG_ICONS , icons_size(Default_Icons_Size) , scale(1.0f) -#else - , icons_scale(1.0f) -#endif // ENABLE_SVG_ICONS , width(0.0f) , height(0.0f) , dirty(true) { } -#if ENABLE_SVG_ICONS GLToolbar::GLToolbar(GLToolbar::EType type, const std::string& name) -#else -GLToolbar::GLToolbar(GLToolbar::EType type) -#endif // ENABLE_SVG_ICONS : m_type(type) -#if ENABLE_SVG_ICONS , m_name(name) -#endif // ENABLE_SVG_ICONS , m_enabled(false) -#if ENABLE_SVG_ICONS , m_icons_texture_dirty(true) -#endif // ENABLE_SVG_ICONS , m_tooltip("") + , m_pressed_toggable_id(-1) { } @@ -171,27 +163,13 @@ GLToolbar::~GLToolbar() } } -#if ENABLE_SVG_ICONS bool GLToolbar::init(const BackgroundTexture::Metadata& background_texture) -#else -bool GLToolbar::init(const ItemsIconsTexture::Metadata& icons_texture, const BackgroundTexture::Metadata& background_texture) -#endif // ENABLE_SVG_ICONS { -#if ENABLE_SVG_ICONS if (m_background_texture.texture.get_id() != 0) return true; std::string path = resources_dir() + "/icons/"; bool res = false; -#else - if (m_icons_texture.texture.get_id() != 0) - return true; - - std::string path = resources_dir() + "/icons/"; - bool res = !icons_texture.filename.empty() && m_icons_texture.texture.load_from_file(path + icons_texture.filename, false); - if (res) - m_icons_texture.metadata = icons_texture; -#endif // ENABLE_SVG_ICONS if (!background_texture.filename.empty()) res = m_background_texture.texture.load_from_file(path + background_texture.filename, false, true); @@ -247,7 +225,6 @@ void GLToolbar::set_gap_size(float size) m_layout.dirty = true; } -#if ENABLE_SVG_ICONS void GLToolbar::set_icons_size(float size) { if (m_layout.icons_size != size) @@ -267,13 +244,6 @@ void GLToolbar::set_scale(float scale) m_icons_texture_dirty = true; } } -#else -void GLToolbar::set_icons_scale(float scale) -{ - m_layout.icons_scale = scale; - m_layout.dirty = true; -} -#endif // ENABLE_SVG_ICONS bool GLToolbar::is_enabled() const { @@ -341,7 +311,7 @@ void GLToolbar::select_item(const std::string& name) bool GLToolbar::is_item_pressed(const std::string& name) const { - for (GLToolbarItem* item : m_items) + for (const GLToolbarItem* item : m_items) { if (item->get_name() == name) return item->is_pressed(); @@ -352,7 +322,7 @@ bool GLToolbar::is_item_pressed(const std::string& name) const bool GLToolbar::is_item_disabled(const std::string& name) const { - for (GLToolbarItem* item : m_items) + for (const GLToolbarItem* item : m_items) { if (item->get_name() == name) return item->is_disabled(); @@ -363,7 +333,7 @@ bool GLToolbar::is_item_disabled(const std::string& name) const bool GLToolbar::is_item_visible(const std::string& name) const { - for (GLToolbarItem* item : m_items) + for (const GLToolbarItem* item : m_items) { if (item->get_name() == name) return item->is_visible(); @@ -372,11 +342,46 @@ bool GLToolbar::is_item_visible(const std::string& name) const return false; } +bool GLToolbar::is_any_item_pressed() const +{ + for (const GLToolbarItem* item : m_items) + { + if (item->is_pressed()) + return true; + } + + return false; +} + +unsigned int GLToolbar::get_item_id(const std::string& name) const +{ + for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i) + { + if (m_items[i]->get_name() == name) + return i; + } + + return -1; +} + +void GLToolbar::force_left_action(unsigned int item_id, GLCanvas3D& parent) +{ + do_action(GLToolbarItem::Left, item_id, parent, false); +} + +void GLToolbar::force_right_action(unsigned int item_id, GLCanvas3D& parent) +{ + do_action(GLToolbarItem::Right, item_id, parent, false); +} + bool GLToolbar::update_items_state() { bool ret = false; ret |= update_items_visibility(); ret |= update_items_enabled_state(); + if (!is_any_item_pressed()) + m_pressed_toggable_id = -1; + return ret; } @@ -385,10 +390,8 @@ void GLToolbar::render(const GLCanvas3D& parent) const if (!m_enabled || m_items.empty()) return; -#if ENABLE_SVG_ICONS if (m_icons_texture_dirty) generate_icons_texture(); -#endif // ENABLE_SVG_ICONS switch (m_layout.type) { @@ -440,10 +443,11 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) m_mouse_capture.left = true; m_mouse_capture.parent = &parent; processed = true; - if ((item_id != -2) && !m_items[item_id]->is_separator()) + if ((item_id != -2) && !m_items[item_id]->is_separator() && ((m_pressed_toggable_id == -1) || (m_items[item_id]->get_last_action_type() == GLToolbarItem::Left))) { // mouse is inside an icon - do_action((unsigned int)item_id, parent); + do_action(GLToolbarItem::Left, (unsigned int)item_id, parent, true); + parent.set_as_dirty(); } } else if (evt.MiddleDown()) @@ -455,6 +459,13 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) { m_mouse_capture.right = true; m_mouse_capture.parent = &parent; + processed = true; + if ((item_id != -2) && !m_items[item_id]->is_separator() && ((m_pressed_toggable_id == -1) || (m_items[item_id]->get_last_action_type() == GLToolbarItem::Right))) + { + // mouse is inside an icon + do_action(GLToolbarItem::Right, (unsigned int)item_id, parent, true); + parent.set_as_dirty(); + } } else if (evt.LeftUp()) processed = true; @@ -492,20 +503,12 @@ float GLToolbar::get_width_horizontal() const float GLToolbar::get_width_vertical() const { -#if ENABLE_SVG_ICONS return (2.0f * m_layout.border + m_layout.icons_size) * m_layout.scale; -#else - return 2.0f * m_layout.border * m_layout.icons_scale + m_icons_texture.metadata.icon_size * m_layout.icons_scale; -#endif // ENABLE_SVG_ICONS } float GLToolbar::get_height_horizontal() const { -#if ENABLE_SVG_ICONS return (2.0f * m_layout.border + m_layout.icons_size) * m_layout.scale; -#else - return 2.0f * m_layout.border * m_layout.icons_scale + m_icons_texture.metadata.icon_size * m_layout.icons_scale; -#endif // ENABLE_SVG_ICONS } float GLToolbar::get_height_vertical() const @@ -515,7 +518,6 @@ float GLToolbar::get_height_vertical() const float GLToolbar::get_main_size() const { -#if ENABLE_SVG_ICONS float size = 2.0f * m_layout.border; for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i) { @@ -531,59 +533,64 @@ float GLToolbar::get_main_size() const if (m_items.size() > 1) size += ((float)m_items.size() - 1.0f) * m_layout.gap_size; - size *= m_layout.scale; -#else - float size = 2.0f * m_layout.border * m_layout.icons_scale; - for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i) - { - if (!m_items[i]->is_visible()) - continue; - - if (m_items[i]->is_separator()) - size += m_layout.separator_size * m_layout.icons_scale; - else - size += (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale; - } - - if (m_items.size() > 1) - size += ((float)m_items.size() - 1.0f) * m_layout.gap_size * m_layout.icons_scale; -#endif // ENABLE_SVG_ICONS - - return size; + return size * m_layout.scale; } -void GLToolbar::do_action(unsigned int item_id, GLCanvas3D& parent) +void GLToolbar::do_action(GLToolbarItem::EActionType type, unsigned int item_id, GLCanvas3D& parent, bool check_hover) { - if (item_id < (unsigned int)m_items.size()) + if ((m_pressed_toggable_id == -1) || (m_pressed_toggable_id == item_id)) { - GLToolbarItem* item = m_items[item_id]; - if ((item != nullptr) && !item->is_separator() && item->is_hovered()) + if (item_id < (unsigned int)m_items.size()) { - if (item->is_toggable()) + GLToolbarItem* item = m_items[item_id]; + if ((item != nullptr) && !item->is_separator() && (!check_hover || item->is_hovered())) { - GLToolbarItem::EState state = item->get_state(); - if (state == GLToolbarItem::Hover) - item->set_state(GLToolbarItem::HoverPressed); - else if (state == GLToolbarItem::HoverPressed) - item->set_state(GLToolbarItem::Hover); - - parent.render(); - item->do_action(); - } - else - { - if (m_type == Radio) - select_item(item->get_name()); - else - item->set_state(GLToolbarItem::HoverPressed); - - parent.render(); - item->do_action(); - if ((m_type == Normal) && (item->get_state() != GLToolbarItem::Disabled)) + if (((type == GLToolbarItem::Right) && item->is_right_toggable()) || + ((type == GLToolbarItem::Left) && item->is_left_toggable())) { - // the item may get disabled during the action, if not, set it back to hover state - item->set_state(GLToolbarItem::Hover); + GLToolbarItem::EState state = item->get_state(); + if (state == GLToolbarItem::Hover) + item->set_state(GLToolbarItem::HoverPressed); + else if (state == GLToolbarItem::HoverPressed) + item->set_state(GLToolbarItem::Hover); + else if (state == GLToolbarItem::Pressed) + item->set_state(GLToolbarItem::Normal); + else if (state == GLToolbarItem::Normal) + item->set_state(GLToolbarItem::Pressed); + + m_pressed_toggable_id = item->is_pressed() ? item_id : -1; + item->reset_last_action_type(); + parent.render(); + switch (type) + { + default: + case GLToolbarItem::Left: { item->do_left_action(); break; } + case GLToolbarItem::Right: { item->do_right_action(); break; } + } + } + else + { + if (m_type == Radio) + select_item(item->get_name()); + else + item->set_state(item->is_hovered() ? GLToolbarItem::HoverPressed : GLToolbarItem::Pressed); + + item->reset_last_action_type(); + parent.render(); + switch (type) + { + default: + case GLToolbarItem::Left: { item->do_left_action(); break; } + case GLToolbarItem::Right: { item->do_right_action(); break; } + } + + if ((m_type == Normal) && (item->get_state() != GLToolbarItem::Disabled)) + { + // the item may get disabled during the action, if not, set it back to hover state + item->set_state(GLToolbarItem::Hover); + parent.render(); + } } } } @@ -609,20 +616,12 @@ std::string GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLC float zoom = (float)parent.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; -#if ENABLE_SVG_ICONS float factor = m_layout.scale * inv_zoom; -#else - float factor = m_layout.icons_scale * inv_zoom; -#endif // ENABLE_SVG_ICONS Size cnv_size = parent.get_canvas_size(); Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); -#if ENABLE_SVG_ICONS float scaled_icons_size = m_layout.icons_size * factor; -#else - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; -#endif // ENABLE_SVG_ICONS float scaled_separator_size = m_layout.separator_size * factor; float scaled_gap_size = m_layout.gap_size * factor; float scaled_border = m_layout.border * factor; @@ -714,20 +713,12 @@ std::string GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCan float zoom = (float)parent.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; -#if ENABLE_SVG_ICONS float factor = m_layout.scale * inv_zoom; -#else - float factor = m_layout.icons_scale * inv_zoom; -#endif // ENABLE_SVG_ICONS Size cnv_size = parent.get_canvas_size(); Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); -#if ENABLE_SVG_ICONS float scaled_icons_size = m_layout.icons_size * factor; -#else - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; -#endif // ENABLE_SVG_ICONS float scaled_separator_size = m_layout.separator_size * factor; float scaled_gap_size = m_layout.gap_size * factor; float scaled_border = m_layout.border * factor; @@ -831,20 +822,12 @@ int GLToolbar::contains_mouse_horizontal(const Vec2d& mouse_pos, const GLCanvas3 float zoom = (float)parent.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; -#if ENABLE_SVG_ICONS float factor = m_layout.scale * inv_zoom; -#else - float factor = m_layout.icons_scale * inv_zoom; -#endif // ENABLE_SVG_ICONS Size cnv_size = parent.get_canvas_size(); Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); -#if ENABLE_SVG_ICONS float scaled_icons_size = m_layout.icons_size * factor; -#else - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; -#endif // ENABLE_SVG_ICONS float scaled_separator_size = m_layout.separator_size * factor; float scaled_gap_size = m_layout.gap_size * factor; float scaled_border = m_layout.border * factor; @@ -914,20 +897,12 @@ int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& float zoom = (float)parent.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; -#if ENABLE_SVG_ICONS float factor = m_layout.scale * inv_zoom; -#else - float factor = m_layout.icons_scale * inv_zoom; -#endif // ENABLE_SVG_ICONS Size cnv_size = parent.get_canvas_size(); Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); -#if ENABLE_SVG_ICONS float scaled_icons_size = m_layout.icons_size * factor; -#else - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; -#endif // ENABLE_SVG_ICONS float scaled_separator_size = m_layout.separator_size * factor; float scaled_gap_size = m_layout.gap_size * factor; float scaled_border = m_layout.border * factor; @@ -993,34 +968,15 @@ int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& void GLToolbar::render_horizontal(const GLCanvas3D& parent) const { -#if ENABLE_SVG_ICONS unsigned int tex_id = m_icons_texture.get_id(); int tex_width = m_icons_texture.get_width(); int tex_height = m_icons_texture.get_height(); -#else - unsigned int tex_id = m_icons_texture.texture.get_id(); - int tex_width = m_icons_texture.texture.get_width(); - int tex_height = m_icons_texture.texture.get_height(); -#endif // ENABLE_SVG_ICONS - -#if !ENABLE_SVG_ICONS - if ((tex_id == 0) || (tex_width <= 0) || (tex_height <= 0)) - return; -#endif // !ENABLE_SVG_ICONS float zoom = (float)parent.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; -#if ENABLE_SVG_ICONS float factor = inv_zoom * m_layout.scale; -#else - float factor = inv_zoom * m_layout.icons_scale; -#endif // ENABLE_SVG_ICONS -#if ENABLE_SVG_ICONS float scaled_icons_size = m_layout.icons_size * factor; -#else - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; -#endif // ENABLE_SVG_ICONS float scaled_separator_size = m_layout.separator_size * factor; float scaled_gap_size = m_layout.gap_size * factor; float scaled_border = m_layout.border * factor; @@ -1121,10 +1077,8 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent) const left += scaled_border; top -= scaled_border; -#if ENABLE_SVG_ICONS if ((tex_id == 0) || (tex_width <= 0) || (tex_height <= 0)) return; -#endif // ENABLE_SVG_ICONS // renders icons for (const GLToolbarItem* item : m_items) @@ -1136,11 +1090,7 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent) const left += separator_stride; else { -#if ENABLE_SVG_ICONS item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_width, (unsigned int)tex_height, (unsigned int)(m_layout.icons_size * m_layout.scale)); -#else - item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_width, (unsigned int)tex_height, m_icons_texture.metadata.icon_size); -#endif // ENABLE_SVG_ICONS left += icon_stride; } } @@ -1148,34 +1098,15 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent) const void GLToolbar::render_vertical(const GLCanvas3D& parent) const { -#if ENABLE_SVG_ICONS unsigned int tex_id = m_icons_texture.get_id(); int tex_width = m_icons_texture.get_width(); int tex_height = m_icons_texture.get_height(); -#else - unsigned int tex_id = m_icons_texture.texture.get_id(); - int tex_width = m_icons_texture.texture.get_width(); - int tex_height = m_icons_texture.texture.get_height(); -#endif // ENABLE_SVG_ICONS - -#if !ENABLE_SVG_ICONS - if ((tex_id == 0) || (tex_width <= 0) || (tex_height <= 0)) - return; -#endif // !ENABLE_SVG_ICONS float zoom = (float)parent.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; -#if ENABLE_SVG_ICONS float factor = inv_zoom * m_layout.scale; -#else - float factor = inv_zoom * m_layout.icons_scale; -#endif // ENABLE_SVG_ICONS -#if ENABLE_SVG_ICONS float scaled_icons_size = m_layout.icons_size * factor; -#else - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale * factor; -#endif // ENABLE_SVG_ICONS float scaled_separator_size = m_layout.separator_size * factor; float scaled_gap_size = m_layout.gap_size * factor; float scaled_border = m_layout.border * factor; @@ -1276,10 +1207,8 @@ void GLToolbar::render_vertical(const GLCanvas3D& parent) const left += scaled_border; top -= scaled_border; -#if ENABLE_SVG_ICONS if ((tex_id == 0) || (tex_width <= 0) || (tex_height <= 0)) return; -#endif // ENABLE_SVG_ICONS // renders icons for (const GLToolbarItem* item : m_items) @@ -1291,17 +1220,12 @@ void GLToolbar::render_vertical(const GLCanvas3D& parent) const top -= separator_stride; else { -#if ENABLE_SVG_ICONS item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_width, (unsigned int)tex_height, (unsigned int)(m_layout.icons_size * m_layout.scale)); -#else - item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_width, (unsigned int)tex_height, m_icons_texture.metadata.icon_size); -#endif // ENABLE_SVG_ICONS top -= icon_stride; } } } -#if ENABLE_SVG_ICONS bool GLToolbar::generate_icons_texture() const { std::string path = resources_dir() + "/icons/"; @@ -1337,7 +1261,6 @@ bool GLToolbar::generate_icons_texture() const return res; } -#endif // ENABLE_SVG_ICONS bool GLToolbar::update_items_visibility() { @@ -1371,9 +1294,15 @@ bool GLToolbar::update_items_enabled_state() { bool ret = false; - for (GLToolbarItem* item : m_items) + for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i) { + GLToolbarItem* item = m_items[i]; ret |= item->update_enabled_state(); + if (item->is_enabled() && (m_pressed_toggable_id != -1) && (m_pressed_toggable_id != i)) + { + ret = true; + item->set_state(GLToolbarItem::Disabled); + } } if (ret) diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index 24314d60f..875b2f9f6 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -36,7 +36,8 @@ class GLToolbarItem public: typedef std::function ActionCallback; typedef std::function VisibilityCallback; - typedef std::function EnabledStateCallback; + typedef std::function EnablingCallback; + typedef std::function RenderCallback; enum EType : unsigned char { @@ -45,6 +46,14 @@ public: Num_Types }; + enum EActionType : unsigned char + { + Undefined, + Left, + Right, + Num_Action_Types + }; + enum EState : unsigned char { Normal, @@ -57,29 +66,42 @@ public: struct Data { + struct Option + { + bool toggable; + ActionCallback action_callback; + RenderCallback render_callback; + + Option(); + + bool can_render() const { return toggable && (render_callback != nullptr); } + }; + std::string name; -#if ENABLE_SVG_ICONS std::string icon_filename; -#endif // ENABLE_SVG_ICONS std::string tooltip; unsigned int sprite_id; - bool is_toggable; + // mouse left click + Option left; + // mouse right click + Option right; bool visible; - ActionCallback action_callback; VisibilityCallback visibility_callback; - EnabledStateCallback enabled_state_callback; + EnablingCallback enabling_callback; Data(); }; static const ActionCallback Default_Action_Callback; static const VisibilityCallback Default_Visibility_Callback; - static const EnabledStateCallback Default_Enabled_State_Callback; + static const EnablingCallback Default_Enabling_Callback; + static const RenderCallback Default_Render_Callback; private: EType m_type; EState m_state; Data m_data; + EActionType m_last_action_type; public: GLToolbarItem(EType type, const Data& data); @@ -88,22 +110,28 @@ public: void set_state(EState state) { m_state = state; } const std::string& get_name() const { return m_data.name; } -#if ENABLE_SVG_ICONS const std::string& get_icon_filename() const { return m_data.icon_filename; } -#endif // ENABLE_SVG_ICONS const std::string& get_tooltip() const { return m_data.tooltip; } - void do_action() { m_data.action_callback(); } + void do_left_action() { m_last_action_type = Left; m_data.left.action_callback(); } + void do_right_action() { m_last_action_type = Right; m_data.right.action_callback(); } bool is_enabled() const { return m_state != Disabled; } bool is_disabled() const { return m_state == Disabled; } bool is_hovered() const { return (m_state == Hover) || (m_state == HoverPressed); } bool is_pressed() const { return (m_state == Pressed) || (m_state == HoverPressed); } - - bool is_toggable() const { return m_data.is_toggable; } bool is_visible() const { return m_data.visible; } bool is_separator() const { return m_type == Separator; } + bool is_left_toggable() const { return m_data.left.toggable; } + bool is_right_toggable() const { return m_data.right.toggable; } + + bool has_left_render_callback() const { return m_data.left.render_callback != nullptr; } + bool has_right_render_callback() const { return m_data.right.render_callback != nullptr; } + + EActionType get_last_action_type() const { return m_last_action_type; } + void reset_last_action_type() { m_last_action_type = Undefined; } + // returns true if the state changes bool update_visibility(); // returns true if the state changes @@ -118,27 +146,6 @@ private: friend class GLToolbar; }; -#if !ENABLE_SVG_ICONS -// items icon textures are assumed to be square and all with the same size in pixels, no internal check is done -// icons are layed-out into the texture starting from the top-left corner in the same order as enum GLToolbarItem::EState -// from left to right -struct ItemsIconsTexture -{ - struct Metadata - { - // path of the file containing the icons' texture - std::string filename; - // size of the square icons, in pixels - unsigned int icon_size; - - Metadata(); - }; - - GLTexture texture; - Metadata metadata; -}; -#endif // !ENABLE_SVG_ICONS - struct BackgroundTexture { struct Metadata @@ -164,9 +171,7 @@ struct BackgroundTexture class GLToolbar { public: -#if ENABLE_SVG_ICONS static const float Default_Icons_Size; -#endif // ENABLE_SVG_ICONS enum EType : unsigned char { @@ -201,12 +206,8 @@ public: float border; float separator_size; float gap_size; -#if ENABLE_SVG_ICONS float icons_size; float scale; -#else - float icons_scale; -#endif // ENABLE_SVG_ICONS float width; float height; @@ -219,16 +220,10 @@ private: typedef std::vector ItemsList; EType m_type; -#if ENABLE_SVG_ICONS std::string m_name; -#endif // ENABLE_SVG_ICONS bool m_enabled; -#if ENABLE_SVG_ICONS mutable GLTexture m_icons_texture; mutable bool m_icons_texture_dirty; -#else - ItemsIconsTexture m_icons_texture; -#endif // ENABLE_SVG_ICONS BackgroundTexture m_background_texture; mutable Layout m_layout; @@ -249,20 +244,13 @@ private: MouseCapture m_mouse_capture; std::string m_tooltip; + unsigned int m_pressed_toggable_id; public: -#if ENABLE_SVG_ICONS GLToolbar(EType type, const std::string& name); -#else - explicit GLToolbar(EType type); -#endif // ENABLE_SVG_ICONS ~GLToolbar(); -#if ENABLE_SVG_ICONS bool init(const BackgroundTexture::Metadata& background_texture); -#else - bool init(const ItemsIconsTexture::Metadata& icons_texture, const BackgroundTexture::Metadata& background_texture); -#endif // ENABLE_SVG_ICONS Layout::EType get_layout_type() const; void set_layout_type(Layout::EType type); @@ -273,12 +261,8 @@ public: void set_border(float border); void set_separator_size(float size); void set_gap_size(float size); -#if ENABLE_SVG_ICONS void set_icons_size(float size); void set_scale(float scale); -#else - void set_icons_scale(float scale); -#endif // ENABLE_SVG_ICONS bool is_enabled() const; void set_enabled(bool enable); @@ -295,8 +279,14 @@ public: bool is_item_disabled(const std::string& name) const; bool is_item_visible(const std::string& name) const; - const std::string& get_tooltip() const { return m_tooltip; } + bool is_any_item_pressed() const; + unsigned int get_item_id(const std::string& name) const; + + void force_left_action(unsigned int item_id, GLCanvas3D& parent); + void force_right_action(unsigned int item_id, GLCanvas3D& parent); + + const std::string& get_tooltip() const { return m_tooltip; } // returns true if any item changed its state bool update_items_state(); @@ -312,7 +302,7 @@ private: float get_height_horizontal() const; float get_height_vertical() const; float get_main_size() const; - void do_action(unsigned int item_id, GLCanvas3D& parent); + void do_action(GLToolbarItem::EActionType type, unsigned int item_id, GLCanvas3D& parent, bool check_hover); std::string update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent); std::string update_hover_state_horizontal(const Vec2d& mouse_pos, GLCanvas3D& parent); std::string update_hover_state_vertical(const Vec2d& mouse_pos, GLCanvas3D& parent); @@ -324,9 +314,7 @@ private: void render_horizontal(const GLCanvas3D& parent) const; void render_vertical(const GLCanvas3D& parent) const; -#if ENABLE_SVG_ICONS bool generate_icons_texture() const; -#endif // ENABLE_SVG_ICONS // returns true if any item changed its state bool update_items_visibility(); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 4f1c3adc8..8a376c3a3 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -141,6 +141,18 @@ GUI_App::GUI_App() , m_imgui(new ImGuiWrapper()) {} +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; +} + bool GUI_App::OnInit() { try { @@ -922,6 +934,11 @@ ObjectList* GUI_App::obj_list() return sidebar().obj_list(); } +ObjectLayers* GUI_App::obj_layers() +{ + return sidebar().obj_layers(); +} + Plater* GUI_App::plater() { return plater_; diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 3f8b23e2d..e69503ff8 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -95,6 +95,7 @@ public: bool initialized() const { return m_initialized; } GUI_App(); + ~GUI_App(); static unsigned get_colour_approx_luma(const wxColour &colour); static bool dark_mode(); @@ -155,6 +156,7 @@ public: ObjectManipulation* obj_manipul(); ObjectSettings* obj_settings(); ObjectList* obj_list(); + ObjectLayers* obj_layers(); Plater* plater(); std::vector *model_objects(); diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp new file mode 100644 index 000000000..b30d3ecd3 --- /dev/null +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -0,0 +1,341 @@ +#include "GUI_ObjectLayers.hpp" +#include "GUI_ObjectList.hpp" + +#include "OptionsGroup.hpp" +#include "PresetBundle.hpp" +#include "libslic3r/Model.hpp" +#include "GLCanvas3D.hpp" + +#include + +#include "I18N.hpp" + +#include + +namespace Slic3r +{ +namespace GUI +{ + +ObjectLayers::ObjectLayers(wxWindow* parent) : + OG_Settings(parent, true) +{ + m_grid_sizer = new wxFlexGridSizer(3, 5, 5); // "Min Z", "Max Z", "Layer height" & buttons sizer + m_grid_sizer->SetFlexibleDirection(wxHORIZONTAL); + + // Legend for object layers + for (const std::string col : { "Min Z", "Max Z", "Layer height" }) { + auto temp = new wxStaticText(m_parent, wxID_ANY, _(L(col)), wxDefaultPosition, /*size*/wxDefaultSize, wxST_ELLIPSIZE_MIDDLE); + temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + temp->SetBackgroundStyle(wxBG_STYLE_PAINT); + temp->SetFont(wxGetApp().bold_font()); + + m_grid_sizer->Add(temp); + } + + m_og->sizer->Clear(true); + m_og->sizer->Add(m_grid_sizer, 0, wxEXPAND | wxALL, wxOSX ? 0 : 5); + + m_bmp_delete = ScalableBitmap(parent, "remove_copies"/*"cross"*/); + m_bmp_add = ScalableBitmap(parent, "add_copies"); +} + +void ObjectLayers::select_editor(LayerRangeEditor* editor, const bool is_last_edited_range) +{ + if (is_last_edited_range && m_selection_type == editor->type()) { + /* Workaround! Under OSX we should use CallAfter() for SetFocus() after LayerEditors "reorganizations", + * because of selected control's strange behavior: + * cursor is set to the control, but blue border - doesn't. + * And as a result we couldn't edit this control. + * */ +#ifdef __WXOSX__ + wxTheApp->CallAfter([editor]() { +#endif + editor->SetFocus(); + editor->SetInsertionPointEnd(); +#ifdef __WXOSX__ + }); +#endif + } +} + +wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range) +{ + const bool is_last_edited_range = range == m_selectable_range; + + auto set_focus_data = [range, this](const EditorType type) + { + m_selectable_range = range; + m_selection_type = type; + }; + + auto update_focus_data = [range, this](const t_layer_height_range& new_range, EditorType type, bool enter_pressed) + { + // change selectable range for new one, if enter was pressed or if same range was selected + if (enter_pressed || m_selectable_range == range) + m_selectable_range = new_range; + if (enter_pressed) + m_selection_type = type; + }; + + // Add control for the "Min Z" + + auto editor = new LayerRangeEditor(this, double_to_string(range.first), etMinZ, + set_focus_data, [range, update_focus_data, this](coordf_t min_z, bool enter_pressed) + { + if (fabs(min_z - range.first) < EPSILON) { + m_selection_type = etUndef; + return false; + } + + // data for next focusing + coordf_t max_z = min_z < range.second ? range.second : min_z + 0.5; + const t_layer_height_range& new_range = { min_z, max_z }; + update_focus_data(new_range, etMinZ, enter_pressed); + + return wxGetApp().obj_list()->edit_layer_range(range, new_range); + }); + + select_editor(editor, is_last_edited_range); + m_grid_sizer->Add(editor); + + // Add control for the "Max Z" + + editor = new LayerRangeEditor(this, double_to_string(range.second), etMaxZ, + set_focus_data, [range, update_focus_data, this](coordf_t max_z, bool enter_pressed) + { + if (fabs(max_z - range.second) < EPSILON || range.first > max_z) { + m_selection_type = etUndef; + return false; // LayersList would not be updated/recreated + } + + // data for next focusing + const t_layer_height_range& new_range = { range.first, max_z }; + update_focus_data(new_range, etMaxZ, enter_pressed); + + return wxGetApp().obj_list()->edit_layer_range(range, new_range); + }); + + select_editor(editor, is_last_edited_range); + m_grid_sizer->Add(editor); + + // Add control for the "Layer height" + + editor = new LayerRangeEditor(this, + double_to_string(m_object->layer_config_ranges[range].option("layer_height")->getFloat()), + etLayerHeight, set_focus_data, [range, this](coordf_t layer_height, bool) + { + return wxGetApp().obj_list()->edit_layer_range(range, layer_height); + }); + + select_editor(editor, is_last_edited_range); + + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(editor); + m_grid_sizer->Add(sizer); + + return sizer; +} + +void ObjectLayers::create_layers_list() +{ + for (const auto layer : m_object->layer_config_ranges) + { + const t_layer_height_range& range = layer.first; + auto sizer = create_layer(range); + + auto del_btn = new ScalableButton(m_parent, wxID_ANY, m_bmp_delete); + del_btn->SetToolTip(_(L("Remove layer"))); + + sizer->Add(del_btn, 0, wxRIGHT | wxLEFT, em_unit(m_parent)); + + del_btn->Bind(wxEVT_BUTTON, [this, range](wxEvent &event) { + wxGetApp().obj_list()->del_layer_range(range); + }); + + auto add_btn = new ScalableButton(m_parent, wxID_ANY, m_bmp_add); + add_btn->SetToolTip(_(L("Add layer"))); + + sizer->Add(add_btn, 0, wxRIGHT, em_unit(m_parent)); + + add_btn->Bind(wxEVT_BUTTON, [this, range](wxEvent &event) { + wxGetApp().obj_list()->add_layer_range_after_current(range); + }); + } +} + +void ObjectLayers::update_layers_list() +{ + ObjectList* objects_ctrl = wxGetApp().obj_list(); + if (objects_ctrl->multiple_selection()) return; + + const auto item = objects_ctrl->GetSelection(); + if (!item) return; + + const int obj_idx = objects_ctrl->get_selected_obj_idx(); + if (obj_idx < 0) return; + + const ItemType type = objects_ctrl->GetModel()->GetItemType(item); + if (!(type & (itLayerRoot | itLayer))) return; + + m_object = objects_ctrl->object(obj_idx); + if (!m_object || m_object->layer_config_ranges.empty()) return; + + // Delete all controls from options group except of the legends + + const int cols = m_grid_sizer->GetEffectiveColsCount(); + const int rows = m_grid_sizer->GetEffectiveRowsCount(); + for (int idx = cols*rows-1; idx >= cols; idx--) { + wxSizerItem* t = m_grid_sizer->GetItem(idx); + if (t->IsSizer()) + t->GetSizer()->Clear(true); + else + t->DeleteWindows(); + m_grid_sizer->Remove(idx); + } + + // Add new control according to the selected item + + if (type & itLayerRoot) + create_layers_list(); + else + create_layer(objects_ctrl->GetModel()->GetLayerRangeByItem(item)); + + m_parent->Layout(); +} + +void ObjectLayers::update_scene_from_editor_selection() const +{ + // needed to show the visual hints in 3D scene + wxGetApp().plater()->canvas3D()->handle_layers_data_focus_event(m_selectable_range, m_selection_type); +} + +void ObjectLayers::UpdateAndShow(const bool show) +{ + if (show) + update_layers_list(); + + OG_Settings::UpdateAndShow(show); +} + +void ObjectLayers::msw_rescale() +{ + m_bmp_delete.msw_rescale(); + m_bmp_add.msw_rescale(); +} + +void ObjectLayers::reset_selection() +{ + m_selectable_range = { 0.0, 0.0 }; + m_selection_type = etLayerHeight; +} + +LayerRangeEditor::LayerRangeEditor( ObjectLayers* parent, + const wxString& value, + EditorType type, + std::function set_focus_data_fn, + std::function edit_fn + ) : + m_valid_value(value), + m_type(type), + m_set_focus_data(set_focus_data_fn), + wxTextCtrl(parent->m_parent, wxID_ANY, value, wxDefaultPosition, + wxSize(8 * em_unit(parent->m_parent), wxDefaultCoord), wxTE_PROCESS_ENTER) +{ + this->SetFont(wxGetApp().normal_font()); + + this->Bind(wxEVT_TEXT_ENTER, [this, edit_fn](wxEvent&) + { + m_enter_pressed = true; + // If LayersList wasn't updated/recreated, we can call wxEVT_KILL_FOCUS.Skip() + if (m_type&etLayerHeight) { + if (!edit_fn(get_value(), true)) + SetValue(m_valid_value); + else + m_valid_value = double_to_string(get_value()); + m_call_kill_focus = true; + } + else if (!edit_fn(get_value(), true)) { + SetValue(m_valid_value); + m_call_kill_focus = true; + } + }, this->GetId()); + + this->Bind(wxEVT_KILL_FOCUS, [this, edit_fn](wxFocusEvent& e) + { + if (!m_enter_pressed) { +#ifndef __WXGTK__ + /* Update data for next editor selection. + * But under GTK it lucks like there is no information about selected control at e.GetWindow(), + * so we'll take it from wxEVT_LEFT_DOWN event + * */ + LayerRangeEditor* new_editor = dynamic_cast(e.GetWindow()); + if (new_editor) + new_editor->set_focus_data(); +#endif // not __WXGTK__ + // If LayersList wasn't updated/recreated, we should call e.Skip() + if (m_type & etLayerHeight) { + if (!edit_fn(get_value(), false)) + SetValue(m_valid_value); + else + m_valid_value = double_to_string(get_value()); + e.Skip(); + } + else if (!edit_fn(get_value(), false)) { + SetValue(m_valid_value); + e.Skip(); + } + } + else if (m_call_kill_focus) { + m_call_kill_focus = false; + e.Skip(); + } + }, this->GetId()); + + this->Bind(wxEVT_SET_FOCUS, [this, parent](wxFocusEvent& e) + { + set_focus_data(); + parent->update_scene_from_editor_selection(); + e.Skip(); + }, this->GetId()); + +#ifdef __WXGTK__ // Workaround! To take information about selectable range + this->Bind(wxEVT_LEFT_DOWN, [this](wxEvent& e) + { + set_focus_data(); + e.Skip(); + }, this->GetId()); +#endif //__WXGTK__ + + this->Bind(wxEVT_CHAR, ([this](wxKeyEvent& event) + { + // select all text using Ctrl+A + if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL)) + this->SetSelection(-1, -1); //select all + event.Skip(); + })); +} + +coordf_t LayerRangeEditor::get_value() +{ + wxString str = GetValue(); + + coordf_t layer_height; + // Replace the first occurence of comma in decimal number. + str.Replace(",", ".", false); + if (str == ".") + layer_height = 0.0; + else + { + if (!str.ToCDouble(&layer_height) || layer_height < 0.0f) + { + show_error(m_parent, _(L("Invalid numeric input."))); + SetValue(double_to_string(layer_height)); + } + } + + return layer_height; +} + +} //namespace GUI +} //namespace Slic3r \ No newline at end of file diff --git a/src/slic3r/GUI/GUI_ObjectLayers.hpp b/src/slic3r/GUI/GUI_ObjectLayers.hpp new file mode 100644 index 000000000..f274183e2 --- /dev/null +++ b/src/slic3r/GUI/GUI_ObjectLayers.hpp @@ -0,0 +1,88 @@ +#ifndef slic3r_GUI_ObjectLayers_hpp_ +#define slic3r_GUI_ObjectLayers_hpp_ + +#include "GUI_ObjectSettings.hpp" +#include "wxExtensions.hpp" + +#ifdef __WXOSX__ +#include "../libslic3r/PrintConfig.hpp" +#endif + +class wxBoxSizer; + +namespace Slic3r { +class ModelObject; + +namespace GUI { +class ConfigOptionsGroup; + +typedef double coordf_t; +typedef std::pair t_layer_height_range; + +class ObjectLayers; + +enum EditorType +{ + etUndef = 0, + etMinZ = 1, + etMaxZ = 2, + etLayerHeight = 4, +}; + +class LayerRangeEditor : public wxTextCtrl +{ + bool m_enter_pressed { false }; + bool m_call_kill_focus { false }; + wxString m_valid_value; + EditorType m_type; + + std::function m_set_focus_data; + +public: + LayerRangeEditor( ObjectLayers* parent, + const wxString& value = wxEmptyString, + EditorType type = etUndef, + std::function set_focus_data_fn = [](EditorType) {;}, + std::function edit_fn = [](coordf_t, bool) {return false; } + ); + ~LayerRangeEditor() {} + + EditorType type() const {return m_type;} + void set_focus_data() const { m_set_focus_data(m_type);} + +private: + coordf_t get_value(); +}; + +class ObjectLayers : public OG_Settings +{ + ScalableBitmap m_bmp_delete; + ScalableBitmap m_bmp_add; + ModelObject* m_object {nullptr}; + + wxFlexGridSizer* m_grid_sizer; + t_layer_height_range m_selectable_range; + EditorType m_selection_type {etUndef}; + +public: + ObjectLayers(wxWindow* parent); + ~ObjectLayers() {} + + void select_editor(LayerRangeEditor* editor, const bool is_last_edited_range); + wxSizer* create_layer(const t_layer_height_range& range); // without_buttons + void create_layers_list(); + void update_layers_list(); + + void update_scene_from_editor_selection() const; + + void UpdateAndShow(const bool show) override; + void msw_rescale(); + void reset_selection(); + void set_selectable_range(const t_layer_height_range& range) { m_selectable_range = range; } + + friend class LayerRangeEditor; +}; + +}} + +#endif // slic3r_GUI_ObjectLayers_hpp_ diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index e926dcf97..9e681257c 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1,6 +1,7 @@ #include "libslic3r/libslic3r.h" #include "GUI_ObjectList.hpp" #include "GUI_ObjectManipulation.hpp" +#include "GUI_ObjectLayers.hpp" #include "GUI_App.hpp" #include "I18N.hpp" @@ -24,7 +25,7 @@ namespace GUI wxDEFINE_EVENT(EVT_OBJ_LIST_OBJECT_SELECT, SimpleEvent); // pt_FFF -FreqSettingsBundle FREQ_SETTINGS_BUNDLE_FFF = +SettingsBundle FREQ_SETTINGS_BUNDLE_FFF = { { L("Layers and Perimeters"), { "layer_height" , "perimeters", "top_solid_layers", "bottom_solid_layers" } }, { L("Infill") , { "fill_density", "fill_pattern" } }, @@ -35,7 +36,7 @@ FreqSettingsBundle FREQ_SETTINGS_BUNDLE_FFF = }; // pt_SLA -FreqSettingsBundle FREQ_SETTINGS_BUNDLE_SLA = +SettingsBundle FREQ_SETTINGS_BUNDLE_SLA = { { L("Pad and Support") , { "supports_enable", "pad_enable" } } }; @@ -65,6 +66,14 @@ static int extruders_count() return wxGetApp().extruders_cnt(); } +static void take_snapshot(const wxString& snapshot_name) +{ + wxGetApp().plater()->take_snapshot(snapshot_name); +} + +static void suppress_snapshots(){ wxGetApp().plater()->suppress_snapshots(); } +static void allow_snapshots() { wxGetApp().plater()->allow_snapshots(); } + ObjectList::ObjectList(wxWindow* parent) : wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_MULTIPLE), m_parent(parent) @@ -137,20 +146,24 @@ ObjectList::ObjectList(wxWindow* parent) : // Bind(wxEVT_KEY_DOWN, &ObjectList::OnChar, this); { // Accelerators - wxAcceleratorEntry entries[6]; + wxAcceleratorEntry entries[8]; entries[0].Set(wxACCEL_CTRL, (int) 'C', wxID_COPY); entries[1].Set(wxACCEL_CTRL, (int) 'X', wxID_CUT); entries[2].Set(wxACCEL_CTRL, (int) 'V', wxID_PASTE); entries[3].Set(wxACCEL_CTRL, (int) 'A', wxID_SELECTALL); - entries[4].Set(wxACCEL_NORMAL, WXK_DELETE, wxID_DELETE); - entries[5].Set(wxACCEL_NORMAL, WXK_BACK, wxID_DELETE); - wxAcceleratorTable accel(6, entries); + entries[4].Set(wxACCEL_CTRL, (int) 'Z', wxID_UNDO); + entries[5].Set(wxACCEL_CTRL, (int) 'Y', wxID_REDO); + entries[6].Set(wxACCEL_NORMAL, WXK_DELETE, wxID_DELETE); + entries[7].Set(wxACCEL_NORMAL, WXK_BACK, wxID_DELETE); + wxAcceleratorTable accel(8, entries); SetAcceleratorTable(accel); - this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_COPY)); }, wxID_COPY); - this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_PASTE)); }, wxID_PASTE); - this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->select_item_all_children(); }, wxID_SELECTALL); - this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->remove(); }, wxID_DELETE); + this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->copy(); }, wxID_COPY); + this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->paste(); }, wxID_PASTE); + this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->select_item_all_children(); }, wxID_SELECTALL); + this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->remove(); }, wxID_DELETE); + this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->undo(); }, wxID_UNDO); + this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->redo(); }, wxID_REDO); } #else __WXOSX__ Bind(wxEVT_CHAR, [this](wxKeyEvent& event) { key_event(event); }); // doesn't work on OSX @@ -350,12 +363,13 @@ DynamicPrintConfig& ObjectList::get_item_config(const wxDataViewItem& item) cons const ItemType type = m_objects_model->GetItemType(item); const int obj_idx = type & itObject ? m_objects_model->GetIdByItem(item) : - m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); + m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); const int vol_idx = type & itVolume ? m_objects_model->GetVolumeIdByItem(item) : -1; assert(obj_idx >= 0 || ((type & itVolume) && vol_idx >=0)); return type & itVolume ?(*m_objects)[obj_idx]->volumes[vol_idx]->config : + type & itLayer ?(*m_objects)[obj_idx]->layer_config_ranges[m_objects_model->GetLayerRangeByItem(item)] : (*m_objects)[obj_idx]->config; } @@ -441,16 +455,23 @@ void ObjectList::update_extruder_in_config(const wxDataViewItem& item) { if (m_prevent_update_extruder_in_config) return; - if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { + + const ItemType item_type = m_objects_model->GetItemType(item); + if (item_type & itObject) { const int obj_idx = m_objects_model->GetIdByItem(item); m_config = &(*m_objects)[obj_idx]->config; } else { - const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetParent(item)); + const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); + if (item_type & itVolume) + { const int volume_id = m_objects_model->GetVolumeIdByItem(item); if (obj_idx < 0 || volume_id < 0) return; m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; + } + else if (item_type & itLayer) + m_config = &get_item_config(item); } wxVariant variant; @@ -569,9 +590,75 @@ void ObjectList::selection_changed() wxPostEvent(this, event); } + if (const wxDataViewItem item = GetSelection()) + { + const ItemType type = m_objects_model->GetItemType(item); + // to correct visual hints for layers editing on the Scene + if (type & (itLayer|itLayerRoot)) { + wxGetApp().obj_layers()->reset_selection(); + + if (type & itLayerRoot) + wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false); + else { + wxGetApp().obj_layers()->set_selectable_range(m_objects_model->GetLayerRangeByItem(item)); + wxGetApp().obj_layers()->update_scene_from_editor_selection(); + } + } + } + part_selection_changed(); } +void ObjectList::fill_layer_config_ranges_cache() +{ + wxDataViewItemArray sel_layers; + GetSelections(sel_layers); + + const int obj_idx = m_objects_model->GetObjectIdByItem(sel_layers[0]); + if (obj_idx < 0 || (int)m_objects->size() <= obj_idx) + return; + + const t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; + m_layer_config_ranges_cache.clear(); + + for (const auto layer_item : sel_layers) + if (m_objects_model->GetItemType(layer_item) & itLayer) { + auto range = m_objects_model->GetLayerRangeByItem(layer_item); + auto it = ranges.find(range); + if (it != ranges.end()) + m_layer_config_ranges_cache[it->first] = it->second; + } +} + +void ObjectList::paste_layers_into_list() +{ + const int obj_idx = m_objects_model->GetObjectIdByItem(GetSelection()); + + if (obj_idx < 0 || (int)m_objects->size() <= obj_idx || + m_layer_config_ranges_cache.empty() || printer_technology() == ptSLA) + return; + + const wxDataViewItem object_item = m_objects_model->GetItemById(obj_idx); + wxDataViewItem layers_item = m_objects_model->GetLayerRootItem(object_item); + if (layers_item) + m_objects_model->Delete(layers_item); + + t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; + + // and create Layer item(s) according to the layer_config_ranges + for (const auto range : m_layer_config_ranges_cache) + ranges.emplace(range); + + layers_item = add_layer_root_item(object_item); + + changed_object(obj_idx); + + select_item(layers_item); +#ifndef __WXOSX__ + selection_changed(); +#endif //no __WXOSX__ +} + void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& volumes) { if ((obj_idx < 0) || ((int)m_objects->size() <= obj_idx)) @@ -580,7 +667,6 @@ void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& vol if (volumes.empty()) return; - ModelObject& model_object = *(*m_objects)[obj_idx]; const auto object_item = m_objects_model->GetItemById(obj_idx); wxDataViewItemArray items; @@ -590,10 +676,7 @@ void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& vol const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(object_item, wxString::FromUTF8(volume->name.c_str()), volume->type(), volume->get_mesh_errors_count()>0 , volume->config.has("extruder") ? volume->config.option("extruder")->value : 0); - auto opt_keys = volume->config.keys(); - if (!opt_keys.empty() && !((opt_keys.size() == 1) && (opt_keys[0] == "extruder"))) - select_item(m_objects_model->AddSettingsChild(vol_item)); - + add_settings_item(vol_item, &volume->config); items.Add(vol_item); } @@ -653,7 +736,7 @@ void ObjectList::OnContextMenu(wxDataViewEvent&) const wxPoint pt = get_mouse_position_in_control(); HitTest(pt, item, col); if (!item) -#ifdef __WXOSX__ // #ys_FIXME temporary workaround for OSX +#ifdef __WXOSX__ // temporary workaround for OSX // after Yosemite OS X version, HitTest return undefined item item = GetSelection(); if (item) @@ -699,10 +782,11 @@ void ObjectList::show_context_menu() if (item) { const ItemType type = m_objects_model->GetItemType(item); - if (!(type & (itObject | itVolume | itInstance))) + if (!(type & (itObject | itVolume | itLayer | itInstance))) return; wxMenu* menu = type & itInstance ? &m_menu_instance : + type & itLayer ? &m_menu_layer : m_objects_model->GetParent(item) != wxDataViewItem(0) ? &m_menu_part : printer_technology() == ptFFF ? &m_menu_object : &m_menu_sla_object; @@ -713,6 +797,32 @@ void ObjectList::show_context_menu() } } +void ObjectList::copy() +{ + if (m_selection_mode & smLayer) + fill_layer_config_ranges_cache(); + else + wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_COPY)); +} + +void ObjectList::paste() +{ + if (!m_layer_config_ranges_cache.empty()) + paste_layers_into_list(); + else + wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_PASTE)); +} + +void ObjectList::undo() +{ + wxGetApp().plater()->undo(); +} + +void ObjectList::redo() +{ + wxGetApp().plater()->redo(); +} + #ifndef __WXOSX__ void ObjectList::key_event(wxKeyEvent& event) { @@ -727,10 +837,14 @@ void ObjectList::key_event(wxKeyEvent& event) } else if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL/*WXK_SHIFT*/)) select_item_all_children(); - else if (wxGetKeyState(wxKeyCode('C')) && wxGetKeyState(WXK_CONTROL)) - wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_COPY)); + else if (wxGetKeyState(wxKeyCode('C')) && wxGetKeyState(WXK_CONTROL)) + copy(); else if (wxGetKeyState(wxKeyCode('V')) && wxGetKeyState(WXK_CONTROL)) - wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_PASTE)); + paste(); + else if (wxGetKeyState(wxKeyCode('Y')) && wxGetKeyState(WXK_CONTROL)) + redo(); + else if (wxGetKeyState(wxKeyCode('Z')) && wxGetKeyState(WXK_CONTROL)) + undo(); else event.Skip(); } @@ -816,6 +930,7 @@ void ObjectList::OnDrop(wxDataViewEvent &event) if (m_dragged_data.type() == itInstance) { + take_snapshot(_(L("Instances to Separated Objects"))); instances_to_separated_object(m_dragged_data.obj_idx(), m_dragged_data.inst_idxs()); m_dragged_data.clear(); return; @@ -833,6 +948,8 @@ void ObjectList::OnDrop(wxDataViewEvent &event) // if (to_volume_id > from_volume_id) to_volume_id--; // #endif // __WXGTK__ + take_snapshot(_(L("Remov Volume(s)"))); + auto& volumes = (*m_objects)[m_dragged_data.obj_idx()]->volumes; auto delta = to_volume_id < from_volume_id ? -1 : 1; int cnt = 0; @@ -871,7 +988,7 @@ std::vector ObjectList::get_options(const bool is_part) const std::vector& ObjectList::get_options_for_bundle(const wxString& bundle_name) { - const FreqSettingsBundle& bundle = printer_technology() == ptSLA ? + const SettingsBundle& bundle = printer_technology() == ptSLA ? FREQ_SETTINGS_BUNDLE_SLA : FREQ_SETTINGS_BUNDLE_FFF; for (auto& it : bundle) @@ -881,7 +998,7 @@ const std::vector& ObjectList::get_options_for_bundle(const wxStrin } #if 0 // if "Quick menu" is selected - FreqSettingsBundle& bundle_quick = printer_technology() == ptSLA ? + SettingsBundle& bundle_quick = printer_technology() == ptSLA ? m_freq_settings_sla: m_freq_settings_fff; for (auto& it : bundle_quick) @@ -957,7 +1074,7 @@ void ObjectList::get_settings_choice(const wxString& category_name) if (selection_cnt > 0) { // Add selected items to the "Quick menu" - FreqSettingsBundle& freq_settings = printer_technology() == ptSLA ? + SettingsBundle& freq_settings = printer_technology() == ptSLA ? m_freq_settings_sla : m_freq_settings_fff; bool changed_existing = false; @@ -998,6 +1115,8 @@ void ObjectList::get_settings_choice(const wxString& category_name) } #endif + take_snapshot(wxString::Format(_(L("Add Settings for %s")), is_part ? _(L("Sub-object")) : _(L("Object")))); + std::vector selected_options; selected_options.reserve(selection_cnt); for (auto sel : selections) @@ -1027,17 +1146,29 @@ void ObjectList::get_settings_choice(const wxString& category_name) } - // Add settings item for object - update_settings_item(); + // Add settings item for object/sub-object and show them + show_settings(add_settings_item(GetSelection(), m_config)); } void ObjectList::get_freq_settings_choice(const wxString& bundle_name) { - const std::vector& options = get_options_for_bundle(bundle_name); + std::vector options = get_options_for_bundle(bundle_name); + + /* Because of we couldn't edited layer_height for ItVolume from settings list, + * correct options according to the selected item type : + * remove "layer_height" option + */ + if ((m_objects_model->GetItemType(GetSelection()) & itVolume) && bundle_name == _("Layers and Perimeters")) { + const auto layer_height_it = std::find(options.begin(), options.end(), "layer_height"); + if (layer_height_it != options.end()) + options.erase(layer_height_it); + } assert(m_config); auto opt_keys = m_config->keys(); + take_snapshot(wxString::Format(_(L("Add Settings Bundle for %s")), m_objects_model->GetItemType(GetSelection()) & itObject ? _(L("Object")) : _(L("Sub-object")))); + const DynamicPrintConfig& from_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; for (auto& opt_key : options) { @@ -1052,13 +1183,21 @@ void ObjectList::get_freq_settings_choice(const wxString& bundle_name) } } - // Add settings item for object - update_settings_item(); + // Add settings item for object/sub-object and show them + show_settings(add_settings_item(GetSelection(), m_config)); } -void ObjectList::update_settings_item() +void ObjectList::show_settings(const wxDataViewItem settings_item) { - auto item = GetSelection(); + if (!settings_item) + return; + + select_item(settings_item); + + // update object selection on Plater + if (!m_prevent_canvas_selection_update) + update_selections_on_canvas(); +/* auto item = GetSelection(); if (item) { if (m_objects_model->GetItemType(item) == itInstance) item = m_objects_model->GetTopParent(item); @@ -1070,12 +1209,14 @@ void ObjectList::update_settings_item() if (!m_prevent_canvas_selection_update) update_selections_on_canvas(); } - else { + else { + //# ys_FIXME ??? use case ??? auto panel = wxGetApp().sidebar().scrolled_panel(); panel->Freeze(); wxGetApp().obj_settings()->UpdateAndShow(true); panel->Thaw(); } + */ } wxMenu* ObjectList::append_submenu_add_generic(wxMenu* menu, const ModelVolumeType type) { @@ -1137,6 +1278,12 @@ wxMenuItem* ObjectList::append_menu_item_split(wxMenu* menu) [this]() { return is_splittable(); }, wxGetApp().plater()); } +wxMenuItem* ObjectList::append_menu_item_layers_editing(wxMenu* menu) +{ + return append_menu_item(menu, wxID_ANY, _(L("Edit Layers")), "", + [this](wxCommandEvent&) { layers_editing(); }, "layers", menu); +} + wxMenuItem* ObjectList::append_menu_item_settings(wxMenu* menu_) { MenuWithSeparators* menu = dynamic_cast(menu_); @@ -1301,7 +1448,11 @@ void ObjectList::create_object_popupmenu(wxMenu *menu) append_menu_item_scale_selection_to_fit_print_volume(menu); // Split object to parts - m_menu_item_split = append_menu_item_split(menu); + append_menu_item_split(menu); + menu->AppendSeparator(); + + // Layers Editing for object + append_menu_item_layers_editing(menu); menu->AppendSeparator(); // rest of a object_menu will be added later in: @@ -1330,7 +1481,7 @@ void ObjectList::create_part_popupmenu(wxMenu *menu) append_menu_item_fix_through_netfabb(menu); append_menu_item_export_stl(menu); - m_menu_item_split_part = append_menu_item_split(menu); + append_menu_item_split(menu); // Append change part type menu->AppendSeparator(); @@ -1376,7 +1527,7 @@ wxMenu* ObjectList::create_settings_popupmenu(wxMenu *parent_menu) void ObjectList::create_freq_settings_popupmenu(wxMenu *menu) { // Add default settings bundles - const FreqSettingsBundle& bundle = printer_technology() == ptFFF ? + const SettingsBundle& bundle = printer_technology() == ptFFF ? FREQ_SETTINGS_BUNDLE_FFF : FREQ_SETTINGS_BUNDLE_SLA; const int extruders_cnt = extruders_count(); @@ -1391,7 +1542,7 @@ void ObjectList::create_freq_settings_popupmenu(wxMenu *menu) } #if 0 // Add "Quick" settings bundles - const FreqSettingsBundle& bundle_quick = printer_technology() == ptFFF ? + const SettingsBundle& bundle_quick = printer_technology() == ptFFF ? m_freq_settings_fff : m_freq_settings_sla; for (auto& it : bundle_quick) { @@ -1427,6 +1578,8 @@ void ObjectList::load_subobject(ModelVolumeType type) if (m_objects_model->GetItemType(item)&itInstance) item = m_objects_model->GetItemById(obj_idx); + take_snapshot(_(L("Load Part"))); + std::vector> volumes_info; load_part((*m_objects)[obj_idx], volumes_info, type); @@ -1502,6 +1655,8 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode if (instance_idx == -1) return; + take_snapshot(_(L("Add Generic Subobject"))); + // Selected object ModelObject &model_object = *(*m_objects)[obj_idx]; // Bounding box of the selected instance in world coordinate system including the translation, without modifiers. @@ -1576,38 +1731,54 @@ void ObjectList::del_subobject_item(wxDataViewItem& item) ItemType type; m_objects_model->GetItemInfo(item, type, obj_idx, idx); - if (type == itUndef) + if (type & itUndef) return; - if (type == itSettings) - del_settings_from_config(); - else if (type == itInstanceRoot && obj_idx != -1) + if (type & itSettings) + del_settings_from_config(m_objects_model->GetParent(item)); + else if (type & itInstanceRoot && obj_idx != -1) del_instances_from_object(obj_idx); + else if (type & itLayerRoot && obj_idx != -1) + del_layers_from_object(obj_idx); + else if (type & itLayer && obj_idx != -1) + del_layer_from_object(obj_idx, m_objects_model->GetLayerRangeByItem(item)); else if (idx == -1) return; else if (!del_subobject_from_object(obj_idx, idx, type)) return; // If last volume item with warning was deleted, unmark object item - if (type == itVolume && (*m_objects)[obj_idx]->get_mesh_errors_count() == 0) + if (type & itVolume && (*m_objects)[obj_idx]->get_mesh_errors_count() == 0) m_objects_model->DeleteWarningIcon(m_objects_model->GetParent(item)); m_objects_model->Delete(item); } -void ObjectList::del_settings_from_config() +void ObjectList::del_settings_from_config(const wxDataViewItem& parent_item) { - auto opt_keys = m_config->keys(); - if (opt_keys.size() == 1 && opt_keys[0] == "extruder") + const bool is_layer_settings = m_objects_model->GetItemType(parent_item) == itLayer; + + const int opt_cnt = m_config->keys().size(); + if (opt_cnt == 1 && m_config->has("extruder") || + is_layer_settings && opt_cnt == 2 && m_config->has("extruder") && m_config->has("layer_height")) return; + + take_snapshot(_(L("Delete Settings"))); + int extruder = -1; if (m_config->has("extruder")) extruder = m_config->option("extruder")->value; + coordf_t layer_height = 0.0; + if (is_layer_settings) + layer_height = m_config->opt_float("layer_height"); + m_config->clear(); if (extruder >= 0) m_config->set_key_value("extruder", new ConfigOptionInt(extruder)); + if (is_layer_settings) + m_config->set_key_value("layer_height", new ConfigOptionFloat(layer_height)); } void ObjectList::del_instances_from_object(const int obj_idx) @@ -1616,6 +1787,8 @@ void ObjectList::del_instances_from_object(const int obj_idx) if (instances.size() <= 1) return; + take_snapshot(_(L("Delete All Instances from Object"))); + while ( instances.size()> 1) instances.pop_back(); @@ -1624,6 +1797,26 @@ void ObjectList::del_instances_from_object(const int obj_idx) changed_object(obj_idx); } +void ObjectList::del_layer_from_object(const int obj_idx, const t_layer_height_range& layer_range) +{ + const auto del_range = object(obj_idx)->layer_config_ranges.find(layer_range); + if (del_range == object(obj_idx)->layer_config_ranges.end()) + return; + + take_snapshot(_(L("Delete Layers Range"))); + + object(obj_idx)->layer_config_ranges.erase(del_range); + + changed_object(obj_idx); +} + +void ObjectList::del_layers_from_object(const int obj_idx) +{ + object(obj_idx)->layer_config_ranges.clear(); + + changed_object(obj_idx); +} + bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, const int type) { if (obj_idx == 1000) @@ -1645,6 +1838,8 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con return false; } + take_snapshot(_(L("Delete Subobject"))); + object->delete_volume(idx); if (object->volumes.size() == 1) @@ -1661,6 +1856,8 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con Slic3r::GUI::show_error(nullptr, _(L("You can't delete the last intance from object."))); return false; } + + take_snapshot(_(L("Delete Instance"))); object->delete_instance(idx); } else @@ -1688,6 +1885,8 @@ void ObjectList::split() return; } + take_snapshot(_(L("Split to Parts"))); + wxBusyCursor wait; auto model_object = (*m_objects)[obj_idx]; @@ -1706,11 +1905,7 @@ void ObjectList::split() volume->config.option("extruder")->value : 0, false); // add settings to the part, if it has those - auto opt_keys = volume->config.keys(); - if ( !(opt_keys.size() == 1 && opt_keys[0] == "extruder") ) { - select_item(m_objects_model->AddSettingsChild(vol_item)); - Expand(vol_item); - } + add_settings_item(vol_item, &volume->config); } if (parent == item) @@ -1719,6 +1914,73 @@ void ObjectList::split() changed_object(obj_idx); } +void ObjectList::layers_editing() +{ + const auto item = GetSelection(); + const int obj_idx = get_selected_obj_idx(); + if (!item || obj_idx < 0) + return; + + const wxDataViewItem obj_item = m_objects_model->GetTopParent(item); + wxDataViewItem layers_item = m_objects_model->GetLayerRootItem(obj_item); + + // if it doesn't exist now + if (!layers_item.IsOk()) + { + t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; + + // set some default value + if (ranges.empty()) { + take_snapshot(_(L("Add Layers"))); + ranges[{ 0.0f, 2.0f }] = get_default_layer_config(obj_idx); + } + + // create layer root item + layers_item = add_layer_root_item(obj_item); + } + if (!layers_item.IsOk()) + return; + + // to correct visual hints for layers editing on the Scene, reset previous selection + wxGetApp().obj_layers()->reset_selection(); + wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false); + + // select LayerRoor item and expand + select_item(layers_item); + Expand(layers_item); +} + +wxDataViewItem ObjectList::add_layer_root_item(const wxDataViewItem obj_item) +{ + const int obj_idx = m_objects_model->GetIdByItem(obj_item); + if (obj_idx < 0 || + object(obj_idx)->layer_config_ranges.empty() || + printer_technology() == ptSLA) + return wxDataViewItem(0); + + // create LayerRoot item + wxDataViewItem layers_item = m_objects_model->AddLayersRoot(obj_item); + + // and create Layer item(s) according to the layer_config_ranges + for (const auto range : object(obj_idx)->layer_config_ranges) + add_layer_item(range.first, layers_item); + + Expand(layers_item); + return layers_item; +} + +DynamicPrintConfig ObjectList::get_default_layer_config(const int obj_idx) +{ + DynamicPrintConfig config; + coordf_t layer_height = object(obj_idx)->config.has("layer_height") ? + object(obj_idx)->config.opt_float("layer_height") : + wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_float("layer_height"); + config.set_key_value("layer_height",new ConfigOptionFloat(layer_height)); + config.set_key_value("extruder", new ConfigOptionInt(0)); + + return config; +} + bool ObjectList::get_volume_by_item(const wxDataViewItem& item, ModelVolume*& volume) { auto obj_idx = get_selected_obj_idx(); @@ -1788,6 +2050,7 @@ void ObjectList::part_selection_changed() bool update_and_show_manipulations = false; bool update_and_show_settings = false; + bool update_and_show_layers = false; const auto item = GetSelection(); @@ -1810,36 +2073,47 @@ void ObjectList::part_selection_changed() update_and_show_manipulations = true; } else { - auto parent = m_objects_model->GetParent(item); - // Take ID of the parent object to "inform" perl-side which object have to be selected on the scene - obj_idx = m_objects_model->GetIdByItem(parent); - if (m_objects_model->GetItemType(item) == itSettings) { - if (m_objects_model->GetParent(parent) == wxDataViewItem(0)) { + obj_idx = m_objects_model->GetObjectIdByItem(item); + + const ItemType type = m_objects_model->GetItemType(item); + if (type & itSettings) { + const auto parent = m_objects_model->GetParent(item); + const ItemType parent_type = m_objects_model->GetItemType(parent); + + if (parent_type & itObject) { og_name = _(L("Object Settings to modify")); m_config = &(*m_objects)[obj_idx]->config; } - else { + else if (parent_type & itVolume) { og_name = _(L("Part Settings to modify")); - auto main_parent = m_objects_model->GetParent(parent); - obj_idx = m_objects_model->GetIdByItem(main_parent); - const auto volume_id = m_objects_model->GetVolumeIdByItem(parent); + volume_id = m_objects_model->GetVolumeIdByItem(parent); m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; } + else if (parent_type & itLayer) { + og_name = _(L("Layer range Settings to modify")); + m_config = &get_item_config(parent); + } update_and_show_settings = true; } - else if (m_objects_model->GetItemType(item) == itVolume) { + else if (type & itVolume) { og_name = _(L("Part manipulation")); volume_id = m_objects_model->GetVolumeIdByItem(item); m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; update_and_show_manipulations = true; } - else if (m_objects_model->GetItemType(item) == itInstance) { + else if (type & itInstance) { og_name = _(L("Instance manipulation")); update_and_show_manipulations = true; // fill m_config by object's values - const int obj_idx_ = m_objects_model->GetObjectIdByItem(item); - m_config = &(*m_objects)[obj_idx_]->config; + m_config = &(*m_objects)[obj_idx]->config; + } + else if (type & (itLayerRoot|itLayer)) { + og_name = type & itLayerRoot ? _(L("Layers Editing")) : _(L("Layer Editing")); + update_and_show_layers = true; + + if (type & itLayer) + m_config = &get_item_config(item); } } } @@ -1859,18 +2133,97 @@ void ObjectList::part_selection_changed() if (update_and_show_settings) wxGetApp().obj_settings()->get_og()->set_name(" " + og_name + " "); + if (printer_technology() == ptSLA) + update_and_show_layers = false; + else if (update_and_show_layers) + wxGetApp().obj_layers()->get_og()->set_name(" " + og_name + " "); + Sidebar& panel = wxGetApp().sidebar(); panel.Freeze(); + wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false); wxGetApp().obj_manipul() ->UpdateAndShow(update_and_show_manipulations); wxGetApp().obj_settings()->UpdateAndShow(update_and_show_settings); + wxGetApp().obj_layers() ->UpdateAndShow(update_and_show_layers); wxGetApp().sidebar().show_info_sizer(); panel.Layout(); panel.Thaw(); } -void ObjectList::add_object_to_list(size_t obj_idx) +SettingsBundle ObjectList::get_item_settings_bundle(const DynamicPrintConfig* config, const bool is_layers_range_settings) +{ + auto opt_keys = config->keys(); + if (opt_keys.empty()) + return SettingsBundle(); + + update_opt_keys(opt_keys); // update options list according to print technology + + if (opt_keys.size() == 1 && opt_keys[0] == "extruder" || + is_layers_range_settings && opt_keys.size() == 2) + return SettingsBundle(); + + const int extruders_cnt = wxGetApp().extruders_edited_cnt(); + + SettingsBundle bundle; + for (auto& opt_key : opt_keys) + { + auto category = config->def()->get(opt_key)->category; + if (category.empty() || (category == "Extruders" && extruders_cnt == 1)) + continue; + + std::vector< std::string > new_category; + + auto& cat_opt = bundle.find(category) == bundle.end() ? new_category : bundle.at(category); + cat_opt.push_back(opt_key); + if (cat_opt.size() == 1) + bundle[category] = cat_opt; + } + + return bundle; +} + +// Add new SettingsItem for parent_item if it doesn't exist, or just update a digest according to new config +wxDataViewItem ObjectList::add_settings_item(wxDataViewItem parent_item, const DynamicPrintConfig* config) +{ + wxDataViewItem ret = wxDataViewItem(0); + + if (!parent_item) + return ret; + + const bool is_layers_range_settings = m_objects_model->GetItemType(parent_item) == itLayer; + SettingsBundle cat_options = get_item_settings_bundle(config, is_layers_range_settings); + if (cat_options.empty()) + return ret; + + std::vector categories; + categories.reserve(cat_options.size()); + for (auto& cat : cat_options) + { + if (cat.second.size() == 1 && + (cat.second[0] == "extruder" || is_layers_range_settings && cat.second[0] == "layer_height")) + continue; + + categories.push_back(cat.first); + } + + if (categories.empty()) + return ret; + + if (m_objects_model->GetItemType(parent_item) & itInstance) + parent_item = m_objects_model->GetTopParent(parent_item); + + ret = m_objects_model->IsSettingsItem(parent_item) ? parent_item : m_objects_model->GetSettingsItem(parent_item); + + if (!ret) ret = m_objects_model->AddSettingsChild(parent_item); + + m_objects_model->UpdateSettingsDigest(ret, categories); + Expand(parent_item); + + return ret; +} + +void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed) { auto model_object = (*m_objects)[obj_idx]; const wxString& item_name = from_u8(model_object->name); @@ -1889,11 +2242,7 @@ void ObjectList::add_object_to_list(size_t obj_idx) !volume->config.has("extruder") ? 0 : volume->config.option("extruder")->value, false); - auto opt_keys = volume->config.keys(); - if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) { - select_item(m_objects_model->AddSettingsChild(vol_item)); - Expand(vol_item); - } + add_settings_item(vol_item, &volume->config); } Expand(item); } @@ -1903,14 +2252,14 @@ void ObjectList::add_object_to_list(size_t obj_idx) increase_object_instances(obj_idx, model_object->instances.size()); // add settings to the object, if it has those - auto opt_keys = model_object->config.keys(); - if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) { - select_item(m_objects_model->AddSettingsChild(item)); - Expand(item); - } + add_settings_item(item, &model_object->config); + + // Add layers if it has + add_layer_root_item(item); #ifndef __WXOSX__ - selection_changed(); + if (call_selection_changed) + selection_changed(); #endif //__WXMSW__ } @@ -1945,6 +2294,8 @@ void ObjectList::delete_from_model_and_list(const ItemType type, const int obj_i if ( !(type&(itObject|itVolume|itInstance)) ) return; + take_snapshot(_(L("Delete Selected Item"))); + if (type&itObject) { del_object(obj_idx); delete_object_from_list(obj_idx); @@ -2053,16 +2404,209 @@ void ObjectList::remove() wxDataViewItemArray sels; GetSelections(sels); + wxDataViewItem parent = wxDataViewItem(0); + + take_snapshot(_(L("Delete Selected"))); + suppress_snapshots(); + for (auto& item : sels) { if (m_objects_model->GetParent(item) == wxDataViewItem(0)) delete_from_model_and_list(itObject, m_objects_model->GetIdByItem(item), -1); else { - if (sels.size() == 1) + if (m_objects_model->GetItemType(item) & itLayer) { + parent = m_objects_model->GetParent(item); + wxDataViewItemArray children; + if (m_objects_model->GetChildren(parent, children) == 1) + parent = m_objects_model->GetTopParent(item); + } + else if (sels.size() == 1) select_item(m_objects_model->GetParent(item)); + del_subobject_item(item); } } + + if (parent) + select_item(parent); + + allow_snapshots(); +} + +void ObjectList::del_layer_range(const t_layer_height_range& range) +{ + const int obj_idx = get_selected_obj_idx(); + if (obj_idx < 0) return; + + t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; + + wxDataViewItem selectable_item = GetSelection(); + + if (ranges.size() == 1) + selectable_item = m_objects_model->GetParent(selectable_item); + + wxDataViewItem layer_item = m_objects_model->GetItemByLayerRange(obj_idx, range); + del_subobject_item(layer_item); + + select_item(selectable_item); +} + +double get_min_layer_height(const int extruder_idx) +{ + const DynamicPrintConfig& config = wxGetApp().preset_bundle->printers.get_edited_preset().config; + return config.opt_float("min_layer_height", extruder_idx <= 0 ? 0 : extruder_idx-1); +} + +double get_max_layer_height(const int extruder_idx) +{ + const DynamicPrintConfig& config = wxGetApp().preset_bundle->printers.get_edited_preset().config; + return config.opt_float("max_layer_height", extruder_idx <= 0 ? 0 : extruder_idx-1); +} + +void ObjectList::add_layer_range_after_current(const t_layer_height_range& current_range) +{ + const int obj_idx = get_selected_obj_idx(); + if (obj_idx < 0) return; + + const wxDataViewItem layers_item = GetSelection(); + + t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; + + const t_layer_height_range& last_range = (--ranges.end())->first; + + if (current_range == last_range) + { + take_snapshot(_(L("Add New Layers Range"))); + + const t_layer_height_range& new_range = { last_range.second, last_range.second + 2.0f }; + ranges[new_range] = get_default_layer_config(obj_idx); + add_layer_item(new_range, layers_item); + } + else + { + const t_layer_height_range& next_range = (++ranges.find(current_range))->first; + + if (current_range.second > next_range.first) + return; // range division has no sense + + const int layer_idx = m_objects_model->GetItemIdByLayerRange(obj_idx, next_range); + if (layer_idx < 0) + return; + + if (current_range.second == next_range.first) + { + const auto old_config = ranges.at(next_range); + + const coordf_t delta = (next_range.second - next_range.first); + if (delta < get_min_layer_height(old_config.opt_int("extruder"))/*0.05f*/) // next range division has no sense + return; + + const coordf_t midl_layer = next_range.first + 0.5f * delta; + + t_layer_height_range new_range = { midl_layer, next_range.second }; + + take_snapshot(_(L("Add New Layers Range"))); + suppress_snapshots(); + + // create new 2 layers instead of deleted one + + // delete old layer + + wxDataViewItem layer_item = m_objects_model->GetItemByLayerRange(obj_idx, next_range); + del_subobject_item(layer_item); + + ranges[new_range] = old_config; + add_layer_item(new_range, layers_item, layer_idx); + + new_range = { current_range.second, midl_layer }; + ranges[new_range] = get_default_layer_config(obj_idx); + add_layer_item(new_range, layers_item, layer_idx); + allow_snapshots(); + } + else + { + take_snapshot(_(L("Add New Layers Range"))); + + const t_layer_height_range new_range = { current_range.second, next_range.first }; + ranges[new_range] = get_default_layer_config(obj_idx); + add_layer_item(new_range, layers_item, layer_idx); + } + } + + changed_object(obj_idx); + + // select item to update layers sizer + select_item(layers_item); +} + +void ObjectList::add_layer_item(const t_layer_height_range& range, + const wxDataViewItem layers_item, + const int layer_idx /* = -1*/) +{ + const int obj_idx = m_objects_model->GetObjectIdByItem(layers_item); + if (obj_idx < 0) return; + + const DynamicPrintConfig& config = object(obj_idx)->layer_config_ranges[range]; + if (!config.has("extruder")) + return; + + const auto layer_item = m_objects_model->AddLayersChild(layers_item, + range, + config.opt_int("extruder"), + layer_idx); + add_settings_item(layer_item, &config); +} + +bool ObjectList::edit_layer_range(const t_layer_height_range& range, coordf_t layer_height) +{ + const int obj_idx = get_selected_obj_idx(); + if (obj_idx < 0) + return false; + + DynamicPrintConfig* config = &object(obj_idx)->layer_config_ranges[range]; + if (fabs(layer_height - config->opt_float("layer_height")) < EPSILON) + return false; + + const int extruder_idx = config->opt_int("extruder"); + + if (layer_height >= get_min_layer_height(extruder_idx) && + layer_height <= get_max_layer_height(extruder_idx)) + { + config->set_key_value("layer_height", new ConfigOptionFloat(layer_height)); + return true; + } + + return false; +} + +bool ObjectList::edit_layer_range(const t_layer_height_range& range, const t_layer_height_range& new_range) +{ + const int obj_idx = get_selected_obj_idx(); + if (obj_idx < 0) return false; + + take_snapshot(_(L("Edit Layers Range"))); + + const ItemType sel_type = m_objects_model->GetItemType(GetSelection()); + + t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; + + const DynamicPrintConfig config = ranges[range]; + + ranges.erase(range); + ranges[new_range] = config; + + wxDataViewItem root_item = m_objects_model->GetLayerRootItem(m_objects_model->GetItemById(obj_idx)); + m_objects_model->DeleteChildren(root_item); + + if (root_item.IsOk()) + // create Layer item(s) according to the layer_config_ranges + for (const auto r : ranges) + add_layer_item(r.first, root_item); + + select_item(sel_type&itLayer ? m_objects_model->GetItemByLayerRange(obj_idx, new_range) : root_item); + Expand(root_item); + + return true; } void ObjectList::init_objects() @@ -2092,11 +2636,12 @@ void ObjectList::update_selections() m_selection_mode = smInstance; // We doesn't update selection if SettingsItem for the current object/part is selected - if (GetSelectedItemsCount() == 1 && m_objects_model->GetItemType(GetSelection()) == itSettings ) +// if (GetSelectedItemsCount() == 1 && m_objects_model->GetItemType(GetSelection()) == itSettings ) + if (GetSelectedItemsCount() == 1 && m_objects_model->GetItemType(GetSelection()) & (itSettings | itLayerRoot | itLayer)) { const auto item = GetSelection(); if (selection.is_single_full_object()) { - if ( m_objects_model->GetIdByItem(m_objects_model->GetParent(item)) == selection.get_object_idx()) + if (m_objects_model->GetObjectIdByItem(item) == selection.get_object_idx()) return; sels.Add(m_objects_model->GetItemById(selection.get_object_idx())); } @@ -2217,22 +2762,18 @@ void ObjectList::update_selections_on_canvas() auto add_to_selection = [this](const wxDataViewItem& item, Selection& selection, int instance_idx, bool as_single_selection) { const ItemType& type = m_objects_model->GetItemType(item); - if ( type == itInstanceRoot || m_objects_model->GetParent(item) == wxDataViewItem(0) ) { - wxDataViewItem obj_item = type == itInstanceRoot ? m_objects_model->GetParent(item) : item; - selection.add_object(m_objects_model->GetIdByItem(obj_item), as_single_selection); - return; - } + const int obj_idx = m_objects_model->GetObjectIdByItem(item); if (type == itVolume) { - const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetParent(item)); const int vol_idx = m_objects_model->GetVolumeIdByItem(item); selection.add_volume(obj_idx, vol_idx, std::max(instance_idx, 0), as_single_selection); } else if (type == itInstance) { - const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); const int inst_idx = m_objects_model->GetInstanceIdByItem(item); selection.add_instance(obj_idx, inst_idx, as_single_selection); } + else + selection.add_object(obj_idx, as_single_selection); }; // stores current instance idx before to clear the selection @@ -2240,7 +2781,7 @@ void ObjectList::update_selections_on_canvas() if (sel_cnt == 1) { wxDataViewItem item = GetSelection(); - if (m_objects_model->GetItemType(item) & (itSettings|itInstanceRoot)) + if (m_objects_model->GetItemType(item) & (itSettings | itInstanceRoot | itLayerRoot | itLayer)) add_to_selection(m_objects_model->GetParent(item), selection, instance_idx, true); else add_to_selection(item, selection, instance_idx, true); @@ -2303,11 +2844,13 @@ void ObjectList::select_item_all_children() } else { const auto item = GetSelection(); - // Some volume(instance) is selected => select all volumes(instances) inside the current object - if (m_objects_model->GetItemType(item) & (itVolume | itInstance)) + const ItemType item_type = m_objects_model->GetItemType(item); + // Some volume/layer/instance is selected => select all volumes/layers/instances inside the current object + if (item_type & (itVolume | itInstance | itLayer)) m_objects_model->GetChildren(m_objects_model->GetParent(item), sels); - m_selection_mode = m_objects_model->GetItemType(item)&itVolume ? smVolume : smInstance; + m_selection_mode = item_type&itVolume ? smVolume : + item_type&itLayer ? smLayer : smInstance; } SetSelections(sels); @@ -2326,8 +2869,9 @@ void ObjectList::update_selection_mode() } const ItemType type = m_objects_model->GetItemType(GetSelection()); - m_selection_mode = type&itSettings ? smUndef : - type&itVolume ? smVolume : smInstance; + m_selection_mode = type & itSettings ? smUndef : + type & itLayer ? smLayer : + type & itVolume ? smVolume : smInstance; } // check last selected item. If is it possible to select it @@ -2338,33 +2882,37 @@ bool ObjectList::check_last_selection(wxString& msg_str) const bool is_shift_pressed = wxGetKeyState(WXK_SHIFT); - /* We can't mix Parts and Objects/Instances. + /* We can't mix Volumes, Layers and Objects/Instances. * So, show information about it */ const ItemType type = m_objects_model->GetItemType(m_last_selected_item); - // check a case of a selection of the Parts from different Objects - bool impossible_multipart_selection = false; - if (type & itVolume && m_selection_mode == smVolume) - { + // check a case of a selection of the same type items from different Objects + auto impossible_multi_selection = [type, this](const ItemType item_type, const SELECTION_MODE selection_mode) { + if (!(type & item_type && m_selection_mode & selection_mode)) + return false; + wxDataViewItemArray sels; GetSelections(sels); - for (const auto& sel: sels) - if (sel != m_last_selected_item && - m_objects_model->GetParent(sel) != m_objects_model->GetParent(m_last_selected_item)) - { - impossible_multipart_selection = true; - break; - } - } + for (const auto& sel : sels) + if (sel != m_last_selected_item && + m_objects_model->GetTopParent(sel) != m_objects_model->GetTopParent(m_last_selected_item)) + return true; - if (impossible_multipart_selection || + return false; + }; + + if (impossible_multi_selection(itVolume, smVolume) || + impossible_multi_selection(itLayer, smLayer ) || type & itSettings || - type & itVolume && m_selection_mode == smInstance || - !(type & itVolume) && m_selection_mode == smVolume) + type & itVolume && !(m_selection_mode & smVolume ) || + type & itLayer && !(m_selection_mode & smLayer ) || + type & itInstance && !(m_selection_mode & smInstance) + ) { // Inform user why selection isn't complited - const wxString item_type = m_selection_mode == smInstance ? _(L("Object or Instance")) : _(L("Part")); + const wxString item_type = m_selection_mode & smInstance ? _(L("Object or Instance")) : + m_selection_mode & smVolume ? _(L("Part")) : _(L("Layer")); msg_str = wxString::Format( _(L("Unsupported selection")) + "\n\n" + _(L("You started your selection with %s Item.")) + "\n" + @@ -2401,7 +2949,7 @@ void ObjectList::fix_multiselection_conflicts() wxDataViewItemArray sels; GetSelections(sels); - if (m_selection_mode == smVolume) + if (m_selection_mode & (smVolume|smLayer)) { // identify correct parent of the initial selected item const wxDataViewItem& parent = m_objects_model->GetParent(m_last_selected_item == sels.front() ? sels.back() : sels.front()); @@ -2410,8 +2958,10 @@ void ObjectList::fix_multiselection_conflicts() wxDataViewItemArray children; // selected volumes from current parent m_objects_model->GetChildren(parent, children); + const ItemType item_type = m_selection_mode & smVolume ? itVolume : itLayer; + for (const auto child : children) - if (IsSelected(child) && m_objects_model->GetItemType(child)&itVolume) + if (IsSelected(child) && m_objects_model->GetItemType(child) & item_type) sels.Add(child); // If some part is selected, unselect all items except of selected parts of the current object @@ -2502,6 +3052,8 @@ void ObjectList::change_part_type() if (new_type == type || new_type == ModelVolumeType::INVALID) return; + take_snapshot(_(L("Paste from Clipboard"))); + const auto item = GetSelection(); volume->set_type(new_type); m_objects_model->SetVolumeType(item, new_type); @@ -2517,18 +3069,17 @@ void ObjectList::change_part_type() } else if (!settings_item && (new_type == ModelVolumeType::MODEL_PART || new_type == ModelVolumeType::PARAMETER_MODIFIER)) { - select_item(m_objects_model->AddSettingsChild(item)); + add_settings_item(item, &volume->config); } } void ObjectList::last_volume_is_deleted(const int obj_idx) { - if (obj_idx < 0 || m_objects->empty() || - obj_idx <= m_objects->size() || - (*m_objects)[obj_idx]->volumes.empty()) + if (obj_idx < 0 || obj_idx >= m_objects->size() || (*m_objects)[obj_idx]->volumes.empty()) return; - auto volume = (*m_objects)[obj_idx]->volumes[0]; + + auto volume = (*m_objects)[obj_idx]->volumes.front(); // clear volume's config values volume->config.clear(); @@ -2550,6 +3101,7 @@ bool ObjectList::has_multi_part_objects() return false; } +/* #lm_FIXME_delete_after_testing void ObjectList::update_settings_items() { m_prevent_canvas_selection_update = true; @@ -2575,6 +3127,117 @@ void ObjectList::update_settings_items() SetSelections(sel); m_prevent_canvas_selection_update = false; } +*/ +void ObjectList::update_and_show_object_settings_item() +{ + const wxDataViewItem item = GetSelection(); + if (!item) return; + + const wxDataViewItem& obj_item = m_objects_model->IsSettingsItem(item) ? m_objects_model->GetParent(item) : item; + select_item(add_settings_item(obj_item, &get_item_config(obj_item))); +} + +// Update settings item for item had it +void ObjectList::update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections) +{ + const wxDataViewItem old_settings_item = m_objects_model->GetSettingsItem(item); + const wxDataViewItem new_settings_item = add_settings_item(item, &get_item_config(item)); + + if (!new_settings_item && old_settings_item) + m_objects_model->Delete(old_settings_item); + + // if ols settings item was is selected area + if (selections.Index(old_settings_item) != wxNOT_FOUND) + { + // If settings item was just updated + if (old_settings_item == new_settings_item) + { + Sidebar& panel = wxGetApp().sidebar(); + panel.Freeze(); + + // update settings list + wxGetApp().obj_settings()->UpdateAndShow(true); + + panel.Layout(); + panel.Thaw(); + } + else + // If settings item was deleted from the list, + // it's need to be deleted from selection array, if it was there + { + selections.Remove(old_settings_item); + + // Select item, if settings_item doesn't exist for item anymore, but was selected + if (selections.Index(item) == wxNOT_FOUND) { + selections.Add(item); + select_item(item); // to correct update of the SettingsList and ManipulationPanel sizers + } + } + } +} + +void ObjectList::update_object_list_by_printer_technology() +{ + m_prevent_canvas_selection_update = true; + wxDataViewItemArray sel; + GetSelections(sel); // stash selection + + wxDataViewItemArray object_items; + m_objects_model->GetChildren(wxDataViewItem(0), object_items); + + for (auto& object_item : object_items) { + // Update Settings Item for object + update_settings_item_and_selection(object_item, sel); + + // Update settings for Volumes + wxDataViewItemArray all_object_subitems; + m_objects_model->GetChildren(object_item, all_object_subitems); + for (auto item : all_object_subitems) + if (m_objects_model->GetItemType(item) & itVolume) + // update settings for volume + update_settings_item_and_selection(item, sel); + + // Update Layers Items + wxDataViewItem layers_item = m_objects_model->GetLayerRootItem(object_item); + if (!layers_item) + layers_item = add_layer_root_item(object_item); + else if (printer_technology() == ptSLA) { + // If layers root item will be deleted from the list, so + // it's need to be deleted from selection array, if it was there + wxDataViewItemArray del_items; + bool some_layers_was_selected = false; + m_objects_model->GetAllChildren(layers_item, del_items); + for (auto& del_item:del_items) + if (sel.Index(del_item) != wxNOT_FOUND) { + some_layers_was_selected = true; + sel.Remove(del_item); + } + if (sel.Index(layers_item) != wxNOT_FOUND) { + some_layers_was_selected = true; + sel.Remove(layers_item); + } + + // delete all "layers" items + m_objects_model->Delete(layers_item); + + // Select object_item, if layers_item doesn't exist for item anymore, but was some of layer items was/were selected + if (some_layers_was_selected) + sel.Add(object_item); + } + else { + wxDataViewItemArray all_obj_layers; + m_objects_model->GetChildren(layers_item, all_obj_layers); + + for (auto item : all_obj_layers) + // update settings for layer + update_settings_item_and_selection(item, sel); + } + } + + // restore selection: + SetSelections(sel); + m_prevent_canvas_selection_update = false; +} void ObjectList::update_object_menu() { @@ -2749,7 +3412,8 @@ void ObjectList::msw_rescale() for (MenuWithSeparators* menu : { &m_menu_object, &m_menu_part, &m_menu_sla_object, - &m_menu_instance }) + &m_menu_instance, + &m_menu_layer }) msw_rescale_menu(menu); Layout(); @@ -2862,5 +3526,42 @@ void ObjectList::set_extruder_for_selected_items(const int extruder) const wxGetApp().plater()->update(); } +void ObjectList::update_after_undo_redo() +{ + m_prevent_list_events = true; + m_prevent_canvas_selection_update = true; + + suppress_snapshots(); + + // Unselect all objects before deleting them, so that no change of selection is emitted during deletion. + this->UnselectAll(); + m_objects_model->DeleteAll(); + + size_t obj_idx = 0; + while (obj_idx < m_objects->size()) { + add_object_to_list(obj_idx, false); + ++obj_idx; + } + + allow_snapshots(); + +#ifndef __WXOSX__ + selection_changed(); +#endif /* __WXOSX__ */ + + update_selections(); + + m_prevent_canvas_selection_update = false; + m_prevent_list_events = false; +} + +ModelObject* ObjectList::object(const int obj_idx) const +{ + if (obj_idx < 0) + return nullptr; + + return (*m_objects)[obj_idx]; +} + } //namespace GUI } //namespace Slic3r \ No newline at end of file diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 166606e2e..2d7e9f5f1 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -26,13 +26,17 @@ enum class ModelVolumeType : int; // FIXME: broken build on mac os because of this is missing: typedef std::vector t_config_option_keys; -typedef std::map> FreqSettingsBundle; +typedef std::map> SettingsBundle; // category -> vector ( option ; label ) typedef std::map< std::string, std::vector< std::pair > > settings_menu_hierarchy; typedef std::vector ModelVolumePtrs; +typedef double coordf_t; +typedef std::pair t_layer_height_range; +typedef std::map t_layer_config_ranges; + namespace GUI { wxDECLARE_EVENT(EVT_OBJ_LIST_OBJECT_SELECT, SimpleEvent); @@ -64,9 +68,10 @@ class ObjectList : public wxDataViewCtrl { enum SELECTION_MODE { - smUndef, - smVolume, - smInstance + smUndef = 0, + smVolume = 1, + smInstance = 2, + smLayer = 4 } m_selection_mode {smUndef}; struct dragged_item_data @@ -119,12 +124,17 @@ class ObjectList : public wxDataViewCtrl MenuWithSeparators m_menu_part; MenuWithSeparators m_menu_sla_object; MenuWithSeparators m_menu_instance; - wxMenuItem* m_menu_item_split { nullptr }; - wxMenuItem* m_menu_item_split_part { nullptr }; + MenuWithSeparators m_menu_layer; wxMenuItem* m_menu_item_settings { nullptr }; wxMenuItem* m_menu_item_split_instances { nullptr }; - std::vector m_bmp_vector; + ObjectDataViewModel *m_objects_model{ nullptr }; + DynamicPrintConfig *m_config {nullptr}; + std::vector *m_objects{ nullptr }; + + std::vector m_bmp_vector; + + t_layer_config_ranges m_layer_config_ranges_cache; int m_selected_object_id = -1; bool m_prevent_list_events = false; // We use this flag to avoid circular event handling Select() @@ -142,8 +152,8 @@ class ObjectList : public wxDataViewCtrl wxDataViewItem m_last_selected_item {nullptr}; #if 0 - FreqSettingsBundle m_freq_settings_fff; - FreqSettingsBundle m_freq_settings_sla; + SettingsBundle m_freq_settings_fff; + SettingsBundle m_freq_settings_sla; #endif public: @@ -153,11 +163,11 @@ public: std::map CATEGORY_ICON; - ObjectDataViewModel *m_objects_model{ nullptr }; - DynamicPrintConfig *m_config {nullptr}; - - std::vector *m_objects{ nullptr }; + ObjectDataViewModel* GetModel() const { return m_objects_model; } + DynamicPrintConfig* config() const { return m_config; } + std::vector* objects() const { return m_objects; } + ModelObject* object(const int obj_idx) const ; void create_objects_ctrl(); void create_popup_menus(); @@ -192,13 +202,19 @@ public: void key_event(wxKeyEvent& event); #endif /* __WXOSX__ */ + void copy(); + void paste(); + void undo(); + void redo(); + void get_settings_choice(const wxString& category_name); void get_freq_settings_choice(const wxString& bundle_name); - void update_settings_item(); + void show_settings(const wxDataViewItem settings_item); wxMenu* append_submenu_add_generic(wxMenu* menu, const ModelVolumeType type); void append_menu_items_add_volume(wxMenu* menu); wxMenuItem* append_menu_item_split(wxMenu* menu); + wxMenuItem* append_menu_item_layers_editing(wxMenu* menu); wxMenuItem* append_menu_item_settings(wxMenu* menu); wxMenuItem* append_menu_item_change_type(wxMenu* menu); wxMenuItem* append_menu_item_instance_to_object(wxMenu* menu, wxWindow* parent); @@ -222,10 +238,18 @@ public: void load_generic_subobject(const std::string& type_name, const ModelVolumeType type); void del_object(const int obj_idx); void del_subobject_item(wxDataViewItem& item); - void del_settings_from_config(); + void del_settings_from_config(const wxDataViewItem& parent_item); void del_instances_from_object(const int obj_idx); + void del_layer_from_object(const int obj_idx, const t_layer_height_range& layer_range); + void del_layers_from_object(const int obj_idx); bool del_subobject_from_object(const int obj_idx, const int idx, const int type); void split(); + void layers_editing(); + + wxDataViewItem add_layer_root_item(const wxDataViewItem obj_item); + wxDataViewItem add_settings_item(wxDataViewItem parent_item, const DynamicPrintConfig* config); + + DynamicPrintConfig get_default_layer_config(const int obj_idx); bool get_volume_by_item(const wxDataViewItem& item, ModelVolume*& volume); bool is_splittable(); bool selected_instances_of_same_object(); @@ -235,12 +259,13 @@ public: wxBoxSizer* get_sizer() {return m_sizer;} int get_selected_obj_idx() const; DynamicPrintConfig& get_item_config(const wxDataViewItem& item) const; + SettingsBundle get_item_settings_bundle(const DynamicPrintConfig* config, const bool is_layers_range_settings); void changed_object(const int obj_idx = -1) const; void part_selection_changed(); // Add object to the list - void add_object_to_list(size_t obj_idx); + void add_object_to_list(size_t obj_idx, bool call_selection_changed = true); // Delete object from the list void delete_object_from_list(); void delete_object_from_list(const size_t obj_idx); @@ -265,6 +290,14 @@ public: // Remove objects/sub-object from the list void remove(); + void del_layer_range(const t_layer_height_range& range); + void add_layer_range_after_current(const t_layer_height_range& current_range); + void add_layer_item (const t_layer_height_range& range, + const wxDataViewItem layers_item, + const int layer_idx = -1); + bool edit_layer_range(const t_layer_height_range& range, coordf_t layer_height); + bool edit_layer_range(const t_layer_height_range& range, + const t_layer_height_range& new_range); void init_objects(); bool multiple_selection() const ; @@ -286,6 +319,9 @@ public: void last_volume_is_deleted(const int obj_idx); bool has_multi_part_objects(); void update_settings_items(); + void update_and_show_object_settings_item(); + void update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections); + void update_object_list_by_printer_technology(); void update_object_menu(); void instances_to_separated_object(const int obj_idx, const std::set& inst_idx); @@ -295,11 +331,15 @@ public: void fix_through_netfabb(); void update_item_error_icon(const int obj_idx, int vol_idx) const ; + void fill_layer_config_ranges_cache(); + void paste_layers_into_list(); void paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& volumes); void paste_objects_into_list(const std::vector& object_idxs); void msw_rescale(); + void update_after_undo_redo(); + private: #ifdef __WXOSX__ // void OnChar(wxKeyEvent& event); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 372cd79ef..fcb047139 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -17,6 +17,28 @@ namespace Slic3r namespace GUI { + +// Helper function to be used by drop to bed button. Returns lowest point of this +// volume in world coordinate system. +static double get_volume_min_z(const GLVolume* volume) +{ + const Transform3f& world_matrix = volume->world_matrix().cast(); + + // need to get the ModelVolume pointer + const ModelObject* mo = wxGetApp().model_objects()->at(volume->composite_id.object_id); + const ModelVolume* mv = mo->volumes[volume->composite_id.volume_id]; + const TriangleMesh& hull = mv->get_convex_hull(); + + float min_z = std::numeric_limits::max(); + for (const stl_facet& facet : hull.stl.facet_start) { + for (int i = 0; i < 3; ++ i) + min_z = std::min(min_z, Vec3f::UnitZ().dot(world_matrix * facet.vertex[i])); + } + return min_z; +} + + + static wxBitmapComboBox* create_word_local_combo(wxWindow *parent) { wxSize size(15 * wxGetApp().em_unit(), -1); @@ -185,7 +207,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : unsigned int axis_idx = (axis[0] - 'x'); // 0, 1 or 2 // We will add a button to toggle mirroring to each axis: - auto mirror_button = [=](wxWindow* parent) { + auto mirror_button = [this, mirror_btn_width, axis_idx, &label](wxWindow* parent) { wxSize btn_size(em_unit(parent) * mirror_btn_width, em_unit(parent) * mirror_btn_width); auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_off.png", wxEmptyString, btn_size, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW); btn->SetToolTip(wxString::Format(_(L("Toggle %s axis mirroring")), label)); @@ -195,7 +217,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn); - btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { + btn->Bind(wxEVT_BUTTON, [this, axis_idx](wxCommandEvent &e) { Axis axis = (Axis)(axis_idx + X); if (m_mirror_buttons[axis_idx].second == mbHidden) return; @@ -220,8 +242,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL); selection.synchronize_unselected_volumes(); // Copy mirroring values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing. - canvas->do_mirror(); - canvas->set_as_dirty(); + canvas->do_mirror(L("Set Mirror")); UpdateAndShow(true); }); return sizer; @@ -258,13 +279,13 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : return btn; }; // Add reset scale button - auto reset_scale_button = [=](wxWindow* parent) { + auto reset_scale_button = [this](wxWindow* parent) { auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo")); btn->SetToolTip(_(L("Reset scale"))); m_reset_scale_button = btn; auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn, wxBU_EXACTFIT); - btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { + btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { change_scale_value(0, 100.); change_scale_value(1, 100.); change_scale_value(2, 100.); @@ -275,13 +296,13 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : } else if (option_name == "Rotation") { // Add reset rotation button - auto reset_rotation_button = [=](wxWindow* parent) { + auto reset_rotation_button = [this](wxWindow* parent) { auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo")); btn->SetToolTip(_(L("Reset rotation"))); m_reset_rotation_button = btn; auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn, wxBU_EXACTFIT); - btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { + btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); Selection& selection = canvas->get_selection(); @@ -302,7 +323,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL); selection.synchronize_unselected_volumes(); // Copy rotation values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing. - canvas->do_rotate(); + canvas->do_rotate(L("Set Rotation")); UpdateAndShow(true); }); @@ -310,6 +331,34 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : }; line.append_widget(reset_rotation_button); } + else if (option_name == "Position") { + // Add drop to bed button + auto drop_to_bed_button = [=](wxWindow* parent) { + auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "drop_to_bed.png")); + btn->SetToolTip(_(L("Drop to bed"))); + m_drop_to_bed_button = btn; + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(btn, wxBU_EXACTFIT); + btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { + // ??? + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + Selection& selection = canvas->get_selection(); + + if (selection.is_single_volume() || selection.is_single_modifier()) { + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + + const Geometry::Transformation& instance_trafo = volume->get_instance_transformation(); + Vec3d diff = m_cache.position - instance_trafo.get_matrix(true).inverse() * Vec3d(0., 0., get_volume_min_z(volume)); + + change_position_value(0, diff.x()); + change_position_value(1, diff.y()); + change_position_value(2, diff.z()); + } + }); + return sizer; + }; + line.append_widget(drop_to_bed_button); + } // Add empty bmp (Its size have to be equal to PrusaLockButton) in front of "Size" option to label alignment else if (option_name == "Size") { line.near_label_widget = [this](wxWindow* parent) { @@ -441,7 +490,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_position = volume->get_volume_offset(); m_new_rotation = volume->get_volume_rotation() * (180. / M_PI); m_new_scale = volume->get_volume_scaling_factor() * 100.; - m_new_size = volume->get_volume_transformation().get_scaling_factor().cwiseProduct(volume->bounding_box.size()); + m_new_size = volume->get_volume_transformation().get_scaling_factor().cwiseProduct(volume->bounding_box().size()); m_new_enabled = true; } else if (obj_list->multiple_selection() || obj_list->is_selected(itInstanceRoot)) @@ -536,11 +585,13 @@ void ObjectManipulation::update_reset_buttons_visibility() bool show_rotation = false; bool show_scale = false; + bool show_drop_to_bed = false; if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); Vec3d rotation; Vec3d scale; + double min_z = 0.; if (selection.is_single_full_instance()) { rotation = volume->get_instance_rotation(); @@ -549,14 +600,17 @@ void ObjectManipulation::update_reset_buttons_visibility() else { rotation = volume->get_volume_rotation(); scale = volume->get_volume_scaling_factor(); + min_z = get_volume_min_z(volume); } show_rotation = !rotation.isApprox(Vec3d::Zero()); show_scale = !scale.isApprox(Vec3d::Ones()); + show_drop_to_bed = (std::abs(min_z) > EPSILON); } - wxGetApp().CallAfter([this, show_rotation, show_scale]{ + wxGetApp().CallAfter([this, show_rotation, show_scale, show_drop_to_bed]{ m_reset_rotation_button->Show(show_rotation); m_reset_scale_button->Show(show_scale); + m_drop_to_bed_button->Show(show_drop_to_bed); }); } @@ -655,7 +709,7 @@ void ObjectManipulation::change_position_value(int axis, double value) Selection& selection = canvas->get_selection(); selection.start_dragging(); selection.translate(position - m_cache.position, selection.requires_local_axes()); - canvas->do_move(); + canvas->do_move(L("Set Position")); m_cache.position = position; m_cache.position_rounded(axis) = DBL_MAX; @@ -686,7 +740,7 @@ void ObjectManipulation::change_rotation_value(int axis, double value) selection.rotate( (M_PI / 180.0) * (transformation_type.absolute() ? rotation : rotation - m_cache.rotation), transformation_type); - canvas->do_rotate(); + canvas->do_rotate(L("Set Orientation")); m_cache.rotation = rotation; m_cache.rotation_rounded(axis) = DBL_MAX; @@ -721,8 +775,8 @@ void ObjectManipulation::change_size_value(int axis, double value) Vec3d ref_size = m_cache.size; if (selection.is_single_volume() || selection.is_single_modifier()) - ref_size = selection.get_volume(*selection.get_volume_idxs().begin())->bounding_box.size(); - else if (selection.is_single_full_instance()) + ref_size = selection.get_volume(*selection.get_volume_idxs().begin())->bounding_box().size(); + else if (selection.is_single_full_instance()) ref_size = m_world_coordinates ? selection.get_unscaled_instance_bounding_box().size() : (*wxGetApp().model_objects())[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size(); @@ -751,7 +805,7 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const selection.start_dragging(); selection.scale(scaling_factor * 0.01, transformation_type); - wxGetApp().plater()->canvas3D()->do_scale(); + wxGetApp().plater()->canvas3D()->do_scale(L("Set Scale")); } void ObjectManipulation::on_change(t_config_option_key opt_key, const boost::any& value) @@ -869,6 +923,7 @@ void ObjectManipulation::msw_rescale() m_mirror_bitmap_hidden.msw_rescale(); m_reset_scale_button->msw_rescale(); m_reset_rotation_button->msw_rescale(); + m_drop_to_bed_button->msw_rescale(); get_og()->msw_rescale(); } diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index cc2154514..e4e190b5b 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -56,6 +56,7 @@ class ObjectManipulation : public OG_Settings // Non-owning pointers to the reset buttons, so we can hide and show them. ScalableButton* m_reset_scale_button = nullptr; ScalableButton* m_reset_rotation_button = nullptr; + ScalableButton* m_drop_to_bed_button = nullptr; // Mirroring buttons and their current state enum MirrorButtonState { diff --git a/src/slic3r/GUI/GUI_ObjectSettings.cpp b/src/slic3r/GUI/GUI_ObjectSettings.cpp index 4107e872a..16c64360a 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.cpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.cpp @@ -63,18 +63,28 @@ ObjectSettings::ObjectSettings(wxWindow* parent) : m_bmp_delete = ScalableBitmap(parent, "cross"); } -void ObjectSettings::update_settings_list() +bool ObjectSettings::update_settings_list() { m_settings_list_sizer->Clear(true); + m_og_settings.resize(0); auto objects_ctrl = wxGetApp().obj_list(); - auto objects_model = wxGetApp().obj_list()->m_objects_model; - auto config = wxGetApp().obj_list()->m_config; + auto objects_model = wxGetApp().obj_list()->GetModel(); + auto config = wxGetApp().obj_list()->config(); const auto item = objects_ctrl->GetSelection(); - if (item && !objects_ctrl->multiple_selection() && - config && objects_model->IsSettingsItem(item)) - { + + if (!item || !objects_model->IsSettingsItem(item) || !config || objects_ctrl->multiple_selection()) + return false; + + const bool is_layers_range_settings = objects_model->GetItemType(objects_model->GetParent(item)) == itLayer; + SettingsBundle cat_options = objects_ctrl->get_item_settings_bundle(config, is_layers_range_settings); + + if (!cat_options.empty()) + { + std::vector categories; + categories.reserve(cat_options.size()); + auto extra_column = [config, this](wxWindow* parent, const Line& line) { auto opt_key = (line.get_options())[0].opt_id; //we assume that we have one option per line @@ -94,85 +104,61 @@ void ObjectSettings::update_settings_list() return btn; }; - std::map> cat_options; - auto opt_keys = config->keys(); - objects_ctrl->update_opt_keys(opt_keys); // update options list according to print technology - - m_og_settings.resize(0); - std::vector categories; - if (!(opt_keys.size() == 1 && opt_keys[0] == "extruder"))// return; + for (auto& cat : cat_options) { - const int extruders_cnt = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA ? 1 : - wxGetApp().preset_bundle->printers.get_edited_preset().config.option("nozzle_diameter")->values.size(); + if (cat.second.size() == 1 && + (cat.second[0] == "extruder" || is_layers_range_settings && cat.second[0] == "layer_height")) + continue; - for (auto& opt_key : opt_keys) { - auto category = config->def()->get(opt_key)->category; - if (category.empty() || - (category == "Extruders" && extruders_cnt == 1)) continue; + categories.push_back(cat.first); - std::vector< std::string > new_category; + auto optgroup = std::make_shared(m_og->ctrl_parent(), _(cat.first), config, false, extra_column); + optgroup->label_width = 15; + optgroup->sidetext_width = 5.5; - auto& cat_opt = cat_options.find(category) == cat_options.end() ? new_category : cat_options.at(category); - cat_opt.push_back(opt_key); - if (cat_opt.size() == 1) - cat_options[category] = cat_opt; - } + optgroup->m_on_change = [](const t_config_option_key& opt_id, const boost::any& value) { + wxGetApp().obj_list()->changed_object(); }; - for (auto& cat : cat_options) { - if (cat.second.size() == 1 && cat.second[0] == "extruder") + // call back for rescaling of the extracolumn control + optgroup->rescale_extra_column_item = [this](wxWindow* win) { + auto *ctrl = dynamic_cast(win); + if (ctrl == nullptr) + return; + ctrl->SetBitmap_(m_bmp_delete); + }; + + const bool is_extruders_cat = cat.first == "Extruders"; + for (auto& opt : cat.second) + { + if (opt == "extruder" || is_layers_range_settings && opt == "layer_height") continue; - - auto optgroup = std::make_shared(m_og->ctrl_parent(), _(cat.first), config, false, extra_column); - optgroup->label_width = 15; - optgroup->sidetext_width = 5.5; - - optgroup->m_on_change = [](const t_config_option_key& opt_id, const boost::any& value) { - wxGetApp().obj_list()->changed_object(); }; - - const bool is_extriders_cat = cat.first == "Extruders"; - for (auto& opt : cat.second) - { - if (opt == "extruder") - continue; - Option option = optgroup->get_option(opt); - option.opt.width = 12; - if (is_extriders_cat) - option.opt.max = wxGetApp().extruders_cnt(); - optgroup->append_single_option_line(option); - } - optgroup->reload_config(); - m_settings_list_sizer->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 0); - - // call back for rescaling of the extracolumn control - optgroup->rescale_extra_column_item = [this](wxWindow* win) { - auto *ctrl = dynamic_cast(win); - if (ctrl == nullptr) - return; - ctrl->SetBitmap_(m_bmp_delete); - }; - - m_og_settings.push_back(optgroup); - - categories.push_back(cat.first); + Option option = optgroup->get_option(opt); + option.opt.width = 12; + if (is_extruders_cat) + option.opt.max = wxGetApp().extruders_edited_cnt(); + optgroup->append_single_option_line(option); } + optgroup->reload_config(); + + m_settings_list_sizer->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 0); + m_og_settings.push_back(optgroup); } - if (m_og_settings.empty()) { - objects_ctrl->select_item(objects_model->Delete(item)); - } - else { - if (!categories.empty()) - objects_model->UpdateSettingsDigest(item, categories); - } - } + if (!categories.empty()) + objects_model->UpdateSettingsDigest(item, categories); + } + else + { + objects_ctrl->select_item(objects_model->Delete(item)); + return false; + } + + return true; } void ObjectSettings::UpdateAndShow(const bool show) { - if (show) - update_settings_list(); - - OG_Settings::UpdateAndShow(show); + OG_Settings::UpdateAndShow(show ? update_settings_list() : false); } void ObjectSettings::msw_rescale() diff --git a/src/slic3r/GUI/GUI_ObjectSettings.hpp b/src/slic3r/GUI/GUI_ObjectSettings.hpp index 3d49f13b7..01daa5622 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.hpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.hpp @@ -44,7 +44,7 @@ public: ObjectSettings(wxWindow* parent); ~ObjectSettings() {} - void update_settings_list(); + bool update_settings_list(); void UpdateAndShow(const bool show) override; void msw_rescale(); }; diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 671c49eef..0ba447c1b 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -541,6 +541,26 @@ void Preview::on_checkbox_shells(wxCommandEvent& evt) refresh_print(); } +void Preview::update_view_type() +{ + const DynamicPrintConfig& config = wxGetApp().preset_bundle->project_config; + + const wxString& choice = !config.option("colorprint_heights")->values.empty() && + wxGetApp().extruders_edited_cnt()==1 ? + _(L("Color Print")) : + config.option("wiping_volumes_matrix")->values.size() > 1 ? + _(L("Tool")) : + _(L("Feature type")); + + int type = m_choice_view_type->FindString(choice); + if (m_choice_view_type->GetSelection() != type) { + m_choice_view_type->SetSelection(type); + if (0 <= type && type < (int)GCodePreviewData::Extrusion::Num_View_Types) + m_gcode_preview_data->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)type; + m_preferred_color_mode = "feature"; + } +} + void Preview::create_double_slider() { m_slider = new DoubleSlider(this, wxID_ANY, 0, 0, 0, 100); @@ -553,23 +573,13 @@ void Preview::create_double_slider() Bind(wxCUSTOMEVT_TICKSCHANGED, [this](wxEvent&) { - auto& config = wxGetApp().preset_bundle->project_config; - ((config.option("colorprint_heights"))->values) = (m_slider->GetTicksValues()); - m_schedule_background_process(); + wxGetApp().preset_bundle->project_config.option("colorprint_heights")->values = m_slider->GetTicksValues(); + m_schedule_background_process(); - const wxString& choise = !config.option("colorprint_heights")->values.empty() ? _(L("Color Print")) : - config.option("wiping_volumes_matrix")->values.size() > 1 ? - _(L("Tool")) : _(L("Feature type")); + update_view_type(); - int type = m_choice_view_type->FindString(choise); - if (m_choice_view_type->GetSelection() != type) { - m_choice_view_type->SetSelection(type); - if ((0 <= type) && (type < (int)GCodePreviewData::Extrusion::Num_View_Types)) - m_gcode_preview_data->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)type; - m_preferred_color_mode = "feature"; - } - reload_print(); - }); + reload_print(); + }); } // Find an index of a value in a sorted vector, which is in . @@ -787,9 +797,14 @@ void Preview::load_print_as_fff(bool keep_z_range) // Load the real G-code preview. m_canvas->load_gcode_preview(*m_gcode_preview_data, colors); m_loaded = true; - } else + } else { + // disable color change information for multi-material presets + if (wxGetApp().extruders_edited_cnt() > 1) + color_print_values.clear(); + // Load the initial preview based on slices, not the final G-code. m_canvas->load_preview(colors, color_print_values); + } show_hide_ui_elements(gcode_preview_data_valid ? "full" : "simple"); // recalculates zs and update sliders accordingly std::vector zs = m_canvas->get_current_print_zs(true); diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index 993e260e4..e86d0e430 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -127,6 +127,8 @@ public: void move_double_slider(wxKeyEvent& evt); void edit_double_slider(wxKeyEvent& evt); + void update_view_type(); + private: bool init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp index 030bd0146..4b975e87e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp @@ -132,18 +132,13 @@ void GLGizmoBase::Grabber::render_face(float half_size) const glsafe(::glEnd()); } -#if ENABLE_SVG_ICONS + GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) -#else -GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, unsigned int sprite_id) -#endif // ENABLE_SVG_ICONS : m_parent(parent) , m_group_id(-1) , m_state(Off) , m_shortcut_key(0) -#if ENABLE_SVG_ICONS , m_icon_filename(icon_filename) -#endif // ENABLE_SVG_ICONS , m_sprite_id(sprite_id) , m_hover_id(-1) , m_dragging(false) @@ -185,7 +180,7 @@ void GLGizmoBase::disable_grabber(unsigned int id) on_disable_grabber(id); } -void GLGizmoBase::start_dragging(const Selection& selection) +void GLGizmoBase::start_dragging() { m_dragging = true; @@ -194,7 +189,7 @@ void GLGizmoBase::start_dragging(const Selection& selection) m_grabbers[i].dragging = (m_hover_id == i); } - on_start_dragging(selection); + on_start_dragging(); } void GLGizmoBase::stop_dragging() @@ -209,10 +204,10 @@ void GLGizmoBase::stop_dragging() on_stop_dragging(); } -void GLGizmoBase::update(const UpdateData& data, const Selection& selection) +void GLGizmoBase::update(const UpdateData& data) { if (m_hover_id != -1) - on_update(data, selection); + on_update(data); } std::array GLGizmoBase::picking_color_component(unsigned int id) const diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp index a29aaed3f..b84442b94 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp @@ -6,6 +6,7 @@ #include "slic3r/GUI/I18N.hpp" #include "slic3r/GUI/Selection.hpp" +#include class wxWindow; class GLUquadric; @@ -75,10 +76,10 @@ public: struct UpdateData { - const Linef3 mouse_ray; - const Point* mouse_pos; + const Linef3& mouse_ray; + const Point& mouse_pos; - UpdateData(const Linef3& mouse_ray, const Point* mouse_pos = nullptr) + UpdateData(const Linef3& mouse_ray, const Point& mouse_pos) : mouse_ray(mouse_ray), mouse_pos(mouse_pos) {} }; @@ -89,9 +90,7 @@ protected: int m_group_id; EState m_state; int m_shortcut_key; -#if ENABLE_SVG_ICONS std::string m_icon_filename; -#endif // ENABLE_SVG_ICONS unsigned int m_sprite_id; int m_hover_id; bool m_dragging; @@ -102,15 +101,14 @@ protected: ImGuiWrapper* m_imgui; public: -#if ENABLE_SVG_ICONS GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); -#else - GLGizmoBase(GLCanvas3D& parent, unsigned int sprite_id); -#endif // ENABLE_SVG_ICONS virtual ~GLGizmoBase() {} bool init() { return on_init(); } + void load(cereal::BinaryInputArchive& ar) { m_state = On; on_load(ar); } + void save(cereal::BinaryOutputArchive& ar) const { on_save(ar); } + std::string get_name() const { return on_get_name(); } int get_group_id() const { return m_group_id; } @@ -122,11 +120,9 @@ public: int get_shortcut_key() const { return m_shortcut_key; } void set_shortcut_key(int key) { m_shortcut_key = key; } -#if ENABLE_SVG_ICONS const std::string& get_icon_filename() const { return m_icon_filename; } -#endif // ENABLE_SVG_ICONS - bool is_activable(const Selection& selection) const { return on_is_activable(selection); } + bool is_activable() const { return on_is_activable(); } bool is_selectable() const { return on_is_selectable(); } unsigned int get_sprite_id() const { return m_sprite_id; } @@ -139,32 +135,34 @@ public: void enable_grabber(unsigned int id); void disable_grabber(unsigned int id); - void start_dragging(const Selection& selection); + void start_dragging(); void stop_dragging(); bool is_dragging() const { return m_dragging; } - void update(const UpdateData& data, const Selection& selection); + void update(const UpdateData& data); - void render(const Selection& selection) const { on_render(selection); } - void render_for_picking(const Selection& selection) const { on_render_for_picking(selection); } - void render_input_window(float x, float y, float bottom_limit, const Selection& selection) { on_render_input_window(x, y, bottom_limit, selection); } + void render() const { on_render(); } + void render_for_picking() const { on_render_for_picking(); } + void render_input_window(float x, float y, float bottom_limit) { on_render_input_window(x, y, bottom_limit); } protected: virtual bool on_init() = 0; + virtual void on_load(cereal::BinaryInputArchive& ar) {} + virtual void on_save(cereal::BinaryOutputArchive& ar) const {} virtual std::string on_get_name() const = 0; virtual void on_set_state() {} virtual void on_set_hover_id() {} - virtual bool on_is_activable(const Selection& selection) const { return true; } + virtual bool on_is_activable() const { return true; } virtual bool on_is_selectable() const { return true; } virtual void on_enable_grabber(unsigned int id) {} virtual void on_disable_grabber(unsigned int id) {} - virtual void on_start_dragging(const Selection& selection) {} + virtual void on_start_dragging() {} virtual void on_stop_dragging() {} - virtual void on_update(const UpdateData& data, const Selection& selection) = 0; - virtual void on_render(const Selection& selection) const = 0; - virtual void on_render_for_picking(const Selection& selection) const = 0; - virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) {} + virtual void on_update(const UpdateData& data) {} + virtual void on_render() const = 0; + virtual void on_render_for_picking() const = 0; + virtual void on_render_input_window(float x, float y, float bottom_limit) {} // Returns the picking color for the given id, based on the BASE_ID constant // No check is made for clashing with other picking color (i.e. GLVolumes) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 8934bc52b..39399fc0d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -15,59 +15,12 @@ namespace Slic3r { namespace GUI { - -class GLGizmoCutPanel : public wxPanel -{ -public: - GLGizmoCutPanel(wxWindow *parent); - - void display(bool display); -private: - bool m_active; - wxCheckBox *m_cb_rotate; - wxButton *m_btn_cut; - wxButton *m_btn_cancel; -}; - -GLGizmoCutPanel::GLGizmoCutPanel(wxWindow *parent) - : wxPanel(parent) - , m_active(false) - , m_cb_rotate(new wxCheckBox(this, wxID_ANY, _(L("Rotate lower part upwards")))) - , m_btn_cut(new wxButton(this, wxID_OK, _(L("Perform cut")))) - , m_btn_cancel(new wxButton(this, wxID_CANCEL, _(L("Cancel")))) -{ - enum { MARGIN = 5 }; - - auto *sizer = new wxBoxSizer(wxHORIZONTAL); - - auto *label = new wxStaticText(this, wxID_ANY, _(L("Cut object:"))); - sizer->Add(label, 0, wxALL | wxALIGN_CENTER, MARGIN); - sizer->Add(m_cb_rotate, 0, wxALL | wxALIGN_CENTER, MARGIN); - sizer->AddStretchSpacer(); - sizer->Add(m_btn_cut, 0, wxALL | wxALIGN_CENTER, MARGIN); - sizer->Add(m_btn_cancel, 0, wxALL | wxALIGN_CENTER, MARGIN); - - SetSizer(sizer); -} - -void GLGizmoCutPanel::display(bool display) -{ - Show(display); - GetParent()->Layout(); -} - - const double GLGizmoCut::Offset = 10.0; const double GLGizmoCut::Margin = 20.0; const std::array GLGizmoCut::GrabberColor = { 1.0, 0.5, 0.0 }; -#if ENABLE_SVG_ICONS GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) -#else -GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, unsigned int sprite_id) - : GLGizmoBase(parent, sprite_id) -#endif // ENABLE_SVG_ICONS , m_cut_z(0.0) , m_max_z(0.0) , m_keep_upper(true) @@ -75,7 +28,6 @@ GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, unsigned int sprite_id) , m_rotate_lower(false) {} - bool GLGizmoCut::on_init() { m_grabbers.emplace_back(); @@ -96,15 +48,18 @@ void GLGizmoCut::on_set_state() } } -bool GLGizmoCut::on_is_activable(const Selection& selection) const +bool GLGizmoCut::on_is_activable() const { + const Selection& selection = m_parent.get_selection(); return selection.is_single_full_instance() && !selection.is_wipe_tower(); } -void GLGizmoCut::on_start_dragging(const Selection& selection) +void GLGizmoCut::on_start_dragging() { - if (m_hover_id == -1) { return; } + if (m_hover_id == -1) + return; + const Selection& selection = m_parent.get_selection(); const BoundingBoxf3& box = selection.get_bounding_box(); m_start_z = m_cut_z; update_max_z(selection); @@ -113,19 +68,21 @@ void GLGizmoCut::on_start_dragging(const Selection& selection) m_drag_center(2) = m_cut_z; } -void GLGizmoCut::on_update(const UpdateData& data, const Selection& selection) +void GLGizmoCut::on_update(const UpdateData& data) { if (m_hover_id != -1) { set_cut_z(m_start_z + calc_projection(data.mouse_ray)); } } -void GLGizmoCut::on_render(const Selection& selection) const +void GLGizmoCut::on_render() const { if (m_grabbers[0].dragging) { set_tooltip("Z: " + format(m_cut_z, 2)); } + const Selection& selection = m_parent.get_selection(); + update_max_z(selection); const BoundingBoxf3& box = selection.get_bounding_box(); @@ -171,14 +128,13 @@ void GLGizmoCut::on_render(const Selection& selection) const m_grabbers[0].render(m_hover_id == 0, (float)((box.size()(0) + box.size()(1) + box.size()(2)) / 3.0)); } -void GLGizmoCut::on_render_for_picking(const Selection& selection) const +void GLGizmoCut::on_render_for_picking() const { glsafe(::glDisable(GL_DEPTH_TEST)); - - render_grabbers_for_picking(selection.get_bounding_box()); + render_grabbers_for_picking(m_parent.get_selection().get_bounding_box()); } -void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) +void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit) { const float approx_height = m_imgui->scaled(11.0f); y = std::min(y, bottom_limit - approx_height); @@ -188,7 +144,7 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit, co m_imgui->begin(_(L("Cut")), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); ImGui::PushItemWidth(m_imgui->scaled(5.0f)); - bool _value_changed = ImGui::InputDouble("Z", &m_cut_z, 0.0f, 0.0f, "%.2f"); + ImGui::InputDouble("Z", &m_cut_z, 0.0f, 0.0f, "%.2f"); m_imgui->checkbox(_(L("Keep upper part")), m_keep_upper); m_imgui->checkbox(_(L("Keep lower part")), m_keep_lower); @@ -201,7 +157,7 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit, co m_imgui->end(); if (cut_clicked && (m_keep_upper || m_keep_lower)) { - perform_cut(selection); + perform_cut(m_parent.get_selection()); } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index fd4e8d8dc..5bfeda526 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -23,22 +23,20 @@ class GLGizmoCut : public GLGizmoBase bool m_rotate_lower; public: -#if ENABLE_SVG_ICONS GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); -#else - GLGizmoCut(GLCanvas3D& parent, unsigned int sprite_id); -#endif // ENABLE_SVG_ICONS protected: virtual bool on_init(); + virtual void on_load(cereal::BinaryInputArchive& ar) { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); } + virtual void on_save(cereal::BinaryOutputArchive& ar) const { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); } virtual std::string on_get_name() const; virtual void on_set_state(); - virtual bool on_is_activable(const Selection& selection) const; - virtual void on_start_dragging(const Selection& selection); - virtual void on_update(const UpdateData& data, const Selection& selection); - virtual void on_render(const Selection& selection) const; - virtual void on_render_for_picking(const Selection& selection) const; - virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection); + virtual bool on_is_activable() const; + virtual void on_start_dragging(); + virtual void on_update(const UpdateData& data); + virtual void on_render() const; + virtual void on_render_for_picking() const; + virtual void on_render_input_window(float x, float y, float bottom_limit); private: void update_max_z(const Selection& selection) const; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp index ec991a241..cb996a104 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp @@ -1,5 +1,7 @@ // Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. #include "GLGizmoFlatten.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/GUI_App.hpp" #include @@ -9,13 +11,8 @@ namespace Slic3r { namespace GUI { -#if ENABLE_SVG_ICONS GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) -#else -GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent, unsigned int sprite_id) - : GLGizmoBase(parent, sprite_id) -#endif // ENABLE_SVG_ICONS , m_normal(Vec3d::Zero()) , m_starting_center(Vec3d::Zero()) { @@ -27,28 +24,46 @@ bool GLGizmoFlatten::on_init() return true; } +void GLGizmoFlatten::on_set_state() +{ + // m_model_object pointer can be invalid (for instance because of undo/redo action), + // we should recover it from the object id + m_model_object = nullptr; + for (const auto mo : *wxGetApp().model_objects()) { + if (mo->id() == m_model_object_id) { + m_model_object = mo; + break; + } + } + + if (m_state == On && is_plane_update_necessary()) + update_planes(); +} + std::string GLGizmoFlatten::on_get_name() const { return (_(L("Place on face")) + " [F]").ToUTF8().data(); } -bool GLGizmoFlatten::on_is_activable(const Selection& selection) const +bool GLGizmoFlatten::on_is_activable() const { - return selection.is_single_full_instance(); + return m_parent.get_selection().is_single_full_instance(); } -void GLGizmoFlatten::on_start_dragging(const Selection& selection) +void GLGizmoFlatten::on_start_dragging() { if (m_hover_id != -1) { assert(m_planes_valid); m_normal = m_planes[m_hover_id].normal; - m_starting_center = selection.get_bounding_box().center(); + m_starting_center = m_parent.get_selection().get_bounding_box().center(); } } -void GLGizmoFlatten::on_render(const Selection& selection) const +void GLGizmoFlatten::on_render() const { + const Selection& selection = m_parent.get_selection(); + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); glsafe(::glEnable(GL_DEPTH_TEST)); @@ -83,8 +98,10 @@ void GLGizmoFlatten::on_render(const Selection& selection) const glsafe(::glDisable(GL_BLEND)); } -void GLGizmoFlatten::on_render_for_picking(const Selection& selection) const +void GLGizmoFlatten::on_render_for_picking() const { + const Selection& selection = m_parent.get_selection(); + glsafe(::glDisable(GL_DEPTH_TEST)); glsafe(::glDisable(GL_BLEND)); @@ -120,6 +137,7 @@ void GLGizmoFlatten::set_flattening_data(const ModelObject* model_object) m_planes_valid = false; } m_model_object = model_object; + m_model_object_id = model_object ? model_object->id() : 0; } void GLGizmoFlatten::update_planes() diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp index 1bd17e5ef..c69d64134 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp @@ -31,17 +31,14 @@ private: bool m_planes_valid = false; mutable Vec3d m_starting_center; const ModelObject* m_model_object = nullptr; + ObjectID m_model_object_id = 0; std::vector instances_matrices; void update_planes(); bool is_plane_update_necessary() const; public: -#if ENABLE_SVG_ICONS GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); -#else - GLGizmoFlatten(GLCanvas3D& parent, unsigned int sprite_id); -#endif // ENABLE_SVG_ICONS void set_flattening_data(const ModelObject* model_object); Vec3d get_flattening_normal() const; @@ -49,16 +46,11 @@ public: protected: virtual bool on_init(); virtual std::string on_get_name() const; - virtual bool on_is_activable(const Selection& selection) const; - virtual void on_start_dragging(const Selection& selection); - virtual void on_update(const UpdateData& data, const Selection& selection) {} - virtual void on_render(const Selection& selection) const; - virtual void on_render_for_picking(const Selection& selection) const; - virtual void on_set_state() - { - if (m_state == On && is_plane_update_necessary()) - update_planes(); - } + virtual bool on_is_activable() const; + virtual void on_start_dragging(); + virtual void on_render() const; + virtual void on_render_for_picking() const; + virtual void on_set_state() override; }; } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index a2ea738f5..11bdcd4f8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -1,5 +1,6 @@ // Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. #include "GLGizmoMove.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" #include @@ -10,13 +11,8 @@ namespace GUI { const double GLGizmoMove3D::Offset = 10.0; -#if ENABLE_SVG_ICONS GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) -#else -GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent, unsigned int sprite_id) - : GLGizmoBase(parent, sprite_id) -#endif // ENABLE_SVG_ICONS , m_displacement(Vec3d::Zero()) , m_snap_step(1.0) , m_starting_drag_position(Vec3d::Zero()) @@ -52,12 +48,12 @@ std::string GLGizmoMove3D::on_get_name() const return (_(L("Move")) + " [M]").ToUTF8().data(); } -void GLGizmoMove3D::on_start_dragging(const Selection& selection) +void GLGizmoMove3D::on_start_dragging() { if (m_hover_id != -1) { m_displacement = Vec3d::Zero(); - const BoundingBoxf3& box = selection.get_bounding_box(); + const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); m_starting_drag_position = m_grabbers[m_hover_id].center; m_starting_box_center = box.center(); m_starting_box_bottom_center = box.center(); @@ -70,7 +66,7 @@ void GLGizmoMove3D::on_stop_dragging() m_displacement = Vec3d::Zero(); } -void GLGizmoMove3D::on_update(const UpdateData& data, const Selection& selection) +void GLGizmoMove3D::on_update(const UpdateData& data) { if (m_hover_id == 0) m_displacement(0) = calc_projection(data); @@ -80,8 +76,10 @@ void GLGizmoMove3D::on_update(const UpdateData& data, const Selection& selection m_displacement(2) = calc_projection(data); } -void GLGizmoMove3D::on_render(const Selection& selection) const +void GLGizmoMove3D::on_render() const { + const Selection& selection = m_parent.get_selection(); + bool show_position = selection.is_single_full_instance(); const Vec3d& position = selection.get_bounding_box().center(); @@ -157,20 +155,21 @@ void GLGizmoMove3D::on_render(const Selection& selection) const } } -void GLGizmoMove3D::on_render_for_picking(const Selection& selection) const +void GLGizmoMove3D::on_render_for_picking() const { glsafe(::glDisable(GL_DEPTH_TEST)); - const BoundingBoxf3& box = selection.get_bounding_box(); + const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); render_grabbers_for_picking(box); render_grabber_extension(X, box, true); render_grabber_extension(Y, box, true); render_grabber_extension(Z, box, true); } -void GLGizmoMove3D::on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) -{ #if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI +void GLGizmoMove3D::on_render_input_window(float x, float y, float bottom_limit) +{ + const Selection& selection = m_parent.get_selection(); bool show_position = selection.is_single_full_instance(); const Vec3d& position = selection.get_bounding_box().center(); @@ -183,8 +182,8 @@ void GLGizmoMove3D::on_render_input_window(float x, float y, float bottom_limit, m_imgui->input_vec3("", displacement, 100.0f, "%.2f"); m_imgui->end(); -#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI } +#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI double GLGizmoMove3D::calc_projection(const UpdateData& data) const { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp index ddab2b777..21b1d397b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -22,11 +22,7 @@ class GLGizmoMove3D : public GLGizmoBase GLUquadricObj* m_quadric; public: -#if ENABLE_SVG_ICONS GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); -#else - GLGizmoMove3D(GLCanvas3D& parent, unsigned int sprite_id); -#endif // ENABLE_SVG_ICONS virtual ~GLGizmoMove3D(); double get_snap_step(double step) const { return m_snap_step; } @@ -37,12 +33,14 @@ public: protected: virtual bool on_init(); virtual std::string on_get_name() const; - virtual void on_start_dragging(const Selection& selection); + virtual void on_start_dragging(); virtual void on_stop_dragging(); - virtual void on_update(const UpdateData& data, const Selection& selection); - virtual void on_render(const Selection& selection) const; - virtual void on_render_for_picking(const Selection& selection) const; - virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection); + virtual void on_update(const UpdateData& data); + virtual void on_render() const; + virtual void on_render_for_picking() const; +#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI + virtual void on_render_input_window(float x, float y, float bottom_limit); +#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI private: double calc_projection(const UpdateData& data) const; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 68b9a8c33..f481bb5d7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -1,5 +1,6 @@ // Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. #include "GLGizmoRotate.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" #include @@ -19,11 +20,7 @@ const unsigned int GLGizmoRotate::SnapRegionsCount = 8; const float GLGizmoRotate::GrabberOffset = 0.15f; // in percent of radius GLGizmoRotate::GLGizmoRotate(GLCanvas3D& parent, GLGizmoRotate::Axis axis) -#if ENABLE_SVG_ICONS : GLGizmoBase(parent, "", -1) -#else - : GLGizmoBase(parent, -1) -#endif // ENABLE_SVG_ICONS , m_axis(axis) , m_angle(0.0) , m_quadric(nullptr) @@ -40,11 +37,7 @@ GLGizmoRotate::GLGizmoRotate(GLCanvas3D& parent, GLGizmoRotate::Axis axis) } GLGizmoRotate::GLGizmoRotate(const GLGizmoRotate& other) -#if ENABLE_SVG_ICONS : GLGizmoBase(other.m_parent, other.m_icon_filename, other.m_sprite_id) -#else - : GLGizmoBase(other.m_parent, other.m_sprite_id) -#endif // ENABLE_SVG_ICONS , m_axis(other.m_axis) , m_angle(other.m_angle) , m_quadric(nullptr) @@ -80,9 +73,9 @@ bool GLGizmoRotate::on_init() return true; } -void GLGizmoRotate::on_start_dragging(const Selection& selection) +void GLGizmoRotate::on_start_dragging() { - const BoundingBoxf3& box = selection.get_bounding_box(); + const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); m_center = box.center(); m_radius = Offset + box.radius(); m_snap_coarse_in_radius = m_radius / 3.0f; @@ -91,9 +84,9 @@ void GLGizmoRotate::on_start_dragging(const Selection& selection) m_snap_fine_out_radius = m_snap_fine_in_radius + m_radius * ScaleLongTooth; } -void GLGizmoRotate::on_update(const UpdateData& data, const Selection& selection) +void GLGizmoRotate::on_update(const UpdateData& data) { - Vec2d mouse_pos = to_2d(mouse_position_in_local_plane(data.mouse_ray, selection)); + Vec2d mouse_pos = to_2d(mouse_position_in_local_plane(data.mouse_ray, m_parent.get_selection())); Vec2d orig_dir = Vec2d::UnitX(); Vec2d new_dir = mouse_pos.normalized(); @@ -126,11 +119,12 @@ void GLGizmoRotate::on_update(const UpdateData& data, const Selection& selection m_angle = theta; } -void GLGizmoRotate::on_render(const Selection& selection) const +void GLGizmoRotate::on_render() const { if (!m_grabbers[0].enabled) return; + const Selection& selection = m_parent.get_selection(); const BoundingBoxf3& box = selection.get_bounding_box(); std::string axis; @@ -183,8 +177,10 @@ void GLGizmoRotate::on_render(const Selection& selection) const glsafe(::glPopMatrix()); } -void GLGizmoRotate::on_render_for_picking(const Selection& selection) const +void GLGizmoRotate::on_render_for_picking() const { + const Selection& selection = m_parent.get_selection(); + glsafe(::glDisable(GL_DEPTH_TEST)); glsafe(::glPushMatrix()); @@ -417,13 +413,8 @@ Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray, cons return transform(mouse_ray, m).intersect_plane(0.0); } -#if ENABLE_SVG_ICONS GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) -#else -GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent, unsigned int sprite_id) - : GLGizmoBase(parent, sprite_id) -#endif // ENABLE_SVG_ICONS { m_gizmos.emplace_back(parent, GLGizmoRotate::X); m_gizmos.emplace_back(parent, GLGizmoRotate::Y); @@ -458,10 +449,10 @@ std::string GLGizmoRotate3D::on_get_name() const return (_(L("Rotate")) + " [R]").ToUTF8().data(); } -void GLGizmoRotate3D::on_start_dragging(const Selection& selection) +void GLGizmoRotate3D::on_start_dragging() { if ((0 <= m_hover_id) && (m_hover_id < 3)) - m_gizmos[m_hover_id].start_dragging(selection); + m_gizmos[m_hover_id].start_dragging(); } void GLGizmoRotate3D::on_stop_dragging() @@ -470,23 +461,23 @@ void GLGizmoRotate3D::on_stop_dragging() m_gizmos[m_hover_id].stop_dragging(); } -void GLGizmoRotate3D::on_render(const Selection& selection) const +void GLGizmoRotate3D::on_render() const { glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); if ((m_hover_id == -1) || (m_hover_id == 0)) - m_gizmos[X].render(selection); + m_gizmos[X].render(); if ((m_hover_id == -1) || (m_hover_id == 1)) - m_gizmos[Y].render(selection); + m_gizmos[Y].render(); if ((m_hover_id == -1) || (m_hover_id == 2)) - m_gizmos[Z].render(selection); + m_gizmos[Z].render(); } -void GLGizmoRotate3D::on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) -{ #if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI +void GLGizmoRotate3D::on_render_input_window(float x, float y, float bottom_limit) +{ Vec3d rotation(Geometry::rad2deg(m_gizmos[0].get_angle()), Geometry::rad2deg(m_gizmos[1].get_angle()), Geometry::rad2deg(m_gizmos[2].get_angle())); wxString label = _(L("Rotation (deg)")); @@ -495,8 +486,8 @@ void GLGizmoRotate3D::on_render_input_window(float x, float y, float bottom_limi m_imgui->begin(label, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); m_imgui->input_vec3("", rotation, 100.0f, "%.2f"); m_imgui->end(); -#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI } +#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index c65dee4d8..7846edb22 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -52,10 +52,10 @@ public: protected: virtual bool on_init(); virtual std::string on_get_name() const { return ""; } - virtual void on_start_dragging(const Selection& selection); - virtual void on_update(const UpdateData& data, const Selection& selection); - virtual void on_render(const Selection& selection) const; - virtual void on_render_for_picking(const Selection& selection) const; + virtual void on_start_dragging(); + virtual void on_update(const UpdateData& data); + virtual void on_render() const; + virtual void on_render_for_picking() const; private: void render_circle() const; @@ -76,11 +76,7 @@ class GLGizmoRotate3D : public GLGizmoBase std::vector m_gizmos; public: -#if ENABLE_SVG_ICONS GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); -#else - GLGizmoRotate3D(GLCanvas3D& parent, unsigned int sprite_id); -#endif // ENABLE_SVG_ICONS Vec3d get_rotation() const { return Vec3d(m_gizmos[X].get_angle(), m_gizmos[Y].get_angle(), m_gizmos[Z].get_angle()); } void set_rotation(const Vec3d& rotation) { m_gizmos[X].set_angle(rotation(0)); m_gizmos[Y].set_angle(rotation(1)); m_gizmos[Z].set_angle(rotation(2)); } @@ -102,7 +98,6 @@ protected: m_gizmos[i].set_hover_id((m_hover_id == i) ? 0 : -1); } } - virtual bool on_is_activable(const Selection& selection) const { return true; } virtual void on_enable_grabber(unsigned int id) { if ((0 <= id) && (id < 3)) @@ -113,25 +108,26 @@ protected: if ((0 <= id) && (id < 3)) m_gizmos[id].disable_grabber(0); } - virtual void on_start_dragging(const Selection& selection); + virtual void on_start_dragging(); virtual void on_stop_dragging(); - virtual void on_update(const UpdateData& data, const Selection& selection) + virtual void on_update(const UpdateData& data) { for (GLGizmoRotate& g : m_gizmos) { - g.update(data, selection); + g.update(data); } } - virtual void on_render(const Selection& selection) const; - virtual void on_render_for_picking(const Selection& selection) const + virtual void on_render() const; + virtual void on_render_for_picking() const { for (const GLGizmoRotate& g : m_gizmos) { - g.render_for_picking(selection); + g.render_for_picking(); } } - - virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection); +#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI + virtual void on_render_input_window(float x, float y, float bottom_limit); +#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index e8027c871..7dc38b801 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -1,7 +1,6 @@ - - // Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. #include "GLGizmoScale.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" #include @@ -13,13 +12,8 @@ namespace GUI { const float GLGizmoScale3D::Offset = 5.0f; -#if ENABLE_SVG_ICONS GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) -#else -GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent, unsigned int sprite_id) - : GLGizmoBase(parent, sprite_id) -#endif // ENABLE_SVG_ICONS , m_scale(Vec3d::Ones()) , m_offset(Vec3d::Zero()) , m_snap_step(0.05) @@ -53,13 +47,18 @@ std::string GLGizmoScale3D::on_get_name() const return (_(L("Scale")) + " [S]").ToUTF8().data(); } -void GLGizmoScale3D::on_start_dragging(const Selection& selection) +bool GLGizmoScale3D::on_is_activable() const +{ + return !m_parent.get_selection().is_wipe_tower(); +} + +void GLGizmoScale3D::on_start_dragging() { if (m_hover_id != -1) { m_starting.drag_position = m_grabbers[m_hover_id].center; m_starting.ctrl_down = wxGetKeyState(WXK_CONTROL); - m_starting.box = (m_starting.ctrl_down && (m_hover_id < 6)) ? m_box : selection.get_bounding_box(); + m_starting.box = (m_starting.ctrl_down && (m_hover_id < 6)) ? m_box : m_parent.get_selection().get_bounding_box(); const Vec3d& center = m_starting.box.center(); m_starting.pivots[0] = m_transform * Vec3d(m_starting.box.max(0), center(1), center(2)); @@ -71,7 +70,7 @@ void GLGizmoScale3D::on_start_dragging(const Selection& selection) } } -void GLGizmoScale3D::on_update(const UpdateData& data, const Selection& selection) +void GLGizmoScale3D::on_update(const UpdateData& data) { if ((m_hover_id == 0) || (m_hover_id == 1)) do_scale_along_axis(X, data); @@ -83,8 +82,10 @@ void GLGizmoScale3D::on_update(const UpdateData& data, const Selection& selectio do_scale_uniform(data); } -void GLGizmoScale3D::on_render(const Selection& selection) const +void GLGizmoScale3D::on_render() const { + const Selection& selection = m_parent.get_selection(); + bool single_instance = selection.is_single_full_instance(); bool single_volume = selection.is_single_modifier() || selection.is_single_volume(); bool single_selection = single_instance || single_volume; @@ -136,7 +137,7 @@ void GLGizmoScale3D::on_render(const Selection& selection) const for (unsigned int idx : idxs) { const GLVolume* vol = selection.get_volume(idx); - m_box.merge(vol->bounding_box.transformed(vol->get_volume_transformation().get_matrix())); + m_box.merge(vol->bounding_box().transformed(vol->get_volume_transformation().get_matrix())); } // gets transform from first selected volume @@ -151,7 +152,7 @@ void GLGizmoScale3D::on_render(const Selection& selection) const else if (single_volume) { const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); - m_box = v->bounding_box; + m_box = v->bounding_box(); m_transform = v->world_matrix(); angles = Geometry::extract_euler_angles(m_transform); // consider rotation+mirror only components of the transform for offsets @@ -277,16 +278,16 @@ void GLGizmoScale3D::on_render(const Selection& selection) const } } -void GLGizmoScale3D::on_render_for_picking(const Selection& selection) const +void GLGizmoScale3D::on_render_for_picking() const { glsafe(::glDisable(GL_DEPTH_TEST)); - - render_grabbers_for_picking(selection.get_bounding_box()); + render_grabbers_for_picking(m_parent.get_selection().get_bounding_box()); } -void GLGizmoScale3D::on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) -{ #if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI +void GLGizmoScale3D::on_render_input_window(float x, float y, float bottom_limit) +{ + const Selection& selection = m_parent.get_selection(); bool single_instance = selection.is_single_full_instance(); wxString label = _(L("Scale (%)")); @@ -295,8 +296,8 @@ void GLGizmoScale3D::on_render_input_window(float x, float y, float bottom_limit m_imgui->begin(label, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); m_imgui->input_vec3("", m_scale * 100.f, 100.0f, "%.2f"); m_imgui->end(); -#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI } +#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int id_2) const { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index 3b0717f04..7b77fe559 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -32,11 +32,7 @@ class GLGizmoScale3D : public GLGizmoBase StartingData m_starting; public: -#if ENABLE_SVG_ICONS GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); -#else - GLGizmoScale3D(GLCanvas3D& parent, unsigned int sprite_id); -#endif // ENABLE_SVG_ICONS double get_snap_step(double step) const { return m_snap_step; } void set_snap_step(double step) { m_snap_step = step; } @@ -49,12 +45,14 @@ public: protected: virtual bool on_init(); virtual std::string on_get_name() const; - virtual bool on_is_activable(const Selection& selection) const { return !selection.is_wipe_tower(); } - virtual void on_start_dragging(const Selection& selection); - virtual void on_update(const UpdateData& data, const Selection& selection); - virtual void on_render(const Selection& selection) const; - virtual void on_render_for_picking(const Selection& selection) const; - virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection); + virtual bool on_is_activable() const; + virtual void on_start_dragging(); + virtual void on_update(const UpdateData& data); + virtual void on_render() const; + virtual void on_render_for_picking() const; +#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI + virtual void on_render_input_window(float x, float y, float bottom_limit); +#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI private: void render_grabbers_connection(unsigned int id_1, unsigned int id_2) const; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index cfe07a61c..833ba3ee2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -19,13 +19,8 @@ namespace Slic3r { namespace GUI { -#if ENABLE_SVG_ICONS GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) -#else -GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, unsigned int sprite_id) - : GLGizmoBase(parent, sprite_id) -#endif // ENABLE_SVG_ICONS , m_quadric(nullptr) , m_its(nullptr) { @@ -99,8 +94,10 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S } } -void GLGizmoSlaSupports::on_render(const Selection& selection) const +void GLGizmoSlaSupports::on_render() const { + const Selection& selection = m_parent.get_selection(); + // If current m_model_object does not match selection, ask GLCanvas3D to turn us off if (m_state == On && (m_model_object != selection.get_model()->objects[selection.get_object_idx()] @@ -109,6 +106,9 @@ void GLGizmoSlaSupports::on_render(const Selection& selection) const return; } + if (! m_its || ! m_mesh) + const_cast(this)->update_mesh(); + glsafe(::glEnable(GL_BLEND)); glsafe(::glEnable(GL_DEPTH_TEST)); @@ -257,8 +257,13 @@ void GLGizmoSlaSupports::render_clipping_plane(const Selection& selection) const } -void GLGizmoSlaSupports::on_render_for_picking(const Selection& selection) const +void GLGizmoSlaSupports::on_render_for_picking() const { + 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); } @@ -380,23 +385,31 @@ bool GLGizmoSlaSupports::is_point_clipped(const Vec3d& point) const bool GLGizmoSlaSupports::is_mesh_update_necessary() const { return ((m_state == On) && (m_model_object != nullptr) && !m_model_object->instances.empty()) - && ((m_model_object->id() != m_current_mesh_model_id) || m_its == nullptr); + && ((m_model_object->id() != m_current_mesh_object_id) || m_its == nullptr); } void GLGizmoSlaSupports::update_mesh() { + if (! m_model_object) + return; + wxBusyCursor wait; // this way we can use that mesh directly. // This mesh does not account for the possible Z up SLA offset. m_mesh = &m_model_object->volumes.front()->mesh(); m_its = &m_mesh->its; - m_current_mesh_model_id = m_model_object->id(); - m_editing_mode = false; - m_AABB.deinit(); - m_AABB.init( - MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3), - MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3)); + // If this is different mesh than last time or if the AABB tree is uninitialized, recalculate it. + if (m_current_mesh_object_id != m_model_object->id() || (m_AABB.m_left == NULL && m_AABB.m_right == NULL)) + { + m_AABB.deinit(); + m_AABB.init( + MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3), + MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3)); + } + + m_current_mesh_object_id = m_model_object->id(); + m_editing_mode = false; } // Unprojects the mouse position on the mesh and return the hit point and normal of the facet. @@ -703,12 +716,12 @@ void GLGizmoSlaSupports::delete_selected_points(bool force) //m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); } -void GLGizmoSlaSupports::on_update(const UpdateData& data, const Selection& selection) +void GLGizmoSlaSupports::on_update(const UpdateData& data) { - if (m_editing_mode && m_hover_id != -1 && data.mouse_pos && (!m_editing_mode_cache[m_hover_id].support_point.is_new_island || !m_lock_unique_islands)) { + if (m_editing_mode && m_hover_id != -1 && (!m_editing_mode_cache[m_hover_id].support_point.is_new_island || !m_lock_unique_islands)) { std::pair pos_and_normal; try { - pos_and_normal = unproject_on_mesh(Vec2d((*data.mouse_pos)(0), (*data.mouse_pos)(1))); + pos_and_normal = unproject_on_mesh(data.mouse_pos.cast()); } catch (...) { return; } m_editing_mode_cache[m_hover_id].support_point.pos = pos_and_normal.first; @@ -823,7 +836,7 @@ void GLGizmoSlaSupports::make_line_segments() const */ -void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) +void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_limit) { if (!m_model_object) return; @@ -924,10 +937,12 @@ RENDER_AGAIN: } if (value_changed) { // Update side panel - wxTheApp->CallAfter([]() { - wxGetApp().obj_settings()->UpdateAndShow(true); - wxGetApp().obj_list()->update_settings_items(); - }); +/* wxTheApp->CallAfter([]() { + * wxGetApp().obj_settings()->UpdateAndShow(true); + * wxGetApp().obj_list()->update_settings_items(); + * }); + * #lm_FIXME_delete_after_testing */ + wxGetApp().obj_list()->update_and_show_object_settings_item(); } bool generate = m_imgui->button(m_desc.at("auto_generate")); @@ -1001,11 +1016,13 @@ RENDER_AGAIN: m_parent.set_as_dirty(); } -bool GLGizmoSlaSupports::on_is_activable(const Selection& selection) const +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; + 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(); @@ -1028,53 +1045,72 @@ std::string GLGizmoSlaSupports::on_get_name() const void GLGizmoSlaSupports::on_set_state() { - if (m_state == On && m_old_state != On) { // the gizmo was just turned on - if (is_mesh_update_necessary()) - update_mesh(); - - // we'll now reload support points: - if (m_model_object) - editing_mode_reload_cache(); - - m_parent.toggle_model_objects_visibility(false); - if (m_model_object) - m_parent.toggle_model_objects_visibility(true, m_model_object, m_active_instance); - - // 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; + // m_model_object pointer can be invalid (for instance because of undo/redo action), + // we should recover it from the object id + const ModelObject* old_model_object = m_model_object; + m_model_object = nullptr; + for (const auto mo : *wxGetApp().model_objects()) { + if (mo->id() == m_current_mesh_object_id) { + m_model_object = mo; + break; } - if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off - wxGetApp().CallAfter([this]() { - // Following is called through CallAfter, because otherwise there was a problem - // on OSX with the wxMessageDialog being shown several times when clicked into. - if (m_model_object) { - if (m_unsaved_changes) { - wxMessageDialog dlg(GUI::wxGetApp().mainframe, _(L("Do you want to save your manually edited support points?")) + "\n", - _(L("Save changes?")), wxICON_QUESTION | wxYES | wxNO); - if (dlg.ShowModal() == wxID_YES) - editing_mode_apply_changes(); - else - editing_mode_discard_changes(); - } + } + + // If ModelObject pointer really changed, invalidate mesh and do everything + // as if the gizmo was switched from Off state + if (m_model_object == nullptr || old_model_object != m_model_object) { + m_mesh = nullptr; + m_its = nullptr; + m_old_state = Off; + } + + if (m_state == On && m_old_state != On) { // the gizmo was just turned on + if (is_mesh_update_necessary()) + update_mesh(); + + // we'll now reload support points: + if (m_model_object) + editing_mode_reload_cache(); + + m_parent.toggle_model_objects_visibility(false); + if (m_model_object) + m_parent.toggle_model_objects_visibility(true, m_model_object, m_active_instance); + + // 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 + wxGetApp().CallAfter([this]() { + // Following is called through CallAfter, because otherwise there was a problem + // on OSX with the wxMessageDialog being shown several times when clicked into. + if (m_model_object) { + if (m_unsaved_changes) { + wxMessageDialog dlg(GUI::wxGetApp().mainframe, _(L("Do you want to save your manually edited support points?")) + "\n", + _(L("Save changes?")), wxICON_QUESTION | wxYES | wxNO); + if (dlg.ShowModal() == wxID_YES) + editing_mode_apply_changes(); + else + editing_mode_discard_changes(); } - m_parent.toggle_model_objects_visibility(true); - m_editing_mode = false; // so it is not active next time the gizmo opens - m_editing_mode_cache.clear(); - m_clipping_plane_distance = 0.f; - // Release triangle mesh slicer and the AABB spatial search structure. - m_AABB.deinit(); - m_its = nullptr; - m_tms.reset(); - m_supports_tms.reset(); - }); - } - m_old_state = m_state; + } + m_parent.toggle_model_objects_visibility(true); + m_editing_mode = false; // so it is not active next time the gizmo opens + m_editing_mode_cache.clear(); + m_clipping_plane_distance = 0.f; + // Release triangle mesh slicer and the AABB spatial search structure. + m_AABB.deinit(); + m_its = nullptr; + m_tms.reset(); + m_supports_tms.reset(); + }); + } + m_old_state = m_state; } -void GLGizmoSlaSupports::on_start_dragging(const Selection& selection) +void GLGizmoSlaSupports::on_start_dragging() { if (m_hover_id != -1) { select_point(NoPoints); @@ -1084,6 +1120,26 @@ void GLGizmoSlaSupports::on_start_dragging(const Selection& selection) +void GLGizmoSlaSupports::on_load(cereal::BinaryInputArchive& ar) +{ + ar(m_clipping_plane_distance, + m_clipping_plane_normal, + m_current_mesh_object_id + ); +} + + + +void GLGizmoSlaSupports::on_save(cereal::BinaryOutputArchive& ar) const +{ + ar(m_clipping_plane_distance, + m_clipping_plane_normal, + m_current_mesh_object_id + ); +} + + + void GLGizmoSlaSupports::select_point(int i) { if (i == AllPoints || i == NoPoints) { @@ -1140,6 +1196,7 @@ void GLGizmoSlaSupports::editing_mode_apply_changes() // If there are no changes, don't touch the front-end. The data in the cache could have been // taken from the backend and copying them to ModelObject would needlessly invalidate them. if (m_unsaved_changes) { + wxGetApp().plater()->take_snapshot(_(L("Support points edit"))); m_model_object->sla_points_status = sla::PointsStatus::UserModified; m_model_object->sla_support_points.clear(); for (const CacheEntry& cache_entry : m_editing_mode_cache) @@ -1198,6 +1255,7 @@ void GLGizmoSlaSupports::auto_generate() )), _(L("Warning")), wxICON_WARNING | wxYES | wxNO); if (m_model_object->sla_points_status != sla::PointsStatus::UserModified || m_editing_mode_cache.empty() || dlg.ShowModal() == wxID_YES) { + wxGetApp().plater()->take_snapshot(_(L("Autogenerate support points"))); m_model_object->sla_support_points.clear(); m_model_object->sla_points_status = sla::PointsStatus::Generating; m_editing_mode_cache.clear(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index 30238cc9d..fbc438f1d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -14,6 +14,8 @@ #include "libslic3r/SLAPrint.hpp" #include +#include + namespace Slic3r { namespace GUI { @@ -26,7 +28,7 @@ class GLGizmoSlaSupports : public GLGizmoBase { private: ModelObject* m_model_object = nullptr; - ModelID m_current_mesh_model_id = 0; + ObjectID m_current_mesh_object_id = 0; int m_active_instance = -1; float m_active_instance_bb_radius; // to cache the bb mutable float m_z_shift = 0.f; @@ -49,20 +51,25 @@ private: class CacheEntry { public: + CacheEntry() : + support_point(sla::SupportPoint()), selected(false), normal(Vec3f::Zero()) {} + CacheEntry(const sla::SupportPoint& point, bool sel, const Vec3f& norm = Vec3f::Zero()) : support_point(point), selected(sel), normal(norm) {} sla::SupportPoint support_point; bool selected; // whether the point is selected Vec3f normal; + + template + void serialize(Archive & ar) + { + ar(support_point, selected, normal); + } }; public: -#if ENABLE_SVG_ICONS GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); -#else - GLGizmoSlaSupports(GLCanvas3D& parent, unsigned int sprite_id); -#endif // ENABLE_SVG_ICONS virtual ~GLGizmoSlaSupports(); void set_sla_support_data(ModelObject* model_object, const Selection& selection); bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); @@ -74,9 +81,9 @@ public: private: bool on_init(); - void on_update(const UpdateData& data, const Selection& selection); - virtual void on_render(const Selection& selection) const; - virtual void on_render_for_picking(const Selection& selection) const; + void on_update(const UpdateData& data); + virtual void on_render() const; + virtual void on_render_for_picking() const; //void render_selection_rectangle() const; void render_points(const Selection& selection, bool picking = false) const; @@ -137,12 +144,14 @@ protected: if ((int)m_editing_mode_cache.size() <= m_hover_id) m_hover_id = -1; } - void on_start_dragging(const Selection& selection) override; - virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) override; + void on_start_dragging() override; + virtual void on_render_input_window(float x, float y, float bottom_limit) override; virtual std::string on_get_name() const; - virtual bool on_is_activable(const Selection& selection) const; + virtual bool on_is_activable() const; virtual bool on_is_selectable() const; + virtual void on_load(cereal::BinaryInputArchive& ar) override; + virtual void on_save(cereal::BinaryOutputArchive& ar) const override; }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index c7435636d..df53b40aa 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -12,51 +12,29 @@ namespace Slic3r { namespace GUI { -#if ENABLE_SVG_ICONS - const float GLGizmosManager::Default_Icons_Size = 64; -#endif // ENABLE_SVG_ICONS +const float GLGizmosManager::Default_Icons_Size = 64; -GLGizmosManager::GLGizmosManager() - : m_enabled(false) -#if ENABLE_SVG_ICONS +GLGizmosManager::GLGizmosManager(GLCanvas3D& parent) + : m_parent(parent) + , m_enabled(false) , m_icons_texture_dirty(true) -#endif // ENABLE_SVG_ICONS , m_current(Undefined) -#if ENABLE_SVG_ICONS , m_overlay_icons_size(Default_Icons_Size) , m_overlay_scale(1.0f) , m_overlay_border(5.0f) , m_overlay_gap_y(5.0f) , m_tooltip("") + , m_serializing(false) { } -#else -{ - set_overlay_scale(1.0); -} -#endif // ENABLE_SVG_ICONS GLGizmosManager::~GLGizmosManager() { reset(); } -bool GLGizmosManager::init(GLCanvas3D& parent) +bool GLGizmosManager::init() { -#if !ENABLE_SVG_ICONS - m_icons_texture.metadata.filename = "gizmos.png"; - m_icons_texture.metadata.icon_size = 64; - - if (!m_icons_texture.metadata.filename.empty()) - { - if (!m_icons_texture.texture.load_from_file(resources_dir() + "/icons/" + m_icons_texture.metadata.filename, false)) - { - reset(); - return false; - } - } -#endif // !ENABLE_SVG_ICONS - m_background_texture.metadata.filename = "toolbar_background.png"; m_background_texture.metadata.left = 16; m_background_texture.metadata.top = 16; @@ -72,11 +50,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent) } } -#if ENABLE_SVG_ICONS - GLGizmoBase* gizmo = new GLGizmoMove3D(parent, "move.svg", 0); -#else - GLGizmoBase* gizmo = new GLGizmoMove3D(parent, 0); -#endif // ENABLE_SVG_ICONS + GLGizmoBase* gizmo = new GLGizmoMove3D(m_parent, "move.svg", 0); if (gizmo == nullptr) return false; @@ -85,11 +59,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent) m_gizmos.insert(GizmosMap::value_type(Move, gizmo)); -#if ENABLE_SVG_ICONS - gizmo = new GLGizmoScale3D(parent, "scale.svg", 1); -#else - gizmo = new GLGizmoScale3D(parent, 1); -#endif // ENABLE_SVG_ICONS + gizmo = new GLGizmoScale3D(m_parent, "scale.svg", 1); if (gizmo == nullptr) return false; @@ -98,11 +68,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent) m_gizmos.insert(GizmosMap::value_type(Scale, gizmo)); -#if ENABLE_SVG_ICONS - gizmo = new GLGizmoRotate3D(parent, "rotate.svg", 2); -#else - gizmo = new GLGizmoRotate3D(parent, 2); -#endif // ENABLE_SVG_ICONS + gizmo = new GLGizmoRotate3D(m_parent, "rotate.svg", 2); if (gizmo == nullptr) { reset(); @@ -117,11 +83,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent) m_gizmos.insert(GizmosMap::value_type(Rotate, gizmo)); -#if ENABLE_SVG_ICONS - gizmo = new GLGizmoFlatten(parent, "place.svg", 3); -#else - gizmo = new GLGizmoFlatten(parent, 3); -#endif // ENABLE_SVG_ICONS + gizmo = new GLGizmoFlatten(m_parent, "place.svg", 3); if (gizmo == nullptr) return false; @@ -132,11 +94,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent) m_gizmos.insert(GizmosMap::value_type(Flatten, gizmo)); -#if ENABLE_SVG_ICONS - gizmo = new GLGizmoCut(parent, "cut.svg", 4); -#else - gizmo = new GLGizmoCut(parent, 4); -#endif // ENABLE_SVG_ICONS + gizmo = new GLGizmoCut(m_parent, "cut.svg", 4); if (gizmo == nullptr) return false; @@ -147,11 +105,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent) m_gizmos.insert(GizmosMap::value_type(Cut, gizmo)); -#if ENABLE_SVG_ICONS - gizmo = new GLGizmoSlaSupports(parent, "sla_supports.svg", 5); -#else - gizmo = new GLGizmoSlaSupports(parent, 5); -#endif // ENABLE_SVG_ICONS + gizmo = new GLGizmoSlaSupports(m_parent, "sla_supports.svg", 5); if (gizmo == nullptr) return false; @@ -165,7 +119,6 @@ bool GLGizmosManager::init(GLCanvas3D& parent) return true; } -#if ENABLE_SVG_ICONS void GLGizmosManager::set_overlay_icon_size(float size) { if (m_overlay_icons_size != size) @@ -174,29 +127,25 @@ void GLGizmosManager::set_overlay_icon_size(float size) m_icons_texture_dirty = true; } } -#endif // ENABLE_SVG_ICONS void GLGizmosManager::set_overlay_scale(float scale) { -#if ENABLE_SVG_ICONS if (m_overlay_scale != scale) { m_overlay_scale = scale; m_icons_texture_dirty = true; } -#else - m_overlay_icons_scale = scale; - m_overlay_border = 5.0f * scale; - m_overlay_gap_y = 5.0f * scale; -#endif // ENABLE_SVG_ICONS } -void GLGizmosManager::refresh_on_off_state(const Selection& selection) +void GLGizmosManager::refresh_on_off_state() { + if (m_serializing) + return; + GizmosMap::iterator it = m_gizmos.find(m_current); if ((it != m_gizmos.end()) && (it->second != nullptr)) { - if (!it->second->is_activable(selection)) + if (!it->second->is_activable()) { it->second->set_state(GLGizmoBase::Off); m_current = Undefined; @@ -209,6 +158,9 @@ void GLGizmosManager::reset_all_states() if (!m_enabled) return; + if (m_serializing) + return; + for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { if (it->second != nullptr) @@ -248,22 +200,22 @@ void GLGizmosManager::enable_grabber(EType type, unsigned int id, bool enable) } } -void GLGizmosManager::update(const Linef3& mouse_ray, const Selection& selection, const Point* mouse_pos) +void GLGizmosManager::update(const Linef3& mouse_ray, const Point& mouse_pos) { if (!m_enabled) return; GLGizmoBase* curr = get_current(); if (curr != nullptr) - curr->update(GLGizmoBase::UpdateData(mouse_ray, mouse_pos), selection); + curr->update(GLGizmoBase::UpdateData(mouse_ray, mouse_pos)); } -void GLGizmosManager::update_data(GLCanvas3D& canvas) +void GLGizmosManager::update_data() { if (!m_enabled) return; - const Selection& selection = canvas.get_selection(); + const Selection& selection = m_parent.get_selection(); bool is_wipe_tower = selection.is_wipe_tower(); enable_grabber(Move, 2, !is_wipe_tower); @@ -284,7 +236,7 @@ void GLGizmosManager::update_data(GLCanvas3D& canvas) set_rotation(Vec3d::Zero()); ModelObject* model_object = selection.get_model()->objects[selection.get_object_idx()]; set_flattening_data(model_object); - set_sla_support_data(model_object, selection); + set_sla_support_data(model_object); } else if (selection.is_single_volume() || selection.is_single_modifier()) { @@ -292,7 +244,7 @@ void GLGizmosManager::update_data(GLCanvas3D& canvas) set_scale(volume->get_volume_scaling_factor()); set_rotation(Vec3d::Zero()); set_flattening_data(nullptr); - set_sla_support_data(nullptr, selection); + set_sla_support_data(nullptr); } else if (is_wipe_tower) { @@ -300,14 +252,14 @@ void GLGizmosManager::update_data(GLCanvas3D& canvas) set_scale(Vec3d::Ones()); set_rotation(Vec3d(0., 0., (M_PI/180.) * dynamic_cast(config.option("wipe_tower_rotation_angle"))->value)); set_flattening_data(nullptr); - set_sla_support_data(nullptr, selection); + set_sla_support_data(nullptr); } else { set_scale(Vec3d::Ones()); set_rotation(Vec3d::Zero()); set_flattening_data(selection.is_from_single_object() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr); - set_sla_support_data(nullptr, selection); + set_sla_support_data(nullptr); } } @@ -320,9 +272,12 @@ bool GLGizmosManager::is_running() const return (curr != nullptr) ? (curr->get_state() == GLGizmoBase::On) : false; } -bool GLGizmosManager::handle_shortcut(int key, const Selection& selection) +bool GLGizmosManager::handle_shortcut(int key) { - if (!m_enabled || selection.is_empty()) + if (!m_enabled) + return false; + + if (m_parent.get_selection().is_empty()) return false; EType old_current = m_current; @@ -334,7 +289,7 @@ bool GLGizmosManager::handle_shortcut(int key, const Selection& selection) int it_key = it->second->get_shortcut_key(); - if (it->second->is_activable(selection) && ((it_key == key - 64) || (it_key == key - 96))) + if (it->second->is_activable() && ((it_key == key - 64) || (it_key == key - 96))) { if ((it->second->get_state() == GLGizmoBase::On)) { @@ -370,14 +325,14 @@ bool GLGizmosManager::is_dragging() const return (curr != nullptr) ? curr->is_dragging() : false; } -void GLGizmosManager::start_dragging(const Selection& selection) +void GLGizmosManager::start_dragging() { if (!m_enabled) return; GLGizmoBase* curr = get_current(); if (curr != nullptr) - curr->start_dragging(selection); + curr->start_dragging(); } void GLGizmosManager::stop_dragging() @@ -465,14 +420,14 @@ void GLGizmosManager::set_flattening_data(const ModelObject* model_object) reinterpret_cast(it->second)->set_flattening_data(model_object); } -void GLGizmosManager::set_sla_support_data(ModelObject* model_object, const Selection& selection) +void GLGizmosManager::set_sla_support_data(ModelObject* model_object) { if (!m_enabled) return; GizmosMap::const_iterator it = m_gizmos.find(SlaSupports); if (it != m_gizmos.end()) - reinterpret_cast(it->second)->set_sla_support_data(model_object, selection); + reinterpret_cast(it->second)->set_sla_support_data(model_object, m_parent.get_selection()); } // Returns true if the gizmo used the event to do something, false otherwise. @@ -501,40 +456,38 @@ ClippingPlane GLGizmosManager::get_sla_clipping_plane() const } -void GLGizmosManager::render_current_gizmo(const Selection& selection) const +void GLGizmosManager::render_current_gizmo() const { if (!m_enabled) return; GLGizmoBase* curr = get_current(); if (curr != nullptr) - curr->render(selection); + curr->render(); } -void GLGizmosManager::render_current_gizmo_for_picking_pass(const Selection& selection) const +void GLGizmosManager::render_current_gizmo_for_picking_pass() const { if (!m_enabled) return; GLGizmoBase* curr = get_current(); if (curr != nullptr) - curr->render_for_picking(selection); + curr->render_for_picking(); } -void GLGizmosManager::render_overlay(const GLCanvas3D& canvas, const Selection& selection) const +void GLGizmosManager::render_overlay() const { if (!m_enabled) return; -#if ENABLE_SVG_ICONS if (m_icons_texture_dirty) generate_icons_texture(); -#endif // ENABLE_SVG_ICONS - do_render_overlay(canvas, selection); + do_render_overlay(); } -bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt, GLCanvas3D& canvas) +bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt) { bool processed = false; @@ -547,14 +500,12 @@ bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt, GLCanvas3D& canvas) return processed; } - - -bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) +bool GLGizmosManager::on_mouse(wxMouseEvent& evt) { Point pos(evt.GetX(), evt.GetY()); Vec2d mouse_pos((double)evt.GetX(), (double)evt.GetY()); - Selection& selection = canvas.get_selection(); + Selection& selection = m_parent.get_selection(); int selected_object_idx = selection.get_object_idx(); bool processed = false; @@ -570,7 +521,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) // mouse anywhere if (evt.Moving()) - m_tooltip = update_hover_state(canvas, mouse_pos); + m_tooltip = update_hover_state(mouse_pos); else if (evt.LeftUp()) m_mouse_capture.left = false; else if (evt.MiddleUp()) @@ -581,7 +532,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) // if the button down was done on this toolbar, prevent from dragging into the scene processed = true; - if (!overlay_contains_mouse(canvas, mouse_pos)) + if (!overlay_contains_mouse(mouse_pos)) { // mouse is outside the toolbar m_tooltip = ""; @@ -593,41 +544,40 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) processed = true; else if (!selection.is_empty() && grabber_contains_mouse()) { - update_data(canvas); + update_data(); selection.start_dragging(); - start_dragging(selection); + start_dragging(); if (m_current == Flatten) { // Rotate the object so the normal points downward: - selection.flattening_rotate(get_flattening_normal()); - canvas.do_flatten(); + m_parent.do_flatten(get_flattening_normal(), L("Gizmo-Place on Face")); wxGetApp().obj_manipul()->set_dirty(); } - canvas.set_as_dirty(); + m_parent.set_as_dirty(); processed = true; } } else if (evt.RightDown() && (selected_object_idx != -1) && (m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::RightDown)) // event was taken care of by the SlaSupports gizmo processed = true; - else if (evt.Dragging() && (canvas.get_move_volume_id() != -1) && (m_current == SlaSupports)) - // don't allow dragging objects with the Sla gizmo on + else if (evt.Dragging() && (m_parent.get_move_volume_id() != -1) && (m_current == SlaSupports)) + // don't allow dragging objects with the Sla gizmo on processed = true; else if (evt.Dragging() && (m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown())) { // the gizmo got the event and took some action, no need to do anything more here - canvas.set_as_dirty(); + m_parent.set_as_dirty(); processed = true; } else if (evt.Dragging() && is_dragging()) { - if (!canvas.get_wxglcanvas()->HasCapture()) - canvas.get_wxglcanvas()->CaptureMouse(); + if (!m_parent.get_wxglcanvas()->HasCapture()) + m_parent.get_wxglcanvas()->CaptureMouse(); - canvas.set_mouse_as_dragging(); - update(canvas.mouse_ray(pos), selection, &pos); + m_parent.set_mouse_as_dragging(); + update(m_parent.mouse_ray(pos), pos); switch (m_current) { @@ -664,7 +614,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) break; } - canvas.set_as_dirty(); + m_parent.set_as_dirty(); processed = true; } else if (evt.LeftUp() && is_dragging()) @@ -673,18 +623,18 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) { case Move: { - canvas.disable_regenerate_volumes(); - canvas.do_move(); + m_parent.disable_regenerate_volumes(); + m_parent.do_move(L("Gizmo-Move")); break; } case Scale: { - canvas.do_scale(); + m_parent.do_scale(L("Gizmo-Scale")); break; } case Rotate: { - canvas.do_rotate(); + m_parent.do_rotate(L("Gizmo-Rotate")); break; } default: @@ -692,25 +642,25 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) } stop_dragging(); - update_data(canvas); + update_data(); wxGetApp().obj_manipul()->set_dirty(); // Let the platter know that the dragging finished, so a delayed refresh // of the scene with the background processing data should be performed. - canvas.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); // updates camera target constraints - canvas.refresh_camera_scene_box(); + m_parent.refresh_camera_scene_box(); processed = true; } - else if (evt.LeftUp() && (m_current == SlaSupports) && !canvas.is_mouse_dragging()) + else if (evt.LeftUp() && (m_current == SlaSupports) && !m_parent.is_mouse_dragging()) { // in case SLA gizmo is selected, we just pass the LeftUp event and stop processing - neither // object moving or selecting is suppressed in that case gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()); processed = true; } - else if (evt.LeftUp() && (m_current == Flatten) && ((canvas.get_first_hover_volume_idx() != -1) || grabber_contains_mouse())) + else if (evt.LeftUp() && (m_current == Flatten) && ((m_parent.get_first_hover_volume_idx() != -1) || grabber_contains_mouse())) { // to avoid to loose the selection when user clicks an object while the Flatten gizmo is active processed = true; @@ -722,24 +672,24 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) if (evt.LeftDown() || evt.LeftDClick()) { m_mouse_capture.left = true; - m_mouse_capture.parent = &canvas; + m_mouse_capture.parent = &m_parent; processed = true; if (!selection.is_empty()) { - update_on_off_state(canvas, mouse_pos, selection); - update_data(canvas); - canvas.set_as_dirty(); + update_on_off_state(mouse_pos); + update_data(); + m_parent.set_as_dirty(); } } else if (evt.MiddleDown()) { m_mouse_capture.middle = true; - m_mouse_capture.parent = &canvas; + m_mouse_capture.parent = &m_parent; } else if (evt.RightDown()) { m_mouse_capture.right = true; - m_mouse_capture.parent = &canvas; + m_mouse_capture.parent = &m_parent; } else if (evt.LeftUp()) processed = true; @@ -748,7 +698,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) return processed; } -bool GLGizmosManager::on_char(wxKeyEvent& evt, GLCanvas3D& canvas) +bool GLGizmosManager::on_char(wxKeyEvent& evt) { // see include/wx/defs.h enum wxKeyCode int keyCode = evt.GetKeyCode(); @@ -856,20 +806,20 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt, GLCanvas3D& canvas) if (!processed && !evt.HasModifiers()) { - if (handle_shortcut(keyCode, canvas.get_selection())) + if (handle_shortcut(keyCode)) { - update_data(canvas); + update_data(); processed = true; } } if (processed) - canvas.set_as_dirty(); + m_parent.set_as_dirty(); return processed; } -bool GLGizmosManager::on_key(wxKeyEvent& evt, GLCanvas3D& canvas) +bool GLGizmosManager::on_key(wxKeyEvent& evt) { const int keyCode = evt.GetKeyCode(); bool processed = false; @@ -895,23 +845,29 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt, GLCanvas3D& canvas) } // if (processed) -// canvas.set_cursor(GLCanvas3D::Standard); +// m_parent.set_cursor(GLCanvas3D::Standard); } else if (evt.GetEventType() == wxEVT_KEY_DOWN) { if ((m_current == SlaSupports) && ((keyCode == WXK_SHIFT) || (keyCode == WXK_ALT)) && reinterpret_cast(get_current())->is_in_editing_mode()) { -// canvas.set_cursor(GLCanvas3D::Cross); +// m_parent.set_cursor(GLCanvas3D::Cross); processed = true; } } if (processed) - canvas.set_as_dirty(); + m_parent.set_as_dirty(); return processed; } +void GLGizmosManager::update_after_undo_redo() +{ + update_data(); + m_serializing = false; +} + void GLGizmosManager::reset() { for (GizmosMap::value_type& gizmo : m_gizmos) @@ -923,23 +879,19 @@ void GLGizmosManager::reset() m_gizmos.clear(); } -void GLGizmosManager::do_render_overlay(const GLCanvas3D& canvas, const Selection& selection) const +void GLGizmosManager::do_render_overlay() const { if (m_gizmos.empty()) return; - float cnv_w = (float)canvas.get_canvas_size().get_width(); - float cnv_h = (float)canvas.get_canvas_size().get_height(); - float zoom = (float)canvas.get_camera().get_zoom(); + float cnv_w = (float)m_parent.get_canvas_size().get_width(); + float cnv_h = (float)m_parent.get_canvas_size().get_height(); + float zoom = (float)m_parent.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; float height = get_total_overlay_height(); float width = get_total_overlay_width(); -#if ENABLE_SVG_ICONS float scaled_border = m_overlay_border * m_overlay_scale * inv_zoom; -#else - float scaled_border = m_overlay_border * inv_zoom; -#endif // ENABLE_SVG_ICONS float top_x = (-0.5f * cnv_w) * inv_zoom; float top_y = (0.5f * height) * inv_zoom; @@ -1015,7 +967,6 @@ void GLGizmosManager::do_render_overlay(const GLCanvas3D& canvas, const Selectio } } -#if ENABLE_SVG_ICONS top_x += scaled_border; top_y -= scaled_border; float scaled_gap_y = m_overlay_gap_y * m_overlay_scale * inv_zoom; @@ -1027,21 +978,9 @@ void GLGizmosManager::do_render_overlay(const GLCanvas3D& canvas, const Selectio unsigned int tex_height = m_icons_texture.get_height(); float inv_tex_width = (tex_width != 0) ? 1.0f / (float)tex_width : 0.0f; float inv_tex_height = (tex_height != 0) ? 1.0f / (float)tex_height : 0.0f; -#else - top_x += m_overlay_border * inv_zoom; - top_y -= m_overlay_border * inv_zoom; - float scaled_gap_y = m_overlay_gap_y * inv_zoom; - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_overlay_icons_scale * inv_zoom; - unsigned int icons_texture_id = m_icons_texture.texture.get_id(); - unsigned int texture_size = m_icons_texture.texture.get_width(); - float inv_texture_size = (texture_size != 0) ? 1.0f / (float)texture_size : 0.0f; -#endif // ENABLE_SVG_ICONS - -#if ENABLE_SVG_ICONS if ((icons_texture_id == 0) || (tex_width <= 0) || (tex_height <= 0)) return; -#endif // ENABLE_SVG_ICONS for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { @@ -1051,78 +990,44 @@ void GLGizmosManager::do_render_overlay(const GLCanvas3D& canvas, const Selectio unsigned int sprite_id = it->second->get_sprite_id(); GLGizmoBase::EState state = it->second->get_state(); -#if ENABLE_SVG_ICONS float u_icon_size = m_overlay_icons_size * m_overlay_scale * inv_tex_width; float v_icon_size = m_overlay_icons_size * m_overlay_scale * inv_tex_height; float v_top = sprite_id * v_icon_size; float u_left = state * u_icon_size; float v_bottom = v_top + v_icon_size; float u_right = u_left + u_icon_size; -#else - float uv_icon_size = (float)m_icons_texture.metadata.icon_size * inv_texture_size; - float v_top = sprite_id * uv_icon_size; - float u_left = state * uv_icon_size; - float v_bottom = v_top + uv_icon_size; - float u_right = u_left + uv_icon_size; -#endif // ENABLE_SVG_ICONS GLTexture::render_sub_texture(icons_texture_id, top_x, top_x + scaled_icons_size, top_y - scaled_icons_size, top_y, { { u_left, v_bottom }, { u_right, v_bottom }, { u_right, v_top }, { u_left, v_top } }); if (it->second->get_state() == GLGizmoBase::On) { - float toolbar_top = (float)cnv_h - canvas.get_view_toolbar_height(); -#if ENABLE_SVG_ICONS - it->second->render_input_window(width, 0.5f * cnv_h - top_y * zoom, toolbar_top, selection); -#else - it->second->render_input_window(2.0f * m_overlay_border + icon_size * zoom, 0.5f * cnv_h - top_y * zoom, toolbar_top, selection); -#endif // ENABLE_SVG_ICONS + float toolbar_top = (float)cnv_h - m_parent.get_view_toolbar_height(); + it->second->render_input_window(width, 0.5f * cnv_h - top_y * zoom, toolbar_top); } -#if ENABLE_SVG_ICONS top_y -= scaled_stride_y; -#else - top_y -= (scaled_icons_size + scaled_gap_y); -#endif // ENABLE_SVG_ICONS } } float GLGizmosManager::get_total_overlay_height() const { -#if ENABLE_SVG_ICONS float scaled_icons_size = m_overlay_icons_size * m_overlay_scale; float scaled_border = m_overlay_border * m_overlay_scale; float scaled_gap_y = m_overlay_gap_y * m_overlay_scale; float scaled_stride_y = scaled_icons_size + scaled_gap_y; float height = 2.0f * scaled_border; -#else - float height = 2.0f * m_overlay_border; - - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_overlay_icons_scale; -#endif // ENABLE_SVG_ICONS for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { if ((it->second == nullptr) || !it->second->is_selectable()) continue; -#if ENABLE_SVG_ICONS height += scaled_stride_y; -#else - height += (scaled_icons_size + m_overlay_gap_y); -#endif // ENABLE_SVG_ICONS } -#if ENABLE_SVG_ICONS return height - scaled_gap_y; -#else - return height - m_overlay_gap_y; -#endif // ENABLE_SVG_ICONS } float GLGizmosManager::get_total_overlay_width() const { -#if ENABLE_SVG_ICONS return (2.0f * m_overlay_border + m_overlay_icons_size) * m_overlay_scale; -#else - return (float)m_icons_texture.metadata.icon_size * m_overlay_icons_scale + 2.0f * m_overlay_border; -#endif // ENABLE_SVG_ICONS } GLGizmoBase* GLGizmosManager::get_current() const @@ -1131,7 +1036,6 @@ GLGizmoBase* GLGizmosManager::get_current() const return (it != m_gizmos.end()) ? it->second : nullptr; } -#if ENABLE_SVG_ICONS bool GLGizmosManager::generate_icons_texture() const { std::string path = resources_dir() + "/icons/"; @@ -1157,38 +1061,28 @@ bool GLGizmosManager::generate_icons_texture() const return res; } -#endif // ENABLE_SVG_ICONS -void GLGizmosManager::update_on_off_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos, const Selection& selection) +void GLGizmosManager::update_on_off_state(const Vec2d& mouse_pos) { if (!m_enabled) return; - float cnv_h = (float)canvas.get_canvas_size().get_height(); + float cnv_h = (float)m_parent.get_canvas_size().get_height(); float height = get_total_overlay_height(); -#if ENABLE_SVG_ICONS float scaled_icons_size = m_overlay_icons_size * m_overlay_scale; float scaled_border = m_overlay_border * m_overlay_scale; float scaled_gap_y = m_overlay_gap_y * m_overlay_scale; float scaled_stride_y = scaled_icons_size + scaled_gap_y; float top_y = 0.5f * (cnv_h - height) + scaled_border; -#else - float top_y = 0.5f * (cnv_h - height) + m_overlay_border; - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_overlay_icons_scale; -#endif // ENABLE_SVG_ICONS for (GizmosMap::iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { if ((it->second == nullptr) || !it->second->is_selectable()) continue; -#if ENABLE_SVG_ICONS bool inside = (scaled_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= scaled_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size); -#else - bool inside = (m_overlay_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= m_overlay_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size); -#endif // ENABLE_SVG_ICONS - if (it->second->is_activable(selection) && inside) + if (it->second->is_activable() && inside) { if ((it->second->get_state() == GLGizmoBase::On)) { @@ -1204,11 +1098,7 @@ void GLGizmosManager::update_on_off_state(const GLCanvas3D& canvas, const Vec2d& else it->second->set_state(GLGizmoBase::Off); -#if ENABLE_SVG_ICONS top_y += scaled_stride_y; -#else - top_y += (scaled_icons_size + m_overlay_gap_y); -#endif // ENABLE_SVG_ICONS } GizmosMap::iterator it = m_gizmos.find(m_current); @@ -1216,90 +1106,62 @@ void GLGizmosManager::update_on_off_state(const GLCanvas3D& canvas, const Vec2d& it->second->set_state(GLGizmoBase::On); } -std::string GLGizmosManager::update_hover_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos) +std::string GLGizmosManager::update_hover_state(const Vec2d& mouse_pos) { std::string name = ""; if (!m_enabled) return name; - const Selection& selection = canvas.get_selection(); - - float cnv_h = (float)canvas.get_canvas_size().get_height(); + float cnv_h = (float)m_parent.get_canvas_size().get_height(); float height = get_total_overlay_height(); -#if ENABLE_SVG_ICONS float scaled_icons_size = m_overlay_icons_size * m_overlay_scale; float scaled_border = m_overlay_border * m_overlay_scale; float scaled_gap_y = m_overlay_gap_y * m_overlay_scale; float scaled_stride_y = scaled_icons_size + scaled_gap_y; float top_y = 0.5f * (cnv_h - height) + scaled_border; -#else - float top_y = 0.5f * (cnv_h - height) + m_overlay_border; - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_overlay_icons_scale; -#endif // ENABLE_SVG_ICONS for (GizmosMap::iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { if ((it->second == nullptr) || !it->second->is_selectable()) continue; -#if ENABLE_SVG_ICONS bool inside = (scaled_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= scaled_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size); -#else - bool inside = (m_overlay_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= m_overlay_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size); -#endif // ENABLE_SVG_ICONS if (inside) name = it->second->get_name(); - if (it->second->is_activable(selection) && (it->second->get_state() != GLGizmoBase::On)) + if (it->second->is_activable() && (it->second->get_state() != GLGizmoBase::On)) it->second->set_state(inside ? GLGizmoBase::Hover : GLGizmoBase::Off); -#if ENABLE_SVG_ICONS top_y += scaled_stride_y; -#else - top_y += (scaled_icons_size + m_overlay_gap_y); -#endif // ENABLE_SVG_ICONS } return name; } -bool GLGizmosManager::overlay_contains_mouse(const GLCanvas3D& canvas, const Vec2d& mouse_pos) const +bool GLGizmosManager::overlay_contains_mouse(const Vec2d& mouse_pos) const { if (!m_enabled) return false; - float cnv_h = (float)canvas.get_canvas_size().get_height(); + float cnv_h = (float)m_parent.get_canvas_size().get_height(); float height = get_total_overlay_height(); -#if ENABLE_SVG_ICONS float scaled_icons_size = m_overlay_icons_size * m_overlay_scale; float scaled_border = m_overlay_border * m_overlay_scale; float scaled_gap_y = m_overlay_gap_y * m_overlay_scale; float scaled_stride_y = scaled_icons_size + scaled_gap_y; float top_y = 0.5f * (cnv_h - height) + scaled_border; -#else - float top_y = 0.5f * (cnv_h - height) + m_overlay_border; - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_overlay_icons_scale; -#endif // ENABLE_SVG_ICONS for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { if ((it->second == nullptr) || !it->second->is_selectable()) continue; -#if ENABLE_SVG_ICONS if ((scaled_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= scaled_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size)) -#else - if ((m_overlay_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= m_overlay_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size)) -#endif // ENABLE_SVG_ICONS return true; -#if ENABLE_SVG_ICONS top_y += scaled_stride_y; -#else - top_y += (scaled_icons_size + m_overlay_gap_y); -#endif // ENABLE_SVG_ICONS } return false; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index 1e42a29e6..803613ec7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -4,14 +4,13 @@ #include "slic3r/GUI/GLTexture.hpp" #include "slic3r/GUI/GLToolbar.hpp" #include "slic3r/GUI/Gizmos/GLGizmos.hpp" +#include "libslic3r/ObjectID.hpp" #include namespace Slic3r { namespace GUI { -class Selection; -class GLGizmoBase; class GLCanvas3D; class ClippingPlane; @@ -43,12 +42,10 @@ public: float get_height() const { return m_top - m_bottom; } }; -class GLGizmosManager +class GLGizmosManager : public Slic3r::ObjectBase { public: -#if ENABLE_SVG_ICONS static const float Default_Icons_Size; -#endif // ENABLE_SVG_ICONS enum EType : unsigned char { @@ -63,24 +60,17 @@ public: }; private: + GLCanvas3D& m_parent; bool m_enabled; typedef std::map GizmosMap; GizmosMap m_gizmos; -#if ENABLE_SVG_ICONS mutable GLTexture m_icons_texture; mutable bool m_icons_texture_dirty; -#else - ItemsIconsTexture m_icons_texture; -#endif // ENABLE_SVG_ICONS BackgroundTexture m_background_texture; EType m_current; -#if ENABLE_SVG_ICONS float m_overlay_icons_size; float m_overlay_scale; -#else - float m_overlay_icons_scale; -#endif // ENABLE_SVG_ICONS float m_overlay_border; float m_overlay_gap_y; @@ -99,38 +89,67 @@ private: MouseCapture m_mouse_capture; std::string m_tooltip; + bool m_serializing; public: - GLGizmosManager(); + explicit GLGizmosManager(GLCanvas3D& parent); ~GLGizmosManager(); - bool init(GLCanvas3D& parent); + bool init(); + + template + void load(Archive& ar) + { + if (!m_enabled) + return; + + m_serializing = true; + + ar(m_current); + + GLGizmoBase* curr = get_current(); + if (curr != nullptr) + { + curr->set_state(GLGizmoBase::On); + curr->load(ar); + } + } + + template + void save(Archive& ar) const + { + if (!m_enabled) + return; + + ar(m_current); + + GLGizmoBase* curr = get_current(); + if (curr != nullptr) + curr->save(ar); + } bool is_enabled() const { return m_enabled; } void set_enabled(bool enable) { m_enabled = enable; } -#if ENABLE_SVG_ICONS void set_overlay_icon_size(float size); -#endif // ENABLE_SVG_ICONS void set_overlay_scale(float scale); - void refresh_on_off_state(const Selection& selection); + void refresh_on_off_state(); void reset_all_states(); void set_hover_id(int id); void enable_grabber(EType type, unsigned int id, bool enable); - void update(const Linef3& mouse_ray, const Selection& selection, const Point* mouse_pos = nullptr); - void update_data(GLCanvas3D& canvas); + void update(const Linef3& mouse_ray, const Point& mouse_pos); + void update_data(); - Rect get_reset_rect_viewport(const GLCanvas3D& canvas) const; EType get_current_type() const { return m_current; } bool is_running() const; - bool handle_shortcut(int key, const Selection& selection); + bool handle_shortcut(int key); bool is_dragging() const; - void start_dragging(const Selection& selection); + void start_dragging(); void stop_dragging(); Vec3d get_displacement() const; @@ -147,43 +166,48 @@ public: void set_flattening_data(const ModelObject* model_object); - void set_sla_support_data(ModelObject* model_object, const Selection& selection); + void set_sla_support_data(ModelObject* model_object); bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position = Vec2d::Zero(), bool shift_down = false, bool alt_down = false, bool control_down = false); ClippingPlane get_sla_clipping_plane() const; - void render_current_gizmo(const Selection& selection) const; - void render_current_gizmo_for_picking_pass(const Selection& selection) const; + void render_current_gizmo() const; + void render_current_gizmo_for_picking_pass() const; - void render_overlay(const GLCanvas3D& canvas, const Selection& selection) const; + void render_overlay() const; const std::string& get_tooltip() const { return m_tooltip; } - bool on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas); - bool on_mouse_wheel(wxMouseEvent& evt, GLCanvas3D& canvas); - bool on_char(wxKeyEvent& evt, GLCanvas3D& canvas); - bool on_key(wxKeyEvent& evt, GLCanvas3D& canvas); + bool on_mouse(wxMouseEvent& evt); + bool on_mouse_wheel(wxMouseEvent& evt); + bool on_char(wxKeyEvent& evt); + bool on_key(wxKeyEvent& evt); + + void update_after_undo_redo(); private: void reset(); - void do_render_overlay(const GLCanvas3D& canvas, const Selection& selection) const; + void do_render_overlay() const; float get_total_overlay_height() const; float get_total_overlay_width() const; GLGizmoBase* get_current() const; -#if ENABLE_SVG_ICONS bool generate_icons_texture() const; -#endif // ENABLE_SVG_ICONS - void update_on_off_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos, const Selection& selection); - std::string update_hover_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos); - bool overlay_contains_mouse(const GLCanvas3D& canvas, const Vec2d& mouse_pos) const; + void update_on_off_state(const Vec2d& mouse_pos); + std::string update_hover_state(const Vec2d& mouse_pos); + bool overlay_contains_mouse(const Vec2d& mouse_pos) const; bool grabber_contains_mouse() const; }; } // namespace GUI } // namespace Slic3r +namespace cereal +{ + template struct specialize {}; +} + #endif // slic3r_GUI_GLGizmosManager_hpp_ diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index ca1538bf7..f58266a5d 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -233,9 +233,9 @@ ImVec2 ImGuiWrapper::calc_text_size(const wxString &text) return size; } -void ImGuiWrapper::set_next_window_pos(float x, float y, int flag) +void ImGuiWrapper::set_next_window_pos(float x, float y, int flag, float pivot_x, float pivot_y) { - ImGui::SetNextWindowPos(ImVec2(x, y), (ImGuiCond)flag); + ImGui::SetNextWindowPos(ImVec2(x, y), (ImGuiCond)flag, ImVec2(pivot_x, pivot_y)); ImGui::SetNextWindowSize(ImVec2(0.0, 0.0)); } @@ -326,9 +326,9 @@ bool ImGuiWrapper::combo(const wxString& label, const std::vector& int selection_out = -1; bool res = false; - const char *selection_str = selection < options.size() ? options[selection].c_str() : ""; + const char *selection_str = selection < (int)options.size() ? options[selection].c_str() : ""; if (ImGui::BeginCombo("", selection_str)) { - for (int i = 0; i < options.size(); i++) { + for (int i = 0; i < (int)options.size(); i++) { if (ImGui::Selectable(options[i].c_str(), i == selection)) { selection_out = i; } @@ -342,6 +342,32 @@ bool ImGuiWrapper::combo(const wxString& label, const std::vector& return res; } +bool ImGuiWrapper::undo_redo_list(const ImVec2& size, const bool is_undo, bool (*items_getter)(const bool , int , const char**), int& hovered, int& selected) +{ + bool is_hovered = false; + ImGui::ListBoxHeader("", size); + + int i=0; + const char* item_text; + while (items_getter(is_undo, i, &item_text)) + { + ImGui::Selectable(item_text, i < hovered); + + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip(item_text); + hovered = i; + is_hovered = true; + } + + if (ImGui::IsItemClicked()) + selected = i; + i++; + } + + ImGui::ListBoxFooter(); + return is_hovered; +} + void ImGuiWrapper::disabled_begin(bool disabled) { wxCHECK_RET(!m_disabled, "ImGUI: Unbalanced disabled_begin() call"); diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index 0479e4743..c6550351e 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -51,7 +51,7 @@ public: ImVec2 scaled(float x, float y) const { return ImVec2(x * m_font_size, y * m_font_size); } ImVec2 calc_text_size(const wxString &text); - void set_next_window_pos(float x, float y, int flag); + void set_next_window_pos(float x, float y, int flag, float pivot_x = 0.0f, float pivot_y = 0.0f); void set_next_window_bg_alpha(float alpha); bool begin(const std::string &name, int flags = 0); @@ -67,6 +67,7 @@ public: void text(const std::string &label); void text(const wxString &label); bool combo(const wxString& label, const std::vector& options, int& selection); // Use -1 to not mark any option as selected + bool undo_redo_list(const ImVec2& size, const bool is_undo, bool (*items_getter)(const bool, int, const char**), int& hovered, int& selected); void disabled_begin(bool disabled); void disabled_end(); diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 1af658ed3..955c4b60b 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -154,6 +154,9 @@ void KBShortcutsDialog::fill_shortcuts() plater_shortcuts.push_back(Shortcut("I", L("Zoom in"))); plater_shortcuts.push_back(Shortcut("O", L("Zoom out"))); plater_shortcuts.push_back(Shortcut("ESC", L("Unselect gizmo / Clear selection"))); +#if ENABLE_RENDER_PICKING_PASS + plater_shortcuts.push_back(Shortcut("T", L("Toggle picking pass texture rendering on/off"))); +#endif // ENABLE_RENDER_PICKING_PASS m_full_shortcuts.push_back(std::make_pair(_(L("Plater Shortcuts")), std::make_pair(plater_shortcuts, szRight))); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index d800f6f38..b08df1690 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -35,6 +35,7 @@ namespace GUI { MainFrame::MainFrame() : DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE, "mainframe"), m_printhost_queue_dlg(new PrintHostQueueDialog(this)) + , m_recent_projects(9) { // Fonts were created by the DPIFrame constructor for the monitor, on which the window opened. wxGetApp().update_fonts(this); @@ -383,6 +384,40 @@ void MainFrame::init_menubar() append_menu_item(fileMenu, wxID_ANY, _(L("&Open Project")) + dots + "\tCtrl+O", _(L("Open a project file")), [this](wxCommandEvent&) { if (m_plater) m_plater->load_project(); }, menu_icon("open"), nullptr, [this](){return m_plater != nullptr; }, this); + + wxMenu* recent_projects_menu = new wxMenu(); + wxMenuItem* recent_projects_submenu = append_submenu(fileMenu, recent_projects_menu, wxID_ANY, _(L("Recent projects")), ""); + m_recent_projects.UseMenu(recent_projects_menu); + Bind(wxEVT_MENU, [this](wxCommandEvent& evt) { + size_t file_id = evt.GetId() - wxID_FILE1; + wxString filename = m_recent_projects.GetHistoryFile(file_id); + if (wxFileExists(filename)) + m_plater->load_project(filename); + else + { + wxMessageDialog msg(this, _(L("The selected project is no more available")), _(L("Error"))); + msg.ShowModal(); + + m_recent_projects.RemoveFileFromHistory(file_id); + std::vector recent_projects; + size_t count = m_recent_projects.GetCount(); + for (size_t i = 0; i < count; ++i) + { + recent_projects.push_back(into_u8(m_recent_projects.GetHistoryFile(i))); + } + wxGetApp().app_config->set_recent_projects(recent_projects); + wxGetApp().app_config->save(); + } + }, wxID_FILE1, wxID_FILE9); + + std::vector recent_projects = wxGetApp().app_config->get_recent_projects(); + for (const std::string& project : recent_projects) + { + m_recent_projects.AddFileToHistory(from_u8(project)); + } + + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(m_recent_projects.GetCount() > 0); }, recent_projects_submenu->GetId()); + append_menu_item(fileMenu, wxID_ANY, _(L("&Save Project")) + "\tCtrl+S", _(L("Save current project file")), [this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(into_path(m_plater->get_project_filename(".3mf"))); }, menu_icon("save"), nullptr, [this](){return m_plater != nullptr && can_save(); }, this); @@ -502,6 +537,14 @@ void MainFrame::init_menubar() _(L("Deletes all objects")), [this](wxCommandEvent&) { m_plater->reset_with_confirm(); }, menu_icon("delete_all_menu"), nullptr, [this](){return can_delete_all(); }, this); + editMenu->AppendSeparator(); + append_menu_item(editMenu, wxID_ANY, _(L("&Undo")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "Z", + _(L("Undo")), [this](wxCommandEvent&) { m_plater->undo(); }, + "undo", nullptr, [this](){return m_plater->can_undo(); }, this); + append_menu_item(editMenu, wxID_ANY, _(L("&Redo")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "Y", + _(L("Redo")), [this](wxCommandEvent&) { m_plater->redo(); }, + "redo", nullptr, [this](){return m_plater->can_redo(); }, this); + editMenu->AppendSeparator(); append_menu_item(editMenu, wxID_ANY, _(L("&Copy")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "C", _(L("Copy selection to clipboard")), [this](wxCommandEvent&) { m_plater->copy_selection_to_clipboard(); }, @@ -1046,6 +1089,23 @@ void MainFrame::on_config_changed(DynamicPrintConfig* config) const m_plater->on_config_change(*config); // propagate config change events to the plater } +void MainFrame::add_to_recent_projects(const wxString& filename) +{ + if (wxFileExists(filename)) + { + m_recent_projects.AddFileToHistory(filename); + std::vector recent_projects; + size_t count = m_recent_projects.GetCount(); + for (size_t i = 0; i < count; ++i) + { + recent_projects.push_back(into_u8(m_recent_projects.GetHistoryFile(i))); + } + wxGetApp().app_config->set_recent_projects(recent_projects); + wxGetApp().app_config->save(); + } +} + +// // Called after the Preferences dialog is closed and the program settings are saved. // Update the UI based on the current preferences. void MainFrame::update_ui_from_settings() diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 805b663bb..a41f33824 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -84,6 +85,8 @@ class MainFrame : public DPIFrame // vector of a MenuBar items changeable in respect to printer technology std::vector m_changeable_menu_items; + wxFileHistory m_recent_projects; + protected: virtual void on_dpi_changed(const wxRect &suggested_rect); @@ -121,6 +124,8 @@ public: // Propagate changed configuration from the Tab to the Platter and save changes to the AppConfig void on_config_changed(DynamicPrintConfig* cfg) const ; + void add_to_recent_projects(const wxString& filename); + PrintHostQueueDialog* printhost_queue_dlg() { return m_printhost_queue_dlg; } Plater* m_plater { nullptr }; diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 014932900..9feca2f3d 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -266,7 +266,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n is_sizer_field(field) ? v_sizer->Add(field->getSizer(), 0, wxEXPAND) : v_sizer->Add(field->getWindow(), 0, wxEXPAND); - return; + break;//return; } is_sizer_field(field) ? @@ -300,7 +300,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n { // extra widget for non-staticbox option group (like for the frequently used parameters on the sidebar) should be wxALIGN_RIGHT const auto v_sizer = new wxBoxSizer(wxVERTICAL); - sizer->Add(v_sizer, 1, wxEXPAND); + sizer->Add(v_sizer, option_set.size() == 1 ? 0 : 1, wxEXPAND); v_sizer->Add(extra_widget(this->ctrl_parent()), 0, wxALIGN_RIGHT); return; } @@ -320,6 +320,17 @@ Line OptionsGroup::create_single_option_line(const Option& option) const { return retval; } +void OptionsGroup::clear_fields_except_of(const std::vector left_fields) +{ + auto it = m_fields.begin(); + while (it != m_fields.end()) { + if (std::find(left_fields.begin(), left_fields.end(), it->first) == left_fields.end()) + it = m_fields.erase(it); + else + it++; + } +} + void OptionsGroup::on_set_focus(const std::string& opt_key) { if (m_set_focus != nullptr) diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index 73b2c5110..d720787b6 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -160,6 +160,14 @@ public: m_show_modified_btns = show; } + void clear_fields_except_of(const std::vector left_fields); + + void hide_labels() { + label_width = 0; + m_grid_sizer->SetCols(m_grid_sizer->GetEffectiveColsCount()-1); + static_cast(m_grid_sizer)->AddGrowableCol(!extra_column ? 0 : 1); + } + OptionsGroup( wxWindow* _parent, const wxString& title, bool is_tab_opt = false, column_t extra_clmn = nullptr) : m_parent(_parent), title(title), diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 151642613..0c7a8a3ee 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -50,6 +50,7 @@ #include "GUI_App.hpp" #include "GUI_ObjectList.hpp" #include "GUI_ObjectManipulation.hpp" +#include "GUI_ObjectLayers.hpp" #include "GUI_Utils.hpp" #include "wxExtensions.hpp" #include "MainFrame.hpp" @@ -69,6 +70,7 @@ #include "../Utils/ASCIIFolding.hpp" #include "../Utils/PrintHost.hpp" #include "../Utils/FixModelByWin10.hpp" +#include "../Utils/UndoRedo.hpp" #include // Needs to be last because reasons :-/ #include "WipeTowerDialog.hpp" @@ -244,6 +246,7 @@ wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(15 * last_selected(wxNOT_FOUND), m_em_unit(wxGetApp().em_unit()) { + SetFont(wxGetApp().normal_font()); Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &evt) { auto selected_item = this->GetSelection(); @@ -371,24 +374,36 @@ class FreqChangedParams : public OG_Settings wxSizer* m_sizer {nullptr}; std::shared_ptr m_og_sla; + std::vector m_empty_buttons; public: - FreqChangedParams(wxWindow* parent, const int label_width); + FreqChangedParams(wxWindow* parent); ~FreqChangedParams() {} wxButton* get_wiping_dialog_button() { return m_wiping_dialog_button; } wxSizer* get_sizer() override; ConfigOptionsGroup* get_og(const bool is_fff); void Show(const bool is_fff); + + void msw_rescale(); }; -FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : +void FreqChangedParams::msw_rescale() +{ + m_og->msw_rescale(); + m_og_sla->msw_rescale(); + + for (auto btn: m_empty_buttons) + btn->msw_rescale(); +} + +FreqChangedParams::FreqChangedParams(wxWindow* parent) : OG_Settings(parent, false) { DynamicPrintConfig* config = &wxGetApp().preset_bundle->prints.get_edited_preset().config; // Frequently changed parameters for FFF_technology m_og->set_config(config); - m_og->label_width = label_width == 0 ? 1 : label_width; + m_og->hide_labels(); m_og->m_on_change = [config, this](t_config_option_key opt_key, boost::any value) { Tab* tab_print = wxGetApp().get_tab(Preset::TYPE_PRINT); @@ -460,6 +475,20 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : Option option = Option(support_def, "support"); option.opt.full_width = true; line.append_option(option); + + /* Not a best solution, but + * Temporary workaround for right border alignment + */ + auto empty_widget = [this] (wxWindow* parent) { + auto sizer = new wxBoxSizer(wxHORIZONTAL); + auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_transparent.png", wxEmptyString, + wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW); + sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, int(0.3 * wxGetApp().em_unit())); + m_empty_buttons.push_back(btn); + return sizer; + }; + line.append_widget(empty_widget); + m_og->append_line(line); @@ -468,7 +497,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : option = m_og->get_option("fill_density"); option.opt.label = L("Infill"); option.opt.width = 7/*6*/; - option.opt.sidetext = " "; + option.opt.sidetext = " "; line.append_option(option); m_brim_width = config->opt_float("brim_width"); @@ -479,13 +508,14 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : def.gui_type = ""; def.set_default_value(new ConfigOptionBool{ m_brim_width > 0.0 ? true : false }); option = Option(def, "brim"); - option.opt.sidetext = " "; + option.opt.sidetext = ""; line.append_option(option); auto wiping_dialog_btn = [config, this](wxWindow* parent) { m_wiping_dialog_button = new wxButton(parent, wxID_ANY, _(L("Purging volumes")) + dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); + m_wiping_dialog_button->SetFont(wxGetApp().normal_font()); auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(m_wiping_dialog_button); + sizer->Add(m_wiping_dialog_button, 0, wxALIGN_CENTER_VERTICAL); m_wiping_dialog_button->Bind(wxEVT_BUTTON, ([parent](wxCommandEvent& e) { auto &config = wxGetApp().preset_bundle->project_config; @@ -502,6 +532,13 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : wxPostEvent(parent, SimpleEvent(EVT_SCHEDULE_BACKGROUND_PROCESS, parent)); } })); + + auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_transparent.png", wxEmptyString, + wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW); + sizer->Add(btn , 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, + int(0.3 * wxGetApp().em_unit())); + m_empty_buttons.push_back(btn); + return sizer; }; line.append_widget(wiping_dialog_btn); @@ -511,9 +548,9 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : // Frequently changed parameters for SLA_technology m_og_sla = std::make_shared(parent, ""); + m_og_sla->hide_labels(); DynamicPrintConfig* config_sla = &wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; m_og_sla->set_config(config_sla); - m_og_sla->label_width = label_width == 0 ? 1 : label_width; m_og_sla->m_on_change = [config_sla, this](t_config_option_key opt_key, boost::any value) { Tab* tab = wxGetApp().get_tab(Preset::TYPE_SLA_PRINT); @@ -551,7 +588,8 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : support_def_sla.enum_labels.erase(support_def_sla.enum_labels.begin() + 2); option = Option(support_def_sla, "support"); option.opt.full_width = true; - line.append_option(option); + line.append_option(option); + line.append_widget(empty_widget); m_og_sla->append_line(line); line = Line{ "", "" }; @@ -614,10 +652,11 @@ struct Sidebar::priv PresetComboBox *combo_printer; wxBoxSizer *sizer_params; - FreqChangedParams *frequently_changed_parameters; - ObjectList *object_list; - ObjectManipulation *object_manipulation; - ObjectSettings *object_settings; + FreqChangedParams *frequently_changed_parameters{ nullptr }; + ObjectList *object_list{ nullptr }; + ObjectManipulation *object_manipulation{ nullptr }; + ObjectSettings *object_settings{ nullptr }; + ObjectLayers *object_layers{ nullptr }; ObjectInfo *object_info; SlicedInfo *sliced_info; @@ -626,10 +665,26 @@ struct Sidebar::priv wxButton *btn_send_gcode; priv(Plater *plater) : plater(plater) {} + ~priv(); void show_preset_comboboxes(); }; +Sidebar::priv::~priv() +{ + if (object_manipulation != nullptr) + delete object_manipulation; + + if (object_settings != nullptr) + delete object_settings; + + if (frequently_changed_parameters != nullptr) + delete frequently_changed_parameters; + + if (object_layers != nullptr) + delete object_layers; +} + void Sidebar::priv::show_preset_comboboxes() { const bool showSLA = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA; @@ -715,13 +770,12 @@ Sidebar::Sidebar(Plater *parent) init_combo(&p->combo_printer, _(L("Printer")), Preset::TYPE_PRINTER, false); const int margin_5 = int(0.5*wxGetApp().em_unit());// 5; - const int margin_10 = 10;//int(1.5*wxGetApp().em_unit());// 15; p->sizer_params = new wxBoxSizer(wxVERTICAL); // Frequently changed parameters - p->frequently_changed_parameters = new FreqChangedParams(p->scrolled, 0/*label_width*/); - p->sizer_params->Add(p->frequently_changed_parameters->get_sizer(), 0, wxEXPAND | wxTOP | wxBOTTOM, margin_10); + p->frequently_changed_parameters = new FreqChangedParams(p->scrolled); + p->sizer_params->Add(p->frequently_changed_parameters->get_sizer(), 0, wxEXPAND | wxTOP | wxBOTTOM, wxOSX ? 1 : margin_5); // Object List p->object_list = new ObjectList(p->scrolled); @@ -736,6 +790,11 @@ Sidebar::Sidebar(Plater *parent) p->object_settings = new ObjectSettings(p->scrolled); p->object_settings->Hide(); p->sizer_params->Add(p->object_settings->get_sizer(), 0, wxEXPAND | wxTOP, margin_5); + + // Object Layers + p->object_layers = new ObjectLayers(p->scrolled); + p->object_layers->Hide(); + p->sizer_params->Add(p->object_layers->get_sizer(), 0, wxEXPAND | wxTOP, margin_5); // Info boxes p->object_info = new ObjectInfo(p->scrolled); @@ -923,12 +982,11 @@ void Sidebar::msw_rescale() // ... then refill them and set min size to correct layout of the sidebar update_all_preset_comboboxes(); - p->frequently_changed_parameters->get_og(true)->msw_rescale(); - p->frequently_changed_parameters->get_og(false)->msw_rescale(); - + p->frequently_changed_parameters->msw_rescale(); p->object_list->msw_rescale(); p->object_manipulation->msw_rescale(); p->object_settings->msw_rescale(); + p->object_layers->msw_rescale(); p->object_info->msw_rescale(); @@ -950,6 +1008,11 @@ ObjectSettings* Sidebar::obj_settings() return p->object_settings; } +ObjectLayers* Sidebar::obj_layers() +{ + return p->object_layers; +} + wxScrolledWindow* Sidebar::scrolled_panel() { return p->scrolled; @@ -1103,10 +1166,20 @@ void Sidebar::show_sliced_info_sizer(const bool show) if (ps.estimated_normal_print_time != "N/A") { new_label += wxString::Format("\n - %s", _(L("normal mode"))); info_text += wxString::Format("\n%s", ps.estimated_normal_print_time); + for (int i = (int)ps.estimated_normal_color_print_times.size() - 1; i >= 0; --i) + { + new_label += wxString::Format("\n - %s%d", _(L("Color ")), i + 1); + info_text += wxString::Format("\n%s", ps.estimated_normal_color_print_times[i]); + } } if (ps.estimated_silent_print_time != "N/A") { new_label += wxString::Format("\n - %s", _(L("stealth mode"))); info_text += wxString::Format("\n%s", ps.estimated_silent_print_time); + for (int i = (int)ps.estimated_normal_color_print_times.size() - 1; i >= 0; --i) + { + new_label += wxString::Format("\n - %s%d", _(L("Color ")), i + 1); + info_text += wxString::Format("\n%s", ps.estimated_normal_color_print_times[i]); + } } p->sliced_info->SetTextAndShow(siEstimatedTime, info_text, new_label); } @@ -1182,6 +1255,8 @@ const std::regex PlaterDropTarget::pattern_drop(".*[.](stl|obj|amf|3mf|prusa)", bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames) { + plater->take_snapshot(_(L("Load Files"))); + std::vector paths; for (const auto &filename : filenames) { @@ -1247,7 +1322,12 @@ struct Plater::priv Slic3r::Model model; PrinterTechnology printer_technology = ptFFF; Slic3r::GCodePreviewData gcode_preview_data; - + Slic3r::UndoRedo::Stack undo_redo_stack; + int m_prevent_snapshots = 0; /* Used for avoid of excess "snapshoting". + * Like for "delete selected" or "set numbers of copies" + * we should call tack_snapshot just ones + * instead of calls for each action separately + * */ // GUI elements wxSizer* panel_sizer{ nullptr }; wxPanel* current_panel{ nullptr }; @@ -1633,6 +1713,7 @@ struct Plater::priv static const std::regex pattern_prusa; priv(Plater *q, MainFrame *main_frame); + ~priv(); void update(bool force_full_scene_refresh = false); void select_view(const std::string& direction); @@ -1669,6 +1750,23 @@ struct Plater::priv void split_object(); void split_volume(); void scale_selection_to_fit_print_volume(); + + void take_snapshot(const std::string& snapshot_name) + { + if (this->m_prevent_snapshots > 0) + return; + assert(this->m_prevent_snapshots >= 0); + this->undo_redo_stack.take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), view3D->get_canvas3d()->get_gizmos_manager()); + } + void take_snapshot(const wxString& snapshot_name) { this->take_snapshot(std::string(snapshot_name.ToUTF8().data())); } + int get_active_snapshot_index(); + void undo(); + void redo(); + void undo_to(size_t time_to_load); + void redo_to(size_t time_to_load); + void suppress_snapshots() { this->m_prevent_snapshots++; } + void allow_snapshots() { this->m_prevent_snapshots--; } + bool background_processing_enabled() const { return this->get_config("background_processing") == "1"; } void update_print_volume_state(); void schedule_background_process(); @@ -1759,6 +1857,7 @@ private: void update_fff_scene(); void update_sla_scene(); + void update_after_undo_redo(); // path to project file stored with no extension wxString m_project_filename; @@ -1786,11 +1885,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) })) , sidebar(new Sidebar(q)) , delayed_scene_refresh(false) -#if ENABLE_SVG_ICONS , view_toolbar(GLToolbar::Radio, "View") -#else - , view_toolbar(GLToolbar::Radio) -#endif // ENABLE_SVG_ICONS , m_project_filename(wxEmptyString) { this->q->SetFont(Slic3r::GUI::wxGetApp().normal_font()); @@ -1865,6 +1960,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, &priv::on_3dcanvas_mouse_dragging_finished, this); view3D_canvas->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); }); view3D_canvas->Bind(EVT_GLCANVAS_RESETGIZMOS, [this](SimpleEvent&) { reset_all_gizmos(); }); + view3D_canvas->Bind(EVT_GLCANVAS_UNDO, [this](SimpleEvent&) { this->undo(); }); + view3D_canvas->Bind(EVT_GLCANVAS_REDO, [this](SimpleEvent&) { this->redo(); }); // 3DScene/Toolbar: view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this); @@ -1903,6 +2000,15 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) // updates camera type from .ini file camera.set_type(get_config("use_perspective_camera")); + + // Initialize the Undo / Redo stack with a first snapshot. + this->take_snapshot(_(L("New Project"))); +} + +Plater::priv::~priv() +{ + if (config != nullptr) + delete config; } void Plater::priv::update(bool force_full_scene_refresh) @@ -2267,7 +2373,7 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mode // the size of the object is too big -> this could lead to overflow when moving to clipper coordinates, // so scale down the mesh double inv = 1. / max_ratio; - object->scale_mesh(Vec3d(inv, inv, inv)); + object->scale_mesh_after_creation(Vec3d(inv, inv, inv)); object->origin_translation = Vec3d::Zero(); object->center_around_origin(); scaled_down = true; @@ -2280,9 +2386,6 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mode } object->ensure_on_bed(); - - // print.auto_assign_extruders(object); - // print.add_model_object(object); } #ifdef AUTOPLACEMENT_ON_LOAD @@ -2449,12 +2552,15 @@ void Plater::priv::object_list_changed() void Plater::priv::select_all() { +// this->take_snapshot(_(L("Select All"))); + view3D->select_all(); this->sidebar->obj_list()->update_selections(); } void Plater::priv::deselect_all() { +// this->take_snapshot(_(L("Deselect All"))); view3D->deselect_all(); } @@ -2475,7 +2581,8 @@ void Plater::priv::remove(size_t obj_idx) void Plater::priv::delete_object_from_model(size_t obj_idx) -{ +{ + this->take_snapshot(_(L("Delete Object"))); model.delete_object(obj_idx); update(); object_list_changed(); @@ -2483,6 +2590,8 @@ void Plater::priv::delete_object_from_model(size_t obj_idx) void Plater::priv::reset() { + this->take_snapshot(_(L("Reset Project"))); + set_project_filename(wxEmptyString); // Prevent toolpaths preview from rendering while we modify the Print object @@ -2508,17 +2617,20 @@ void Plater::priv::reset() void Plater::priv::mirror(Axis axis) { + this->take_snapshot(_(L("Mirror"))); view3D->mirror_selection(axis); } void Plater::priv::arrange() { + this->take_snapshot(_(L("Arrange"))); m_ui_jobs.start(Jobs::Arrange); } // This method will find an optimal orientation for the currently selected item // Very similar in nature to the arrange method above... void Plater::priv::sla_optimize_rotation() { + this->take_snapshot(_(L("Optimize Rotation"))); m_ui_jobs.start(Jobs::Rotoptimize); } @@ -2671,6 +2783,8 @@ void Plater::priv::split_object() Slic3r::GUI::warning_catcher(q, _(L("The selected object couldn't be split because it contains only one part."))); else { + this->take_snapshot(_(L("Split to Objects"))); + unsigned int counter = 1; for (ModelObject* m : new_objects) m->name = current_model_object->name + "_" + std::to_string(counter++); @@ -2909,6 +3023,8 @@ void Plater::priv::update_sla_scene() void Plater::priv::reload_from_disk() { + this->take_snapshot(_(L("Reload from Disk"))); + const auto &selection = get_selection(); const auto obj_orig_idx = selection.get_object_idx(); if (selection.is_wipe_tower() || obj_orig_idx == -1) { return; } @@ -2942,6 +3058,9 @@ void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* = { if (obj_idx < 0) return; + + this->take_snapshot(_(L("Fix Throught NetFabb"))); + fix_model_by_win10_sdk_gui(*model.objects[obj_idx], vol_idx); this->update(); this->object_list_changed(); @@ -3049,8 +3168,14 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) // update plater with new config wxGetApp().plater()->on_config_change(wxGetApp().preset_bundle->full_config()); + /* Settings list can be changed after printer preset changing, so + * update all settings items for all item had it. + * Furthermore, Layers editing is implemented only for FFF printers + * and for SLA presets they should be deleted + */ if (preset_type == Preset::TYPE_PRINTER) - wxGetApp().obj_list()->update_settings_items(); +// wxGetApp().obj_list()->update_settings_items(); + wxGetApp().obj_list()->update_object_list_by_printer_technology(); } void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) @@ -3181,6 +3306,8 @@ void Plater::priv::on_action_layersediting(SimpleEvent&) void Plater::priv::on_object_select(SimpleEvent& evt) { +// this->take_snapshot(_(L("Object Selection"))); + wxGetApp().obj_list()->update_selections(); selection_changed(); } @@ -3319,6 +3446,9 @@ void Plater::priv::set_project_filename(const wxString& filename) m_project_filename = from_path(full_path); wxGetApp().mainframe->update_title(); + + if (!filename.empty()) + wxGetApp().mainframe->add_to_recent_projects(filename); } bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/) @@ -3395,6 +3525,10 @@ bool Plater::priv::complit_init_object_menu() [this]() { return can_split() && wxGetApp().get_mode() > comSimple; }, q); object_menu.AppendSeparator(); + // Layers Editing for object + sidebar->obj_list()->append_menu_item_layers_editing(&object_menu); + object_menu.AppendSeparator(); + // "Add (volumes)" popupmenu will be added later in append_menu_items_add_volume() return true; @@ -3429,12 +3563,6 @@ bool Plater::priv::complit_init_part_menu() void Plater::priv::init_view_toolbar() { -#if !ENABLE_SVG_ICONS - ItemsIconsTexture::Metadata icons_data; - icons_data.filename = "view_toolbar.png"; - icons_data.icon_size = 64; -#endif // !ENABLE_SVG_ICONS - BackgroundTexture::Metadata background_data; background_data.filename = "toolbar_background.png"; background_data.left = 16; @@ -3442,11 +3570,7 @@ void Plater::priv::init_view_toolbar() background_data.right = 16; background_data.bottom = 16; -#if ENABLE_SVG_ICONS if (!view_toolbar.init(background_data)) -#else - if (!view_toolbar.init(icons_data, background_data)) -#endif // ENABLE_SVG_ICONS return; view_toolbar.set_layout_orientation(GLToolbar::Layout::Bottom); @@ -3456,24 +3580,18 @@ void Plater::priv::init_view_toolbar() GLToolbarItem::Data item; item.name = "3D"; -#if ENABLE_SVG_ICONS item.icon_filename = "editor.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("3D editor view")) + " [" + GUI::shortkey_ctrl_prefix() + "5]"; item.sprite_id = 0; - item.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_3D)); }; - item.is_toggable = false; + item.left.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_3D)); }; if (!view_toolbar.add_item(item)) return; item.name = "Preview"; -#if ENABLE_SVG_ICONS item.icon_filename = "preview.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Preview")) + " [" + GUI::shortkey_ctrl_prefix() + "6]"; item.sprite_id = 1; - item.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_PREVIEW)); }; - item.is_toggable = false; + item.left.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_PREVIEW)); }; if (!view_toolbar.add_item(item)) return; @@ -3603,6 +3721,52 @@ void Plater::priv::show_action_buttons(const bool is_ready_to_slice) const } } +int Plater::priv::get_active_snapshot_index() +{ + const size_t& active_snapshot_time = this->undo_redo_stack.active_snapshot_time(); + const std::vector& ss_stack = this->undo_redo_stack.snapshots(); + const auto it = std::lower_bound(ss_stack.begin(), ss_stack.end(), UndoRedo::Snapshot(active_snapshot_time)); + return it - ss_stack.begin(); +} + +void Plater::priv::undo() +{ + if (this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection(), this->view3D->get_canvas3d()->get_gizmos_manager())) + this->update_after_undo_redo(); +} + +void Plater::priv::redo() +{ + if (this->undo_redo_stack.redo(model, this->view3D->get_canvas3d()->get_gizmos_manager())) + this->update_after_undo_redo(); +} + +void Plater::priv::undo_to(size_t time_to_load) +{ + if (this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection(), this->view3D->get_canvas3d()->get_gizmos_manager(), time_to_load)) + this->update_after_undo_redo(); +} + +void Plater::priv::redo_to(size_t time_to_load) +{ + if (this->undo_redo_stack.redo(model, this->view3D->get_canvas3d()->get_gizmos_manager(), time_to_load)) + this->update_after_undo_redo(); +} + +void Plater::priv::update_after_undo_redo() +{ + this->view3D->get_canvas3d()->get_selection().clear(); + this->update(false); // update volumes from the deserializd model + //YS_FIXME update obj_list from the deserialized model (maybe store ObjectIDs into the tree?) (no selections at this point of time) + this->view3D->get_canvas3d()->get_selection().set_deserialized(GUI::Selection::EMode(this->undo_redo_stack.selection_deserialized().mode), this->undo_redo_stack.selection_deserialized().volumes_and_instances); + this->view3D->get_canvas3d()->get_gizmos_manager().update_after_undo_redo(); + + wxGetApp().obj_list()->update_after_undo_redo(); + + //FIXME what about the state of the manipulators? + //FIXME what about the focus? Cursor in the side panel? +} + void Sidebar::set_btn_label(const ActionButtonType btn_type, const wxString& label) const { switch (btn_type) @@ -3640,17 +3804,23 @@ void Plater::new_project() void Plater::load_project() { + this->take_snapshot(_(L("Load Project"))); + wxString input_file; wxGetApp().load_project(this, input_file); + load_project(input_file); +} - if (input_file.empty()) +void Plater::load_project(const wxString& filename) +{ + if (filename.empty()) return; p->reset(); - p->set_project_filename(input_file); + p->set_project_filename(filename); std::vector input_paths; - input_paths.push_back(into_path(input_file)); + input_paths.push_back(into_path(filename)); load_files(input_paths); } @@ -3661,6 +3831,8 @@ void Plater::add_model() if (input_files.empty()) return; + this->take_snapshot(_(L("Add object(s)"))); + std::vector input_paths; for (const auto &file : input_files) { input_paths.push_back(into_path(file)); @@ -3718,13 +3890,18 @@ void Plater::delete_object_from_model(size_t obj_idx) { p->delete_object_from_mo void Plater::remove_selected() { + this->take_snapshot(_(L("Delete Selected Objects"))); + this->suppress_snapshots(); this->p->view3D->delete_selected(); + this->allow_snapshots(); } void Plater::increase_instances(size_t num) { if (! can_increase_instances()) { return; } + this->take_snapshot(_(L("Increase Instances"))); + int obj_idx = p->get_selected_object_idx(); ModelObject* model_object = p->model.objects[obj_idx]; @@ -3759,6 +3936,8 @@ void Plater::decrease_instances(size_t num) { if (! can_decrease_instances()) { return; } + this->take_snapshot(_(L("Decrease Instances"))); + int obj_idx = p->get_selected_object_idx(); ModelObject* model_object = p->model.objects[obj_idx]; @@ -3793,11 +3972,16 @@ void Plater::set_number_of_copies(/*size_t num*/) if (num < 0) return; + this->take_snapshot(wxString::Format(_(L("Set numbers of copies to %d")), num)); + this->suppress_snapshots(); + int diff = (int)num - (int)model_object->instances.size(); if (diff > 0) increase_instances(diff); else if (diff < 0) decrease_instances(-diff); + + this->allow_snapshots(); } bool Plater::is_selection_empty() const @@ -3821,6 +4005,8 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_uppe return; } + this->take_snapshot(_(L("Gizmo-Cut"))); + wxBusyCursor wait; const auto new_objects = object->cut(instance_idx, z, keep_upper, keep_lower, rotate_lower); @@ -4047,6 +4233,9 @@ void Plater::reslice() } else if (!p->background_process.empty() && !p->background_process.idle()) p->show_action_buttons(true); + + // update type of preview + p->preview->update_view_type(); } void Plater::reslice_SLA_supports(const ModelObject &object) @@ -4107,6 +4296,45 @@ void Plater::send_gcode() } } +void Plater::take_snapshot(const std::string &snapshot_name) { p->take_snapshot(snapshot_name); } +void Plater::take_snapshot(const wxString &snapshot_name) { p->take_snapshot(snapshot_name); } +void Plater::suppress_snapshots() { p->suppress_snapshots(); } +void Plater::allow_snapshots() { p->allow_snapshots(); } +void Plater::undo() { p->undo(); } +void Plater::redo() { p->redo(); } +void Plater::undo_to(int selection) +{ + if (selection == 0) { + p->undo(); + return; + } + + const int idx = p->get_active_snapshot_index() - selection - 1; + p->undo_to(p->undo_redo_stack.snapshots()[idx].timestamp); +} +void Plater::redo_to(int selection) +{ + if (selection == 0) { + p->redo(); + return; + } + + const int idx = p->get_active_snapshot_index() + selection + 1; + p->redo_to(p->undo_redo_stack.snapshots()[idx].timestamp); +} +bool Plater::undo_redo_string_getter(const bool is_undo, int idx, const char** out_text) +{ + const std::vector& ss_stack = p->undo_redo_stack.snapshots(); + const int idx_in_ss_stack = p->get_active_snapshot_index() + (is_undo ? -(++idx) : idx); + + if (0 < idx_in_ss_stack && idx_in_ss_stack < ss_stack.size() - 1) { + *out_text = ss_stack[idx_in_ss_stack].name.c_str(); + return true; + } + + return false; +} + void Plater::on_extruders_change(int num_extruders) { auto& choices = sidebar().combos_filament(); @@ -4332,8 +4560,11 @@ void Plater::copy_selection_to_clipboard() void Plater::paste_from_clipboard() { - if (can_paste_from_clipboard()) - p->view3D->get_canvas3d()->get_selection().paste_from_clipboard(); + if (!can_paste_from_clipboard()) + return; + + this->take_snapshot(_(L("Paste From Clipboard"))); + p->view3D->get_canvas3d()->get_selection().paste_from_clipboard(); } void Plater::msw_rescale() @@ -4350,6 +4581,11 @@ void Plater::msw_rescale() GetParent()->Layout(); } +const Camera& Plater::get_camera() const +{ + return p->camera; +} + bool Plater::can_delete() const { return p->can_delete(); } bool Plater::can_delete_all() const { return p->can_delete_all(); } bool Plater::can_increase_instances() const { return p->can_increase_instances(); } @@ -4393,6 +4629,16 @@ bool Plater::can_copy_to_clipboard() const return true; } +bool Plater::can_undo() const +{ + return p->undo_redo_stack.has_undo_snapshot(); +} + +bool Plater::can_redo() const +{ + return p->undo_redo_stack.has_redo_snapshot(); +} + SuppressBackgroundProcessingUpdate::SuppressBackgroundProcessingUpdate() : m_was_running(wxGetApp().plater()->is_background_process_running()) { diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 5b1347891..e73f88ef3 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -33,6 +33,7 @@ class MainFrame; class ConfigOptionsGroup; class ObjectManipulation; class ObjectSettings; +class ObjectLayers; class ObjectList; class GLCanvas3D; @@ -93,6 +94,7 @@ public: ObjectManipulation* obj_manipul(); ObjectList* obj_list(); ObjectSettings* obj_settings(); + ObjectLayers* obj_layers(); wxScrolledWindow* scrolled_panel(); wxPanel* presets_panel(); @@ -136,6 +138,7 @@ public: void new_project(); void load_project(); + void load_project(const wxString& filename); void add_model(); void extract_config_from_project(); @@ -181,6 +184,16 @@ public: void fix_through_netfabb(const int obj_idx, const int vol_idx = -1); void send_gcode(); + void take_snapshot(const std::string &snapshot_name); + void take_snapshot(const wxString &snapshot_name); + void suppress_snapshots(); + void allow_snapshots(); + void undo(); + void redo(); + void undo_to(int selection); + void redo_to(int selection); + bool undo_redo_string_getter(const bool is_undo, int idx, const char** out_text); + void on_extruders_change(int extruders_count); void on_config_change(const DynamicPrintConfig &config); // On activating the parent window. @@ -215,9 +228,13 @@ public: bool can_layers_editing() const; bool can_paste_from_clipboard() const; bool can_copy_to_clipboard() const; + bool can_undo() const; + bool can_redo() const; void msw_rescale(); + const Camera& get_camera() const; + private: struct priv; std::unique_ptr p; diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 7192d485c..3d467d7f8 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -461,6 +461,7 @@ const std::vector& Preset::sla_print_options() "support_pillar_widening_factor", "support_base_diameter", "support_base_height", + "support_base_safety_distance", "support_critical_angle", "support_max_bridge_length", "support_max_pillar_link_distance", @@ -474,6 +475,10 @@ const std::vector& Preset::sla_print_options() "pad_max_merge_distance", "pad_edge_radius", "pad_wall_slope", + "pad_object_gap", + "pad_object_connector_stride", + "pad_object_connector_width", + "pad_object_connector_penetration", "output_filename_format", "default_sla_print_profile", "compatible_printers", @@ -824,11 +829,25 @@ const Preset* PresetCollection::get_selected_preset_parent() const if (this->get_selected_idx() == -1) // This preset collection has no preset activated yet. Only the get_edited_preset() is valid. return nullptr; - const std::string &inherits = this->get_edited_preset().inherits(); +// const std::string &inherits = this->get_edited_preset().inherits(); +// if (inherits.empty()) +// return this->get_selected_preset().is_system ? &this->get_selected_preset() : nullptr; + + std::string inherits = this->get_edited_preset().inherits(); if (inherits.empty()) - return this->get_selected_preset().is_system ? &this->get_selected_preset() : nullptr; + { + if (this->get_selected_preset().is_system || this->get_selected_preset().is_default) + return &this->get_selected_preset(); + if (this->get_selected_preset().is_external) + return nullptr; + + inherits = m_type != Preset::Type::TYPE_PRINTER ? "- default -" : + this->get_edited_preset().printer_technology() == ptFFF ? + "- default FFF -" : "- default SLA -" ; + } + const Preset* preset = this->find_preset(inherits, false); - return (preset == nullptr || preset->is_default || preset->is_external) ? nullptr : preset; + return (preset == nullptr/* || preset->is_default*/ || preset->is_external) ? nullptr : preset; } const Preset* PresetCollection::get_preset_parent(const Preset& child) const diff --git a/src/slic3r/GUI/PresetBundle.cpp b/src/slic3r/GUI/PresetBundle.cpp index b28cb2eda..00c1f8168 100644 --- a/src/slic3r/GUI/PresetBundle.cpp +++ b/src/slic3r/GUI/PresetBundle.cpp @@ -1366,7 +1366,7 @@ void PresetBundle::export_configbundle(const std::string &path, bool export_syst continue; c << std::endl << "[" << presets->section_name() << ":" << preset.name << "]" << std::endl; for (const std::string &opt_key : preset.config.keys()) - c << opt_key << " = " << preset.config.serialize(opt_key) << std::endl; + c << opt_key << " = " << preset.config.opt_serialize(opt_key) << std::endl; } } diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 97168ee04..b990f28b8 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -6,7 +6,8 @@ #include "GUI_ObjectManipulation.hpp" #include "GUI_ObjectList.hpp" #include "Gizmos/GLGizmoBase.hpp" -#include "slic3r/GUI/3DScene.hpp" +#include "3DScene.hpp" +#include "Camera.hpp" #include @@ -99,14 +100,14 @@ void Selection::set_volumes(GLVolumePtrs* volumes) update_valid(); } -bool Selection::init(bool useVBOs) +bool Selection::init() { - if (!m_arrow.init(useVBOs)) + if (!m_arrow.init()) return false; m_arrow.set_scale(5.0 * Vec3d::Ones()); - if (!m_curved_arrow.init(useVBOs)) + if (!m_curved_arrow.init()) return false; m_curved_arrow.set_scale(5.0 * Vec3d::Ones()); @@ -311,6 +312,22 @@ void Selection::add_all() this->set_bounding_boxes_dirty(); } +void Selection::set_deserialized(EMode mode, const std::vector> &volumes_and_instances) +{ + if (! m_valid) + return; + + m_mode = mode; + for (unsigned int i : m_list) + (*m_volumes)[i]->selected = false; + m_list.clear(); + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++ i) + if (std::binary_search(volumes_and_instances.begin(), volumes_and_instances.end(), (*m_volumes)[i]->geometry_id)) + this->do_add_volume(i); + update_type(); + this->set_bounding_boxes_dirty(); +} + void Selection::clear() { if (!m_valid) @@ -331,6 +348,9 @@ void Selection::clear() // resets the cache in the sidebar wxGetApp().obj_manipul()->reset_cache(); + + // #et_FIXME fake KillFocus from sidebar + wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false); } // Update the selection based on the new instance IDs. @@ -786,6 +806,8 @@ void Selection::scale_to_fit_print_volume(const DynamicPrintConfig& config) double s = std::min(sx, std::min(sy, sz)); if (s != 1.0) { + wxGetApp().plater()->take_snapshot(_(L("Scale To Fit"))); + TransformationType type; type.set_world(); type.set_relative(); @@ -794,12 +816,12 @@ void Selection::scale_to_fit_print_volume(const DynamicPrintConfig& config) // apply scale start_dragging(); scale(s * Vec3d::Ones(), type); - wxGetApp().plater()->canvas3D()->do_scale(); + wxGetApp().plater()->canvas3D()->do_scale(L("")); // avoid storing another snapshot // center selection on print bed start_dragging(); translate(print_volume.center() - get_bounding_box().center()); - wxGetApp().plater()->canvas3D()->do_move(); + wxGetApp().plater()->canvas3D()->do_move(L("")); // avoid storing another snapshot wxGetApp().obj_manipul()->set_dirty(); } @@ -1070,61 +1092,68 @@ void Selection::render_center(bool gizmo_is_dragging) const } #endif // ENABLE_RENDER_SELECTION_CENTER -void Selection::render_sidebar_hints(const std::string& sidebar_field) const +void Selection::render_sidebar_hints(const std::string& sidebar_field, const Shader& shader) const { if (sidebar_field.empty()) return; - glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); - glsafe(::glEnable(GL_DEPTH_TEST)); + if (!boost::starts_with(sidebar_field, "layer")) + { + shader.start_using(); + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); + glsafe(::glEnable(GL_LIGHTING)); + } - glsafe(::glEnable(GL_LIGHTING)); + glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glPushMatrix()); - const Vec3d& center = get_bounding_box().center(); - - if (is_single_full_instance() && ! wxGetApp().obj_manipul()->get_world_coordinates()) + if (!boost::starts_with(sidebar_field, "layer")) { - glsafe(::glTranslated(center(0), center(1), center(2))); - if (!boost::starts_with(sidebar_field, "position")) + const Vec3d& center = get_bounding_box().center(); + + if (is_single_full_instance() && !wxGetApp().obj_manipul()->get_world_coordinates()) { - Transform3d orient_matrix = Transform3d::Identity(); - if (boost::starts_with(sidebar_field, "scale")) - orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); - else if (boost::starts_with(sidebar_field, "rotation")) + glsafe(::glTranslated(center(0), center(1), center(2))); + if (!boost::starts_with(sidebar_field, "position")) { - if (boost::ends_with(sidebar_field, "x")) + Transform3d orient_matrix = Transform3d::Identity(); + if (boost::starts_with(sidebar_field, "scale")) orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); - else if (boost::ends_with(sidebar_field, "y")) + else if (boost::starts_with(sidebar_field, "rotation")) { - const Vec3d& rotation = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation(); - if (rotation(0) == 0.0) + if (boost::ends_with(sidebar_field, "x")) orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); - else - orient_matrix.rotate(Eigen::AngleAxisd(rotation(2), Vec3d::UnitZ())); + else if (boost::ends_with(sidebar_field, "y")) + { + const Vec3d& rotation = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation(); + if (rotation(0) == 0.0) + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); + else + orient_matrix.rotate(Eigen::AngleAxisd(rotation(2), Vec3d::UnitZ())); + } } + + glsafe(::glMultMatrixd(orient_matrix.data())); } + } + else if (is_single_volume() || is_single_modifier()) + { + glsafe(::glTranslated(center(0), center(1), center(2))); + Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); + if (!boost::starts_with(sidebar_field, "position")) + orient_matrix = orient_matrix * (*m_volumes)[*m_list.begin()]->get_volume_transformation().get_matrix(true, false, true, true); glsafe(::glMultMatrixd(orient_matrix.data())); } - } - else if (is_single_volume() || is_single_modifier()) - { - glsafe(::glTranslated(center(0), center(1), center(2))); - Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); - if (!boost::starts_with(sidebar_field, "position")) - orient_matrix = orient_matrix * (*m_volumes)[*m_list.begin()]->get_volume_transformation().get_matrix(true, false, true, true); - - glsafe(::glMultMatrixd(orient_matrix.data())); - } - else - { - glsafe(::glTranslated(center(0), center(1), center(2))); - if (requires_local_axes()) + else { - Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); - glsafe(::glMultMatrixd(orient_matrix.data())); + glsafe(::glTranslated(center(0), center(1), center(2))); + if (requires_local_axes()) + { + Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); + glsafe(::glMultMatrixd(orient_matrix.data())); + } } } @@ -1136,10 +1165,16 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) const render_sidebar_scale_hints(sidebar_field); else if (boost::starts_with(sidebar_field, "size")) render_sidebar_size_hints(sidebar_field); + else if (boost::starts_with(sidebar_field, "layer")) + render_sidebar_layers_hints(sidebar_field); glsafe(::glPopMatrix()); - glsafe(::glDisable(GL_LIGHTING)); + if (!boost::starts_with(sidebar_field, "layer")) + { + glsafe(::glDisable(GL_LIGHTING)); + shader.stop_using(); + } } bool Selection::requires_local_axes() const @@ -1160,10 +1195,10 @@ void Selection::copy_to_clipboard() ModelObject* dst_object = m_clipboard.add_object(); dst_object->name = src_object->name; dst_object->input_file = src_object->input_file; - dst_object->config = src_object->config; + static_cast(dst_object->config) = static_cast(src_object->config); dst_object->sla_support_points = src_object->sla_support_points; dst_object->sla_points_status = src_object->sla_points_status; - dst_object->layer_height_ranges = src_object->layer_height_ranges; + dst_object->layer_config_ranges = src_object->layer_config_ranges; // #ys_FIXME_experiment dst_object->layer_height_profile = src_object->layer_height_profile; dst_object->origin_translation = src_object->origin_translation; @@ -1709,6 +1744,78 @@ void Selection::render_sidebar_size_hints(const std::string& sidebar_field) cons render_sidebar_scale_hints(sidebar_field); } +void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) const +{ + static const double Margin = 10.0; + + std::string field = sidebar_field; + + // extract max_z + std::string::size_type pos = field.rfind("_"); + if (pos == std::string::npos) + return; + + double max_z = std::stod(field.substr(pos + 1)); + + // extract min_z + field = field.substr(0, pos); + pos = field.rfind("_"); + if (pos == std::string::npos) + return; + + double min_z = std::stod(field.substr(pos + 1)); + + // extract type + field = field.substr(0, pos); + pos = field.rfind("_"); + if (pos == std::string::npos) + return; + + int type = std::stoi(field.substr(pos + 1)); + + const BoundingBoxf3& box = get_bounding_box(); + + const float min_x = box.min(0) - Margin; + const float max_x = box.max(0) + Margin; + const float min_y = box.min(1) - Margin; + const float max_y = box.max(1) + Margin; + + // view dependend order of rendering to keep correct transparency + bool camera_on_top = wxGetApp().plater()->get_camera().get_theta() <= 90.0f; + float z1 = camera_on_top ? min_z : max_z; + float z2 = camera_on_top ? max_z : min_z; + + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glDisable(GL_CULL_FACE)); + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + + ::glBegin(GL_QUADS); + if ((camera_on_top && (type == 1)) || (!camera_on_top && (type == 2))) + ::glColor4f(1.0f, 0.38f, 0.0f, 1.0f); + else + ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); + ::glVertex3f(min_x, min_y, z1); + ::glVertex3f(max_x, min_y, z1); + ::glVertex3f(max_x, max_y, z1); + ::glVertex3f(min_x, max_y, z1); + glsafe(::glEnd()); + + ::glBegin(GL_QUADS); + if ((camera_on_top && (type == 2)) || (!camera_on_top && (type == 1))) + ::glColor4f(1.0f, 0.38f, 0.0f, 1.0f); + else + ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); + ::glVertex3f(min_x, min_y, z2); + ::glVertex3f(max_x, min_y, z2); + ::glVertex3f(max_x, max_y, z2); + ::glVertex3f(min_x, max_y, z2); + glsafe(::glEnd()); + + glsafe(::glEnable(GL_CULL_FACE)); + glsafe(::glDisable(GL_BLEND)); +} + void Selection::render_sidebar_position_hint(Axis axis) const { m_arrow.set_color(AXES_COLOR[axis], 3); @@ -1927,6 +2034,10 @@ bool Selection::is_from_fully_selected_instance(unsigned int volume_idx) const void Selection::paste_volumes_from_clipboard() { +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ + int dst_obj_idx = get_object_idx(); if ((dst_obj_idx < 0) || ((int)m_model->objects.size() <= dst_obj_idx)) return; @@ -1968,6 +2079,9 @@ void Selection::paste_volumes_from_clipboard() } volumes.push_back(dst_volume); +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ } // keeps relative position of multivolume selections @@ -1981,10 +2095,18 @@ void Selection::paste_volumes_from_clipboard() wxGetApp().obj_list()->paste_volumes_into_list(dst_obj_idx, volumes); } + +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ } void Selection::paste_objects_from_clipboard() { +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ + std::vector object_idxs; const ModelObjectPtrs& src_objects = m_clipboard.get_objects(); for (const ModelObject* src_object : src_objects) @@ -1998,9 +2120,16 @@ void Selection::paste_objects_from_clipboard() } object_idxs.push_back(m_model->objects.size() - 1); +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ } wxGetApp().obj_list()->paste_objects_into_list(object_idxs); + +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ } } // namespace GUI diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 802f8d284..915a8c855 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -11,8 +11,8 @@ typedef class GLUquadric GLUquadricObj; #endif // ENABLE_RENDER_SELECTION_CENTER namespace Slic3r { +class Shader; namespace GUI { - class TransformationType { public: @@ -171,7 +171,7 @@ private: Vec3d dragging_center; // Map from indices of ModelObject instances in Model::objects // to a set of indices of ModelVolume instances in ModelObject::instances - // Here the index means a position inside the respective std::vector, not ModelID. + // Here the index means a position inside the respective std::vector, not ObjectID. ObjectIdxsToInstanceIdxsMap content; }; @@ -212,7 +212,7 @@ public: #endif // ENABLE_RENDER_SELECTION_CENTER void set_volumes(GLVolumePtrs* volumes); - bool init(bool useVBOs); + bool init(); bool is_enabled() const { return m_enabled; } void set_enabled(bool enable) { m_enabled = enable; } @@ -237,6 +237,9 @@ public: void add_all(); + // To be called after Undo or Redo once the volumes are updated. + void set_deserialized(EMode mode, const std::vector> &volumes_and_instances); + // Update the selection based on the new instance IDs. void instances_changed(const std::vector &instance_ids_selected); // Update the selection based on the map from old indices to new indices after m_volumes changed. @@ -302,7 +305,7 @@ public: #if ENABLE_RENDER_SELECTION_CENTER void render_center(bool gizmo_is_dragging) const; #endif // ENABLE_RENDER_SELECTION_CENTER - void render_sidebar_hints(const std::string& sidebar_field) const; + void render_sidebar_hints(const std::string& sidebar_field, const Shader& shader) const; bool requires_local_axes() const; @@ -332,6 +335,7 @@ private: void render_sidebar_rotation_hints(const std::string& sidebar_field) const; void render_sidebar_scale_hints(const std::string& sidebar_field) const; void render_sidebar_size_hints(const std::string& sidebar_field) const; + void render_sidebar_layers_hints(const std::string& sidebar_field) const; void render_sidebar_position_hint(Axis axis) const; void render_sidebar_rotation_hint(Axis axis) const; void render_sidebar_scale_hint(Axis axis) const; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 6e8b8a471..7ab564beb 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -423,7 +423,7 @@ void Tab::update_changed_ui() const ScalableBitmap *sys_icon = &m_bmp_value_lock; const ScalableBitmap *icon = &m_bmp_value_revert; - const wxColour *color = &m_sys_label_clr; + const wxColour *color = m_is_default_preset ? &m_default_text_clr : &m_sys_label_clr; const wxString *sys_tt = &m_tt_value_lock; const wxString *tt = &m_tt_value_revert; @@ -590,7 +590,7 @@ void Tab::update_changed_tree_ui() } } - const wxColor *clr = sys_page ? &m_sys_label_clr : + const wxColor *clr = sys_page ? (m_is_default_preset ? &m_default_text_clr : &m_sys_label_clr) : modified_page ? &m_modified_label_clr : &m_default_text_clr; @@ -874,11 +874,10 @@ void Tab::update_wiping_button_visibility() { return; // ys_FIXME bool wipe_tower_enabled = dynamic_cast( (m_preset_bundle->prints.get_edited_preset().config ).option("wipe_tower"))->value; bool multiple_extruders = dynamic_cast((m_preset_bundle->printers.get_edited_preset().config).option("nozzle_diameter"))->values.size() > 1; - bool single_extruder_mm = dynamic_cast( (m_preset_bundle->printers.get_edited_preset().config).option("single_extruder_multi_material"))->value; auto wiping_dialog_button = wxGetApp().sidebar().get_wiping_dialog_button(); if (wiping_dialog_button) { - wiping_dialog_button->Show(wipe_tower_enabled && multiple_extruders && single_extruder_mm); + wiping_dialog_button->Show(wipe_tower_enabled && multiple_extruders); wiping_dialog_button->GetParent()->Layout(); } } @@ -1557,6 +1556,9 @@ void TabFilament::build() }; optgroup->append_line(line); + optgroup = page->new_optgroup(_(L("Wipe tower parameters"))); + optgroup->append_single_option_line("filament_minimal_purge_on_wipe_tower"); + optgroup = page->new_optgroup(_(L("Toolchange parameters with single extruder MM printers"))); optgroup->append_single_option_line("filament_loading_speed_start"); optgroup->append_single_option_line("filament_loading_speed"); @@ -1568,7 +1570,6 @@ void TabFilament::build() optgroup->append_single_option_line("filament_cooling_moves"); optgroup->append_single_option_line("filament_cooling_initial_speed"); optgroup->append_single_option_line("filament_cooling_final_speed"); - optgroup->append_single_option_line("filament_minimal_purge_on_wipe_tower"); line = optgroup->create_single_option_line("filament_ramming_parameters");// { _(L("Ramming")), "" }; line.widget = [this](wxWindow* parent) { @@ -2584,11 +2585,14 @@ void Tab::load_current_preset() // Reload preset pages with the new configuration values. reload_config(); - m_bmp_non_system = m_presets->get_selected_preset_parent() ? &m_bmp_value_unlock : &m_bmp_white_bullet; - m_ttg_non_system = m_presets->get_selected_preset_parent() ? &m_ttg_value_unlock : &m_ttg_white_bullet_ns; - m_tt_non_system = m_presets->get_selected_preset_parent() ? &m_tt_value_unlock : &m_ttg_white_bullet_ns; + const Preset* selected_preset_parent = m_presets->get_selected_preset_parent(); + m_is_default_preset = selected_preset_parent != nullptr && selected_preset_parent->is_default; - m_undo_to_sys_btn->Enable(!preset.is_default); + m_bmp_non_system = selected_preset_parent ? &m_bmp_value_unlock : &m_bmp_white_bullet; + m_ttg_non_system = selected_preset_parent ? &m_ttg_value_unlock : &m_ttg_white_bullet_ns; + m_tt_non_system = selected_preset_parent ? &m_tt_value_unlock : &m_ttg_white_bullet_ns; + +// m_undo_to_sys_btn->Enable(!preset.is_default); #if 0 // use CallAfter because some field triggers schedule on_change calls using CallAfter, @@ -3174,18 +3178,18 @@ void Tab::fill_icon_descriptions() { m_icon_descriptions.emplace_back(&m_bmp_value_lock, L("LOCKED LOCK"), // TRN Description for "LOCKED LOCK" - L("indicates that the settings are the same as the system values for the current option group")); + L("indicates that the settings are the same as the system (or default) values for the current option group")); m_icon_descriptions.emplace_back(&m_bmp_value_unlock, L("UNLOCKED LOCK"), // TRN Description for "UNLOCKED LOCK" - L("indicates that some settings were changed and are not equal to the system values for " + L("indicates that some settings were changed and are not equal to the system (or default) values for " "the current option group.\n" "Click the UNLOCKED LOCK icon to reset all settings for current option group to " - "the system values.")); + "the system (or default) values.")); m_icon_descriptions.emplace_back(&m_bmp_white_bullet, L("WHITE BULLET"), // TRN Description for "WHITE BULLET" - L("for the left button: \tindicates a non-system preset,\n" + L("for the left button: \tindicates a non-system (or non-default) preset,\n" "for the right button: \tindicates that the settings hasn't been modified.")); m_icon_descriptions.emplace_back(&m_bmp_value_revert, L("BACK ARROW"), @@ -3198,29 +3202,14 @@ void Tab::fill_icon_descriptions() void Tab::set_tooltips_text() { -// m_undo_to_sys_btn->SetToolTip(_(L( "LOCKED LOCK icon indicates that the settings are the same as the system values " -// "for the current option group.\n" -// "UNLOCKED LOCK icon indicates that some settings were changed and are not equal " -// "to the system values for the current option group.\n" -// "WHITE BULLET icon indicates a non system preset.\n\n" -// "Click the UNLOCKED LOCK icon to reset all settings for current option group to " -// "the system values."))); -// -// m_undo_btn->SetToolTip(_(L( "WHITE BULLET icon indicates that the settings are the same as in the last saved" -// "preset for the current option group.\n" -// "BACK ARROW icon indicates that the settings were changed and are not equal to " -// "the last saved preset for the current option group.\n\n" -// "Click the BACK ARROW icon to reset all settings for the current option group to " -// "the last saved preset."))); - // --- Tooltip text for reset buttons (for whole options group) // Text to be shown on the "Revert to system" aka "Lock to system" button next to each input field. - m_ttg_value_lock = _(L("LOCKED LOCK icon indicates that the settings are the same as the system values " + m_ttg_value_lock = _(L("LOCKED LOCK icon indicates that the settings are the same as the system (or default) values " "for the current option group")); m_ttg_value_unlock = _(L("UNLOCKED LOCK icon indicates that some settings were changed and are not equal " - "to the system values for the current option group.\n" - "Click to reset all settings for current option group to the system values.")); - m_ttg_white_bullet_ns = _(L("WHITE BULLET icon indicates a non system preset.")); + "to the system (or default) values for the current option group.\n" + "Click to reset all settings for current option group to the system (or default) values.")); + m_ttg_white_bullet_ns = _(L("WHITE BULLET icon indicates a non system (or non default) preset.")); m_ttg_non_system = &m_ttg_white_bullet_ns; // Text to be shown on the "Undo user changes" button next to each input field. m_ttg_white_bullet = _(L("WHITE BULLET icon indicates that the settings are the same as in the last saved " @@ -3231,10 +3220,10 @@ void Tab::set_tooltips_text() // --- Tooltip text for reset buttons (for each option in group) // Text to be shown on the "Revert to system" aka "Lock to system" button next to each input field. - m_tt_value_lock = _(L("LOCKED LOCK icon indicates that the value is the same as the system value.")); + m_tt_value_lock = _(L("LOCKED LOCK icon indicates that the value is the same as the system (or default) value.")); m_tt_value_unlock = _(L("UNLOCKED LOCK icon indicates that the value was changed and is not equal " - "to the system value.\n" - "Click to reset current value to the system value.")); + "to the system (or default) value.\n" + "Click to reset current value to the system (or default) value.")); // m_tt_white_bullet_ns= _(L("WHITE BULLET icon indicates a non system preset.")); m_tt_non_system = &m_ttg_white_bullet_ns; // Text to be shown on the "Undo user changes" button next to each input field. @@ -3488,9 +3477,9 @@ void TabSLAMaterial::reload_config() void TabSLAMaterial::update() { if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptFFF) - return; // #ys_FIXME + return; -// #ys_FIXME +// #ys_FIXME. Just a template for this function // m_update_cnt++; // ! something to update // m_update_cnt--; @@ -3527,6 +3516,7 @@ void TabSLAPrint::build() // optgroup->append_single_option_line("support_pillar_widening_factor"); optgroup->append_single_option_line("support_base_diameter"); optgroup->append_single_option_line("support_base_height"); + optgroup->append_single_option_line("support_base_safety_distance"); optgroup->append_single_option_line("support_object_elevation"); optgroup = page->new_optgroup(_(L("Connection of the support sticks and junctions"))); @@ -3547,7 +3537,12 @@ void TabSLAPrint::build() // TODO: Disabling this parameter for the beta release // optgroup->append_single_option_line("pad_edge_radius"); optgroup->append_single_option_line("pad_wall_slope"); - + + optgroup->append_single_option_line("pad_object_gap"); + optgroup->append_single_option_line("pad_object_connector_stride"); + optgroup->append_single_option_line("pad_object_connector_width"); + optgroup->append_single_option_line("pad_object_connector_penetration"); + page = add_options_page(_(L("Advanced")), "wrench"); optgroup = page->new_optgroup(_(L("Slicing"))); optgroup->append_single_option_line("slice_closing_radius"); @@ -3588,41 +3583,61 @@ void TabSLAPrint::reload_config() void TabSLAPrint::update() { if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptFFF) - return; // #ys_FIXME + return; -// #ys_FIXME m_update_cnt++; - double head_penetration = m_config->opt_float("support_head_penetration"); - double head_width = m_config->opt_float("support_head_width"); - if(head_penetration > head_width) { - wxString msg_text = _(L("Head penetration should not be greater than the head width.")); - auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Invalid Head penetration")), wxICON_WARNING | wxOK); - DynamicPrintConfig new_conf = *m_config; - if (dialog->ShowModal() == wxID_OK) { - new_conf.set_key_value("support_head_penetration", new ConfigOptionFloat(head_width)); - } + double head_penetration = m_config->opt_float("support_head_penetration"); + double head_width = m_config->opt_float("support_head_width"); + if (head_penetration > head_width) { + wxString msg_text = _( + L("Head penetration should not be greater than the head width.")); - load_config(new_conf); - } + auto dialog = new wxMessageDialog(parent(), + msg_text, + _(L("Invalid Head penetration")), + wxICON_WARNING | wxOK); - double pinhead_d = m_config->opt_float("support_head_front_diameter"); - double pillar_d = m_config->opt_float("support_pillar_diameter"); - if(pinhead_d > pillar_d) { - wxString msg_text = _(L("Pinhead diameter should be smaller than the pillar diameter.")); - auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Invalid pinhead diameter")), wxICON_WARNING | wxOK); - DynamicPrintConfig new_conf = *m_config; - if (dialog->ShowModal() == wxID_OK) { - new_conf.set_key_value("support_head_front_diameter", new ConfigOptionFloat(pillar_d / 2.0)); - } + DynamicPrintConfig new_conf = *m_config; + if (dialog->ShowModal() == wxID_OK) { + new_conf.set_key_value("support_head_penetration", + new ConfigOptionFloat(head_width)); + } - load_config(new_conf); - } + load_config(new_conf); + } - m_update_cnt--; + double pinhead_d = m_config->opt_float("support_head_front_diameter"); + double pillar_d = m_config->opt_float("support_pillar_diameter"); + if (pinhead_d > pillar_d) { + wxString msg_text = _(L( + "Pinhead diameter should be smaller than the pillar diameter.")); - if (m_update_cnt == 0) - wxGetApp().mainframe->on_config_changed(m_config); + auto dialog = new wxMessageDialog(parent(), + msg_text, + _(L("Invalid pinhead diameter")), + wxICON_WARNING | wxOK); + + DynamicPrintConfig new_conf = *m_config; + if (dialog->ShowModal() == wxID_OK) { + new_conf.set_key_value("support_head_front_diameter", + new ConfigOptionFloat(pillar_d / 2.0)); + } + + load_config(new_conf); + } + + // if(m_config->opt_float("support_object_elevation") < EPSILON && + // m_config->opt_bool("pad_enable")) { + // // TODO: disable editding of: + // // pad_object_connector_stride + // // pad_object_connector_width + // // pad_object_connector_penetration + // } + + m_update_cnt--; + + if (m_update_cnt == 0) wxGetApp().mainframe->on_config_changed(m_config); } } // GUI diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 6bbe15f7f..73b6bb08d 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -142,6 +142,12 @@ protected: PresetDependencies m_compatible_printers; PresetDependencies m_compatible_prints; + /* Indicates, that default preset or preset inherited from default is selected + * This value is used for a options color updating + * (use green color only for options, which values are equal to system values) + */ + bool m_is_default_preset {false}; + ScalableButton* m_undo_btn; ScalableButton* m_undo_to_sys_btn; ScalableButton* m_question_btn; diff --git a/src/slic3r/GUI/WipeTowerDialog.cpp b/src/slic3r/GUI/WipeTowerDialog.cpp index 4c2b2480e..894b1ee62 100644 --- a/src/slic3r/GUI/WipeTowerDialog.cpp +++ b/src/slic3r/GUI/WipeTowerDialog.cpp @@ -172,7 +172,7 @@ void WipingPanel::format_sizer(wxSizer* sizer, wxPanel* page, wxGridSizer* grid_ wxSize text_size = GetTextExtent(info); auto info_str = new wxStaticText(page, wxID_ANY, info ,wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER); info_str->Wrap(int(0.6*text_size.x)); - sizer->Add( info_str, 0, wxALIGN_CENTER_HORIZONTAL | wxEXPAND); + sizer->Add( info_str, 0, wxEXPAND); auto table_sizer = new wxBoxSizer(wxVERTICAL); sizer->Add(table_sizer, 0, wxALIGN_CENTER | wxCENTER, table_lshift); table_sizer->Add(new wxStaticText(page, wxID_ANY, table_title), 0, wxALIGN_CENTER | wxTOP, 50); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index aed423674..7e8a2d92d 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -437,27 +437,69 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent m_type(type), m_extruder(wxEmptyString) { - if (type == itSettings) { + if (type == itSettings) m_name = "Settings to modified"; - } - else if (type == itInstanceRoot) { + else if (type == itInstanceRoot) m_name = _(L("Instances")); -#ifdef __WXGTK__ - m_container = true; -#endif //__WXGTK__ - } - else if (type == itInstance) { + else if (type == itInstance) + { m_idx = parent->GetChildCount(); m_name = wxString::Format(_(L("Instance %d")), m_idx + 1); set_action_icon(); } + else if (type == itLayerRoot) + { + m_bmp = create_scaled_bitmap(nullptr, "layers"); // FIXME: pass window ptr + m_name = _(L("Layers")); + } + +#ifdef __WXGTK__ + // it's necessary on GTK because of control have to know if this item will be container + // in another case you couldn't to add subitem for this item + // it will be produce "segmentation fault" + if (type & (itInstanceRoot | itLayerRoot)) + m_container = true; +#endif //__WXGTK__ +} + +ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent, + const t_layer_height_range& layer_range, + const int idx /*= -1 */, + const wxString& extruder) : + m_parent(parent), + m_type(itLayer), + m_idx(idx), + m_layer_range(layer_range), + m_extruder(extruder) +{ + const int children_cnt = parent->GetChildCount(); + if (idx < 0) + m_idx = children_cnt; + else + { + // update indexes for another Laeyr Nodes + for (int i = m_idx; i < children_cnt; i++) + parent->GetNthChild(i)->SetIdx(i + 1); + } + const std::string label_range = (boost::format(" %.2f-%.2f ") % layer_range.first % layer_range.second).str(); + m_name = _(L("Range")) + label_range + "(" + _(L("mm")) + ")"; + m_bmp = create_scaled_bitmap(nullptr, "layers_white"); // FIXME: pass window ptr + +#ifdef __WXGTK__ + // it's necessary on GTK because of control have to know if this item will be container + // in another case you couldn't to add subitem for this item + // it will be produce "segmentation fault" + m_container = true; +#endif //__WXGTK__ + + set_action_icon(); } void ObjectDataViewModelNode::set_action_icon() { - m_action_icon_name = m_type == itObject ? "advanced_plus" : - m_type == itVolume ? "cog" : "set_separate_obj"; + m_action_icon_name = m_type & itObject ? "advanced_plus" : + m_type & (itVolume | itLayer) ? "cog" : /*m_type & itInstance*/ "set_separate_obj"; m_action_icon = create_scaled_bitmap(nullptr, m_action_icon_name); // FIXME: pass window ptr } @@ -523,6 +565,22 @@ void ObjectDataViewModelNode::SetIdx(const int& idx) // ObjectDataViewModel // ---------------------------------------------------------------------------- +static int get_root_idx(ObjectDataViewModelNode *parent_node, const ItemType root_type) +{ + // because of istance_root and layers_root are at the end of the list, so + // start locking from the end + for (int root_idx = parent_node->GetChildCount() - 1; root_idx >= 0; root_idx--) + { + // if there is SettingsItem or VolumeItem, then RootItems don't exist in current ObjectItem + if (parent_node->GetNthChild(root_idx)->GetType() & (itSettings | itVolume)) + break; + if (parent_node->GetNthChild(root_idx)->GetType() & root_type) + return root_idx; + } + + return -1; +} + ObjectDataViewModel::ObjectDataViewModel() { m_bitmap_cache = new Slic3r::GUI::BitmapCache; @@ -567,10 +625,10 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent wxString extruder_str = extruder == 0 ? _(L("default")) : wxString::Format("%d", extruder); - // because of istance_root is a last item of the object - int insert_position = root->GetChildCount() - 1; - if (insert_position < 0 || root->GetNthChild(insert_position)->m_type != itInstanceRoot) - insert_position = -1; + // get insertion position according to the existed Layers and/or Instances Items + int insert_position = get_root_idx(root, itLayerRoot); + if (insert_position < 0) + insert_position = get_root_idx(root, itInstanceRoot); const bool obj_errors = root->m_bmp.IsOk(); @@ -619,15 +677,30 @@ wxDataViewItem ObjectDataViewModel::AddSettingsChild(const wxDataViewItem &paren return child; } -int get_istances_root_idx(ObjectDataViewModelNode *parent_node) +/* return values: + * true => root_node is created and added to the parent_root + * false => root node alredy exists +*/ +static bool append_root_node(ObjectDataViewModelNode *parent_node, + ObjectDataViewModelNode **root_node, + const ItemType root_type) { - // because of istance_root is a last item of the object - const int inst_root_idx = parent_node->GetChildCount()-1; + const int inst_root_id = get_root_idx(parent_node, root_type); - if (inst_root_idx < 0 || parent_node->GetNthChild(inst_root_idx)->GetType() == itInstanceRoot) - return inst_root_idx; + *root_node = inst_root_id < 0 ? + new ObjectDataViewModelNode(parent_node, root_type) : + parent_node->GetNthChild(inst_root_id); - return -1; + if (inst_root_id < 0) { + if ((root_type&itInstanceRoot) || + (root_type&itLayerRoot) && get_root_idx(parent_node, itInstanceRoot)<0) + parent_node->Append(*root_node); + else if (root_type&itLayerRoot) + parent_node->Insert(*root_node, static_cast(get_root_idx(parent_node, itInstanceRoot))); + return true; + } + + return false; } wxDataViewItem ObjectDataViewModel::AddInstanceChild(const wxDataViewItem &parent_item, size_t num) @@ -635,20 +708,15 @@ wxDataViewItem ObjectDataViewModel::AddInstanceChild(const wxDataViewItem &paren ObjectDataViewModelNode *parent_node = (ObjectDataViewModelNode*)parent_item.GetID(); if (!parent_node) return wxDataViewItem(0); - // Check and create/get instances root node - const int inst_root_id = get_istances_root_idx(parent_node); + // get InstanceRoot node + ObjectDataViewModelNode *inst_root_node { nullptr }; - ObjectDataViewModelNode *inst_root_node = inst_root_id < 0 ? - new ObjectDataViewModelNode(parent_node, itInstanceRoot) : - parent_node->GetNthChild(inst_root_id); + const bool appended = append_root_node(parent_node, &inst_root_node, itInstanceRoot); const wxDataViewItem inst_root_item((void*)inst_root_node); + if (!inst_root_node) return wxDataViewItem(0); - if (inst_root_id < 0) { - parent_node->Append(inst_root_node); - // notify control - ItemAdded(parent_item, inst_root_item); -// if (num == 1) num++; - } + if (appended) + ItemAdded(parent_item, inst_root_item);// notify control // Add instance nodes ObjectDataViewModelNode *instance_node = nullptr; @@ -665,6 +733,63 @@ wxDataViewItem ObjectDataViewModel::AddInstanceChild(const wxDataViewItem &paren return wxDataViewItem((void*)instance_node); } +wxDataViewItem ObjectDataViewModel::AddLayersRoot(const wxDataViewItem &parent_item) +{ + ObjectDataViewModelNode *parent_node = (ObjectDataViewModelNode*)parent_item.GetID(); + if (!parent_node) return wxDataViewItem(0); + + // get LayerRoot node + ObjectDataViewModelNode *layer_root_node{ nullptr }; + const bool appended = append_root_node(parent_node, &layer_root_node, itLayerRoot); + if (!layer_root_node) return wxDataViewItem(0); + + const wxDataViewItem layer_root_item((void*)layer_root_node); + + if (appended) + ItemAdded(parent_item, layer_root_item);// notify control + + return layer_root_item; +} + +wxDataViewItem ObjectDataViewModel::AddLayersChild(const wxDataViewItem &parent_item, + const t_layer_height_range& layer_range, + const int extruder/* = 0*/, + const int index /* = -1*/) +{ + ObjectDataViewModelNode *parent_node = (ObjectDataViewModelNode*)parent_item.GetID(); + if (!parent_node) return wxDataViewItem(0); + + wxString extruder_str = extruder == 0 ? _(L("default")) : wxString::Format("%d", extruder); + + // get LayerRoot node + ObjectDataViewModelNode *layer_root_node; + wxDataViewItem layer_root_item; + + if (parent_node->GetType() & itLayerRoot) { + layer_root_node = parent_node; + layer_root_item = parent_item; + } + else { + const int root_idx = get_root_idx(parent_node, itLayerRoot); + if (root_idx < 0) return wxDataViewItem(0); + layer_root_node = parent_node->GetNthChild(root_idx); + layer_root_item = wxDataViewItem((void*)layer_root_node); + } + + // Add layer node + ObjectDataViewModelNode *layer_node = new ObjectDataViewModelNode(layer_root_node, layer_range, index, extruder_str); + if (index < 0) + layer_root_node->Append(layer_node); + else + layer_root_node->Insert(layer_node, index); + + // notify control + const wxDataViewItem layer_item((void*)layer_node); + ItemAdded(layer_root_item, layer_item); + + return layer_item; +} + wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) { auto ret_item = wxDataViewItem(0); @@ -679,10 +804,14 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) // NOTE: MyObjectTreeModelNodePtrArray is only an array of _pointers_ // thus removing the node from it doesn't result in freeing it if (node_parent) { - if (node->m_type == itInstanceRoot) + if (node->m_type & (itInstanceRoot|itLayerRoot)) { - for (int i = node->GetChildCount() - 1; i > 0; i--) + // node can be deleted by the Delete, let's check its type while we safely can + bool is_instance_root = (node->m_type & itInstanceRoot); + + for (int i = node->GetChildCount() - 1; i >= (is_instance_root ? 1 : 0); i--) Delete(wxDataViewItem(node->GetNthChild(i))); + return parent; } @@ -690,7 +819,7 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) auto idx = node->GetIdx(); - if (node->m_type == itVolume) { + if (node->m_type & (itVolume|itLayer)) { node_parent->m_volumes_cnt--; DeleteSettings(item); } @@ -726,6 +855,22 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) delete node_parent; ret_item = wxDataViewItem(obj_node); +#ifndef __WXGTK__ + if (obj_node->GetChildCount() == 0) + obj_node->m_container = false; +#endif //__WXGTK__ + ItemDeleted(ret_item, wxDataViewItem(node_parent)); + return ret_item; + } + + // if there was last layer item, delete this one and layers root item + if (node_parent->GetChildCount() == 0 && node_parent->m_type == itLayerRoot) + { + ObjectDataViewModelNode *obj_node = node_parent->GetParent(); + obj_node->GetChildren().Remove(node_parent); + delete node_parent; + ret_item = wxDataViewItem(obj_node); + #ifndef __WXGTK__ if (obj_node->GetChildCount() == 0) obj_node->m_container = false; @@ -735,7 +880,7 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) } // if there is last volume item after deleting, delete this last volume too - if (node_parent->GetChildCount() <= 3) + if (node_parent->GetChildCount() <= 3) // 3??? #ys_FIXME { int vol_cnt = 0; int vol_idx = 0; @@ -817,7 +962,7 @@ wxDataViewItem ObjectDataViewModel::DeleteLastInstance(const wxDataViewItem &par ObjectDataViewModelNode *parent_node = (ObjectDataViewModelNode*)parent_item.GetID(); if (!parent_node) return ret_item; - const int inst_root_id = get_istances_root_idx(parent_node); + const int inst_root_id = get_root_idx(parent_node, itInstanceRoot); if (inst_root_id < 0) return ret_item; wxDataViewItemArray items; @@ -974,28 +1119,67 @@ wxDataViewItem ObjectDataViewModel::GetItemByVolumeId(int obj_idx, int volume_id return wxDataViewItem(0); } -wxDataViewItem ObjectDataViewModel::GetItemByInstanceId(int obj_idx, int inst_idx) +wxDataViewItem ObjectDataViewModel::GetItemById(const int obj_idx, const int sub_obj_idx, const ItemType parent_type) { if (obj_idx >= m_objects.size() || obj_idx < 0) { printf("Error! Out of objects range.\n"); return wxDataViewItem(0); } - auto instances_item = GetInstanceRootItem(wxDataViewItem(m_objects[obj_idx])); - if (!instances_item) + auto item = GetItemByType(wxDataViewItem(m_objects[obj_idx]), parent_type); + if (!item) return wxDataViewItem(0); - auto parent = (ObjectDataViewModelNode*)instances_item.GetID();; + auto parent = (ObjectDataViewModelNode*)item.GetID(); for (size_t i = 0; i < parent->GetChildCount(); i++) - if (parent->GetNthChild(i)->m_idx == inst_idx) + if (parent->GetNthChild(i)->m_idx == sub_obj_idx) return wxDataViewItem(parent->GetNthChild(i)); return wxDataViewItem(0); } +wxDataViewItem ObjectDataViewModel::GetItemByInstanceId(int obj_idx, int inst_idx) +{ + return GetItemById(obj_idx, inst_idx, itInstanceRoot); +} + +wxDataViewItem ObjectDataViewModel::GetItemByLayerId(int obj_idx, int layer_idx) +{ + return GetItemById(obj_idx, layer_idx, itLayerRoot); +} + +wxDataViewItem ObjectDataViewModel::GetItemByLayerRange(const int obj_idx, const t_layer_height_range& layer_range) +{ + if (obj_idx >= m_objects.size() || obj_idx < 0) { + printf("Error! Out of objects range.\n"); + return wxDataViewItem(0); + } + + auto item = GetItemByType(wxDataViewItem(m_objects[obj_idx]), itLayerRoot); + if (!item) + return wxDataViewItem(0); + + auto parent = (ObjectDataViewModelNode*)item.GetID(); + for (size_t i = 0; i < parent->GetChildCount(); i++) + if (parent->GetNthChild(i)->m_layer_range == layer_range) + return wxDataViewItem(parent->GetNthChild(i)); + + return wxDataViewItem(0); +} + +int ObjectDataViewModel::GetItemIdByLayerRange(const int obj_idx, const t_layer_height_range& layer_range) +{ + wxDataViewItem item = GetItemByLayerRange(obj_idx, layer_range); + if (!item) + return -1; + + return GetLayerIdByItem(item); +} + int ObjectDataViewModel::GetIdByItem(const wxDataViewItem& item) const { - wxASSERT(item.IsOk()); + if(!item.IsOk()) + return -1; ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); auto it = find(m_objects.begin(), m_objects.end(), node); @@ -1030,13 +1214,28 @@ int ObjectDataViewModel::GetInstanceIdByItem(const wxDataViewItem& item) const return GetIdByItemAndType(item, itInstance); } +int ObjectDataViewModel::GetLayerIdByItem(const wxDataViewItem& item) const +{ + return GetIdByItemAndType(item, itLayer); +} + +t_layer_height_range ObjectDataViewModel::GetLayerRangeByItem(const wxDataViewItem& item) const +{ + wxASSERT(item.IsOk()); + + ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); + if (!node || node->m_type != itLayer) + return { 0.0f, 0.0f }; + return node->GetLayerRange(); +} + void ObjectDataViewModel::GetItemInfo(const wxDataViewItem& item, ItemType& type, int& obj_idx, int& idx) { wxASSERT(item.IsOk()); type = itUndef; ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); - if (!node || node->GetIdx() <-1 || node->GetIdx() ==-1 && !(node->GetType() & (itObject | itSettings | itInstanceRoot))) + if (!node || node->GetIdx() <-1 || node->GetIdx() == -1 && !(node->GetType() & (itObject | itSettings | itInstanceRoot | itLayerRoot/* | itLayer*/))) return; idx = node->GetIdx(); @@ -1044,9 +1243,10 @@ void ObjectDataViewModel::GetItemInfo(const wxDataViewItem& item, ItemType& type ObjectDataViewModelNode *parent_node = node->GetParent(); if (!parent_node) return; - if (type == itInstance) - parent_node = node->GetParent()->GetParent(); - if (!parent_node || parent_node->m_type != itObject) { type = itUndef; return; } + + // get top parent (Object) node + while (parent_node->m_type != itObject) + parent_node = parent_node->GetParent(); auto it = find(m_objects.begin(), m_objects.end(), parent_node); if (it != m_objects.end()) @@ -1214,10 +1414,7 @@ wxDataViewItem ObjectDataViewModel::GetTopParent(const wxDataViewItem &item) con ObjectDataViewModelNode *parent_node = node->GetParent(); while (parent_node->m_type != itObject) - { - node = parent_node; - parent_node = node->GetParent(); - } + parent_node = parent_node->GetParent(); return wxDataViewItem((void*)parent_node); } @@ -1318,6 +1515,11 @@ wxDataViewItem ObjectDataViewModel::GetInstanceRootItem(const wxDataViewItem &it return GetItemByType(item, itInstanceRoot); } +wxDataViewItem ObjectDataViewModel::GetLayerRootItem(const wxDataViewItem &item) const +{ + return GetItemByType(item, itLayerRoot); +} + bool ObjectDataViewModel::IsSettingsItem(const wxDataViewItem &item) const { if (!item.IsOk()) @@ -2027,6 +2229,9 @@ void DoubleSlider::draw_thumbs(wxDC& dc, const wxCoord& lower_pos, const wxCoord void DoubleSlider::draw_ticks(wxDC& dc) { + if (!m_is_enabled_tick_manipulation) + return; + dc.SetPen(m_is_enabled_tick_manipulation ? DARK_GREY_PEN : LIGHT_GREY_PEN ); int height, width; get_size(&width, &height); @@ -2044,6 +2249,9 @@ void DoubleSlider::draw_ticks(wxDC& dc) void DoubleSlider::draw_colored_band(wxDC& dc) { + if (!m_is_enabled_tick_manipulation) + return; + int height, width; get_size(&width, &height); @@ -2113,7 +2321,7 @@ void DoubleSlider::draw_one_layer_icon(wxDC& dc) void DoubleSlider::draw_revert_icon(wxDC& dc) { - if (m_ticks.empty()) + if (m_ticks.empty() || !m_is_enabled_tick_manipulation) return; int width, height; @@ -2218,7 +2426,7 @@ void DoubleSlider::OnLeftDown(wxMouseEvent& event) m_selection == ssLower ? correct_lower_value() : correct_higher_value(); if (!m_selection) m_selection = ssHigher; } - else if (is_point_in_rect(pos, m_rect_revert_icon)) { + else if (is_point_in_rect(pos, m_rect_revert_icon) && m_is_enabled_tick_manipulation) { // discard all color changes SetLowerValue(m_min_value); SetHigherValue(m_max_value); @@ -2647,7 +2855,7 @@ ModeSizer::ModeSizer(wxWindow *parent, int hgap/* = 10*/) : m_mode_btns.push_back(new ModeButton(parent, wxID_ANY, button.second, button.first));; #endif // __WXOSX__ - m_mode_btns.back()->Bind(wxEVT_BUTTON, std::bind(modebtnfn, std::placeholders::_1, m_mode_btns.size() - 1)); + m_mode_btns.back()->Bind(wxEVT_BUTTON, std::bind(modebtnfn, std::placeholders::_1, int(m_mode_btns.size() - 1))); Add(m_mode_btns.back()); } } diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 081c0d48f..d0edf9760 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -20,6 +20,9 @@ namespace Slic3r { enum class ModelVolumeType : int; }; +typedef double coordf_t; +typedef std::pair t_layer_height_range; + #ifdef __WXMSW__ void msw_rescale_menu(wxMenu* menu); #else /* __WXMSW__ */ @@ -159,12 +162,14 @@ DECLARE_VARIANT_OBJECT(DataViewBitmapText) // ---------------------------------------------------------------------------- enum ItemType { - itUndef = 0, - itObject = 1, - itVolume = 2, - itInstanceRoot = 4, - itInstance = 8, - itSettings = 16 + itUndef = 0, + itObject = 1, + itVolume = 2, + itInstanceRoot = 4, + itInstance = 8, + itSettings = 16, + itLayerRoot = 32, + itLayer = 64, }; class ObjectDataViewModelNode; @@ -177,6 +182,7 @@ class ObjectDataViewModelNode wxBitmap m_empty_bmp; size_t m_volumes_cnt = 0; std::vector< std::string > m_opt_categories; + t_layer_height_range m_layer_range = { 0.0f, 0.0f }; wxString m_name; wxBitmap& m_bmp = m_empty_bmp; @@ -229,6 +235,11 @@ public: set_action_icon(); } + ObjectDataViewModelNode(ObjectDataViewModelNode* parent, + const t_layer_height_range& layer_range, + const int idx = -1, + const wxString& extruder = wxEmptyString ); + ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const ItemType type); ~ObjectDataViewModelNode() @@ -318,6 +329,7 @@ public: ItemType GetType() const { return m_type; } void SetIdx(const int& idx); int GetIdx() const { return m_idx; } + t_layer_height_range GetLayerRange() const { return m_layer_range; } // use this function only for childrens void AssignAllVal(ObjectDataViewModelNode& from_node) @@ -348,7 +360,7 @@ public: } // Set action icons for node - void set_action_icon(); + void set_action_icon(); void update_settings_digest_bitmaps(); bool update_settings_digest(const std::vector& categories); @@ -388,6 +400,11 @@ public: const bool create_frst_child = true); wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item); wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, size_t num); + wxDataViewItem AddLayersRoot(const wxDataViewItem &parent_item); + wxDataViewItem AddLayersChild( const wxDataViewItem &parent_item, + const t_layer_height_range& layer_range, + const int extruder = 0, + const int index = -1); wxDataViewItem Delete(const wxDataViewItem &item); wxDataViewItem DeleteLastInstance(const wxDataViewItem &parent_item, size_t num); void DeleteAll(); @@ -395,13 +412,18 @@ public: void DeleteVolumeChildren(wxDataViewItem& parent); void DeleteSettings(const wxDataViewItem& parent); wxDataViewItem GetItemById(int obj_idx); + wxDataViewItem GetItemById(const int obj_idx, const int sub_obj_idx, const ItemType parent_type); wxDataViewItem GetItemByVolumeId(int obj_idx, int volume_idx); wxDataViewItem GetItemByInstanceId(int obj_idx, int inst_idx); + wxDataViewItem GetItemByLayerId(int obj_idx, int layer_idx); + wxDataViewItem GetItemByLayerRange(const int obj_idx, const t_layer_height_range& layer_range); + int GetItemIdByLayerRange(const int obj_idx, const t_layer_height_range& layer_range); int GetIdByItem(const wxDataViewItem& item) const; int GetIdByItemAndType(const wxDataViewItem& item, const ItemType type) const; int GetObjectIdByItem(const wxDataViewItem& item) const; int GetVolumeIdByItem(const wxDataViewItem& item) const; int GetInstanceIdByItem(const wxDataViewItem& item) const; + int GetLayerIdByItem(const wxDataViewItem& item) const; void GetItemInfo(const wxDataViewItem& item, ItemType& type, int& obj_idx, int& idx); int GetRowByItem(const wxDataViewItem& item) const; bool IsEmpty() { return m_objects.empty(); } @@ -450,6 +472,7 @@ public: ItemType type) const; wxDataViewItem GetSettingsItem(const wxDataViewItem &item) const; wxDataViewItem GetInstanceRootItem(const wxDataViewItem &item) const; + wxDataViewItem GetLayerRootItem(const wxDataViewItem &item) const; bool IsSettingsItem(const wxDataViewItem &item) const; void UpdateSettingsDigest( const wxDataViewItem &item, const std::vector& categories); @@ -465,6 +488,7 @@ public: wxBitmap GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, const bool is_marked = false); void DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object = false); + t_layer_height_range GetLayerRangeByItem(const wxDataViewItem& item) const; }; // ---------------------------------------------------------------------------- diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index 8c3ced31a..bc600fcad 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -402,15 +402,8 @@ Updates PresetUpdater::priv::get_config_updates() const } } - copy_file_fix(idx.path(), bundle_path_idx); - const auto ver_current = idx.find(vp.config_version); const bool ver_current_found = ver_current != idx.end(); - if (! ver_current_found) { - auto message = (boost::format("Preset bundle `%1%` version not found in index: %2%") % idx.vendor() % vp.config_version.to_string()).str(); - BOOST_LOG_TRIVIAL(error) << message; - GUI::show_error(nullptr, GUI::from_u8(message)); - } BOOST_LOG_TRIVIAL(debug) << boost::format("Vendor: %1%, version installed: %2%%3%, version cached: %4%") % vp.name @@ -418,6 +411,13 @@ Updates PresetUpdater::priv::get_config_updates() const % (ver_current_found ? "" : " (not found in index!)") % recommended->config_version.to_string(); + if (! ver_current_found) { + auto message = (boost::format("Preset bundle `%1%` version not found in index: %2%") % idx.vendor() % vp.config_version.to_string()).str(); + BOOST_LOG_TRIVIAL(error) << message; + GUI::show_error(nullptr, GUI::from_u8(message)); + continue; + } + if (ver_current_found && !ver_current->is_current_slic3r_supported()) { BOOST_LOG_TRIVIAL(warning) << "Current Slic3r incompatible with installed bundle: " << bundle_path.string(); updates.incompats.emplace_back(std::move(bundle_path), *ver_current, vp.name); @@ -459,10 +459,16 @@ Updates PresetUpdater::priv::get_config_updates() const found = true; } } - if (! found) + + if (found) { + // 'Install' the index in the vendor directory. This is used to memoize + // offered updates and to not offer the same update again if it was cancelled by the user. + copy_file_fix(idx.path(), bundle_path_idx); + } else { BOOST_LOG_TRIVIAL(warning) << boost::format("Index for vendor %1% indicates update (%2%) but the new bundle was found neither in cache nor resources") % idx.vendor() % recommended->config_version.to_string(); + } } } diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp new file mode 100644 index 000000000..69d60603e --- /dev/null +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -0,0 +1,819 @@ +#include "UndoRedo.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#define CEREAL_FUTURE_EXPERIMENTAL +#include + +#include + +#ifndef NDEBUG +// #define SLIC3R_UNDOREDO_DEBUG +#endif /* NDEBUG */ + +namespace Slic3r { +namespace UndoRedo { + +static std::string topmost_snapsnot_name = "@@@ Topmost @@@"; + +bool Snapshot::is_topmost() const +{ + return this->name == topmost_snapsnot_name; +} + +// Time interval, start is closed, end is open. +struct Interval +{ +public: + Interval(size_t begin, size_t end) : m_begin(begin), m_end(end) {} + + size_t begin() const { return m_begin; } + size_t end() const { return m_end; } + + bool is_valid() const { return m_begin >= 0 && m_begin < m_end; } + // This interval comes strictly before the rhs interval. + bool strictly_before(const Interval &rhs) const { return this->is_valid() && rhs.is_valid() && m_end <= rhs.m_begin; } + // This interval comes strictly after the rhs interval. + bool strictly_after(const Interval &rhs) const { return this->is_valid() && rhs.is_valid() && rhs.m_end <= m_begin; } + + bool operator<(const Interval &rhs) const { return (m_begin < rhs.m_begin) || (m_begin == rhs.m_begin && m_end < rhs.m_end); } + bool operator==(const Interval &rhs) const { return m_begin == rhs.m_begin && m_end == rhs.m_end; } + + void trim_end(size_t new_end) { m_end = std::min(m_end, new_end); } + void extend_end(size_t new_end) { assert(new_end >= m_end); m_end = new_end; } + +private: + size_t m_begin; + size_t m_end; +}; + +// History of a single object tracked by the Undo / Redo stack. The object may be mutable or immutable. +class ObjectHistoryBase +{ +public: + virtual ~ObjectHistoryBase() {} + + // Is the object captured by this history mutable or immutable? + virtual bool is_mutable() const = 0; + virtual bool is_immutable() const = 0; + virtual const void* immutable_object_ptr() const { return nullptr; } + + // If the history is empty, the ObjectHistory object could be released. + virtual bool empty() = 0; + + // Release all data after the given timestamp. For the ImmutableObjectHistory, the shared pointer is NOT released. + virtual void release_after_timestamp(size_t timestamp) = 0; + +#ifdef SLIC3R_UNDOREDO_DEBUG + // Human readable debug information. + virtual std::string format() = 0; +#endif /* SLIC3R_UNDOREDO_DEBUG */ + +#ifndef NDEBUG + virtual bool valid() = 0; +#endif /* NDEBUG */ +}; + +template class ObjectHistory : public ObjectHistoryBase +{ +public: + ~ObjectHistory() override {} + + // If the history is empty, the ObjectHistory object could be released. + bool empty() override { return m_history.empty(); } + + // Release all data after the given timestamp. The shared pointer is NOT released. + void release_after_timestamp(size_t timestamp) override { + assert(! m_history.empty()); + assert(this->valid()); + // it points to an interval which either starts with timestamp, or follows the timestamp. + auto it = std::lower_bound(m_history.begin(), m_history.end(), T(timestamp, timestamp)); + if (it != m_history.begin()) { + auto it_prev = it; + -- it_prev; + assert(it_prev->begin() < timestamp); + // Trim the last interval with timestamp. + it_prev->trim_end(timestamp); + } + m_history.erase(it, m_history.end()); + assert(this->valid()); + } + +protected: + std::vector m_history; +}; + +// Big objects (mainly the triangle meshes) are tracked by Slicer using the shared pointers +// and they are immutable. +// The Undo / Redo stack therefore may keep a shared pointer to these immutable objects +// and as long as the ref counter of these objects is higher than 1 (1 reference is held +// by the Undo / Redo stack), there is no cost associated to holding the object +// at the Undo / Redo stack. Once the reference counter drops to 1 (only the Undo / Redo +// stack holds the reference), the shared pointer may get serialized (and possibly compressed) +// and the shared pointer may be released. +// The history of a single immutable object may not be continuous, as an immutable object may +// be removed from the scene while being kept at the Copy / Paste stack. +template +class ImmutableObjectHistory : public ObjectHistory +{ +public: + ImmutableObjectHistory(std::shared_ptr shared_object) : m_shared_object(shared_object) {} + ~ImmutableObjectHistory() override {} + + bool is_mutable() const override { return false; } + bool is_immutable() const override { return true; } + const void* immutable_object_ptr() const { return (const void*)m_shared_object.get(); } + + void save(size_t active_snapshot_time, size_t current_time) { + assert(m_history.empty() || m_history.back().end() <= active_snapshot_time || + // The snapshot of an immutable object may have already been taken from another mutable object. + (m_history.back().begin() <= active_snapshot_time && m_history.back().end() == current_time + 1)); + if (m_history.empty() || m_history.back().end() < active_snapshot_time) + m_history.emplace_back(active_snapshot_time, current_time + 1); + else + m_history.back().extend_end(current_time + 1); + } + + bool has_snapshot(size_t timestamp) { + if (m_history.empty()) + return false; + auto it = std::lower_bound(m_history.begin(), m_history.end(), Interval(timestamp, timestamp)); + if (it == m_history.end() || it->begin() > timestamp) { + if (it == m_history.begin()) + return false; + -- it; + } + return timestamp >= it->begin() && timestamp < it->end(); + } + + bool is_serialized() const { return m_shared_object.get() == nullptr; } + const std::string& serialized_data() const { return m_serialized; } + std::shared_ptr& shared_ptr(StackImpl &stack); + +#ifdef SLIC3R_UNDOREDO_DEBUG + std::string format() override { + std::string out = typeid(T).name(); + out += this->is_serialized() ? + std::string(" len:") + std::to_string(m_serialized.size()) : + std::string(" shared_ptr:") + ptr_to_string(m_shared_object.get()); + for (const Interval &interval : m_history) + out += std::string(", <") + std::to_string(interval.begin()) + "," + std::to_string(interval.end()) + ")"; + return out; + } +#endif /* SLIC3R_UNDOREDO_DEBUG */ + +#ifndef NDEBUG + bool valid() override; +#endif /* NDEBUG */ + +private: + // Either the source object is held by a shared pointer and the m_serialized field is empty, + // or the shared pointer is null and the object is being serialized into m_serialized. + std::shared_ptr m_shared_object; + std::string m_serialized; +}; + +struct MutableHistoryInterval +{ +private: + struct Data + { + // Reference counter of this data chunk. We may have used shared_ptr, but the shared_ptr is thread safe + // with the associated cost of CPU cache invalidation on refcount change. + size_t refcnt; + size_t size; + char data[1]; + + bool matches(const std::string& rhs) { return this->size == rhs.size() && memcmp(this->data, rhs.data(), this->size) == 0; } + }; + + Interval m_interval; + Data *m_data; + +public: + MutableHistoryInterval(const Interval &interval, const std::string &input_data) : m_interval(interval), m_data(nullptr) { + m_data = (Data*)new char[offsetof(Data, data) + input_data.size()]; + m_data->refcnt = 1; + m_data->size = input_data.size(); + memcpy(m_data->data, input_data.data(), input_data.size()); + } + + MutableHistoryInterval(const Interval &interval, MutableHistoryInterval &other) : m_interval(interval), m_data(other.m_data) { + ++ m_data->refcnt; + } + + // as a key for std::lower_bound + MutableHistoryInterval(const size_t begin, const size_t end) : m_interval(begin, end), m_data(nullptr) {} + + MutableHistoryInterval(MutableHistoryInterval&& rhs) : m_interval(rhs.m_interval), m_data(rhs.m_data) { rhs.m_data = nullptr; } + MutableHistoryInterval& operator=(MutableHistoryInterval&& rhs) { m_interval = rhs.m_interval; m_data = rhs.m_data; rhs.m_data = nullptr; return *this; } + + ~MutableHistoryInterval() { + if (m_data != nullptr && -- m_data->refcnt == 0) + delete[] (char*)m_data; + } + + const Interval& interval() const { return m_interval; } + size_t begin() const { return m_interval.begin(); } + size_t end() const { return m_interval.end(); } + void trim_end(size_t timestamp) { m_interval.trim_end(timestamp); } + void extend_end(size_t timestamp) { m_interval.extend_end(timestamp); } + + bool operator<(const MutableHistoryInterval& rhs) const { return m_interval < rhs.m_interval; } + bool operator==(const MutableHistoryInterval& rhs) const { return m_interval == rhs.m_interval; } + + const char* data() const { return m_data->data; } + size_t size() const { return m_data->size; } + size_t refcnt() const { return m_data->refcnt; } + bool matches(const std::string& data) { return m_data->matches(data); } + +private: + MutableHistoryInterval(const MutableHistoryInterval &rhs); + MutableHistoryInterval& operator=(const MutableHistoryInterval &rhs); +}; + +static inline std::string ptr_to_string(const void* ptr) +{ + char buf[64]; + sprintf(buf, "%p", ptr); + return buf; +} + +// Smaller objects (Model, ModelObject, ModelInstance, ModelVolume, DynamicPrintConfig) +// are mutable and there is not tracking of the changes, therefore a snapshot needs to be +// taken every time and compared to the previous data at the Undo / Redo stack. +// The serialized data is stored if it is different from the last value on the stack, otherwise +// the serialized data is discarded. +// The history of a single mutable object may not be continuous, as an mutable object may +// be removed from the scene while being kept at the Copy / Paste stack, therefore an object snapshot +// with the same serialized object data may be shared by multiple history intervals. +template +class MutableObjectHistory : public ObjectHistory +{ +public: + ~MutableObjectHistory() override {} + + bool is_mutable() const override { return true; } + bool is_immutable() const override { return false; } + + void save(size_t active_snapshot_time, size_t current_time, const std::string &data) { + assert(m_history.empty() || m_history.back().end() <= active_snapshot_time); + if (m_history.empty() || m_history.back().end() < active_snapshot_time) { + if (! m_history.empty() && m_history.back().matches(data)) + // Share the previous data by reference counting. + m_history.emplace_back(Interval(current_time, current_time + 1), m_history.back()); + else + // Allocate new data. + m_history.emplace_back(Interval(current_time, current_time + 1), data); + } else { + assert(! m_history.empty()); + assert(m_history.back().end() == active_snapshot_time); + if (m_history.back().matches(data)) + // Just extend the last interval using the old data. + m_history.back().extend_end(current_time + 1); + else + // Allocate new data time continuous with the previous data. + m_history.emplace_back(Interval(active_snapshot_time, current_time + 1), data); + } + } + + std::string load(size_t timestamp) const { + assert(! m_history.empty()); + auto it = std::lower_bound(m_history.begin(), m_history.end(), MutableHistoryInterval(timestamp, timestamp)); + if (it == m_history.end() || it->begin() > timestamp) { + assert(it != m_history.begin()); + -- it; + } + assert(timestamp >= it->begin() && timestamp < it->end()); + return std::string(it->data(), it->data() + it->size()); + } + +#ifdef SLIC3R_UNDOREDO_DEBUG + std::string format() override { + std::string out = typeid(T).name(); + for (const MutableHistoryInterval &interval : m_history) + out += std::string(", ptr:") + ptr_to_string(interval.data()) + " len:" + std::to_string(interval.size()) + " <" + std::to_string(interval.begin()) + "," + std::to_string(interval.end()) + ")"; + return out; + } +#endif /* SLIC3R_UNDOREDO_DEBUG */ + +#ifndef NDEBUG + bool valid() override; +#endif /* NDEBUG */ +}; + +#ifndef NDEBUG +template +bool ImmutableObjectHistory::valid() +{ + // The immutable object content is captured either by a shared object, or by its serialization, but not both. + assert(! m_shared_object == ! m_serialized.empty()); + // Verify that the history intervals are sorted and do not overlap. + if (! m_history.empty()) + for (size_t i = 1; i < m_history.size(); ++ i) + assert(m_history[i - 1].strictly_before(m_history[i])); + return true; +} +#endif /* NDEBUG */ + +#ifndef NDEBUG +template +bool MutableObjectHistory::valid() +{ + // Verify that the history intervals are sorted and do not overlap, and that the data reference counters are correct. + if (! m_history.empty()) { + std::map refcntrs; + assert(m_history.front().data() != nullptr); + ++ refcntrs[m_history.front().data()]; + for (size_t i = 1; i < m_history.size(); ++ i) { + assert(m_history[i - 1].interval().strictly_before(m_history[i].interval())); + ++ refcntrs[m_history[i].data()]; + } + for (const auto &hi : m_history) { + assert(hi.data() != nullptr); + assert(refcntrs[hi.data()] == hi.refcnt()); + } + } + return true; +} +#endif /* NDEBUG */ + +class StackImpl +{ +public: + // Stack needs to be initialized. An empty stack is not valid, there must be a "New Project" status stored at the beginning. + StackImpl() : m_active_snapshot_time(0), m_current_time(0) {} + + // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. + void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos); + void load_snapshot(size_t timestamp, Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos); + + bool has_undo_snapshot() const; + bool has_redo_snapshot() const; + bool undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, size_t jump_to_time); + bool redo(Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos, size_t jump_to_time); + + // Snapshot history (names with timestamps). + const std::vector& snapshots() const { return m_snapshots; } + // Timestamp of the active snapshot. + size_t active_snapshot_time() const { return m_active_snapshot_time; } + + const Selection& selection_deserialized() const { return m_selection; } + +//protected: + template ObjectID save_mutable_object(const T &object); + template ObjectID save_immutable_object(std::shared_ptr &object); + template T* load_mutable_object(const Slic3r::ObjectID id); + template std::shared_ptr load_immutable_object(const Slic3r::ObjectID id); + template void load_mutable_object(const Slic3r::ObjectID id, T &target); + +#ifdef SLIC3R_UNDOREDO_DEBUG + std::string format() const { + std::string out = "Objects\n"; + for (const std::pair> &kvp : m_objects) + out += std::string("ObjectID:") + std::to_string(kvp.first.id) + " " + kvp.second->format() + "\n"; + out += "Snapshots\n"; + for (const Snapshot &snapshot : m_snapshots) { + if (snapshot.timestamp == m_active_snapshot_time) + out += ">>> "; + out += std::string("Name: \"") + snapshot.name + "\", timestamp: " + std::to_string(snapshot.timestamp) + + ", Model ID:" + ((snapshot.model_id == 0) ? "Invalid" : std::to_string(snapshot.model_id)) + "\n"; + } + if (m_active_snapshot_time > m_snapshots.back().timestamp) + out += ">>>\n"; + out += "Current time: " + std::to_string(m_current_time) + "\n"; + return out; + } + void print() const { + std::cout << "Undo / Redo stack" << std::endl; + std::cout << this->format() << std::endl; + } +#endif /* SLIC3R_UNDOREDO_DEBUG */ + + +#ifndef NDEBUG + bool valid() const { + assert(! m_snapshots.empty()); + auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); + assert(it != m_snapshots.begin() && it != m_snapshots.end() && it->timestamp == m_active_snapshot_time); + assert(m_active_snapshot_time < m_snapshots.back().timestamp || m_snapshots.back().is_topmost()); + for (auto it = m_objects.begin(); it != m_objects.end(); ++ it) + assert(it->second->valid()); + return true; + } +#endif /* NDEBUG */ + +private: + template ObjectID immutable_object_id(const std::shared_ptr &ptr) { + return this->immutable_object_id_impl((const void*)ptr.get()); + } + ObjectID immutable_object_id_impl(const void *ptr) { + auto it = m_shared_ptr_to_object_id.find(ptr); + if (it == m_shared_ptr_to_object_id.end()) { + // Allocate a new temporary ObjectID for this shared pointer. + ObjectBase object_with_id; + it = m_shared_ptr_to_object_id.insert(it, std::make_pair(ptr, object_with_id.id())); + } + return it->second; + } + void collect_garbage(); + + // Each individual object (Model, ModelObject, ModelInstance, ModelVolume, Selection, TriangleMesh) + // is stored with its own history, referenced by the ObjectID. Immutable objects do not provide + // their own IDs, therefore there are temporary IDs generated for them and stored to m_shared_ptr_to_object_id. + std::map> m_objects; + std::map m_shared_ptr_to_object_id; + // Snapshot history (names with timestamps). + std::vector m_snapshots; + // Timestamp of the active snapshot. + size_t m_active_snapshot_time; + // Logical time counter. m_current_time is being incremented with each snapshot taken. + size_t m_current_time; + // Last selection serialized or deserialized. + Selection m_selection; +}; + +using InputArchive = cereal::UserDataAdapter; +using OutputArchive = cereal::UserDataAdapter; + +} // namespace UndoRedo + +class Model; +class ModelObject; +class ModelVolume; +class ModelInstance; +class ModelMaterial; +class ModelConfig; +class DynamicPrintConfig; +class TriangleMesh; + +} // namespace Slic3r + +namespace cereal +{ + // Let cereal know that there are load / save non-member functions declared for ModelObject*, ignore serialization of pointers triggering + // static assert, that cereal does not support serialization of raw pointers. + template struct specialize {}; + template struct specialize {}; + template struct specialize {}; + template struct specialize {}; + template struct specialize {}; + template struct specialize {}; + template struct specialize, cereal::specialization::non_member_load_save> {}; + + // Store ObjectBase derived class onto the Undo / Redo stack as a separate object, + // store just the ObjectID to this stream. + template void save(BinaryOutputArchive& ar, T* const& ptr) + { + ar(cereal::get_user_data(ar).save_mutable_object(*ptr)); + } + + // Load ObjectBase derived class from the Undo / Redo stack as a separate object + // based on the ObjectID loaded from this stream. + template void load(BinaryInputArchive& ar, T*& ptr) + { + Slic3r::UndoRedo::StackImpl& stack = cereal::get_user_data(ar); + size_t id; + ar(id); + ptr = stack.load_mutable_object(Slic3r::ObjectID(id)); + } + + // Store ObjectBase derived class onto the Undo / Redo stack as a separate object, + // store just the ObjectID to this stream. + template void save(BinaryOutputArchive &ar, const std::unique_ptr &ptr) + { + ar(cereal::get_user_data(ar).save_mutable_object(*ptr.get())); + } + + // Load ObjectBase derived class from the Undo / Redo stack as a separate object + // based on the ObjectID loaded from this stream. + template void load(BinaryInputArchive &ar, std::unique_ptr &ptr) + { + Slic3r::UndoRedo::StackImpl& stack = cereal::get_user_data(ar); + size_t id; + ar(id); + ptr.reset(stack.load_mutable_object(Slic3r::ObjectID(id))); + } + + // Store ObjectBase derived class onto the Undo / Redo stack as a separate object, + // store just the ObjectID to this stream. + void save(BinaryOutputArchive& ar, const Slic3r::ModelConfig &cfg) + { + ar(cereal::get_user_data(ar).save_mutable_object(cfg)); + } + + // Load ObjectBase derived class from the Undo / Redo stack as a separate object + // based on the ObjectID loaded from this stream. + void load(BinaryInputArchive& ar, Slic3r::ModelConfig &cfg) + { + Slic3r::UndoRedo::StackImpl& stack = cereal::get_user_data(ar); + size_t id; + ar(id); + stack.load_mutable_object(Slic3r::ObjectID(id), cfg); + } + + // Store ObjectBase derived class onto the Undo / Redo stack as a separate object, + // store just the ObjectID to this stream. + template void save(BinaryOutputArchive &ar, const std::shared_ptr &ptr) + { + ar(cereal::get_user_data(ar).save_immutable_object(const_cast&>(ptr))); + } + + // Load ObjectBase derived class from the Undo / Redo stack as a separate object + // based on the ObjectID loaded from this stream. + template void load(BinaryInputArchive &ar, std::shared_ptr &ptr) + { + Slic3r::UndoRedo::StackImpl &stack = cereal::get_user_data(ar); + size_t id; + ar(id); + ptr = stack.load_immutable_object(Slic3r::ObjectID(id)); + } +} + +#include +#include +#include +#include + +namespace Slic3r { +namespace UndoRedo { + +template std::shared_ptr& ImmutableObjectHistory::shared_ptr(StackImpl &stack) +{ + if (m_shared_object.get() == nullptr && ! this->m_serialized.empty()) { + // Deserialize the object. + std::istringstream iss(m_serialized); + { + Slic3r::UndoRedo::InputArchive archive(stack, iss); + typedef typename std::remove_const::type Type; + std::unique_ptr mesh(new Type()); + archive(*mesh.get()); + m_shared_object = std::move(mesh); + } + } + return m_shared_object; +} + +template ObjectID StackImpl::save_mutable_object(const T &object) +{ + // First find or allocate a history stack for the ObjectID of this object instance. + auto it_object_history = m_objects.find(object.id()); + if (it_object_history == m_objects.end()) + it_object_history = m_objects.insert(it_object_history, std::make_pair(object.id(), std::unique_ptr>(new MutableObjectHistory()))); + auto *object_history = static_cast*>(it_object_history->second.get()); + // Then serialize the object into a string. + std::ostringstream oss; + { + Slic3r::UndoRedo::OutputArchive archive(*this, oss); + archive(static_cast(object)); + } + object_history->save(m_active_snapshot_time, m_current_time, oss.str()); + return object.id(); +} + +template ObjectID StackImpl::save_immutable_object(std::shared_ptr &object) +{ + // First allocate a temporary ObjectID for this pointer. + ObjectID object_id = this->immutable_object_id(object); + // and find or allocate a history stack for the ObjectID associated to this shared_ptr. + auto it_object_history = m_objects.find(object_id); + if (it_object_history == m_objects.end()) + it_object_history = m_objects.emplace_hint(it_object_history, object_id, std::unique_ptr>(new ImmutableObjectHistory(object))); + // Then save the interval. + static_cast*>(it_object_history->second.get())->save(m_active_snapshot_time, m_current_time); + return object_id; +} + +template T* StackImpl::load_mutable_object(const Slic3r::ObjectID id) +{ + T *target = new T(); + this->load_mutable_object(id, *target); + return target; +} + +template std::shared_ptr StackImpl::load_immutable_object(const Slic3r::ObjectID id) +{ + // First find a history stack for the ObjectID of this object instance. + auto it_object_history = m_objects.find(id); + assert(it_object_history != m_objects.end()); + auto *object_history = static_cast*>(it_object_history->second.get()); + assert(object_history->has_snapshot(m_active_snapshot_time)); + return object_history->shared_ptr(*this); +} + +template void StackImpl::load_mutable_object(const Slic3r::ObjectID id, T &target) +{ + // First find a history stack for the ObjectID of this object instance. + auto it_object_history = m_objects.find(id); + assert(it_object_history != m_objects.end()); + auto *object_history = static_cast*>(it_object_history->second.get()); + // Then get the data associated with the object history and m_active_snapshot_time. + std::istringstream iss(object_history->load(m_active_snapshot_time)); + Slic3r::UndoRedo::InputArchive archive(*this, iss); + target.m_id = id; + archive(static_cast(target)); +} + +// Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. +void StackImpl::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos) +{ + // Release old snapshot data. + assert(m_active_snapshot_time <= m_current_time); + for (auto &kvp : m_objects) + kvp.second->release_after_timestamp(m_active_snapshot_time); + { + auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); + m_snapshots.erase(it, m_snapshots.end()); + } + // Take new snapshots. + this->save_mutable_object(model); + m_selection.volumes_and_instances.clear(); + m_selection.volumes_and_instances.reserve(selection.get_volume_idxs().size()); + m_selection.mode = selection.get_mode(); + for (unsigned int volume_idx : selection.get_volume_idxs()) + m_selection.volumes_and_instances.emplace_back(selection.get_volume(volume_idx)->geometry_id); + this->save_mutable_object(m_selection); + this->save_mutable_object(gizmos); + // Save the snapshot info. + m_snapshots.emplace_back(snapshot_name, m_current_time ++, model.id().id); + m_active_snapshot_time = m_current_time; + // Save snapshot info of the last "current" aka "top most" state, that is only being serialized + // if undoing an action. Such a snapshot has an invalid Model ID assigned if it was not taken yet. + m_snapshots.emplace_back(topmost_snapsnot_name, m_active_snapshot_time, 0); + // Release empty objects from the history. + this->collect_garbage(); + assert(this->valid()); +#ifdef SLIC3R_UNDOREDO_DEBUG + std::cout << "After snapshot" << std::endl; + this->print(); +#endif /* SLIC3R_UNDOREDO_DEBUG */ +} + +void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos) +{ + // Find the snapshot by time. It must exist. + const auto it_snapshot = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(timestamp)); + if (it_snapshot == m_snapshots.end() || it_snapshot->timestamp != timestamp) + throw std::runtime_error((boost::format("Snapshot with timestamp %1% does not exist") % timestamp).str()); + + m_active_snapshot_time = timestamp; + model.clear_objects(); + model.clear_materials(); + this->load_mutable_object(ObjectID(it_snapshot->model_id), model); + model.update_links_bottom_up_recursive(); + m_selection.volumes_and_instances.clear(); + this->load_mutable_object(m_selection.id(), m_selection); + gizmos.reset_all_states(); + this->load_mutable_object(gizmos.id(), gizmos); + // Sort the volumes so that we may use binary search. + std::sort(m_selection.volumes_and_instances.begin(), m_selection.volumes_and_instances.end()); + this->m_active_snapshot_time = timestamp; + assert(this->valid()); +} + +bool StackImpl::has_undo_snapshot() const +{ + assert(this->valid()); + auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); + return -- it != m_snapshots.begin(); +} + +bool StackImpl::has_redo_snapshot() const +{ + assert(this->valid()); + auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); + return ++ it != m_snapshots.end(); +} + +bool StackImpl::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load) +{ + assert(this->valid()); + if (time_to_load == SIZE_MAX) { + auto it_current = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); + if (-- it_current == m_snapshots.begin()) + return false; + time_to_load = it_current->timestamp; + } + assert(time_to_load < m_active_snapshot_time); + assert(std::binary_search(m_snapshots.begin(), m_snapshots.end(), Snapshot(time_to_load))); + if (m_active_snapshot_time == m_snapshots.back().timestamp && ! m_snapshots.back().is_topmost_captured()) { + // The current state is temporary. The current state needs to be captured to be redoable. + this->take_snapshot(topmost_snapsnot_name, model, selection, gizmos); + // The line above entered another topmost_snapshot_name. + assert(m_snapshots.back().is_topmost()); + assert(! m_snapshots.back().is_topmost_captured()); + // Pop it back, it is not needed as there is now a captured topmost state. + m_snapshots.pop_back(); + // current_time was extended, but it should not cause any harm. Resetting it back may complicate the logic unnecessarily. + //-- m_current_time; + assert(m_snapshots.back().is_topmost()); + assert(m_snapshots.back().is_topmost_captured()); + } + this->load_snapshot(time_to_load, model, gizmos); +#ifdef SLIC3R_UNDOREDO_DEBUG + std::cout << "After undo" << std::endl; + this->print(); +#endif /* SLIC3R_UNDOREDO_DEBUG */ + return true; +} + +bool StackImpl::redo(Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load) +{ + assert(this->valid()); + if (time_to_load == SIZE_MAX) { + auto it_current = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); + if (++ it_current == m_snapshots.end()) + return false; + time_to_load = it_current->timestamp; + } + assert(time_to_load > m_active_snapshot_time); + assert(std::binary_search(m_snapshots.begin(), m_snapshots.end(), Snapshot(time_to_load))); + this->load_snapshot(time_to_load, model, gizmos); +#ifdef SLIC3R_UNDOREDO_DEBUG + std::cout << "After redo" << std::endl; + this->print(); +#endif /* SLIC3R_UNDOREDO_DEBUG */ + return true; +} + +void StackImpl::collect_garbage() +{ + // Purge objects with empty histories. + for (auto it = m_objects.begin(); it != m_objects.end();) { + if (it->second->empty()) { + if (it->second->immutable_object_ptr() != nullptr) + // Release the immutable object from the ptr to ObjectID map. + m_shared_ptr_to_object_id.erase(it->second->immutable_object_ptr()); + it = m_objects.erase(it); + } else + ++ it; + } +} + +// Wrappers of the private implementation. +Stack::Stack() : pimpl(new StackImpl()) {} +Stack::~Stack() {} +void Stack::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos) { pimpl->take_snapshot(snapshot_name, model, selection, gizmos); } +bool Stack::has_undo_snapshot() const { return pimpl->has_undo_snapshot(); } +bool Stack::has_redo_snapshot() const { return pimpl->has_redo_snapshot(); } +bool Stack::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load) { return pimpl->undo(model, selection, gizmos, time_to_load); } +bool Stack::redo(Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load) { return pimpl->redo(model, gizmos, time_to_load); } +const Selection& Stack::selection_deserialized() const { return pimpl->selection_deserialized(); } + +const std::vector& Stack::snapshots() const { return pimpl->snapshots(); } +size_t Stack::active_snapshot_time() const { return pimpl->active_snapshot_time(); } + +} // namespace UndoRedo +} // namespace Slic3r + + +//FIXME we should have unit tests for testing serialization of basic types as DynamicPrintConfig. +#if 0 +#include "libslic3r/Config.hpp" +#include "libslic3r/PrintConfig.hpp" +namespace Slic3r { + bool test_dynamic_print_config_serialization() { + FullPrintConfig full_print_config; + DynamicPrintConfig cfg; + cfg.apply(full_print_config, false); + + std::string serialized; + try { + std::ostringstream ss; + cereal::BinaryOutputArchive oarchive(ss); + oarchive(cfg); + serialized = ss.str(); + } catch (std::runtime_error e) { + e.what(); + } + + DynamicPrintConfig cfg2; + try { + std::stringstream ss(serialized); + cereal::BinaryInputArchive iarchive(ss); + iarchive(cfg2); + } catch (std::runtime_error e) { + e.what(); + } + + if (cfg == cfg2) { + printf("Yes!\n"); + return true; + } + printf("No!\n"); + return false; + } +} // namespace Slic3r +#endif diff --git a/src/slic3r/Utils/UndoRedo.hpp b/src/slic3r/Utils/UndoRedo.hpp new file mode 100644 index 000000000..236970b68 --- /dev/null +++ b/src/slic3r/Utils/UndoRedo.hpp @@ -0,0 +1,95 @@ +#ifndef slic3r_Utils_UndoRedo_hpp_ +#define slic3r_Utils_UndoRedo_hpp_ + +#include +#include +#include +#include +#include + +#include + +namespace Slic3r { + +class Model; + +namespace GUI { + class Selection; + class GLGizmosManager; +} // namespace GUI + +namespace UndoRedo { + +struct Snapshot +{ + Snapshot(size_t timestamp) : timestamp(timestamp) {} + Snapshot(const std::string &name, size_t timestamp, size_t model_id) : name(name), timestamp(timestamp), model_id(model_id) {} + + std::string name; + size_t timestamp; + size_t model_id; + + bool operator< (const Snapshot &rhs) const { return this->timestamp < rhs.timestamp; } + bool operator==(const Snapshot &rhs) const { return this->timestamp == rhs.timestamp; } + + // The topmost snapshot represents the current state when going forward. + bool is_topmost() const; + // The topmost snapshot is not being serialized to the Undo / Redo stack until going back in time, + // when the top most state is being serialized, so we can redo back to the top most state. + bool is_topmost_captured() const { assert(this->is_topmost()); return model_id > 0; } +}; + +// Excerpt of Slic3r::GUI::Selection for serialization onto the Undo / Redo stack. +struct Selection : public Slic3r::ObjectBase { + unsigned char mode; + std::vector> volumes_and_instances; + template void serialize(Archive &ar) { ar(mode, volumes_and_instances); } +}; + +class StackImpl; + +class Stack +{ +public: + // Stack needs to be initialized. An empty stack is not valid, there must be a "New Project" status stored at the beginning. + // The first "New Project" snapshot shall not be removed. + Stack(); + ~Stack(); + + // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. + void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos); + + // To be queried to enable / disable the Undo / Redo buttons at the UI. + bool has_undo_snapshot() const; + bool has_redo_snapshot() const; + + // Roll back the time. If time_to_load is SIZE_MAX, the previous snapshot is activated. + // Undoing an action may need to take a snapshot of the current application state, so that redo to the current state is possible. + bool undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load = SIZE_MAX); + + // Jump forward in time. If time_to_load is SIZE_MAX, the next snapshot is activated. + bool redo(Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load = SIZE_MAX); + + // Snapshot history (names with timestamps). + // Each snapshot indicates start of an interval in which this operation is performed. + // There is one additional snapshot taken at the very end, which indicates the current unnamed state. + + const std::vector& snapshots() const; + // Timestamp of the active snapshot. One of the snapshots of this->snapshots() shall have Snapshot::timestamp equal to this->active_snapshot_time(). + // The snapshot time indicates start of an operation, which is finished at the time of the following snapshot, therefore + // the active snapshot is the successive snapshot. The same logic applies to the time_to_load parameter of undo() and redo() operations. + size_t active_snapshot_time() const; + + // After load_snapshot() / undo() / redo() the selection is deserialized into a list of ObjectIDs, which needs to be converted + // into the list of GLVolume pointers once the 3D scene is updated. + const Selection& selection_deserialized() const; + +private: + friend class StackImpl; + std::unique_ptr pimpl; +}; + +}; // namespace UndoRedo +}; // namespace Slic3r + +#endif /* slic3r_Utils_UndoRedo_hpp_ */ diff --git a/t/combineinfill.t b/t/combineinfill.t index 8aa0ff5e3..282bf467a 100644 --- a/t/combineinfill.t +++ b/t/combineinfill.t @@ -89,7 +89,7 @@ plan tests => 8; # we disable combination after infill has been generated $config->set('infill_every_layers', 1); - $print->apply_config_perl_tests_only($config); + $print->apply($print->print->model->clone, $config); $print->process; ok !(defined first { @{$_->get_region(0)->fill_surfaces} == 0 } diff --git a/t/multi.t b/t/multi.t index 75ce0c286..8e7225bec 100644 --- a/t/multi.t +++ b/t/multi.t @@ -25,7 +25,9 @@ use Slic3r::Test; $config->set('extruder_offset', [ [0,0], [20,0], [0,20], [20,20] ]); $config->set('temperature', [200, 180, 170, 160]); $config->set('first_layer_temperature', [206, 186, 166, 156]); - $config->set('toolchange_gcode', ';toolchange'); # test that it doesn't crash when this is supplied + $config->set('toolchange_gcode', 'T[next_extruder] ;toolchange'); # test that it doesn't crash when this is supplied + # Since July 2019, PrusaSlicer only emits automatic Tn command in case that the toolchange_gcode is empty + # The "T[next_extruder]" is therefore needed in this test. my $print = Slic3r::Test::init_print('20mm_cube', config => $config); diff --git a/t/print.t b/t/print.t index be2db3431..2144e80c1 100644 --- a/t/print.t +++ b/t/print.t @@ -34,25 +34,30 @@ use Slic3r::Test; { # this represents the aggregate config from presets my $config = Slic3r::Config::new_from_defaults; + # Define 4 extruders. + $config->set('nozzle_diameter', [0.4, 0.4, 0.4, 0.4]); # user adds one object to the plater my $print = Slic3r::Test::init_print(my $model = Slic3r::Test::model('20mm_cube'), config => $config); # user sets a per-region option - $print->print->objects->[0]->model_object->config->set('fill_density', 100); - $print->print->reload_object(0); + my $model2 = $model->clone; + $model2->get_object(0)->config->set('fill_density', 100); + $print->apply($model2, $config); + is $print->print->regions->[0]->config->fill_density, 100, 'region config inherits model object config'; # user exports G-code, thus the default config is reapplied - $print->print->apply_config_perl_tests_only($config); - - is $print->print->regions->[0]->config->fill_density, 100, 'apply_config() does not override per-object settings'; + $model2->get_object(0)->config->erase('fill_density'); + $print->apply($model2, $config); + + is $print->print->regions->[0]->config->fill_density, 20, 'region config is resetted'; # user assigns object extruders - $print->print->objects->[0]->model_object->config->set('extruder', 3); - $print->print->objects->[0]->model_object->config->set('perimeter_extruder', 2); - $print->print->reload_object(0); - + $model2->get_object(0)->config->set('extruder', 3); + $model2->get_object(0)->config->set('perimeter_extruder', 2); + $print->apply($model2, $config); + is $print->print->regions->[0]->config->infill_extruder, 3, 'extruder setting is correctly expanded'; is $print->print->regions->[0]->config->perimeter_extruder, 2, 'extruder setting does not override explicitely specified extruders'; } diff --git a/t/skirt_brim.t b/t/skirt_brim.t index b05435784..beb3c94c5 100644 --- a/t/skirt_brim.t +++ b/t/skirt_brim.t @@ -91,6 +91,8 @@ use Slic3r::Test; { my $config = Slic3r::Config::new_from_defaults; + # Define 4 extruders. + $config->set('nozzle_diameter', [0.4, 0.4, 0.4, 0.4]); $config->set('layer_height', 0.4); $config->set('first_layer_height', 0.4); $config->set('skirts', 1); @@ -106,7 +108,7 @@ use Slic3r::Test; # we enable support material after skirt has been generated $config->set('support_material', 1); - $print->apply_config_perl_tests_only($config); + $print->apply($print->print->model->clone, $config); my $skirt_length = 0; my @extrusion_points = (); diff --git a/xs/t/15_config.t b/xs/t/15_config.t index 397f7e479..1f9fc939b 100644 --- a/xs/t/15_config.t +++ b/xs/t/15_config.t @@ -9,19 +9,19 @@ use Test::More tests => 147; foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintConfig) { $config->set('layer_height', 0.3); ok abs($config->get('layer_height') - 0.3) < 1e-4, 'set/get float'; - is $config->serialize('layer_height'), '0.3', 'serialize float'; + is $config->opt_serialize('layer_height'), '0.3', 'serialize float'; $config->set('perimeters', 2); is $config->get('perimeters'), 2, 'set/get int'; - is $config->serialize('perimeters'), '2', 'serialize int'; + is $config->opt_serialize('perimeters'), '2', 'serialize int'; $config->set('extrusion_axis', 'A'); is $config->get('extrusion_axis'), 'A', 'set/get string'; - is $config->serialize('extrusion_axis'), 'A', 'serialize string'; + is $config->opt_serialize('extrusion_axis'), 'A', 'serialize string'; $config->set('notes', "foo\nbar"); is $config->get('notes'), "foo\nbar", 'set/get string with newline'; - is $config->serialize('notes'), 'foo\nbar', 'serialize string with newline'; + is $config->opt_serialize('notes'), 'foo\nbar', 'serialize string with newline'; $config->set_deserialize('notes', 'bar\nbaz'); is $config->get('notes'), "bar\nbaz", 'deserialize string with newline'; @@ -59,7 +59,7 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo ) { $config->set('filament_notes', $test_data->{values}); - is $config->serialize('filament_notes'), $test_data->{serialized}, 'serialize multi-string value ' . $test_data->{name}; + is $config->opt_serialize('filament_notes'), $test_data->{serialized}, 'serialize multi-string value ' . $test_data->{name}; $config->set_deserialize('filament_notes', ''); is_deeply $config->get('filament_notes'), [], 'deserialize multi-string value - empty ' . $test_data->{name}; $config->set_deserialize('filament_notes', $test_data->{serialized}); @@ -68,12 +68,12 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo $config->set('first_layer_height', 0.3); ok abs($config->get('first_layer_height') - 0.3) < 1e-4, 'set/get absolute floatOrPercent'; - is $config->serialize('first_layer_height'), '0.3', 'serialize absolute floatOrPercent'; + is $config->opt_serialize('first_layer_height'), '0.3', 'serialize absolute floatOrPercent'; $config->set('first_layer_height', '50%'); $config->get_abs_value('first_layer_height'); ok abs($config->get_abs_value('first_layer_height') - 0.15) < 1e-4, 'set/get relative floatOrPercent'; - is $config->serialize('first_layer_height'), '50%', 'serialize relative floatOrPercent'; + is $config->opt_serialize('first_layer_height'), '50%', 'serialize relative floatOrPercent'; # Uh-oh, we have no point option to test at the moment #ok $config->set('print_center', [50,80]), 'valid point coordinates'; @@ -86,11 +86,10 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo $config->set('use_relative_e_distances', 1); is $config->get('use_relative_e_distances'), 1, 'set/get bool'; - is $config->serialize('use_relative_e_distances'), '1', 'serialize bool'; - + is $config->opt_serialize('use_relative_e_distances'), '1', 'serialize bool'; $config->set('gcode_flavor', 'teacup'); is $config->get('gcode_flavor'), 'teacup', 'set/get enum'; - is $config->serialize('gcode_flavor'), 'teacup', 'serialize enum'; + is $config->opt_serialize('gcode_flavor'), 'teacup', 'serialize enum'; $config->set_deserialize('gcode_flavor', 'mach3'); is $config->get('gcode_flavor'), 'mach3', 'deserialize enum (gcode_flavor)'; $config->set_deserialize('gcode_flavor', 'machinekit'); @@ -106,7 +105,7 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo is_deeply [ map $_->pp, @{$config->get('extruder_offset')} ], [[10,20],[30,45]], 'set/get points'; $config->set('extruder_offset', [Slic3r::Pointf->new(10,20),Slic3r::Pointf->new(30,45)]); is_deeply [ map $_->pp, @{$config->get('extruder_offset')} ], [[10,20],[30,45]], 'set/get points'; - is $config->serialize('extruder_offset'), '10x20,30x45', 'serialize points'; + is $config->opt_serialize('extruder_offset'), '10x20,30x45', 'serialize points'; $config->set_deserialize('extruder_offset', '20x10'); is_deeply [ map $_->pp, @{$config->get('extruder_offset')} ], [[20,10]], 'deserialize points'; $config->set_deserialize('extruder_offset', '0x0'); @@ -120,7 +119,7 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo # truncate ->get() to first decimal digit $config->set('nozzle_diameter', [0.2,3]); is_deeply [ map int($_*10)/10, @{$config->get('nozzle_diameter')} ], [0.2,3], 'set/get floats'; - is $config->serialize('nozzle_diameter'), '0.2,3', 'serialize floats'; + is $config->opt_serialize('nozzle_diameter'), '0.2,3', 'serialize floats'; $config->set_deserialize('nozzle_diameter', '0.1,0.4'); is_deeply [ map int($_*10)/10, @{$config->get('nozzle_diameter')} ], [0.1,0.4], 'deserialize floats'; $config->set_deserialize('nozzle_diameter', '3'); @@ -133,7 +132,7 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo $config->set('temperature', [180,210]); is_deeply $config->get('temperature'), [180,210], 'set/get ints'; - is $config->serialize('temperature'), '180,210', 'serialize ints'; + is $config->opt_serialize('temperature'), '180,210', 'serialize ints'; $config->set_deserialize('temperature', '195,220'); is_deeply $config->get('temperature'), [195,220], 'deserialize ints'; { @@ -147,7 +146,7 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo is $config->get_at('wipe', 0), 1, 'get_at bools'; is $config->get_at('wipe', 1), 0, 'get_at bools'; is $config->get_at('wipe', 9), 1, 'get_at bools'; - is $config->serialize('wipe'), '1,0', 'serialize bools'; + is $config->opt_serialize('wipe'), '1,0', 'serialize bools'; $config->set_deserialize('wipe', '0,1,1'); is_deeply $config->get('wipe'), [0,1,1], 'deserialize bools'; $config->set_deserialize('wipe', ''); @@ -162,7 +161,7 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo $config->set('post_process', ['foo','bar']); is_deeply $config->get('post_process'), ['foo','bar'], 'set/get strings'; - is $config->serialize('post_process'), 'foo;bar', 'serialize strings'; + is $config->opt_serialize('post_process'), 'foo;bar', 'serialize strings'; $config->set_deserialize('post_process', 'bar;baz'); is_deeply $config->get('post_process'), ['bar','baz'], 'deserialize strings'; { diff --git a/xs/t/19_model.t b/xs/t/19_model.t index d6f6d97a1..48a000e46 100644 --- a/xs/t/19_model.t +++ b/xs/t/19_model.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 4; +use Test::More tests => 3; { my $model = Slic3r::Model->new; @@ -14,9 +14,9 @@ use Test::More tests => 4; $object->origin_translation->translate(10,0,0); is_deeply \@{$object->origin_translation}, [10,0,0], 'origin_translation is modified by ref'; - my $lhr = [ [ 5, 10, 0.1 ] ]; - $object->set_layer_height_ranges($lhr); - is_deeply $object->layer_height_ranges, $lhr, 'layer_height_ranges roundtrip'; +# my $lhr = [ [ 5, 10, 0.1 ] ]; +# $object->set_layer_height_ranges($lhr); +# is_deeply $object->layer_height_ranges, $lhr, 'layer_height_ranges roundtrip'; } __END__ diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp index 4d48a2c6f..f5e6ffb05 100644 --- a/xs/xsp/Config.xsp +++ b/xs/xsp/Config.xsp @@ -33,7 +33,7 @@ %code{% RETVAL = ConfigBase__set_deserialize(THIS, opt_key, str); %}; void set_ifndef(t_config_option_key opt_key, SV* value, bool deserialize = false) %code{% ConfigBase__set_ifndef(THIS, opt_key, value, deserialize); %}; - std::string serialize(t_config_option_key opt_key); + std::string opt_serialize(t_config_option_key opt_key); double get_abs_value(t_config_option_key opt_key); %name{get_abs_value_over} double get_abs_value(t_config_option_key opt_key, double ratio_over); @@ -49,7 +49,7 @@ void erase(t_config_option_key opt_key); void normalize(); %name{setenv} void setenv_(); - double min_object_distance() %code{% RETVAL = PrintConfig::min_object_distance(THIS); %}; + double min_object_distance() %code{% PrintConfig cfg; cfg.apply(*THIS, true); RETVAL = cfg.min_object_distance(); %}; static DynamicPrintConfig* load(char *path) %code%{ auto config = new DynamicPrintConfig(); @@ -95,7 +95,7 @@ %code{% RETVAL = ConfigBase__set_deserialize(THIS, opt_key, str); %}; void set_ifndef(t_config_option_key opt_key, SV* value, bool deserialize = false) %code{% ConfigBase__set_ifndef(THIS, opt_key, value, deserialize); %}; - std::string serialize(t_config_option_key opt_key); + std::string opt_serialize(t_config_option_key opt_key); double get_abs_value(t_config_option_key opt_key); %name{get_abs_value_over} double get_abs_value(t_config_option_key opt_key, double ratio_over); diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index 6a2cc6080..35b1c01ce 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -206,16 +206,12 @@ ModelMaterial::attributes() Ref model() %code%{ RETVAL = THIS->get_model(); %}; - t_layer_height_ranges layer_height_ranges() - %code%{ RETVAL = THIS->layer_height_ranges; %}; - void set_layer_height_ranges(t_layer_height_ranges ranges) - %code%{ THIS->layer_height_ranges = ranges; %}; - Ref origin_translation() %code%{ RETVAL = &THIS->origin_translation; %}; void set_origin_translation(Vec3d* point) %code%{ THIS->origin_translation = *point; %}; + void ensure_on_bed(); bool needed_repair() const; int materials_count() const; int facets_count(); diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index c35f967f8..df5f48587 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -70,6 +70,8 @@ _constant() Print(); ~Print(); + Ref model() + %code%{ RETVAL = const_cast(&THIS->model()); %}; Ref config() %code%{ RETVAL = const_cast(static_cast(&THIS->config())); %}; Ref placeholder_parser() @@ -100,7 +102,6 @@ _constant() %code%{ RETVAL = const_cast(&THIS->objects()); %}; Ref get_object(int idx) %code%{ RETVAL = THIS->objects()[idx]; %}; - void reload_object(int idx); size_t object_count() %code%{ RETVAL = THIS->objects().size(); %}; @@ -141,9 +142,8 @@ _constant() } %}; - void add_model_object(ModelObject* model_object, int idx = -1); - bool apply_config_perl_tests_only(DynamicPrintConfig* config) - %code%{ RETVAL = THIS->apply_config_perl_tests_only(*config); %}; + bool apply(Model *model, DynamicPrintConfig* config) + %code%{ RETVAL = THIS->apply(*model, *config); %}; bool has_infinite_skirt(); std::vector extruders() const; int validate() %code%{