diff --git a/.travis.yml b/.travis.yml
index 2d84fd28d..5e6930fbf 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,7 +8,6 @@ perl:
branches:
only:
- master
- - stable
sudo: false
cache:
apt: true
@@ -26,3 +25,10 @@ addons:
- liblocal-lib-perl
- g++-4.9
env: CC=g++-4.9
+notifications:
+ irc:
+ channels:
+ - "chat.freenode.net#slic3r"
+ on_success: change
+ on_failure: always
+ use_notice: true
diff --git a/README.md b/README.md
index af047f762..aebe1df8d 100644
--- a/README.md
+++ b/README.md
@@ -1,63 +1,50 @@
-_Q: Oh cool, a new RepRap slicer?_
-
-A: Yes.
-
-Slic3r [](https://travis-ci.org/alexrj/Slic3r) [](https://ci.appveyor.com/project/lordofhyphens/slic3r) [](http://osx-build.slic3r.org:8080/job/Slic3r)
+ Slic3r [](https://travis-ci.org/alexrj/Slic3r) [](https://ci.appveyor.com/project/lordofhyphens/slic3r) [](http://osx-build.slic3r.org:8080/job/Slic3r)
======
-Prebuilt Windows (64-bit) and OSX (>10.7) builds:
-* http://dl.slic3r.org/dev/
-
+We have automated builds for Windows (64-bit) and OSX (>= 10.7). [Get a fresh build now](http://dl.slic3r.org/dev/) and stay up-to-date with the development!
-Slic3r takes 3D models (STL, OBJ, AMF) and converts them into G-code instructions for
-3D printers. It's compatible with any modern printer based on the RepRap toolchain,
-including all those based on the Marlin, Sprinter and Repetier firmware. It also works
-with Mach3, LinuxCNC and Machinekit controllers.
+The MacOS X build server is kindly sponsored by:
-See the [project homepage](http://slic3r.org/) at slic3r.org and the
-[manual](http://manual.slic3r.org/) for more information.
+### So, what's this Slic3r?
+
+Slic3r is mainly a **toolpath generator** for 3D printers: it reads 3D models (STL, OBJ, AMF) and it converts them into **G-code** instructions for 3D printers. But it does much more than that, see the [features list](#features) below.
+
+Slic3r was born in **2011** within the RepRap community and thanks to its high configurability became the swiss-army knife for 3D printing. It served as a platform for experimenting several **new ideas that later became technology standards**, such as multiple extruders, brim, variable-height layers, per-object settings, modifiers, post-processing scripts, G-code macros and more. Despite being based on volunteer efforts, Slic3r is still pushing the boundaries of 3D printing.
+
+Slic3r is:
+
+* **Open:** it is totally **open source** and it's **independent from any commercial company** or printer manufacturer. We want to keep 3D printing open and free.
+* **Compatible:** it supports all the known G-code dialects (Marlin, Repetier, Mach3, LinuxCNC, Machinekit, Smoothie, Makerware, Sailfish).
+* **Advanced:** many configuration options allow for fine-tuning and full control. While novice users often need just few options, Slic3r is mostly used by advanced users.
+* **Community-driven:** new features or issues are discussed in the [GitHub repository](https://github.com/alexrj/Slic3r/issues). Join our collaborative effort and help improve it!
+* **Robust:** the codebase includes more than 1,000 unit and regression tests, collected in 6 years of development.
+* **Modular:** the core of Slic3r is libslic3r, a C++ library that provides a granular API and reusable components.
+* **Embeddable:** a complete and powerful command line interface allows to use Slic3r from the shell or to integrate it in server-side applications.
+* **Powerful:** see the list below!
+
+See the [project homepage](http://slic3r.org/) at slic3r.org for more information.
+
+### Features
+
+(Most of these are also available in the command line interface.)
+
+* **G-code generation** for FFF/FDM printers;
+* **conversion** between STL, OBJ, AMF and POV formats;
+* **auto-repair** of non-manifold meshes (and ability to re-export them);
+* **SVG export** of slices;
+* built-in **USB/serial** host controller, supporting **multiple simultaneous printers** each one with a spool queue;
+* **OctoPrint integration** (send to printer);
+* built-in **projector and host for DLP printers**;
+* tool for **cutting meshes** in multiple solid parts with visual preview (also in batch using a grid);
+* tool for **extruding 2.5D TIN meshes**.
### What language is it written in?
-The core geometric algorithms and data structures are written in C++,
-and Perl is used for high-level flow abstraction, GUI and testing.
-If you're wondering why Perl, see https://xkcd.com/224/
-
-The C++ API is public and its use in other projects is encouraged.
-The goal is to make Slic3r fully modular so that any part of its logic
-can be used separately.
-
-### What are Slic3r's main features?
-
-Key features are:
-
-* **multi-platform** (Linux/Mac/Win) and packaged as standalone-app with no dependencies required
-* complete **command-line interface** to use it with no GUI
-* multi-material **(multiple extruders)** object printing
-* multiple G-code flavors supported (RepRap, Makerbot, Mach3, Machinekit etc.)
-* ability to plate **multiple objects having distinct print settings**
-* **multithread** processing
-* **STL auto-repair** (tolerance for broken models)
-* wide automated unit testing
-
-Other major features are:
-
-* combine infill every 'n' perimeters layer to speed up printing
-* **3D preview** (including multi-material files)
-* **multiple layer heights** in a single print
-* **spiral vase** mode for bumpless vases
-* fine-grained configuration of speed, acceleration, extrusion width
-* several infill patterns including honeycomb, spirals, Hilbert curves
-* support material, raft, brim, skirt
-* **standby temperature** and automatic wiping for multi-extruder printing
-* customizable **G-code macros** and output filename with variable placeholders
-* support for **post-processing scripts**
-* **cooling logic** controlling fan speed and dynamic print speed
+The core parts of Slic3r are written in C++11, with multithreading. The graphical interface is still mostly written in Perl, but we're gradually porting it to C++ (want to help?). The goal is to port everything to C++.
### How to install?
-You can download a precompiled package from [slic3r.org](http://slic3r.org/);
-it will run without the need for any dependency.
+You can download a precompiled package from [slic3r.org](http://slic3r.org/) (releases) or from [dl.slicr3r.org](http://dl.slic3r.org/dev/) (automated builds).
If you want to compile the source yourself follow the instructions on one of these wiki pages:
* [Linux](https://github.com/alexrj/Slic3r/wiki/Running-Slic3r-from-git-on-GNU-Linux)
@@ -67,28 +54,40 @@ If you want to compile the source yourself follow the instructions on one of the
### Can I help?
Sure! You can do the following to find things that are available to help with:
-* [Pull Request Milestone](https://github.com/alexrj/Slic3r/milestone/31)
- * Please comment in the related github issue that you are working on it so that other people know.
-* Items in the [TODO](https://github.com/alexrj/Slic3r/wiki/TODO) wiki page.
- * Please comment in the related github issue that you are working on it so that other people know.
-* Drop me a line at aar@cpan.org.
-* You can also find me (rarely) in #reprap and in #slic3r on [FreeNode](https://webchat.freenode.net) with the nickname _Sound_. Another contributor, _LoH_, is also in both channels.
-* Add an [issue](https://github.com/alexrj/Slic3r/issues) to the github tracker if it isn't already present.
-Before sending patches and pull requests contact me (preferably through opening a github issue or commenting on an existing, related, issue) to discuss your proposed
-changes: this way we'll ensure nobody wastes their time and no conflicts arise
-in development.
+* Development
+ * [Low Effort tasks](https://github.com/alexrj/Slic3r/labels/Low%20Effort): pick one of them!
+ * [More available tasks](https://github.com/alexrj/Slic3r/milestone/31): let's discuss together before you start working on them
+ * Please comment in the related github issue that you are working on it so that other people know.
+ * Please comment in the related GitHub issue that you are working on it so that other people know.
+* Contribute to the [Manual](http://manual.slic3r.org/)! (see its [GitHub repository](https://github.com/alexrj/Slic3r-Manual))
+* You can also find us in #slic3r on [FreeNode](https://webchat.freenode.net): talk to _Sound_, _LoH_ or the other members of the Slic3r community.
+* Add an [issue](https://github.com/alexrj/Slic3r/issues) to the GitHub tracker if it isn't already present.
+* Drop Alessandro a line at aar@cpan.org.
-### What's Slic3r license?
+### Directory structure
-Slic3r is licensed under the _GNU Affero General Public License, version 3_.
-The author is Alessandro Ranellucci.
+* `Build.PL`: this script installs dependencies into `local-lib/`, compiles the C++ part and runs tests
+* `lib/`: the Perl code
+* `package/`: the scripts used for packaging the executables
+* `slic3r.pl`: the main executable script, launching the GUI and providing the CLI
+* `src/`: the C++ source of the `slic3r` executable the and CMake definition file for compiling it (note that this C++ `slic3r` executable can do many things but can't generate G-code yet because the porting isn't finished yet - the main executable is `slic3r.pl`)
+* `t/`: the test suite
+* `utils/`: various useful scripts
+* `xs/src/libslic3r/`: C++ sources for libslic3r
+* `xs/src/slic3r/`: C++ sources for the Slic3r GUI application
+* `xs/t/`: test suite for libslic3r
+* `xs/xsp/`: bindings for calling libslic3r from Perl (XS)
-The [Silk icon set](http://www.famfamfam.com/lab/icons/silk/) used in Slic3r is
-licensed under the _Creative Commons Attribution 3.0 License_.
-The author of the Silk icon set is Mark James.
+### Acknowledgements
-### How can I invoke slic3r.pl using the command line?
+The main author of Slic3r is Alessandro Ranellucci (@alexrj, *Sound* in IRC, [@alranel](http://twitter.com/alranel) on Twitter), who started the project in 2011 and still leads development.
+
+Joseph Lenox (@lordofhyphens, *Loh* in IRC) is the current co-maintainer.
+
+Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Y. Sapir, Mike Sheldrake, Vojtech Bubnik and numerous others. Original manual by Gary Hodgson. Slic3r logo designed by Corey Daniels, Silk Icon Set designed by Mark James.
+
+### How can I invoke Slic3r using the command line?
Usage: slic3r.pl [ OPTIONS ] [ file.stl ] [ file2.stl ] ...
@@ -383,11 +382,4 @@ The author of the Silk icon set is Mark James.
Temperature difference to be applied when an extruder is not active and
--ooze-prevention is enabled (default: -5)
-
-If you want to change a preset file, just do
-
- slic3r.pl --load config.ini --layer-height 0.25 --save config.ini
-
-If you want to slice a file overriding an option contained in your preset file:
-
- slic3r.pl --load config.ini --layer-height 0.25 file.stl
+For more information about command line usage see the relevant [manual page](http://manual.slic3r.org/advanced/command-line).
diff --git a/lib/Slic3r/GUI/Controller/ManualControlDialog.pm b/lib/Slic3r/GUI/Controller/ManualControlDialog.pm
index 37dec1634..424ca3d73 100644
--- a/lib/Slic3r/GUI/Controller/ManualControlDialog.pm
+++ b/lib/Slic3r/GUI/Controller/ManualControlDialog.pm
@@ -1,5 +1,3 @@
-# A printer "Controller" -> "ManualControlDialog" subtab, opened per 3D printer connected?
-
package Slic3r::GUI::Controller::ManualControlDialog;
use strict;
use warnings;
@@ -9,7 +7,7 @@ use Scalar::Util qw(looks_like_number);
use Slic3r::Geometry qw(PI X Y unscale);
use Wx qw(:dialog :id :misc :sizer :choicebook :button :bitmap :textctrl
wxBORDER_NONE wxTAB_TRAVERSAL);
-use Wx::Event qw(EVT_CLOSE EVT_BUTTON);
+use Wx::Event qw(EVT_CLOSE EVT_BUTTON EVT_TEXT_ENTER);
use base qw(Wx::Dialog Class::Accessor);
__PACKAGE__->mk_accessors(qw(sender writer config2 x_homed y_homed));
@@ -163,6 +161,7 @@ sub new {
return;
}
my $cmd = $self->writer->set_temperature($self->config2->{temperature});
+ $self->GetParent->append_to_log(">> $cmd\n");
$self->sender->send($cmd, 1);
});
$optgroup->append_line($line);
@@ -182,6 +181,7 @@ sub new {
return;
}
my $cmd = $self->writer->set_bed_temperature($self->config2->{bed_temperature});
+ $self->GetParent->append_to_log(">> $cmd\n");
$self->sender->send($cmd, 1);
});
$optgroup->append_line($line);
@@ -189,19 +189,12 @@ sub new {
}
{
- my $box = Wx::StaticBox->new($self, -1, "Console");
+ my $box = Wx::StaticBox->new($self, -1, "Send manual command");
my $sbsizer = Wx::StaticBoxSizer->new($box, wxVERTICAL);
- $right_sizer->Add($sbsizer, 1, wxEXPAND, 0);
-
- my $log = $self->{log_textctrl} = Wx::TextCtrl->new($self, -1, "", wxDefaultPosition, wxDefaultSize,
- wxTE_MULTILINE | wxBORDER_NONE);
- $log->SetBackgroundColour($self->GetBackgroundColour);
- $log->SetFont($Slic3r::GUI::small_font);
- $log->SetEditable(0);
- $sbsizer->Add($self->{log_textctrl}, 1, wxEXPAND, 0);
+ $right_sizer->Add($sbsizer, 1, wxEXPAND | wxALL, 10);
my $cmd_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
- my $cmd_textctrl = Wx::TextCtrl->new($self, -1, '');
+ my $cmd_textctrl = Wx::TextCtrl->new($self, -1, '', wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER);
$cmd_sizer->Add($cmd_textctrl, 1, wxEXPAND, 0);
my $btn = Wx::Button->new($self, -1,
@@ -212,13 +205,17 @@ sub new {
}
$cmd_sizer->Add($btn, 0, wxEXPAND | wxLEFT, 5);
- EVT_BUTTON($self, $btn, sub {
- return if $cmd_textctrl->GetValue eq '';
- $self->sender->send($cmd_textctrl->GetValue, 1);
+ my $do_send = sub {
+ my $cmd = $cmd_textctrl->GetValue;
+ return if $cmd eq '';
+ $self->GetParent->append_to_log(">> $cmd\n");
+ $self->sender->send($cmd, 1);
$cmd_textctrl->SetValue('');
- });
+ };
+ EVT_BUTTON($self, $btn, $do_send);
+ EVT_TEXT_ENTER($self, $cmd_textctrl, $do_send);
- $sbsizer->Add($cmd_sizer, 0, wxEXPAND | wxTOP, 2);
+ $sbsizer->Add($cmd_sizer, 0, wxEXPAND | wxTOP, 5);
}
my $main_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
@@ -239,12 +236,6 @@ sub new {
return $self;
}
-sub update_log {
- my ($self, $log) = @_;
-
- $self->{log_textctrl}->SetValue($log);
-}
-
sub abs_xy_move {
my ($self, $pos) = @_;
diff --git a/lib/Slic3r/GUI/Controller/PrinterPanel.pm b/lib/Slic3r/GUI/Controller/PrinterPanel.pm
index 53850a69e..6a28e2ace 100644
--- a/lib/Slic3r/GUI/Controller/PrinterPanel.pm
+++ b/lib/Slic3r/GUI/Controller/PrinterPanel.pm
@@ -43,9 +43,7 @@ sub new {
$self->print_completed;
}
}
- $self->{log_textctrl}->AppendText("$_\n") for @{$self->sender->purge_log};
- $self->{manual_control_dialog}->update_log($self->{log_textctrl}->GetValue)
- if $self->{manual_control_dialog};
+ $self->append_to_log("$_\n") for @{$self->sender->purge_log};
{
my $temp = $self->sender->getT;
if ($temp eq '') {
@@ -272,6 +270,12 @@ sub new {
return $self;
}
+sub append_to_log {
+ my ($self, $text) = @_;
+
+ $self->{log_textctrl}->AppendText($text);
+}
+
sub is_connected {
my ($self) = @_;
return $self->sender && $self->sender->is_connected;
@@ -409,9 +413,9 @@ sub print_job {
$self->Layout;
$self->set_status('Printing...');
- $self->{log_textctrl}->AppendText(sprintf "=====\n");
- $self->{log_textctrl}->AppendText(sprintf "Printing %s\n", $job->name);
- $self->{log_textctrl}->AppendText(sprintf "Print started at %s\n", $self->_timestamp);
+ $self->append_to_log(sprintf "=====\n");
+ $self->append_to_log(sprintf "Printing %s\n", $job->name);
+ $self->append_to_log(sprintf "Print started at %s\n", $self->_timestamp);
}
sub print_completed {
@@ -426,7 +430,7 @@ sub print_completed {
$self->Layout;
$self->set_status('Print completed.');
- $self->{log_textctrl}->AppendText(sprintf "Print completed at %s\n", $self->_timestamp);
+ $self->append_to_log(sprintf "Print completed at %s\n", $self->_timestamp);
$self->reload_jobs;
}
@@ -479,7 +483,7 @@ sub reload_jobs {
$self->{gauge}->Disable;
$self->{gauge}->Hide;
$self->set_status('Print was aborted.');
- $self->{log_textctrl}->AppendText(sprintf "Print aborted at %s\n", $self->_timestamp);
+ $self->append_to_log(sprintf "Print aborted at %s\n", $self->_timestamp);
});
$panel->on_resume_print(sub {
my ($job) = @_;
diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
index 6ad698e7a..355c075c6 100644
--- a/lib/Slic3r/GUI/Plater.pm
+++ b/lib/Slic3r/GUI/Plater.pm
@@ -238,43 +238,10 @@ sub new {
EVT_LEFT_UP($self->{btn_send_gcode}, sub {
my (undef, $e) = @_;
- my $filename = basename($self->{print}->output_filepath($main::opt{output} // ''));
-
- if (!$e->AltDown) {
- # When the alt key is pressed, bypass the dialog.
- my $dlg = Slic3r::GUI::Plater::OctoPrintSpoolDialog->new($self, $filename);
- return unless $dlg->ShowModal == wxID_OK;
- $filename = $dlg->{filename};
- }
-
- if (!$Slic3r::GUI::Settings->{octoprint}{overwrite}) {
- my $progress = Wx::ProgressDialog->new('Querying OctoPrint…',
- "Checking whether file already exists…", 100, $self, 0);
- $progress->Pulse;
-
- my $ua = LWP::UserAgent->new;
- $ua->timeout(5);
- my $res = $ua->get("http://" . $self->{config}->octoprint_host . "/api/files/local");
- $progress->Destroy;
- if ($res->is_success) {
- if ($res->decoded_content =~ /"name":\s*"\Q$filename\E"/) {
- my $dialog = Wx::MessageDialog->new($self,
- "It looks like a file with the same name already exists in the server. "
- . "Shall I overwrite it?",
- 'OctoPrint', wxICON_WARNING | wxYES | wxNO);
- if ($dialog->ShowModal() == wxID_NO) {
- return;
- }
- }
- } else {
- my $message = "Error while connecting to the OctoPrint server: " . $res->status_line;
- Slic3r::GUI::show_error($self, $message);
- return;
- }
- }
-
- $self->{send_gcode_file_print} = $Slic3r::GUI::Settings->{octoprint}{start};
- $self->{send_gcode_file} = $self->export_gcode(Wx::StandardPaths::Get->GetTempDir() . "/" . $filename);
+ my $alt = $e->AltDown;
+ wxTheApp->CallAfter(sub {
+ $self->prepare_send($alt);
+ });
});
EVT_BUTTON($self, $self->{btn_export_stl}, \&export_stl);
@@ -897,6 +864,9 @@ sub load_model_objects {
# add a default instance and center object around origin
$o->center_around_origin; # also aligns object to Z = 0
$o->add_instance(offset => $bed_centerf);
+ } else {
+ # if object has defined positions we still need to ensure it's aligned to Z = 0
+ $o->align_to_ground;
}
{
@@ -1045,6 +1015,7 @@ sub set_number_of_copies {
# prompt user
my $copies = Wx::GetNumberFromUser("", "Enter the number of copies of the selected object:", "Copies", $model_object->instances_count, 0, 1000, $self);
+ return if $copies == -1;
my $diff = $copies - $model_object->instances_count;
if ($diff == 0) {
# no variation
@@ -1688,6 +1659,52 @@ sub do_print {
$self->GetFrame->select_tab(1);
}
+sub prepare_send {
+ my ($self, $skip_dialog) = @_;
+
+ return if !$self->{btn_send_gcode}->IsEnabled;
+ my $filename = basename($self->{print}->output_filepath($main::opt{output} // ''));
+
+ if (!$skip_dialog) {
+ # When the alt key is pressed, bypass the dialog.
+ my $dlg = Slic3r::GUI::Plater::OctoPrintSpoolDialog->new($self, $filename);
+ return unless $dlg->ShowModal == wxID_OK;
+ $filename = $dlg->{filename};
+ }
+
+ if (!$Slic3r::GUI::Settings->{octoprint}{overwrite}) {
+ my $progress = Wx::ProgressDialog->new('Querying OctoPrint…',
+ "Checking whether file already exists…", 100, $self, 0);
+ $progress->Pulse;
+
+ my $ua = LWP::UserAgent->new;
+ $ua->timeout(5);
+ my $res = $ua->get(
+ "http://" . $self->{config}->octoprint_host . "/api/files/local",
+ 'X-Api-Key' => $self->{config}->octoprint_apikey,
+ );
+ $progress->Destroy;
+ if ($res->is_success) {
+ if ($res->decoded_content =~ /"name":\s*"\Q$filename\E"/) {
+ my $dialog = Wx::MessageDialog->new($self,
+ "It looks like a file with the same name already exists in the server. "
+ . "Shall I overwrite it?",
+ 'OctoPrint', wxICON_WARNING | wxYES | wxNO);
+ if ($dialog->ShowModal() == wxID_NO) {
+ return;
+ }
+ }
+ } else {
+ my $message = "Error while connecting to the OctoPrint server: " . $res->status_line;
+ Slic3r::GUI::show_error($self, $message);
+ return;
+ }
+ }
+
+ $self->{send_gcode_file_print} = $Slic3r::GUI::Settings->{octoprint}{start};
+ $self->{send_gcode_file} = $self->export_gcode(Wx::StandardPaths::Get->GetTempDir() . "/" . $filename);
+}
+
sub send_gcode {
my ($self) = @_;
diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm
index 99c69cd4a..cba64323a 100644
--- a/lib/Slic3r/Model.pm
+++ b/lib/Slic3r/Model.pm
@@ -52,33 +52,6 @@ sub set_material {
return $material;
}
-sub looks_like_multipart_object {
- my ($self) = @_;
-
- return 0 if $self->objects_count == 1;
- return 0 if any { $_->volumes_count > 1 } @{$self->objects};
- return 0 if any { @{$_->config->get_keys} > 1 } @{$self->objects};
-
- my %heights = map { $_ => 1 } map $_->mesh->bounding_box->z_min, map @{$_->volumes}, @{$self->objects};
- return scalar(keys %heights) > 1;
-}
-
-sub convert_multipart_object {
- my ($self) = @_;
-
- my @objects = @{$self->objects};
- my $object = $self->add_object(
- input_file => $objects[0]->input_file,
- );
- foreach my $v (map @{$_->volumes}, @objects) {
- my $volume = $object->add_volume($v);
- $volume->set_name($v->object->name);
- }
- $object->add_instance($_) for map @{$_->instances}, @objects;
-
- $self->delete_object($_) for reverse 0..($self->objects_count-2);
-}
-
# Extends C++ class Slic3r::ModelMaterial
package Slic3r::Model::Material;
diff --git a/package/osx/plist.sh b/package/osx/plist.sh
index 898ae3be2..38e03edbc 100644
--- a/package/osx/plist.sh
+++ b/package/osx/plist.sh
@@ -91,6 +91,8 @@ cat << EOF >> $plistfile
LSMinimumSystemVersion
10.7
+ NSPrincipalClass
+ NSApplication
EOF
diff --git a/package/osx/startup_script.sh b/package/osx/startup_script.sh
index 4f665ac64..c7fd92d2b 100644
--- a/package/osx/startup_script.sh
+++ b/package/osx/startup_script.sh
@@ -1,4 +1,4 @@
#!/bin/bash
DIR=$(dirname "$0")
-"$DIR/perl-local" -I"$DIR/local-lib/lib/perl5" "$DIR/slic3r.pl" $@
+exec "$DIR/perl-local" -I"$DIR/local-lib/lib/perl5" "$DIR/slic3r.pl" $@
diff --git a/package/win/slic3r.rc b/package/win/slic3r.rc
index 20cfa265e..290d51939 100644
--- a/package/win/slic3r.rc
+++ b/package/win/slic3r.rc
@@ -2,6 +2,8 @@ id ICON "../../var/Slic3r.ico"
1 VERSIONINFO
FILEVERSION 1,3,0,0
PRODUCTVERSION 1,3,0,0
+FILEOS 0x4
+FILETYPE 0x1
BEGIN
BLOCK "StringFileInfo"
BEGIN
@@ -10,7 +12,7 @@ BEGIN
VALUE "CompanyName", "Slic3r.org"
VALUE "FileDescription", "3D Printer Slicer application"
VALUE "FileVersion", "1.3.0"
- VALUE "InternalName", "slic3r"
+ VALUE "InternalName", "slic3r.exe"
VALUE "LegalCopyright", "Alessandro Ranellucci"
VALUE "OriginalFilename", "slic3r.exe"
VALUE "ProductName", "Slic3r"
@@ -22,4 +24,4 @@ BEGIN
VALUE "Translation", 0x409, 1252
END
END
-1 Manifest slic3r.exe.manifest
+1 24 "slic3r.exe.manifest"
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 85e98471e..14f4a657a 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -58,6 +58,7 @@ add_library(libslic3r STATIC
${LIBDIR}/libslic3r/GCode/SpiralVase.cpp
${LIBDIR}/libslic3r/GCodeReader.cpp
${LIBDIR}/libslic3r/GCodeSender.cpp
+ ${LIBDIR}/libslic3r/GCodeTimeEstimator.cpp
${LIBDIR}/libslic3r/GCodeWriter.cpp
${LIBDIR}/libslic3r/Geometry.cpp
${LIBDIR}/libslic3r/IO.cpp
diff --git a/t/fill.t b/t/fill.t
index c9cb21391..710e60536 100644
--- a/t/fill.t
+++ b/t/fill.t
@@ -2,7 +2,7 @@ use Test::More;
use strict;
use warnings;
-plan tests => 93;
+plan tests => 95;
BEGIN {
use FindBin;
@@ -86,6 +86,23 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ }
'paths don\'t cross hole') or done_testing, exit;
}
}
+
+ # rotated square
+ $filler->set_angle(PI/4);
+ $filler->set_dont_adjust(0);
+ $filler->set_min_spacing(0.654498);
+ $filler->set_endpoints_overlap(unscale(359974));
+ $filler->set_density(1);
+ $filler->set_layer_id(66);
+ $filler->set_z(20.15);
+ {
+ my $e = Slic3r::ExPolygon->new(
+ Slic3r::Polygon->new([25771516,14142125],[14142138,25771515],[2512749,14142131],[14142125,2512749]),
+ );
+ my $paths = $test->($e);
+ is(scalar @$paths, 1, 'one continuous path') or done_testing, exit;
+ ok abs($paths->[0]->length - scale(3*100 + 2*50)) - scaled_epsilon, 'path has expected length';
+ }
}
{
diff --git a/utils/estimate-gcode-time.pl b/utils/estimate-gcode-time.pl
new file mode 100755
index 000000000..43c558b68
--- /dev/null
+++ b/utils/estimate-gcode-time.pl
@@ -0,0 +1,21 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+
+BEGIN {
+ use FindBin;
+ use lib "$FindBin::Bin/../lib";
+ use local::lib "$FindBin::Bin/../local-lib";
+}
+
+use Slic3r;
+
+die "Usage: estimate-gcode-time.pl FILE\n"
+ if @ARGV != 1;
+
+my $estimator = Slic3r::GCode::TimeEstimator->new;
+$estimator->parse_file($ARGV[0]);
+printf "Time: %d minutes and %d seconds\n", int($estimator->time / 60), $estimator->time % 60;
+
+__END__
diff --git a/utils/send-gcode.pl b/utils/send-gcode.pl
index 0b803baa6..d87e6826a 100644
--- a/utils/send-gcode.pl
+++ b/utils/send-gcode.pl
@@ -14,7 +14,8 @@ use Slic3r;
die "Usage: send-gcode.pl SERIALPORT BAUDRATE GCODE_FILE\n"
if @ARGV != 3;
-my $serial = Slic3r::GCode::Sender->new($ARGV[0], $ARGV[1]);
+my $serial = Slic3r::GCode::Sender->new;
+$serial->connect($ARGV[0], $ARGV[1]);
1 until $serial->is_connected;
print "Connected to printer\n";
diff --git a/xs/Build.PL b/xs/Build.PL
index 619cc879f..2fc5b33c1 100644
--- a/xs/Build.PL
+++ b/xs/Build.PL
@@ -70,6 +70,9 @@ if (defined $ENV{BOOST_INCLUDEDIR}) {
my $subdir = $ENV{BOOST_DIR} . (($mswin == 1) ? '\include' : '/include');
if (-d $subdir) {
push @boost_include, $subdir;
+ } elsif (-d "../$subdir") {
+ # User might have provided a path relative to the Build.PL in the main directory
+ push @boost_include, "../$subdir";
} else {
push @boost_include, $ENV{BOOST_DIR};
}
@@ -99,6 +102,9 @@ if (defined $ENV{BOOST_LIBRARYPATH}) {
my $subdir = $ENV{BOOST_DIR} . ($mswin ? '\stage\lib' : '/stage/lib');
if (-d $subdir) {
push @boost_libs, $subdir;
+ } elsif (-d "../$subdir") {
+ # User might have provided a path relative to the Build.PL in the main directory
+ push @boost_libs, "../$subdir";
} else {
push @boost_libs, $ENV{BOOST_DIR};
}
diff --git a/xs/MANIFEST b/xs/MANIFEST
index 61a97afed..5b2925f2c 100644
--- a/xs/MANIFEST
+++ b/xs/MANIFEST
@@ -72,6 +72,8 @@ src/libslic3r/GCodeReader.cpp
src/libslic3r/GCodeReader.hpp
src/libslic3r/GCodeSender.cpp
src/libslic3r/GCodeSender.hpp
+src/libslic3r/GCodeTimeEstimator.cpp
+src/libslic3r/GCodeTimeEstimator.hpp
src/libslic3r/GCodeWriter.cpp
src/libslic3r/GCodeWriter.hpp
src/libslic3r/Geometry.cpp
@@ -180,6 +182,7 @@ xsp/Filler.xsp
xsp/Flow.xsp
xsp/GCode.xsp
xsp/GCodeSender.xsp
+xsp/GCodeTimeEstimator.xsp
xsp/GCodeWriter.xsp
xsp/Geometry.xsp
xsp/GUI.xsp
diff --git a/xs/src/libslic3r/Fill/FillRectilinear.cpp b/xs/src/libslic3r/Fill/FillRectilinear.cpp
index c74da2877..e02c3a298 100644
--- a/xs/src/libslic3r/Fill/FillRectilinear.cpp
+++ b/xs/src/libslic3r/Fill/FillRectilinear.cpp
@@ -37,9 +37,14 @@ FillRectilinear::_fill_single_direction(ExPolygon expolygon,
if (bounding_box.size().x < min_spacing) return;
// Due to integer rounding, rotated polygons might not preserve verticality
- // (i.e. when rotating by PI/2 two points having the same x coordinate
- // they might get different y coordinates), thus the first line will be skipped.
- bounding_box.offset(-1);
+ // (i.e. when rotating by PI/2 two points having the same y coordinate
+ // they might get different x coordinates), thus the first line will be skipped.
+ // Reducing by 1 is not enough, as we observed normal squares being off by about 30
+ // units along x between points supposed to be vertically aligned (coming from an
+ // axis-aligned polygon edge). We need to be very tolerant here, especially when
+ // making solid infill where lack of lines is visible.
+ bounding_box.min.x += SCALED_EPSILON;
+ bounding_box.max.x -= SCALED_EPSILON;
// define flow spacing according to requested density
if (this->density > 0.9999f && !this->dont_adjust) {
diff --git a/xs/src/libslic3r/GCode/SpiralVase.cpp b/xs/src/libslic3r/GCode/SpiralVase.cpp
index b56c9e58b..9299da86a 100644
--- a/xs/src/libslic3r/GCode/SpiralVase.cpp
+++ b/xs/src/libslic3r/GCode/SpiralVase.cpp
@@ -39,7 +39,7 @@ SpiralVase::process_layer(const std::string &gcode)
{
GCodeReader r = this->_reader; // clone
r.parse(gcode, [&total_layer_length, &layer_height, &z, &set_z]
- (GCodeReader &, GCodeReader::GCodeLine &line) {
+ (GCodeReader &, const GCodeReader::GCodeLine &line) {
if (line.cmd == "G1") {
if (line.extruding()) {
total_layer_length += line.dist_XY();
diff --git a/xs/src/libslic3r/GCodeReader.cpp b/xs/src/libslic3r/GCodeReader.cpp
index 1a0742864..6ad937bbd 100644
--- a/xs/src/libslic3r/GCodeReader.cpp
+++ b/xs/src/libslic3r/GCodeReader.cpp
@@ -1,6 +1,7 @@
#include "GCodeReader.hpp"
#include
#include
+#include
#include
namespace Slic3r {
@@ -17,59 +18,73 @@ GCodeReader::parse(const std::string &gcode, callback_t callback)
{
std::istringstream ss(gcode);
std::string line;
- while (std::getline(ss, line)) {
- GCodeLine gline(this);
- gline.raw = line;
- if (this->verbose)
- std::cout << line << std::endl;
-
- // strip comment
- {
- size_t pos = line.find(';');
- if (pos != std::string::npos) {
- gline.comment = line.substr(pos+1);
- line.erase(pos);
- }
- }
-
- // command and args
- {
- std::vector args;
- boost::split(args, line, boost::is_any_of(" "));
-
- // first one is cmd
- gline.cmd = args.front();
- args.erase(args.begin());
-
- for (std::string &arg : args) {
- if (arg.size() < 2) continue;
- gline.args.insert(std::make_pair(arg[0], arg.substr(1)));
- }
- }
-
- // convert extrusion axis
- if (this->_extrusion_axis != 'E') {
- const auto it = gline.args.find(this->_extrusion_axis);
- if (it != gline.args.end()) {
- std::swap(gline.args['E'], it->second);
- gline.args.erase(it);
- }
- }
-
- if (gline.has('E') && this->_config.use_relative_e_distances)
- this->E = 0;
-
- if (callback) callback(*this, gline);
-
- // update coordinates
- if (gline.cmd == "G0" || gline.cmd == "G1" || gline.cmd == "G92") {
- this->X = gline.new_X();
- this->Y = gline.new_Y();
- this->Z = gline.new_Z();
- this->E = gline.new_E();
- this->F = gline.new_F();
+ while (std::getline(ss, line))
+ this->parse_line(line, callback);
+}
+
+void
+GCodeReader::parse_line(std::string line, callback_t callback)
+{
+ GCodeLine gline(this);
+ gline.raw = line;
+ if (this->verbose)
+ std::cout << line << std::endl;
+
+ // strip comment
+ {
+ size_t pos = line.find(';');
+ if (pos != std::string::npos) {
+ gline.comment = line.substr(pos+1);
+ line.erase(pos);
}
}
+
+ // command and args
+ {
+ std::vector args;
+ boost::split(args, line, boost::is_any_of(" "));
+
+ // first one is cmd
+ gline.cmd = args.front();
+ args.erase(args.begin());
+
+ for (std::string &arg : args) {
+ if (arg.size() < 2) continue;
+ gline.args.insert(std::make_pair(arg[0], arg.substr(1)));
+ }
+ }
+
+ // convert extrusion axis
+ if (this->_extrusion_axis != 'E') {
+ const auto it = gline.args.find(this->_extrusion_axis);
+ if (it != gline.args.end()) {
+ std::swap(gline.args['E'], it->second);
+ gline.args.erase(it);
+ }
+ }
+
+ if (gline.has('E') && this->_config.use_relative_e_distances)
+ this->E = 0;
+
+ if (callback) callback(*this, gline);
+
+ // update coordinates
+ if (gline.cmd == "G0" || gline.cmd == "G1" || gline.cmd == "G92") {
+ this->X = gline.new_X();
+ this->Y = gline.new_Y();
+ this->Z = gline.new_Z();
+ this->E = gline.new_E();
+ this->F = gline.new_F();
+ }
+}
+
+void
+GCodeReader::parse_file(const std::string &file, callback_t callback)
+{
+ std::ifstream f(file);
+ std::string line;
+ while (std::getline(f, line))
+ this->parse_line(line, callback);
}
void
diff --git a/xs/src/libslic3r/GCodeReader.hpp b/xs/src/libslic3r/GCodeReader.hpp
index 267ac0e18..c627fc851 100644
--- a/xs/src/libslic3r/GCodeReader.hpp
+++ b/xs/src/libslic3r/GCodeReader.hpp
@@ -25,6 +25,7 @@ class GCodeReader {
GCodeLine(GCodeReader* _reader) : reader(_reader) {};
bool has(char arg) const { return this->args.count(arg) > 0; };
+ float get_float(char arg) const { return atof(this->args.at(arg).c_str()); };
float new_X() const { return this->has('X') ? atof(this->args.at('X').c_str()) : this->reader->X; };
float new_Y() const { return this->has('Y') ? atof(this->args.at('Y').c_str()) : this->reader->Y; };
float new_Z() const { return this->has('Z') ? atof(this->args.at('Z').c_str()) : this->reader->Z; };
@@ -44,7 +45,7 @@ class GCodeReader {
bool travel() const { return this->cmd == "G1" && !this->has('E'); };
void set(char arg, std::string value);
};
- typedef std::function callback_t;
+ typedef std::function callback_t;
float X, Y, Z, E, F;
bool verbose;
@@ -53,6 +54,8 @@ class GCodeReader {
GCodeReader() : X(0), Y(0), Z(0), E(0), F(0), verbose(false), _extrusion_axis('E') {};
void apply_config(const PrintConfigBase &config);
void parse(const std::string &gcode, callback_t callback);
+ void parse_line(std::string line, callback_t callback);
+ void parse_file(const std::string &file, callback_t callback);
private:
GCodeConfig _config;
diff --git a/xs/src/libslic3r/GCodeSender.cpp b/xs/src/libslic3r/GCodeSender.cpp
index c1568790e..646fdb4bd 100644
--- a/xs/src/libslic3r/GCodeSender.cpp
+++ b/xs/src/libslic3r/GCodeSender.cpp
@@ -8,16 +8,36 @@
#include
#include
-#if defined(__APPLE__) || defined(__linux) || defined(__OpenBSD__)
+#if defined(__APPLE__) || defined(__OpenBSD__)
#include
#endif
-#if __APPLE__
+#ifdef __APPLE__
#include
#include
#endif
-#ifdef __linux
+#ifdef __linux__
#include
-#include
+#include
+#include "/usr/include/asm-generic/ioctls.h"
+
+/* The following definitions are kindly borrowed from:
+ /usr/include/asm-generic/termbits.h
+ Unfortunately we cannot just include that one because
+ it would redefine the "struct termios" already defined
+ the already included by Boost.ASIO. */
+#define K_NCCS 19
+struct termios2 {
+ tcflag_t c_iflag;
+ tcflag_t c_oflag;
+ tcflag_t c_cflag;
+ tcflag_t c_lflag;
+ cc_t c_line;
+ cc_t c_cc[K_NCCS];
+ speed_t c_ispeed;
+ speed_t c_ospeed;
+};
+#define BOTHER CBAUDEX
+
#endif
//#define DEBUG_SERIAL
@@ -65,7 +85,6 @@ GCodeSender::connect(std::string devname, unsigned int baud_rate)
this->open = true;
this->reset();
} catch (boost::system::system_error &e) {
- printf("Caught error\n");
this->set_error_status(true);
return false;
}
@@ -107,27 +126,17 @@ GCodeSender::set_baud_rate(unsigned int baud_rate)
ioctl(handle, IOSSIOSPEED, &newSpeed);
::tcsetattr(handle, TCSANOW, &ios);
#elif __linux
- termios ios;
- ::tcgetattr(handle, &ios);
- ::cfsetispeed(&ios, B38400);
- ::cfsetospeed(&ios, B38400);
- ::tcflush(handle, TCIFLUSH);
- ::tcsetattr(handle, TCSANOW, &ios);
-
- struct serial_struct ss;
- ioctl(handle, TIOCGSERIAL, &ss);
- ss.flags = (ss.flags & ~ASYNC_SPD_MASK) | ASYNC_SPD_CUST;
- ss.custom_divisor = (ss.baud_base + (baud_rate / 2)) / baud_rate;
- //cout << "bbase " << ss.baud_base << " div " << ss.custom_divisor;
- long closestSpeed = ss.baud_base / ss.custom_divisor;
- //cout << " Closest speed " << closestSpeed << endl;
- ss.reserved_char[0] = 0;
- if (closestSpeed < baud_rate * 98 / 100 || closestSpeed > baud_rate * 102 / 100) {
- printf("Failed to set baud rate\n");
- }
-
- ioctl(handle, TIOCSSERIAL, &ss);
- printf("< set_baud_rate: %u\n", baud_rate);
+ termios2 ios;
+ if (ioctl(handle, TCGETS2, &ios))
+ printf("Error in TCGETS2: %s\n", strerror(errno));
+ ios.c_ispeed = ios.c_ospeed = baud_rate;
+ ios.c_cflag &= ~CBAUD;
+ ios.c_cflag |= BOTHER | CLOCAL | CREAD;
+ ios.c_cc[VMIN] = 1; // Minimum of characters to read, prevents eof errors when 0 bytes are read
+ ios.c_cc[VTIME] = 1;
+ if (ioctl(handle, TCSETS2, &ios))
+ printf("Error in TCSETS2: %s\n", strerror(errno));
+
#elif __OpenBSD__
struct termios ios;
::tcgetattr(handle, &ios);
@@ -296,17 +305,20 @@ GCodeSender::on_read(const boost::system::error_code& error,
{
this->set_error_status(false);
if (error) {
- if (error.value() == 45) {
+ #ifdef __APPLE__
+ if (error.value() == 45 || ) {
// OS X bug: http://osdir.com/ml/lib.boost.asio.user/2008-08/msg00004.html
this->do_read();
- } else {
- // printf("ERROR: [%d] %s\n", error.value(), error.message().c_str());
- // error can be true even because the serial port was closed.
- // In this case it is not a real error, so ignore.
- if (this->open) {
- this->do_close();
- this->set_error_status(true);
- }
+ return;
+ }
+ #endif
+
+ // printf("ERROR: [%d] %s\n", error.value(), error.message().c_str());
+ // error can be true even because the serial port was closed.
+ // In this case it is not a real error, so ignore.
+ if (this->open) {
+ this->do_close();
+ this->set_error_status(true);
}
return;
}
diff --git a/xs/src/libslic3r/GCodeTimeEstimator.cpp b/xs/src/libslic3r/GCodeTimeEstimator.cpp
new file mode 100644
index 000000000..c6fa353b4
--- /dev/null
+++ b/xs/src/libslic3r/GCodeTimeEstimator.cpp
@@ -0,0 +1,78 @@
+#include "GCodeTimeEstimator.hpp"
+#include
+#include
+
+namespace Slic3r {
+
+void
+GCodeTimeEstimator::parse(const std::string &gcode)
+{
+ GCodeReader::parse(gcode, boost::bind(&GCodeTimeEstimator::_parser, this, _1, _2));
+}
+
+void
+GCodeTimeEstimator::parse_file(const std::string &file)
+{
+ GCodeReader::parse_file(file, boost::bind(&GCodeTimeEstimator::_parser, this, _1, _2));
+}
+
+void
+GCodeTimeEstimator::_parser(GCodeReader&, const GCodeReader::GCodeLine &line)
+{
+ // std::cout << "[" << this->time << "] " << line.raw << std::endl;
+ if (line.cmd == "G1") {
+ const float dist_XY = line.dist_XY();
+ const float new_F = line.new_F();
+
+ if (dist_XY > 0) {
+ //this->time += dist_XY / new_F * 60;
+ this->time += _accelerated_move(dist_XY, new_F/60, this->acceleration);
+ } else {
+ //this->time += std::abs(line.dist_E()) / new_F * 60;
+ this->time += _accelerated_move(std::abs(line.dist_E()), new_F/60, this->acceleration);
+ }
+ //this->time += std::abs(line.dist_Z()) / new_F * 60;
+ this->time += _accelerated_move(std::abs(line.dist_Z()), new_F/60, this->acceleration);
+ } else if (line.cmd == "M204" && line.has('S')) {
+ this->acceleration = line.get_float('S');
+ } else if (line.cmd == "G4") { // swell
+ if (line.has('S')) {
+ this->time += line.get_float('S');
+ } else if (line.has('P')) {
+ this->time += line.get_float('P')/1000;
+ }
+ }
+}
+
+// Wildly optimistic acceleration "bell" curve modeling.
+// Returns an estimate of how long the move with a given accel
+// takes in seconds.
+// It is assumed that the movement is smooth and uniform.
+float
+GCodeTimeEstimator::_accelerated_move(double length, double v, double acceleration)
+{
+ // for half of the move, there are 2 zones, where the speed is increasing/decreasing and
+ // where the speed is constant.
+ // Since the slowdown is assumed to be uniform, calculate the average velocity for half of the
+ // expected displacement.
+ // final velocity v = a*t => a * (dx / 0.5v) => v^2 = 2*a*dx
+ // v_avg = 0.5v => 2*v_avg = v
+ // d_x = v_avg*t => t = d_x / v_avg
+ acceleration = (acceleration == 0.0 ? 4000.0 : acceleration); // Set a default accel to use for print time in case it's 0 somehow.
+ auto half_length = length / 2.0;
+ auto t_init = v / acceleration; // time to final velocity
+ auto dx_init = (0.5*v*t_init); // Initial displacement for the time to get to final velocity
+ auto t = 0.0;
+ if (half_length >= dx_init) {
+ half_length -= (0.5*v*t_init);
+ t += t_init;
+ t += (half_length / v); // rest of time is at constant speed.
+ } else {
+ // If too much displacement for the expected final velocity, we don't hit the max, so reduce
+ // the average velocity to fit the displacement we actually are looking for.
+ t += std::sqrt(std::abs(length) * 2.0 * acceleration) / acceleration;
+ }
+ return 2.0*t; // cut in half before, so double to get full time spent.
+}
+
+}
diff --git a/xs/src/libslic3r/GCodeTimeEstimator.hpp b/xs/src/libslic3r/GCodeTimeEstimator.hpp
new file mode 100644
index 000000000..dd301c929
--- /dev/null
+++ b/xs/src/libslic3r/GCodeTimeEstimator.hpp
@@ -0,0 +1,24 @@
+#ifndef slic3r_GCodeTimeEstimator_hpp_
+#define slic3r_GCodeTimeEstimator_hpp_
+
+#include "libslic3r.h"
+#include "GCodeReader.hpp"
+
+namespace Slic3r {
+
+class GCodeTimeEstimator : public GCodeReader {
+ public:
+ float time = 0; // in seconds
+
+ void parse(const std::string &gcode);
+ void parse_file(const std::string &file);
+
+ protected:
+ float acceleration = 9000;
+ void _parser(GCodeReader&, const GCodeReader::GCodeLine &line);
+ static float _accelerated_move(double length, double v, double acceleration);
+};
+
+} /* namespace Slic3r */
+
+#endif /* slic3r_GCodeTimeEstimator_hpp_ */
diff --git a/xs/src/libslic3r/LayerRegionFill.cpp b/xs/src/libslic3r/LayerRegionFill.cpp
index 954f1d135..d6b567ceb 100644
--- a/xs/src/libslic3r/LayerRegionFill.cpp
+++ b/xs/src/libslic3r/LayerRegionFill.cpp
@@ -246,6 +246,12 @@ LayerRegion::make_fill()
// apply half spacing using this flow's own spacing and generate infill
f->density = density/100;
f->dont_adjust = false;
+ /*
+ std::cout << surface.expolygon.dump_perl() << std::endl
+ << " layer_id: " << f->layer_id << " z: " << f->z
+ << " angle: " << f->angle << " min-spacing: " << f->min_spacing
+ << " endpoints_overlap: " << f->endpoints_overlap << std::endl << std::endl;
+ */
Polylines polylines = f->fill_surface(surface);
if (polylines.empty())
continue;
diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp
index 9182cf56b..5243799ca 100644
--- a/xs/src/libslic3r/Model.cpp
+++ b/xs/src/libslic3r/Model.cpp
@@ -2,6 +2,7 @@
#include "Geometry.hpp"
#include "IO.hpp"
#include
+#include
#include
#include
@@ -372,6 +373,43 @@ Model::print_info() const
(*o)->print_info();
}
+bool
+Model::looks_like_multipart_object() const
+{
+ if (this->objects.size() == 1) return false;
+ for (const ModelObject* o : this->objects) {
+ if (o->volumes.size() > 1) return false;
+ if (o->config.keys().size() > 1) return false;
+ }
+
+ std::set heights;
+ for (const ModelObject* o : this->objects)
+ for (const ModelVolume* v : o->volumes)
+ heights.insert(v->mesh.bounding_box().min.z);
+ return heights.size() > 1;
+}
+
+void
+Model::convert_multipart_object()
+{
+ if (this->objects.empty()) return;
+
+ ModelObject* object = this->add_object();
+ object->input_file = this->objects.front()->input_file;
+
+ for (const ModelObject* o : this->objects) {
+ for (const ModelVolume* v : o->volumes) {
+ ModelVolume* v2 = object->add_volume(*v);
+ v2->name = o->name;
+ }
+ }
+ for (const ModelInstance* i : this->objects.front()->instances)
+ object->add_instance(*i);
+
+ while (this->objects.size() > 1)
+ this->delete_object(0);
+}
+
ModelMaterial::ModelMaterial(Model *model) : model(model) {}
ModelMaterial::ModelMaterial(Model *model, const ModelMaterial &other)
: attributes(other.attributes), config(other.config), model(model)
@@ -597,6 +635,20 @@ ModelObject::instance_bounding_box(size_t instance_idx) const
return bb;
}
+void
+ModelObject::align_to_ground()
+{
+ // calculate the displacements needed to
+ // center this object around the origin
+ BoundingBoxf3 bb;
+ for (const ModelVolume* v : this->volumes)
+ if (!v->modifier)
+ bb.merge(v->mesh.bounding_box());
+
+ this->translate(0, 0, -bb.min.z);
+ this->origin_translation.translate(0, 0, -bb.min.z);
+}
+
void
ModelObject::center_around_origin()
{
diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp
index 9c8e0d1e2..36571ff5d 100644
--- a/xs/src/libslic3r/Model.hpp
+++ b/xs/src/libslic3r/Model.hpp
@@ -74,6 +74,8 @@ class Model
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 print_info() const;
+ bool looks_like_multipart_object() const;
+ void convert_multipart_object();
};
// Material, which may be shared across multiple ModelObjects of a single Model.
@@ -149,6 +151,7 @@ class ModelObject
TriangleMesh raw_mesh() const;
BoundingBoxf3 raw_bounding_box() const;
BoundingBoxf3 instance_bounding_box(size_t instance_idx) const;
+ void align_to_ground();
void center_around_origin();
void translate(const Vectorf3 &vector);
void translate(coordf_t x, coordf_t y, coordf_t z);
diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp
index 9ce012a36..a60de7252 100644
--- a/xs/src/perlglue.cpp
+++ b/xs/src/perlglue.cpp
@@ -19,6 +19,7 @@ REGISTER_CLASS(SpiralVase, "GCode::SpiralVase");
REGISTER_CLASS(Wipe, "GCode::Wipe");
REGISTER_CLASS(GCode, "GCode");
REGISTER_CLASS(GCodeSender, "GCode::Sender");
+REGISTER_CLASS(GCodeTimeEstimator, "GCode::TimeEstimator");
REGISTER_CLASS(GCodeWriter, "GCode::Writer");
REGISTER_CLASS(Layer, "Layer");
REGISTER_CLASS(SupportLayer, "Layer::Support");
diff --git a/xs/t/15_config.t b/xs/t/15_config.t
index 214baad9f..f6efc8a5d 100644
--- a/xs/t/15_config.t
+++ b/xs/t/15_config.t
@@ -286,6 +286,7 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo
is_deeply $config->get('retract_speed'), [0.4, 0.5], 'read_cli(): floats array';
}
{
+ no warnings 'qw';
my $config = $parse->(qw(--extruder-offset 0,0 --extruder-offset 10x5));
is_deeply [ map $_->pp, @{$config->get('extruder_offset')} ],
[[0,0], [10,5]], 'read_cli(): points array';
diff --git a/xs/xsp/GCodeTimeEstimator.xsp b/xs/xsp/GCodeTimeEstimator.xsp
new file mode 100644
index 000000000..9489ff917
--- /dev/null
+++ b/xs/xsp/GCodeTimeEstimator.xsp
@@ -0,0 +1,15 @@
+%module{Slic3r::XS};
+
+%{
+#include
+#include "libslic3r/GCodeTimeEstimator.hpp"
+%}
+
+%name{Slic3r::GCode::TimeEstimator} class GCodeTimeEstimator {
+ GCodeTimeEstimator();
+ ~GCodeTimeEstimator();
+
+ float time %get{time};
+ void parse(std::string gcode);
+ void parse_file(std::string file);
+};
diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp
index c5c700606..cb415ad14 100644
--- a/xs/xsp/Model.xsp
+++ b/xs/xsp/Model.xsp
@@ -95,6 +95,8 @@
void duplicate_objects(unsigned int copies_num, double dist, BoundingBoxf* bb = NULL);
void duplicate_objects_grid(unsigned int x, unsigned int y, double dist);
void print_info();
+ bool looks_like_multipart_object();
+ void convert_multipart_object();
void repair();
};
@@ -207,6 +209,7 @@ ModelMaterial::attributes()
bool needed_repair() const;
int materials_count() const;
int facets_count();
+ void align_to_ground();
void center_around_origin();
void translate(double x, double y, double z);
void scale_xyz(Pointf3* versor)
diff --git a/xs/xsp/my.map b/xs/xsp/my.map
index c47d79e00..b1f026eaa 100644
--- a/xs/xsp/my.map
+++ b/xs/xsp/my.map
@@ -214,6 +214,10 @@ GCodeSender* O_OBJECT_SLIC3R
Ref O_OBJECT_SLIC3R_T
Clone O_OBJECT_SLIC3R_T
+GCodeTimeEstimator* O_OBJECT_SLIC3R
+Ref O_OBJECT_SLIC3R_T
+Clone O_OBJECT_SLIC3R_T
+
GCodeWriter* O_OBJECT_SLIC3R
Ref O_OBJECT_SLIC3R_T
Clone O_OBJECT_SLIC3R_T