diff --git a/.gitignore b/.gitignore
index c8c13aed4..8cac56759 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,3 +16,9 @@ package/osx/Slic3r*.app
*.dmg
*.swp
*.swo
+CMakeFiles
+*.orig
+*.a
+core
+CMakeCache.txt
+*.cmake
diff --git a/.travis.yml b/.travis.yml
index f399c1de8..f76e6822f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,7 +2,6 @@ language: perl
before_install:
- sh package/linux/travis-decrypt-key
install:
-- export LDLOADLIBS=-lstdc++
- export BOOST_DIR=$HOME/boost_1_63_0
- export SLIC3R_STATIC=1
- export CXX=g++-4.9
@@ -15,7 +14,10 @@ script:
after_success:
- eval $(perl -Mlocal::lib=$TRAVIS_BUILD_DIR/local-lib)
- LD_LIBRARY_PATH=$WXDIR/lib package/linux/make_archive.sh linux-x64
-- package/deploy/sftp.sh linux ~/slic3r-upload.rsa *.bz2
+- package/linux/appimage.sh x86_64
+- package/deploy/sftp.sh linux ~/slic3r-upload.rsa *.bz2 Slic3r*.AppImage
+- package/deploy/sftp-symlink.sh linux ~/slic3r-upload.rsa AppImage Slic3r*.AppImage
+- package/deploy/sftp-symlink.sh linux ~/slic3r-upload.rsa tar.bz2 *.bz2
branches:
only:
- master
@@ -33,6 +35,7 @@ addons:
- gcc-4.9
- libgtk2.0-0
- libgtk2.0-dev
+ - freeglut3
ssh_known_hosts: dl.slic3r.org
notifications:
irc:
@@ -41,7 +44,6 @@ notifications:
on_success: change
on_failure: always
use_notice: true
-sudo: required
dist: trusty
env:
matrix:
diff --git a/Build.PL b/Build.PL
old mode 100644
new mode 100755
index b7df2b192..2dd18435a
--- a/Build.PL
+++ b/Build.PL
@@ -12,7 +12,7 @@ my %prereqs = qw(
Encode::Locale 1.05
ExtUtils::CppGuess 0
ExtUtils::MakeMaker 6.80
- ExtUtils::ParseXS 3.22
+ ExtUtils::ParseXS 3.35
File::Basename 0
File::Spec 0
Getopt::Long 0
diff --git a/README.md b/README.md
index 5517ca174..ec7b6d790 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
- 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/slic3r/Slic3r) [](https://ci.appveyor.com/project/lordofhyphens/slic3r) [](http://osx-build.slic3r.org:8080/job/Slic3r)
======
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!
@@ -46,7 +46,7 @@ The core parts of Slic3r are written in C++11, with multithreading. The graphica
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:
+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)
* [Windows](https://github.com/alexrj/Slic3r/wiki/Running-Slic3r-from-git-on-Windows)
* [Mac OSX](https://github.com/alexrj/Slic3r/wiki/Running-Slic3r-from-git-on-OS-X)
@@ -58,7 +58,7 @@ Sure! You can do the following to find things that are available to help with:
* 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.
@@ -84,12 +84,12 @@ The main author of Slic3r is Alessandro Ranellucci (@alexrj, *Sound* in IRC, [@a
Joseph Lenox (@lordofhyphens, *Loh* in IRC) is the current co-maintainer.
-Contributions by Henrik Brix Andersen, Vojtech Bubnik, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Y. Sapir, Mike Sheldrake, Kliment Yanev and numerous others. Original manual by Gary Hodgson. Slic3r logo designed by Corey Daniels, Silk Icon Set designed by Mark James.
+Contributions by Henrik Brix Andersen, Vojtech Bubnik, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Y. Sapir, Mike Sheldrake, Kliment Yanev and numerous others. Original manual by Gary Hodgson. Slic3r logo designed by Corey Daniels, Silk Icon Set designed by Mark James, stl and gcode file icons designed by Akira Yasuda.
### How can I invoke Slic3r using the command line?
Usage: slic3r.pl [ OPTIONS ] [ file.stl ] [ file2.stl ] ...
-
+
--help Output this usage screen and exit
--version Output the version of Slic3r and exit
--save Save configuration to the specified file
@@ -108,14 +108,16 @@ Contributions by Henrik Brix Andersen, Vojtech Bubnik, Nicolas Dandrimont, Mark
them as _upper.stl and _lower.stl
--split Split the shells contained in given STL file into several STL files
--info Output information about the supplied file(s) and exit
-
+
-j, --threads Number of threads to use (1+, default: 2)
-
+
GUI options:
--gui Forces the GUI launch instead of command line slicing (if you
supply a model file, it will be loaded into the plater)
+ --no-gui Forces the command line slicing instead of gui.
+ This takes precedence over --gui if both are present.
--autosave Automatically export current configuration to the specified file
-
+
Output options:
--output-filename-format
Output file name format; all config options enclosed in brackets
@@ -126,8 +128,10 @@ Contributions by Henrik Brix Andersen, Vojtech Bubnik, Nicolas Dandrimont, Mark
--export-svg Export a SVG file containing slices instead of G-code.
-m, --merge If multiple files are supplied, they will be composed into a single
print rather than processed individually.
-
+
Printer options:
+ --bed-shape Coordinates in mm of the bed's points (default: 0x0,200x0,200x200,0x200)
+ --has-heatbed This will provide automatic generation of bed heating gcode
--nozzle-diameter Diameter of nozzle in mm (default: 0.5)
--print-center Coordinates in mm of the point to center the print around
(default: 100,100)
@@ -145,7 +149,7 @@ Contributions by Henrik Brix Andersen, Vojtech Bubnik, Nicolas Dandrimont, Mark
default: 0)
--pressure-advance Adjust pressure using the experimental advance algorithm (K constant,
set zero to disable; default: 0)
-
+
Filament options:
--filament-diameter Diameter in mm of your raw filament (default: 3)
--extrusion-multiplier
@@ -158,7 +162,7 @@ Contributions by Henrik Brix Andersen, Vojtech Bubnik, Nicolas Dandrimont, Mark
--bed-temperature Heated bed temperature in degree Celsius, set 0 to disable (default: 0)
--first-layer-bed-temperature Heated bed temperature for the first layer, in degree Celsius,
set 0 to disable (default: same as --bed-temperature)
-
+
Speed options:
--travel-speed Speed of non-print moves in mm/s (default: 130)
--perimeter-speed Speed of print moves for perimeters in mm/s (default: 30)
@@ -182,7 +186,7 @@ Contributions by Henrik Brix Andersen, Vojtech Bubnik, Nicolas Dandrimont, Mark
--gap-fill-speed Speed of gap fill print moves in mm/s (default: 20)
--first-layer-speed Speed of print moves for bottom layer, expressed either as an absolute
value or as a percentage over normal speeds (default: 30%)
-
+
Acceleration options:
--perimeter-acceleration
Overrides firmware's default acceleration for perimeters. (mm/s^2, set zero
@@ -199,7 +203,7 @@ Contributions by Henrik Brix Andersen, Vojtech Bubnik, Nicolas Dandrimont, Mark
--default-acceleration
Acceleration will be reset to this value after the specific settings above
have been applied. (mm/s^2, set zero to disable; default: 0)
-
+
Accuracy options:
--layer-height Layer height in mm (default: 0.3)
--first-layer-height Layer height for first layer (mm or %, default: 0.35)
@@ -207,11 +211,12 @@ Contributions by Henrik Brix Andersen, Vojtech Bubnik, Nicolas Dandrimont, Mark
Infill every N layers (default: 1)
--solid-infill-every-layers
Force a solid layer every N layers (default: 0)
-
+
Print options:
--perimeters Number of perimeters/horizontal skins (range: 0+, default: 3)
--top-solid-layers Number of solid layers to do for top surfaces (range: 0+, default: 3)
--bottom-solid-layers Number of solid layers to do for bottom surfaces (range: 0+, default: 3)
+ --min-shell-thickness Minimum thickness of all solid shells (range: 0+, default: 0)
--solid-layers Shortcut for setting the two options above at once
--fill-density Infill density (range: 0%-100%, default: 40%)
--fill-angle Infill angle in degrees (range: 0-90, default: 45)
@@ -238,14 +243,14 @@ Contributions by Henrik Brix Andersen, Vojtech Bubnik, Nicolas Dandrimont, Mark
--infill-only-where-needed
Only infill under ceilings (default: no)
--infill-first Make infill before perimeters (default: no)
-
+
Quality options (slower slicing):
--extra-perimeters Add more perimeters when needed (default: yes)
--avoid-crossing-perimeters Optimize travel moves so that no perimeters are crossed (default: no)
--thin-walls Detect single-width walls (default: yes)
--overhangs Experimental option to use bridge flow, speed and fan for overhangs
(default: yes)
-
+
Support material options:
--support-material Generate support material for overhangs
--support-material-threshold
@@ -270,7 +275,7 @@ Contributions by Henrik Brix Andersen, Vojtech Bubnik, Nicolas Dandrimont, Mark
regardless of --support-material and threshold (0+, default: 0)
--dont-support-bridges
Experimental option for preventing support material from being generated under bridged areas (default: yes)
-
+
Retraction options:
--retract-length Length of retraction in mm when pausing extrusion (default: 1)
--retract-speed Speed for retraction in mm/s (default: 30)
@@ -285,14 +290,14 @@ Contributions by Henrik Brix Andersen, Vojtech Bubnik, Nicolas Dandrimont, Mark
--retract-layer-change
Enforce a retraction before each Z move (default: no)
--wipe Wipe the nozzle while doing a retraction (default: no)
-
+
Retraction options for multi-extruder setups:
--retract-length-toolchange
Length of retraction in mm when disabling tool (default: 10)
--retract-restart-extra-toolchange
Additional amount of filament in mm to push after
switching tool (default: 0)
-
+
Cooling options:
--cooling Enable fan and cooling control
--min-fan-speed Minimum fan speed (default: 35%)
@@ -306,7 +311,7 @@ Contributions by Henrik Brix Andersen, Vojtech Bubnik, Nicolas Dandrimont, Mark
--disable-fan-first-layers Disable fan for the first N layers (default: 1)
--fan-always-on Keep fan always on at min fan speed, even for layers that don't need
cooling
-
+
Skirt options:
--skirts Number of skirts to draw (0+, default: 1)
--skirt-distance Distance in mm between innermost skirt and object
@@ -316,7 +321,7 @@ Contributions by Henrik Brix Andersen, Vojtech Bubnik, Nicolas Dandrimont, Mark
of filament on the first layer, for each extruder (mm, 0+, default: 0)
--brim-width Width of the brim that will get added to each object to help adhesion
(mm, default: 0)
-
+
Transform options:
--scale Factor for scaling input object (default: 1)
--rotate Rotation angle in degrees (0-360, default: 0)
@@ -324,11 +329,11 @@ Contributions by Henrik Brix Andersen, Vojtech Bubnik, Nicolas Dandrimont, Mark
--duplicate-grid Number of items with grid arrangement (default: 1,1)
--duplicate-distance Distance in mm between copies (default: 6)
--dont-arrange Don't arrange the objects on the build plate. The model coordinates
- define the absolute positions on the build plate.
+ define the absolute positions on the build plate.
The option --print-center will be ignored.
--xy-size-compensation
Grow/shrink objects by the configured absolute distance (mm, default: 0)
-
+
Sequential printing options:
--complete-objects When printing multiple objects and/or copies, complete each one before
starting the next one; watch out for extruder collisions (default: no)
@@ -336,11 +341,11 @@ Contributions by Henrik Brix Andersen, Vojtech Bubnik, Nicolas Dandrimont, Mark
(default: 20)
--extruder-clearance-height Maximum vertical extruder depth; i.e. vertical distance from
extruder tip and carriage bottom (default: 20)
-
+
Miscellaneous options:
--notes Notes to be added as comments to the output file
--resolution Minimum detail resolution (mm, set zero for full resolution, default: 0)
-
+
Flow options (advanced):
--extrusion-width Set extrusion width manually; it accepts either an absolute value in mm
(like 0.65) or a percentage over layer height (like 200%)
@@ -360,7 +365,7 @@ Contributions by Henrik Brix Andersen, Vojtech Bubnik, Nicolas Dandrimont, Mark
Set a different extrusion width for support material
--infill-overlap Overlap between infill and perimeters (default: 15%)
--bridge-flow-ratio Multiplier for extrusion when bridging (> 0, default: 1)
-
+
Multiple extruder options:
--extruder-offset Offset of each extruder, if firmware doesn't handle the displacement
(can be specified multiple times, default: 0x0)
diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm
index 8b1f2a7a2..47ed095f3 100644
--- a/lib/Slic3r.pm
+++ b/lib/Slic3r.pm
@@ -229,6 +229,7 @@ sub thread_cleanup {
*Slic3r::Geometry::BoundingBoxf::DESTROY = sub {};
*Slic3r::Geometry::BoundingBoxf3::DESTROY = sub {};
*Slic3r::Layer::PerimeterGenerator::DESTROY = sub {};
+ *Slic3r::LayerHeightSpline::DESTROY = sub {};
*Slic3r::Line::DESTROY = sub {};
*Slic3r::Linef3::DESTROY = sub {};
*Slic3r::Model::DESTROY = sub {};
diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm
index 52d67bfee..1de0bdda3 100644
--- a/lib/Slic3r/Config.pm
+++ b/lib/Slic3r/Config.pm
@@ -168,11 +168,17 @@ sub validate {
if $self->first_layer_height !~ /^(?:\d*(?:\.\d+)?)%?$/;
die "Invalid value for --first-layer-height\n"
if $self->get_value('first_layer_height') <= 0;
+
+ die "Adaptive slicing requires a non-relative first layer height.\n"
+ if $self->get_value('adaptive_slicing') == 1 and $self->first_layer_height =~ /^(?:\d*(?:\.\d+)?)%$/;
# --filament-diameter
die "Invalid value for --filament-diameter\n"
if grep $_ < 1, @{$self->filament_diameter};
+ die "Invalid value for --min-shell-thickness\n"
+ if $self->min_shell_thickness < 0;
+
# --nozzle-diameter
die "Invalid value for --nozzle-diameter\n"
if grep $_ < 0, @{$self->nozzle_diameter};
@@ -185,6 +191,7 @@ sub validate {
die "Invalid value for --solid-layers\n" if defined $self->solid_layers && $self->solid_layers < 0;
die "Invalid value for --top-solid-layers\n" if $self->top_solid_layers < 0;
die "Invalid value for --bottom-solid-layers\n" if $self->bottom_solid_layers < 0;
+ die "Invalid value for --min-top-bottom-shell-thickness\n" if $self->min_top_bottom_shell_thickness < 0;
# --gcode-flavor
die "Invalid value for --gcode-flavor\n"
@@ -250,7 +257,10 @@ sub validate {
die "Can't make less than one perimeter when spiral vase mode is enabled\n"
if $self->perimeters < 1;
-
+
+ die "Minimum shell thickness should be 0 when spiral vase mode is enabled\n"
+ if $self->min_shell_thickness > 0;
+
die "Spiral vase mode can only print hollow objects, so you need to set Fill density to 0\n"
if $self->fill_density > 0;
diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm
index 139e6774b..1bbffc92e 100644
--- a/lib/Slic3r/GUI.pm
+++ b/lib/Slic3r/GUI.pm
@@ -39,9 +39,11 @@ use Slic3r::GUI::Plater::3D;
use Slic3r::GUI::Plater::3DPreview;
use Slic3r::GUI::Plater::ObjectPartsPanel;
use Slic3r::GUI::Plater::ObjectCutDialog;
+use Slic3r::GUI::Plater::ObjectRotateFaceDialog;
use Slic3r::GUI::Plater::ObjectSettingsDialog;
use Slic3r::GUI::Plater::LambdaObjectDialog;
use Slic3r::GUI::Plater::OverrideSettingsPanel;
+use Slic3r::GUI::Plater::SplineControl;
use Slic3r::GUI::Preferences;
use Slic3r::GUI::ProgressStatusBar;
use Slic3r::GUI::Projector;
@@ -51,6 +53,7 @@ use Slic3r::GUI::Preset;
use Slic3r::GUI::PresetEditor;
use Slic3r::GUI::PresetEditorDialog;
use Slic3r::GUI::SLAPrintOptions;
+use Slic3r::GUI::ReloadDialog;
our $have_OpenGL = eval "use Slic3r::GUI::3DScene; 1";
our $have_LWP = eval "use LWP::UserAgent; 1";
@@ -59,17 +62,19 @@ use Wx::Event qw(EVT_IDLE EVT_COMMAND);
use base 'Wx::App';
use constant FILE_WILDCARDS => {
- known => 'Known files (*.stl, *.obj, *.amf, *.xml)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML',
+ known => 'Known files (*.stl, *.obj, *.amf, *.xml, *.3mf)|*.3mf;*.3MF;*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML',
stl => 'STL files (*.stl)|*.stl;*.STL',
obj => 'OBJ files (*.obj)|*.obj;*.OBJ',
amf => 'AMF files (*.amf)|*.amf;*.AMF;*.xml;*.XML',
+ tmf => '3MF files (*.3mf)|*.3mf;*.3MF',
ini => 'INI files *.ini|*.ini;*.INI',
gcode => 'G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC',
svg => 'SVG files *.svg|*.svg;*.SVG',
};
-use constant MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(known stl obj amf)};
+use constant MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(known stl obj amf tmf)};
use constant STL_MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(stl)};
use constant AMF_MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(amf)};
+use constant TMF_MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(tmf)};
our $datadir;
# If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden.
@@ -81,11 +86,16 @@ our $Settings = {
_ => {
version_check => 1,
autocenter => 1,
+ autoalignz => 1,
invert_zoom => 0,
background_processing => 0,
threads => $Slic3r::Config::Options->{threads}{default},
color_toolpaths_by => 'role',
tabbed_preset_editors => 1,
+ show_host => 0,
+ nudge_val => 1,
+ reload_hide_dialog => 0,
+ reload_behavior => 0
},
};
@@ -397,7 +407,7 @@ sub open_model {
|| $Slic3r::GUI::Settings->{recent}{config_directory}
|| '';
- my $dialog = Wx::FileDialog->new($window // $self->GetTopWindow, 'Choose one or more files (STL/OBJ/AMF):', $dir, "",
+ my $dialog = Wx::FileDialog->new($window // $self->GetTopWindow, 'Choose one or more files (STL/OBJ/AMF/3MF):', $dir, "",
MODEL_WILDCARD, wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
if ($dialog->ShowModal != wxID_OK) {
$dialog->Destroy;
diff --git a/lib/Slic3r/GUI/2DBed.pm b/lib/Slic3r/GUI/2DBed.pm
index afa31f951..b18bfa821 100644
--- a/lib/Slic3r/GUI/2DBed.pm
+++ b/lib/Slic3r/GUI/2DBed.pm
@@ -11,11 +11,21 @@ use Wx qw(:misc :pen :brush :font :systemsettings wxTAB_TRAVERSAL wxSOLID);
use Wx::Event qw(EVT_PAINT EVT_ERASE_BACKGROUND EVT_MOUSE_EVENTS EVT_SIZE);
use base qw(Wx::Panel Class::Accessor);
+# Color Scheme
+use Slic3r::GUI::ColorScheme;
+
__PACKAGE__->mk_accessors(qw(bed_shape interactive pos _scale_factor _shift on_move _painted));
sub new {
my ($class, $parent, $bed_shape) = @_;
+ if ( ( defined $Slic3r::GUI::Settings->{_}{colorscheme} ) && ( Slic3r::GUI::ColorScheme->can($Slic3r::GUI::Settings->{_}{colorscheme}) ) ) {
+ my $myGetSchemeName = \&{"Slic3r::GUI::ColorScheme::$Slic3r::GUI::Settings->{_}{colorscheme}"};
+ $myGetSchemeName->();
+ } else {
+ Slic3r::GUI::ColorScheme->getDefault();
+ }
+
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, [250,-1], wxTAB_TRAVERSAL);
$self->{user_drawn_background} = $^O ne 'darwin';
$self->bed_shape($bed_shape // []);
@@ -38,9 +48,10 @@ sub _repaint {
# On MacOS the background is erased, on Windows the background is not erased
# and on Linux/GTK the background is erased to gray color.
# Fill DC with the background on Windows & Linux/GTK.
- my $color = Wx::SystemSettings::GetSystemColour(wxSYS_COLOUR_3DLIGHT);
- $dc->SetPen(Wx::Pen->new($color, 1, wxSOLID));
- $dc->SetBrush(Wx::Brush->new($color, wxSOLID));
+ my $brush_background = Wx::Brush->new(Wx::Colour->new(@BACKGROUND255), wxSOLID);
+ my $pen_background = Wx::Pen->new(Wx::Colour->new(@BACKGROUND255), 1, wxSOLID);
+ $dc->SetPen($pen_background);
+ $dc->SetBrush($brush_background);
my $rect = $self->GetUpdateRegion()->GetBox();
$dc->DrawRectangle($rect->GetLeft(), $rect->GetTop(), $rect->GetWidth(), $rect->GetHeight());
}
@@ -89,7 +100,7 @@ sub _repaint {
# draw bed fill
{
$dc->SetPen(Wx::Pen->new(Wx::Colour->new(0,0,0), 1, wxSOLID));
- $dc->SetBrush(Wx::Brush->new(Wx::Colour->new(255,255,255), wxSOLID));
+ $dc->SetBrush(Wx::Brush->new(Wx::Colour->new(@BED_COLOR), wxSOLID));
$dc->DrawPolygon([ map $self->to_pixels($_), @$bed_shape ], 0, 0);
}
@@ -105,7 +116,7 @@ sub _repaint {
}
@polylines = @{intersection_pl(\@polylines, [$bed_polygon])};
- $dc->SetPen(Wx::Pen->new(Wx::Colour->new(230,230,230), 1, wxSOLID));
+ $dc->SetPen(Wx::Pen->new(Wx::Colour->new(@BED_GRID), 1, wxSOLID));
$dc->DrawLine(map @{$self->to_pixels([map unscale($_), @$_])}, @$_[0,-1]) for @polylines;
}
diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm
index 72e7d28ef..d59e8da5e 100644
--- a/lib/Slic3r/GUI/3DScene.pm
+++ b/lib/Slic3r/GUI/3DScene.pm
@@ -14,6 +14,7 @@ use Wx::GLCanvas qw(:all);
__PACKAGE__->mk_accessors( qw(_quat _dirty init
enable_picking
+ enable_face_select
enable_moving
on_viewport_changed
on_hover
@@ -44,14 +45,11 @@ __PACKAGE__->mk_accessors( qw(_quat _dirty init
use constant TRACKBALLSIZE => 0.8;
use constant TURNTABLE_MODE => 1;
use constant GROUND_Z => -0.02;
-use constant SELECTED_COLOR => [0,1,0];
-use constant HOVER_COLOR => [0.4,0.9,0];
use constant PI => 3.1415927;
# Constant to determine if Vertex Buffer objects are used to draw
# bed grid and the cut plane for object separation.
-use constant HAS_VBO => eval { glGenBuffersARB_p(0); 1 };
-
+use constant HAS_VBO => eval { glGenBuffersARB_p(0); GL_ARRAY_BUFFER_ARB; 1 };
# phi / theta angles to orient the camera.
use constant VIEW_TOP => [0.0,0.0];
@@ -62,6 +60,9 @@ use constant VIEW_FRONT => [0.0,90.0];
use constant VIEW_BACK => [180.0,90.0];
use constant VIEW_DIAGONAL => [45.0,45.0];
+# Color Scheme
+use Slic3r::GUI::ColorScheme;
+
use constant GIMBAL_LOCK_THETA_MAX => 170;
# make OpenGL::Array thread-safe
@@ -72,9 +73,15 @@ use constant GIMBAL_LOCK_THETA_MAX => 170;
sub new {
my ($class, $parent) = @_;
-
- # We can only enable multi sample anti aliasing wih wxWidgets 3.0.3 and with a hacked Wx::GLCanvas,
+ if ( ( defined $Slic3r::GUI::Settings->{_}{colorscheme} ) && ( Slic3r::GUI::ColorScheme->can($Slic3r::GUI::Settings->{_}{colorscheme}) ) ) {
+ my $myGetSchemeName = \&{"Slic3r::GUI::ColorScheme::$Slic3r::GUI::Settings->{_}{colorscheme}"};
+ $myGetSchemeName->();
+ } else {
+ Slic3r::GUI::ColorScheme->getDefault();
+ }
+
+ # We can only enable multi sample anti aliasing with wxWidgets 3.0.3 and with a hacked Wx::GLCanvas,
# which exports some new WX_GL_XXX constants, namely WX_GL_SAMPLE_BUFFERS and WX_GL_SAMPLES.
my $can_multisample =
Wx::wxVERSION >= 3.000003 &&
@@ -100,6 +107,7 @@ sub new {
$self->GetContext();
}
+ $self->{can_multisample} = $can_multisample;
$self->background(1);
$self->_quat((0, 0, 0, 1));
$self->_stheta(45);
@@ -510,13 +518,16 @@ sub set_bed_shape {
sub deselect_volumes {
my ($self) = @_;
$_->selected(0) for @{$self->volumes};
+ $_->selected_face(-1) for @{$self->volumes};
}
sub select_volume {
my ($self, $volume_idx) = @_;
- $self->volumes->[$volume_idx]->selected(1)
- if $volume_idx != -1;
+ if ($volume_idx != -1) {
+ $self->volumes->[$volume_idx]->selected(1);
+ $self->volumes->[$volume_idx]->selected_face($self->volumes->[$volume_idx]->hover_face);
+ }
}
sub SetCuttingPlane {
@@ -744,10 +755,10 @@ sub InitGL {
# Set antialiasing/multisampling
glDisable(GL_LINE_SMOOTH);
glDisable(GL_POLYGON_SMOOTH);
- glEnable(GL_MULTISAMPLE);
+ glEnable(GL_MULTISAMPLE) if ($self->{can_multisample});
# ambient lighting
- glLightModelfv_p(GL_LIGHT_MODEL_AMBIENT, 0.3, 0.3, 0.3, 1);
+ glLightModelfv_p(GL_LIGHT_MODEL_AMBIENT, 0.1, 0.1, 0.1, 1);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
@@ -755,10 +766,10 @@ sub InitGL {
# light from camera
glLightfv_p(GL_LIGHT1, GL_POSITION, 1, 0, 1, 0);
- glLightfv_p(GL_LIGHT1, GL_SPECULAR, 0.3, 0.3, 0.3, 1);
- glLightfv_p(GL_LIGHT1, GL_DIFFUSE, 0.2, 0.2, 0.2, 1);
+ glLightfv_p(GL_LIGHT1, GL_SPECULAR, 0.8, 0.8, 0.8, 1);
+ glLightfv_p(GL_LIGHT1, GL_DIFFUSE, 0.4, 0.4, 0.4, 1);
- # Enables Smooth Color Shading; try GL_FLAT for (lack of) fun.
+ # Enables Smooth Color Shading; try GL_FLAT for (lack of) fun. Default: GL_SMOOTH
glShadeModel(GL_SMOOTH);
glMaterialfv_p(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, 0.3, 0.3, 0.3, 1);
@@ -769,7 +780,7 @@ sub InitGL {
# A handy trick -- have surface material mirror the color.
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
glEnable(GL_COLOR_MATERIAL);
- glEnable(GL_MULTISAMPLE);
+ glEnable(GL_MULTISAMPLE) if ($self->{can_multisample});
}
sub Render {
@@ -781,7 +792,12 @@ sub Render {
$self->SetCurrent($context);
$self->InitGL;
- glClearColor(1, 1, 1, 1);
+ if($SOLID_BACKGROUNDCOLOR == 1){
+ glClearColor(@BACKGROUND_COLOR, 0);
+ } else {
+ glClearColor(1, 1, 1, 1);
+ }
+
glClearDepth(1);
glDepthFunc(GL_LESS);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
@@ -804,6 +820,7 @@ sub Render {
glLightfv_p(GL_LIGHT0, GL_DIFFUSE, 0.5, 0.5, 0.5, 1);
if ($self->enable_picking) {
+ glDisable(GL_MULTISAMPLE) if ($self->{can_multisample});
glDisable(GL_LIGHTING);
$self->draw_volumes(1);
glFlush();
@@ -814,6 +831,7 @@ sub Render {
my $volume_idx = $col->[0] + $col->[1]*256 + $col->[2]*256*256;
$self->_hover_volume_idx(undef);
$_->hover(0) for @{$self->volumes};
+ $_->hover_face(-1) for @{$self->volumes};
if ($volume_idx <= $#{$self->volumes}) {
$self->_hover_volume_idx($volume_idx);
@@ -824,6 +842,16 @@ sub Render {
}
$self->on_hover->($volume_idx) if $self->on_hover;
+
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ glFlush();
+ glFinish();
+ if($self->enable_face_select){
+ $self->draw_volumes(2, $volume_idx);
+ my $color = [ glReadPixels_p($pos->x, $self->GetSize->GetHeight - $pos->y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE) ];
+ my $face_idx = $color->[0] + $color->[1]*256 + $color->[2]*256*256;
+ $self->volumes->[$volume_idx]->hover_face($face_idx);
+ }
}
}
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
@@ -833,7 +861,7 @@ sub Render {
}
# draw fixed background
- if ($self->background) {
+ if ($SOLID_BACKGROUNDCOLOR == 0 && $self->background) {
glDisable(GL_LIGHTING);
glPushMatrix();
glLoadIdentity();
@@ -843,10 +871,10 @@ sub Render {
glLoadIdentity();
glBegin(GL_QUADS);
- glColor3f(0.0,0.0,0.0);
+ glColor3f( @BOTTOM_COLOR ); # bottom color
glVertex2f(-1.0,-1.0);
glVertex2f(1,-1.0);
- glColor3f(10/255,98/255,144/255);
+ glColor3f( @TOP_COLOR ); # top color
glVertex2f(1, 1);
glVertex2f(-1.0, 1);
glEnd();
@@ -880,7 +908,7 @@ sub Render {
# fall back on old behavior
glVertexPointer_c(3, GL_FLOAT, 0, $self->bed_triangles->ptr());
}
- glColor4f(0.8, 0.6, 0.5, 0.4);
+ glColor4f( @GROUND_COLOR );
glNormal3d(0,0,1);
glDrawArrays(GL_TRIANGLES, 0, $self->bed_triangles->elements / 3);
glDisableClientState(GL_VERTEX_ARRAY);
@@ -890,7 +918,7 @@ sub Render {
glEnable(GL_DEPTH_TEST);
# draw grid
- glLineWidth(3);
+ glLineWidth(2);
glEnableClientState(GL_VERTEX_ARRAY);
my $grid_vertex;
if (HAS_VBO) {
@@ -903,7 +931,7 @@ sub Render {
# fall back on old behavior
glVertexPointer_c(3, GL_FLOAT, 0, $self->bed_grid_lines->ptr());
}
- glColor4f(0.2, 0.2, 0.2, 0.4);
+ glColor4f( @GRID_COLOR );
glNormal3d(0,0,1);
glDrawArrays(GL_LINES, 0, $self->bed_grid_lines->elements / 3);
glDisableClientState(GL_VERTEX_ARRAY);
@@ -964,7 +992,7 @@ sub Render {
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBegin(GL_QUADS);
- glColor4f(0.8, 0.8, 0.8, 0.5);
+ glColor4f( @COLOR_CUTPLANE );
if ($self->cutting_plane_axis == X) {
glVertex3f($bb->x_min+$plane_z, $bb->y_min-20, $bb->z_min-20);
glVertex3f($bb->x_min+$plane_z, $bb->y_max+20, $bb->z_min-20);
@@ -1030,7 +1058,7 @@ sub draw_center_of_rotation {
}
sub draw_volumes {
- my ($self, $fakecolor) = @_;
+ my ($self, $fakecolor, $volume_to_render) = @_;
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
@@ -1038,22 +1066,28 @@ sub draw_volumes {
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
- foreach my $volume_idx (0..$#{$self->volumes}) {
+ my $upper = $volume_to_render // $#{$self->volumes};
+ my $lower = $volume_to_render // 0;
+ foreach my $volume_idx ($lower..$upper) {
my $volume = $self->volumes->[$volume_idx];
glPushMatrix();
glTranslatef(@{$volume->origin});
-
+ my @baseColor;
if ($fakecolor) {
my $r = ($volume_idx & 0x000000FF) >> 0;
my $g = ($volume_idx & 0x0000FF00) >> 8;
my $b = ($volume_idx & 0x00FF0000) >> 16;
- glColor4f($r/255.0, $g/255.0, $b/255.0, 1);
+ @baseColor = ($r/255.0, $g/255.0, $b/255.0, 1);
+ } elsif ($self->enable_face_select){
+ @baseColor = @{ $volume->color };
} elsif ($volume->selected) {
- glColor4f(@{ &SELECTED_COLOR }, $volume->color->[3]);
+ @baseColor = @SELECTED_COLOR;
+ push(@baseColor, $volume->color->[3]);
} elsif ($volume->hover) {
- glColor4f(@{ &HOVER_COLOR }, $volume->color->[3]);
+ @baseColor = @HOVER_COLOR;
+ push(@baseColor, $volume->color->[3]);
} else {
- glColor4f(@{ $volume->color });
+ @baseColor = @{ $volume->color };
}
my @sorted_z = ();
@@ -1078,6 +1112,7 @@ sub draw_volumes {
$min_offset //= 0;
$max_offset //= $volume->qverts->size;
+ glColor4f(@baseColor);
glVertexPointer_c(3, GL_FLOAT, 0, $volume->qverts->verts_ptr);
glNormalPointer_c(GL_FLOAT, 0, $volume->qverts->norms_ptr);
glDrawArrays(GL_QUADS, $min_offset / 3, ($max_offset-$min_offset) / 3);
@@ -1093,10 +1128,56 @@ sub draw_volumes {
}
$min_offset //= 0;
$max_offset //= $volume->tverts->size;
-
- glVertexPointer_c(3, GL_FLOAT, 0, $volume->tverts->verts_ptr);
- glNormalPointer_c(GL_FLOAT, 0, $volume->tverts->norms_ptr);
- glDrawArrays(GL_TRIANGLES, $min_offset / 3, ($max_offset-$min_offset) / 3);
+ if($fakecolor && $fakecolor == 2){
+ our @_cached_colors;
+ our $_cached_ptr;
+ my $pLen = @_cached_colors;
+ my $toAdd = max($max_offset/3*4 - $pLen, 0);
+ if($toAdd){
+ # TODO: move this into CPP to reduce memory consumption and decrease time when new models are added
+ my @colors = (@baseColor)x($toAdd);
+ for my $i (0 .. ($#colors/12)){
+ my $r = (($i+($pLen/12)) & 0x000000FF) >> 0;
+ my $g = (($i+($pLen/12)) & 0x0000FF00) >> 8;
+ my $b = (($i+($pLen/12)) & 0x00FF0000) >> 16;
+ $colors[$i*4*3 + 0] = $r / 255.0;
+ $colors[$i*4*3 + 1] = $g / 255.0;
+ $colors[$i*4*3 + 2] = $b / 255.0;
+ $colors[$i*4*3 + 3] = 1.0;
+ $colors[$i*4*3 + 4] = $r / 255.0;
+ $colors[$i*4*3 + 5] = $g / 255.0;
+ $colors[$i*4*3 + 6] = $b / 255.0;
+ $colors[$i*4*3 + 7] = 1.0;
+ $colors[$i*4*3 + 8] = $r / 255.0;
+ $colors[$i*4*3 + 9] = $g / 255.0;
+ $colors[$i*4*3 + 10] = $b / 255.0;
+ $colors[$i*4*3 + 11] = 1.0;
+ }
+ push(@_cached_colors, @colors);
+ $_cached_ptr = OpenGL::Array->new_list(GL_FLOAT,@_cached_colors);
+ }
+ glEnableClientState(GL_COLOR_ARRAY);
+ glColorPointer_c(4, GL_FLOAT, 0, $_cached_ptr->ptr());
+ glVertexPointer_c(3, GL_FLOAT, 0, $volume->tverts->verts_ptr);
+ glNormalPointer_c(GL_FLOAT, 0, $volume->tverts->norms_ptr);
+ glDrawArrays(GL_TRIANGLES, $min_offset / 3, ($max_offset-$min_offset) / 3);
+ glDisableClientState(GL_COLOR_ARRAY);
+ } else {
+ glVertexPointer_c(3, GL_FLOAT, 0, $volume->tverts->verts_ptr);
+ glNormalPointer_c(GL_FLOAT, 0, $volume->tverts->norms_ptr);
+ if ( (not $fakecolor) && $volume->selected && $volume->selected_face != -1 && $volume->selected_face <= $max_offset/3){
+ my $i = $volume->selected_face;
+ glColor4f(@SELECTED_COLOR,$volume->color->[3]);
+ glDrawArrays(GL_TRIANGLES, $i*3, 3);
+ }
+ if ( (not $fakecolor) && $volume->hover && $volume->hover_face != -1 && $volume->hover_face <= $max_offset/3){
+ my $i = $volume->hover_face;
+ glColor4f(@HOVER_COLOR,$volume->color->[3]);
+ glDrawArrays(GL_TRIANGLES, $i*3, 3);
+ }
+ glColor4f(@baseColor);
+ glDrawArrays(GL_TRIANGLES, $min_offset / 3, ($max_offset-$min_offset) / 3);
+ }
}
glPopMatrix();
@@ -1131,6 +1212,34 @@ sub draw_volumes {
glDisableClientState(GL_VERTEX_ARRAY);
}
+sub calculate_normal {
+ my $self = shift;
+ my ($volume_idx) = @_;
+ return undef if $volume_idx == -1;
+ my $volume = $self->volumes->[$volume_idx];
+ my $max_offset = $volume->tverts->size; # For now just assume this TODO: add the other checks
+
+ if ($volume->selected && $volume->selected_face != -1 && $volume->selected_face <= $max_offset/3){
+ my $i = $volume->selected_face;
+ my $p1 = $volume->tverts->get_point($i*3);
+ my $p2 = $volume->tverts->get_point($i*3+1);
+ my $p3 = $volume->tverts->get_point($i*3+2);
+ my $v1 = $p1->vector_to($p2);
+ my $v2 = $p1->vector_to($p3);
+
+ # Calculate the cross product
+ my $x = $v1->y() * $v2->z() - $v1->z() * $v2->y();
+ my $y = $v1->z() * $v2->x() - $v1->x() * $v2->z();
+ my $z = $v1->x() * $v2->y() - $v1->y() * $v2->x();
+
+ # Normalize it
+ my $d = sqrt($x*$x + $y*$y + $z*$z);
+ return Slic3r::Pointf3->new($x/$d,$y/$d,$z/$d);
+ }
+
+ return undef;
+}
+
package Slic3r::GUI::3DScene::Volume;
use Moo;
@@ -1140,7 +1249,9 @@ has 'color' => (is => 'ro', required => 1);
has 'select_group_id' => (is => 'rw', default => sub { -1 });
has 'drag_group_id' => (is => 'rw', default => sub { -1 });
has 'selected' => (is => 'rw', default => sub { 0 });
+has 'selected_face' => (is => 'rw', default => sub { -1 });
has 'hover' => (is => 'rw', default => sub { 0 });
+has 'hover_face' => (is => 'rw', default => sub { -1 });
has 'range' => (is => 'rw');
# geometric data
@@ -1164,6 +1275,7 @@ use OpenGL qw(:glconstants :gluconstants :glufunctions);
use List::Util qw(first min max);
use Slic3r::Geometry qw(scale unscale epsilon);
use Slic3r::Print::State ':steps';
+use Slic3r::GUI::ColorScheme;
__PACKAGE__->mk_accessors(qw(
colors
@@ -1175,12 +1287,20 @@ __PACKAGE__->mk_accessors(qw(
_objects_by_volumes
));
-sub default_colors { [1,0.95,0.2,1], [1,0.45,0.45,1], [0.5,1,0.5,1], [0.5,0.5,1,1] }
+sub default_colors { [@COLOR_PARTS], [@COLOR_INFILL], [@COLOR_SUPPORT], [@COLOR_UNKNOWN] }
sub new {
my $class = shift;
my $self = $class->SUPER::new(@_);
+
+ if ( ( defined $Slic3r::GUI::Settings->{_}{colorscheme} ) && ( Slic3r::GUI::ColorScheme->can($Slic3r::GUI::Settings->{_}{colorscheme}) ) ) {
+ my $myGetSchemeName = \&{"Slic3r::GUI::ColorScheme::$Slic3r::GUI::Settings->{_}{colorscheme}"};
+ $myGetSchemeName->();
+ } else {
+ Slic3r::GUI::ColorScheme->getDefault();
+ }
+
$self->colors([ $self->default_colors ]);
$self->color_by('volume'); # object | volume
$self->color_toolpaths_by('role'); # role | extruder
diff --git a/lib/Slic3r/GUI/BedShapeDialog.pm b/lib/Slic3r/GUI/BedShapeDialog.pm
index 1ef787e70..d333b4b28 100644
--- a/lib/Slic3r/GUI/BedShapeDialog.pm
+++ b/lib/Slic3r/GUI/BedShapeDialog.pm
@@ -281,7 +281,7 @@ sub _init_shape_options_page {
sub _load_stl {
my ($self) = @_;
- my $dialog = Wx::FileDialog->new($self, 'Choose a file to import bed shape from (STL/OBJ/AMF):', "", "", &Slic3r::GUI::MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
+ my $dialog = Wx::FileDialog->new($self, 'Choose a file to import bed shape from (STL/OBJ/AMF/3MF):', "", "", &Slic3r::GUI::MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if ($dialog->ShowModal != wxID_OK) {
$dialog->Destroy;
return;
diff --git a/lib/Slic3r/GUI/ColorScheme.pm b/lib/Slic3r/GUI/ColorScheme.pm
new file mode 100644
index 000000000..dec1dc392
--- /dev/null
+++ b/lib/Slic3r/GUI/ColorScheme.pm
@@ -0,0 +1,125 @@
+package Slic3r::GUI::ColorScheme;
+use strict;
+use warnings;
+
+use POSIX;
+use vars qw(@ISA @EXPORT);
+use Exporter 'import';
+our @ISA = 'Exporter';
+our @EXPORT = qw($DEFAULT_COLORSCHEME $SOLID_BACKGROUNDCOLOR @SELECTED_COLOR @HOVER_COLOR @TOP_COLOR @BOTTOM_COLOR @GRID_COLOR @GROUND_COLOR @COLOR_CUTPLANE @COLOR_PARTS @COLOR_INFILL @COLOR_SUPPORT @COLOR_UNKNOWN @BED_COLOR @BED_GRID @BED_SELECTED @BED_OBJECTS @BED_INSTANCE @BED_DRAGGED @BED_CENTER @BED_SKIRT @BED_CLEARANCE @BED_DARK @BACKGROUND255 @TOOL_DARK @TOOL_SUPPORT @TOOL_STEPPERIM @TOOL_INFILL @TOOL_SHADE @TOOL_COLOR @BACKGROUND_COLOR @SPLINE_L_PEN @SPLINE_O_PEN @SPLINE_I_PEN @SPLINE_R_PEN );
+
+# DEFAULT values
+our $DEFAULT_COLORSCHEME = 1;
+our $SOLID_BACKGROUNDCOLOR = 0;
+our @SELECTED_COLOR = (0, 1, 0);
+our @HOVER_COLOR = (0.4, 0.9, 0); # Hover over Model
+our @TOP_COLOR = (10/255,98/255,144/255); # TOP Backgroud color
+our @BOTTOM_COLOR = (0,0,0); # BOTTOM Backgroud color
+our @BACKGROUND_COLOR = @TOP_COLOR; # SOLID background color
+our @GRID_COLOR = (0.2, 0.2, 0.2, 0.4); # Grid color
+our @GROUND_COLOR = (0.8, 0.6, 0.5, 0.4); # Ground or Plate color
+our @COLOR_CUTPLANE = (.8, .8, .8, 0.5);
+our @COLOR_PARTS = (1, 0.95, 0.2, 1); # Perimeter color
+our @COLOR_INFILL = (1, 0.45, 0.45, 1);
+our @COLOR_SUPPORT = (0.5, 1, 0.5, 1);
+our @COLOR_UNKNOWN = (0.5, 0.5, 1, 1);
+our @BED_COLOR = (255, 255, 255);
+our @BED_GRID = (230, 230, 230);
+our @BED_SELECTED = (255, 166, 128);
+our @BED_OBJECTS = (210, 210, 210);
+our @BED_INSTANCE = (255, 128, 128);
+our @BED_DRAGGED = (128, 128, 255);
+our @BED_CENTER = (200, 200, 200);
+our @BED_SKIRT = (150, 150, 150);
+our @BED_CLEARANCE = (0, 0, 200);
+our @BACKGROUND255 = (255, 255, 255);
+our @TOOL_DARK = (0, 0, 0);
+our @TOOL_SUPPORT = (0, 0, 0);
+our @TOOL_INFILL = (0, 0, 0.7);
+our @TOOL_STEPPERIM = (0.7, 0, 0);
+our @TOOL_SHADE = (0.95, 0.95, 0.95);
+our @TOOL_COLOR = (0.9, 0.9, 0.9);
+our @SPLINE_L_PEN = (50, 50, 50);
+our @SPLINE_O_PEN = (200, 200, 200);
+our @SPLINE_I_PEN = (255, 0, 0);
+our @SPLINE_R_PEN = (5, 120, 160);
+our @BED_DARK = (0, 0, 0);
+
+# S O L A R I Z E
+# # http://ethanschoonover.com/solarized
+our @COLOR_BASE03 = (0.00000,0.16863,0.21176);
+our @COLOR_BASE02 = (0.02745,0.21176,0.25882);
+our @COLOR_BASE01 = (0.34510,0.43137,0.45882);
+our @COLOR_BASE00 = (0.39608,0.48235,0.51373);
+our @COLOR_BASE0 = (0.51373,0.58039,0.58824);
+our @COLOR_BASE1 = (0.57647,0.63137,0.63137);
+our @COLOR_BASE2 = (0.93333,0.90980,0.83529);
+our @COLOR_BASE3 = (0.99216,0.96471,0.89020);
+our @COLOR_YELLOW = (0.70980,0.53725,0.00000);
+our @COLOR_ORANGE = (0.79608,0.29412,0.08627);
+our @COLOR_RED = (0.86275,0.19608,0.18431);
+our @COLOR_MAGENTA = (0.82745,0.21176,0.50980);
+our @COLOR_VIOLET = (0.42353,0.44314,0.76863);
+our @COLOR_BLUE = (0.14902,0.54510,0.82353);
+our @COLOR_CYAN = (0.16471,0.63137,0.59608);
+our @COLOR_GREEN = (0.52157,0.60000,0.00000);
+
+# create your own theme:
+# 1. add new sub and name it according to your scheme
+# 2. add that name to Preferences.pm
+# 3. Choose your newly created theme in Slic3rs' Preferences (File -> Preferences).
+
+sub getSolarized { # add this name to Preferences.pm
+ $DEFAULT_COLORSCHEME = 0; # DISABLE default color scheme
+ $SOLID_BACKGROUNDCOLOR = 1; # Switch between SOLID or FADED background color
+ @SELECTED_COLOR = @COLOR_MAGENTA; # Color of selected Model
+ @HOVER_COLOR = @COLOR_VIOLET; # Color when hovering over Model
+ # @TOP_COLOR = @COLOR_BASE2; # FADE Background color - only used if $SOLID_BACKGROUNDCOLOR = 0
+ # @BOTTOM_COLOR = @COLOR_BASE02; # FADE Background color - only used if $SOLID_BACKGROUNDCOLOR = 0
+ @BACKGROUND_COLOR = @COLOR_BASE3; # SOLID Background color - REQUIRED for NOT getDefault
+ @GRID_COLOR = (@COLOR_BASE1, 0.4); # Grid
+ @GROUND_COLOR = (@COLOR_BASE2, 0.4); # Ground or Plate
+ @COLOR_CUTPLANE = (@COLOR_BASE1, 0.5); # Cut plane
+ @COLOR_PARTS = (@TOOL_COLOR, 1); # Perimeter
+ @COLOR_INFILL = (@COLOR_BASE2, 1); # Infill
+ @COLOR_SUPPORT = (@TOOL_SUPPORT, 1); # Support
+ @COLOR_UNKNOWN = (@COLOR_CYAN, 1); # I don't know what that color's for!
+
+ # 2DBed.pm and ./plater/2D.pm colors
+ @BED_COLOR = map { ceil($_ * 255) } @COLOR_BASE2; # do math -> multiply each value by 255 and round up
+ @BED_GRID = map { ceil($_ * 255) } @COLOR_BASE1; # Bed, Ground or Plate
+ @BED_SELECTED = map { ceil($_ * 255) } @COLOR_YELLOW; # Selected Model
+ @BED_INSTANCE = map { ceil($_ * 255) } @SELECTED_COLOR; # Real selected Model
+ @BED_OBJECTS = map { ceil($_ * 255) } @COLOR_PARTS; # Models on bed
+ @BED_DRAGGED = map { ceil($_ * 255) } @COLOR_CYAN; # Color while dragging
+ @BED_CENTER = map { ceil($_ * 255) } @COLOR_BASE1; # Cross hair
+ @BED_SKIRT = map { ceil($_ * 255) } @COLOR_BASE01; # Brim/Skirt
+ @BED_CLEARANCE = map { ceil($_ * 255) } @COLOR_BLUE; # not sure what that does
+ @BED_DARK = map { ceil($_ * 255) } @COLOR_BASE01; # not sure what that does
+ @BACKGROUND255 = map { ceil($_ * 255) } @BACKGROUND_COLOR; # Backgroud color, this time RGB
+
+ # 2DToolpaths.pm colors : LAYERS Tab
+ @TOOL_DARK = @COLOR_BASE01; # Brim/Skirt
+ @TOOL_SUPPORT = @COLOR_GREEN; # Support
+ @TOOL_INFILL = @COLOR_BASE1; # Infill
+ @TOOL_COLOR = @COLOR_BLUE; # Real Perimeter
+ @TOOL_STEPPERIM = @COLOR_CYAN; # Inner Perimeter
+ @TOOL_SHADE = @COLOR_BASE2; # Shade; model inside
+
+ # Colors for *Layer heights...* dialog
+ @SPLINE_L_PEN = map { ceil($_ * 255) } @COLOR_BASE01; # Line color
+ @SPLINE_O_PEN = map { ceil($_ * 255) } @COLOR_BASE1; # Original color
+ @SPLINE_I_PEN = map { ceil($_ * 255) } @COLOR_MAGENTA; # Interactive color
+ @SPLINE_R_PEN = map { ceil($_ * 255) } @COLOR_VIOLET; # Resulting color
+
+}
+
+sub getDefault{
+ $DEFAULT_COLORSCHEME = 1; # ENABLE default color scheme
+ # Define your function here
+ # getDefault is just a dummy function and uses the default values from above.
+}
+
+
+
+1; # REQUIRED at EOF
diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm
index aac9b85d3..629334c3b 100644
--- a/lib/Slic3r/GUI/MainFrame.pm
+++ b/lib/Slic3r/GUI/MainFrame.pm
@@ -100,6 +100,8 @@ sub _init_tabpanel {
$panel->OnActivate if $panel->can('OnActivate');
if ($self->{tabpanel}->GetSelection > 1) {
$self->{tabpanel}->SetWindowStyle($self->{tabpanel}->GetWindowStyleFlag | wxAUI_NB_CLOSE_ON_ACTIVE_TAB);
+ } elsif(($Slic3r::GUI::Settings->{_}{show_host} == 0) && ($self->{tabpanel}->GetSelection == 1)){
+ $self->{tabpanel}->SetWindowStyle($self->{tabpanel}->GetWindowStyleFlag | wxAUI_NB_CLOSE_ON_ACTIVE_TAB);
} else {
$self->{tabpanel}->SetWindowStyle($self->{tabpanel}->GetWindowStyleFlag & ~wxAUI_NB_CLOSE_ON_ACTIVE_TAB);
}
@@ -115,7 +117,8 @@ sub _init_tabpanel {
});
$panel->AddPage($self->{plater} = Slic3r::GUI::Plater->new($panel), "Plater");
- $panel->AddPage($self->{controller} = Slic3r::GUI::Controller->new($panel), "Controller");
+ $panel->AddPage($self->{controller} = Slic3r::GUI::Controller->new($panel), "Controller")
+ if ($Slic3r::GUI::Settings->{_}{show_host});
}
sub _init_menubar {
@@ -124,7 +127,7 @@ sub _init_menubar {
# File menu
my $fileMenu = Wx::Menu->new;
{
- wxTheApp->append_menu_item($fileMenu, "Open STL/OBJ/AMF…\tCtrl+O", 'Open a model', sub {
+ wxTheApp->append_menu_item($fileMenu, "Open STL/OBJ/AMF/3MF…\tCtrl+O", 'Open a model', sub {
$self->{plater}->add if $self->{plater};
}, undef, 'brick_add.png');
wxTheApp->append_menu_item($fileMenu, "Open 2.5D TIN mesh…", 'Import a 2.5D TIN mesh', sub {
@@ -143,6 +146,9 @@ sub _init_menubar {
wxTheApp->append_menu_item($fileMenu, "&Export Config Bundle…", 'Export all presets to file', sub {
$self->export_configbundle;
}, undef, 'lorry_go.png');
+ wxTheApp->append_menu_item($fileMenu, "&Import Config from GCode-File…", 'Load presets from a created GCode-File', sub {
+ $self->import_fromGCode;
+ }, undef, 'lorry_import.png');
$fileMenu->AppendSeparator();
my $repeat;
wxTheApp->append_menu_item($fileMenu, "Q&uick Slice…\tCtrl+U", 'Slice file', sub {
@@ -191,6 +197,12 @@ sub _init_menubar {
my $selectMenu = $self->{plater_select_menu} = Wx::Menu->new;
wxTheApp->append_submenu($self->{plater_menu}, "Select", 'Select an object in the plater', $selectMenu, undef, 'brick.png');
}
+ wxTheApp->append_menu_item($self->{plater_menu}, "Undo\tCtrl+Z", 'Undo', sub {
+ $plater->undo;
+ }, undef, 'arrow_undo.png');
+ wxTheApp->append_menu_item($self->{plater_menu}, "Redo\tCtrl+Shift+Z", 'Redo', sub {
+ $plater->redo;
+ }, undef, 'arrow_redo.png');
wxTheApp->append_menu_item($self->{plater_menu}, "Select Next Object\tCtrl+Right", 'Select Next Object in the plater', sub {
$plater->select_next;
}, undef, 'arrow_right.png');
@@ -211,6 +223,9 @@ sub _init_menubar {
wxTheApp->append_menu_item($self->{plater_menu}, "Export plate with modifiers as AMF...", 'Export current plate as AMF, including all modifier meshes', sub {
$plater->export_amf;
}, undef, 'brick_go.png');
+ wxTheApp->append_menu_item($self->{plater_menu}, "Export plate with modifiers as 3MF...", 'Export current plate as 3MF, including all modifier meshes', sub {
+ $plater->export_tmf;
+ }, undef, 'brick_go.png');
$self->{object_menu} = $self->{plater}->object_menu;
$self->on_plater_object_list_changed(0);
$self->on_plater_selection_changed(0);
@@ -279,6 +294,7 @@ sub _init_menubar {
}, undef, 'printer_empty.png');
wxTheApp->append_menu_item($windowMenu, "DLP Projector…\tCtrl+P", 'Open projector window for DLP printing', sub {
$self->{plater}->pause_background_process;
+ $self->{slaconfig} = Slic3r::Config->new;
Slic3r::GUI::SLAPrintOptions->new($self)->ShowModal;
$self->{plater}->resume_background_process;
}, undef, 'film.png');
@@ -328,12 +344,20 @@ sub is_loaded {
return $self->{loaded};
}
+sub on_undo_redo_stacks_changed {
+ my $self = shift;
+ # Enable undo or redo if they have operations in their stack.
+ $self->{plater_menu}->Enable($self->{plater_menu}->FindItem("Undo\tCtrl+Z"), $#{$self->{plater}->{undo_stack}} < 0 ? 0 : 1);
+ $self->{plater_menu}->Enable( $self->{plater_menu}->FindItem("Redo\tCtrl+Shift+Z"), $#{$self->{plater}->{redo_stack}} < 0 ? 0 : 1);
+}
+
sub on_plater_object_list_changed {
my ($self, $have_objects) = @_;
return if !defined $self->{plater_menu};
$self->{plater_menu}->Enable($_->GetId, $have_objects)
for $self->{plater_menu}->GetMenuItems;
+ $self->on_undo_redo_stacks_changed;
}
sub on_plater_selection_changed {
@@ -342,6 +366,8 @@ sub on_plater_selection_changed {
return if !defined $self->{object_menu};
$self->{object_menu}->Enable($_->GetId, $have_selection)
for $self->{object_menu}->GetMenuItems;
+ $self->on_undo_redo_stacks_changed;
+
}
sub quick_slice {
@@ -358,7 +384,7 @@ sub quick_slice {
my $input_file;
my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} || $Slic3r::GUI::Settings->{recent}{config_directory} || '';
if (!$params{reslice}) {
- my $dialog = Wx::FileDialog->new($self, 'Choose a file to slice (STL/OBJ/AMF):', $dir, "", &Slic3r::GUI::MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
+ my $dialog = Wx::FileDialog->new($self, 'Choose a file to slice (STL/OBJ/AMF/3MF):', $dir, "", &Slic3r::GUI::MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if ($dialog->ShowModal != wxID_OK) {
$dialog->Destroy;
return;
@@ -535,6 +561,64 @@ sub load_config_file {
$self->{plater}->select_preset_by_name($name, $_) for qw(print filament printer);
}
+sub import_fromGCode{ # import configuration from gcode file sliced with Slic3r
+ my ($self, $file) = @_;
+
+ if (!$file) {
+ my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || '';
+ my $dlg = Wx::FileDialog->new($self, 'Select GCode File to load config from:', $dir, &Slic3r::GUI::FILE_WILDCARDS->{gcode}, &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
+ return unless $dlg->ShowModal == wxID_OK;
+ $file = Slic3r::decode_path($dlg->GetPaths);
+ $dlg->Destroy;
+ }
+
+ $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file);
+ wxTheApp->save_settings;
+
+ # open $file and read the first line to make sure it was sliced using Slic3r
+ open(FILEFIRSTLINE, '<', "$file")
+ or die( "Can't open file to import gcode: $!" );
+ my $firstLine = ;
+ close (FILEFIRSTLINE);
+
+ # Exit, if file was not sliced using Slic3r
+ if( index($firstLine, "generated by Slic3r") < 0){
+ return;
+ }
+
+ # if file sliced by Slic3r, read it
+ open(GFILE, "$file")
+ or die( "Can't open file to import gcode: $!" );
+ my @lines = reverse ; # read the file from the back
+
+ my $line;
+ my @settinglines="";
+ foreach $line (@lines) {
+ # if line is empty, we're done -> EOF
+ if (substr($line, 0, 1) eq ";"){
+ $line = substr($line,2, length($line)-2);
+ push @settinglines, $line;
+ } else {
+ last;
+ }
+ }
+
+ close (GFILE);
+
+ # (over-) write config to temp-file ->
+ my $tempfile = substr $file, 0, rindex( $file, q{.} );
+ $tempfile="$tempfile.ini";
+
+ open (TEMPFILESETTINGS, "> $tempfile");
+ print TEMPFILESETTINGS @settinglines;
+ close (TEMPFILESETTINGS);
+
+ $self->load_config_file($tempfile);
+
+ # todo: do we want to delete the file after load_config?
+
+}
+
sub export_configbundle {
my $self = shift;
diff --git a/lib/Slic3r/GUI/OptionsGroup.pm b/lib/Slic3r/GUI/OptionsGroup.pm
index 776f80ec3..03a42ed02 100644
--- a/lib/Slic3r/GUI/OptionsGroup.pm
+++ b/lib/Slic3r/GUI/OptionsGroup.pm
@@ -205,6 +205,11 @@ sub _build_field {
parent => $self->parent,
option => $opt,
);
+ } elsif ($type eq 'point3'){
+ $field = Slic3r::GUI::OptionsGroup::Field::Point3->new(
+ parent => $self->parent,
+ option => $opt,
+ );
} elsif ($type eq 'slider') {
$field = Slic3r::GUI::OptionsGroup::Field::Slider->new(
parent => $self->parent,
diff --git a/lib/Slic3r/GUI/OptionsGroup/Field.pm b/lib/Slic3r/GUI/OptionsGroup/Field.pm
index 9d3fb7e0e..638612116 100644
--- a/lib/Slic3r/GUI/OptionsGroup/Field.pm
+++ b/lib/Slic3r/GUI/OptionsGroup/Field.pm
@@ -509,6 +509,87 @@ sub disable {
$self->y_textctrl->Disable;
}
+package Slic3r::GUI::OptionsGroup::Field::Point3;
+use Moo;
+extends 'Slic3r::GUI::OptionsGroup::Field::wxSizer';
+
+has 'x_textctrl' => (is => 'rw');
+has 'y_textctrl' => (is => 'rw');
+has 'z_textctrl' => (is => 'rw');
+
+use Slic3r::Geometry qw(X Y Z);
+use Wx qw(:misc :sizer);
+use Wx::Event qw(EVT_TEXT);
+
+sub BUILD {
+ my ($self) = @_;
+
+ my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
+ $self->wxSizer($sizer);
+
+ my $field_size = Wx::Size->new(40, -1);
+
+ $self->x_textctrl(Wx::TextCtrl->new($self->parent, -1, $self->option->default->[X], wxDefaultPosition, $field_size));
+ $self->y_textctrl(Wx::TextCtrl->new($self->parent, -1, $self->option->default->[Y], wxDefaultPosition, $field_size));
+ $self->z_textctrl(Wx::TextCtrl->new($self->parent, -1, $self->option->default->[Z], wxDefaultPosition, $field_size));
+
+ my @items = (
+ Wx::StaticText->new($self->parent, -1, "x:"),
+ $self->x_textctrl,
+ Wx::StaticText->new($self->parent, -1, " y:"),
+ $self->y_textctrl,
+ Wx::StaticText->new($self->parent, -1, " z:"),
+ $self->z_textctrl,
+ );
+ $sizer->Add($_, 0, wxALIGN_CENTER_VERTICAL, 0) for @items;
+
+ if ($self->option->tooltip) {
+ foreach my $item (@items) {
+ $item->SetToolTipString($self->option->tooltip)
+ if $item->can('SetToolTipString');
+ }
+ }
+
+ EVT_TEXT($self->parent, $_, sub {
+ $self->_on_change($self->option->opt_id);
+ }) for $self->x_textctrl, $self->y_textctrl, $self->z_textctrl;
+}
+
+sub set_value {
+ my ($self, $value) = @_;
+
+ $self->disable_change_event(1);
+ $self->x_textctrl->SetValue($value->[X]);
+ $self->y_textctrl->SetValue($value->[Y]);
+ $self->z_textctrl->SetValue($value->[Z]);
+ $self->disable_change_event(0);
+}
+
+sub get_value {
+ my ($self) = @_;
+
+ return [
+ $self->x_textctrl->GetValue,
+ $self->y_textctrl->GetValue,
+ $self->z_textctrl->GetValue,
+ ];
+}
+
+sub enable {
+ my ($self) = @_;
+
+ $self->x_textctrl->Enable;
+ $self->y_textctrl->Enable;
+ $self->z_textctrl->Enable;
+}
+
+sub disable {
+ my ($self) = @_;
+
+ $self->x_textctrl->Disable;
+ $self->y_textctrl->Disable;
+ $self->z_textctrl->Disable;
+}
package Slic3r::GUI::OptionsGroup::Field::Slider;
use Moo;
@@ -580,6 +661,17 @@ sub get_value {
return $self->slider->GetValue/$self->scale;
}
+# Update internal scaling
+sub set_scale {
+ my ($self, $scale) = @_;
+ $self->disable_change_event(1);
+ my $current_value = $self->get_value;
+ $self->slider->SetRange($self->slider->GetMin / $self->scale * $scale, $self->slider->GetMax / $self->scale * $scale);
+ $self->scale($scale);
+ $self->set_value($current_value);
+ $self->disable_change_event(0);
+}
+
sub _update_textctrl {
my ($self) = @_;
diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
index 9154acee3..335eff826 100644
--- a/lib/Slic3r/GUI/Plater.pm
+++ b/lib/Slic3r/GUI/Plater.pm
@@ -1,5 +1,20 @@
# The "Plater" tab. It contains the "3D", "2D", "Preview" and "Layers" subtabs.
+package Slic3r::GUI::Plater::UndoOperation;
+use strict;
+use warnings;
+
+sub new{
+ my $class = shift;
+ my $self = {
+ type => shift,
+ object_identifier => shift,
+ attributes => shift,
+ };
+ bless ($self, $class);
+ return $self;
+}
+
package Slic3r::GUI::Plater;
use strict;
use warnings;
@@ -8,12 +23,13 @@ use utf8;
use File::Basename qw(basename dirname);
use List::Util qw(sum first max none any);
use Slic3r::Geometry qw(X Y Z MIN MAX scale unscale deg2rad rad2deg);
+use Math::Trig qw(acos);
use LWP::UserAgent;
use threads::shared qw(shared_clone);
use Wx qw(:button :cursor :dialog :filedialog :keycode :icon :font :id :misc
:panel :sizer :toolbar :window wxTheApp :notebook :combobox);
use Wx::Event qw(EVT_BUTTON EVT_COMMAND EVT_KEY_DOWN EVT_MOUSE_EVENTS EVT_PAINT EVT_TOOL
- EVT_CHOICE EVT_COMBOBOX EVT_TIMER EVT_NOTEBOOK_PAGE_CHANGED EVT_LEFT_UP);
+ EVT_CHOICE EVT_COMBOBOX EVT_TIMER EVT_NOTEBOOK_PAGE_CHANGED EVT_LEFT_UP EVT_CLOSE);
use base qw(Wx::Panel Class::Accessor);
__PACKAGE__->mk_accessors(qw(presets));
@@ -28,9 +44,11 @@ use constant TB_MORE => &Wx::NewId;
use constant TB_FEWER => &Wx::NewId;
use constant TB_45CW => &Wx::NewId;
use constant TB_45CCW => &Wx::NewId;
+use constant TB_ROTFACE => &Wx::NewId;
use constant TB_SCALE => &Wx::NewId;
use constant TB_SPLIT => &Wx::NewId;
use constant TB_CUT => &Wx::NewId;
+use constant TB_LAYERS => &Wx::NewId;
use constant TB_SETTINGS => &Wx::NewId;
# package variables to avoid passing lexicals to threads
@@ -49,14 +67,23 @@ sub new {
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
$self->{config} = Slic3r::Config->new_from_defaults(qw(
bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width
- serial_port serial_speed octoprint_host octoprint_apikey shortcuts filament_colour
+ serial_port serial_speed host_type print_host octoprint_apikey shortcuts filament_colour
));
$self->{model} = Slic3r::Model->new;
$self->{print} = Slic3r::Print->new;
$self->{processed} = 0;
# List of Perl objects Slic3r::GUI::Plater::Object, representing a 2D preview of the platter.
$self->{objects} = [];
-
+
+ # Objects identifier used for undo/redo operations. It's a one time id assigned to each newly created object.
+ $self->{object_identifier} = 0;
+
+ # Stack of undo operations.
+ $self->{undo_stack} = [];
+
+ # Stack of redo operations.
+ $self->{redo_stack} = [];
+
$self->{print}->set_status_cb(sub {
my ($percent, $message) = @_;
@@ -166,11 +193,13 @@ sub new {
$self->{htoolbar}->AddSeparator;
$self->{htoolbar}->AddTool(TB_45CCW, "45° ccw", Wx::Bitmap->new($Slic3r::var->("arrow_rotate_anticlockwise.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_45CW, "45° cw", Wx::Bitmap->new($Slic3r::var->("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG), '');
+ $self->{htoolbar}->AddTool(TB_ROTFACE, "Rotate face", Wx::Bitmap->new($Slic3r::var->("rotate_face.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_SCALE, "Scale…", Wx::Bitmap->new($Slic3r::var->("arrow_out.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_SPLIT, "Split", Wx::Bitmap->new($Slic3r::var->("shape_ungroup.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_CUT, "Cut…", Wx::Bitmap->new($Slic3r::var->("package.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddSeparator;
$self->{htoolbar}->AddTool(TB_SETTINGS, "Settings…", Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG), '');
+ $self->{htoolbar}->AddTool(TB_LAYERS, "Layer heights…", Wx::Bitmap->new($Slic3r::var->("variable_layer_height.png"), wxBITMAP_TYPE_PNG), '');
} else {
my %tbar_buttons = (
add => "Add…",
@@ -181,13 +210,15 @@ sub new {
decrease => "",
rotate45ccw => "",
rotate45cw => "",
+ rotateFace => "",
changescale => "Scale…",
split => "Split",
cut => "Cut…",
+ layers => "Layer heights…",
settings => "Settings…",
);
$self->{btoolbar} = Wx::BoxSizer->new(wxHORIZONTAL);
- for (qw(add remove reset arrange increase decrease rotate45ccw rotate45cw changescale split cut settings)) {
+ for (qw(add remove reset arrange increase decrease rotate45ccw rotate45cw rotateFace changescale split cut layers settings)) {
$self->{"btn_$_"} = Wx::Button->new($self, -1, $tbar_buttons{$_}, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
$self->{btoolbar}->Add($self->{"btn_$_"});
}
@@ -218,9 +249,11 @@ sub new {
decrease delete.png
rotate45cw arrow_rotate_clockwise.png
rotate45ccw arrow_rotate_anticlockwise.png
+ rotateFace rotate_face.png
changescale arrow_out.png
split shape_ungroup.png
cut package.png
+ layers variable_layer_height.png
settings cog.png
);
for (grep $self->{"btn_$_"}, keys %icons) {
@@ -254,9 +287,11 @@ sub new {
EVT_TOOL($self, TB_FEWER, sub { $self->decrease; });
EVT_TOOL($self, TB_45CW, sub { $_[0]->rotate(-45) });
EVT_TOOL($self, TB_45CCW, sub { $_[0]->rotate(45) });
+ EVT_TOOL($self, TB_ROTFACE, sub { $_[0]->rotate_face });
EVT_TOOL($self, TB_SCALE, sub { $self->changescale(undef); });
EVT_TOOL($self, TB_SPLIT, sub { $self->split_object; });
EVT_TOOL($self, TB_CUT, sub { $_[0]->object_cut_dialog });
+ EVT_TOOL($self, TB_LAYERS, sub { $_[0]->object_layers_dialog });
EVT_TOOL($self, TB_SETTINGS, sub { $_[0]->object_settings_dialog });
} else {
EVT_BUTTON($self, $self->{btn_add}, sub { $self->add; });
@@ -267,9 +302,11 @@ sub new {
EVT_BUTTON($self, $self->{btn_decrease}, sub { $self->decrease; });
EVT_BUTTON($self, $self->{btn_rotate45cw}, sub { $_[0]->rotate(-45) });
EVT_BUTTON($self, $self->{btn_rotate45ccw}, sub { $_[0]->rotate(45) });
+ EVT_BUTTON($self, $self->{btn_rotateFace}, sub { $_[0]->rotate_face });
EVT_BUTTON($self, $self->{btn_changescale}, sub { $self->changescale(undef); });
EVT_BUTTON($self, $self->{btn_split}, sub { $self->split_object; });
EVT_BUTTON($self, $self->{btn_cut}, sub { $_[0]->object_cut_dialog });
+ EVT_BUTTON($self, $self->{btn_layers}, sub { $_[0]->object_layers_dialog });
EVT_BUTTON($self, $self->{btn_settings}, sub { $_[0]->object_settings_dialog });
}
@@ -811,7 +848,7 @@ sub show_preset_editor {
$preset_editor->on_save_preset($cb);
if ($dlg) {
- $dlg->ShowModal;
+ $dlg->Show;
}
});
}
@@ -857,11 +894,276 @@ sub config {
return $config;
}
+sub get_object_index {
+ my $self = shift;
+ my ($object_indentifier) = @_;
+ return undef if !defined $object_indentifier;
+
+ for (my $i = 0; $i <= $#{$self->{objects}}; $i++){
+ if ($self->{objects}->[$i]->identifier eq $object_indentifier) {
+ return $i;
+ }
+ }
+ return undef;
+}
+
+sub add_undo_operation {
+ my $self = shift;
+ my @parameters = @_;
+
+ my $type = $parameters[0];
+ my $object_identifier = $parameters[1];
+ my @attributes = @parameters[2..$#parameters]; # operation values.
+
+ my $new_undo_operation = new Slic3r::GUI::Plater::UndoOperation($type, $object_identifier, \@attributes);
+
+ push @{$self->{undo_stack}}, $new_undo_operation;
+
+ $self->{redo_stack} = [];
+
+ $self->limit_undo_operations(8); # Current limit of undo/redo operations.
+ $self->GetFrame->on_undo_redo_stacks_changed;
+
+ return $new_undo_operation;
+}
+
+sub limit_undo_operations {
+ my ($self, $limit)= @_;
+ return if !defined $limit;
+ # Delete undo operations succeeded by 4 operations or more to save memory.
+ while ($#{$self->{undo_stack}} + 1 > $limit) {
+ print "Removing an old operation.\n";
+ splice @{$self->{undo_stack}}, 0, 1;
+ }
+}
+
+sub undo {
+ my $self = shift;
+
+ my $operation = pop @{$self->{undo_stack}};
+ return if !defined $operation;
+
+ push @{$self->{redo_stack}}, $operation;
+
+ my $type = $operation->{type};
+
+ if ($type eq "ROTATE") {
+ my $object_id = $operation->{object_identifier};
+ my $obj_idx = $self->get_object_index($object_id);
+ $self->select_object($obj_idx);
+
+ my $angle = $operation->{attributes}->[0];
+ my $axis = $operation->{attributes}->[1];
+ $self->rotate(-1 * $angle, $axis, 'true'); # Apply inverse transformation.
+
+ } elsif ($type eq "INCREASE") {
+ my $object_id = $operation->{object_identifier};
+ my $obj_idx = $self->get_object_index($object_id);
+ $self->select_object($obj_idx);
+
+ my $copies = $operation->{attributes}->[0];
+ $self->decrease($copies, 'true');
+
+ } elsif ($type eq "DECREASE") {
+ my $object_id = $operation->{object_identifier};
+ my $obj_idx = $self->get_object_index($object_id);
+ $self->select_object($obj_idx);
+
+ my $copies = $operation->{attributes}->[0];
+ $self->increase($copies, 'true');
+
+ } elsif ($type eq "MIRROR") {
+ my $object_id = $operation->{object_identifier};
+ my $obj_idx = $self->get_object_index($object_id);
+ $self->select_object($obj_idx);
+
+ my $axis = $operation->{attributes}->[0];
+ $self->mirror($axis, 'true');
+
+ } elsif ($type eq "REMOVE") {
+ my $_model = $operation->{attributes}->[0];
+ $self->load_model_objects(@{$_model->objects});
+ $self->{object_identifier}--; # Decrement the identifier as we will change the object identifier with the saved one.
+ $self->{objects}->[-1]->identifier($operation->{object_identifier});
+
+ } elsif ($type eq "CUT" || $type eq "SPLIT") {
+ # Delete the produced objects.
+ my $obj_identifiers_start = $operation->{attributes}->[2];
+ for (my $i_object = 0; $i_object < $#{$operation->{attributes}->[1]->objects} + 1; $i_object++) {
+ $self->remove($self->get_object_index($obj_identifiers_start++), 'true');
+ }
+ # Add the original object.
+ $self->load_model_objects(@{$operation->{attributes}->[0]->objects});
+ $self->{object_identifier}--;
+ $self->{objects}->[-1]->identifier($operation->{object_identifier}); # Add the original assigned identifier.
+
+ } elsif ($type eq "CHANGE_SCALE") {
+ my $object_id = $operation->{object_identifier};
+ my $obj_idx = $self->get_object_index($object_id);
+ $self->select_object($obj_idx);
+
+ my $axis = $operation->{attributes}->[0];
+ my $tosize = $operation->{attributes}->[1];
+ my $saved_scale = $operation->{attributes}->[3];
+ $self->changescale($axis, $tosize, $saved_scale, 'true');
+
+ } elsif ($type eq "RESET") {
+ # Revert changes to the plater object identifier. It's modified when adding new objects only not when undo/redo is executed.
+ my $current_objects_identifier = $self->{object_identifier};
+ my $_model = $operation->{attributes}->[0];
+ $self->load_model_objects(@{$_model->objects});
+ $self->{object_identifier} = $current_objects_identifier;
+
+ # don't forget the identifiers.
+ my $objects_count = $#{$operation->{attributes}->[0]->objects} + 1;
+
+ foreach my $identifier (@{$operation->{attributes}->[1]})
+ {
+ $self->{objects}->[-$objects_count]->identifier($identifier);
+ $objects_count--;
+ }
+
+ } elsif ($type eq "ADD") {
+ my $objects_count = $#{$operation->{attributes}->[0]->objects} + 1;
+ my $identifier_start = $operation->{attributes}->[1];
+ for (my $identifier = $identifier_start; $identifier < $objects_count + $identifier_start; $identifier++) {
+ my $obj_idx = $self->get_object_index($identifier);
+ $self->remove($obj_idx, 'true');
+ }
+ } elsif ($type eq "GROUP"){
+ my @ops = @{$operation->{attributes}};
+ push @{$self->{undo_stack}}, @ops;
+ foreach my $op (@ops) {
+ $self->undo;
+ pop @{$self->{redo_stack}};
+ }
+ }
+}
+
+sub redo {
+ my $self = shift;
+
+ my $operation = pop @{$self->{redo_stack}};
+ return if !defined $operation;
+
+ push @{$self->{undo_stack}}, $operation;
+
+ my $type = $operation->{type};
+
+ if ($type eq "ROTATE") {
+ my $object_id = $operation->{object_identifier};
+ my $obj_idx = $self->get_object_index($object_id);
+ $self->select_object($obj_idx);
+
+ my $angle = $operation->{attributes}->[0];
+ my $axis = $operation->{attributes}->[1];
+ $self->rotate($angle, $axis, 'true');
+
+ } elsif ($type eq "INCREASE") {
+ my $object_id = $operation->{object_identifier};
+ my $obj_idx = $self->get_object_index($object_id);
+ $self->select_object($obj_idx);
+
+ my $copies = $operation->{attributes}->[0];
+ $self->increase($copies, 'true');
+
+ } elsif ($type eq "DECREASE") {
+ my $object_id = $operation->{object_identifier};
+ my $obj_idx = $self->get_object_index($object_id);
+ $self->select_object($obj_idx);
+
+ my $copies = $operation->{attributes}->[0];
+ $self->decrease($copies, 'true');
+
+ } elsif ($type eq "MIRROR") {
+ my $object_id = $operation->{object_identifier};
+ my $obj_idx = $self->get_object_index($object_id);
+ $self->select_object($obj_idx);
+
+ my $axis = $operation->{attributes}->[0];
+ $self->mirror($axis, 'true');
+
+ } elsif ($type eq "REMOVE") {
+ my $object_id = $operation->{object_identifier};
+ my $obj_idx = $self->get_object_index($object_id);
+ $self->select_object($obj_idx);
+
+ $self->remove(undef, 'true');
+
+ } elsif ($type eq "CUT" || $type eq "SPLIT") {
+ # Delete the org objects.
+ $self->remove($self->get_object_index($operation->{object_identifier}), 'true');
+ # Add the new objects and revert changes to the plater object identifier.
+ my $current_objects_identifier = $self->{object_identifier};
+ $self->load_model_objects(@{$operation->{attributes}->[1]->objects});
+ $self->{object_identifier} = $current_objects_identifier;
+ # Add their identifiers.
+ my $obj_identifiers_start = $operation->{attributes}->[2];
+ my $obj_count = $#{$operation->{attributes}->[1]->objects} + 1;
+ for (my $i_object = 0; $i_object <= $#{$operation->{attributes}->[1]->objects}; $i_object++){
+ $self->{objects}->[-$obj_count]->identifier($obj_identifiers_start++);
+ $obj_count--;
+ }
+ } elsif ($type eq "CHANGE_SCALE") {
+ my $object_id = $operation->{object_identifier};
+ my $obj_idx = $self->get_object_index($object_id);
+ $self->select_object($obj_idx);
+
+ my $axis = $operation->{attributes}->[0];
+ my $tosize = $operation->{attributes}->[1];
+ my $old_scale = $operation->{attributes}->[2];
+ $self->changescale($axis, $tosize, $old_scale, 'true');
+
+ } elsif ($type eq "RESET") {
+ $self->reset('true');
+ } elsif ($type eq "ADD") {
+ # Revert changes to the plater object identifier. It's modified when adding new objects only not when undo/redo is executed.
+ my $current_objects_identifier = $self->{object_identifier};
+ $self->load_model_objects(@{$operation->{attributes}->[0]->objects});
+ $self->{object_identifier} = $current_objects_identifier;
+
+ my $objects_count = $#{$operation->{attributes}->[0]->objects} + 1;
+ my $start_identifier = $operation->{attributes}->[1];
+ foreach my $object (@{$operation->{attributes}->[0]->objects})
+ {
+ $self->{objects}->[-$objects_count]->identifier($start_identifier++);
+ $objects_count--;
+ }
+ } elsif ($type eq "GROUP"){
+ my @ops = @{$operation->{attributes}};
+ foreach my $op (@ops) {
+ push @{$self->{redo_stack}}, $op;
+ $self->redo;
+ pop @{$self->{undo_stack}};
+ }
+ }
+}
+
sub add {
my $self = shift;
+ # Save the current object identifier to track added objects.
+ my $start_object_id = $self->{object_identifier};
+
my @input_files = wxTheApp->open_model($self);
$self->load_file($_) for @input_files;
+
+ # Check if no objects are added.
+ if ($start_object_id == $self->{object_identifier}) {
+ return;
+ }
+
+ # Save the added objects.
+ my $new_model = $self->{model}->new;
+
+ # Get newly added objects count.
+ my $new_objects_count = $self->{object_identifier} - $start_object_id;
+ for (my $i_object = $start_object_id; $i_object < $new_objects_count + $start_object_id; $i_object++){
+ my $object_index = $self->get_object_index($i_object);
+ $new_model->add_object($self->{model}->get_object($object_index));
+ }
+
+ $self->add_undo_operation("ADD", undef, $new_model, $start_object_id);
}
sub add_tin {
@@ -896,7 +1198,7 @@ sub add_tin {
sub load_file {
my $self = shift;
- my ($input_file, $obj_idx) = @_;
+ my ($input_file, $obj_idx_to_load) = @_;
$Slic3r::GUI::Settings->{recent}{skein_directory} = dirname($input_file);
wxTheApp->save_settings;
@@ -922,23 +1224,48 @@ sub load_file {
}
}
- if (defined $obj_idx) {
- return () if $obj_idx >= $model->objects_count;
- @obj_idx = $self->load_model_objects($model->get_object($obj_idx));
+ for my $obj_idx (0..($model->objects_count-1)) {
+ my $object = $model->objects->[$obj_idx];
+ $object->set_input_file($input_file);
+ for my $vol_idx (0..($object->volumes_count-1)) {
+ my $volume = $object->get_volume($vol_idx);
+ $volume->set_input_file($input_file);
+ $volume->set_input_file_obj_idx($obj_idx);
+ $volume->set_input_file_obj_idx($vol_idx);
+ }
+ }
+
+ my $i = 0;
+
+ if (defined $obj_idx_to_load) {
+ return () if $obj_idx_to_load >= $model->objects_count;
+ @obj_idx = $self->load_model_objects($model->get_object($obj_idx_to_load));
+ $i = $obj_idx_to_load;
} else {
@obj_idx = $self->load_model_objects(@{$model->objects});
}
- my $i = 0;
foreach my $obj_idx (@obj_idx) {
$self->{objects}[$obj_idx]->input_file($input_file);
$self->{objects}[$obj_idx]->input_file_obj_idx($i++);
}
+
$self->statusbar->SetStatusText("Loaded " . basename($input_file));
+
+ if($self->{scaled_down}) {
+ $self->statusbar->SetStatusText('Your object appears to be too large, so it was automatically scaled down to fit your print bed.');
+ }
+ if($self->{outside_bounds}) {
+ $self->statusbar->SetStatusText('Some of your object(s) appear to be outside the print bed. Use the arrange button to correct this.');
+ }
+
}
$process_dialog->Destroy;
-
+
+ # Empty the redo stack
+ $self->{redo_stack} = [];
+
return @obj_idx;
}
@@ -955,27 +1282,40 @@ sub load_model_objects {
my $bed_size = $bed_shape->bounding_box->size;
my $need_arrange = 0;
- my $scaled_down = 0;
my @obj_idx = ();
foreach my $model_object (@model_objects) {
my $o = $self->{model}->add_object($model_object);
$o->repair;
-
+
push @{ $self->{objects} }, Slic3r::GUI::Plater::Object->new(
- name => $model_object->name || basename($model_object->input_file),
- );
+ name => $model_object->name || basename($model_object->input_file), identifier =>
+ $self->{object_identifier}++
+ );
+
push @obj_idx, $#{ $self->{objects} };
if ($model_object->instances_count == 0) {
- # if object has no defined position(s) we need to rearrange everything after loading
- $need_arrange = 1;
-
- # 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);
+ if ($Slic3r::GUI::Settings->{_}{autocenter}) {
+ # if object has no defined position(s) we need to rearrange everything after loading
+ $need_arrange = 1;
+
+ # 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 user turned autocentering off, automatic arranging would disappoint them
+ $need_arrange = 0;
+
+ if ($Slic3r::GUI::Settings->{_}{autoalignz}) {
+ $o->align_to_ground; # aligns object to Z = 0
+ }
+ $o->add_instance();
+ }
} else {
- # if object has defined positions we still need to ensure it's aligned to Z = 0
- $o->align_to_ground;
+ if ($Slic3r::GUI::Settings->{_}{autoalignz}) {
+ # if object has defined positions we still need to ensure it's aligned to Z = 0
+ $o->align_to_ground;
+ }
}
{
@@ -984,27 +1324,26 @@ sub load_model_objects {
my $ratio = max(@$size[X,Y]) / unscale(max(@$bed_size[X,Y]));
if ($ratio > 5) {
$_->set_scaling_factor(1/$ratio) for @{$o->instances};
- $scaled_down = 1;
+ $self->{scaled_down} = 1;
}
}
+
+ {
+ # if after scaling the object does not fit on the bed provide a warning
+ my $bed_bounds = Slic3r::Geometry::BoundingBoxf->new_from_points($self->{config}->bed_shape);
+ my $o_bounds = $o->bounding_box;
+ my $min = Slic3r::Pointf->new($o_bounds->x_min, $o_bounds->y_min);
+ my $max = Slic3r::Pointf->new($o_bounds->x_max, $o_bounds->y_max);
+ if (!$bed_bounds->contains_point($min) || !$bed_bounds->contains_point($max))
+ {
+ $self->{outside_bounds} = 1;
+ }
+ }
$self->{print}->auto_assign_extruders($o);
$self->{print}->add_model_object($o);
}
- # if user turned autocentering off, automatic arranging would disappoint them
- if (!$Slic3r::GUI::Settings->{_}{autocenter}) {
- $need_arrange = 0;
- }
-
- if ($scaled_down) {
- Slic3r::GUI::show_info(
- $self,
- 'Your object appears to be too large, so it was automatically scaled down to fit your print bed.',
- 'Object too large?',
- );
- }
-
$self->make_thumbnail($_) for @obj_idx;
$self->arrange if $need_arrange;
$self->on_model_change;
@@ -1028,7 +1367,7 @@ sub bed_centerf {
sub remove {
my $self = shift;
- my ($obj_idx) = @_;
+ my ($obj_idx, $dont_push) = @_;
$self->stop_background_process;
@@ -1040,7 +1379,12 @@ sub remove {
if (!defined $obj_idx) {
($obj_idx, undef) = $self->selected_object;
}
-
+
+ # Save the object identifier and copy the object for undo/redo operations.
+ my $object_id = $self->{objects}->[$obj_idx]->identifier;
+ my $new_model = Slic3r::Model->new; # store this before calling get_object()
+ $new_model->add_object($self->{model}->get_object($obj_idx));
+
splice @{$self->{objects}}, $obj_idx, 1;
$self->{model}->delete_object($obj_idx);
$self->{print}->delete_object($obj_idx);
@@ -1048,28 +1392,44 @@ sub remove {
$self->select_object(undef);
$self->on_model_change;
+
+ if (!defined $dont_push) {
+ $self->add_undo_operation("REMOVE", $object_id, $new_model);
+ }
}
sub reset {
- my $self = shift;
+ my ($self, $dont_push) = @_;
$self->stop_background_process;
# Prevent toolpaths preview from rendering while we modify the Print object
$self->{toolpaths2D}->enabled(0) if $self->{toolpaths2D};
$self->{preview3D}->enabled(0) if $self->{preview3D};
-
+
+ # Save the current model.
+ my $current_model = $self->{model}->clone;
+
+ if (!defined $dont_push) {
+ # Get the identifiers of the curent model objects.
+ my $objects_identifiers = [];
+ for (my $i = 0; $i <= $#{$self->{objects}}; $i++){
+ push @{$objects_identifiers}, $self->{objects}->[$i]->identifier;
+ }
+ $self->add_undo_operation("RESET", undef, $current_model, $objects_identifiers);
+ }
+
@{$self->{objects}} = ();
$self->{model}->clear_objects;
$self->{print}->clear_objects;
$self->object_list_changed;
-
+
$self->select_object(undef);
$self->on_model_change;
}
sub increase {
- my ($self, $copies) = @_;
+ my ($self, $copies, $dont_push) = @_;
$copies //= 1;
my ($obj_idx, $object) = $self->selected_object;
@@ -1078,12 +1438,20 @@ sub increase {
for my $i (1..$copies) {
$instance = $model_object->add_instance(
offset => Slic3r::Pointf->new(map 10+$_, @{$instance->offset}),
+ z_translation => $instance->z_translation,
scaling_factor => $instance->scaling_factor,
+ scaling_vector => $instance->scaling_vector,
rotation => $instance->rotation,
+ x_rotation => $instance->x_rotation,
+ y_rotation => $instance->y_rotation,
);
$self->{print}->objects->[$obj_idx]->add_copy($instance->offset);
}
-
+
+ if (!defined $dont_push) {
+ $self->add_undo_operation("INCREASE", $object->identifier , $copies);
+ }
+
# only autoarrange if user has autocentering enabled
$self->stop_background_process;
if ($Slic3r::GUI::Settings->{_}{autocenter}) {
@@ -1094,7 +1462,7 @@ sub increase {
}
sub decrease {
- my ($self, $copies) = @_;
+ my ($self, $copies, $dont_push) = @_;
$copies //= 1;
$self->stop_background_process;
@@ -1106,6 +1474,9 @@ sub decrease {
$model_object->delete_last_instance;
$self->{print}->objects->[$obj_idx]->delete_last_copy;
}
+ if (!defined $dont_push) {
+ $self->add_undo_operation("DECREASE", $object->identifier, $copies);
+ }
} else {
$self->remove;
}
@@ -1150,12 +1521,50 @@ sub center_selected_object_on_bed {
$self->bed_centerf->y - $bb->y_min - $size->y/2, #//
);
$_->offset->translate(@$vector) for @{$model_object->instances};
- $self->refresh_canvases;
+ $self->on_model_change;
+}
+
+sub rotate_face {
+ my $self = shift;
+ my ($obj_idx, $object) = $self->selected_object;
+ return if !defined $obj_idx;
+
+ # Get the selected normal
+ if (!$Slic3r::GUI::have_OpenGL) {
+ Slic3r::GUI::show_error($self, "Please install the OpenGL modules to use this feature (see build instructions).");
+ return;
+ }
+ my $dlg = Slic3r::GUI::Plater::ObjectRotateFaceDialog->new($self,
+ object => $self->{objects}[$obj_idx],
+ model_object => $self->{model}->objects->[$obj_idx],
+ );
+ return unless $dlg->ShowModal == wxID_OK;
+ my $normal = $dlg->SelectedNormal;
+ return if !defined $normal;
+ my $axis = $dlg->SelectedAxis;
+ return if !defined $axis;
+
+ # Actual math to rotate
+ my $angleToXZ = atan2($normal->y(),$normal->x());
+ my $angleToZ = acos(-$normal->z());
+ $self->rotate(-rad2deg($angleToXZ),Z);
+ $self->rotate(rad2deg($angleToZ),Y);
+
+ if($axis == Z){
+ $self->add_undo_operation("GROUP", $object->identifier, splice(@{$self->{undo_stack}},-2));
+ } else {
+ if($axis == X){
+ $self->rotate(90,Y);
+ } else {
+ $self->rotate(90,X);
+ }
+ $self->add_undo_operation("GROUP", $object->identifier, splice(@{$self->{undo_stack}},-3));
+ }
}
sub rotate {
my $self = shift;
- my ($angle, $axis) = @_;
+ my ($angle, $axis, $dont_push) = @_;
# angle is in degrees
$axis //= Z;
@@ -1194,17 +1603,21 @@ sub rotate {
$model_object->center_around_origin;
$self->make_thumbnail($obj_idx);
}
-
+
$model_object->update_bounding_box;
# update print and start background processing
$self->{print}->add_model_object($model_object, $obj_idx);
-
+
+ if (!defined $dont_push) {
+ $self->add_undo_operation("ROTATE", $object->identifier, $angle, $axis);
+ }
+
$self->selection_changed; # refresh info (size etc.)
$self->on_model_change;
}
sub mirror {
- my ($self, $axis) = @_;
+ my ($self, $axis, $dont_push) = @_;
my ($obj_idx, $object) = $self->selected_object;
return if !defined $obj_idx;
@@ -1225,13 +1638,17 @@ sub mirror {
# update print and start background processing
$self->stop_background_process;
$self->{print}->add_model_object($model_object, $obj_idx);
-
+
+ if (!defined $dont_push) {
+ $self->add_undo_operation("MIRROR", $object->identifier, $axis);
+ }
+
$self->selection_changed; # refresh info (size etc.)
$self->on_model_change;
}
sub changescale {
- my ($self, $axis, $tosize) = @_;
+ my ($self, $axis, $tosize, $saved_scale, $dont_push) = @_;
my ($obj_idx, $object) = $self->selected_object;
return if !defined $obj_idx;
@@ -1244,27 +1661,36 @@ sub changescale {
my $object_size = $model_object->bounding_box->size;
my $bed_size = Slic3r::Polygon->new_scale(@{$self->{config}->bed_shape})->bounding_box->size;
-
+
+ my $old_scale;
+ my $scale;
+
if (defined $axis) {
my $axis_name = $axis == X ? 'X' : $axis == Y ? 'Y' : 'Z';
- my $scale;
- if ($tosize) {
- my $cursize = $object_size->[$axis];
- # Wx::GetNumberFromUser() does not support decimal numbers
- my $newsize = Wx::GetTextFromUser(
- sprintf("Enter the new size for the selected object (print bed: %smm):", $bed_size->[$axis]),
- "Scale along $axis_name",
- $cursize, $self);
- return if !$newsize || $newsize !~ /^\d*(?:\.\d*)?$/ || $newsize < 0;
- $scale = $newsize / $cursize * 100;
- } else {
- # Wx::GetNumberFromUser() does not support decimal numbers
- $scale = Wx::GetTextFromUser("Enter the scale % for the selected object:",
- "Scale along $axis_name", 100, $self);
- $scale =~ s/%$//;
- return if !$scale || $scale !~ /^\d*(?:\.\d*)?$/ || $scale < 0;
+ if (!defined $saved_scale) {
+ if ($tosize) {
+ my $cursize = $object_size->[$axis];
+ # Wx::GetNumberFromUser() does not support decimal numbers
+ my $newsize = Wx::GetTextFromUser(
+ sprintf("Enter the new size for the selected object (print bed: %smm):", $bed_size->[$axis]),
+ "Scale along $axis_name",
+ $cursize, $self);
+ return if !$newsize || $newsize !~ /^\d*(?:\.\d*)?$/ || $newsize < 0;
+ $scale = $newsize / $cursize * 100;
+ $old_scale = $cursize / $newsize * 100;
+ } else {
+ # Wx::GetNumberFromUser() does not support decimal numbers
+ $scale = Wx::GetTextFromUser("Enter the scale % for the selected object:",
+ "Scale along $axis_name", 100, $self);
+ $scale =~ s/%$//;
+ return if !$scale || $scale !~ /^\d*(?:\.\d*)?$/ || $scale < 0;
+ $old_scale = 100 * 100 / $scale;
+ }
}
-
+ else {
+ $scale = $saved_scale;
+ }
+
# apply Z rotation before scaling
$model_object->transform_by_instance($model_instance, 1);
@@ -1274,23 +1700,28 @@ sub changescale {
# object was already aligned to Z = 0, so no need to realign it
$self->make_thumbnail($obj_idx);
} else {
- my $scale;
- if ($tosize) {
- my $cursize = max(@$object_size);
- # Wx::GetNumberFromUser() does not support decimal numbers
- my $newsize = Wx::GetTextFromUser("Enter the new max size for the selected object:",
- "Scale", $cursize, $self);
- return if !$newsize || $newsize !~ /^\d*(?:\.\d*)?$/ || $newsize < 0;
- $scale = $model_instance->scaling_factor * $newsize / $cursize * 100;
+ if (!defined $saved_scale) {
+ if ($tosize) {
+ my $cursize = max(@$object_size);
+ # Wx::GetNumberFromUser() does not support decimal numbers
+ my $newsize = Wx::GetTextFromUser("Enter the new max size for the selected object:",
+ "Scale", $cursize, $self);
+ return if !$newsize || $newsize !~ /^\d*(?:\.\d*)?$/ || $newsize < 0;
+ $scale = $model_instance->scaling_factor * $newsize / $cursize * 100;
+ $old_scale = $model_instance->scaling_factor * 100;
+ } else {
+ # max scale factor should be above 2540 to allow importing files exported in inches
+ # Wx::GetNumberFromUser() does not support decimal numbers
+ $scale = Wx::GetTextFromUser("Enter the scale % for the selected object:", 'Scale',
+ $model_instance->scaling_factor * 100, $self);
+ return if !$scale || $scale !~ /^\d*(?:\.\d*)?$/ || $scale < 0;
+ $old_scale = $model_instance->scaling_factor * 100;
+ }
+ return if !$scale || $scale < 0;
} else {
- # max scale factor should be above 2540 to allow importing files exported in inches
- # Wx::GetNumberFromUser() does not support decimal numbers
- $scale = Wx::GetTextFromUser("Enter the scale % for the selected object:", 'Scale',
- $model_instance->scaling_factor*100, $self);
- return if !$scale || $scale !~ /^\d*(?:\.\d*)?$/ || $scale < 0;
+ $scale = $saved_scale;
}
- return if !$scale || $scale < 0;
-
+
$scale /= 100; # turn percent into factor
my $variation = $scale / $model_instance->scaling_factor;
@@ -1300,7 +1731,15 @@ sub changescale {
}
$_->set_scaling_factor($scale) for @{ $model_object->instances };
$object->transform_thumbnail($self->{model}, $obj_idx);
+
+ $scale *= 100;
}
+
+ # Add the new undo operation.
+ if (!defined $dont_push) {
+ $self->add_undo_operation("CHANGE_SCALE", $object->identifier, $axis, $tosize, $scale, $old_scale);
+ }
+
$model_object->update_bounding_box;
# update print and start background processing
@@ -1320,12 +1759,13 @@ sub arrange {
my $success = $self->{model}->arrange_objects($self->config->min_object_distance, $bb);
# ignore arrange failures on purpose: user has visual feedback and we don't need to warn him
# when parts don't fit in print bed
-
+
+ $self->statusbar->SetStatusText('Objects were arranged.');
$self->on_model_change(1);
}
sub split_object {
- my $self = shift;
+ my ($self, $dont_push) = @_;
my ($obj_idx, $current_object) = $self->selected_object;
@@ -1340,7 +1780,14 @@ sub split_object {
}
$self->pause_background_process;
-
+
+ # Save the curent model object for undo/redo operataions.
+ my $org_object_model = Slic3r::Model->new;
+ $org_object_model->add_object($current_model_object);
+
+ # Save the org object identifier.
+ my $object_id = $self->{objects}->[$obj_idx]->identifier;
+
my @model_objects = @{$current_model_object->split_object};
if (@model_objects == 1) {
$self->resume_background_process;
@@ -1360,12 +1807,24 @@ sub split_object {
# remove the original object before spawning the object_loaded event, otherwise
# we'll pass the wrong $obj_idx to it (which won't be recognized after the
# thumbnail thread returns)
- $self->remove($obj_idx);
+ $self->remove($obj_idx, 'true'); # Don't push to the undo stack it's considered a split opeation not a remove one.
$current_object = $obj_idx = undef;
-
+
+ # Save the object identifiers used in undo/redo operations.
+ my $new_objects_id_start = $self->{object_identifier};
+ print "The new object identifier start for split is " .$new_objects_id_start . "\n";
+
# load all model objects at once, otherwise the plate would be rearranged after each one
# causing original positions not to be kept
$self->load_model_objects(@model_objects);
+
+ # Create two models to save the current object and the resulted objects.
+ my $new_objects_model = Slic3r::Model->new;
+ foreach my $new_object (@model_objects) {
+ $new_objects_model->add_object($new_object);
+ }
+
+ $self->add_undo_operation("SPLIT", $object_id, $org_object_model, $new_objects_model, $new_objects_id_start);
}
sub toggle_print_stats {
@@ -1410,8 +1869,8 @@ sub config_changed {
$self->{btn_print}->Hide;
}
$self->Layout;
- } elsif ($opt_key eq 'octoprint_host') {
- if ($config->get('octoprint_host')) {
+ } elsif ($opt_key eq 'print_host') {
+ if ($config->get('print_host')) {
$self->{btn_send_gcode}->Show;
} else {
$self->{btn_send_gcode}->Hide;
@@ -1460,6 +1919,7 @@ sub async_apply_config {
# reset preview canvases (invalidated contents will be hidden)
$self->{toolpaths2D}->reload_print if $self->{toolpaths2D};
$self->{preview3D}->reload_print if $self->{preview3D};
+ $self->{AdaptiveLayersDialog}->reload_preview if $self->{AdaptiveLayersDialog};
if (!$Slic3r::GUI::Settings->{_}{background_processing}) {
$self->hide_preview if $invalidated;
@@ -1536,6 +1996,7 @@ sub stop_background_process {
$self->{toolpaths2D}->reload_print if $self->{toolpaths2D};
$self->{preview3D}->reload_print if $self->{preview3D};
+ $self->{AdaptiveLayersDialog}->reload_preview if $self->{AdaptiveLayersDialog};
if ($self->{process_thread}) {
Slic3r::debugf "Killing background process.\n";
@@ -1679,6 +2140,7 @@ sub on_process_completed {
return if $error;
$self->{toolpaths2D}->reload_print if $self->{toolpaths2D};
$self->{preview3D}->reload_print if $self->{preview3D};
+ $self->{AdaptiveLayersDialog}->reload_preview if $self->{AdaptiveLayersDialog};
# if we have an export filename, start a new thread for exporting G-code
if ($self->{export_gcode_output_file}) {
@@ -1731,7 +2193,7 @@ sub on_export_completed {
$message = "File added to print queue";
$do_print = 1;
} elsif ($self->{send_gcode_file}) {
- $message = "Sending G-code file to the OctoPrint server...";
+ $message = "Sending G-code file to the " . $self->{config}->host_type . " server...";
$send_gcode = 1;
} else {
$message = "G-code file exported to " . $self->{export_gcode_output_file};
@@ -1804,23 +2266,33 @@ sub prepare_send {
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,
- );
+ my $res;
+ if ($self->{config}->print_host) {
+ if($self->{config}->host_type eq 'octoprint'){
+ $res = $ua->get(
+ "http://" . $self->{config}->print_host . "/api/files/local",
+ 'X-Api-Key' => $self->{config}->octoprint_apikey,
+ );
+ }else {
+ $res = $ua->get(
+ "http://" . $self->{config}->print_host . "/rr_files",
+ );
+ }
+ }
$progress->Destroy;
if ($res->is_success) {
- if ($res->decoded_content =~ /"name":\s*"\Q$filename\E"/) {
+ my $searchterm = ($self->{config}->host_type eq 'octoprint') ? '/"name":\s*"\Q$filename\E"/' : '"'.$filename.'"';
+ if ($res->decoded_content =~ $searchterm) {
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);
+ $self->{config}->host_type, wxICON_WARNING | wxYES | wxNO);
if ($dialog->ShowModal() == wxID_NO) {
return;
}
}
} else {
- my $message = "Error while connecting to the OctoPrint server: " . $res->status_line;
+ my $message = "Error while connecting to the " . $self->{config}->host_type . " server: " . $res->status_line;
Slic3r::GUI::show_error($self, $message);
return;
}
@@ -1839,24 +2311,52 @@ sub send_gcode {
$ua->timeout(180);
my $path = Slic3r::encode_path($self->{send_gcode_file});
- my $res = $ua->post(
- "http://" . $self->{config}->octoprint_host . "/api/files/local",
- Content_Type => 'form-data',
- 'X-Api-Key' => $self->{config}->octoprint_apikey,
- Content => [
- # OctoPrint doesn't like Windows paths so we use basename()
- # Also, since we need to read from filesystem we process it through encode_path()
- file => [ $path, basename($path) ],
- print => $self->{send_gcode_file_print} ? 1 : 0,
- ],
- );
-
+ my $filename = basename($self->{print}->output_filepath($main::opt{output} // ''));
+ my $res;
+ if($self->{config}->print_host){
+ if($self->{config}->host_type eq 'octoprint'){
+ $res = $ua->post(
+ "http://" . $self->{config}->print_host . "/api/files/local",
+ Content_Type => 'form-data',
+ 'X-Api-Key' => $self->{config}->octoprint_apikey,
+ Content => [
+ # OctoPrint doesn't like Windows paths so we use basename()
+ # Also, since we need to read from filesystem we process it through encode_path()
+ file => [ $path, basename($path) ],
+ print => $self->{send_gcode_file_print} ? 1 : 0,
+ ],
+ );
+ }else{
+ # slurp the file we would send into a string - should be someplace to reference this but could not find it?
+ local $/=undef;
+ open (my $gch,$path);
+ my $gcode=<$gch>;
+ close($gch);
+
+ # get the time string
+ my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
+ my $t = sprintf("%4d-%02d-%02dT%02d:%02d:%02d",$year+1900,$mon+1,$mday,$hour,$min,$sec);
+
+ my $req = HTTP::Request->new(POST => "http://" . $self->{config}->print_host . "/rr_upload?name=0:/gcodes/" . basename($path) . "&time=$t",);
+ $req->content( $gcode );
+ $res = $ua->request($req);
+
+ if ($res->is_success) {
+ if ($self->{send_gcode_file_print}) {
+ $res = $ua->get(
+ "http://" . $self->{config}->print_host . "/rr_gcode?gcode=M32%20" . basename($path),
+ );
+ }
+ }
+ }
+ }
+
$self->statusbar->StopBusy;
if ($res->is_success) {
- $self->statusbar->SetStatusText("G-code file successfully uploaded to the OctoPrint server");
+ $self->statusbar->SetStatusText("G-code file successfully uploaded to the " . $self->{config}->host_type . " server");
} else {
- my $message = "Error while uploading to the OctoPrint server: " . $res->status_line;
+ my $message = "Error while uploading to the " . $self->{config}->host_type . " server: " . $res->status_line;
Slic3r::GUI::show_error($self, $message);
$self->statusbar->SetStatusText($message);
}
@@ -1878,26 +2378,141 @@ sub reload_from_disk {
my ($obj_idx, $object) = $self->selected_object;
return if !defined $obj_idx;
- return if !$object->input_file
- || !-e $object->input_file;
+ if (!$object->input_file) {
+ Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be reloaded because it isn't referenced to its input file any more. This is the case after performing operations like cut or split.");
+ return;
+ }
+ if (!-e $object->input_file) {
+ Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be reloaded because the file doesn't exist anymore on the disk.");
+ return;
+ }
# Only reload the selected object and not all objects from the input file.
my @new_obj_idx = $self->load_file($object->input_file, $object->input_file_obj_idx);
- return if !@new_obj_idx;
-
- my $model_object = $self->{model}->objects->[$obj_idx];
- foreach my $new_obj_idx (@new_obj_idx) {
- my $o = $self->{model}->objects->[$new_obj_idx];
- $o->clear_instances;
- $o->add_instance($_) for @{$model_object->instances};
-
- if ($o->volumes_count == $model_object->volumes_count) {
- for my $i (0..($o->volumes_count-1)) {
- $o->get_volume($i)->config->apply($model_object->get_volume($i)->config);
- }
+ if (!@new_obj_idx) {
+ Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be reloaded because the new file doesn't contain the object.");
+ return;
+ }
+
+ my $org_obj = $self->{model}->objects->[$obj_idx];
+
+ # check if the object is dependant of more than one file
+ my $org_obj_has_modifiers=0;
+ for my $i (0..($org_obj->volumes_count-1)) {
+ if ($org_obj->input_file ne $org_obj->get_volume($i)->input_file) {
+ $org_obj_has_modifiers=1;
+ last;
+ }
+ }
+
+ my $reload_behavior = $Slic3r::GUI::Settings->{_}{reload_behavior};
+
+ # ask the user how to proceed, if option is selected in preferences
+ if ($org_obj_has_modifiers && !$Slic3r::GUI::Settings->{_}{reload_hide_dialog}) {
+ my $dlg = Slic3r::GUI::ReloadDialog->new(undef,$reload_behavior);
+ my $res = $dlg->ShowModal;
+ if ($res==wxID_CANCEL) {
+ $self->remove($_) for @new_obj_idx;
+ $dlg->Destroy;
+ return;
+ }
+ $reload_behavior = $dlg->GetSelection;
+ my $save = 0;
+ if ($reload_behavior != $Slic3r::GUI::Settings->{_}{reload_behavior}) {
+ $Slic3r::GUI::Settings->{_}{reload_behavior} = $reload_behavior;
+ $save = 1;
+ }
+ if ($dlg->GetHideOnNext) {
+ $Slic3r::GUI::Settings->{_}{reload_hide_dialog} = 1;
+ $save = 1;
+ }
+ Slic3r::GUI->save_settings if $save;
+ $dlg->Destroy;
+ }
+
+ my $volume_unmatched=0;
+
+ foreach my $new_obj_idx (@new_obj_idx) {
+ my $new_obj = $self->{model}->objects->[$new_obj_idx];
+ $new_obj->clear_instances;
+ $new_obj->add_instance($_) for @{$org_obj->instances};
+ $new_obj->config->apply($org_obj->config);
+
+ my $new_vol_idx = 0;
+ my $org_vol_idx = 0;
+ my $new_vol_count=$new_obj->volumes_count;
+ my $org_vol_count=$org_obj->volumes_count;
+
+ while ($new_vol_idx<=$new_vol_count-1) {
+ if (($org_vol_idx<=$org_vol_count-1) && ($org_obj->get_volume($org_vol_idx)->input_file eq $new_obj->input_file)) {
+ # apply config from the matching volumes
+ $new_obj->get_volume($new_vol_idx++)->config->apply($org_obj->get_volume($org_vol_idx++)->config);
+ } else {
+ # reload has more volumes than original (first file), apply config from the first volume
+ $new_obj->get_volume($new_vol_idx++)->config->apply($org_obj->get_volume(0)->config);
+ $volume_unmatched=1;
+ }
+ }
+ $org_vol_idx=$org_vol_count if $reload_behavior==2; # Reload behavior: discard
+ while (($org_vol_idx<=$org_vol_count-1) && ($org_obj->get_volume($org_vol_idx)->input_file eq $new_obj->input_file)) {
+ # original has more volumes (first file), skip those
+ $org_vol_idx++;
+ $volume_unmatched=1;
+ }
+ while ($org_vol_idx<=$org_vol_count-1) {
+ if ($reload_behavior==1) { # Reload behavior: copy
+ my $new_volume = $new_obj->add_volume($org_obj->get_volume($org_vol_idx));
+ $new_volume->mesh->translate(@{$org_obj->origin_translation->negative});
+ $new_volume->mesh->translate(@{$new_obj->origin_translation});
+ if ($new_volume->name =~ m/link to path\z/) {
+ my $new_name = $new_volume->name;
+ $new_name =~ s/ - no link to path$/ - copied/;
+ $new_volume->set_name($new_name);
+ }elsif(!($new_volume->name =~ m/copied\z/)) {
+ $new_volume->set_name($new_volume->name . " - copied");
+ }
+ }else{ # Reload behavior: Reload all, also fallback solution if ini was manually edited to a wrong value
+ if ($org_obj->get_volume($org_vol_idx)->input_file) {
+ my $model = eval { Slic3r::Model->read_from_file($org_obj->get_volume($org_vol_idx)->input_file) };
+ if ($@) {
+ $org_obj->get_volume($org_vol_idx)->set_input_file("");
+ }elsif ($org_obj->get_volume($org_vol_idx)->input_file_obj_idx > ($model->objects_count-1)) {
+ # Object Index for that part / modifier not found in current version of the file
+ $org_obj->get_volume($org_vol_idx)->set_input_file("");
+ }else{
+ my $prt_mod_obj = $model->objects->[$org_obj->get_volume($org_vol_idx)->input_file_obj_idx];
+ if ($org_obj->get_volume($org_vol_idx)->input_file_vol_idx > ($prt_mod_obj->volumes_count-1)) {
+ # Volume Index for that part / modifier not found in current version of the file
+ $org_obj->get_volume($org_vol_idx)->set_input_file("");
+ }else{
+ # all checks passed, load new mesh and copy metadata
+ my $new_volume = $new_obj->add_volume($prt_mod_obj->get_volume($org_obj->get_volume($org_vol_idx)->input_file_vol_idx));
+ $new_volume->set_input_file($org_obj->get_volume($org_vol_idx)->input_file);
+ $new_volume->set_input_file_obj_idx($org_obj->get_volume($org_vol_idx)->input_file_obj_idx);
+ $new_volume->set_input_file_vol_idx($org_obj->get_volume($org_vol_idx)->input_file_vol_idx);
+ $new_volume->config->apply($org_obj->get_volume($org_vol_idx)->config);
+ $new_volume->set_modifier($org_obj->get_volume($org_vol_idx)->modifier);
+ $new_volume->mesh->translate(@{$new_obj->origin_translation});
+ }
+ }
+ }
+ if (!$org_obj->get_volume($org_vol_idx)->input_file) {
+ my $new_volume = $new_obj->add_volume($org_obj->get_volume($org_vol_idx)); # error -> copy old mesh
+ $new_volume->mesh->translate(@{$org_obj->origin_translation->negative});
+ $new_volume->mesh->translate(@{$new_obj->origin_translation});
+ if ($new_volume->name =~ m/copied\z/) {
+ my $new_name = $new_volume->name;
+ $new_name =~ s/ - copied$/ - no link to path/;
+ $new_volume->set_name($new_name);
+ }elsif(!($new_volume->name =~ m/link to path\z/)) {
+ $new_volume->set_name($new_volume->name . " - no link to path");
+ }
+ $volume_unmatched=1;
+ }
+ }
+ $org_vol_idx++;
}
}
-
$self->remove($obj_idx);
# TODO: refresh object list which contains wrong count and scale
@@ -1907,6 +2522,18 @@ sub reload_from_disk {
# event, so the on_thumbnail_made callback is called with the wrong $obj_idx.
# When porting to C++ we'll probably have cleaner ways to do this.
$self->make_thumbnail($_-1) for @new_obj_idx;
+
+ # update print
+ $self->stop_background_process;
+ $self->{print}->reload_object($_-1) for @new_obj_idx;
+ $self->on_model_change;
+
+ # Empty the redo stack
+ $self->{redo_stack} = [];
+
+ if ($volume_unmatched) {
+ Slic3r::GUI::warning_catcher($self)->("At least 1 volume couldn't be matched between the original object and the reloaded one.");
+ }
}
sub export_object_stl {
@@ -1939,6 +2566,23 @@ sub export_object_amf {
$self->statusbar->SetStatusText("AMF file exported to $output_file");
}
+# Export function for a single 3MF output
+sub export_object_tmf {
+ my $self = shift;
+
+ my ($obj_idx, $object) = $self->selected_object;
+ return if !defined $obj_idx;
+
+ my $local_model = Slic3r::Model->new;
+ my $model_object = $self->{model}->objects->[$obj_idx];
+ # copy model_object -> local_model
+ $local_model->add_object($model_object);
+
+ my $output_file = $self->_get_export_file('TMF') or return;
+ $local_model->write_tmf($output_file);
+ $self->statusbar->SetStatusText("3MF file exported to $output_file");
+}
+
sub export_amf {
my $self = shift;
@@ -1949,11 +2593,21 @@ sub export_amf {
$self->statusbar->SetStatusText("AMF file exported to $output_file");
}
+sub export_tmf {
+ my $self = shift;
+
+ return if !@{$self->{objects}};
+
+ my $output_file = $self->_get_export_file('TMF') or return;
+ $self->{model}->write_tmf($output_file);
+ $self->statusbar->SetStatusText("3MF file exported to $output_file");
+}
+
sub _get_export_file {
my $self = shift;
my ($format) = @_;
- my $suffix = $format eq 'STL' ? '.stl' : '.amf';
+ my $suffix = $format eq 'STL' ? '.stl' : ( $format eq 'AMF' ? '.amf' : '.3mf');
my $output_file = $main::opt{output};
{
@@ -1968,6 +2622,10 @@ sub _get_export_file {
basename($output_file), &Slic3r::GUI::AMF_MODEL_WILDCARD, wxFD_SAVE | wxFD_OVERWRITE_PROMPT)
if $format eq 'AMF';
+ $dlg = Wx::FileDialog->new($self, "Save $format file as:", dirname($output_file),
+ basename($output_file), &Slic3r::GUI::TMF_MODEL_WILDCARD, wxFD_SAVE | wxFD_OVERWRITE_PROMPT)
+ if $format eq 'TMF';
+
if ($dlg->ShowModal != wxID_OK) {
$dlg->Destroy;
return undef;
@@ -1983,6 +2641,8 @@ sub make_thumbnail {
my ($obj_idx) = @_;
my $plater_object = $self->{objects}[$obj_idx];
+ return if($plater_object->remaking_thumbnail);
+ $plater_object->remaking_thumbnail(1);
$plater_object->thumbnail(Slic3r::ExPolygon::Collection->new);
my $cb = sub {
$plater_object->make_thumbnail($self->{model}, $obj_idx);
@@ -2006,6 +2666,7 @@ sub on_thumbnail_made {
my $self = shift;
my ($obj_idx) = @_;
+ $self->{objects}[$obj_idx]->remaking_thumbnail(0);
$self->{objects}[$obj_idx]->transform_thumbnail($self->{model}, $obj_idx);
$self->refresh_canvases;
}
@@ -2162,8 +2823,23 @@ sub object_cut_dialog {
if (my @new_objects = $dlg->NewModelObjects) {
my $process_dialog = Wx::ProgressDialog->new('Loading…', "Loading new objects…", 100, $self, 0);
$process_dialog->Pulse;
-
- $self->remove($obj_idx);
+
+ # Create two models to save the current object and the resulted objects.
+ my $new_objects_model = Slic3r::Model->new;
+ foreach my $new_object (@new_objects) {
+ $new_objects_model->add_object($new_object);
+ }
+
+ my $org_object_model = Slic3r::Model->new;
+ $org_object_model->add_object($self->{model}->get_object($obj_idx));
+
+ # Save the object identifiers used in undo/redo operations.
+ my $object_id = $self->{objects}->[$obj_idx]->identifier;
+ my $new_objects_id_start = $self->{object_identifier};
+
+ $self->add_undo_operation("CUT", $object_id, $org_object_model, $new_objects_model, $new_objects_id_start);
+
+ $self->remove($obj_idx, 'true');
$self->load_model_objects(grep defined($_), @new_objects);
$self->arrange if @new_objects <= 2; # don't arrange for grid cuts
@@ -2171,9 +2847,16 @@ sub object_cut_dialog {
}
}
-sub object_settings_dialog {
+sub object_layers_dialog {
my $self = shift;
my ($obj_idx) = @_;
+
+ $self->object_settings_dialog($obj_idx, adaptive_layers => 1);
+}
+
+sub object_settings_dialog {
+ my $self = shift;
+ my ($obj_idx, %params) = @_;
if (!defined $obj_idx) {
($obj_idx, undef) = $self->selected_object;
@@ -2188,9 +2871,15 @@ sub object_settings_dialog {
my $dlg = Slic3r::GUI::Plater::ObjectSettingsDialog->new($self,
object => $self->{objects}[$obj_idx],
model_object => $model_object,
+ obj_idx => $obj_idx,
);
+ # store pointer to the adaptive layer tab to push preview updates
+ $self->{AdaptiveLayersDialog} = $dlg->{adaptive_layers};
+ # and jump directly to the tab if called by "promo-button"
+ $dlg->{tabpanel}->SetSelection(1) if $params{adaptive_layers};
$self->pause_background_process;
$dlg->ShowModal;
+ $self->{AdaptiveLayersDialog} = undef;
# update thumbnail since parts may have changed
if ($dlg->PartsChanged) {
@@ -2237,7 +2926,10 @@ sub selection_changed {
my ($obj_idx, $object) = $self->selected_object;
my $have_sel = defined $obj_idx;
-
+
+ # Remove selection in 2d Plater.
+ $self->{canvas}->{selected_instance} = undef;
+
if (my $menu = $self->GetFrame->{plater_select_menu}) {
$_->Check(0) for $menu->GetMenuItems;
if ($have_sel) {
@@ -2247,11 +2939,11 @@ sub selection_changed {
my $method = $have_sel ? 'Enable' : 'Disable';
$self->{"btn_$_"}->$method
- for grep $self->{"btn_$_"}, qw(remove increase decrease rotate45cw rotate45ccw changescale split cut settings);
+ for grep $self->{"btn_$_"}, qw(remove increase decrease rotate45cw rotate45ccw rotateFace changescale split cut layers settings);
if ($self->{htoolbar}) {
$self->{htoolbar}->EnableTool($_, $have_sel)
- for (TB_REMOVE, TB_MORE, TB_FEWER, TB_45CW, TB_45CCW, TB_SCALE, TB_SPLIT, TB_CUT, TB_SETTINGS);
+ for (TB_REMOVE, TB_MORE, TB_FEWER, TB_45CW, TB_45CCW, TB_ROTFACE, TB_SCALE, TB_SPLIT, TB_CUT, TB_LAYERS, TB_SETTINGS);
}
if ($self->{object_info_size}) { # have we already loaded the info pane?
@@ -2306,10 +2998,13 @@ sub selection_changed {
sub select_object {
my ($self, $obj_idx) = @_;
-
+
$_->selected(0) for @{ $self->{objects} };
+ $_->selected_instance(-1) for @{ $self->{objects} };
+
if (defined $obj_idx) {
$self->{objects}->[$obj_idx]->selected(1);
+ $self->{objects}->[$obj_idx]->selected_instance(0);
}
$self->selection_changed(1);
}
@@ -2400,6 +3095,9 @@ sub object_menu {
wxTheApp->append_menu_item($menu, "Rotate 45° counter-clockwise", 'Rotate the selected object by 45° counter-clockwise', sub {
$self->rotate(+45);
}, undef, 'arrow_rotate_anticlockwise.png');
+ wxTheApp->append_menu_item($menu, "Rotate Face to Plane", 'Rotates the selected object to have the selected face parallel with a plane', sub {
+ $self->rotate_face;
+ }, undef, 'rotate_face.png');
{
my $rotateMenu = Wx::Menu->new;
@@ -2469,6 +3167,9 @@ sub object_menu {
wxTheApp->append_menu_item($menu, "Cut…", 'Open the 3D cutting tool', sub {
$self->object_cut_dialog;
}, undef, 'package.png');
+ wxTheApp->append_menu_item($menu, "Layer heights…", 'Open the dynamic layer height control', sub {
+ $self->object_layers_dialog;
+ }, undef, 'variable_layer_height.png');
$menu->AppendSeparator();
wxTheApp->append_menu_item($menu, "Settings…", 'Open the object editor dialog', sub {
$self->object_settings_dialog;
@@ -2483,6 +3184,9 @@ sub object_menu {
wxTheApp->append_menu_item($menu, "Export object and modifiers as AMF…", 'Export this single object and all associated modifiers as AMF file', sub {
$self->export_object_amf;
}, undef, 'brick_go.png');
+ wxTheApp->append_menu_item($menu, "Export object and modifiers as 3MF…", 'Export this single object and all associated modifiers as 3MF file', sub {
+ $self->export_object_tmf;
+ }, undef, 'brick_go.png');
return $menu;
}
@@ -2550,12 +3254,15 @@ use List::Util qw(first);
use Slic3r::Geometry qw(X Y Z MIN MAX deg2rad);
has 'name' => (is => 'rw', required => 1);
+has 'identifier' => (is => 'rw', required => 1);
has 'input_file' => (is => 'rw');
has 'input_file_obj_idx' => (is => 'rw');
has 'thumbnail' => (is => 'rw'); # ExPolygon::Collection in scaled model units with no transforms
has 'transformed_thumbnail' => (is => 'rw');
+has 'remaking_thumbnail' => (is => 'rw', default => sub { 0 });
has 'instance_thumbnails' => (is => 'ro', default => sub { [] }); # array of ExPolygon::Collection objects, each one representing the actual placed thumbnail of each instance in pixel units
has 'selected' => (is => 'rw', default => sub { 0 });
+has 'selected_instance' => (is => 'rw', default => sub { -1 });
sub make_thumbnail {
my ($self, $model, $obj_idx) = @_;
@@ -2564,6 +3271,12 @@ sub make_thumbnail {
$self->thumbnail->clear;
my $mesh = $model->objects->[$obj_idx]->raw_mesh;
+ # Apply x, y rotations and scaling vector in case of reading a 3MF model object.
+ my $model_instance = $model->objects->[$obj_idx]->instances->[0];
+ $mesh->rotate_x($model_instance->x_rotation);
+ $mesh->rotate_y($model_instance->y_rotation);
+ $mesh->scale_xyz($model_instance->scaling_vector);
+
if ($mesh->facets_count <= 5000) {
# remove polygons with area <= 1mm
my $area_threshold = Slic3r::Geometry::scale 1;
@@ -2605,7 +3318,7 @@ use base 'Wx::Dialog';
sub new {
my $class = shift;
my ($parent, $filename) = @_;
- my $self = $class->SUPER::new($parent, -1, "Send to OctoPrint", wxDefaultPosition,
+ my $self = $class->SUPER::new($parent, -1, "Send to Server", wxDefaultPosition,
[400, -1]);
$self->{filename} = $filename;
@@ -2614,7 +3327,7 @@ sub new {
my $optgroup;
$optgroup = Slic3r::GUI::OptionsGroup->new(
parent => $self,
- title => 'Send to OctoPrint',
+ title => 'Send to Server',
on_change => sub {
my ($opt_id) = @_;
diff --git a/lib/Slic3r/GUI/Plater/2D.pm b/lib/Slic3r/GUI/Plater/2D.pm
index 8fb8908e1..5b2573802 100644
--- a/lib/Slic3r/GUI/Plater/2D.pm
+++ b/lib/Slic3r/GUI/Plater/2D.pm
@@ -10,9 +10,12 @@ use List::Util qw(min max first);
use Slic3r::Geometry qw(X Y scale unscale convex_hull);
use Slic3r::Geometry::Clipper qw(offset JT_ROUND intersection_pl);
use Wx qw(:misc :pen :brush :sizer :font :cursor wxTAB_TRAVERSAL);
-use Wx::Event qw(EVT_MOUSE_EVENTS EVT_PAINT EVT_ERASE_BACKGROUND EVT_SIZE);
+use Wx::Event qw(EVT_MOUSE_EVENTS EVT_KEY_DOWN EVT_PAINT EVT_ERASE_BACKGROUND EVT_SIZE);
use base 'Wx::Panel';
+# Color Scheme
+use Slic3r::GUI::ColorScheme;
+
use constant CANVAS_TEXT => join('-', +(localtime)[3,4]) eq '13-8'
? 'What do you want to print today? ™' # Sept. 13, 2006. The first part ever printed by a RepRap to make another RepRap.
: 'Drag your objects here';
@@ -21,9 +24,16 @@ sub new {
my $class = shift;
my ($parent, $size, $objects, $model, $config) = @_;
+ if ( ( defined $Slic3r::GUI::Settings->{_}{colorscheme} ) && ( Slic3r::GUI::ColorScheme->can($Slic3r::GUI::Settings->{_}{colorscheme}) ) ) {
+ my $myGetSchemeName = \&{"Slic3r::GUI::ColorScheme::$Slic3r::GUI::Settings->{_}{colorscheme}"};
+ $myGetSchemeName->();
+ } else {
+ Slic3r::GUI::ColorScheme->getDefault();
+ }
+
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, $size, wxTAB_TRAVERSAL);
# This has only effect on MacOS. On Windows and Linux/GTK, the background is painted by $self->repaint().
- $self->SetBackgroundColour(Wx::wxWHITE);
+ $self->SetBackgroundColour(Wx::Colour->new(@BACKGROUND255));
$self->{objects} = $objects;
$self->{model} = $model;
@@ -33,17 +43,22 @@ sub new {
$self->{on_right_click} = sub {};
$self->{on_instances_moved} = sub {};
- $self->{objects_brush} = Wx::Brush->new(Wx::Colour->new(210,210,210), wxSOLID);
- $self->{selected_brush} = Wx::Brush->new(Wx::Colour->new(255,128,128), wxSOLID);
- $self->{dragged_brush} = Wx::Brush->new(Wx::Colour->new(128,128,255), wxSOLID);
+ $self->{objects_brush} = Wx::Brush->new(Wx::Colour->new(@BED_OBJECTS), wxSOLID);
+ $self->{instance_brush} = Wx::Brush->new(Wx::Colour->new(@BED_INSTANCE), wxSOLID);
+ $self->{selected_brush} = Wx::Brush->new(Wx::Colour->new(@BED_SELECTED), wxSOLID);
+ $self->{dragged_brush} = Wx::Brush->new(Wx::Colour->new(@BED_DRAGGED), wxSOLID);
+ $self->{bed_brush} = Wx::Brush->new(Wx::Colour->new(@BED_COLOR), wxSOLID);
$self->{transparent_brush} = Wx::Brush->new(Wx::Colour->new(0,0,0), wxTRANSPARENT);
- $self->{grid_pen} = Wx::Pen->new(Wx::Colour->new(230,230,230), 1, wxSOLID);
- $self->{print_center_pen} = Wx::Pen->new(Wx::Colour->new(200,200,200), 1, wxSOLID);
- $self->{clearance_pen} = Wx::Pen->new(Wx::Colour->new(0,0,200), 1, wxSOLID);
- $self->{skirt_pen} = Wx::Pen->new(Wx::Colour->new(150,150,150), 1, wxSOLID);
+ $self->{grid_pen} = Wx::Pen->new(Wx::Colour->new(@BED_GRID), 1, wxSOLID);
+ $self->{print_center_pen} = Wx::Pen->new(Wx::Colour->new(@BED_CENTER), 1, wxSOLID);
+ $self->{clearance_pen} = Wx::Pen->new(Wx::Colour->new(@BED_CLEARANCE), 1, wxSOLID);
+ $self->{skirt_pen} = Wx::Pen->new(Wx::Colour->new(@BED_SKIRT), 1, wxSOLID);
+ $self->{dark_pen} = Wx::Pen->new(Wx::Colour->new(@BED_DARK), 1, wxSOLID);
$self->{user_drawn_background} = $^O ne 'darwin';
-
+
+ $self->{selected_instance} = undef;
+
EVT_PAINT($self, \&repaint);
EVT_ERASE_BACKGROUND($self, sub {}) if $self->{user_drawn_background};
EVT_MOUSE_EVENTS($self, \&mouse_event);
@@ -51,6 +66,22 @@ sub new {
$self->update_bed_size;
$self->Refresh;
});
+ EVT_KEY_DOWN($self, sub {
+ my ($s, $event) = @_;
+
+ my $key = $event->GetKeyCode;
+ if ($key == 65 || $key == 314) {
+ $self->nudge_instance('left');
+ } elsif ($key == 87 || $key == 315) {
+ $self->nudge_instance('up');
+ } elsif ($key == 68 || $key == 316) {
+ $self->nudge_instance('right');
+ } elsif ($key == 83 || $key == 317) {
+ $self->nudge_instance('down');
+ } else {
+ $event->Skip;
+ }
+ });
return $self;
}
@@ -77,7 +108,10 @@ sub on_instances_moved {
sub repaint {
my ($self, $event) = @_;
-
+
+ # Focus is needed in order to catch keyboard events.
+ $self->SetFocus;
+
my $dc = Wx::AutoBufferedPaintDC->new($self);
my $size = $self->GetSize;
my @size = ($size->GetWidth, $size->GetHeight);
@@ -87,21 +121,18 @@ sub repaint {
# On MacOS the background is erased, on Windows the background is not erased
# and on Linux/GTK the background is erased to gray color.
# Fill DC with the background on Windows & Linux/GTK.
- my $brush_background = Wx::Brush->new(Wx::wxWHITE, wxSOLID);
- $dc->SetPen(wxWHITE_PEN);
+ my $brush_background = Wx::Brush->new(Wx::Colour->new(@BACKGROUND255), wxSOLID);
+ my $pen_background = Wx::Pen->new(Wx::Colour->new(@BACKGROUND255), 1, wxSOLID);
+ $dc->SetPen($pen_background);
$dc->SetBrush($brush_background);
my $rect = $self->GetUpdateRegion()->GetBox();
$dc->DrawRectangle($rect->GetLeft(), $rect->GetTop(), $rect->GetWidth(), $rect->GetHeight());
}
-
- # draw grid
- $dc->SetPen($self->{grid_pen});
- $dc->DrawLine(map @$_, @$_) for @{$self->{grid}};
# draw bed
{
$dc->SetPen($self->{print_center_pen});
- $dc->SetBrush($self->{transparent_brush});
+ $dc->SetBrush($self->{bed_brush});
$dc->DrawPolygon($self->scaled_points_to_pixel($self->{bed_polygon}, 1), 0, 0);
}
@@ -119,20 +150,24 @@ sub repaint {
# draw frame
if (0) {
- $dc->SetPen(wxBLACK_PEN);
+ $dc->SetPen($self->{dark_pen});
$dc->SetBrush($self->{transparent_brush});
$dc->DrawRectangle(0, 0, @size);
}
# draw text if plate is empty
if (!@{$self->{objects}}) {
- $dc->SetTextForeground(Wx::Colour->new(150,50,50));
+ $dc->SetTextForeground(Wx::Colour->new(@BED_OBJECTS));
$dc->SetFont(Wx::Font->new(14, wxDEFAULT, wxNORMAL, wxNORMAL));
$dc->DrawLabel(CANVAS_TEXT, Wx::Rect->new(0, 0, $self->GetSize->GetWidth, $self->GetSize->GetHeight), wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL);
+ } else {
+ # draw grid
+ $dc->SetPen($self->{grid_pen});
+ $dc->DrawLine(map @$_, @$_) for @{$self->{grid}};
}
# draw thumbnails
- $dc->SetPen(wxBLACK_PEN);
+ $dc->SetPen($self->{dark_pen});
$self->clean_instance_thumbnails;
for my $obj_idx (0 .. $#{$self->{objects}}) {
my $object = $self->{objects}[$obj_idx];
@@ -149,6 +184,8 @@ sub repaint {
if (defined $self->{drag_object} && $self->{drag_object}[0] == $obj_idx && $self->{drag_object}[1] == $instance_idx) {
$dc->SetBrush($self->{dragged_brush});
+ } elsif ($object->selected && $object->selected_instance == $instance_idx) {
+ $dc->SetBrush($self->{instance_brush});
} elsif ($object->selected) {
$dc->SetBrush($self->{selected_brush});
} else {
@@ -201,7 +238,11 @@ sub mouse_event {
my $pos = $event->GetPosition;
my $point = $self->point_to_model_units([ $pos->x, $pos->y ]); #]]
if ($event->ButtonDown) {
+ # On Linux, Focus is needed in order to move selected instance using keyboard arrows.
+ $self->SetFocus;
+
$self->{on_select_object}->(undef);
+ $self->{selected_instance} = undef;
# traverse objects and instances in reverse order, so that if they're overlapping
# we get the one that gets drawn last, thus on top (as user expects that to move)
OBJECTS: for my $obj_idx (reverse 0 .. $#{$self->{objects}}) {
@@ -220,6 +261,8 @@ sub mouse_event {
$point->y - $instance_origin->[Y], #-
];
$self->{drag_object} = [ $obj_idx, $instance_idx ];
+ $self->{objects}->[$obj_idx]->selected_instance($instance_idx);
+ $self->{selected_instance} = $self->{drag_object};
} elsif ($event->RightDown) {
$self->{on_right_click}->($pos);
}
@@ -236,7 +279,7 @@ sub mouse_event {
$self->{drag_object} = undef;
$self->SetCursor(wxSTANDARD_CURSOR);
} elsif ($event->LeftDClick) {
- $self->{on_double_click}->();
+ $self->{on_double_click}->();
} elsif ($event->Dragging) {
return if !$self->{drag_start_pos}; # concurrency problems
my ($obj_idx, $instance_idx) = @{ $self->{drag_object} };
@@ -257,6 +300,55 @@ sub mouse_event {
}
}
+sub nudge_instance{
+ my ($self, $direction) = @_;
+
+ # Get the selected instance of an object.
+ if (!defined $self->{selected_instance}) {
+ # Check if an object is selected.
+ for my $obj_idx (0 .. $#{$self->{objects}}) {
+ if ($self->{objects}->[$obj_idx]->selected) {
+ if ($self->{objects}->[$obj_idx]->selected_instance != -1) {
+ $self->{selected_instance} = [$obj_idx, $self->{objects}->[$obj_idx]->selected_instance];
+ }
+ }
+ }
+ }
+ return if not defined ($self->{selected_instance});
+ my ($obj_idx, $instance_idx) = @{ $self->{selected_instance} };
+ my $object = $self->{model}->objects->[$obj_idx];
+ my $instance = $object->instances->[$instance_idx];
+
+ # Get the nudge values.
+ my $x_nudge = 0;
+ my $y_nudge = 0;
+
+ $self->{nudge_value} = ($Slic3r::GUI::Settings->{_}{nudge_val} < 0.1 ? 0.1 : $Slic3r::GUI::Settings->{_}{nudge_val}) / &Slic3r::SCALING_FACTOR;
+
+ if ($direction eq 'right'){
+ $x_nudge = $self->{nudge_value};
+ } elsif ($direction eq 'left'){
+ $x_nudge = -1 * $self->{nudge_value};
+ } elsif ($direction eq 'up'){
+ $y_nudge = $self->{nudge_value};
+ } elsif ($direction eq 'down'){
+ $y_nudge = -$self->{nudge_value};
+ }
+ my $point = Slic3r::Pointf->new($x_nudge, $y_nudge);
+ my $instance_origin = [ map scale($_), @{$instance->offset} ];
+ $point = [ map scale($_), @{$point} ];
+
+ $instance->set_offset(
+ Slic3r::Pointf->new(
+ unscale( $instance_origin->[X] + $x_nudge),
+ unscale( $instance_origin->[Y] + $y_nudge),
+ ));
+
+ $object->update_bounding_box;
+ $self->Refresh;
+ $self->{on_instances_moved}->();
+}
+
sub update_bed_size {
my $self = shift;
diff --git a/lib/Slic3r/GUI/Plater/2DToolpaths.pm b/lib/Slic3r/GUI/Plater/2DToolpaths.pm
index 2c5a4a7d6..87d819c74 100644
--- a/lib/Slic3r/GUI/Plater/2DToolpaths.pm
+++ b/lib/Slic3r/GUI/Plater/2DToolpaths.pm
@@ -12,15 +12,24 @@ use Wx qw(:misc :sizer :slider :statictext wxWHITE);
use Wx::Event qw(EVT_SLIDER EVT_KEY_DOWN);
use base qw(Wx::Panel Class::Accessor);
+# Color Scheme
+use Slic3r::GUI::ColorScheme;
+
__PACKAGE__->mk_accessors(qw(print enabled));
sub new {
my $class = shift;
my ($parent, $print) = @_;
-
+
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition);
- $self->SetBackgroundColour(wxWHITE);
-
+ if ( ( defined $Slic3r::GUI::Settings->{_}{colorscheme} ) && ( my $getScheme = Slic3r::GUI::ColorScheme->can($Slic3r::GUI::Settings->{_}{colorscheme}) ) ) {
+ $getScheme->();
+ $self->SetBackgroundColour(Wx::Colour->new(@BACKGROUND255));
+ } else {
+ Slic3r::GUI::ColorScheme->getDefault();
+ $self->SetBackgroundColour(Wx::wxWHITE);
+ }
+
# init GUI elements
my $canvas = $self->{canvas} = Slic3r::GUI::Plater::2DToolpaths::Canvas->new($self, $print);
my $slider = $self->{slider} = Wx::Slider->new(
@@ -130,6 +139,9 @@ use List::Util qw(min max first);
use Slic3r::Geometry qw(scale unscale epsilon X Y);
use Slic3r::Print::State ':steps';
+# Color Scheme
+use Slic3r::GUI::ColorScheme;
+
__PACKAGE__->mk_accessors(qw(
print z layers color init
bb
@@ -148,7 +160,13 @@ __PACKAGE__->mk_accessors(qw(
sub new {
my ($class, $parent, $print) = @_;
-
+
+ if ( ( defined $Slic3r::GUI::Settings->{_}{colorscheme} ) && ( Slic3r::GUI::ColorScheme->can($Slic3r::GUI::Settings->{_}{colorscheme}) ) ) {
+ my $myGetSchemeName = \&{"Slic3r::GUI::ColorScheme::$Slic3r::GUI::Settings->{_}{colorscheme}"};
+ $myGetSchemeName->();
+ } else {
+ Slic3r::GUI::ColorScheme->getDefault();
+ }
my $self = (Wx::wxVERSION >= 3.000003) ?
# The wxWidgets 3.0.3-beta have a bug, they crash with NULL attribute list.
$class->SUPER::new($parent, -1, Wx::wxDefaultPosition, Wx::wxDefaultSize, 0, "",
@@ -310,7 +328,12 @@ sub Render {
$self->SetCurrent($context);
$self->InitGL;
- glClearColor(1, 1, 1, 0);
+ if ($DEFAULT_COLORSCHEME==1){
+ glClearColor(1, 1, 1, 0);
+ } else{
+ glClearColor(@BACKGROUND_COLOR, 0);
+ }
+
glClear(GL_COLOR_BUFFER_BIT);
if (!$self->GetParent->enabled || !$self->layers) {
@@ -358,7 +381,7 @@ sub Render {
glTranslatef(@$copy, 0);
foreach my $slice (@{$layer->slices}) {
- glColor3f(0.95, 0.95, 0.95);
+ glColor3f(@TOOL_SHADE); # Inside part shade
if ($tess) {
gluTessBeginPolygon($tess);
@@ -370,7 +393,7 @@ sub Render {
gluTessEndPolygon($tess);
}
- glColor3f(0.9, 0.9, 0.9);
+ glColor3f(@TOOL_COLOR); # Perimeter
foreach my $polygon (@$slice) {
foreach my $line (@{$polygon->lines}) {
glBegin(GL_LINES);
@@ -392,33 +415,33 @@ sub Render {
# draw brim
if ($self->print->step_done(STEP_BRIM) && $layer->id == 0 && !$brim_drawn) {
- $self->color([0, 0, 0]);
+ $self->color(@TOOL_DARK);
$self->_draw(undef, $print_z, $_) for @{$self->print->brim};
$brim_drawn = 1;
}
if ($self->print->step_done(STEP_SKIRT)
&& ($self->print->has_infinite_skirt() || $self->print->config->skirt_height > $layer->id)
&& !$skirt_drawn) {
- $self->color([0, 0, 0]);
+ $self->color(@TOOL_DARK);
$self->_draw(undef, $print_z, $_) for @{$self->print->skirt};
$skirt_drawn = 1;
}
foreach my $layerm (@{$layer->regions}) {
if ($object->step_done(STEP_PERIMETERS)) {
- $self->color([0.7, 0, 0]);
+ $self->color(@TOOL_STEPPERIM);
$self->_draw($object, $print_z, $_) for map @$_, @{$layerm->perimeters};
}
if ($object->step_done(STEP_INFILL)) {
- $self->color([0, 0, 0.7]);
+ $self->color(@TOOL_INFILL);
$self->_draw($object, $print_z, $_) for map @$_, @{$layerm->fills};
}
}
if ($object->step_done(STEP_SUPPORTMATERIAL)) {
if ($layer->isa('Slic3r::Layer::Support')) {
- $self->color([0, 0, 0]);
+ $self->color(@TOOL_SUPPORT);
$self->_draw($object, $print_z, $_) for @{$layer->support_fills};
$self->_draw($object, $print_z, $_) for @{$layer->support_interface_fills};
}
@@ -446,7 +469,7 @@ sub _draw_path {
return if $print_z - $path->height > $self->z - epsilon;
if (abs($print_z - $self->z) < epsilon) {
- glColor3f(@{$self->color});
+ glColor3f($self->color->[0], $self->color->[1], $self->color->[2]);
} else {
glColor3f(0.8, 0.8, 0.8);
}
diff --git a/lib/Slic3r/GUI/Plater/3DPreview.pm b/lib/Slic3r/GUI/Plater/3DPreview.pm
index 8fa9188ef..c4f9e6c5e 100644
--- a/lib/Slic3r/GUI/Plater/3DPreview.pm
+++ b/lib/Slic3r/GUI/Plater/3DPreview.pm
@@ -4,7 +4,7 @@ use warnings;
use utf8;
use Slic3r::Print::State ':steps';
-use Wx qw(:misc :sizer :slider :statictext wxWHITE);
+use Wx qw(:misc :sizer :slider :statictext);
use Wx::Event qw(EVT_SLIDER EVT_KEY_DOWN);
use base qw(Wx::Panel Class::Accessor);
@@ -75,15 +75,15 @@ sub new {
}
sub reload_print {
- my ($self) = @_;
+ my ($self, $obj_idx) = @_;
$self->canvas->reset_objects;
$self->_loaded(0);
- $self->load_print;
+ $self->load_print($obj_idx);
}
sub load_print {
- my ($self) = @_;
+ my ($self, $obj_idx) = @_;
return if $self->_loaded;
@@ -100,10 +100,16 @@ sub load_print {
my $z_idx;
{
my %z = (); # z => 1
- foreach my $object (@{$self->{print}->objects}) {
- foreach my $layer (@{$object->layers}, @{$object->support_layers}) {
+ if(defined $obj_idx) { # Load only given object
+ foreach my $layer (@{$self->{print}->get_object($obj_idx)->layers}) {
$z{$layer->print_z} = 1;
}
+ }else{ # Load all objects on the plater + support material
+ foreach my $object (@{$self->{print}->objects}) {
+ foreach my $layer (@{$object->layers}, @{$object->support_layers}) {
+ $z{$layer->print_z} = 1;
+ }
+ }
}
$self->enabled(1);
$self->{layers_z} = [ sort { $a <=> $b } keys %z ];
@@ -129,14 +135,18 @@ sub load_print {
$self->canvas->colors([ $self->canvas->default_colors ]);
}
- # load skirt and brim
- $self->canvas->load_print_toolpaths($self->print);
-
- foreach my $object (@{$self->print->objects}) {
- $self->canvas->load_print_object_toolpaths($object);
+ if(defined $obj_idx) { # Load only one object
+ $self->canvas->load_print_object_toolpaths($self->{print}->get_object($obj_idx));
+ }else{ # load all objects
+ # load skirt and brim
+ $self->canvas->load_print_toolpaths($self->print);
- #my @volume_ids = $self->canvas->load_object($object->model_object);
- #$self->canvas->volumes->[$_]->color->[3] = 0.2 for @volume_ids;
+ foreach my $object (@{$self->print->objects}) {
+ $self->canvas->load_print_object_toolpaths($object);
+
+ #my @volume_ids = $self->canvas->load_object($object->model_object);
+ #$self->canvas->volumes->[$_]->color->[3] = 0.2 for @volume_ids;
+ }
}
$self->_loaded(1);
}
diff --git a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm
index e0fe90f6e..35c1af28b 100644
--- a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm
+++ b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm
@@ -297,23 +297,20 @@ sub selection_changed {
# attach volume config to settings panel
my $volume = $self->{model_object}->volumes->[ $itemData->{volume_id} ];
- if ($volume->modifier) {
- my $movers = $self->{optgroup_movers};
-
- my $obj_bb = $self->{model_object}->raw_bounding_box;
- my $vol_bb = $volume->mesh->bounding_box;
- my $vol_size = $vol_bb->size;
- $movers->get_field('x')->set_range($obj_bb->x_min - $vol_size->x, $obj_bb->x_max);
- $movers->get_field('y')->set_range($obj_bb->y_min - $vol_size->y, $obj_bb->y_max); #,,
- $movers->get_field('z')->set_range($obj_bb->z_min - $vol_size->z, $obj_bb->z_max);
- $movers->get_field('x')->set_value($vol_bb->x_min);
- $movers->get_field('y')->set_value($vol_bb->y_min);
- $movers->get_field('z')->set_value($vol_bb->z_min);
-
- $self->{left_sizer}->Show($movers->sizer);
- } else {
- $self->{left_sizer}->Hide($self->{optgroup_movers}->sizer);
- }
+ my $movers = $self->{optgroup_movers};
+
+ my $obj_bb = $self->{model_object}->raw_bounding_box;
+ my $vol_bb = $volume->mesh->bounding_box;
+ my $vol_size = $vol_bb->size;
+ $movers->get_field('x')->set_range($obj_bb->x_min - $vol_size->x, $obj_bb->x_max);
+ $movers->get_field('y')->set_range($obj_bb->y_min - $vol_size->y, $obj_bb->y_max); #,,
+ $movers->get_field('z')->set_range($obj_bb->z_min - $vol_size->z, $obj_bb->z_max);
+ $movers->get_field('x')->set_value($vol_bb->x_min);
+ $movers->get_field('y')->set_value($vol_bb->y_min);
+ $movers->get_field('z')->set_value($vol_bb->z_min);
+
+ $self->{left_sizer}->Show($movers->sizer);
+
$config = $volume->config;
$self->{staticbox}->SetLabel('Part Settings');
@@ -356,11 +353,17 @@ sub on_btn_load {
next;
}
- foreach my $object (@{$model->objects}) {
- foreach my $volume (@{$object->volumes}) {
- my $new_volume = $self->{model_object}->add_volume($volume);
+ for my $obj_idx (0..($model->objects_count-1)) {
+ my $object = $model->objects->[$obj_idx];
+ for my $vol_idx (0..($object->volumes_count-1)) {
+ my $new_volume = $self->{model_object}->add_volume($object->get_volume($vol_idx));
$new_volume->set_modifier($is_modifier);
$new_volume->set_name(basename($input_file));
+
+ # input_file needed to reload / update modifiers' volumes
+ $new_volume->set_input_file($input_file);
+ $new_volume->set_input_file_obj_idx($obj_idx);
+ $new_volume->set_input_file_vol_idx($vol_idx);
# apply the same translation we applied to the object
$new_volume->mesh->translate(@{$self->{model_object}->origin_translation});
@@ -410,6 +413,14 @@ sub on_btn_lambda {
} else {
return;
}
+
+ my $center = $self->{model_object}->bounding_box->center;
+ if (!$Slic3r::GUI::Settings->{_}{autocenter}) {
+ #TODO what we really want to do here is just align the
+ # center of the modifier to the center of the part.
+ $mesh->translate($center->x, $center->y, 0);
+ }
+
$mesh->repair;
my $new_volume = $self->{model_object}->add_volume(mesh => $mesh);
$new_volume->set_modifier($is_modifier);
diff --git a/lib/Slic3r/GUI/Plater/ObjectRotateFaceDialog.pm b/lib/Slic3r/GUI/Plater/ObjectRotateFaceDialog.pm
new file mode 100644
index 000000000..b1b84e7be
--- /dev/null
+++ b/lib/Slic3r/GUI/Plater/ObjectRotateFaceDialog.pm
@@ -0,0 +1,121 @@
+# Rotate an object such that a face is aligned with a specified plane.
+# This dialog gets opened with the "Rotate face" button above the platter.
+
+package Slic3r::GUI::Plater::ObjectRotateFaceDialog;
+use strict;
+use warnings;
+use utf8;
+
+use POSIX qw(ceil);
+use Scalar::Util qw(looks_like_number);
+use Slic3r::Geometry qw(PI X Y Z);
+use Wx qw(wxTheApp :dialog :id :misc :sizer wxTAB_TRAVERSAL);
+use Wx::Event qw(EVT_CLOSE EVT_BUTTON);
+use base 'Wx::Dialog';
+
+sub new {
+ my $class = shift;
+ my ($parent, %params) = @_;
+ my $self = $class->SUPER::new($parent, -1, $params{object}->name, wxDefaultPosition, [500,500], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
+ $self->{model_object_idx} = $params{model_object_idx};
+ $self->{model_object} = $params{model_object};
+ $self->{normal} = undef;
+ # Note whether the window was already closed, so a pending update is not executed.
+ $self->{already_closed} = 0;
+ $self->{model_object}->transform_by_instance($self->{model_object}->get_instance(0), 1);
+
+ # Options
+ $self->{options} = {
+ axis => Z,
+ };
+ my $optgroup;
+ $optgroup = $self->{optgroup} = Slic3r::GUI::OptionsGroup->new(
+ parent => $self,
+ title => 'Rotate to Align Face with Plane',
+ on_change => sub {
+ my ($opt_id) = @_;
+ if ($self->{options}{$opt_id} != $optgroup->get_value($opt_id)){
+ $self->{options}{$opt_id} = $optgroup->get_value($opt_id);
+ }
+ },
+ label_width => 120,
+ );
+ $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
+ opt_id => 'axis',
+ type => 'select',
+ label => 'Plane',
+ labels => ['YZ','XZ','XY'],
+ values => [X,Y,Z],
+ default => $self->{options}{axis},
+ ));
+
+ {
+ my $button_sizer = Wx::BoxSizer->new(wxVERTICAL);
+
+ $self->{btn_rot} = Wx::Button->new($self, -1, "Rotate to Plane", wxDefaultPosition, wxDefaultSize);
+ $self->{btn_rot}->SetDefault;
+ $self->{btn_rot}->Disable;
+ $button_sizer->Add($self->{btn_rot}, 0, wxALIGN_RIGHT | wxALL, 10);
+ $optgroup->append_line(Slic3r::GUI::OptionsGroup::Line->new(
+ sizer => $button_sizer,
+ ));
+ }
+
+ # left pane with tree
+ my $left_sizer = Wx::BoxSizer->new(wxVERTICAL);
+ $left_sizer->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
+
+ # right pane with preview canvas
+ my $canvas;
+ if ($Slic3r::GUI::have_OpenGL) {
+ $canvas = $self->{canvas} = Slic3r::GUI::3DScene->new($self);
+ $canvas->load_object($self->{model_object}, undef, [0]);
+ $canvas->set_auto_bed_shape;
+ $canvas->SetSize([500,500]);
+ $canvas->enable_picking(1);
+ $canvas->enable_face_select(1);
+ $canvas->SetMinSize($canvas->GetSize);
+ $canvas->zoom_to_volumes;
+ $canvas->on_select(sub {
+ my ($volume_idx) = @_;
+ $self->{btn_rot}->Disable;
+ $self->{normal} = $canvas->calculate_normal($volume_idx);
+ $self->{btn_rot}->Enable if defined $self->{normal};
+ });
+ }
+
+ $self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL);
+ $self->{sizer}->Add($left_sizer, 0, wxEXPAND | wxTOP | wxBOTTOM, 10);
+ $self->{sizer}->Add($canvas, 1, wxEXPAND | wxALL, 0) if $canvas;
+
+ $self->SetSizer($self->{sizer});
+ $self->SetMinSize($self->GetSize);
+ $self->{sizer}->SetSizeHints($self);
+
+ EVT_BUTTON($self, $self->{btn_rot}, sub {
+ $self->{already_closed} = 1;
+ $self->EndModal(wxID_OK);
+ $self->Destroy();
+ });
+
+ EVT_CLOSE($self, sub {
+ # Note that the window was already closed, so a pending update will not be executed.
+ $self->{already_closed} = 1;
+ $self->EndModal(wxID_CANCEL);
+ $self->Destroy();
+ });
+
+ return $self;
+}
+
+sub SelectedNormal {
+ my ($self) = @_;
+ return $self->{normal};
+}
+
+sub SelectedAxis {
+ my ($self) = @_;
+ return $self->{options}->{axis};
+}
+
+1;
diff --git a/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm b/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm
index 752b4a32c..638a02c9b4 100644
--- a/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm
+++ b/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm
@@ -14,12 +14,17 @@ use base 'Wx::Dialog';
sub new {
my $class = shift;
my ($parent, %params) = @_;
- my $self = $class->SUPER::new($parent, -1, "Settings for " . $params{object}->name, wxDefaultPosition, [700,500], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
+ my $self = $class->SUPER::new($parent, -1, "Settings for " . $params{object}->name, wxDefaultPosition, [1000,700], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
$self->{$_} = $params{$_} for keys %params;
$self->{tabpanel} = Wx::Notebook->new($self, -1, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL);
$self->{tabpanel}->AddPage($self->{parts} = Slic3r::GUI::Plater::ObjectPartsPanel->new($self->{tabpanel}, model_object => $params{model_object}), "Parts");
- $self->{tabpanel}->AddPage($self->{layers} = Slic3r::GUI::Plater::ObjectDialog::LayersTab->new($self->{tabpanel}), "Layers");
+ $self->{tabpanel}->AddPage($self->{adaptive_layers} = Slic3r::GUI::Plater::ObjectDialog::AdaptiveLayersTab->new( $self->{tabpanel},
+ plater => $parent,
+ model_object => $params{model_object},
+ obj_idx => $params{obj_idx}
+ ), "Adaptive Layers");
+ $self->{tabpanel}->AddPage($self->{layers} = Slic3r::GUI::Plater::ObjectDialog::LayersTab->new($self->{tabpanel}), "Layer height table");
my $buttons = $self->CreateStdDialogButtonSizer(wxOK);
EVT_BUTTON($self, wxID_OK, sub {
@@ -68,6 +73,168 @@ sub model_object {
return $self->GetParent->GetParent->{model_object};
}
+package Slic3r::GUI::Plater::ObjectDialog::AdaptiveLayersTab;
+use Slic3r::Geometry qw(X Y Z scale unscale);
+use Slic3r::Print::State ':steps';
+use List::Util qw(min max sum first);
+use Wx qw(wxTheApp :dialog :id :misc :sizer :systemsettings :statictext wxTAB_TRAVERSAL);
+use base 'Slic3r::GUI::Plater::ObjectDialog::BaseTab';
+
+sub new {
+ my $class = shift;
+ my ($parent, %params) = @_;
+ my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize);
+ my $model_object = $self->{model_object} = $params{model_object};
+ my $obj_idx = $self->{obj_idx} = $params{obj_idx};
+ my $plater = $self->{plater} = $params{plater};
+ my $object = $self->{object} = $self->{plater}->{print}->get_object($self->{obj_idx});
+
+ # store last raft height to correctly draw z-indicator plane during a running background job where the printObject is not valid
+ $self->{last_raft_height} = 0;
+
+ # Initialize 3D toolpaths preview
+ if ($Slic3r::GUI::have_OpenGL) {
+ $self->{preview3D} = Slic3r::GUI::Plater::3DPreview->new($self, $plater->{print});
+ $self->{preview3D}->canvas->set_auto_bed_shape;
+ $self->{preview3D}->canvas->SetSize([500,500]);
+ $self->{preview3D}->canvas->SetMinSize($self->{preview3D}->canvas->GetSize);
+ # object already processed?
+ wxTheApp->CallAfter(sub {
+ if (!$plater->{processed}) {
+ $self->_trigger_slicing(0); # trigger processing without invalidating STEP_SLICE to keep current height distribution
+ }else{
+ $self->{preview3D}->reload_print($obj_idx);
+ $self->{preview3D}->canvas->zoom_to_volumes;
+ $self->{preview_zoomed} = 1;
+ }
+ });
+ }
+
+ $self->{splineControl} = Slic3r::GUI::Plater::SplineControl->new($self, Wx::Size->new(150, 200), $object);
+
+ my $optgroup;
+ $optgroup = $self->{optgroup} = Slic3r::GUI::OptionsGroup->new(
+ parent => $self,
+ title => 'Adaptive quality %',
+ on_change => sub {
+ my ($opt_id) = @_;
+ # There seems to be an issue with wxWidgets 3.0.2/3.0.3, where the slider
+ # genates tens of events for a single value change.
+ # Only trigger the recalculation if the value changes
+ # or a live preview was activated and the mesh cut is not valid yet.
+ if ($self->{adaptive_quality} != $optgroup->get_value($opt_id)) {
+ $self->{adaptive_quality} = $optgroup->get_value($opt_id);
+ $self->{model_object}->config->set('adaptive_slicing_quality', $optgroup->get_value($opt_id));
+ $self->{object}->config->set('adaptive_slicing_quality', $optgroup->get_value($opt_id));
+ $self->{object}->invalidate_step(STEP_LAYERS);
+ # trigger re-slicing
+ $self->_trigger_slicing;
+ }
+ },
+ label_width => 0,
+ );
+
+ $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
+ opt_id => 'adaptive_slicing_quality',
+ type => 'slider',
+ label => '',
+ default => $object->config->get('adaptive_slicing_quality'),
+ min => 0,
+ max => 100,
+ full_width => 1,
+ ));
+ $optgroup->get_field('adaptive_slicing_quality')->set_scale(1);
+ $self->{adaptive_quality} = $object->config->get('adaptive_slicing_quality');
+ # init quality slider
+ if(!$object->config->get('adaptive_slicing')) {
+ # disable slider
+ $optgroup->get_field('adaptive_slicing_quality')->disable;
+ }
+
+ my $right_sizer = Wx::BoxSizer->new(wxVERTICAL);
+ $right_sizer->Add($self->{splineControl}, 1, wxEXPAND | wxALL, 0);
+ $right_sizer->Add($optgroup->sizer, 0, wxEXPAND | wxALL, 0);
+
+
+ $self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL);
+ $self->{sizer}->Add($self->{preview3D}, 3, wxEXPAND | wxTOP | wxBOTTOM, 0) if $self->{preview3D};
+ $self->{sizer}->Add($right_sizer, 1, wxEXPAND | wxTOP | wxBOTTOM, 10);
+
+ $self->SetSizerAndFit($self->{sizer});
+
+ # init spline control values
+ # determine min and max layer height from perimeter extruder capabilities.
+ my %extruders;
+ for my $region_id (0 .. ($object->region_count - 1)) {
+ foreach (qw(perimeter_extruder infill_extruder solid_infill_extruder)) {
+ my $extruder_id = $self->{plater}->{print}->get_region($region_id)->config->get($_)-1;
+ $extruders{$extruder_id} = $extruder_id;
+ }
+ }
+ my $min_height = max(map {$self->{plater}->{print}->config->get_at('min_layer_height', $_)} (values %extruders));
+ my $max_height = min(map {$self->{plater}->{print}->config->get_at('max_layer_height', $_)} (values %extruders));
+
+ $self->{splineControl}->set_size_parameters($min_height, $max_height, unscale($object->size->z));
+
+ $self->{splineControl}->on_layer_update(sub {
+ # trigger re-slicing
+ $self->_trigger_slicing;
+ });
+
+ $self->{splineControl}->on_z_indicator(sub {
+ my ($z) = @_;
+
+ if($z) { # compensate raft height
+ $z += $self->{last_raft_height};
+ }
+ $self->{preview3D}->canvas->SetCuttingPlane(Z, $z, []);
+ $self->{preview3D}->canvas->Render;
+ });
+
+ return $self;
+}
+
+# This is called by the plater after processing to update the preview and spline
+sub reload_preview {
+ my ($self) = @_;
+ $self->{splineControl}->update;
+ $self->{preview3D}->reload_print($self->{obj_idx});
+ my $object = $self->{plater}->{print}->get_object($self->{obj_idx});
+ if($object->layer_count-1 > 0) {
+ my $first_layer = $self->{object}->get_layer(0);
+ $self->{last_raft_height} = max(0, $first_layer->print_z - $first_layer->height);
+ $self->{preview3D}->set_z(unscale($self->{object}->size->z));
+ if(!$self->{preview_zoomed}) {
+ $self->{preview3D}->canvas->set_auto_bed_shape;
+ $self->{preview3D}->canvas->zoom_to_volumes;
+ $self->{preview_zoomed} = 1;
+ }
+ }
+}
+
+# Trigger background slicing at the plater
+sub _trigger_slicing {
+ my ($self, $invalidate) = @_;
+ $invalidate //= 1;
+ my $object = $self->{plater}->{print}->get_object($self->{obj_idx});
+ $self->{model_object}->set_layer_height_spline($self->{object}->layer_height_spline); # push modified spline object to model_object
+ #$self->{plater}->pause_background_process;
+ $self->{plater}->stop_background_process;
+ if (!$Slic3r::GUI::Settings->{_}{background_processing}) {
+ $self->{plater}->statusbar->SetCancelCallback(sub {
+ $self->{plater}->stop_background_process;
+ $self->{plater}->statusbar->SetStatusText("Slicing cancelled");
+ $self->{plater}->preview_notebook->SetSelection(0);
+ });
+ $object->invalidate_step(STEP_SLICE) if($invalidate);
+ $self->{plater}->start_background_process;
+ }else{
+ $object->invalidate_step(STEP_SLICE) if($invalidate);
+ $self->{plater}->schedule_background_process;
+ }
+}
+
+
package Slic3r::GUI::Plater::ObjectDialog::LayersTab;
use Wx qw(:dialog :id :misc :sizer :systemsettings);
use Wx::Grid;
@@ -82,9 +249,10 @@ sub new {
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
{
- my $label = Wx::StaticText->new($self, -1, "You can use this section to override the default layer height for parts of this object. Set layer height to zero to skip portions of the input file.",
+ my $label = Wx::StaticText->new($self, -1, "You can use this section to override the layer height for parts of this object. The values from this table will override the default layer height and adaptive layer heights, but not the interactively modified height curve.",
wxDefaultPosition, wxDefaultSize);
$label->SetFont(Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
+ $label->Wrap(800);
$sizer->Add($label, 0, wxEXPAND | wxALL, 10);
}
diff --git a/lib/Slic3r/GUI/Plater/SplineControl.pm b/lib/Slic3r/GUI/Plater/SplineControl.pm
new file mode 100644
index 000000000..f47a91af2
--- /dev/null
+++ b/lib/Slic3r/GUI/Plater/SplineControl.pm
@@ -0,0 +1,393 @@
+package Slic3r::GUI::Plater::SplineControl;
+use strict;
+use warnings;
+use utf8;
+
+use List::Util qw(min max first);
+use Slic3r::Geometry qw(X Y scale unscale);
+use Wx qw(:misc :pen :brush :sizer :font :cursor wxTAB_TRAVERSAL);
+use Wx::Event qw(EVT_MOUSE_EVENTS EVT_PAINT EVT_ERASE_BACKGROUND EVT_SIZE);
+use base 'Wx::Panel';
+
+# Color Scheme
+use Slic3r::GUI::ColorScheme;
+
+sub new {
+ my $class = shift;
+ my ($parent, $size, $object) = @_;
+
+ if ( ( defined $Slic3r::GUI::Settings->{_}{colorscheme} ) && ( Slic3r::GUI::ColorScheme->can($Slic3r::GUI::Settings->{_}{colorscheme}) ) ) {
+ my $myGetSchemeName = \&{"Slic3r::GUI::ColorScheme::$Slic3r::GUI::Settings->{_}{colorscheme}"};
+ $myGetSchemeName->();
+ } else {
+ Slic3r::GUI::ColorScheme->getDefault();
+ }
+
+ my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, $size, wxTAB_TRAVERSAL);
+
+ $self->{object} = $object;
+ $self->{is_valid} = 0;
+
+ # This has only effect on MacOS. On Windows and Linux/GTK, the background is painted by $self->repaint().
+ $self->SetBackgroundColour(Wx::Colour->new(@BACKGROUND255));
+
+ $self->{line_pen} = Wx::Pen->new(Wx::Colour->new(@SPLINE_L_PEN), 1, wxSOLID);
+ $self->{original_pen} = Wx::Pen->new(Wx::Colour->new(@SPLINE_O_PEN), 1, wxSOLID);
+ $self->{interactive_pen} = Wx::Pen->new(Wx::Colour->new(@SPLINE_I_PEN), 1, wxSOLID);
+ $self->{resulting_pen} = Wx::Pen->new(Wx::Colour->new(@SPLINE_R_PEN), 1, wxSOLID);
+
+ $self->{user_drawn_background} = $^O ne 'darwin';
+
+ # scale plot data to actual canvas, documentation in set_size_parameters
+ $self->{scaling_factor_x} = 1;
+ $self->{scaling_factor_y} = 1;
+ $self->{min_layer_height} = 0.1;
+ $self->{max_layer_height} = 0.4;
+ $self->{mousover_layer_height} = undef; # display layer height at mousover position
+ $self->{object_height} = 1.0;
+
+ # initialize values
+ $self->update;
+
+ EVT_PAINT($self, \&repaint);
+ EVT_ERASE_BACKGROUND($self, sub {}) if $self->{user_drawn_background};
+ EVT_MOUSE_EVENTS($self, \&mouse_event);
+ EVT_SIZE($self, sub {
+ $self->_update_canvas_size;
+ $self->Refresh;
+ });
+
+ return $self;
+}
+
+sub repaint {
+ my ($self, $event) = @_;
+
+ my $dc = Wx::AutoBufferedPaintDC->new($self);
+ my $size = $self->GetSize;
+ my @size = ($size->GetWidth, $size->GetHeight);
+
+
+ if ($self->{user_drawn_background}) {
+ # On all systems the AutoBufferedPaintDC() achieves double buffering.
+ # On MacOS the background is erased, on Windows the background is not erased
+ # and on Linux/GTK the background is erased to gray color.
+ # Fill DC with the background on Windows & Linux/GTK.
+ my $brush_background = Wx::Brush->new(Wx::Colour->new(@BACKGROUND255), wxSOLID);
+ my $pen_background = Wx::Pen->new(Wx::Colour->new(@BACKGROUND255), 1, wxSOLID);
+ $dc->SetPen($pen_background);
+ $dc->SetBrush($brush_background);
+ my $rect = $self->GetUpdateRegion()->GetBox();
+ $dc->DrawRectangle($rect->GetLeft(), $rect->GetTop(), $rect->GetWidth(), $rect->GetHeight());
+ }
+
+ # draw scale (min and max indicator at the bottom)
+ $dc->SetTextForeground(Wx::Colour->new(0,0,0));
+ $dc->SetFont(Wx::Font->new(10, wxDEFAULT, wxNORMAL, wxNORMAL));
+ $dc->DrawLabel(sprintf('%.4g', $self->{min_layer_height}), Wx::Rect->new(0, $size[1]/2, $size[0], $size[1]/2), wxALIGN_LEFT | wxALIGN_BOTTOM);
+ $dc->DrawLabel(sprintf('%.2g', $self->{max_layer_height}), Wx::Rect->new(0, $size[1]/2, $size[0], $size[1]/2), wxALIGN_RIGHT | wxALIGN_BOTTOM);
+ if($self->{mousover_layer_height}){
+ $dc->DrawLabel(sprintf('%4.2fmm', $self->{mousover_layer_height}), Wx::Rect->new(0, 0, $size[0], $size[1]), wxALIGN_RIGHT | wxALIGN_TOP);
+ }
+
+ if($self->{is_valid}) {
+
+ # draw original spline as reference
+ if($self->{original_height_spline}) {
+ #draw spline
+ $self->_draw_layer_height_spline($dc, $self->{original_height_spline}, $self->{original_pen});
+ }
+
+ # draw interactive (currently modified by the user) layers as lines and spline
+ if($self->{interactive_height_spline}) {
+ # draw layer lines
+ my @interpolated_layers = @{$self->{interactive_height_spline}->getInterpolatedLayers};
+ $self->_draw_layers_as_lines($dc, $self->{interactive_pen}, \@interpolated_layers);
+
+ #draw spline
+ $self->_draw_layer_height_spline($dc, $self->{interactive_height_spline}, $self->{interactive_pen});
+ }
+
+ # draw resulting layers as lines
+ unless($self->{interactive_heights}) {
+ $self->_draw_layers_as_lines($dc, $self->{resulting_pen}, $self->{interpolated_layers});
+ }
+
+ # Always draw current BSpline, gives a reference during a modification
+ $self->_draw_layer_height_spline($dc, $self->{object}->layer_height_spline, $self->{line_pen});
+ }
+
+ $event->Skip;
+}
+
+# Set basic parameters for this control.
+# min/max_layer_height are required to define the x-range, object_height is used to scale the y-range.
+# Must be called if object selection changes.
+sub set_size_parameters {
+ my ($self, $min_layer_height, $max_layer_height, $object_height) = @_;
+
+ $self->{min_layer_height} = $min_layer_height;
+ $self->{max_layer_height} = $max_layer_height;
+ $self->{object_height} = $object_height;
+
+ $self->_update_canvas_size;
+ $self->Refresh;
+}
+
+# Layers have been modified externally, re-initialize this control with new values
+sub update {
+ my $self = shift;
+
+ if(($self->{object}->layer_height_spline->layersUpdated || !$self->{heights}) && $self->{object}->layer_height_spline->hasData) {
+ $self->{original_height_spline} = $self->{object}->layer_height_spline->clone; # make a copy to display the unmodified original spline
+ $self->{original_layers} = $self->{object}->layer_height_spline->getOriginalLayers;
+ $self->{interpolated_layers} = $self->{object}->layer_height_spline->getInterpolatedLayers; # Initialize to current values
+
+ # initialize height vector
+ $self->{heights} = ();
+ $self->{interactive_heights} = ();
+ foreach my $z (@{$self->{original_layers}}) {
+ push (@{$self->{heights}}, $self->{object}->layer_height_spline->getLayerHeightAt($z));
+ }
+ $self->{is_valid} = 1;
+ }
+ $self->Refresh;
+}
+
+# Callback to notify parent element if layers have changed and reslicing should be triggered
+sub on_layer_update {
+ my ($self, $cb) = @_;
+ $self->{on_layer_update} = $cb;
+}
+
+# Callback to tell parent element at which z-position the mouse currently hovers to update indicator in 3D-view
+sub on_z_indicator {
+ my ($self, $cb) = @_;
+ $self->{on_z_indicator} = $cb;
+}
+
+
+sub mouse_event {
+ my ($self, $event) = @_;
+
+ my $pos = $event->GetPosition;
+ my @obj_pos = $self->pixel_to_point($pos);
+
+ if ($event->ButtonDown) {
+ if ($event->LeftDown) {
+ # start dragging
+ $self->{left_drag_start_pos} = $pos;
+ $self->{interactive_height_spline} = $self->{object}->layer_height_spline->clone;
+ }
+ if ($event->RightDown) {
+ # start dragging
+ $self->{right_drag_start_pos} = $pos;
+ $self->{interactive_height_spline} = $self->{object}->layer_height_spline->clone;
+ }
+ } elsif ($event->LeftUp) {
+ if($self->{left_drag_start_pos}) {
+ $self->_modification_done;
+ }
+ $self->{left_drag_start_pos} = undef;
+ } elsif ($event->RightUp) {
+ if($self->{right_drag_start_pos}) {
+ $self->_modification_done;
+ }
+ $self->{right_drag_start_pos} = undef;
+ } elsif ($event->Dragging) {
+ if($self->{left_drag_start_pos}) {
+
+ my @start_pos = $self->pixel_to_point($self->{left_drag_start_pos});
+ my $range = abs($start_pos[1] - $obj_pos[1]);
+
+ # compute updated interactive layer heights
+ $self->_interactive_quadratic_curve($start_pos[1], $obj_pos[0], $range);
+
+ unless($self->{interactive_height_spline}->updateLayerHeights($self->{interactive_heights})) {
+ die "Unable to update interactive interpolated layers!\n";
+ }
+ $self->Refresh;
+ } elsif($self->{right_drag_start_pos}) {
+ my @start_pos = $self->pixel_to_point($self->{right_drag_start_pos});
+ my $range = $obj_pos[1] - $start_pos[1];
+
+ # compute updated interactive layer heights
+ $self->_interactive_linear_curve($start_pos[1], $obj_pos[0], $range);
+ unless($self->{interactive_height_spline}->updateLayerHeights($self->{interactive_heights})) {
+ die "Unable to update interactive interpolated layers!\n";
+ }
+ $self->Refresh;
+ }
+ } elsif ($event->Moving) {
+ if($self->{on_z_indicator}) {
+ $self->{on_z_indicator}->($obj_pos[1]);
+ $self->{mousover_layer_height} = $self->{object}->layer_height_spline->getLayerHeightAt($obj_pos[1]);
+ $self->Refresh;
+ $self->Update;
+ }
+ } elsif ($event->Leaving) {
+ if($self->{on_z_indicator} && !$self->{left_drag_start_pos}) {
+ $self->{on_z_indicator}->(undef);
+ }
+ $self->{mousover_layer_height} = undef;
+ $self->Refresh;
+ $self->Update;
+ }
+}
+
+# Push modified heights to the spline object and update after user modification
+sub _modification_done {
+ my $self = shift;
+
+ if($self->{interactive_heights}) {
+ $self->{heights} = $self->{interactive_heights};
+ $self->{interactive_heights} = ();
+ # update spline database
+ unless($self->{object}->layer_height_spline->updateLayerHeights($self->{heights})) {
+ die "Unable to update interpolated layers!\n";
+ }
+ $self->{interpolated_layers} = $self->{object}->layer_height_spline->getInterpolatedLayers;
+ }
+ $self->Refresh;
+ $self->{on_layer_update}->(@{$self->{interpolated_layers}});
+ $self->{interactive_height_spline} = undef;
+}
+
+# Internal function to cache scaling factors
+sub _update_canvas_size {
+ my $self = shift;
+
+ # when the canvas is not rendered yet, its GetSize() method returns 0,0
+ my $canvas_size = $self->GetSize;
+ my ($canvas_w, $canvas_h) = ($canvas_size->GetWidth, $canvas_size->GetHeight);
+ return if $canvas_w == 0;
+
+ my $padding = $self->{canvas_padding} = 10; # border size in pixels
+
+ my @size = ($canvas_w - 2*$padding, $canvas_h - 2*$padding);
+ $self->{canvas_size} = [@size];
+
+ $self->{scaling_factor_x} = $size[0]/($self->{max_layer_height} - $self->{min_layer_height});
+ $self->{scaling_factor_y} = $size[1]/$self->{object_height};
+}
+
+# calculate new set of layers with quadaratic modifier for interactive display
+sub _interactive_quadratic_curve {
+ my ($self, $mod_z, $target_layer_height, $range) = @_;
+
+ $self->{interactive_heights} = (); # reset interactive curve
+
+ # iterate over original points provided by spline
+ my $last_z = 0;
+ foreach my $i (0..@{$self->{heights}}-1 ) {
+ my $z = $self->{original_layers}[$i];
+ my $layer_h = $self->{heights}[$i];
+ my $quadratic_factor = $self->_quadratic_factor($mod_z, $range, $z);
+ my $diff = $target_layer_height - $layer_h;
+ $layer_h += $diff * $quadratic_factor;
+ push (@{$self->{interactive_heights}}, $layer_h);
+ }
+}
+
+# calculate new set of layers with linear modifier for interactive display
+sub _interactive_linear_curve {
+ my ($self, $mod_z, $target_layer_height, $range) = @_;
+
+ $self->{interactive_heights} = (); # reset interactive curve
+ my $from;
+ my $to;
+
+ if($range >= 0) {
+ $from = $mod_z;
+ $to = $mod_z + $range;
+ }else{
+ $from = $mod_z + $range;
+ $to = $mod_z;
+ }
+
+ # iterate over original points provided by spline
+ foreach my $i (0..@{$self->{heights}}-1 ) {
+ if(($self->{original_layers}[$i]) >= $from && ($self->{original_layers}[$i]< $to)) {
+ push (@{$self->{interactive_heights}}, $target_layer_height);
+ }else{
+ push (@{$self->{interactive_heights}}, $self->{heights}[$i]);
+ }
+ }
+}
+
+sub _quadratic_factor {
+ my ($self, $fixpoint, $range, $value) = @_;
+
+ # avoid division by zero
+ $range = 0.00001 if $range <= 0;
+
+ my $dist = abs($fixpoint - $value);
+ my $x = $dist/$range; # normalize
+ my $result = 1-($x*$x);
+
+ return max(0, $result);
+}
+
+# Draw a set of layers as lines
+sub _draw_layers_as_lines {
+ my ($self, $dc, $pen, $layers) = @_;
+
+ $dc->SetPen($pen);
+ my $last_z = 0.0;
+ foreach my $z (@$layers) {
+ my $layer_h = $z - $last_z;
+ # only draw layers up to object height. The spline might contain one more layer due to
+ # print_z / slice_z effects
+ if($z le $self->{object_height}) {
+ my $pl = $self->point_to_pixel(0, $z);
+ my $pr = $self->point_to_pixel($layer_h, $z);
+ $dc->DrawLine($pl->x, $pl->y, $pr->x, $pr->y);
+ }
+ $last_z = $z;
+ }
+}
+
+# Draw the resulting spline from a LayerHeightSpline object over the full canvas height
+sub _draw_layer_height_spline {
+ my ($self, $dc, $layer_height_spline, $pen) = @_;
+
+ my @size = @{$self->{canvas_size}};
+
+ $dc->SetPen($pen);
+ my @points = ();
+ foreach my $pixel (0..$size[1]) {
+ my @z = $self->pixel_to_point(Wx::Point->new(0, $pixel));
+ my $h = $layer_height_spline->getLayerHeightAt($z[1]);
+ my $p = $self->point_to_pixel($h, $z[1]);
+ push (@points, $p);
+ }
+ $dc->DrawLines(\@points);
+}
+
+# Takes a 2-tupel [layer_height (x), height(y)] and converts it
+# into a Wx::Point in scaled canvas coordinates
+sub point_to_pixel {
+ my ($self, @point) = @_;
+
+ my @size = @{$self->{canvas_size}};
+
+ my $x = ($point[0] - $self->{min_layer_height})*$self->{scaling_factor_x} + $self->{canvas_padding};
+ my $y = $size[1] - $point[1]*$self->{scaling_factor_y} + $self->{canvas_padding}; # invert y-axis
+
+ return Wx::Point->new(min(max($x, $self->{canvas_padding}), $size[0]+$self->{canvas_padding}), min(max($y, $self->{canvas_padding}), $size[1]+$self->{canvas_padding})); # limit to canvas size
+}
+
+# Takes a Wx::Point in scaled canvas coordinates and converts it
+# into a 2-tupel [layer_height (x), height(y)]
+sub pixel_to_point {
+ my ($self, $point) = @_;
+
+ my @size = @{$self->{canvas_size}};
+
+ my $x = ($point->x-$self->{canvas_padding})/$self->{scaling_factor_x} + $self->{min_layer_height};
+ my $y = ($size[1] - $point->y)/$self->{scaling_factor_y}; # invert y-axis
+
+ return (min(max($x, $self->{min_layer_height}), $self->{max_layer_height}), min(max($y, 0), $self->{object_height})); # limit to object size and layer constraints
+}
+
+1;
diff --git a/lib/Slic3r/GUI/Preferences.pm b/lib/Slic3r/GUI/Preferences.pm
index c7d8a84bb..e63ca8cbc 100644
--- a/lib/Slic3r/GUI/Preferences.pm
+++ b/lib/Slic3r/GUI/Preferences.pm
@@ -20,7 +20,7 @@ sub new {
},
label_width => 200,
);
- $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
+ $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( # version_check
opt_id => 'version_check',
type => 'bool',
label => 'Check for updates',
@@ -28,28 +28,35 @@ sub new {
default => $Slic3r::GUI::Settings->{_}{version_check} // 1,
readonly => !wxTheApp->have_version_check,
));
- $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
+ $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( # remember_output_path
opt_id => 'remember_output_path',
type => 'bool',
label => 'Remember output directory',
tooltip => 'If this is enabled, Slic3r will prompt the last output directory instead of the one containing the input files.',
default => $Slic3r::GUI::Settings->{_}{remember_output_path},
));
- $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
+ $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( # autocenter
opt_id => 'autocenter',
type => 'bool',
- label => 'Auto-center parts',
+ label => 'Auto-center parts (x,y)',
tooltip => 'If this is enabled, Slic3r will auto-center objects around the print bed center.',
default => $Slic3r::GUI::Settings->{_}{autocenter},
));
- $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
+ $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( # autoalignz
+ opt_id => 'autoalignz',
+ type => 'bool',
+ label => 'Auto-align parts (z=0)',
+ tooltip => 'If this is enabled, Slic3r will auto-align objects z value to be on the print bed at z=0.',
+ default => $Slic3r::GUI::Settings->{_}{autoalignz},
+ ));
+ $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( # invert_zoom
opt_id => 'invert_zoom',
type => 'bool',
label => 'Invert zoom in previews',
tooltip => 'If this is enabled, Slic3r will invert the direction of mouse-wheel zoom in preview panes.',
default => $Slic3r::GUI::Settings->{_}{invert_zoom},
));
- $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
+ $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( # background_processing
opt_id => 'background_processing',
type => 'bool',
label => 'Background processing',
@@ -57,20 +64,61 @@ sub new {
default => $Slic3r::GUI::Settings->{_}{background_processing},
readonly => !$Slic3r::have_threads,
));
- $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
+ $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( # threads
opt_id => 'threads',
type => 'i',
label => 'Threads',
tooltip => $Slic3r::Config::Options->{threads}{tooltip},
default => $Slic3r::GUI::Settings->{_}{threads},
));
- $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
+ $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( # tabbed_preset_editors
opt_id => 'tabbed_preset_editors',
type => 'bool',
label => 'Display profile editors as tabs',
tooltip => 'When opening a profile editor, it will be shown in a dialog or in a tab according to this option.',
default => $Slic3r::GUI::Settings->{_}{tabbed_preset_editors},
));
+ $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( # show_host
+ opt_id => 'show_host',
+ type => 'bool',
+ label => 'Show Controller Tab (requires restart)',
+ tooltip => 'Shows/Hides the Controller Tab. Requires a restart of Slic3r.',
+ default => $Slic3r::GUI::Settings->{_}{show_host},
+ ));
+ $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( # nudge_val
+ opt_id => 'nudge_val',
+ type => 's',
+ label => '2D plater nudge value',
+ tooltip => 'In 2D plater, Move objects using keyboard by nudge value of',
+ default => $Slic3r::GUI::Settings->{_}{nudge_val},
+ ));
+ $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( # reload hide dialog
+ opt_id => 'reload_hide_dialog',
+ type => 'bool',
+ label => 'Hide Dialog on Reload',
+ tooltip => 'When checked, the dialog on reloading files with added parts & modifiers is suppressed. The reload is performed according to the option given in \'Default Reload Behavior\'',
+ default => $Slic3r::GUI::Settings->{_}{reload_hide_dialog},
+ ));
+ $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( # default reload behavior
+ opt_id => 'reload_behavior',
+ type => 'select',
+ label => 'Default Reload Behavior',
+ tooltip => 'Choose the default behavior of the \'Reload from disk\' function regarding additional parts and modifiers.',
+ labels => ['Reload all','Reload main, copy added','Reload main, discard added'],
+ values => [0, 1, 2],
+ default => $Slic3r::GUI::Settings->{_}{reload_behavior},
+ width => 180,
+ ));
+ $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( # colorscheme
+ opt_id => 'colorscheme',
+ type => 'select',
+ label => 'Color Scheme',
+ tooltip => 'Choose between color schemes - restart of Slic3r required.',
+ labels => ['Default','Solarized'], # add more schemes, if you want in ColorScheme.pm.
+ values => ['getDefault','getSolarized'], # add more schemes, if you want - those are the names of the corresponding function in ColorScheme.pm.
+ default => $Slic3r::GUI::Settings->{_}{colorscheme} // 'getDefault',
+ width => 180,
+ ));
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
$sizer->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
diff --git a/lib/Slic3r/GUI/Preset.pm b/lib/Slic3r/GUI/Preset.pm
index 0dfc33df8..f6d3de733 100644
--- a/lib/Slic3r/GUI/Preset.pm
+++ b/lib/Slic3r/GUI/Preset.pm
@@ -183,7 +183,7 @@ sub dirty_config {
sub load_config {
my ($self) = @_;
- return if $self->_loaded;
+ return $self->_config if $self->_loaded;
my @keys = $self->_group_class->options;
my @extra_keys = $self->_group_class->overriding_options;
diff --git a/lib/Slic3r/GUI/PresetEditor.pm b/lib/Slic3r/GUI/PresetEditor.pm
index 13f578a44..570cf86db 100644
--- a/lib/Slic3r/GUI/PresetEditor.pm
+++ b/lib/Slic3r/GUI/PresetEditor.pm
@@ -4,7 +4,7 @@ use warnings;
use utf8;
use File::Basename qw(basename);
-use List::Util qw(first);
+use List::Util qw(first any);
use Wx qw(:bookctrl :dialog :keycode :icon :id :misc :panel :sizer :treectrl :window
:button wxTheApp);
use Wx::Event qw(EVT_BUTTON EVT_CHOICE EVT_KEY_DOWN EVT_TREE_SEL_CHANGED EVT_CHECKBOX);
@@ -147,12 +147,11 @@ sub on_value_change {
# propagate event to the parent
sub _on_value_change {
my ($self, $opt_key) = @_;
-
wxTheApp->CallAfter(sub {
$self->current_preset->_dirty_config->apply($self->config);
$self->{on_value_change}->($opt_key) if $self->{on_value_change};
$self->load_presets;
- $self->_update;
+ $self->_update($opt_key);
});
}
@@ -437,8 +436,9 @@ sub title { 'Print Settings' }
sub options {
return qw(
layer_height first_layer_height
+ adaptive_slicing adaptive_slicing_quality match_horizontal_surfaces
perimeters spiral_vase
- top_solid_layers bottom_solid_layers
+ top_solid_layers min_shell_thickness min_top_bottom_shell_thickness bottom_solid_layers
extra_perimeters avoid_crossing_perimeters thin_walls overhangs
seam_position external_perimeters_first
fill_density fill_pattern top_infill_pattern bottom_infill_pattern fill_gaps
@@ -455,23 +455,25 @@ sub options {
first_layer_acceleration default_acceleration
skirts skirt_distance skirt_height min_skirt_length
brim_connections_width brim_width interior_brim_width
- support_material support_material_threshold support_material_enforce_layers
+ support_material support_material_threshold support_material_max_layers support_material_enforce_layers
raft_layers
- support_material_pattern support_material_spacing support_material_angle
+ support_material_pattern support_material_spacing support_material_angle
+ support_material_pillar_size support_material_pillar_spacing
support_material_interface_layers support_material_interface_spacing
support_material_contact_distance support_material_buildplate_only dont_support_bridges
notes
complete_objects extruder_clearance_radius extruder_clearance_height
gcode_comments output_filename_format
+ label_printed_objects
post_process
perimeter_extruder infill_extruder solid_infill_extruder
support_material_extruder support_material_interface_extruder
ooze_prevention standby_temperature_delta
- interface_shells
+ interface_shells regions_overlap
extrusion_width first_layer_extrusion_width perimeter_extrusion_width
external_perimeter_extrusion_width infill_extrusion_width solid_infill_extrusion_width
top_infill_extrusion_width support_material_extrusion_width
- infill_overlap bridge_flow_ratio
+ support_material_interface_extrusion_width infill_overlap bridge_flow_ratio
xy_size_compensation resolution shortcuts compatible_printers
print_settings_id
)
@@ -512,10 +514,15 @@ sub build {
my $optgroup = $page->new_optgroup('Layer height');
$optgroup->append_single_option_line('layer_height');
$optgroup->append_single_option_line('first_layer_height');
+ $optgroup->append_single_option_line('adaptive_slicing');
+ $optgroup->append_single_option_line('adaptive_slicing_quality');
+ $optgroup->get_field('adaptive_slicing_quality')->set_scale(1);
+ $optgroup->append_single_option_line('match_horizontal_surfaces');
}
{
my $optgroup = $page->new_optgroup('Vertical shells');
$optgroup->append_single_option_line('perimeters');
+ $optgroup->append_single_option_line('min_shell_thickness');
$optgroup->append_single_option_line('spiral_vase');
}
{
@@ -526,6 +533,8 @@ sub build {
$line->append_option($optgroup->get_option('top_solid_layers'));
$line->append_option($optgroup->get_option('bottom_solid_layers'));
$optgroup->append_line($line);
+
+ $optgroup->append_single_option_line('min_top_bottom_shell_thickness');
}
{
my $optgroup = $page->new_optgroup('Quality (slower slicing)');
@@ -595,6 +604,7 @@ sub build {
my $optgroup = $page->new_optgroup('Support material');
$optgroup->append_single_option_line('support_material');
$optgroup->append_single_option_line('support_material_threshold');
+ $optgroup->append_single_option_line('support_material_max_layers');
$optgroup->append_single_option_line('support_material_enforce_layers');
}
{
@@ -607,13 +617,15 @@ sub build {
$optgroup->append_single_option_line('support_material_pattern');
$optgroup->append_single_option_line('support_material_spacing');
$optgroup->append_single_option_line('support_material_angle');
+ $optgroup->append_single_option_line('support_material_pillar_size');
+ $optgroup->append_single_option_line('support_material_pillar_spacing');
$optgroup->append_single_option_line('support_material_interface_layers');
$optgroup->append_single_option_line('support_material_interface_spacing');
$optgroup->append_single_option_line('support_material_buildplate_only');
$optgroup->append_single_option_line('dont_support_bridges');
}
}
-
+
{
my $page = $self->add_options_page('Speed', 'time.png');
{
@@ -665,6 +677,7 @@ sub build {
}
{
my $optgroup = $page->new_optgroup('Advanced');
+ $optgroup->append_single_option_line('regions_overlap');
$optgroup->append_single_option_line('interface_shells');
}
}
@@ -679,7 +692,8 @@ sub build {
for qw(extrusion_width first_layer_extrusion_width
perimeter_extrusion_width external_perimeter_extrusion_width
infill_extrusion_width solid_infill_extrusion_width
- top_infill_extrusion_width support_material_extrusion_width);
+ top_infill_extrusion_width support_material_interface_extrusion_width
+ support_material_extrusion_width);
}
{
my $optgroup = $page->new_optgroup('Overlap');
@@ -714,6 +728,7 @@ sub build {
{
my $optgroup = $page->new_optgroup('Output file');
$optgroup->append_single_option_line('gcode_comments');
+ $optgroup->append_single_option_line('label_printed_objects');
{
my $option = $optgroup->get_option('output_filename_format');
@@ -786,116 +801,145 @@ sub reload_config {
}
sub _update {
- my ($self) = @_;
-
+ my ($self, $key) = @_;
+ my $opt_key = $key;
+ $opt_key = "all_keys" if (length($key // '') == 0);
my $config = $self->{config};
-
- if ($config->spiral_vase && !($config->perimeters == 1 && $config->top_solid_layers == 0 && $config->fill_density == 0 && $config->support_material == 0)) {
- my $dialog = Wx::MessageDialog->new($self,
- "The Spiral Vase mode requires:\n"
- . "- one perimeter\n"
- . "- no top solid layers\n"
- . "- 0% fill density\n"
- . "- no support material\n"
- . "\nShall I adjust those settings in order to enable Spiral Vase?",
- 'Spiral Vase', wxICON_WARNING | wxYES | wxNO);
- if ($dialog->ShowModal() == wxID_YES) {
- my $new_conf = Slic3r::Config->new;
- $new_conf->set("perimeters", 1);
- $new_conf->set("top_solid_layers", 0);
- $new_conf->set("fill_density", 0);
- $new_conf->set("support_material", 0);
- $self->_load_config($new_conf);
+
+ if (any { /$opt_key/ } qw(all_keys spiral_vase perimeters top_solid_layers fill_density support_material min_shell_thickness min_top_bottom_shell_thickness)) {
+ if ($config->spiral_vase && !($config->perimeters == 1 && $config->min_shell_thickness == 0 && $config->min_top_bottom_shell_thickness == 0 && $config->top_solid_layers == 0 && $config->fill_density == 0 && $config->support_material == 0)) {
+ my $dialog = Wx::MessageDialog->new($self,
+ "The Spiral Vase mode requires:\n"
+ . "- one perimeter\n"
+ . "- shell thickness to be 0\n"
+ . "- no top solid layers\n"
+ . "- 0% fill density\n"
+ . "- no support material\n"
+ . "\nShall I adjust those settings in order to enable Spiral Vase?",
+ 'Spiral Vase', wxICON_WARNING | wxYES | wxNO);
+ if ($dialog->ShowModal() == wxID_YES) {
+ my $new_conf = Slic3r::Config->new;
+ $new_conf->set("perimeters", 1);
+ $new_conf->set("min_shell_thickness", 0);
+ $new_conf->set("min_top_bottom_shell_thickness", 0);
+ $new_conf->set("top_solid_layers", 0);
+ $new_conf->set("fill_density", 0);
+ $new_conf->set("support_material", 0);
+ $self->_load_config($new_conf);
+ } else {
+ my $new_conf = Slic3r::Config->new;
+ $new_conf->set("spiral_vase", 0);
+ $self->_load_config($new_conf);
+ }
+ }
+ }
+
+ if (any { /$opt_key/ } qw(all_keys support_material)) {
+ if ($config->support_material) {
+ # Ask only once.
+ if (! $self->{support_material_overhangs_queried}) {
+ $self->{support_material_overhangs_queried} = 1;
+ if ($config->overhangs != 1) {
+ my $dialog = Wx::MessageDialog->new($self,
+ "Supports work better, if the following feature is enabled:\n"
+ . "- Detect bridging perimeters\n"
+ . "\nShall I adjust those settings for supports?",
+ 'Support Generator', wxICON_WARNING | wxYES | wxNO | wxCANCEL);
+ my $answer = $dialog->ShowModal();
+ my $new_conf = Slic3r::Config->new;
+ if ($answer == wxID_YES) {
+ # Enable "detect bridging perimeters".
+ $new_conf->set("overhangs", 1);
+ } elsif ($answer == wxID_NO) {
+ # Do nothing, leave supports on and "detect bridging perimeters" off.
+ } elsif ($answer == wxID_CANCEL) {
+ # Disable supports.
+ $new_conf->set("support_material", 0);
+ $self->{support_material_overhangs_queried} = 0;
+ }
+ $self->_load_config($new_conf);
+ }
+ }
} else {
+ $self->{support_material_overhangs_queried} = 0;
+ }
+ }
+
+ if (any { /$opt_key/ } qw(all_keys fill_density fill_pattern top_infill_pattern)) {
+ if ($config->fill_density == 100
+ && !first { $_ eq $config->fill_pattern } @{$Slic3r::Config::Options->{top_infill_pattern}{values}}) {
+ my $dialog = Wx::MessageDialog->new($self,
+ "The " . $config->fill_pattern . " infill pattern is not supposed to work at 100% density.\n"
+ . "\nShall I switch to rectilinear fill pattern?",
+ 'Infill', wxICON_WARNING | wxYES | wxNO);
+
my $new_conf = Slic3r::Config->new;
- $new_conf->set("spiral_vase", 0);
+ if ($dialog->ShowModal() == wxID_YES) {
+ $new_conf->set("fill_pattern", 'rectilinear');
+ } else {
+ $new_conf->set("fill_density", 40);
+ }
$self->_load_config($new_conf);
}
}
- if ($config->support_material) {
- # Ask only once.
- if (! $self->{support_material_overhangs_queried}) {
- $self->{support_material_overhangs_queried} = 1;
- if ($config->overhangs != 1) {
- my $dialog = Wx::MessageDialog->new($self,
- "Supports work better, if the following feature is enabled:\n"
- . "- Detect bridging perimeters\n"
- . "\nShall I adjust those settings for supports?",
- 'Support Generator', wxICON_WARNING | wxYES | wxNO | wxCANCEL);
- my $answer = $dialog->ShowModal();
- my $new_conf = Slic3r::Config->new;
- if ($answer == wxID_YES) {
- # Enable "detect bridging perimeters".
- $new_conf->set("overhangs", 1);
- } elsif ($answer == wxID_NO) {
- # Do nothing, leave supports on and "detect bridging perimeters" off.
- } elsif ($answer == wxID_CANCEL) {
- # Disable supports.
- $new_conf->set("support_material", 0);
- $self->{support_material_overhangs_queried} = 0;
- }
- $self->_load_config($new_conf);
- }
- }
- } else {
- $self->{support_material_overhangs_queried} = 0;
+ my $have_perimeters = ($config->perimeters > 0) || ($config->min_shell_thickness > 0);
+ if (any { /$opt_key/ } qw(all_keys perimeters)) {
+ $self->get_field($_)->toggle($have_perimeters)
+ for qw(extra_perimeters thin_walls overhangs seam_position external_perimeters_first
+ external_perimeter_extrusion_width
+ perimeter_speed small_perimeter_speed external_perimeter_speed);
}
-
- if ($config->fill_density == 100
- && !first { $_ eq $config->fill_pattern } @{$Slic3r::Config::Options->{top_infill_pattern}{values}}) {
- my $dialog = Wx::MessageDialog->new($self,
- "The " . $config->fill_pattern . " infill pattern is not supposed to work at 100% density.\n"
- . "\nShall I switch to rectilinear fill pattern?",
- 'Infill', wxICON_WARNING | wxYES | wxNO);
-
- my $new_conf = Slic3r::Config->new;
- if ($dialog->ShowModal() == wxID_YES) {
- $new_conf->set("fill_pattern", 'rectilinear');
- } else {
- $new_conf->set("fill_density", 40);
- }
- $self->_load_config($new_conf);
+
+ my $have_adaptive_slicing = $config->adaptive_slicing;
+ if (any { /$opt_key/ } qw(all_keys adaptive_slicing)) {
+ $self->get_field($_)->toggle($have_adaptive_slicing)
+ for qw(adaptive_slicing_quality match_horizontal_surfaces);
+ $self->get_field($_)->toggle(!$have_adaptive_slicing)
+ for qw(layer_height);
}
-
- my $have_perimeters = $config->perimeters > 0;
- $self->get_field($_)->toggle($have_perimeters)
- for qw(extra_perimeters thin_walls overhangs seam_position external_perimeters_first
- external_perimeter_extrusion_width
- perimeter_speed small_perimeter_speed external_perimeter_speed);
-
+
my $have_infill = $config->fill_density > 0;
- # infill_extruder uses the same logic as in Print::extruders()
- $self->get_field($_)->toggle($have_infill)
- for qw(fill_pattern infill_every_layers infill_only_where_needed solid_infill_every_layers
- solid_infill_below_area infill_extruder);
-
- my $have_solid_infill = ($config->top_solid_layers > 0) || ($config->bottom_solid_layers > 0);
- # solid_infill_extruder uses the same logic as in Print::extruders()
- $self->get_field($_)->toggle($have_solid_infill)
- for qw(top_infill_pattern bottom_infill_pattern infill_first solid_infill_extruder
- solid_infill_extrusion_width solid_infill_speed);
-
- $self->get_field($_)->toggle($have_infill || $have_solid_infill)
- for qw(fill_angle infill_extrusion_width infill_speed bridge_speed);
-
- $self->get_field('fill_gaps')->toggle($have_perimeters && $have_infill);
- $self->get_field('gap_fill_speed')->toggle($have_perimeters && $have_infill && $config->fill_gaps);
-
- my $have_top_solid_infill = $config->top_solid_layers > 0;
+ if (any { /$opt_key/ } qw(all_keys fill_density)) {
+ # infill_extruder uses the same logic as in Print::extruders()
+ $self->get_field($_)->toggle($have_infill)
+ for qw(fill_pattern infill_every_layers infill_only_where_needed solid_infill_every_layers
+ solid_infill_below_area infill_extruder);
+ }
+
+ my $have_solid_infill = ($config->top_solid_layers > 0) || ($config->bottom_solid_layers > 0) || ($config->min_top_bottom_shell_thickness > 0);
+ if (any { /$opt_key/ } qw(all_keys top_solid_layers bottom_solid_layers)) {
+ # solid_infill_extruder uses the same logic as in Print::extruders()
+ $self->get_field($_)->toggle($have_solid_infill)
+ for qw(top_infill_pattern bottom_infill_pattern infill_first solid_infill_extruder
+ solid_infill_extrusion_width solid_infill_speed);
+ }
+
+ if (any { /$opt_key/ } qw(all_keys top_solid_layers bottom_solid_layers fill_density)) {
+ $self->get_field($_)->toggle($have_infill || $have_solid_infill)
+ for qw(fill_angle infill_extrusion_width infill_speed bridge_speed);
+ }
+
+ if (any { /$opt_key/ } qw(all_keys fill_density perimeters)) {
+ $self->get_field('fill_gaps')->toggle($have_perimeters && $have_infill);
+ $self->get_field('gap_fill_speed')->toggle($have_perimeters && $have_infill && $config->fill_gaps);
+ }
+
+ my $have_top_solid_infill = ($config->top_solid_layers > 0) || ($config->min_top_bottom_shell_thickness > 0);
+
$self->get_field($_)->toggle($have_top_solid_infill)
for qw(top_infill_extrusion_width top_solid_infill_speed);
-
+
my $have_autospeed = any { $config->get("${_}_speed") eq '0' }
- qw(perimeter external_perimeter small_perimeter
- infill solid_infill top_solid_infill gap_fill support_material
- support_material_interface);
+ qw(perimeter external_perimeter small_perimeter
+ infill solid_infill top_solid_infill gap_fill support_material
+ support_material_interface);
$self->get_field('max_print_speed')->toggle($have_autospeed);
-
+
my $have_default_acceleration = $config->default_acceleration > 0;
$self->get_field($_)->toggle($have_default_acceleration)
for qw(perimeter_acceleration infill_acceleration bridge_acceleration first_layer_acceleration);
-
+
my $have_skirt = $config->skirts > 0 || $config->min_skirt_length > 0;
$self->get_field($_)->toggle($have_skirt)
for qw(skirt_distance skirt_height);
@@ -907,11 +951,21 @@ sub _update {
my $have_support_material = $config->support_material || $config->raft_layers > 0;
my $have_support_interface = $config->support_material_interface_layers > 0;
+ my $have_support_pillars = $have_support_material && $config->support_material_pattern eq 'pillars';
$self->get_field($_)->toggle($have_support_material)
for qw(support_material_threshold support_material_pattern
support_material_spacing support_material_angle
support_material_interface_layers dont_support_bridges
- support_material_extrusion_width support_material_contact_distance);
+ support_material_extrusion_width support_material_interface_extrusion_width
+ support_material_contact_distance);
+
+ # Disable features that need support to be enabled.
+ $self->get_field($_)->toggle($config->support_material)
+ for qw(support_material_max_layers);
+
+ # Only enable pillar configuration when using pillars
+ $self->get_field($_)->toggle($have_support_pillars)
+ for qw(support_material_pillar_size support_material_pillar_spacing);
$self->get_field($_)->toggle($have_support_material && $have_support_interface)
for qw(support_material_interface_spacing support_material_interface_extruder
@@ -1145,10 +1199,10 @@ sub _update {
$self->_update_description;
- my $cooling = $self->config->cooling;
+ my $cooling = $self->{config}->cooling;
$self->get_field($_)->toggle($cooling)
for qw(max_fan_speed fan_below_layer_time slowdown_below_layer_time min_print_speed);
- $self->get_field($_)->toggle($cooling || $self->config->fan_always_on)
+ $self->get_field($_)->toggle($cooling || $self->{config}->fan_always_on)
for qw(min_fan_speed disable_fan_first_layers);
}
@@ -1194,16 +1248,16 @@ sub options {
bed_shape z_offset z_steps_per_mm has_heatbed
gcode_flavor use_relative_e_distances
serial_port serial_speed
- octoprint_host octoprint_apikey
+ host_type print_host octoprint_apikey
use_firmware_retraction pressure_advance vibration_limit
use_volumetric_e
start_gcode end_gcode before_layer_gcode layer_gcode toolchange_gcode between_objects_gcode
- notes
- nozzle_diameter extruder_offset
+ nozzle_diameter extruder_offset min_layer_height max_layer_height
retract_length retract_lift retract_speed retract_restart_extra retract_before_travel retract_layer_change wipe
retract_length_toolchange retract_restart_extra_toolchange retract_lift_above retract_lift_below
printer_settings_id
printer_notes
+ use_set_and_wait_bed use_set_and_wait_extruder
);
}
@@ -1260,6 +1314,10 @@ sub build {
wxTheApp->CallAfter(sub {
$self->_extruders_count_changed($optgroup->get_value('extruders_count'));
});
+ } else {
+ wxTheApp->CallAfter(sub {
+ $self->_on_value_change($opt_id);
+ });
}
});
}
@@ -1297,14 +1355,16 @@ sub build {
$optgroup->append_line($line);
}
{
- my $optgroup = $page->new_optgroup('OctoPrint upload');
+ my $optgroup = $page->new_optgroup('Print server upload');
+
+ $optgroup->append_single_option_line('host_type');
- my $host_line = $optgroup->create_single_option_line('octoprint_host');
+ my $host_line = $optgroup->create_single_option_line('print_host');
$host_line->append_button("Browse…", "zoom.png", sub {
# look for devices
my $entries;
{
- my $res = Net::Bonjour->new('http');
+ my $res = Net::Bonjour->new('octoprint');
$res->discover;
$entries = [ $res->entries ];
}
@@ -1312,19 +1372,19 @@ sub build {
my $dlg = Slic3r::GUI::BonjourBrowser->new($self, $entries);
if ($dlg->ShowModal == wxID_OK) {
my $value = $dlg->GetValue . ":" . $dlg->GetPort;
- $self->config->set('octoprint_host', $value);
- $self->_on_value_change('octoprint_host');
+ $self->config->set('print_host', $value);
+ $self->_on_value_change('print_host');
}
} else {
Wx::MessageDialog->new($self, 'No Bonjour device found', 'Device Browser', wxOK | wxICON_INFORMATION)->ShowModal;
}
- }, undef, !eval "use Net::Bonjour; 1");
+ }, \$self->{print_host_browse_btn}, !eval "use Net::Bonjour; 1");
$host_line->append_button("Test", "wrench.png", sub {
my $ua = LWP::UserAgent->new;
$ua->timeout(10);
my $res = $ua->get(
- "http://" . $self->config->octoprint_host . "/api/version",
+ "http://" . $self->config->print_host . "/api/version",
'X-Api-Key' => $self->config->octoprint_apikey,
);
if ($res->is_success) {
@@ -1334,7 +1394,7 @@ sub build {
"I wasn't able to connect to OctoPrint (" . $res->status_line . "). "
. "Check hostname and OctoPrint version (at least 1.1.0 is required).");
}
- }, \$self->{octoprint_host_test_btn});
+ }, \$self->{print_host_test_btn});
$optgroup->append_line($host_line);
$optgroup->append_single_option_line('octoprint_apikey');
}
@@ -1350,6 +1410,8 @@ sub build {
$optgroup->append_single_option_line('pressure_advance');
$optgroup->append_single_option_line('vibration_limit');
$optgroup->append_single_option_line('z_steps_per_mm');
+ $optgroup->append_single_option_line('use_set_and_wait_extruder');
+ $optgroup->append_single_option_line('use_set_and_wait_bed');
}
}
{
@@ -1418,7 +1480,7 @@ sub build {
my $optgroup = $page->new_optgroup('Notes',
label_width => 0,
);
- my $option = $optgroup->get_option('notes');
+ my $option = $optgroup->get_option('printer_notes');
$option->full_width(1);
$option->height(250);
$optgroup->append_single_option_line($option);
@@ -1442,7 +1504,7 @@ sub _extruders_count_changed {
$self->_update;
}
-sub _extruder_options { qw(nozzle_diameter extruder_offset retract_length retract_lift retract_lift_above retract_lift_below retract_speed retract_restart_extra retract_before_travel wipe
+sub _extruder_options { qw(nozzle_diameter min_layer_height max_layer_height extruder_offset retract_length retract_lift retract_lift_above retract_lift_below retract_speed retract_restart_extra retract_before_travel wipe
retract_layer_change retract_length_toolchange retract_restart_extra_toolchange) }
sub _build_extruder_pages {
@@ -1471,6 +1533,11 @@ sub _build_extruder_pages {
my $optgroup = $page->new_optgroup('Size');
$optgroup->append_single_option_line('nozzle_diameter', $extruder_idx);
}
+ {
+ my $optgroup = $page->new_optgroup('Limits');
+ $optgroup->append_single_option_line($_, $extruder_idx)
+ for qw(min_layer_height max_layer_height);
+ }
{
my $optgroup = $page->new_optgroup('Position (for multi-extruder printers)');
$optgroup->append_single_option_line('extruder_offset', $extruder_idx);
@@ -1535,12 +1602,17 @@ sub _update {
$self->{serial_test_btn}->Disable;
}
}
- if ($config->get('octoprint_host') && eval "use LWP::UserAgent; 1") {
- $self->{octoprint_host_test_btn}->Enable;
- } else {
- $self->{octoprint_host_test_btn}->Disable;
+ if (($config->get('host_type') eq 'octoprint')) {
+ $self->{print_host_browse_btn}->Enable;
+ }else{
+ $self->{print_host_browse_btn}->Disable;
}
- $self->get_field('octoprint_apikey')->toggle($config->get('octoprint_host'));
+ if (($config->get('host_type') eq 'octoprint') && eval "use LWP::UserAgent; 1") {
+ $self->{print_host_test_btn}->Enable;
+ } else {
+ $self->{print_host_test_btn}->Disable;
+ }
+ $self->get_field('octoprint_apikey')->toggle($config->get('print_host'));
my $have_multiple_extruders = $self->{extruders_count} > 1;
$self->get_field('toolchange_gcode')->toggle($have_multiple_extruders);
@@ -1550,15 +1622,38 @@ sub _update {
# when using firmware retraction, firmware decides retraction length
$self->get_field('retract_length', $i)->toggle(!$config->use_firmware_retraction);
+ if ($config->use_firmware_retraction &&
+ ($config->gcode_flavor eq 'reprap' || $config->gcode_flavor eq 'repetier') &&
+ ($config->get_at('retract_length_toolchange', $i) > 0 || $config->get_at('retract_restart_extra_toolchange', $i) > 0)) {
+ my $dialog = Wx::MessageDialog->new($self,
+ "Retract length for toolchange on extruder " . $i . " is not available when using the Firmware Retraction mode.\n"
+ . "\nShall I disable it in order to enable Firmware Retraction?",
+ 'Firmware Retraction', wxICON_WARNING | wxYES | wxNO);
+
+ my $new_conf = Slic3r::Config->new;
+ if ($dialog->ShowModal() == wxID_YES) {
+ my $retract_length_toolchange = $config->retract_length_toolchange;
+ $retract_length_toolchange->[$i] = 0;
+ $new_conf->set("retract_length_toolchange", $retract_length_toolchange);
+ $new_conf->set("retract_restart_extra_toolchange", $retract_length_toolchange);
+ } else {
+ $new_conf->set("use_firmware_retraction", 0);
+ }
+ $self->_load_config($new_conf);
+ }
# user can customize travel length if we have retraction length or we're using
# firmware retraction
$self->get_field('retract_before_travel', $i)->toggle($have_retract_length || $config->use_firmware_retraction);
# user can customize other retraction options if retraction is enabled
+ # Firmware retract has Z lift built in.
my $retraction = ($have_retract_length || $config->use_firmware_retraction);
+ $self->get_field($_, $i)->toggle($retraction && !$config->use_firmware_retraction)
+ for qw(retract_lift);
+
$self->get_field($_, $i)->toggle($retraction)
- for qw(retract_lift retract_layer_change);
+ for qw(retract_layer_change);
# retract lift above/below only applies if using retract lift
$self->get_field($_, $i)->toggle($retraction && $config->get_at('retract_lift', $i) > 0)
@@ -1588,12 +1683,32 @@ sub _update {
}
$self->_load_config($new_conf);
}
-
- $self->get_field('retract_length_toolchange', $i)->toggle($have_multiple_extruders);
+
+ if ($config->use_firmware_retraction && $config->get_at('retract_lift', $i) > 0) {
+ my $dialog = Wx::MessageDialog->new($self,
+ "The Z Lift option is not available when using the Firmware Retraction mode.\n"
+ . "\nShall I disable it in order to enable Firmware Retraction?",
+ 'Firmware Retraction', wxICON_WARNING | wxYES | wxNO);
+
+ my $new_conf = Slic3r::Config->new;
+ if ($dialog->ShowModal() == wxID_YES) {
+ my $wipe = $config->retract_lift;
+ $wipe->[$i] = 0;
+ $new_conf->set("retract_lift", $wipe);
+ } else {
+ $new_conf->set("use_firmware_retraction", 0);
+ }
+ $self->_load_config($new_conf);
+ }
+
+ $self->get_field('retract_length_toolchange', $i)->toggle
+ ($have_multiple_extruders &&
+ !($config->use_firmware_retraction && ($config->gcode_flavor eq 'reprap' || $config->gcode_flavor eq 'repetier')));
my $toolchange_retraction = $config->get_at('retract_length_toolchange', $i) > 0;
$self->get_field('retract_restart_extra_toolchange', $i)->toggle
- ($have_multiple_extruders && $toolchange_retraction);
+ ($have_multiple_extruders && $toolchange_retraction &&
+ !($config->use_firmware_retraction && ($config->gcode_flavor eq 'reprap' || $config->gcode_flavor eq 'repetier')));
}
}
diff --git a/lib/Slic3r/GUI/Projector.pm b/lib/Slic3r/GUI/Projector.pm
index deb81a43e..9df9345db 100644
--- a/lib/Slic3r/GUI/Projector.pm
+++ b/lib/Slic3r/GUI/Projector.pm
@@ -50,7 +50,7 @@ sub new {
$self->config(Slic3r::Config->new_from_defaults(
qw(serial_port serial_speed bed_shape start_gcode end_gcode z_offset)
));
- $self->config->apply(wxTheApp->{mainframe}->{plater}->config);
+ $self->config->apply(wxTheApp->{mainframe}->{slaconfig});
my @optgroups = ();
{
@@ -559,6 +559,7 @@ sub BUILD {
{
my $print = Slic3r::SLAPrint->new(wxTheApp->{mainframe}->{plater}->{model});
$print->apply_config(wxTheApp->{mainframe}->{plater}->config);
+ $print->apply_config(wxTheApp->{mainframe}->{slaconfig});
$self->_print($print);
$self->screen->print($print);
diff --git a/lib/Slic3r/GUI/ReloadDialog.pm b/lib/Slic3r/GUI/ReloadDialog.pm
new file mode 100644
index 000000000..bb11bf089
--- /dev/null
+++ b/lib/Slic3r/GUI/ReloadDialog.pm
@@ -0,0 +1,60 @@
+# A tiny dialog to select how to reload an object that has additional parts or modifiers.
+
+package Slic3r::GUI::ReloadDialog;
+use strict;
+use warnings;
+use utf8;
+
+use Wx qw(:button :dialog :id :misc :sizer :choicebook wxTAB_TRAVERSAL);
+use Wx::Event qw(EVT_CLOSE);
+use base 'Wx::Dialog';
+
+sub new {
+ my $class = shift;
+ my ($parent,$default_selection) = @_;
+ my $self = $class->SUPER::new($parent, -1, "Additional parts and modifiers detected", wxDefaultPosition, [350,100], wxDEFAULT_DIALOG_STYLE);
+
+ # label
+ my $text = Wx::StaticText->new($self, -1, "Additional parts and modifiers are loaded in the current model. \n\nHow do you want to proceed?", wxDefaultPosition, wxDefaultSize);
+
+ # selector
+ $self->{choice} = my $choice = Wx::Choice->new($self, -1, wxDefaultPosition, wxDefaultSize, []);
+ $choice->Append("Reload all linked files");
+ $choice->Append("Reload main file, copy added parts & modifiers");
+ $choice->Append("Reload main file, discard added parts & modifiers");
+ $choice->SetSelection($default_selection);
+
+ # checkbox
+ $self->{checkbox} = my $checkbox = Wx::CheckBox->new($self, -1, "Don't ask again");
+
+ my $vsizer = Wx::BoxSizer->new(wxVERTICAL);
+ my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL);
+ $vsizer->Add($text, 0, wxEXPAND | wxALL, 10);
+ $vsizer->Add($choice, 0, wxEXPAND | wxALL, 10);
+ $hsizer->Add($checkbox, 1, wxEXPAND | wxALL, 10);
+ $hsizer->Add($self->CreateButtonSizer(wxOK | wxCANCEL), 0, wxEXPAND | wxALL, 10);
+ $vsizer->Add($hsizer, 0, wxEXPAND | wxALL, 0);
+
+ $self->SetSizer($vsizer);
+ $self->SetMinSize($self->GetSize);
+ $vsizer->SetSizeHints($self);
+
+ # needed to actually free memory
+ EVT_CLOSE($self, sub {
+ $self->EndModal(wxID_CANCEL);
+ $self->Destroy;
+ });
+
+ return $self;
+}
+
+sub GetSelection {
+ my ($self) = @_;
+ return $self->{choice}->GetSelection;
+}
+sub GetHideOnNext {
+ my ($self) = @_;
+ return $self->{checkbox}->GetValue;
+}
+
+1;
diff --git a/lib/Slic3r/GUI/SLAPrintOptions.pm b/lib/Slic3r/GUI/SLAPrintOptions.pm
index 289f4e5a1..01b705c88 100644
--- a/lib/Slic3r/GUI/SLAPrintOptions.pm
+++ b/lib/Slic3r/GUI/SLAPrintOptions.pm
@@ -14,6 +14,7 @@ sub new {
# Set some defaults
$self->config->set('infill_extrusion_width', 0.5) if $self->config->infill_extrusion_width == 0;
+ $self->config->set('support_material_extrusion_width', 1) if $self->config->support_material_extrusion_width == 0;
$self->config->set('perimeter_extrusion_width', 1) if $self->config->perimeter_extrusion_width == 0;
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
@@ -76,7 +77,7 @@ sub new {
$line->label('Pillars diameter');
my $opt = $line->get_options->[0];
$opt->sidetext('mm');
- $opt->tooltip('Diameter of the cylindrical support pillars.');
+ $opt->tooltip('Diameter of the cylindrical support pillars. 0.4mm and higher is supported.');
$optgroup->append_line($line);
}
}
@@ -107,6 +108,7 @@ sub _accept {
return;
}
+ wxTheApp->{mainframe}->{slaconfig}->apply_static($self->config);
$self->EndModal(wxID_OK);
$self->Close; # needed on Linux
diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm
index cba64323a..8f75e91ab 100644
--- a/lib/Slic3r/Model.pm
+++ b/lib/Slic3r/Model.pm
@@ -128,10 +128,18 @@ sub add_instance {
$new_instance->set_rotation($args{rotation})
if defined $args{rotation};
+ $new_instance->set_x_rotation($args{x_rotation})
+ if defined $args{x_rotation};
+ $new_instance->set_y_rotation($args{y_rotation})
+ if defined $args{y_rotation};
$new_instance->set_scaling_factor($args{scaling_factor})
if defined $args{scaling_factor};
+ $new_instance->set_scaling_vector($args{scaling_vector})
+ if defined $args{scaling_vector};
$new_instance->set_offset($args{offset})
if defined $args{offset};
+ $new_instance->set_z_translation($args{z_translation})
+ if defined $args{z_translation};
return $new_instance;
}
diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm
index 586bd8a8d..23799e8be 100644
--- a/lib/Slic3r/Print.pm
+++ b/lib/Slic3r/Print.pm
@@ -71,6 +71,34 @@ sub process {
}
}
+sub escaped_split {
+ my ($line) = @_;
+
+ # Free up three characters for temporary replacement
+ $line =~ s/%/%%/g;
+ $line =~ s/#/##/g;
+ $line =~ s/\?/\?\?/g;
+
+ # replace escaped !'s
+ $line =~ s/\!\!/%#\?/g;
+
+ # split on non-escaped whitespace
+ my @split = split /(?<=[^\!])\s+/, $line, -1;
+
+ for my $part (@split) {
+ # replace escaped whitespace with the whitespace
+ $part =~ s/\!(\s+)/$1/g;
+
+ # resub temp symbols
+ $part =~ s/%#\?/\!/g;
+ $part =~ s/%%/%/g;
+ $part =~ s/##/#/g;
+ $part =~ s/\?\?/\?/g;
+ }
+
+ return @split;
+}
+
sub export_gcode {
my $self = shift;
my %params = @_;
@@ -121,11 +149,14 @@ sub export_gcode {
$self->config->setenv;
for my $script (@{$self->config->post_process}) {
Slic3r::debugf " '%s' '%s'\n", $script, $output_file;
+ my @parsed_script = escaped_split $script;
+ my $executable = shift @parsed_script ;
+ push @parsed_script, $output_file;
# -x doesn't return true on Windows except for .exe files
- if (($^O eq 'MSWin32') ? !(-e $script) : !(-x $script)) {
- die "The configured post-processing script is not executable: check permissions. ($script)\n";
+ if (($^O eq 'MSWin32') ? !(-e $executable) : !(-x $executable)) {
+ die "The configured post-processing script is not executable: check permissions or escape whitespace/exclamation points. ($executable) \n";
}
- system($script, $output_file);
+ system($executable, @parsed_script);
}
}
}
diff --git a/lib/Slic3r/Print/GCode.pm b/lib/Slic3r/Print/GCode.pm
index ea4e9c2f8..84c5e6f75 100644
--- a/lib/Slic3r/Print/GCode.pm
+++ b/lib/Slic3r/Print/GCode.pm
@@ -72,9 +72,8 @@ sub export {
my @lt = localtime;
printf $fh "; generated by Slic3r $Slic3r::VERSION on %04d-%02d-%02d at %02d:%02d:%02d\n\n",
$lt[5] + 1900, $lt[4]+1, $lt[3], $lt[2], $lt[1], $lt[0];
- # Write notes (content of the Print Settings tab -> Notes)
- print $fh "; $_\n" foreach split /\R/, $self->config->notes;
- print $fh "\n" if $self->config->notes;
+ # Write notes (content of all Settings tabs -> Notes)
+ print $fh $gcodegen->notes;
# Write some terse information on the slicing parameters.
my $first_object = $self->objects->[0];
my $layer_height = $first_object->config->layer_height;
@@ -150,14 +149,22 @@ sub export {
}
# set extruder(s) temperature before and after start G-code
- $self->_print_first_layer_temperature(0)
- if $self->config->start_gcode !~ /M(?:109|104)/i;
- printf $fh "%s\n", $gcodegen->placeholder_parser->process($self->config->start_gcode);
+ my $include_start_extruder_temp = $self->config->start_gcode !~ /M(?:109|104)/i;
foreach my $start_gcode (@{ $self->config->start_filament_gcode }) { # process filament gcode in order
- printf $fh "%s\n", $gcodegen->placeholder_parser->process($start_gcode);
+ $include_start_extruder_temp = $include_start_extruder_temp && ($start_gcode !~ /M(?:109|104)/i);
+ }
+ my $include_end_extruder_temp = $self->config->end_gcode !~ /M(?:109|104)/i;
+ foreach my $end_gcode (@{ $self->config->end_filament_gcode }) { # process filament gcode in order
+ $include_end_extruder_temp = $include_end_extruder_temp && ($end_gcode !~ /M(?:109|104)/i);
+ }
+ $self->_print_first_layer_temperature(0)
+ if $include_start_extruder_temp;
+ printf $fh "%s\n", Slic3r::ConditionalGCode::apply_math($gcodegen->placeholder_parser->process($self->config->start_gcode));
+ foreach my $start_gcode (@{ $self->config->start_filament_gcode }) { # process filament gcode in order
+ printf $fh "%s\n", Slic3r::ConditionalGCode::apply_math($gcodegen->placeholder_parser->process($start_gcode));
}
$self->_print_first_layer_temperature(1)
- if $self->config->start_gcode !~ /M(?:109|104)/i;
+ if $include_start_extruder_temp;
# set other general things
print $fh $gcodegen->preamble;
@@ -220,7 +227,7 @@ sub export {
if ($self->config->complete_objects) {
# print objects from the smallest to the tallest to avoid collisions
# when moving onto next object starting point
- my @obj_idx = sort { $self->objects->[$a]->size->z <=> $self->objects->[$b]->size->z } 0..($self->print->object_count - 1);
+ my @obj_idx = sort { $self->objects->[$a]->config->sequential_print_priority <=> $self->objects->[$b]->config->sequential_print_priority or $self->objects->[$a]->size->z <=> $self->objects->[$b]->size->z} 0..($self->print->object_count - 1);
my $finished_objects = 0;
for my $obj_idx (@obj_idx) {
@@ -257,9 +264,9 @@ sub export {
&& $self->config->between_objects_gcode !~ /M(?:190|140)/i;
$self->_print_first_layer_temperature(0)
if $self->config->between_objects_gcode !~ /M(?:109|104)/i;
- printf $fh "%s\n", $gcodegen->placeholder_parser->process($self->config->between_objects_gcode);
+ printf $fh "%s\n", Slic3r::ConditionalGCode::apply_math($gcodegen->placeholder_parser->process($self->config->between_objects_gcode));
}
- $self->process_layer($layer, [$copy]);
+ $self->process_layer($obj_idx, $layer, [$copy]);
}
$self->flush_filters;
$finished_objects++;
@@ -284,7 +291,7 @@ sub export {
foreach my $print_z (sort { $a <=> $b } keys %layers) {
foreach my $obj_idx (@obj_idx) {
foreach my $layer (@{ $layers{$print_z}[$obj_idx] // [] }) {
- $self->process_layer($layer, $layer->object->_shifted_copies);
+ $self->process_layer($obj_idx, $layer, $layer->object->_shifted_copies);
}
}
}
@@ -295,9 +302,17 @@ sub export {
print $fh $gcodegen->retract; # TODO: process this retract through PressureRegulator in order to discharge fully
print $fh $gcodegen->writer->set_fan(0);
foreach my $end_gcode (@{ $self->config->end_filament_gcode }) { # Process filament-specific gcode in extruder order.
- printf $fh "%s\n", $gcodegen->placeholder_parser->process($end_gcode);
+ printf $fh "%s\n", Slic3r::ConditionalGCode::apply_math($gcodegen->placeholder_parser->process($end_gcode));
}
- printf $fh "%s\n", $gcodegen->placeholder_parser->process($self->config->end_gcode);
+ printf $fh "%s\n", Slic3r::ConditionalGCode::apply_math($gcodegen->placeholder_parser->process($self->config->end_gcode));
+
+ $self->_print_off_temperature(0)
+ if $include_end_extruder_temp;
+ # set bed temperature
+ if (($self->config->has_heatbed) && $self->config->end_gcode !~ /M(?:190|140)/i) {
+ printf $fh $gcodegen->writer->set_bed_temperature(0, 0);
+ }
+
print $fh $gcodegen->writer->update_progress($gcodegen->layer_count, $gcodegen->layer_count, 1); # 100%
print $fh $gcodegen->writer->postamble;
@@ -353,12 +368,22 @@ sub _print_first_layer_temperature {
}
}
+sub _print_off_temperature {
+ my ($self, $wait) = @_;
+
+ for my $t (@{$self->print->extruders}) {
+ printf {$self->fh} $self->_gcodegen->writer->set_temperature(0, $wait, $t)
+ }
+}
+
+
+
# Called per object's layer.
# First a $gcode string is collected,
# then filtered and finally written to a file $fh.
sub process_layer {
my $self = shift;
- my ($layer, $object_copies) = @_;
+ my ($obj_idx, $layer, $object_copies) = @_;
my $gcode = "";
my $object = $layer->object;
@@ -445,14 +470,16 @@ sub process_layer {
my $pp = $self->_gcodegen->placeholder_parser->clone;
$pp->set('layer_num' => $self->_gcodegen->layer_index + 1);
$pp->set('layer_z' => $layer->print_z);
- $gcode .= $pp->process($self->print->config->before_layer_gcode) . "\n";
+ $pp->set('current_retraction' => $self->_gcodegen->writer->extruder->retracted);
+ $gcode .= Slic3r::ConditionalGCode::apply_math($pp->process($self->print->config->before_layer_gcode) . "\n");
}
$gcode .= $self->_gcodegen->change_layer($layer->as_layer); # this will increase $self->_gcodegen->layer_index
if ($self->print->config->layer_gcode) {
my $pp = $self->_gcodegen->placeholder_parser->clone;
$pp->set('layer_num' => $self->_gcodegen->layer_index);
$pp->set('layer_z' => $layer->print_z);
- $gcode .= $pp->process($self->print->config->layer_gcode) . "\n";
+ $pp->set('current_retraction' => $self->_gcodegen->writer->extruder->retracted);
+ $gcode .= Slic3r::ConditionalGCode::apply_math($pp->process($self->print->config->layer_gcode) . "\n");
}
# extrude skirt along raft layers and normal object layers
@@ -517,7 +544,11 @@ sub process_layer {
$self->_gcodegen->avoid_crossing_perimeters->set_disable_once(1);
}
+ my $copy_idx = 0;
for my $copy (@$object_copies) {
+ if ($self->config->label_printed_objects) {
+ $gcode .= "; printing object " . $object->model_object()->name . " id:" . $obj_idx . " copy " . $copy_idx . "\n";
+ }
# when starting a new object, use the external motion planner for the first travel move
$self->_gcodegen->avoid_crossing_perimeters->set_use_external_mp_once(1) if ($self->_last_obj_copy // '') ne "$copy";
$self->_last_obj_copy("$copy");
@@ -644,6 +675,10 @@ sub process_layer {
}
}
}
+ if ($self->config->label_printed_objects) {
+ $gcode .= "; stop printing object " . $object->model_object()->name . " id:" . $obj_idx . " copy " . $copy_idx . "\n";
+ }
+ $copy_idx = $copy_idx + 1;
}
# apply spiral vase post-processing if this layer contains suitable geometry
diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm
index fa15cc528..be1217167 100644
--- a/lib/Slic3r/Print/Object.pm
+++ b/lib/Slic3r/Print/Object.pm
@@ -3,6 +3,7 @@ package Slic3r::Print::Object;
use strict;
use warnings;
+use POSIX;
use List::Util qw(min max sum first any);
use Slic3r::Flow ':roles';
use Slic3r::Geometry qw(X Y Z PI scale unscale chained_path epsilon);
@@ -48,9 +49,9 @@ sub slice {
return if $self->step_done(STEP_SLICE);
$self->set_step_started(STEP_SLICE);
$self->print->status_cb->(10, "Processing triangulated mesh");
-
+
$self->_slice;
-
+
# detect slicing errors
my $warning_thrown = 0;
for my $i (0 .. ($self->layer_count - 1)) {
@@ -387,10 +388,21 @@ sub discover_horizontal_shells {
];
next if !@$solid;
Slic3r::debugf "Layer %d has %s surfaces\n", $i, ($type == S_TYPE_TOP) ? 'top' : 'bottom';
-
+
my $solid_layers = ($type == S_TYPE_TOP)
? $layerm->region->config->top_solid_layers
: $layerm->region->config->bottom_solid_layers;
+
+ if ($layerm->region->config->min_top_bottom_shell_thickness > 0) {
+ my $current_shell_thickness = $solid_layers * $self->get_layer($i)->height;
+ my $minimum_shell_thickness = $layerm->region->config->min_top_bottom_shell_thickness;
+
+ while ($minimum_shell_thickness - $current_shell_thickness > epsilon) {
+ $solid_layers++;
+ $current_shell_thickness = $solid_layers * $self->get_layer($i)->height;
+ }
+ }
+
NEIGHBOR: for (my $n = ($type == S_TYPE_TOP) ? $i-1 : $i+1;
abs($n - $i) <= $solid_layers-1;
($type == S_TYPE_TOP) ? $n-- : $n++) {
@@ -670,11 +682,16 @@ sub support_material_flow {
my $extruder = ($role == FLOW_ROLE_SUPPORT_MATERIAL)
? $self->config->support_material_extruder
: $self->config->support_material_interface_extruder;
+
+ my $width = $self->config->support_material_extrusion_width || $self->config->extrusion_width;
+ if ($role == FLOW_ROLE_SUPPORT_MATERIAL_INTERFACE) {
+ $width = $self->config->support_material_interface_extrusion_width || $width;
+ }
# we use a bogus layer_height because we use the same flow for all
# support material layers
return Slic3r::Flow->new_from_width(
- width => $self->config->support_material_extrusion_width || $self->config->extrusion_width,
+ width => $width,
role => $role,
nozzle_diameter => $self->print->config->nozzle_diameter->[$extruder-1] // $self->print->config->nozzle_diameter->[0],
layer_height => $self->config->layer_height,
diff --git a/lib/Slic3r/Print/Simple.pm b/lib/Slic3r/Print/Simple.pm
index 26f1d4383..4febfa6ab 100644
--- a/lib/Slic3r/Print/Simple.pm
+++ b/lib/Slic3r/Print/Simple.pm
@@ -91,7 +91,8 @@ sub set_model {
# if all input objects have defined position(s) apply duplication to the whole model
$model->duplicate($self->duplicate, $self->_print->config->min_object_distance, $bb);
}
- $_->translate(0,0,-$_->bounding_box->z_min) for @{$model->objects};
+ $_->translate(0,0,-$_->bounding_box->z_min) for @{$model->objects} ;
+
if (!$self->dont_arrange) {
my $print_center = $self->print_center
diff --git a/lib/Slic3r/Print/State.pm b/lib/Slic3r/Print/State.pm
index d242e3760..91d4a6ef9 100644
--- a/lib/Slic3r/Print/State.pm
+++ b/lib/Slic3r/Print/State.pm
@@ -5,7 +5,7 @@ use warnings;
require Exporter;
our @ISA = qw(Exporter);
-our @EXPORT_OK = qw(STEP_SLICE STEP_PERIMETERS STEP_PREPARE_INFILL
+our @EXPORT_OK = qw(STEP_LAYERS STEP_SLICE STEP_PERIMETERS STEP_PREPARE_INFILL
STEP_INFILL STEP_SUPPORTMATERIAL STEP_SKIRT STEP_BRIM);
our %EXPORT_TAGS = (steps => \@EXPORT_OK);
diff --git a/lib/Slic3r/Print/SupportMaterial.pm b/lib/Slic3r/Print/SupportMaterial.pm
index 4c25dace2..f20a79bc9 100644
--- a/lib/Slic3r/Print/SupportMaterial.pm
+++ b/lib/Slic3r/Print/SupportMaterial.pm
@@ -22,10 +22,6 @@ use constant DEBUG_CONTACT_ONLY => 0;
# increment used to reach MARGIN in steps to avoid trespassing thin objects
use constant MARGIN_STEP => MARGIN/3;
-# generate a tree-like structure to save material
-use constant PILLAR_SIZE => 2.5;
-use constant PILLAR_SPACING => 10;
-
sub generate {
# $object is Slic3r::Print::Object
my ($self, $object) = @_;
@@ -133,6 +129,8 @@ sub contact_area {
last;
}
my $layer = $object->get_layer($layer_id);
+ last if $conf->support_material_max_layers
+ && $layer_id > $conf->support_material_max_layers;
if ($buildplate_only) {
# Collect the top surfaces up to this layer and merge them.
@@ -179,8 +177,8 @@ sub contact_area {
}
$diff = diff(
- offset([ map $_->p, @{$layerm->slices} ], -$d),
- [ map @$_, @{$lower_layer->slices} ],
+ [ map $_->p, @{$layerm->slices} ],
+ offset([ map @$_, @{$lower_layer->slices} ], +$d),
);
# only enforce spacing from the object ($fw/2) if the threshold angle
@@ -753,7 +751,9 @@ sub generate_toolpaths {
# interface and contact infill
if (@$interface || @$contact_infill) {
- $fillers{interface}->set_angle(deg2rad($interface_angle));
+ # make interface layers alternate angles by 90 degrees
+ my $alternate_angle = $interface_angle + (90 * (($layer_id + 1) % 2));
+ $fillers{interface}->set_angle(deg2rad($alternate_angle));
$fillers{interface}->set_min_spacing($_interface_flow->spacing);
# find centerline of the external loop
@@ -907,8 +907,8 @@ sub generate_pillars_shape {
# this prevents supplying an empty point set to BoundingBox constructor
return if !%$contact;
- my $pillar_size = scale PILLAR_SIZE;
- my $pillar_spacing = scale PILLAR_SPACING;
+ my $pillar_size = scale $self->object_config->support_material_pillar_size;
+ my $pillar_spacing = scale $self->object_config->support_material_pillar_spacing;
my $grid; # arrayref of polygons
{
diff --git a/lib/Slic3r/Test.pm b/lib/Slic3r/Test.pm
index 8f0b203e1..f167e30aa 100644
--- a/lib/Slic3r/Test.pm
+++ b/lib/Slic3r/Test.pm
@@ -147,6 +147,16 @@ sub mesh {
$facets = [
[0,1,2],[1,3,2],[3,4,5],[2,3,5],[6,0,2],[6,2,7],[5,8,7],[5,7,2],[9,10,8],[9,8,5],[9,0,6],[9,6,10],[9,11,1],[9,1,0],[3,1,11],[4,3,11],[5,11,9],[5,4,11],[12,10,6],[12,6,13],[6,7,14],[13,6,14],[7,8,15],[14,7,15],[15,8,10],[15,10,12],[12,13,14],[12,14,15]
];
+ } elsif ($name eq 'slopy_cube') {
+ $vertices = [
+ [-10,-10,0], [-10,-10,20], [-10,10,0], [-10,10,20], [0,-10,10], [10,-10,0], [2.92893,-10,10], [10,-10,2.92893],
+ [0,-10,20], [10,10,0], [0,10,10], [0,10,20], [2.92893,10,10], [10,10,2.92893],
+ ];
+ $facets = [
+ [0,1,2], [2,1,3], [4,0,5], [4,1,0], [6,4,7], [7,4,5], [4,8,1], [0,2,5], [5,2,9], [2,10,9], [10,3,11],
+ [2,3,10], [9,10,12], [13,9,12], [3,1,8], [11,3,8], [10,11,8], [4,10,8], [6,12,10], [4,6,10],
+ [7,13,12], [6,7,12], [7,5,9], [13,7,9],
+ ];
} else {
return undef;
}
diff --git a/package/common/coreperl b/package/common/coreperl
new file mode 100644
index 000000000..ac6effc2c
--- /dev/null
+++ b/package/common/coreperl
@@ -0,0 +1,21 @@
+# Core perl modules used in Slic3r
+attributes
+base
+bytes
+B
+POSIX
+FindBin
+Unicode::Normalize
+Tie::Handle
+Time::Local
+Math::Trig
+IO::Socket
+Errno
+Storable
+lib
+overload
+warnings
+local::lib
+strict
+utf8
+parent
diff --git a/package/common/util.sh b/package/common/util.sh
old mode 100644
new mode 100755
diff --git a/package/deploy/sftp-symlink.sh b/package/deploy/sftp-symlink.sh
new file mode 100755
index 000000000..d88c0638d
--- /dev/null
+++ b/package/deploy/sftp-symlink.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+# Prerequisites
+# Environment Variables:
+# UPLOAD_USER - user to upload to sftp server
+# KEY is assumed to be path to a ssh key for UPLOAD_USER
+
+DIR=$1
+shift
+KEY=$1
+shift
+EXT=$1
+shift
+FILES=$*
+
+source $(dirname $0)/../common/util.sh
+set_pr_id
+set_branch
+if [ ! -z ${PR_ID+x} ] || [ $current_branch != "master" ]; then
+ DIR=${DIR}/branches
+fi
+
+if [ -s $KEY ]; then
+ for i in $FILES; do
+ filepath=$(readlink -f "$i")
+ filepath=$(basename $filepath)
+ tmpfile=$(mktemp)
+
+ echo "rm Slic3r-${current_branch}-latest.${EXT}" | sftp -i$KEY "${UPLOAD_USER}@dl.slic3r.org:$DIR/"
+ echo "symlink $filepath Slic3r-${current_branch}-latest.${EXT} " > $tmpfile
+ sftp -b $tmpfile -i$KEY "${UPLOAD_USER}@dl.slic3r.org:$DIR/"
+ result=$?
+ if [ $? -eq 1 ]; then
+ echo "Error with SFTP symlink"
+ exit $result;
+ fi
+ done
+else
+ echo "$KEY is not available, not symlinking."
+fi
+exit $result
diff --git a/package/deploy/sftp.sh b/package/deploy/sftp.sh
index 3fc786fc3..14ffa6aab 100755
--- a/package/deploy/sftp.sh
+++ b/package/deploy/sftp.sh
@@ -19,8 +19,16 @@ fi
if [ -s $KEY ]; then
for i in $FILES; do
filepath=$(readlink -f "$i")
- echo put $filepath | sftp -i$KEY "${UPLOAD_USER}@dl.slic3r.org:$DIR/"
+ tmpfile=$(mktemp)
+ echo put $filepath > $tmpfile
+ sftp -b $tmpfile -i$KEY "${UPLOAD_USER}@dl.slic3r.org:$DIR/"
+ result=$?
+ if [ $? -eq 1 ]; then
+ echo "Error with SFTP"
+ exit $result;
+ fi
done
else
echo "$KEY is not available, not deploying."
fi
+exit $result
diff --git a/package/linux/appimage-bundler.sh b/package/linux/appimage-bundler.sh
deleted file mode 100755
index d12cb2129..000000000
--- a/package/linux/appimage-bundler.sh
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/bin/bash
-
-WD=$(dirname $0)
-source $(dirname $0)/../common/util.sh
-set_version
-set_app_name
-LOWERAPP=${appname,,}
-wget -q https://github.com/probonopd/AppImages/raw/master/functions.sh -O ./functions.sh
-. ./functions.sh
-
-srcfolder="$WD/${appname}"
-if [ ! -e $srcfolder ]; then
- echo "Run make_archive first."
- exit 1
-fi
-
-cd $srcfolder
-
-# make shell exec and call it Slic3r
-
-mkdir -p /usr/{lib,bin}
-mv -R Slic3r local-lib var perl-local slic3r.pl /usr/bin
-mv -R bin/* /usr/lib
-rm -rf bin
-
-get_apprun
-
-# Copy desktop and icon file to application root dir for Apprun to pick them up.
-sed -e "s|SLIC3R_VERSION|$SLIC3R_VERSION|" -e"s|APPLICATION_NAME|$appname|" ../slic3r.desktop.in > ../slic3r.desktop
-cp ../slic3r.desktop $LOWERAPP.desktop
-cp ./var/Slic3r_192px_transparent.png ./slic3r.png
-
-# archive directory has everything we need.
-delete_blacklisted
-
-get_desktopintegration $LOWERAPP
-
-GLIBC_NEEDED=$(glibc_needed)
-VERSION=git$GIT_REV-glibc$GLIBC_NEEDED
-
-cd ..
-mkdir -p out
-generate_appimage
-
-transfer ../out/*
diff --git a/package/linux/appimage.sh b/package/linux/appimage.sh
new file mode 100755
index 000000000..3a68b5e17
--- /dev/null
+++ b/package/linux/appimage.sh
@@ -0,0 +1,100 @@
+#!/usr/bin/env bash
+
+########################################################################
+# Package the binaries built on Travis-CI as an AppImage
+# By Joseph Lenox 2017
+# For more information, see http://appimage.org/
+# Assumes that the results from make_archive are present.
+########################################################################
+
+source $(dirname $0)/../common/util.sh
+
+set_version
+get_commit
+set_build_id
+set_branch
+set_app_name
+set_pr_id
+
+WD=${PWD}/$(dirname $0)
+srcfolder="$WD/${appname}"
+export ARCH=$(arch)
+
+APP=Slic3r
+LOWERAPP=${APP,,}
+
+
+mkdir -p $WD/${APP}.AppDir/usr/
+wget -q https://github.com/probonopd/AppImages/raw/master/functions.sh -O ./functions.sh
+. ./functions.sh
+
+cd $WD/${APP}.AppDir
+
+mkdir -p $WD/${APP}.AppDir/usr/bin
+# Copy primary Slic3r script here and perl-local, as well as var
+for i in {var,slic3r.pl,perl-local}; do
+ cp -R $srcfolder/$i $WD/${APP}.AppDir/usr/bin/
+done
+
+mkdir -p ${WD}/${APP}.AppDir/usr/lib
+# copy Slic3r local-lib here
+for i in $(ls $srcfolder/bin); do
+ install -v $srcfolder/bin/$i ${WD}/${APP}.AppDir/usr/lib
+done
+
+# copy other libraries needed to /usr/lib because this is an AppImage build.
+for i in $(cat $WD/libpaths.appimage.txt | grep -v "^#" | awk -F# '{print $1}'); do
+ install -v $i ${WD}/${APP}.AppDir/usr/lib
+done
+
+
+cp -R $srcfolder/local-lib ${WD}/${APP}.AppDir/usr/lib/local-lib
+
+cat > $WD/${APP}.AppDir/AppRun << 'EOF'
+#!/usr/bin/env bash
+# some magic to find out the real location of this script dealing with symlinks
+DIR=`readlink "$0"` || DIR="$0";
+DIR=`dirname "$DIR"`;
+cd "$DIR"
+DIR=`pwd`
+cd - > /dev/null
+# disable parameter expansion to forward all arguments unprocessed to the VM
+set -f
+# run the VM and pass along all arguments as is
+LD_LIBRARY_PATH="$DIR/usr/lib" "${DIR}/usr/bin/perl-local" -I"${DIR}/usr/lib/local-lib/lib/perl5" "${DIR}/usr/bin/slic3r.pl" --gui "$@"
+EOF
+
+chmod +x AppRun
+
+cp ${WD}/${APP}.AppDir/usr/bin/var/Slic3r_192px_transparent.png $WD/${APP}.AppDir/${APP}.png
+
+cat > $WD/${APP}.AppDir/${APP}.desktop <> $plistfile
CFBundleVersion
${SLIC3R_BUILD_ID}
CFBundleDocumentTypes
-
-
- CFBundleTypeExtensions
-
- stl
- STL
-
- CFBundleTypeIconFile
- Slic3r.icns
- CFBundleTypeName
- STL
- CFBundleTypeRole
- Viewer
- LISsAppleDefaultForType
-
- LSHandlerRank
- Alternate
-
-
- CFBundleTypeExtensions
-
- obj
- OBJ
-
- CFBundleTypeIconFile
- Slic3r.icns
- CFBundleTypeName
- STL
- CFBundleTypeRole
- Viewer
- LISsAppleDefaultForType
-
- LSHandlerRank
- Alternate
-
-
- CFBundleTypeExtensions
-
- amf
- AMF
-
- CFBundleTypeIconFile
- Slic3r.icns
- CFBundleTypeName
- STL
- CFBundleTypeRole
- Viewer
- LISsAppleDefaultForType
-
- LSHandlerRank
- Alternate
-
-
- LSMinimumSystemVersion
- 10.7
+
+
+ CFBundleTypeExtensions
+
+ stl
+ STL
+
+ CFBundleTypeIconFile
+ stl.icns
+ CFBundleTypeName
+ STL
+ CFBundleTypeRole
+ Viewer
+ LISsAppleDefaultForType
+
+ LSHandlerRank
+ Alternate
+
+
+ CFBundleTypeExtensions
+
+ obj
+ OBJ
+
+ CFBundleTypeIconFile
+ Slic3r.icns
+ CFBundleTypeName
+ STL
+ CFBundleTypeRole
+ Viewer
+ LISsAppleDefaultForType
+
+ LSHandlerRank
+ Alternate
+
+
+ CFBundleTypeExtensions
+
+ amf
+ AMF
+
+ CFBundleTypeIconFile
+ Slic3r.icns
+ CFBundleTypeName
+ STL
+ CFBundleTypeRole
+ Viewer
+ LISsAppleDefaultForType
+
+ LSHandlerRank
+ Alternate
+
+
+ CFBundleTypeExtensions
+
+ gcode
+ GCODE
+
+ CFBundleTypeIconFile
+ gcode.icns
+ CFBundleTypeName
+ GCODE
+ CFBundleTypeRole
+ Editor
+ LISsAppleDefaultForType
+
+ LSHandlerRank
+ Alternate
+
+
+ LSMinimumSystemVersion
+ 10.7
NSPrincipalClass
NSApplication
NSHighResolutionCapable
-
+
EOF
diff --git a/package/osx/startup_script.sh b/package/osx/startup_script.sh
old mode 100644
new mode 100755
diff --git a/package/win/package_win32.ps1 b/package/win/package_win32.ps1
index 104681eee..27af2e270 100644
--- a/package/win/package_win32.ps1
+++ b/package/win/package_win32.ps1
@@ -89,7 +89,7 @@ if (!( (Test-Path -Path "${scriptDir}\slic3r.exe") -And (Test-Path -Path "${scri
}
# remove all static libraries, they just take up space.
-if ${env:APPVEYOR} {
+if ($env:APPVEYOR) {
gci ${scriptDir}\..\..\ -recurse | ? {$_.Name -match ".*\.a$"} | ri
gci -recurse ${scriptDir}\..\..\local-lib | ? {$_.PSIsContainer -And $_.Name -match "DocView|IPC|DataView|Media|Ribbon|Calendar|STC|PerlTest|WebView"} | ri
gci -recurse ${scriptDir}\..\..\local-lib| ? {$_.Name -match ".*(webview|ribbon|stc).*\.dll"} | ri
diff --git a/share/locale/ja/slic3r.mo b/share/locale/ja/slic3r.mo
new file mode 100644
index 000000000..2f7d7b8bd
Binary files /dev/null and b/share/locale/ja/slic3r.mo differ
diff --git a/slic3r.pl b/slic3r.pl
index 77b87c3ff..0aeba3cf7 100755
--- a/slic3r.pl
+++ b/slic3r.pl
@@ -29,6 +29,7 @@ my %cli_options = ();
'debug' => \$Slic3r::debug,
'gui' => \$opt{gui},
+ 'no-gui' => \$opt{no_gui},
'o|output=s' => \$opt{output},
'j|threads=i' => \$opt{threads},
@@ -105,7 +106,7 @@ if ($opt{save}) {
# launch GUI
my $gui;
-if ((!@ARGV || $opt{gui}) && !$opt{save} && eval "require Slic3r::GUI; 1") {
+if ((!@ARGV || $opt{gui}) && !$opt{no_gui} && !$opt{save} && eval "require Slic3r::GUI; 1") {
{
no warnings 'once';
$Slic3r::GUI::datadir = Slic3r::decode_path($opt{datadir} // '');
@@ -125,7 +126,7 @@ if ((!@ARGV || $opt{gui}) && !$opt{save} && eval "require Slic3r::GUI; 1") {
$gui->MainLoop;
exit;
}
-die $@ if $@ && $opt{gui};
+die $@ if $@ && $opt{gui} && !$opt{no_gui};
if (@ARGV) { # slicing from command line
# apply command line config on top of default config
@@ -336,6 +337,8 @@ $j
GUI options:
--gui Forces the GUI launch instead of command line slicing (if you
supply a model file, it will be loaded into the plater)
+ --no-gui Forces the command line slicing instead of gui.
+ This takes precedence over --gui if both are present.
--autosave Automatically export current configuration to the specified file
Output options:
@@ -436,6 +439,7 @@ $j
--perimeters Number of perimeters/horizontal skins (range: 0+, default: $config->{perimeters})
--top-solid-layers Number of solid layers to do for top surfaces (range: 0+, default: $config->{top_solid_layers})
--bottom-solid-layers Number of solid layers to do for bottom surfaces (range: 0+, default: $config->{bottom_solid_layers})
+ --min-shell-thickness Minimum thickness of all solid shells (range: 0+, default: 0)
--solid-layers Shortcut for setting the two options above at once
--fill-density Infill density (range: 0%-100%, default: $config->{fill_density}%)
--fill-angle Infill angle in degrees (range: 0-90, default: $config->{fill_angle})
@@ -483,6 +487,10 @@ $j
Pattern to use for support material (default: $config->{support_material_pattern})
--support-material-spacing
Spacing between pattern lines (mm, default: $config->{support_material_spacing})
+ --support-material-pillar-size
+ Size of the pillars in the pillar support pattern (default: $config->{support_material_pillar_size})
+ --support-material-pillar-spacing
+ Spacing between the pillars in the pillar support pattern (default: $config->{support_material_pillar_spacing})
--support-material-angle
Support material angle in degrees (range: 0-90, default: $config->{support_material_angle})
--support-material-contact-distance
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 14f4a657a..ee67ee8e0 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -31,6 +31,7 @@ include_directories(${LIBDIR}/libslic3r)
include_directories(${LIBDIR}/Slic3r/GUI/)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/standalone/)
include_directories(${LIBDIR}/admesh/)
+include_directories(${LIBDIR}/BSpline/)
include_directories(${LIBDIR}/expat/)
include_directories(${LIBDIR}/poly2tri/)
include_directories(${LIBDIR}/poly2tri/sweep)
@@ -52,6 +53,7 @@ add_library(libslic3r STATIC
${LIBDIR}/libslic3r/Fill/FillHoneycomb.cpp
${LIBDIR}/libslic3r/Fill/FillPlanePath.cpp
${LIBDIR}/libslic3r/Fill/FillRectilinear.cpp
+ ${LIBDIR}/libslic3r/Fill/FillGyroid.cpp
${LIBDIR}/libslic3r/Flow.cpp
${LIBDIR}/libslic3r/GCode.cpp
${LIBDIR}/libslic3r/GCode/CoolingBuffer.cpp
@@ -63,9 +65,11 @@ add_library(libslic3r STATIC
${LIBDIR}/libslic3r/Geometry.cpp
${LIBDIR}/libslic3r/IO.cpp
${LIBDIR}/libslic3r/IO/AMF.cpp
+ ${LIBDIR}/libslic3r/IO/TMF.cpp
${LIBDIR}/libslic3r/Layer.cpp
${LIBDIR}/libslic3r/LayerRegion.cpp
${LIBDIR}/libslic3r/LayerRegionFill.cpp
+ ${LIBDIR}/libslic3r/LayerHeightSpline.cpp
${LIBDIR}/libslic3r/Line.cpp
${LIBDIR}/libslic3r/Model.cpp
${LIBDIR}/libslic3r/MotionPlanner.cpp
@@ -81,10 +85,16 @@ add_library(libslic3r STATIC
${LIBDIR}/libslic3r/PrintObject.cpp
${LIBDIR}/libslic3r/PrintRegion.cpp
${LIBDIR}/libslic3r/SLAPrint.cpp
+ ${LIBDIR}/libslic3r/SlicingAdaptive.cpp
${LIBDIR}/libslic3r/Surface.cpp
${LIBDIR}/libslic3r/SurfaceCollection.cpp
${LIBDIR}/libslic3r/SVG.cpp
${LIBDIR}/libslic3r/TriangleMesh.cpp
+ ${LIBDIR}/libslic3r/Zip/ZipArchive.cpp
+)
+
+add_library(BSpline STATIC
+ ${LIBDIR}/BSpline/BSpline.cpp
)
add_library(admesh STATIC
@@ -139,17 +149,29 @@ include_directories(${Boost_INCLUDE_DIRS})
IF(CMAKE_HOST_UNIX)
#set(Boost_LIBRARIES bsystem bthread bfilesystem)
ENDIF(CMAKE_HOST_UNIX)
+
+target_link_libraries (slic3r libslic3r admesh BSpline clipper expat polypartition poly2tri ${Boost_LIBRARIES})
+
IF(wxWidgets_FOUND)
MESSAGE("wx found!")
INCLUDE("${wxWidgets_USE_FILE}")
add_library(slic3r_gui STATIC ${LIBDIR}/slic3r/GUI/3DScene.cpp ${LIBDIR}/slic3r/GUI/GUI.cpp)
#only build GUI lib if building with wx
- target_link_libraries (slic3r slic3r_gui libslic3r admesh clipper expat polypartition poly2tri ${Boost_LIBRARIES} ${wxWidgets_LIBRARIES})
+ target_link_libraries (slic3r slic3r-gui ${wxWidgets_LIBRARIES})
ELSE(wxWidgets_FOUND)
# For convenience. When we cannot continue, inform the user
MESSAGE("wx not found!")
- target_link_libraries (slic3r libslic3r admesh clipper expat polypartition poly2tri ${Boost_LIBRARIES})
#skip gui when no wx included
ENDIF(wxWidgets_FOUND)
-target_link_libraries (extrude-tin libslic3r admesh clipper expat polypartition poly2tri ${Boost_LIBRARIES})
+# Windows needs a compiled component for Boost.nowide
+IF (WIN32)
+ add_library(boost-nowide STATIC
+ ${LIBDIR}/boost/nowide/iostream.cpp
+ )
+
+ target_link_libraries(slic3r boost-nowide)
+ target_link_libraries(extrude-tin boost-nowide)
+ENDIF(WIN32)
+
+target_link_libraries (extrude-tin libslic3r admesh BSpline clipper expat polypartition poly2tri ${Boost_LIBRARIES})
diff --git a/src/slic3r.cpp b/src/slic3r.cpp
index e245caeea..bf2fb2be7 100644
--- a/src/slic3r.cpp
+++ b/src/slic3r.cpp
@@ -134,6 +134,17 @@ main(int argc, char **argv)
print.slice();
print.write_svg(outfile);
boost::nowide::cout << "SVG file exported to " << outfile << std::endl;
+ } else if (cli_config.export_3mf) {
+ std::string outfile = cli_config.output.value;
+ if (outfile.empty()) outfile = model.objects.front()->input_file;
+ // Check if the file is already a 3mf.
+ if(outfile.substr(outfile.find_last_of('.'), outfile.length()) == ".3mf")
+ outfile = outfile.substr(0, outfile.find_last_of('.')) + "_2" + ".3mf";
+ else
+ // Remove the previous extension and add .3mf extention.
+ outfile = outfile.substr(0, outfile.find_last_of('.')) + ".3mf";
+ IO::TMF::write(model, outfile);
+ boost::nowide::cout << "File file exported to " << outfile << std::endl;
} else if (cli_config.cut_x > 0 || cli_config.cut_y > 0 || cli_config.cut > 0) {
model.repair();
model.translate(0, 0, -model.bounding_box().min.z);
@@ -151,14 +162,20 @@ main(int argc, char **argv)
ModelObject &upper = *out.objects[0];
ModelObject &lower = *out.objects[1];
+
+ // Use the input name and trim off the extension.
+ std::string outfile = cli_config.output.value;
+ if (outfile.empty()) outfile = model.objects.front()->input_file;
+ outfile = outfile.substr(0, outfile.find_last_of('.'));
+ std::cerr << outfile << "\n";
if (upper.facets_count() > 0) {
TriangleMesh m = upper.mesh();
- IO::STL::write(m, upper.input_file + "_upper.stl");
+ IO::STL::write(m, outfile + "_upper.stl");
}
if (lower.facets_count() > 0) {
TriangleMesh m = lower.mesh();
- IO::STL::write(m, lower.input_file + "_lower.stl");
+ IO::STL::write(m, outfile + "_lower.stl");
}
}
} else if (cli_config.cut_grid.value.x > 0 && cli_config.cut_grid.value.y > 0) {
diff --git a/t/adaptive_slicing.t b/t/adaptive_slicing.t
new file mode 100644
index 000000000..603e5e13e
--- /dev/null
+++ b/t/adaptive_slicing.t
@@ -0,0 +1,147 @@
+use Test::More tests => 9;
+use strict;
+use warnings;
+
+BEGIN {
+ use FindBin;
+ use lib "$FindBin::Bin/../lib";
+ use local::lib "$FindBin::Bin/../local-lib";
+}
+
+use List::Util qw(first sum);
+use Slic3r;
+use Slic3r::Test qw(_eq);
+use Slic3r::Geometry qw(Z PI scale unscale);
+
+use Devel::Peek;
+
+my $config = Slic3r::Config->new_from_defaults;
+
+my $generate_gcode = sub {
+ my ($conf) = @_;
+ $conf ||= $config;
+
+ my $print = Slic3r::Test::init_print('slopy_cube', config => $conf);
+
+ my @z = ();
+ my @increments = ();
+ Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
+ my ($self, $cmd, $args, $info) = @_;
+
+ if ($info->{dist_Z}) {
+ push @z, 1*$args->{Z};
+ push @increments, $info->{dist_Z};
+ }
+ });
+
+ return (@z);
+};
+
+my $horizontal_feature_test = sub {
+ my (@z) = $generate_gcode->();
+
+ ok (_eq($z[0], $config->get_value('first_layer_height') + $config->z_offset), 'first layer height.');
+
+ ok (_eq($z[1], $config->get_value('first_layer_height') + $config->get('max_layer_height')->[0] + $config->z_offset), 'second layer height.');
+
+ cmp_ok((first { _eq($_, 10.0) } @z[1..$#z]), '>', 0, 'horizontal facet matched');
+
+ 1;
+};
+
+my $height_gradation_test = sub {
+ my (@z) = $generate_gcode->();
+
+ my $gradation = 1 / $config->get('z_steps_per_mm');
+ # +1 is a "dirty fix" to avoid rounding issues with the modulo operator...
+ my @results = map {unscale((scale($_)+1) % scale($gradation))} @z;
+
+ ok (_eq(sum(@results), 0), 'layer z is multiple of gradation ' . $gradation );
+
+ 1;
+};
+
+
+my $adaptive_slicing = Slic3r::SlicingAdaptive->new();
+my $mesh = Slic3r::Test::mesh('slopy_cube');
+$adaptive_slicing->add_mesh($mesh);
+$adaptive_slicing->prepare(20);
+
+
+subtest 'max layer_height limited by extruder capabilities' => sub {
+ plan tests => 3;
+
+ ok (_eq($adaptive_slicing->next_layer_height(1, 20, 0.1, 0.15), 0.15), 'low');
+ ok (_eq($adaptive_slicing->next_layer_height(1, 20, 0.1, 0.4), 0.4), 'higher');
+ ok (_eq($adaptive_slicing->next_layer_height(1, 20, 0.1, 0.65), 0.65), 'highest');
+};
+
+subtest 'min layer_height limited by extruder capabilities' => sub {
+ plan tests => 3;
+
+ ok (_eq($adaptive_slicing->next_layer_height(4, 99, 0.1, 0.15), 0.1), 'low');
+ ok (_eq($adaptive_slicing->next_layer_height(4, 98, 0.2, 0.4), 0.2), 'higher');
+ ok (_eq($adaptive_slicing->next_layer_height(4, 99, 0.3, 0.65), 0.3), 'highest');
+};
+
+subtest 'correct layer_height depending on the facet normals' => sub {
+ plan tests => 3;
+
+ ok (_eq($adaptive_slicing->next_layer_height(1, 10, 0.1, 0.5), 0.5), 'limit');
+ ok (_eq($adaptive_slicing->next_layer_height(4, 80, 0.1, 0.5), 0.1546), '45deg facet, quality_value: 0.2');
+ ok (_eq($adaptive_slicing->next_layer_height(4, 50, 0.1, 0.5), 0.3352), '45deg facet, quality_value: 0.5');
+};
+
+
+# 2.92893 ist lower slope edge
+# distance to slope must be higher than min extruder cap.
+# slopes layer height must be greater than the distance to the slope
+ok (_eq($adaptive_slicing->next_layer_height(2.798, 80, 0.1, 0.5), 0.1546), 'reducing layer_height due to higher slopy facet');
+
+# slopes layer height must be smaller than the distance to the slope
+ok (_eq($adaptive_slicing->next_layer_height(2.6289, 85, 0.1, 0.5), 0.3), 'reducing layer_height to z-diff');
+
+subtest 'horizontal planes' => sub {
+ plan tests => 3;
+
+ ok (_eq($adaptive_slicing->horizontal_facet_distance(1, 1.2), 1.2), 'max_height limit');
+ ok (_eq($adaptive_slicing->horizontal_facet_distance(8.5, 4), 1.5), 'normal horizontal facet');
+ ok (_eq($adaptive_slicing->horizontal_facet_distance(17, 5), 3.0), 'object maximum');
+};
+
+# shrink current layer to fit another layer under horizontal facet
+$config->set('start_gcode', ''); # to avoid dealing with the nozzle lift in start G-code
+$config->set('z_offset', 0);
+
+$config->set('adaptive_slicing', 1);
+$config->set('first_layer_height', 0.42893); # to catch lower slope edge
+$config->set('nozzle_diameter', [0.5]);
+$config->set('min_layer_height', [0.1]);
+$config->set('max_layer_height', [0.5]);
+$config->set('adaptive_slicing_quality', [81]);
+# slope height: 7,07107 (2.92893 to 10)
+
+subtest 'shrink to match horizontal facets' => sub {
+ plan skip_all => 'spline smoothing currently prevents exact horizontal facet matching';
+ plan tests => 3;
+ $horizontal_feature_test->();
+};
+
+# widen current layer to match horizontal facet
+$config->set('adaptive_slicing_quality', [0.1]);
+
+subtest 'widen to match horizontal facets' => sub {
+ plan skip_all => 'spline smoothing currently prevents exact horizontal facet matching';
+ plan tests => 3;
+ $horizontal_feature_test->();
+};
+
+subtest 'layer height gradation' => sub {
+ plan tests => 5;
+ foreach my $gradation (1/0.001, 1/0.01, 1/0.02, 1/0.05, 1/0.08) {
+ $config->set('z_steps_per_mm', $gradation);
+ $height_gradation_test->();
+ }
+};
+
+__END__
diff --git a/t/perimeters.t b/t/perimeters.t
index 513c76ef9..31fe24d0c 100644
--- a/t/perimeters.t
+++ b/t/perimeters.t
@@ -1,4 +1,4 @@
-use Test::More tests => 63;
+use Test::More tests => 66;
use strict;
use warnings;
@@ -313,11 +313,60 @@ use Slic3r::Test;
ok !(grep { $_ % $config->perimeters } values %perimeters), 'no superfluous extra perimeters';
}
+{
+ my $config = Slic3r::Config->new_from_defaults;
+ $config->set('skirts', 0);
+ $config->set('perimeters', 3);
+ $config->set('min_shell_thickness', 3);
+ $config->set('layer_height', 0.4);
+ $config->set('first_layer_height', 0.35);
+ $config->set('extra_perimeters', 0);
+ $config->set('first_layer_extrusion_width', 0.5);
+ $config->set('perimeter_extrusion_width', 0.5);
+ $config->set('external_perimeter_extrusion_width', 0.5);
+ $config->set('cooling', 0); # to prevent speeds from being altered
+ $config->set('first_layer_speed', '100%'); # to prevent speeds from being altered
+ $config->set('perimeter_speed', 99);
+ $config->set('external_perimeter_speed', 99);
+ $config->set('small_perimeter_speed', 99);
+ $config->set('thin_walls', 0);
+
+ my $test = sub {
+ my $print = Slic3r::Test::init_print('ipadstand', config => $config);
+ my %perimeters = (); # z => number of loops
+ my $in_loop = 0;
+ Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
+ my ($self, $cmd, $args, $info) = @_;
+
+ if ($info->{extruding} && $info->{dist_XY} > 0 && ($args->{F} // $self->F) == $config->perimeter_speed*60) {
+ $perimeters{$self->Z}++ if !$in_loop;
+ $in_loop = 1;
+ } else {
+ $in_loop = 0;
+ }
+ });
+ ok !(grep { $_ % $config->min_shell_thickness/$config->perimeter_extrusion_width } values %perimeters), 'should be 6 perimeters';
+ };
+
+ $test->();
+
+ $config->set('first_layer_extrusion_width', 0.54);
+ $config->set('perimeter_extrusion_width', 0.54);
+ $config->set('external_perimeter_extrusion_width', 0.54);
+ $test->();
+
+ $config->set('first_layer_extrusion_width', 0.59);
+ $config->set('perimeter_extrusion_width', 0.51);
+ $config->set('external_perimeter_extrusion_width', 0.51);
+ $test->();
+}
+
{
my $config = Slic3r::Config->new_from_defaults;
$config->set('nozzle_diameter', [0.4]);
$config->set('perimeters', 2);
$config->set('perimeter_extrusion_width', 0.4);
+ $config->set('external_perimeter_extrusion_width', 0.4);
$config->set('infill_extrusion_width', 0.53);
$config->set('solid_infill_extrusion_width', 0.53);
diff --git a/t/support.t b/t/support.t
index 8d4655b3f..0a5a75337 100644
--- a/t/support.t
+++ b/t/support.t
@@ -1,4 +1,4 @@
-use Test::More tests => 28;
+use Test::More tests => 29;
use strict;
use warnings;
@@ -10,7 +10,7 @@ BEGIN {
use List::Util qw(first);
use Slic3r;
-use Slic3r::Geometry qw(epsilon scale);
+use Slic3r::Geometry qw(epsilon scale PI);
use Slic3r::Geometry::Clipper qw(diff);
use Slic3r::Test;
@@ -114,7 +114,6 @@ use Slic3r::Test;
my %first_object_layer_speeds = (); # F => 1
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
my ($self, $cmd, $args, $info) = @_;
-
if ($info->{extruding} && $info->{dist_XY} > 0) {
if ($layer_id <= $config->raft_layers) {
# this is a raft layer or the first object layer
@@ -141,6 +140,61 @@ use Slic3r::Test;
'bridge speed used in first object layer';
}
+{
+ my $config = Slic3r::Config->new_from_defaults;
+ $config->set('layer_height', 0.2);
+ $config->set('skirts', 0);
+ $config->set('raft_layers', 5);
+ $config->set('support_material_pattern', 'rectilinear');
+ $config->set('support_material_extrusion_width', 0.4);
+ $config->set('support_material_interface_extrusion_width', 0.6);
+ $config->set('support_material_interface_layers', 2);
+ $config->set('first_layer_extrusion_width', '100%');
+ $config->set('bridge_speed', 99);
+ $config->set('cooling', 0); # prevent speed alteration
+ $config->set('first_layer_speed', '100%'); # prevent speed alteration
+ $config->set('start_gcode', ''); # prevent any unexpected Z move
+ my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
+
+ my $layer_id = -1;
+ my $success = 1;
+ Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
+ my ($self, $cmd, $args, $info) = @_;
+ if ($info->{extruding} && $info->{dist_XY} > 0) {
+ # this is a raft layer
+ if (($layer_id < $config->raft_layers) && ($layer_id > 0)) {
+ my $width;
+ my $support_layer_height = $config->nozzle_diameter->[0] * 0.75;
+
+ # support layer
+ if ($config->raft_layers - $config->support_material_interface_layers > $layer_id) {
+ $width = $config->support_material_extrusion_width;
+ }
+ # interface layer
+ else {
+ $width = $config->support_material_interface_extrusion_width;
+ }
+ my $expected_E_per_mm3 = 4 / (($config->filament_diameter->[0]**2) * PI);
+ my $expected_mm3_per_mm = $width * $support_layer_height + ($support_layer_height**2) / 4.0 * (PI-4.0);
+ my $expected_e_per_mm = $expected_E_per_mm3 * $expected_mm3_per_mm;
+
+ my $e_per_mm = ($info->{dist_E} / $info->{dist_XY});;
+
+ my $diff = abs($e_per_mm - $expected_e_per_mm);
+
+ if ($diff > 0.001) {
+ $success = 0;
+ }
+ }
+ } elsif ($cmd eq 'G1' && $info->{dist_Z} > 0) {
+ $layer_id++;
+ }
+ });
+
+ ok $success,
+ 'support material interface extrusion width is used for interfaces';
+}
+
{
my $config = Slic3r::Config->new_from_defaults;
$config->set('skirts', 0);
diff --git a/utils/post-processing/strip-toolchange.pl b/utils/post-processing/strip-toolchange.pl
new file mode 100644
index 000000000..8ed227dea
--- /dev/null
+++ b/utils/post-processing/strip-toolchange.pl
@@ -0,0 +1,15 @@
+#!/usr/bin/perl -i
+#
+# Remove all toolchange commands (and T# arguments) from gcode.
+
+use strict;
+use warnings;
+
+# read stdin and any/all files passed as parameters one line at a time
+while (<>) {
+ if (not /^T[0-9]/) {
+ s/\s*(T[0-9])//;
+ print;
+ } else {
+ }
+}
diff --git a/var/arrow_redo.png b/var/arrow_redo.png
new file mode 100644
index 000000000..fdc394c7c
Binary files /dev/null and b/var/arrow_redo.png differ
diff --git a/var/arrow_undo.png b/var/arrow_undo.png
new file mode 100644
index 000000000..6972c5e59
Binary files /dev/null and b/var/arrow_undo.png differ
diff --git a/var/gcode.icns b/var/gcode.icns
new file mode 100755
index 000000000..d5be3bd57
Binary files /dev/null and b/var/gcode.icns differ
diff --git a/var/gcode.ico b/var/gcode.ico
new file mode 100755
index 000000000..b119b38f4
Binary files /dev/null and b/var/gcode.ico differ
diff --git a/var/lorry_import.png b/var/lorry_import.png
new file mode 100644
index 000000000..5aaf8a33e
Binary files /dev/null and b/var/lorry_import.png differ
diff --git a/var/rotate_face.png b/var/rotate_face.png
new file mode 100644
index 000000000..8268ee912
Binary files /dev/null and b/var/rotate_face.png differ
diff --git a/var/slt.ico b/var/slt.ico
new file mode 100755
index 000000000..274c69979
Binary files /dev/null and b/var/slt.ico differ
diff --git a/var/stl.icns b/var/stl.icns
new file mode 100755
index 000000000..a96989598
Binary files /dev/null and b/var/stl.icns differ
diff --git a/var/variable_layer_height.png b/var/variable_layer_height.png
new file mode 100644
index 000000000..2fbdd6920
Binary files /dev/null and b/var/variable_layer_height.png differ
diff --git a/xs/Build.PL b/xs/Build.PL
index 2633b8870..a4d00cd3d 100644
--- a/xs/Build.PL
+++ b/xs/Build.PL
@@ -10,6 +10,7 @@ use Module::Build::WithXSpp;
my $cpp_guess = ExtUtils::CppGuess->new;
my $mswin = $^O eq 'MSWin32';
+my $linux = $^O eq 'linux';
# prevent an annoying concatenation warning by Devel::CheckLib
$ENV{LD_RUN_PATH} //= "";
@@ -18,19 +19,37 @@ $ENV{LD_RUN_PATH} //= "";
# HAS_BOOL : stops Perl/lib/CORE/handy.h from doing "# define bool char" for MSVC
# NOGDI : prevents inclusion of wingdi.h which defines functions Polygon() and Polyline() in global namespace
# BOOST_ASIO_DISABLE_KQUEUE : prevents a Boost ASIO bug on OS X: https://svn.boost.org/trac/boost/ticket/5339
-my @cflags = qw(-D_GLIBCXX_USE_C99 -DHAS_BOOL -DNOGDI -DSLIC3RXS -DBOOST_ASIO_DISABLE_KQUEUE);
+my @cflags = qw(-D_GLIBCXX_USE_C99 -DHAS_BOOL -DNOGDI -DSLIC3RXS -DBOOST_ASIO_DISABLE_KQUEUE -Dexprtk_disable_rtl_io_file -Dexprtk_disable_return_statement -Dexprtk_disable_rtl_vecops -Dexprtk_disable_string_capabilities -Dexprtk_disable_enhanced_features);
if ($cpp_guess->is_gcc) {
# GCC is pedantic with c++11 std, so undefine strict ansi to get M_PI back
push @cflags, qw(-U__STRICT_ANSI__);
}
-if (`$Config{cc} -v` =~ /gcc version 4\.6\./) {
- # Compatibility with GCC 4.6 is not a requirement, but as long as code compiles on it we try to support it.
- push @cflags, qw(-std=c++0x);
-} else {
- # std=c++11 Enforce usage of C++11 (required now). Minimum compiler supported: gcc 4.9, clang 3.3, MSVC 14.0
- push @cflags, qw(-std=c++11);
-}
+
+# std=c++11 Enforce usage of C++11 (required now). Minimum compiler supported: gcc 4.9, clang 3.3, MSVC 14.0
+push @cflags, qw(-std=c++11);
+
my @ldflags = ();
+
+if ($linux && (defined $ENV{SLIC3R_STATIC} && $ENV{SLIC3R_STATIC})) {
+ push @ldflags, qw(-static-libgcc -static-libstdc++);
+ if ($ENV{TRAVIS}) {
+ # On the build server, link to the actual static libraries to make sure we get them in the list.
+ push @ldflags, qw(/usr/lib/gcc/x86_64-linux-gnu/4.9/libstdc++.a /usr/lib/gcc/x86_64-linux-gnu/4.9/libgcc.a);
+ }
+ # ExtUtils::CppGuess has a hard-coded -lstdc++, so we filter it out
+ {
+ no strict 'refs';
+ no warnings 'redefine';
+ my $func = "ExtUtils::CppGuess::_get_lflags";
+ my $orig = *$func{CODE};
+ *{$func} = sub {
+ my $lflags = $orig->(@_);
+ $lflags =~ s/\s*-lstdc\+\+//;
+ return $lflags;
+ };
+ }
+}
+
if ($^O eq 'darwin') {
push @cflags, qw(-stdlib=libc++);
push @ldflags, qw(-framework IOKit -framework CoreFoundation -lc++);
@@ -192,17 +211,9 @@ if ($ENV{SLIC3R_DEBUG}) {
push @cflags, $cpp_guess->is_msvc ? '-Gd' : '-g';
} else {
# Disable asserts in the release builds.
- push @cflags, '-DNDEBUG';
+ push @cflags, '-DNDEBUG', '-O';
}
if ($cpp_guess->is_gcc) {
- # check whether we're dealing with a buggy GCC version
- # see https://github.com/alexrj/Slic3r/issues/1965
- if (`cc --version` =~ / 4\.7\.[012]/) {
- # Workaround suggested by Boost devs:
- # https://svn.boost.org/trac/boost/ticket/8695
- push @cflags, qw(-fno-inline-small-functions);
- }
-
# our templated XS bindings cause undefined-var-template warnings
push @cflags, qw(-Wno-undefined-var-template);
}
@@ -211,10 +222,10 @@ my $build = Module::Build::WithXSpp->new(
module_name => 'Slic3r::XS',
dist_abstract => 'XS code for Slic3r',
build_requires => {qw(
- ExtUtils::ParseXS 3.18
+ ExtUtils::ParseXS 3.35
ExtUtils::Typemaps 1.00
ExtUtils::Typemaps::Default 1.05
- ExtUtils::XSpp 0.17
+ ExtUtils::XSpp 0.18
Module::Build 0.3601
Test::More 0
)},
diff --git a/xs/MANIFEST b/xs/MANIFEST
index b9e98e0a6..519004743 100644
--- a/xs/MANIFEST
+++ b/xs/MANIFEST
@@ -97,10 +97,14 @@ src/libslic3r/Geometry.hpp
src/libslic3r/IO.cpp
src/libslic3r/IO.hpp
src/libslic3r/IO/AMF.cpp
+src/libslic3r/IO/TMF.hpp
+src/libslic3r/IO/TMF.cpp
src/libslic3r/Layer.cpp
src/libslic3r/Layer.hpp
src/libslic3r/LayerRegion.cpp
src/libslic3r/LayerRegionFill.cpp
+src/libslic3r/LayerHeightSpline.hpp
+src/libslic3r/LayerHeightSpline.cpp
src/libslic3r/libslic3r.h
src/libslic3r/Line.cpp
src/libslic3r/Line.hpp
@@ -140,6 +144,9 @@ src/libslic3r/SVG.hpp
src/libslic3r/TriangleMesh.cpp
src/libslic3r/TriangleMesh.hpp
src/libslic3r/utils.cpp
+src/libslic3r/Zip/ZipArchive.cpp
+src/libslic3r/Zip/ZipArchive.hpp
+src/miniz/miniz.h
src/perlglue.cpp
src/poly2tri/common/shapes.cc
src/poly2tri/common/shapes.h
@@ -183,6 +190,12 @@ t/19_model.t
t/20_print.t
t/21_gcode.t
t/22_exception.t
+t/23_3mf.t
+t/models/3mf/box.3mf
+t/models/3mf/chess.3mf
+t/models/3mf/gimblekeychain.3mf
+t/models/amf/FaceColors.amf.xml
+t/models/stl/spikey_top.stl
t/inc/22_config_bad_config_options.ini
xsp/BoundingBox.xsp
xsp/BridgeDetector.xsp
@@ -204,6 +217,7 @@ xsp/Geometry.xsp
xsp/GUI.xsp
xsp/GUI_3DScene.xsp
xsp/Layer.xsp
+xsp/LayerHeightSpline.xsp
xsp/Line.xsp
xsp/Model.xsp
xsp/MotionPlanner.xsp
diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm
index 364264d67..7f269c1a8 100644
--- a/xs/lib/Slic3r/XS.pm
+++ b/xs/lib/Slic3r/XS.pm
@@ -240,6 +240,7 @@ for my $class (qw(
Slic3r::Layer
Slic3r::Layer::Region
Slic3r::Layer::Support
+ Slic3r::LayerHeightSpline
Slic3r::Line
Slic3r::Linef3
Slic3r::Model
@@ -258,6 +259,7 @@ for my $class (qw(
Slic3r::Print::Object
Slic3r::Print::Region
Slic3r::Print::State
+ Slic3r::SlicingAdaptive
Slic3r::Surface
Slic3r::Surface::Collection
Slic3r::TriangleMesh
diff --git a/xs/src/BSpline/BSpline.cpp b/xs/src/BSpline/BSpline.cpp
new file mode 100644
index 000000000..6cce57319
--- /dev/null
+++ b/xs/src/BSpline/BSpline.cpp
@@ -0,0 +1,879 @@
+// -*- mode: c++; c-basic-offset: 4; -*-
+/************************************************************************
+ * Copyright 2009 University Corporation for Atmospheric Research.
+ * All rights reserved.
+ *
+ * Use of this code is subject to the standard BSD license:
+ *
+ * http://www.opensource.org/licenses/bsd-license.html
+ *
+ * See the COPYRIGHT file in the source distribution for the license text,
+ * or see this web page:
+ *
+ * http://www.eol.ucar.edu/homes/granger/bspline/doc/
+ *
+ *************************************************************************/
+
+/**
+ * @file
+ *
+ * This file defines the implementation for the BSpline and BSplineBase
+ * templates.
+ **/
+#include "BSpline.h"
+#include "BandedMatrix.h"
+
+#include
+#include
+#include
+#include
+#include
+#include