Merge remote-tracking branch 'origin/master' into gui3

This commit is contained in:
Alessandro Ranellucci 2017-03-25 15:27:41 +01:00
commit ccaafba4b8
30 changed files with 526 additions and 268 deletions

View File

@ -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

142
README.md
View File

@ -1,63 +1,50 @@
_Q: Oh cool, a new RepRap slicer?_
A: Yes.
Slic3r [![Build Status](https://travis-ci.org/alexrj/Slic3r.svg?branch=master)](https://travis-ci.org/alexrj/Slic3r) [![Build status](https://ci.appveyor.com/api/projects/status/8iqmeat6cj158vo6?svg=true)](https://ci.appveyor.com/project/lordofhyphens/slic3r) [![Build Status](http://osx-build.slic3r.org:8080/buildStatus/icon?job=Slic3r)](http://osx-build.slic3r.org:8080/job/Slic3r)
![](var/Slic3r_128px.png) Slic3r [![Build Status](https://travis-ci.org/alexrj/Slic3r.svg?branch=master)](https://travis-ci.org/alexrj/Slic3r) [![Build status](https://ci.appveyor.com/api/projects/status/8iqmeat6cj158vo6?svg=true)](https://ci.appveyor.com/project/lordofhyphens/slic3r) [![Build Status](http://osx-build.slic3r.org:8080/buildStatus/icon?job=Slic3r)](http://osx-build.slic3r.org:8080/job/Slic3r)
======
Prebuilt Windows (64-bit) and OSX (>10.7) builds:
* http://dl.slic3r.org/dev/
<img width=256 src=https://cloud.githubusercontent.com/assets/31754/22719818/09998c92-ed6d-11e6-9fa0-09de638f3a36.png />
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: <img width=150 src=https://cloud.githubusercontent.com/assets/31754/22719818/09998c92-ed6d-11e6-9fa0-09de638f3a36.png />
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.
### <a name="features"></a>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, <a href="http://www.famfamfam.com/lab/icons/silk/">Silk Icon Set</a> 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).

View File

@ -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) = @_;

View File

@ -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) = @_;

View File

@ -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) = @_;

View File

@ -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;

View File

@ -91,6 +91,8 @@ cat << EOF >> $plistfile
</array>
<key>LSMinimumSystemVersion</key>
<string>10.7</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>
EOF

View File

@ -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" $@

View File

@ -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"

View File

@ -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

View File

@ -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';
}
}
{

21
utils/estimate-gcode-time.pl Executable file
View File

@ -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__

View File

@ -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";

View File

@ -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};
}

View File

@ -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

View File

@ -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) {

View File

@ -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();

View File

@ -1,6 +1,7 @@
#include "GCodeReader.hpp"
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#include <fstream>
#include <iostream>
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<std::string> 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<std::string> 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

View File

@ -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<void(GCodeReader&, GCodeLine&)> callback_t;
typedef std::function<void(GCodeReader&, const GCodeLine&)> 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;

View File

@ -8,16 +8,36 @@
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/lexical_cast.hpp>
#if defined(__APPLE__) || defined(__linux) || defined(__OpenBSD__)
#if defined(__APPLE__) || defined(__OpenBSD__)
#include <termios.h>
#endif
#if __APPLE__
#ifdef __APPLE__
#include <sys/ioctl.h>
#include <IOKit/serial/ioss.h>
#endif
#ifdef __linux
#ifdef __linux__
#include <sys/ioctl.h>
#include <linux/serial.h>
#include <fcntl.h>
#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 <termios.h> 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;
}

View File

@ -0,0 +1,78 @@
#include "GCodeTimeEstimator.hpp"
#include <boost/bind.hpp>
#include <cmath>
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.
}
}

View File

@ -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_ */

View File

@ -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;

View File

@ -2,6 +2,7 @@
#include "Geometry.hpp"
#include "IO.hpp"
#include <iostream>
#include <set>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem.hpp>
@ -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<coordf_t> 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()
{

View File

@ -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);

View File

@ -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");

View File

@ -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';

View File

@ -0,0 +1,15 @@
%module{Slic3r::XS};
%{
#include <xsinit.h>
#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);
};

View File

@ -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)

View File

@ -214,6 +214,10 @@ GCodeSender* O_OBJECT_SLIC3R
Ref<GCodeSender> O_OBJECT_SLIC3R_T
Clone<GCodeSender> O_OBJECT_SLIC3R_T
GCodeTimeEstimator* O_OBJECT_SLIC3R
Ref<GCodeTimeEstimator> O_OBJECT_SLIC3R_T
Clone<GCodeTimeEstimator> O_OBJECT_SLIC3R_T
GCodeWriter* O_OBJECT_SLIC3R
Ref<GCodeWriter> O_OBJECT_SLIC3R_T
Clone<GCodeWriter> O_OBJECT_SLIC3R_T