diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 67528096c..b9ae911db 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -140,9 +140,21 @@ sub new { }; # callback to react to gizmo rotate + # omitting last three parameters means rotation around Z + # otherwise they are the components of the rotation axis vector my $on_gizmo_rotate = sub { - my ($angle_z) = @_; - $self->rotate(rad2deg($angle_z), Z, 'absolute'); + my ($angle, $axis_x, $axis_y, $axis_z) = @_; + if (!defined $axis_x) { + $self->rotate(rad2deg($angle), Z, 'absolute'); + } + else { + + print "angle: "; + print $angle; + print "\n"; + + $self->rotate(rad2deg($angle), undef, 'absolute', $axis_x, $axis_y, $axis_z) if $angle != 0; + } }; # callback to update object's geometry info while using gizmos @@ -1038,9 +1050,10 @@ sub set_number_of_copies { my $model_object = $self->{model}->objects->[$obj_idx]; # prompt user - my $copies = Wx::GetNumberFromUser("", L("Enter the number of copies of the selected object:"), L("Copies"), $model_object->instances_count, 0, 1000, $self); + my $copies = -1; + $copies = Wx::GetNumberFromUser("", L("Enter the number of copies of the selected object:"), L("Copies"), $model_object->instances_count, 0, 1000, $self); my $diff = $copies - $model_object->instances_count; - if ($diff == 0) { + if ($diff == 0 || $copies == -1) { # no variation $self->resume_background_process; } elsif ($diff > 0) { @@ -1073,28 +1086,40 @@ sub _get_number_from_user { } sub rotate { - my ($self, $angle, $axis, $relative_key) = @_; + my ($self, $angle, $axis, $relative_key, $axis_x, $axis_y, $axis_z) = @_; $relative_key //= 'absolute'; # relative or absolute coordinates - $axis //= Z; # angle is in degrees - + $axis_x //= 0; + $axis_y //= 0; + $axis_z //= 0; my $relative = $relative_key eq 'relative'; - + my ($obj_idx, $object) = $self->selected_object; return if !defined $obj_idx; - + my $model_object = $self->{model}->objects->[$obj_idx]; my $model_instance = $model_object->instances->[0]; - + if (!defined $angle) { my $axis_name = $axis == X ? 'X' : $axis == Y ? 'Y' : 'Z'; my $default = $axis == Z ? rad2deg($model_instance->rotation) : 0; $angle = $self->_get_number_from_user(L("Enter the rotation angle:"), L("Rotate around ").$axis_name.(" axis"), L("Invalid rotation angle entered"), $default); return if $angle eq ''; } + + # Let's calculate vector of rotation axis (if we don't have it already) + # The minus is there so that the direction is the same as was established + if (defined $axis) { + if ($axis == X) { + $axis_x = -1; + } + if ($axis == Y) { + $axis_y = -1; + } + } $self->stop_background_process; - if ($axis == Z) { + if (defined $axis && $axis == Z) { my $new_angle = deg2rad($angle); foreach my $inst (@{ $model_object->instances }) { my $rotation = ($relative ? $inst->rotation : 0.) + $new_angle; @@ -1109,13 +1134,15 @@ sub rotate { } # $object->transform_thumbnail($self->{model}, $obj_idx); } else { - # rotation around X and Y needs to be performed on mesh - # so we first apply any Z rotation - if ($model_instance->rotation != 0) { - $model_object->rotate($model_instance->rotation, Z); - $_->set_rotation(0) for @{ $model_object->instances }; + if (defined $axis) { + # rotation around X and Y needs to be performed on mesh + # so we first apply any Z rotation + if ($model_instance->rotation != 0) { + $model_object->rotate($model_instance->rotation, Slic3r::Pointf3->new(0, 0, -1)); + $_->set_rotation(0) for @{ $model_object->instances }; + } } - $model_object->rotate(deg2rad($angle), $axis); + $model_object->rotate(deg2rad($angle), Slic3r::Pointf3->new($axis_x, $axis_y, $axis_z)); # # realign object to Z = 0 # $model_object->center_around_origin; @@ -1141,7 +1168,7 @@ sub mirror { # apply Z rotation before mirroring if ($model_instance->rotation != 0) { - $model_object->rotate($model_instance->rotation, Z); + $model_object->rotate($model_instance->rotation, Slic3r::Pointf3->new(0, 0, 1)); $_->set_rotation(0) for @{ $model_object->instances }; } @@ -1188,7 +1215,7 @@ sub changescale { # apply Z rotation before scaling if ($model_instance->rotation != 0) { - $model_object->rotate($model_instance->rotation, Z); + $model_object->rotate($model_instance->rotation, Slic3r::Pointf3->new(0, 0, 1)); $_->set_rotation(0) for @{ $model_object->instances }; } diff --git a/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm b/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm index 35aa28818..26a6fdec3 100644 --- a/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm +++ b/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm @@ -137,7 +137,7 @@ sub new { # Adjust position / orientation of the split object halves. if ($self->{new_model_objects}{lower}) { if ($self->{cut_options}{rotate_lower}) { - $self->{new_model_objects}{lower}->rotate(PI, X); + $self->{new_model_objects}{lower}->rotate(PI, Slic3r::Pointf3->new(1,0,0)); $self->{new_model_objects}{lower}->center_around_origin; # align to Z = 0 } } diff --git a/resources/icons/overlay/layflat_hover.png b/resources/icons/overlay/layflat_hover.png new file mode 100644 index 000000000..afce81d19 Binary files /dev/null and b/resources/icons/overlay/layflat_hover.png differ diff --git a/resources/icons/overlay/layflat_off.png b/resources/icons/overlay/layflat_off.png new file mode 100644 index 000000000..70d198112 Binary files /dev/null and b/resources/icons/overlay/layflat_off.png differ diff --git a/resources/icons/overlay/layflat_on.png b/resources/icons/overlay/layflat_on.png new file mode 100644 index 000000000..3c1891b3c Binary files /dev/null and b/resources/icons/overlay/layflat_on.png differ diff --git a/xs/src/avrdude/avrdude-slic3r.cpp b/xs/src/avrdude/avrdude-slic3r.cpp index 0577fe6d0..3037f5284 100644 --- a/xs/src/avrdude/avrdude-slic3r.cpp +++ b/xs/src/avrdude/avrdude-slic3r.cpp @@ -2,6 +2,10 @@ #include #include +#include +#include +#include +#include extern "C" { #include "ac_cfg.h" @@ -28,6 +32,11 @@ static void avrdude_progress_handler_closure(const char *task, unsigned progress (*progress_fn)(task, progress); } +static void avrdude_oom_handler(const char *context, void *user_p) +{ + throw std::bad_alloc(); +} + // Private @@ -47,16 +56,22 @@ struct AvrDude::priv priv(std::string &&sys_config) : sys_config(sys_config) {} + void set_handlers(); + void unset_handlers(); int run_one(const std::vector &args); int run(); + + struct HandlerGuard + { + priv &p; + + HandlerGuard(priv &p) : p(p) { p.set_handlers(); } + ~HandlerGuard() { p.unset_handlers(); } + }; }; -int AvrDude::priv::run_one(const std::vector &args) { - std::vector c_args {{ const_cast(PACKAGE_NAME) }}; - for (const auto &arg : args) { - c_args.push_back(const_cast(arg.data())); - } - +void AvrDude::priv::set_handlers() +{ if (message_fn) { ::avrdude_message_handler_set(avrdude_message_handler_closure, reinterpret_cast(&message_fn)); } else { @@ -69,10 +84,27 @@ int AvrDude::priv::run_one(const std::vector &args) { ::avrdude_progress_handler_set(nullptr, nullptr); } - const auto res = ::avrdude_main(static_cast(c_args.size()), c_args.data(), sys_config.c_str()); + ::avrdude_oom_handler_set(avrdude_oom_handler, nullptr); +} +void AvrDude::priv::unset_handlers() +{ ::avrdude_message_handler_set(nullptr, nullptr); ::avrdude_progress_handler_set(nullptr, nullptr); + ::avrdude_oom_handler_set(nullptr, nullptr); +} + + +int AvrDude::priv::run_one(const std::vector &args) { + std::vector c_args {{ const_cast(PACKAGE_NAME) }}; + for (const auto &arg : args) { + c_args.push_back(const_cast(arg.data())); + } + + HandlerGuard guard(*this); + + const auto res = ::avrdude_main(static_cast(c_args.size()), c_args.data(), sys_config.c_str()); + return res; } @@ -134,7 +166,7 @@ AvrDude& AvrDude::on_complete(CompleteFn fn) int AvrDude::run_sync() { - return p->run(); + return p ? p->run() : -1; } AvrDude::Ptr AvrDude::run() @@ -143,19 +175,46 @@ AvrDude::Ptr AvrDude::run() if (self->p) { auto avrdude_thread = std::thread([self]() { - bool cancel = false; - int res = -1; + try { + if (self->p->run_fn) { + self->p->run_fn(self); + } - if (self->p->run_fn) { - self->p->run_fn(); - } + if (! self->p->cancelled) { + self->p->exit_code = self->p->run(); + } - if (! self->p->cancelled) { - self->p->exit_code = self->p->run(); - } + if (self->p->complete_fn) { + self->p->complete_fn(); + } + } catch (const std::exception &ex) { + self->p->exit_code = EXIT_EXCEPTION; - if (self->p->complete_fn) { - self->p->complete_fn(); + static const char *msg = "An exception was thrown in the background thread:\n"; + + const char *what = ex.what(); + auto &message_fn = self->p->message_fn; + if (message_fn) { + message_fn(msg, sizeof(msg)); + message_fn(what, std::strlen(what)); + message_fn("\n", 1); + } + + if (self->p->complete_fn) { + self->p->complete_fn(); + } + } catch (...) { + self->p->exit_code = EXIT_EXCEPTION; + + static const char *msg = "An unkown exception was thrown in the background thread.\n"; + + if (self->p->message_fn) { + self->p->message_fn(msg, sizeof(msg)); + } + + if (self->p->complete_fn) { + self->p->complete_fn(); + } } }); diff --git a/xs/src/avrdude/avrdude-slic3r.hpp b/xs/src/avrdude/avrdude-slic3r.hpp index 86e097034..754e1e345 100644 --- a/xs/src/avrdude/avrdude-slic3r.hpp +++ b/xs/src/avrdude/avrdude-slic3r.hpp @@ -11,8 +11,13 @@ namespace Slic3r { class AvrDude { public: + enum { + EXIT_SUCCEESS = 0, + EXIT_EXCEPTION = -1000, + }; + typedef std::shared_ptr Ptr; - typedef std::function RunFn; + typedef std::function RunFn; typedef std::function MessageFn; typedef std::function ProgressFn; typedef std::function CompleteFn; @@ -49,10 +54,18 @@ public: // This has no effect when using run_sync(). AvrDude& on_complete(CompleteFn fn); + // Perform AvrDude invocation(s) synchronously on the current thread int run_sync(); + + // Perform AvrDude invocation(s) on a background thread. + // Current instance is moved into a shared_ptr which is returned (and also passed in on_run, if any). Ptr run(); + // Cancel current operation void cancel(); + + // If there is a background thread and it is joinable, join() it, + // that is, wait for it to finish. void join(); bool cancelled(); // Whether avrdude run was cancelled diff --git a/xs/src/avrdude/avrdude.h b/xs/src/avrdude/avrdude.h index 9f724433f..f4c92a75d 100644 --- a/xs/src/avrdude/avrdude.h +++ b/xs/src/avrdude/avrdude.h @@ -39,6 +39,12 @@ typedef void (*avrdude_progress_handler_t)(const char *task, unsigned progress, void avrdude_progress_handler_set(avrdude_progress_handler_t newhandler, void *user_p); void avrdude_progress_external(const char *task, unsigned progress); +// OOM handler +typedef void (*avrdude_oom_handler_t)(const char *context, void *user_p); +void avrdude_oom_handler_set(avrdude_oom_handler_t newhandler, void *user_p); +void avrdude_oom(const char *context); + + // Cancellation void avrdude_cancel(); diff --git a/xs/src/avrdude/avrpart.c b/xs/src/avrdude/avrpart.c index b04851ac1..d0bb951ee 100644 --- a/xs/src/avrdude/avrpart.c +++ b/xs/src/avrdude/avrpart.c @@ -36,8 +36,9 @@ OPCODE * avr_new_opcode(void) m = (OPCODE *)malloc(sizeof(*m)); if (m == NULL) { - avrdude_message(MSG_INFO, "avr_new_opcode(): out of memory\n"); - exit(1); + // avrdude_message(MSG_INFO, "avr_new_opcode(): out of memory\n"); + // exit(1); + avrdude_oom("avr_new_opcode(): out of memory\n"); } memset(m, 0, sizeof(*m)); @@ -56,8 +57,9 @@ static OPCODE * avr_dup_opcode(OPCODE * op) m = (OPCODE *)malloc(sizeof(*m)); if (m == NULL) { - avrdude_message(MSG_INFO, "avr_dup_opcode(): out of memory\n"); - exit(1); + // avrdude_message(MSG_INFO, "avr_dup_opcode(): out of memory\n"); + // exit(1); + avrdude_oom("avr_dup_opcode(): out of memory\n"); } memcpy(m, op, sizeof(*m)); @@ -249,8 +251,9 @@ AVRMEM * avr_new_memtype(void) m = (AVRMEM *)malloc(sizeof(*m)); if (m == NULL) { - avrdude_message(MSG_INFO, "avr_new_memtype(): out of memory\n"); - exit(1); + // avrdude_message(MSG_INFO, "avr_new_memtype(): out of memory\n"); + // exit(1); + avrdude_oom("avr_new_memtype(): out of memory\n"); } memset(m, 0, sizeof(*m)); @@ -300,9 +303,10 @@ AVRMEM * avr_dup_mem(AVRMEM * m) if (m->buf != NULL) { n->buf = (unsigned char *)malloc(n->size); if (n->buf == NULL) { - avrdude_message(MSG_INFO, "avr_dup_mem(): out of memory (memsize=%d)\n", - n->size); - exit(1); + // avrdude_message(MSG_INFO, "avr_dup_mem(): out of memory (memsize=%d)\n", + // n->size); + // exit(1); + avrdude_oom("avr_dup_mem(): out of memory"); } memcpy(n->buf, m->buf, n->size); } @@ -310,9 +314,10 @@ AVRMEM * avr_dup_mem(AVRMEM * m) if (m->tags != NULL) { n->tags = (unsigned char *)malloc(n->size); if (n->tags == NULL) { - avrdude_message(MSG_INFO, "avr_dup_mem(): out of memory (memsize=%d)\n", - n->size); - exit(1); + // avrdude_message(MSG_INFO, "avr_dup_mem(): out of memory (memsize=%d)\n", + // n->size); + // exit(1); + avrdude_oom("avr_dup_mem(): out of memory"); } memcpy(n->tags, m->tags, n->size); } @@ -441,8 +446,9 @@ AVRPART * avr_new_part(void) p = (AVRPART *)malloc(sizeof(AVRPART)); if (p == NULL) { - avrdude_message(MSG_INFO, "new_part(): out of memory\n"); - exit(1); + // avrdude_message(MSG_INFO, "new_part(): out of memory\n"); + // exit(1); + avrdude_oom("new_part(): out of memory\n"); } memset(p, 0, sizeof(*p)); diff --git a/xs/src/avrdude/buspirate.c b/xs/src/avrdude/buspirate.c index 435c4ce53..5875d4283 100644 --- a/xs/src/avrdude/buspirate.c +++ b/xs/src/avrdude/buspirate.c @@ -1135,9 +1135,10 @@ static void buspirate_setup(struct programmer_t *pgm) { /* Allocate private data */ if ((pgm->cookie = calloc(1, sizeof(struct pdata))) == 0) { - avrdude_message(MSG_INFO, "%s: buspirate_initpgm(): Out of memory allocating private data\n", - progname); - exit(1); + // avrdude_message(MSG_INFO, "%s: buspirate_initpgm(): Out of memory allocating private data\n", + // progname); + // exit(1); + avrdude_oom("buspirate_initpgm(): Out of memory allocating private data\n"); } PDATA(pgm)->serial_recv_timeout = 100; } diff --git a/xs/src/avrdude/butterfly.c b/xs/src/avrdude/butterfly.c index de9a3175f..beb5e04de 100644 --- a/xs/src/avrdude/butterfly.c +++ b/xs/src/avrdude/butterfly.c @@ -63,9 +63,10 @@ struct pdata static void butterfly_setup(PROGRAMMER * pgm) { if ((pgm->cookie = malloc(sizeof(struct pdata))) == 0) { - avrdude_message(MSG_INFO, "%s: butterfly_setup(): Out of memory allocating private data\n", - progname); - exit(1); + // avrdude_message(MSG_INFO, "%s: butterfly_setup(): Out of memory allocating private data\n", + // progname); + // exit(1); + avrdude_oom("butterfly_setup(): Out of memory allocating private data\n"); } memset(pgm->cookie, 0, sizeof(struct pdata)); } diff --git a/xs/src/avrdude/lexer.c b/xs/src/avrdude/lexer.c index 93249a9ab..f2d8adb4b 100644 --- a/xs/src/avrdude/lexer.c +++ b/xs/src/avrdude/lexer.c @@ -2834,7 +2834,8 @@ YY_BUFFER_STATE yy_scan_bytes (const char * yybytes, int _yybytes_len ) n = (yy_size_t) (_yybytes_len + 2); buf = (char *) yyalloc( n ); if ( ! buf ) - YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" ); + // YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" ); + avrdude_oom("out of dynamic memory in yy_scan_bytes()"); for ( i = 0; i < _yybytes_len; ++i ) buf[i] = yybytes[i]; @@ -2859,8 +2860,9 @@ YY_BUFFER_STATE yy_scan_bytes (const char * yybytes, int _yybytes_len ) static void yynoreturn yy_fatal_error (const char* msg ) { - fprintf( stderr, "%s\n", msg ); - exit( YY_EXIT_FAILURE ); + fprintf( stderr, "%s\n", msg ); + // exit( YY_EXIT_FAILURE ); + abort(); } /* Redefine yyless() so it works in section 3 code. */ diff --git a/xs/src/avrdude/main.c b/xs/src/avrdude/main.c index 5d73403b0..ebda0ba19 100644 --- a/xs/src/avrdude/main.c +++ b/xs/src/avrdude/main.c @@ -144,6 +144,33 @@ void avrdude_progress_external(const char *task, unsigned progress) avrdude_progress_handler(task, progress, avrdude_progress_handler_user_p); } +static void avrdude_oom_handler_null(const char *context, void *user_p) +{ + // Output a message and just exit + fputs("avrdude: Out of memory: ", stderr); + fputs(context, stderr); + exit(99); +} + +static void *avrdude_oom_handler_user_p = NULL; +static avrdude_oom_handler_t avrdude_oom_handler = avrdude_oom_handler_null; + +void avrdude_oom_handler_set(avrdude_oom_handler_t newhandler, void *user_p) +{ + if (newhandler != NULL) { + avrdude_oom_handler = newhandler; + avrdude_oom_handler_user_p = user_p; + } else { + avrdude_oom_handler = avrdude_oom_handler_null; + avrdude_oom_handler_user_p = NULL; + } +} + +void avrdude_oom(const char *context) +{ + avrdude_oom_handler(context, avrdude_oom_handler_user_p); +} + void avrdude_cancel() { cancel_flag = true; diff --git a/xs/src/avrdude/pgm.c b/xs/src/avrdude/pgm.c index 851ac5a87..b8a93f104 100644 --- a/xs/src/avrdude/pgm.c +++ b/xs/src/avrdude/pgm.c @@ -172,9 +172,10 @@ PROGRAMMER * pgm_dup(const PROGRAMMER * const src) for (ln = lfirst(src->usbpid); ln; ln = lnext(ln)) { int *ip = malloc(sizeof(int)); if (ip == NULL) { - avrdude_message(MSG_INFO, "%s: out of memory allocating programmer structure\n", - progname); - exit(1); + // avrdude_message(MSG_INFO, "%s: out of memory allocating programmer structure\n", + // progname); + // exit(1); + avrdude_oom("out of memory allocating programmer structure\n"); } *ip = *(int *) ldata(ln); ladd(pgm->usbpid, ip); diff --git a/xs/src/avrdude/ser_win32.c b/xs/src/avrdude/ser_win32.c index 3a05cfa90..4e1713128 100644 --- a/xs/src/avrdude/ser_win32.c +++ b/xs/src/avrdude/ser_win32.c @@ -246,10 +246,11 @@ static int ser_open(char * port, union pinfo pinfo, union filedescriptor *fdp) newname = malloc(strlen("\\\\.\\") + strlen(port) + 1); if (newname == 0) { - avrdude_message(MSG_INFO, "%s: ser_open(): out of memory\n", - progname); - exit(1); - } + // avrdude_message(MSG_INFO, "%s: ser_open(): out of memory\n", + // progname); + // exit(1); + avrdude_oom("ser_open(): out of memory\n"); + } strcpy(newname, "\\\\.\\"); strcat(newname, port); diff --git a/xs/src/avrdude/stk500v2.c b/xs/src/avrdude/stk500v2.c index 4d62640c0..691152b46 100644 --- a/xs/src/avrdude/stk500v2.c +++ b/xs/src/avrdude/stk500v2.c @@ -295,9 +295,10 @@ static int stk600_xprog_program_enable(PROGRAMMER * pgm, AVRPART * p); void stk500v2_setup(PROGRAMMER * pgm) { if ((pgm->cookie = malloc(sizeof(struct pdata))) == 0) { - avrdude_message(MSG_INFO, "%s: stk500v2_setup(): Out of memory allocating private data\n", - progname); - exit(1); + // avrdude_message(MSG_INFO, "%s: stk500v2_setup(): Out of memory allocating private data\n", + // progname); + // exit(1); + avrdude_oom("stk500v2_setup(): Out of memory allocating private data\n"); } memset(pgm->cookie, 0, sizeof(struct pdata)); PDATA(pgm)->command_sequence = 1; diff --git a/xs/src/avrdude/update.c b/xs/src/avrdude/update.c index 417cbf71d..a255ab4f9 100644 --- a/xs/src/avrdude/update.c +++ b/xs/src/avrdude/update.c @@ -38,8 +38,9 @@ UPDATE * parse_op(char * s) upd = (UPDATE *)malloc(sizeof(UPDATE)); if (upd == NULL) { - avrdude_message(MSG_INFO, "%s: out of memory\n", progname); - exit(1); + // avrdude_message(MSG_INFO, "%s: out of memory\n", progname); + // exit(1); + avrdude_oom("parse_op: out of memory\n"); } i = 0; @@ -53,8 +54,9 @@ UPDATE * parse_op(char * s) upd->op = DEVICE_WRITE; upd->filename = (char *)malloc(strlen(buf) + 1); if (upd->filename == NULL) { - avrdude_message(MSG_INFO, "%s: out of memory\n", progname); - exit(1); + // avrdude_message(MSG_INFO, "%s: out of memory\n", progname); + // exit(1); + avrdude_oom("parse_op: out of memory\n"); } strcpy(upd->filename, buf); upd->format = FMT_AUTO; @@ -63,8 +65,9 @@ UPDATE * parse_op(char * s) upd->memtype = (char *)malloc(strlen(buf)+1); if (upd->memtype == NULL) { - avrdude_message(MSG_INFO, "%s: out of memory\n", progname); - exit(1); + // avrdude_message(MSG_INFO, "%s: out of memory\n", progname); + // exit(1); + avrdude_oom("parse_op: out of memory\n"); } strcpy(upd->memtype, buf); @@ -179,8 +182,9 @@ UPDATE * dup_update(UPDATE * upd) u = (UPDATE *)malloc(sizeof(UPDATE)); if (u == NULL) { - avrdude_message(MSG_INFO, "%s: out of memory\n", progname); - exit(1); + // avrdude_message(MSG_INFO, "%s: out of memory\n", progname); + // exit(1); + avrdude_oom("dup_update: out of memory\n"); } memcpy(u, upd, sizeof(UPDATE)); @@ -200,8 +204,9 @@ UPDATE * new_update(int op, char * memtype, int filefmt, char * filename, unsign u = (UPDATE *)malloc(sizeof(UPDATE)); if (u == NULL) { - avrdude_message(MSG_INFO, "%s: out of memory\n", progname); - exit(1); + // avrdude_message(MSG_INFO, "%s: out of memory\n", progname); + // exit(1); + avrdude_oom("new_update: out of memory\n"); } u->memtype = strdup(memtype); diff --git a/xs/src/avrdude/wiring.c b/xs/src/avrdude/wiring.c index 395459762..562a3f17c 100644 --- a/xs/src/avrdude/wiring.c +++ b/xs/src/avrdude/wiring.c @@ -85,9 +85,10 @@ static void wiring_setup(PROGRAMMER * pgm) * Now prepare our data */ if ((mycookie = malloc(sizeof(struct wiringpdata))) == 0) { - avrdude_message(MSG_INFO, "%s: wiring_setup(): Out of memory allocating private data\n", - progname); - exit(1); + // avrdude_message(MSG_INFO, "%s: wiring_setup(): Out of memory allocating private data\n", + // progname); + // exit(1); + avrdude_oom("wiring_setup(): Out of memory allocating private data\n"); } memset(mycookie, 0, sizeof(struct wiringpdata)); WIRINGPDATA(mycookie)->snoozetime = 0; diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp index 3d0dba07a..42c06252b 100644 --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp @@ -793,11 +793,16 @@ void WipeTowerPrusaMM::toolchange_Unload( 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() - .load_move_x_advanced(turning_point, -15.f, 83.f, 50.f) // this is done at fixed speed + .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 + .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 ) { // Set the extruder temperature, but don't wait. @@ -874,10 +879,15 @@ void WipeTowerPrusaMM::toolchange_Load( 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(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_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(); diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp index e1529bcf4..305dbc40a 100644 --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp @@ -65,9 +65,9 @@ public: // Set the extruder properties. - void set_extruder(size_t idx, material_type material, int temp, int first_layer_temp, float loading_speed, - float unloading_speed, float delay, int cooling_moves, float cooling_initial_speed, - float cooling_final_speed, std::string ramming_parameters, float nozzle_diameter) + 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()); @@ -76,7 +76,9 @@ public: 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; @@ -216,7 +218,9 @@ private: 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; diff --git a/xs/src/libslic3r/Geometry.cpp b/xs/src/libslic3r/Geometry.cpp index c978d46b6..b0ded2d04 100644 --- a/xs/src/libslic3r/Geometry.cpp +++ b/xs/src/libslic3r/Geometry.cpp @@ -195,47 +195,110 @@ using namespace boost::polygon; // provides also high() and low() namespace Slic3r { namespace Geometry { -static bool -sort_points (Point a, Point b) +static bool sort_points(const Point& a, const Point& b) { return (a(0) < b(0)) || (a(0) == b(0) && a(1) < b(1)); } -/* This implementation is based on Andrew's monotone chain 2D convex hull algorithm */ +static bool sort_pointfs(const Vec3d& a, const Vec3d& b) +{ + return (a(0) < b(0)) || (a(0) == b(0) && a(1) < b(1)); +} + +// This implementation is based on Andrew's monotone chain 2D convex hull algorithm Polygon convex_hull(Points points) { assert(points.size() >= 3); // sort input points std::sort(points.begin(), points.end(), sort_points); - + int n = points.size(), k = 0; Polygon hull; if (n >= 3) { - hull.points.resize(2*n); + hull.points.resize(2 * n); // Build lower hull for (int i = 0; i < n; i++) { - while (k >= 2 && points[i].ccw(hull.points[k-2], hull.points[k-1]) <= 0) k--; - hull.points[k++] = points[i]; + while (k >= 2 && points[i].ccw(hull[k-2], hull[k-1]) <= 0) k--; + hull[k++] = points[i]; } // Build upper hull for (int i = n-2, t = k+1; i >= 0; i--) { - while (k >= t && points[i].ccw(hull.points[k-2], hull.points[k-1]) <= 0) k--; - hull.points[k++] = points[i]; + while (k >= t && points[i].ccw(hull[k-2], hull[k-1]) <= 0) k--; + hull[k++] = points[i]; } hull.points.resize(k); - - assert( hull.points.front() == hull.points.back() ); + + assert(hull.points.front() == hull.points.back()); hull.points.pop_back(); } return hull; } +Pointf3s +convex_hull(Pointf3s points) +{ + assert(points.size() >= 3); + // sort input points + std::sort(points.begin(), points.end(), sort_pointfs); + + int n = points.size(), k = 0; + Pointf3s hull; + + if (n >= 3) + { + hull.resize(2 * n); + + // Build lower hull + for (int i = 0; i < n; ++i) + { + Point p = Point::new_scale(points[i](0), points[i](1)); + while (k >= 2) + { + Point k1 = Point::new_scale(hull[k - 1](0), hull[k - 1](1)); + Point k2 = Point::new_scale(hull[k - 2](0), hull[k - 2](1)); + + if (p.ccw(k2, k1) <= 0) + --k; + else + break; + } + + hull[k++] = points[i]; + } + + // Build upper hull + for (int i = n - 2, t = k + 1; i >= 0; --i) + { + Point p = Point::new_scale(points[i](0), points[i](1)); + while (k >= t) + { + Point k1 = Point::new_scale(hull[k - 1](0), hull[k - 1](1)); + Point k2 = Point::new_scale(hull[k - 2](0), hull[k - 2](1)); + + if (p.ccw(k2, k1) <= 0) + --k; + else + break; + } + + hull[k++] = points[i]; + } + + hull.resize(k); + + assert(hull.front() == hull.back()); + hull.pop_back(); + } + + return hull; +} + Polygon convex_hull(const Polygons &polygons) { @@ -243,7 +306,7 @@ convex_hull(const Polygons &polygons) for (Polygons::const_iterator p = polygons.begin(); p != polygons.end(); ++p) { pp.insert(pp.end(), p->points.begin(), p->points.end()); } - return convex_hull(pp); + return convex_hull(std::move(pp)); } /* accepts an arrayref of points and returns a list of indices diff --git a/xs/src/libslic3r/Geometry.hpp b/xs/src/libslic3r/Geometry.hpp index 194534007..3698b996f 100644 --- a/xs/src/libslic3r/Geometry.hpp +++ b/xs/src/libslic3r/Geometry.hpp @@ -108,8 +108,10 @@ inline bool segment_segment_intersection(const Vec2d &p1, const Vec2d &v1, const return true; } +Pointf3s convex_hull(Pointf3s points); Polygon convex_hull(Points points); Polygon convex_hull(const Polygons &polygons); + void chained_path(const Points &points, std::vector &retval, Point start_near); void chained_path(const Points &points, std::vector &retval); template void chained_path_items(Points &points, T &items, T &retval); diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index d046a8ef2..f723ca856 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -715,7 +715,21 @@ void ModelObject::scale(const Vec3d &versor) this->invalidate_bounding_box(); } -void ModelObject::rotate(float angle, const Axis &axis) +void ModelObject::rotate(float angle, const Axis& axis) +{ + for (ModelVolume *v : this->volumes) + { + v->mesh.rotate(angle, axis); + v->m_convex_hull.rotate(angle, axis); + } + + center_around_origin(); + + this->origin_translation = Vec3d::Zero(); + this->invalidate_bounding_box(); +} + +void ModelObject::rotate(float angle, const Vec3d& axis) { for (ModelVolume *v : this->volumes) { diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp index 468e6f833..140a0270a 100644 --- a/xs/src/libslic3r/Model.hpp +++ b/xs/src/libslic3r/Model.hpp @@ -121,7 +121,8 @@ public: void translate(coordf_t x, coordf_t y, coordf_t z); void scale(const Vec3d &versor); void rotate(float angle, const Axis &axis); - void transform(const float* matrix3x4); + void rotate(float angle, const Vec3d& axis); + void transform(const float* matrix3x4); // <<<<<<<<< FIXME (using eigen) void mirror(const Axis &axis); size_t materials_count() const; size_t facets_count() const; diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index 230201cc1..6739366e5 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -200,7 +200,9 @@ bool Print::invalidate_state_by_config_options(const std::vectorconfig.temperature.get_at(i), this->config.first_layer_temperature.get_at(i), this->config.filament_loading_speed.get_at(i), + this->config.filament_loading_speed_start.get_at(i), this->config.filament_unloading_speed.get_at(i), + this->config.filament_unloading_speed_start.get_at(i), this->config.filament_toolchange_delay.get_at(i), this->config.filament_cooling_moves.get_at(i), this->config.filament_cooling_initial_speed.get_at(i), diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 400530151..748e1fb72 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -473,6 +473,14 @@ PrintConfigDef::PrintConfigDef() def->min = 0; def->default_value = new ConfigOptionFloats { 28. }; + def = this->add("filament_loading_speed_start", coFloats); + def->label = L("Loading speed at the start"); + def->tooltip = L("Speed used at the very beginning of loading phase. "); + def->sidetext = L("mm/s"); + def->cli = "filament-loading-speed-start=f@"; + def->min = 0; + def->default_value = new ConfigOptionFloats { 3. }; + def = this->add("filament_unloading_speed", coFloats); def->label = L("Unloading speed"); def->tooltip = L("Speed used for unloading the filament on the wipe tower (does not affect " @@ -482,6 +490,14 @@ PrintConfigDef::PrintConfigDef() def->min = 0; def->default_value = new ConfigOptionFloats { 90. }; + def = this->add("filament_unloading_speed_start", coFloats); + def->label = L("Unloading speed at the start"); + def->tooltip = L("Speed used for unloading the tip of the filament immediately after ramming. "); + def->sidetext = L("mm/s"); + def->cli = "filament-unloading-speed-start=f@"; + def->min = 0; + def->default_value = new ConfigOptionFloats { 100. }; + def = this->add("filament_toolchange_delay", coFloats); def->label = L("Delay after unloading"); def->tooltip = L("Time to wait after the filament is unloaded. " @@ -2043,7 +2059,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("wipe_into_infill", coBool); def->category = L("Extruders"); - def->label = L("Purge into this object's infill"); + def->label = L("Wipe into this object's infill"); def->tooltip = L("Purging after toolchange will done inside this object's infills. " "This lowers the amount of waste but may result in longer print time " " due to additional travel moves."); @@ -2052,7 +2068,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("wipe_into_objects", coBool); def->category = L("Extruders"); - def->label = L("Purge into this object"); + def->label = L("Wipe into this object"); def->tooltip = L("Object will be used to purge the nozzle after a toolchange to save material " "that would otherwise end up in the wipe tower and decrease print time. " "Colours of the objects will be mixed as a result."); diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index 438e90681..932ed6054 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -541,8 +541,10 @@ public: ConfigOptionFloats filament_cost; ConfigOptionFloats filament_max_volumetric_speed; ConfigOptionFloats filament_loading_speed; + ConfigOptionFloats filament_loading_speed_start; ConfigOptionFloats filament_load_time; ConfigOptionFloats filament_unloading_speed; + ConfigOptionFloats filament_unloading_speed_start; ConfigOptionFloats filament_toolchange_delay; ConfigOptionFloats filament_unload_time; ConfigOptionInts filament_cooling_moves; @@ -607,8 +609,10 @@ protected: OPT_PTR(filament_cost); OPT_PTR(filament_max_volumetric_speed); OPT_PTR(filament_loading_speed); + OPT_PTR(filament_loading_speed_start); OPT_PTR(filament_load_time); OPT_PTR(filament_unloading_speed); + OPT_PTR(filament_unloading_speed_start); OPT_PTR(filament_unload_time); OPT_PTR(filament_toolchange_delay); OPT_PTR(filament_cooling_moves); diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp index d559afe52..7b9baaf77 100644 --- a/xs/src/libslic3r/TriangleMesh.cpp +++ b/xs/src/libslic3r/TriangleMesh.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include @@ -255,6 +256,17 @@ void TriangleMesh::rotate(float angle, const Axis &axis) stl_invalidate_shared_vertices(&this->stl); } +void TriangleMesh::rotate(float angle, const Vec3d& axis) +{ + if (angle == 0.f) + return; + + Vec3f axis_norm = axis.cast().normalized(); + Transform3f m = Transform3f::Identity(); + m.rotate(Eigen::AngleAxisf(angle, axis_norm)); + stl_transform(&stl, (float*)m.data()); +} + void TriangleMesh::mirror(const Axis &axis) { if (axis == X) { @@ -459,6 +471,11 @@ ExPolygons TriangleMesh::horizontal_projection() const return union_ex(offset(pp, scale_(0.01)), true); } +const float* TriangleMesh::first_vertex() const +{ + return this->stl.facet_start ? &this->stl.facet_start->vertex[0](0) : nullptr; +} + Polygon TriangleMesh::convex_hull() { this->require_shared_vertices(); @@ -597,6 +614,7 @@ TriangleMesh TriangleMesh::convex_hull_3d() const TriangleMesh output_mesh(dst_vertices, facets); output_mesh.repair(); + output_mesh.require_shared_vertices(); return output_mesh; } diff --git a/xs/src/libslic3r/TriangleMesh.hpp b/xs/src/libslic3r/TriangleMesh.hpp index e4d377a9d..c42a0934d 100644 --- a/xs/src/libslic3r/TriangleMesh.hpp +++ b/xs/src/libslic3r/TriangleMesh.hpp @@ -40,6 +40,7 @@ public: void scale(const Vec3d &versor); void translate(float x, float y, float z); void rotate(float angle, const Axis &axis); + void rotate(float angle, const Vec3d& axis); void rotate_x(float angle) { this->rotate(angle, X); } void rotate_y(float angle) { this->rotate(angle, Y); } void rotate_z(float angle) { this->rotate(angle, Z); } @@ -53,6 +54,7 @@ public: TriangleMeshPtrs split() const; void merge(const TriangleMesh &mesh); ExPolygons horizontal_projection() const; + const float* first_vertex() const; Polygon convex_hull(); BoundingBoxf3 bounding_box() const; // Returns the bbox of this TriangleMesh transformed by the given transformation diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h index 6fea64cd1..58208d116 100644 --- a/xs/src/libslic3r/libslic3r.h +++ b/xs/src/libslic3r/libslic3r.h @@ -14,7 +14,7 @@ #include #define SLIC3R_FORK_NAME "Slic3r Prusa Edition" -#define SLIC3R_VERSION "1.41.0-beta" +#define SLIC3R_VERSION "1.41.0-beta2" #define SLIC3R_BUILD "UNKNOWN" typedef int32_t coord_t; diff --git a/xs/src/slic3r/GUI/FirmwareDialog.cpp b/xs/src/slic3r/GUI/FirmwareDialog.cpp index 77e70c49b..d0cd9f8cf 100644 --- a/xs/src/slic3r/GUI/FirmwareDialog.cpp +++ b/xs/src/slic3r/GUI/FirmwareDialog.cpp @@ -551,8 +551,10 @@ void FirmwareDialog::priv::perform_upload() // because the dialog ensures it doesn't exit before the background thread is done. auto q = this->q; - this->avrdude = avrdude - .on_run([this]() { + avrdude + .on_run([this](AvrDude::Ptr avrdude) { + this->avrdude = std::move(avrdude); + try { switch (this->hex_file.device) { case HexFile::DEV_MK3: diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index 571ac0029..652b8e899 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -1,5 +1,6 @@ #include "GLCanvas3D.hpp" +#include "../../admesh/stl.h" #include "../../libslic3r/libslic3r.h" #include "../../slic3r/GUI/3DScene.hpp" #include "../../slic3r/GUI/GLShader.hpp" @@ -1165,6 +1166,18 @@ bool GLCanvas3D::Gizmos::init(GLCanvas3D& parent) m_gizmos.insert(GizmosMap::value_type(Rotate, gizmo)); + gizmo = new GLGizmoFlatten(parent); + if (gizmo == nullptr) + return false; + + if (!gizmo->init()) { + _reset(); + return false; + } + + m_gizmos.insert(GizmosMap::value_type(Flatten, gizmo)); + + return true; } @@ -1414,11 +1427,32 @@ reinterpret_cast(it->second)->set_angle_z(angle_z); #endif // ENABLE_GIZMOS_3D } +Vec3d GLCanvas3D::Gizmos::get_flattening_normal() const +{ + if (!m_enabled) + return Vec3d::Zero(); + + GizmosMap::const_iterator it = m_gizmos.find(Flatten); + return (it != m_gizmos.end()) ? reinterpret_cast(it->second)->get_flattening_normal() : Vec3d::Zero(); +} + +void GLCanvas3D::Gizmos::set_flattening_data(const ModelObject* model_object) +{ + if (!m_enabled) + return; + + GizmosMap::const_iterator it = m_gizmos.find(Flatten); + if (it != m_gizmos.end()) + reinterpret_cast(it->second)->set_flattening_data(model_object); +} + void GLCanvas3D::Gizmos::render_current_gizmo(const BoundingBoxf3& box) const { if (!m_enabled) return; + ::glDisable(GL_DEPTH_TEST); + if (box.radius() > 0.0) _render_current_gizmo(box); } @@ -2319,6 +2353,7 @@ void GLCanvas3D::update_gizmos_data() { m_gizmos.set_scale(model_instance->scaling_factor); m_gizmos.set_angle_z(model_instance->rotation); + m_gizmos.set_flattening_data(model_object); } } } @@ -2326,6 +2361,7 @@ void GLCanvas3D::update_gizmos_data() { m_gizmos.set_scale(1.0f); m_gizmos.set_angle_z(0.0f); + m_gizmos.set_flattening_data(nullptr); } } @@ -2367,7 +2403,6 @@ void GLCanvas3D::render() _render_axes(false); } _render_objects(); - if (!is_custom_bed) // textured bed needs to be rendered after objects { _render_axes(true); @@ -2982,6 +3017,17 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) update_gizmos_data(); m_gizmos.start_dragging(); m_mouse.drag.gizmo_volume_idx = _get_first_selected_volume_id(selected_object_idx); + + if (m_gizmos.get_current_type() == Gizmos::Flatten) { + // Rotate the object so the normal points downward: + Vec3d normal = m_gizmos.get_flattening_normal(); + if (normal != Vec3d::Zero()) { + Vec3d axis = normal(2) > 0.999f ? Vec3d::UnitX() : normal.cross(-Vec3d::UnitZ()); + float angle = -acos(-normal(2)); + m_on_gizmo_rotate_callback.call(angle, (float)axis(0), (float)axis(1), (float)axis(2)); + } + } + m_dirty = true; } else if (toolbar_contains_mouse != -1) diff --git a/xs/src/slic3r/GUI/GLCanvas3D.hpp b/xs/src/slic3r/GUI/GLCanvas3D.hpp index 2334fe092..2969d12c6 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.hpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp @@ -338,6 +338,7 @@ public: Undefined, Scale, Rotate, + Flatten, Num_Types }; @@ -382,7 +383,11 @@ public: float get_angle_z() const; void set_angle_z(float angle_z); + void set_flattening_data(const ModelObject* model_object); + Vec3d get_flattening_normal() const; + void render_current_gizmo(const BoundingBoxf3& box) const; + void render_current_gizmo_for_picking_pass(const BoundingBoxf3& box) const; void render_overlay(const GLCanvas3D& canvas) const; diff --git a/xs/src/slic3r/GUI/GLGizmo.cpp b/xs/src/slic3r/GUI/GLGizmo.cpp index 39ba440c3..fa5b931e9 100644 --- a/xs/src/slic3r/GUI/GLGizmo.cpp +++ b/xs/src/slic3r/GUI/GLGizmo.cpp @@ -4,10 +4,12 @@ #include "../../slic3r/GUI/GLCanvas3D.hpp" #include +#include "../../libslic3r/Geometry.hpp" #include #include +#include static const float DEFAULT_BASE_COLOR[3] = { 0.625f, 0.625f, 0.625f }; static const float DEFAULT_DRAG_COLOR[3] = { 1.0f, 1.0f, 1.0f }; @@ -163,7 +165,6 @@ GLGizmoBase::GLGizmoBase(GLCanvas3D& parent) , m_group_id(-1) , m_state(Off) , m_hover_id(-1) - , m_is_container(false) { ::memcpy((void*)m_base_color, (const void*)DEFAULT_BASE_COLOR, 3 * sizeof(float)); ::memcpy((void*)m_drag_color, (const void*)DEFAULT_DRAG_COLOR, 3 * sizeof(float)); @@ -172,7 +173,7 @@ GLGizmoBase::GLGizmoBase(GLCanvas3D& parent) void GLGizmoBase::set_hover_id(int id) { - if (m_is_container || (id < (int)m_grabbers.size())) + if (m_grabbers.empty() || (id < (int)m_grabbers.size())) { m_hover_id = id; on_set_hover_id(); @@ -602,8 +603,6 @@ GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent) , m_y(parent, GLGizmoRotate::Y) , m_z(parent, GLGizmoRotate::Z) { - m_is_container = true; - m_x.set_group_id(0); m_y.set_group_id(1); m_z.set_group_id(2); @@ -1165,5 +1164,323 @@ double GLGizmoScale3D::calc_ratio(unsigned int preferred_plane_id, const Linef3& return ratio; } + +GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent) + : GLGizmoBase(parent) + , m_normal(0.0, 0.0, 0.0) +{ +} + +bool GLGizmoFlatten::on_init() +{ + std::string path = resources_dir() + "/icons/overlay/"; + + std::string filename = path + "layflat_off.png"; + if (!m_textures[Off].load_from_file(filename, false)) + return false; + + filename = path + "layflat_hover.png"; + if (!m_textures[Hover].load_from_file(filename, false)) + return false; + + filename = path + "layflat_on.png"; + if (!m_textures[On].load_from_file(filename, false)) + return false; + + return true; +} + +void GLGizmoFlatten::on_start_dragging() +{ + if (m_hover_id != -1) + m_normal = m_planes[m_hover_id].normal; +} + +void GLGizmoFlatten::on_render(const BoundingBoxf3& box) const +{ + // the dragged_offset is a vector measuring where was the object moved + // with the gizmo being on. This is reset in set_flattening_data and + // does not work correctly when there are multiple copies. + if (!m_center) // this is the first bounding box that we see + m_center.reset(new Vec3d(box.center())); + + Vec3d dragged_offset = box.center() - *m_center; + + bool blending_was_enabled = ::glIsEnabled(GL_BLEND); + bool depth_test_was_enabled = ::glIsEnabled(GL_DEPTH_TEST); + ::glEnable(GL_BLEND); + ::glEnable(GL_DEPTH_TEST); + + for (int i=0; i<(int)m_planes.size(); ++i) { + if (i == m_hover_id) + ::glColor4f(0.9f, 0.9f, 0.9f, 0.75f); + else + ::glColor4f(0.9f, 0.9f, 0.9f, 0.5f); + + for (Vec2d offset : m_instances_positions) { + offset += to_2d(dragged_offset); + ::glBegin(GL_POLYGON); + for (const Vec3d& vertex : m_planes[i].vertices) + ::glVertex3f((GLfloat)(vertex(0) + offset(0)), (GLfloat)(vertex(1) + offset(1)), (GLfloat)vertex(2)); + ::glEnd(); + } + } + + if (!blending_was_enabled) + ::glDisable(GL_BLEND); + if (!depth_test_was_enabled) + ::glDisable(GL_DEPTH_TEST); +} + +void GLGizmoFlatten::on_render_for_picking(const BoundingBoxf3& box) const +{ + static const GLfloat INV_255 = 1.0f / 255.0f; + + ::glDisable(GL_DEPTH_TEST); + + for (unsigned int i = 0; i < m_planes.size(); ++i) + { + ::glColor3f(1.0f, 1.0f, (254.0f - (float)i) * INV_255); + for (const Vec2d& offset : m_instances_positions) { + ::glBegin(GL_POLYGON); + for (const Vec3d& vertex : m_planes[i].vertices) + ::glVertex3f((GLfloat)(vertex(0) + offset(0)), (GLfloat)vertex(1) + offset(1), (GLfloat)vertex(2)); + ::glEnd(); + } + } +} + +// TODO - remove and use Eigen instead +static Vec3d super_rotation(Vec3d axis, float angle, const Vec3d& point) +{ + axis.normalize(); + float x = (float)axis(0); + float y = (float)axis(1); + float z = (float)axis(2); + float s = sin(angle); + float c = cos(angle); + float D = 1 - c; + float matrix[3][3] = { { c + x*x*D, x*y*D - z*s, x*z*D + y*s }, + { y*x*D + z*s, c + y*y*D, y*z*D - x*s }, + { z*x*D - y*s, z*y*D + x*s, c + z*z*D } }; + float in[3] = { (float)point(0), (float)point(1), (float)point(2) }; + float out[3] = { 0, 0, 0 }; + + for (unsigned char i = 0; i<3; ++i) + for (unsigned char j = 0; j<3; ++j) + out[i] += matrix[i][j] * in[j]; + + return Vec3d((double)out[0], (double)out[1], (double)out[2]); +} + +void GLGizmoFlatten::set_flattening_data(const ModelObject* model_object) +{ + m_center.release(); // object is not being dragged (this would not be called otherwise) - we must forget about the bounding box position... + m_model_object = model_object; + + // ...and save the updated positions of the object instances: + if (m_model_object && !m_model_object->instances.empty()) { + m_instances_positions.clear(); + for (const auto* instance : m_model_object->instances) + m_instances_positions.emplace_back(instance->offset); + } + + if (is_plane_update_necessary()) + update_planes(); +} + +void GLGizmoFlatten::update_planes() +{ + TriangleMesh ch; + for (const ModelVolume* vol : m_model_object->volumes) + ch.merge(vol->get_convex_hull()); + ch = ch.convex_hull_3d(); + ch.scale(m_model_object->instances.front()->scaling_factor); + ch.rotate_z(m_model_object->instances.front()->rotation); + + m_planes.clear(); + + // Now we'll go through all the facets and append Points of facets sharing the same normal: + const int num_of_facets = ch.stl.stats.number_of_facets; + std::vector facet_queue(num_of_facets, 0); + std::vector facet_visited(num_of_facets, false); + int facet_queue_cnt = 0; + const stl_normal* normal_ptr = nullptr; + while (1) { + // Find next unvisited triangle: + int facet_idx = 0; + for (; facet_idx < num_of_facets; ++ facet_idx) + if (!facet_visited[facet_idx]) { + facet_queue[facet_queue_cnt ++] = facet_idx; + facet_visited[facet_idx] = true; + normal_ptr = &ch.stl.facet_start[facet_idx].normal; + m_planes.emplace_back(); + break; + } + if (facet_idx == num_of_facets) + break; // Everything was visited already + + while (facet_queue_cnt > 0) { + int facet_idx = facet_queue[-- facet_queue_cnt]; + const stl_normal& this_normal = ch.stl.facet_start[facet_idx].normal; + if (std::abs(this_normal(0) - (*normal_ptr)(0)) < 0.001 && std::abs(this_normal(1) - (*normal_ptr)(1)) < 0.001 && std::abs(this_normal(2) - (*normal_ptr)(2)) < 0.001) { + stl_vertex* first_vertex = ch.stl.facet_start[facet_idx].vertex; + for (int j=0; j<3; ++j) + m_planes.back().vertices.emplace_back(first_vertex[j](0), first_vertex[j](1), first_vertex[j](2)); + + facet_visited[facet_idx] = true; + for (int j = 0; j < 3; ++ j) { + int neighbor_idx = ch.stl.neighbors_start[facet_idx].neighbor[j]; + if (! facet_visited[neighbor_idx]) + facet_queue[facet_queue_cnt ++] = neighbor_idx; + } + } + } + m_planes.back().normal = Vec3d((double)(*normal_ptr)(0), (double)(*normal_ptr)(1), (double)(*normal_ptr)(2)); + + // if this is a just a very small triangle, remove it to speed up further calculations (it would be rejected anyway): + if (m_planes.back().vertices.size() == 3 && + (m_planes.back().vertices[0] - m_planes.back().vertices[1]).norm() < 1.f + || (m_planes.back().vertices[0] - m_planes.back().vertices[2]).norm() < 1.f) + m_planes.pop_back(); + } + + // Now we'll go through all the polygons, transform the points into xy plane to process them: + for (unsigned int polygon_id=0; polygon_id < m_planes.size(); ++polygon_id) { + Pointf3s& polygon = m_planes[polygon_id].vertices; + const Vec3d& normal = m_planes[polygon_id].normal; + + // We are going to rotate about z and y to flatten the plane + float angle_z = 0.f; + float angle_y = 0.f; + if (std::abs(normal(1)) > 0.001) + angle_z = -atan2(normal(1), normal(0)); // angle to rotate so that normal ends up in xz-plane + if (std::abs(normal(0)*cos(angle_z) - normal(1)*sin(angle_z)) > 0.001) + angle_y = -atan2(normal(0)*cos(angle_z) - normal(1)*sin(angle_z), normal(2)); // angle to rotate to make normal point upwards + else { + // In case it already was in z-direction, we must ensure it is not the wrong way: + angle_y = normal(2) > 0.f ? 0 : -PI; + } + + // Rotate all points to the xy plane: + for (auto& vertex : polygon) { + vertex = super_rotation(Vec3d::UnitZ(), angle_z, vertex); + vertex = super_rotation(Vec3d::UnitY(), angle_y, vertex); + } + polygon = Slic3r::Geometry::convex_hull(polygon); // To remove the inner points + + // We will calculate area of the polygon and discard ones that are too small + // The limit is more forgiving in case the normal is in the direction of the coordinate axes + const float minimal_area = (std::abs(normal(0)) > 0.999f || std::abs(normal(1)) > 0.999f || std::abs(normal(2)) > 0.999f) ? 1.f : 20.f; + float& area = m_planes[polygon_id].area; + area = 0.f; + for (unsigned int i = 0; i < polygon.size(); i++) // Shoelace formula + area += polygon[i](0)*polygon[i + 1 < polygon.size() ? i + 1 : 0](1) - polygon[i + 1 < polygon.size() ? i + 1 : 0](0)*polygon[i](1); + area = std::abs(area / 2.f); + if (area < minimal_area) { + m_planes.erase(m_planes.begin()+(polygon_id--)); + continue; + } + + // We will shrink the polygon a little bit so it does not touch the object edges: + Vec3d centroid = std::accumulate(polygon.begin(), polygon.end(), Vec3d(0.0, 0.0, 0.0)); + centroid /= (double)polygon.size(); + for (auto& vertex : polygon) + vertex = 0.9f*vertex + 0.1f*centroid; + + // Polygon is now simple and convex, we'll round the corners to make them look nicer. + // The algorithm takes a vertex, calculates middles of respective sides and moves the vertex + // towards their average (controlled by 'aggressivity'). This is repeated k times. + // In next iterations, the neighbours are not always taken at the middle (to increase the + // rounding effect at the corners, where we need it most). + const unsigned int k = 10; // number of iterations + const float aggressivity = 0.2f; // agressivity + const unsigned int N = polygon.size(); + std::vector> neighbours; + if (k != 0) { + Pointf3s points_out(2*k*N); // vector long enough to store the future vertices + for (unsigned int j=0; jvolumes) + m_source_data.bounding_boxes.push_back(vol->get_convex_hull().bounding_box()); + m_source_data.scaling_factor = m_model_object->instances.front()->scaling_factor; + m_source_data.rotation = m_model_object->instances.front()->rotation; + const float* first_vertex = m_model_object->volumes.front()->get_convex_hull().first_vertex(); + m_source_data.mesh_first_point = Vec3d((double)first_vertex[0], (double)first_vertex[1], (double)first_vertex[2]); +} + +// Check if the bounding boxes of each volume's convex hull is the same as before +// and that scaling and rotation has not changed. In that case we don't have to recalculate it. +bool GLGizmoFlatten::is_plane_update_necessary() const +{ + if (m_state != On || !m_model_object || m_model_object->instances.empty()) + return false; + + if (m_model_object->volumes.size() != m_source_data.bounding_boxes.size() + || m_model_object->instances.front()->scaling_factor != m_source_data.scaling_factor + || m_model_object->instances.front()->rotation != m_source_data.rotation) + return true; + + // now compare the bounding boxes: + for (unsigned int i=0; ivolumes.size(); ++i) + if (m_model_object->volumes[i]->get_convex_hull().bounding_box() != m_source_data.bounding_boxes[i]) + return true; + + const float* first_vertex = m_model_object->volumes.front()->get_convex_hull().first_vertex(); + Vec3d first_point((double)first_vertex[0], (double)first_vertex[1], (double)first_vertex[2]); + if (first_point != m_source_data.mesh_first_point) + return true; + + return false; +} + +Vec3d GLGizmoFlatten::get_flattening_normal() const { + Transform3d m = Transform3d::Identity(); + m.rotate(Eigen::AngleAxisd(-m_model_object->instances.front()->rotation, Vec3d::UnitZ())); + Vec3d normal = m * m_normal; + m_normal = Vec3d::Zero(); + return normal; +} + } // namespace GUI } // namespace Slic3r diff --git a/xs/src/slic3r/GUI/GLGizmo.hpp b/xs/src/slic3r/GUI/GLGizmo.hpp index 50bb333e5..ddfd9a7a5 100644 --- a/xs/src/slic3r/GUI/GLGizmo.hpp +++ b/xs/src/slic3r/GUI/GLGizmo.hpp @@ -13,6 +13,7 @@ namespace Slic3r { class BoundingBoxf3; class Linef3; +class ModelObject; namespace GUI { @@ -74,7 +75,6 @@ protected: float m_drag_color[3]; float m_highlight_color[3]; mutable std::vector m_grabbers; - bool m_is_container; public: explicit GLGizmoBase(GLCanvas3D& parent); @@ -312,6 +312,54 @@ private: double calc_ratio(unsigned int preferred_plane_id, const Linef3& mouse_ray, const Vec3d& center) const; }; +class GLGizmoFlatten : public GLGizmoBase +{ +// This gizmo does not use grabbers. The m_hover_id relates to polygon managed by the class itself. + +private: + mutable Vec3d m_normal; + + struct PlaneData { + std::vector vertices; + Vec3d normal; + float area; + }; + struct SourceDataSummary { + std::vector bounding_boxes; // bounding boxes of convex hulls of individual volumes + float scaling_factor; + float rotation; + Vec3d mesh_first_point; + }; + + // This holds information to decide whether recalculation is necessary: + SourceDataSummary m_source_data; + + std::vector m_planes; + std::vector m_instances_positions; + mutable std::unique_ptr m_center = nullptr; + const ModelObject* m_model_object = nullptr; + + void update_planes(); + bool is_plane_update_necessary() const; + +public: + explicit GLGizmoFlatten(GLCanvas3D& parent); + + void set_flattening_data(const ModelObject* model_object); + Vec3d get_flattening_normal() const; + +protected: + virtual bool on_init(); + virtual void on_start_dragging(); + virtual void on_update(const Linef3& mouse_ray) {} + virtual void on_render(const BoundingBoxf3& box) const; + virtual void on_render_for_picking(const BoundingBoxf3& box) const; + virtual void on_set_state() { + if (m_state == On && is_plane_update_necessary()) + update_planes(); + } +}; + } // namespace GUI } // namespace Slic3r diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index 8335e48b5..af2cd8ff6 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -313,13 +313,12 @@ const std::vector& Preset::filament_options() { static std::vector s_opts { "filament_colour", "filament_diameter", "filament_type", "filament_soluble", "filament_notes", "filament_max_volumetric_speed", - "extrusion_multiplier", "filament_density", "filament_cost", - "filament_loading_speed", "filament_load_time", "filament_unloading_speed", "filament_unload_time", "filament_toolchange_delay", - "filament_cooling_moves", "filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", - "filament_minimal_purge_on_wipe_tower", "temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", - "fan_always_on", "cooling", "min_fan_speed", "max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "fan_below_layer_time", - "slowdown_below_layer_time", "min_print_speed", "start_filament_gcode", "end_filament_gcode","compatible_printers", "compatible_printers_condition", - "inherits" + "extrusion_multiplier", "filament_density", "filament_cost", "filament_loading_speed", "filament_loading_speed_start", "filament_load_time", + "filament_unloading_speed", "filament_unloading_speed_start", "filament_unload_time", "filament_toolchange_delay", "filament_cooling_moves", + "filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", "filament_minimal_purge_on_wipe_tower", + "temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed", + "max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed", + "start_filament_gcode", "end_filament_gcode","compatible_printers", "compatible_printers_condition", "inherits" }; return s_opts; } diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index bde4fdc34..6505e1092 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -1290,7 +1290,9 @@ void TabFilament::build() optgroup->append_line(line); optgroup = page->new_optgroup(_(L("Toolchange parameters with single extruder MM printers"))); - optgroup->append_single_option_line("filament_loading_speed"); + optgroup->append_single_option_line("filament_loading_speed_start"); + optgroup->append_single_option_line("filament_loading_speed"); + optgroup->append_single_option_line("filament_unloading_speed_start"); optgroup->append_single_option_line("filament_unloading_speed"); optgroup->append_single_option_line("filament_load_time"); optgroup->append_single_option_line("filament_unload_time"); diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index ac265a3b3..a5925775d 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -301,7 +301,8 @@ ModelMaterial::attributes() void translate(double x, double y, double z); void scale_xyz(Vec3d* versor) %code{% THIS->scale(*versor); %}; - void rotate(float angle, Axis axis); + void rotate(float angle, Vec3d* axis) + %code{% THIS->rotate(angle, *axis); %}; void mirror(Axis axis); Model* cut(double z)