Merge branch 'master' of https://github.com/slic3r/Slic3r into Samir55-3mf-readme

This commit is contained in:
Samir55 2018-07-10 04:38:48 +02:00
commit f590802955
152 changed files with 53644 additions and 1381 deletions

6
.gitignore vendored
View File

@ -16,3 +16,9 @@ package/osx/Slic3r*.app
*.dmg
*.swp
*.swo
CMakeFiles
*.orig
*.a
core
CMakeCache.txt
*.cmake

View File

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

2
Build.PL Normal file → Executable file
View File

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

View File

@ -1,4 +1,4 @@
![](var/Slic3r_128px.png) Slic3r [![Build Status](https://travis-ci.org/alexrj/Slic3r.svg?branch=master)](https://travis-ci.org/alexrj/Slic3r) [![Build status](https://ci.appveyor.com/api/projects/status/8iqmeat6cj158vo6?svg=true)](https://ci.appveyor.com/project/lordofhyphens/slic3r) [![Build Status](http://osx-build.slic3r.org:8080/buildStatus/icon?job=Slic3r)](http://osx-build.slic3r.org:8080/job/Slic3r)
![](var/Slic3r_128px.png) Slic3r [![Build Status](https://travis-ci.org/slic3r/Slic3r.svg?branch=master)](https://travis-ci.org/slic3r/Slic3r) [![Build status](https://ci.appveyor.com/api/projects/status/8iqmeat6cj158vo6?svg=true)](https://ci.appveyor.com/project/lordofhyphens/slic3r) [![Build Status](http://osx-build.slic3r.org:8080/buildStatus/icon?job=Slic3r)](http://osx-build.slic3r.org:8080/job/Slic3r)
======
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, <a href="http://www.famfamfam.com/lab/icons/silk/">Silk Icon Set</a> 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, <a href="http://www.famfamfam.com/lab/icons/silk/">Silk Icon Set</a> 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 <file> Save configuration to the specified file
@ -108,14 +108,16 @@ Contributions by Henrik Brix Andersen, Vojtech Bubnik, Nicolas Dandrimont, Mark
them as <name>_upper.stl and <name>_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 <num> 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 <file> 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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 = <FILEFIRSTLINE>;
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 <GFILE>; # 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 -> <gcode-filename.ini>
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 <gcode-filename.ini> file after load_config?
}
sub export_configbundle {
my $self = shift;

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

21
package/common/coreperl Normal file
View File

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

0
package/common/util.sh Normal file → Executable file
View File

40
package/deploy/sftp-symlink.sh Executable file
View File

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

View File

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

View File

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

100
package/linux/appimage.sh Executable file
View File

@ -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 <<EOF
[Desktop Entry]
Type=Application
Name=$APP
Icon=$APP
Exec=AppRun
Categories=Graphics;
X-Slic3r-Version=$SLIC3R_VERSION
Comment=Prepare 3D Models for Printing
EOF
cd ..
wget "https://github.com/probonopd/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
chmod a+x appimagetool-x86_64.AppImage
./appimagetool-x86_64.AppImage Slic3r.AppDir
# If we're on a branch, add the branch name to the app name.
if [ "$current_branch" == "master" ]; then
if [ ! -z ${PR_ID+x} ]; then
outfile=Slic3r-${SLIC3R_BUILD_ID}-PR${PR_ID}-${1}.AppImage
else
outfile=Slic3r-${SLIC3R_BUILD_ID}-${1}.AppImage
fi
else
outfile=Slic3r-${SLIC3R_BUILD_ID}-${current_branch}-${1}.AppImage
fi
mv Slic3r-x86_64.AppImage ${WD}/../../${outfile}

View File

@ -0,0 +1 @@
/usr/lib/x86_64-linux-gnu/libstdc++.so.6 # needed because of ancient distros and slic3r (and perl for perl reasons) needs modern c++.

View File

@ -8,3 +8,4 @@
/usr/lib/x86_64-linux-gnu/libjpeg.so.8
/usr/lib/x86_64-linux-gnu/libjbig.so.0
/usr/lib/x86_64-linux-gnu/libtiff.so.5
/usr/lib/x86_64-linux-gnu/libglut.so.3 # needed for appimage on some systems that don't have glut installed.

View File

@ -92,20 +92,16 @@ cp -f $WD/startup_script.sh $archivefolder/$appname
chmod +x $archivefolder/$appname
echo "Copying perl from $PERL_BIN"
# Edit package/common/coreperl to add/remove core Perl modules added to this package, one per line.
cp -f $PERL_BIN $archivefolder/perl-local
${PP_BIN} wxextension .0 \
-M attributes -M base -M bytes -M B -M POSIX \
-M FindBin -M Unicode::Normalize -M Tie::Handle \
-M Time::Local -M Math::Trig -M IO::Socket -M Errno \
-M lib -M overload \
-M warnings -M local::lib \
-M strict -M utf8 -M parent \
-M $(grep -v "^#" ${WD}/../common/coreperl | xargs | awk 'BEGIN { OFS=" -M "}; {$1=$1; print $0}') \
-B -p -e "print 123" -o $WD/_tmp/test.par
unzip -qq -o $WD/_tmp/test.par -d $WD/_tmp/
cp -rf $WD/_tmp/lib/* $archivefolder/local-lib/lib/perl5/
cp -rf $WD/_tmp/shlib $archivefolder/
rm -rf $WD/_tmp
for i in $(cat $WD/libpaths.txt); do
for i in $(cat $WD/libpaths.txt | grep -v "^#" | awk -F# '{print $1}'); do
install -v $i $archivefolder/bin
done

View File

@ -2,8 +2,6 @@
Type=Application
Version=SLIC3R_VERSION
Name=APPLICATION_NAME
Comment=Prepare 3D Models for printing

5
package/linux/startup_script.sh Normal file → Executable file
View File

@ -1,5 +1,6 @@
#!/bin/bash
DIR=$(dirname "$0")
export LD_LIBRARY_PATH=./bin
BIN=$(readlink "$0")
DIR=$(dirname "$BIN")
export LD_LIBRARY_PATH="$DIR/bin"
exec "$DIR/perl-local" -I"$DIR/local-lib/lib/perl5" "$DIR/slic3r.pl" $@

View File

@ -79,6 +79,8 @@ mkdir -p $resourcefolder
echo "Copying resources..."
cp -rf $SLIC3R_DIR/var $resourcefolder/
mv $resourcefolder/var/Slic3r.icns $resourcefolder
mv $resourcefolder/var/stl.icns $resourcefolder
mv $resourcefolder/var/gcode.icns $resourcefolder
echo "Copying Slic3r..."
cp $SLIC3R_DIR/slic3r.pl $macosfolder/slic3r.pl
@ -98,13 +100,10 @@ cp -f $WD/startup_script.sh $macosfolder/$appname
chmod +x $macosfolder/$appname
echo "Copying perl from $PERL_BIN"
# Edit package/common/coreperl to add/remove core Perl modules added to this package, one per line.
cp -f $PERL_BIN $macosfolder/perl-local
${PP_BIN} -M attributes -M base -M bytes -M B -M POSIX \
-M FindBin -M Unicode::Normalize -M Tie::Handle \
-M Time::Local -M Math::Trig -M IO::Socket -M Errno \
-M lib -M overload \
-M warnings -M local::lib \
-M strict -M utf8 -M parent \
${PP_BIN} \
-M $(grep -v "^#" ${WD}/../common/coreperl | xargs | awk 'BEGIN { OFS=" -M "}; {$1=$1; print $0}') \
-B -p -e "print 123" -o $WD/_tmp/test.par
unzip -o $WD/_tmp/test.par -d $WD/_tmp/
cp -rf $WD/_tmp/lib/* $macosfolder/local-lib/lib/perl5/

129
package/osx/plist.sh Normal file → Executable file
View File

@ -36,66 +36,83 @@ cat << EOF >> $plistfile
<key>CFBundleVersion</key>
<string>${SLIC3R_BUILD_ID}</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>stl</string>
<string>STL</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>Slic3r.icns</string>
<key>CFBundleTypeName</key>
<string>STL</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LISsAppleDefaultForType</key>
<true/>
<key>LSHandlerRank</key>
<string>Alternate</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>obj</string>
<string>OBJ</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>Slic3r.icns</string>
<key>CFBundleTypeName</key>
<string>STL</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LISsAppleDefaultForType</key>
<true/>
<key>LSHandlerRank</key>
<string>Alternate</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>amf</string>
<string>AMF</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>Slic3r.icns</string>
<key>CFBundleTypeName</key>
<string>STL</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LISsAppleDefaultForType</key>
<true/>
<key>LSHandlerRank</key>
<string>Alternate</string>
</dict>
</array>
<key>LSMinimumSystemVersion</key>
<string>10.7</string>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>stl</string>
<string>STL</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>stl.icns</string>
<key>CFBundleTypeName</key>
<string>STL</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LISsAppleDefaultForType</key>
<true/>
<key>LSHandlerRank</key>
<string>Alternate</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>obj</string>
<string>OBJ</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>Slic3r.icns</string>
<key>CFBundleTypeName</key>
<string>STL</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LISsAppleDefaultForType</key>
<true/>
<key>LSHandlerRank</key>
<string>Alternate</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>amf</string>
<string>AMF</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>Slic3r.icns</string>
<key>CFBundleTypeName</key>
<string>STL</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LISsAppleDefaultForType</key>
<true/>
<key>LSHandlerRank</key>
<string>Alternate</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>gcode</string>
<string>GCODE</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>gcode.icns</string>
<key>CFBundleTypeName</key>
<string>GCODE</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LISsAppleDefaultForType</key>
<true/>
<key>LSHandlerRank</key>
<string>Alternate</string>
</dict>
</array>
<key>LSMinimumSystemVersion</key>
<string>10.7</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSHighResolutionCapable</key>
<true/>
</dict>
</dict>
</plist>
EOF

0
package/osx/startup_script.sh Normal file → Executable file
View File

View File

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

BIN
share/locale/ja/slic3r.mo Normal file

Binary file not shown.

View File

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

View File

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

View File

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

147
t/adaptive_slicing.t Normal file
View File

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

View File

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

View File

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

View File

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

BIN
var/arrow_redo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 625 B

BIN
var/arrow_undo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 B

BIN
var/gcode.icns Executable file

Binary file not shown.

BIN
var/gcode.ico Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

BIN
var/lorry_import.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 798 B

BIN
var/rotate_face.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

BIN
var/slt.ico Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

BIN
var/stl.icns Executable file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

View File

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

View File

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

View File

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

879
xs/src/BSpline/BSpline.cpp Normal file
View File

@ -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 <vector>
#include <algorithm>
#include <iterator>
#include <iostream>
#include <iomanip>
#include <map>
#include <assert.h>
/*
* Original BSplineBase.cpp start here
*/
/*
* This class simulates a namespace for private symbols used by this template
* implementation which should not pollute the global namespace.
*/
class my
{
public:
template<class T> static inline
T abs(const T t)
{
return (t < 0) ? -t : t;
}
template<class T> static inline
const T& min(const T& a,
const T& b)
{
return (a < b) ? a : b;
}
template<class T> static inline
const T& max(const T& a,
const T& b)
{
return (a > b) ? a : b;
}
};
//////////////////////////////////////////////////////////////////////
template<class T> class Matrix : public BandedMatrix<T>
{
public:
Matrix &operator +=(const Matrix &B)
{
Matrix &A = *this;
typename Matrix::size_type M = A.num_rows();
typename Matrix::size_type N = A.num_cols();
assert(M==B.num_rows());
assert(N==B.num_cols());
typename Matrix::size_type i, j;
for (i=0; i<M; i++)
for (j=0; j<N; j++)
A[i][j] += B[i][j];
return A;
}
inline Matrix & operator=(const Matrix &b)
{
return Copy(*this, b);
}
inline Matrix & operator=(const T &e)
{
BandedMatrix<T>::operator= (e);
return *this;
}
};
//////////////////////////////////////////////////////////////////////
// Our private state structure, which hides our use of some matrix
// template classes.
template<class T> struct BSplineBaseP
{
typedef Matrix<T> MatrixT;
MatrixT Q; // Holds P+Q and its factorization
std::vector<T> X;
std::vector<T> Nodes;
};
//////////////////////////////////////////////////////////////////////
// This array contains the beta parameter for the boundary condition
// constraints. The boundary condition type--0, 1, or 2--is the first
// index into the array, followed by the index of the endpoints. See the
// Beta() method.
template<class T> const double BSplineBase<T>::BoundaryConditions[3][4] =
{
// 0 1 M-1 M
{
-4,
-1,
-1,
-4 },
{
0,
1,
1,
0 },
{
2,
-1,
-1,
2 } };
//////////////////////////////////////////////////////////////////////
template<class T> inline bool BSplineBase<T>::Debug(int on)
{
static bool debug = false;
if (on >= 0)
debug = (on > 0);
return debug;
}
//////////////////////////////////////////////////////////////////////
template<class T> const double BSplineBase<T>::BS_PI = 3.1415927;
//////////////////////////////////////////////////////////////////////
template<class T> const char * BSplineBase<T>::ImplVersion()
{
return ("$Id: BSpline.cpp 6352 2008-05-05 04:40:39Z martinc $");
}
//////////////////////////////////////////////////////////////////////
template<class T> const char * BSplineBase<T>::IfaceVersion()
{
return (_BSPLINEBASE_IFACE_ID);
}
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
template<class T> BSplineBase<T>::~BSplineBase()
{
delete base;
}
// This is a member-wise copy except for replacing our
// private base structure with the source's, rather than just copying
// the pointer. But we use the compiler's default copy constructor for
// constructing our BSplineBaseP.
template<class T> BSplineBase<T>::BSplineBase(const BSplineBase<T> &bb) :
K(bb.K), BC(bb.BC), OK(bb.OK), base(new BSplineBaseP<T>(*bb.base))
{
xmin = bb.xmin;
xmax = bb.xmax;
alpha = bb.alpha;
waveLength = bb.waveLength;
DX = bb.DX;
M = bb.M;
NX = base->X.size();
}
//////////////////////////////////////////////////////////////////////
template<class T> BSplineBase<T>::BSplineBase(const T *x,
int nx,
double wl,
int bc,
int num_nodes) :
NX(0), K(2), OK(false), base(new BSplineBaseP<T>)
{
setDomain(x, nx, wl, bc, num_nodes);
}
//////////////////////////////////////////////////////////////////////
// Methods
template<class T> bool BSplineBase<T>::setDomain(const T *x,
int nx,
double wl,
int bc,
int num_nodes)
{
if ((nx <= 0) || (x == 0) || (wl< 0) || (bc< 0) || (bc> 2)) {
return false;
}
OK = false;
waveLength = wl;
BC = bc;
// Copy the x array into our storage.
base->X.resize(nx);
std::copy(x, x+nx, base->X.begin());
NX = base->X.size();
// The Setup() method determines the number and size of node intervals.
if (Setup(num_nodes)) {
if (Debug()) {
std::cerr << "Using M node intervals: " << M << " of length DX: "
<< DX << std::endl;
std::cerr << "X min: " << xmin << " ; X max: " << xmax << std::endl;
std::cerr << "Data points per interval: " << (float)NX/(float)M
<< std::endl;
std::cerr << "Nodes per wavelength: " << (float)waveLength
/(float)DX << std::endl;
std::cerr << "Derivative constraint degree: " << K << std::endl;
}
// Now we can calculate alpha and our Q matrix
alpha = Alpha(waveLength);
if (Debug()) {
std::cerr << "Cutoff wavelength: " << waveLength << " ; "
<< "Alpha: " << alpha << std::endl;
std::cerr << "Calculating Q..." << std::endl;
}
calculateQ();
if (Debug() && M < 30) {
std::cerr.fill(' ');
std::cerr.precision(2);
std::cerr.width(5);
std::cerr << base->Q << std::endl;
}
if (Debug())
std::cerr << "Calculating P..." << std::endl;
addP();
if (Debug()) {
std::cerr << "Done." << std::endl;
if (M < 30) {
std::cerr << "Array Q after addition of P." << std::endl;
std::cerr << base->Q;
}
}
// Now perform the LU factorization on Q
if (Debug())
std::cerr << "Beginning LU factoring of P+Q..." << std::endl;
if (!factor()) {
if (Debug())
std::cerr << "Factoring failed." << std::endl;
} else {
if (Debug())
std::cerr << "Done." << std::endl;
OK = true;
}
}
return OK;
}
//////////////////////////////////////////////////////////////////////
/*
* Calculate the alpha parameter given a wavelength.
*/
template<class T> double BSplineBase<T>::Alpha(double wl)
{
// K is the degree of the derivative constraint: 1, 2, or 3
double a = (double) (wl / (2 * BS_PI * DX));
a *= a; // a^2
if (K == 2)
a = a * a; // a^4
else if (K == 3)
a = a * a * a; // a^6
return a;
}
//////////////////////////////////////////////////////////////////////
/*
* Return the correct beta value given the node index. The value depends
* on the node index and the current boundary condition type.
*/
template<class T> inline double BSplineBase<T>::Beta(int m)
{
if (m > 1 && m < M-1)
return 0.0;
if (m >= M-1)
m -= M-3;
assert(0 <= BC && BC <= 2);
assert(0 <= m && m <= 3);
return BoundaryConditions[BC][m];
}
//////////////////////////////////////////////////////////////////////
/*
* Given an array of y data points defined over the domain
* of x data points in this BSplineBase, create a BSpline
* object which contains the smoothed curve for the y array.
*/
template<class T> BSpline<T> * BSplineBase<T>::apply(const T *y)
{
return new BSpline<T> (*this, y);
}
//////////////////////////////////////////////////////////////////////
/*
* Evaluate the closed basis function at node m for value x,
* using the parameters for the current boundary conditions.
*/
template<class T> double BSplineBase<T>::Basis(int m,
T x)
{
double y = 0;
double xm = xmin + (m * DX);
double z = my::abs((double)(x - xm) / (double)DX);
if (z < 2.0) {
z = 2 - z;
y = 0.25 * (z*z*z);
z -= 1.0;
if (z > 0)
y -= (z*z*z);
}
// Boundary conditions, if any, are an additional addend.
if (m == 0 || m == 1)
y += Beta(m) * Basis(-1, x);
else if (m == M-1 || m == M)
y += Beta(m) * Basis(M+1, x);
return y;
}
//////////////////////////////////////////////////////////////////////
/*
* Evaluate the deriviative of the closed basis function at node m for
* value x, using the parameters for the current boundary conditions.
*/
template<class T> double BSplineBase<T>::DBasis(int m,
T x)
{
double dy = 0;
double xm = xmin + (m * DX);
double delta = (double)(x - xm) / (double)DX;
double z = my::abs(delta);
if (z < 2.0) {
z = 2.0 - z;
dy = 0.25 * z * z;
z -= 1.0;
if (z > 0) {
dy -= z * z;
}
dy *= ((delta > 0) ? -1.0 : 1.0) * 3.0 / DX;
}
// Boundary conditions, if any, are an additional addend.
if (m == 0 || m == 1)
dy += Beta(m) * DBasis(-1, x);
else if (m == M-1 || m == M)
dy += Beta(m) * DBasis(M+1, x);
return dy;
}
//////////////////////////////////////////////////////////////////////
template<class T> double BSplineBase<T>::qDelta(int m1,
int m2)
/*
* Return the integral of the product of the basis function derivative
* restricted to the node domain, 0 to M.
*/
{
// These are the products of the Kth derivative of the
// normalized basis functions
// given a distance m nodes apart, qparts[K-1][m], 0 <= m <= 3
// Each column is the integral over each unit domain, -2 to 2
static const double qparts[3][4][4] =
{
{
{
0.11250,
0.63750,
0.63750,
0.11250 },
{
0.00000,
0.13125,
-0.54375,
0.13125 },
{
0.00000,
0.00000,
-0.22500,
-0.22500 },
{
0.00000,
0.00000,
0.00000,
-0.01875 } },
{
{
0.75000,
2.25000,
2.25000,
0.75000 },
{
0.00000,
-1.12500,
-1.12500,
-1.12500 },
{
0.00000,
0.00000,
0.00000,
0.00000 },
{
0.00000,
0.00000,
0.00000,
0.37500 } },
{
{
2.25000,
20.25000,
20.25000,
2.25000 },
{
0.00000,
-6.75000,
-20.25000,
-6.75000 },
{
0.00000,
0.00000,
6.75000,
6.75000 },
{
0.00000,
0.00000,
0.00000,
-2.25000 } } };
if (m1 > m2)
std::swap(m1, m2);
if (m2 - m1 > 3)
return 0.0;
double q = 0;
for (int m = my::max(m1-2, 0); m < my::min(m1+2, M); ++m)
q += qparts[K-1][m2-m1][m-m1+2];
return q * alpha;
}
//////////////////////////////////////////////////////////////////////
template<class T> void BSplineBase<T>::calculateQ()
{
Matrix<T> &Q = base->Q;
Q.setup(M+1, 3);
Q = 0;
if (alpha == 0)
return;
// First fill in the q values without the boundary constraints.
int i;
for (i = 0; i <= M; ++i) {
Q[i][i] = qDelta(i, i);
for (int j = 1; j < 4 && i+j <= M; ++j) {
Q[i][i+j] = Q[i+j][i] = qDelta(i, i+j);
}
}
// Now add the boundary constraints:
// First the upper left corner.
float b1, b2, q;
for (i = 0; i <= 1; ++i) {
b1 = Beta(i);
for (int j = i; j < i+4; ++j) {
b2 = Beta(j);
assert(j-i >= 0 && j - i < 4);
q = 0.0;
if (i+1 < 4)
q += b2*qDelta(-1, i);
if (j+1 < 4)
q += b1*qDelta(-1, j);
q += b1*b2*qDelta(-1, -1);
Q[j][i] = (Q[i][j] += q);
}
}
// Then the lower right
for (i = M-1; i <= M; ++i) {
b1 = Beta(i);
for (int j = i - 3; j <= i; ++j) {
b2 = Beta(j);
q = 0.0;
if (M+1-i < 4)
q += b2*qDelta(i, M+1);
if (M+1-j < 4)
q += b1*qDelta(j, M+1);
q += b1*b2*qDelta(M+1, M+1);
Q[j][i] = (Q[i][j] += q);
}
}
}
//////////////////////////////////////////////////////////////////////
template<class T> void BSplineBase<T>::addP()
{
// Add directly to Q's elements
Matrix<T> &P = base->Q;
std::vector<T> &X = base->X;
// For each data point, sum the product of the nearest, non-zero Basis
// nodes
int mx, m, n, i;
for (i = 0; i < NX; ++i) {
// Which node does this put us in?
T &x = X[i];
mx = (int)((x - xmin) / DX);
// Loop over the upper triangle of nonzero basis functions,
// and add in the products on each side of the diagonal.
for (m = my::max(0, mx-1); m <= my::min(M, mx+2); ++m) {
float pn;
float pm = Basis(m, x);
float sum = pm * pm;
P[m][m] += sum;
for (n = m+1; n <= my::min(M, mx+2); ++n) {
pn = Basis(n, x);
sum = pm * pn;
P[m][n] += sum;
P[n][m] += sum;
}
}
}
}
//////////////////////////////////////////////////////////////////////
template<class T> bool BSplineBase<T>::factor()
{
Matrix<T> &LU = base->Q;
if (LU_factor_banded(LU, 3) != 0) {
if (Debug())
std::cerr << "LU_factor_banded() failed." << std::endl;
return false;
}
if (Debug() && M < 30)
std::cerr << "LU decomposition: " << std::endl << LU << std::endl;
return true;
}
//////////////////////////////////////////////////////////////////////
template<class T> inline double BSplineBase<T>::Ratiod(int &ni,
double &deltax,
double &ratiof)
{
deltax = (xmax - xmin) / ni;
ratiof = waveLength / deltax;
double ratiod = (double) NX / (double) (ni + 1);
return ratiod;
}
//////////////////////////////////////////////////////////////////////
// Setup the number of nodes (and hence deltax) for the given domain and
// cutoff wavelength. According to Ooyama, the derivative constraint
// approximates a lo-pass filter if the cutoff wavelength is about 4*deltax
// or more, but it should at least be 2*deltax. We can increase the number
// of nodes to increase the number of nodes per cutoff wavelength.
// However, to get a reasonable representation of the data, the setup
// enforces at least as many nodes as data points in the domain. (This
// constraint assumes reasonably even distribution of data points, since
// its really the density of data points which matters.)
//
// Return zero if the setup fails, non-zero otherwise.
//
// The algorithm in this routine is mostly taken from the FORTRAN
// implementation by James Franklin, NOAA/HRD.
//
template<class T> bool BSplineBase<T>::Setup(int num_nodes)
{
std::vector<T> &X = base->X;
// Find the min and max of the x domain
xmin = X[0];
xmax = X[0];
int i;
for (i = 1; i < NX; ++i) {
if (X[i] < xmin)
xmin = X[i];
else if (X[i] > xmax)
xmax = X[i];
}
if (Debug())
std::cerr << "Xmax=" << xmax << ", Xmin=" << xmin << std::endl;
// Number of node intervals (number of spline nodes - 1).
int ni;
double deltax;
if (num_nodes >= 2) {
// We've been told explicitly the number of nodes to use.
ni = num_nodes - 1;
if (waveLength == 0) {
waveLength = 1.0;
}
if (Debug())
{
std::cerr << "Num nodes explicitly given as " << num_nodes
<< ", wavelength set to " << waveLength << std::endl;
}
} else if (waveLength == 0) {
// Turn off frequency constraint and just set two node intervals per
// data point.
ni = NX * 2;
waveLength = 1;
if (Debug())
{
std::cerr << "Frequency constraint disabled, using 2 intervals "
<< "per node: " << ni << " intervals, wavelength="
<< waveLength << std::endl;
}
} else if (waveLength > xmax - xmin) {
if (Debug())
{
std::cerr << "Wavelength " << waveLength << " exceeds X span: "
<< xmin << " - " << xmax << std::endl;
}
return (false);
} else {
if (Debug())
{
std::cerr << "Searching for a reasonable number of "
<< "intervals for wavelength " << waveLength
<< " while keeping at least 2 intervals per "
<< "wavelength ..." << std::endl;
}
// Minimum acceptable number of node intervals per cutoff wavelength.
static const double fmin = 2.0;
// Start out at a minimum number of intervals, meaning the maximum
// number of points per interval, then work up to the maximum
// number of intervals for which the intervals per wavelength is
// still adequate. I think the minimum must be more than 2 since
// the basis function is evaluated on multiple nodes.
ni = 5;
double ratiof; // Nodes per wavelength for current deltax
double ratiod; // Points per node interval
// Increase the number of node intervals until we reach the minimum
// number of intervals per cutoff wavelength, but only as long as
// we can maintain at least one point per interval.
do {
if (Ratiod(++ni, deltax, ratiof) < 1.0)
{
if (Debug())
{
std::cerr << "At " << ni << " intervals, fewer than "
<< "one point per interval, and "
<< "intervals per wavelength is "
<< ratiof << "." << std::endl;
}
return false;
}
} while (ratiof < fmin);
// Now increase the number of intervals until we have at least 4
// intervals per cutoff wavelength, but only as long as we can
// maintain at least 2 points per node interval. There's also no
// point to increasing the number of intervals if we already have
// 15 or more nodes per cutoff wavelength.
//
do {
if ((ratiod = Ratiod(++ni, deltax, ratiof)) < 1.0 || ratiof > 15.0) {
--ni;
break;
}
} while (ratiof < 4 || ratiod > 2.0);
if (Debug())
{
std::cerr << "Found " << ni << " intervals, "
<< "length " << deltax << ", "
<< ratiof << " nodes per wavelength " << waveLength
<< ", "
<< ratiod << " data points per interval." << std::endl;
}
}
// Store the calculations in our state
M = ni;
DX = (xmax - xmin) / ni;
return (true);
}
//////////////////////////////////////////////////////////////////////
template<class T> const T * BSplineBase<T>::nodes(int *nn)
{
if (base->Nodes.size() == 0) {
base->Nodes.reserve(M+1);
for (int i = 0; i <= M; ++i) {
base->Nodes.push_back(xmin + (i * DX));
}
}
if (nn)
*nn = base->Nodes.size();
assert(base->Nodes.size() == (unsigned)(M+1));
return &base->Nodes[0];
}
//////////////////////////////////////////////////////////////////////
template<class T> std::ostream &operator<<(std::ostream &out,
const std::vector<T> &c)
{
for (typename std::vector<T>::const_iterator it = c.begin(); it < c.end(); ++it)
out << *it << ", ";
out << std::endl;
return out;
}
/*
* Original BSpline.cpp start here
*/
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
// BSpline Class
//////////////////////////////////////////////////////////////////////
template<class T> struct BSplineP {
std::vector<T> spline;
std::vector<T> A;
};
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
/*
* This BSpline constructor constructs and sets up a new base and
* solves for the spline curve coeffiecients all at once.
*/
template<class T> BSpline<T>::BSpline(const T *x,
int nx,
const T *y,
double wl,
int bc_type,
int num_nodes) :
BSplineBase<T>(x, nx, wl, bc_type, num_nodes), s(new BSplineP<T>) {
solve(y);
}
//////////////////////////////////////////////////////////////////////
/*
* Create a new spline given a BSplineBase.
*/
template<class T> BSpline<T>::BSpline(BSplineBase<T> &bb,
const T *y) :
BSplineBase<T>(bb), s(new BSplineP<T>) {
solve(y);
}
//////////////////////////////////////////////////////////////////////
/*
* (Re)calculate the spline for the given set of y values.
*/
template<class T> bool BSpline<T>::solve(const T *y) {
if (!OK)
return false;
// Any previously calculated curve is now invalid.
s->spline.clear();
OK = false;
// Given an array of data points over x and its precalculated
// P+Q matrix, calculate the b vector and solve for the coefficients.
std::vector<T> &B = s->A;
std::vector<T> &A = s->A;
A.clear();
A.resize(M+1);
if (Debug())
std::cerr << "Solving for B..." << std::endl;
// Find the mean of these data
mean = 0.0;
int i;
for (i = 0; i < NX; ++i) {
mean += y[i];
}
mean = mean / (double)NX;
if (Debug())
std::cerr << "Mean for y: " << mean << std::endl;
int mx, m, j;
for (j = 0; j < NX; ++j) {
// Which node does this put us in?
T &xj = base->X[j];
T yj = y[j] - mean;
mx = (int)((xj - xmin) / DX);
for (m = my::max(0, mx-1); m <= my::min(mx+2, M); ++m) {
B[m] += yj * Basis(m, xj);
}
}
if (Debug() && M < 30) {
std::cerr << "Solution a for (P+Q)a = b" << std::endl;
std::cerr << " b: " << B << std::endl;
}
// Now solve for the A vector in place.
if (LU_solve_banded(base->Q, A, 3) != 0) {
if (Debug())
std::cerr << "LU_solve_banded() failed." << std::endl;
} else {
OK = true;
if (Debug())
std::cerr << "Done." << std::endl;
if (Debug() && M < 30) {
std::cerr << " a: " << A << std::endl;
std::cerr << "LU factor of (P+Q) = " << std::endl << base->Q
<< std::endl;
}
}
return (OK);
}
//////////////////////////////////////////////////////////////////////
template<class T> BSpline<T>::~BSpline() {
delete s;
}
//////////////////////////////////////////////////////////////////////
template<class T> T BSpline<T>::coefficient(int n) {
if (OK)
if (0 <= n && n <= M)
return s->A[n];
return 0;
}
//////////////////////////////////////////////////////////////////////
template<class T> T BSpline<T>::evaluate(T x) {
T y = 0;
if (OK) {
int n = (int)((x - xmin)/DX);
for (int i = my::max(0, n-1); i <= my::min(M, n+2); ++i) {
y += s->A[i] * Basis(i, x);
}
y += mean;
}
return y;
}
//////////////////////////////////////////////////////////////////////
template<class T> T BSpline<T>::slope(T x) {
T dy = 0;
if (OK) {
int n = (int)((x - xmin)/DX);
for (int i = my::max(0, n-1); i <= my::min(M, n+2); ++i) {
dy += s->A[i] * DBasis(i, x);
}
}
return dy;
}
/// Instantiate BSplineBase
template class BSplineBase<double>;
//template class BSplineBase<float>;
/// Instantiate BSpline
template class BSpline<double>;
//template class BSpline<float>;

452
xs/src/BSpline/BSpline.h Normal file
View File

@ -0,0 +1,452 @@
/* -*- 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/
*
*************************************************************************/
#ifndef BSPLINE_H
#define BSPLINE_H
#include <vector>
/*
* Warning: original BSplineBase.h/cpp merged into BSpline.h/cpp to avoid dependency issues caused by Build::WithXSpp which tries to compile all .cpp files in /src
* Original BSplineBase.h starts here!!!
*/
#ifndef _BSPLINEBASE_IFACE_ID
#define _BSPLINEBASE_IFACE_ID "$Id: BSpline.h 6353 2008-05-05 19:30:48Z martinc $"
#endif
/**
* @file
*
* This file defines the BSpline library interface.
*
*/
template <class T> class BSpline;
/*
* Opaque member structure to hide the matrix implementation.
*/
template <class T> struct BSplineBaseP;
/**
* @class BSplineBase
*
* The base class for a spline object containing the nodes for a given
* domain, cutoff wavelength, and boundary condition.
* To smooth a single curve, the BSpline interface contains a constructor
* which both sets up the domain and solves for the spline. Subsequent
* curves over the same domain can be created by apply()ing them to the
* BSpline object, where apply() is a BSplineBase method. [See apply().]
* New curves can also be smoothed within the same BSpline object by
* calling solve() with the new set of y values. [See BSpline.] A
* BSplineBase can be created on its own, in which case all of the
* computations dependent on the x values, boundary conditions, and cutoff
* wavelength have already been completed.
*
* The solution of the cubic b-spline is divided into two parts. The first
* is the setup of the domain given the x values, boundary conditions, and
* wavelength. The second is the solution of the spline for a set of y
* values corresponding to the x values in the domain. The first part is
* done in the creation of the BSplineBase object (or when calling the
* setDomain method). The second part is done when creating a BSpline
* object (or calling solve() on a BSpline object).
*
* A BSpline object can be created with either one of its constructors, or
* by calling apply() on an existing BSplineBase object. Once a spline has
* been solved, it can be evaluated at any x value. The following example
* creates a spline curve and evaluates it over the domain:
*
@verbatim
vector<float> x;
vector<float> y;
{ ... }
int bc = BSplineBase<float>::BC_ZERO_SECOND;
BSpline<float>::Debug = true;
BSpline<float> spline (x.begin(), x.size(), y.begin(), wl, bc);
if (spline.ok())
{
ostream_iterator<float> of(cout, "\t ");
float xi = spline.Xmin();
float xs = (spline.Xmax() - xi) / 2000.0;
for (; xi <= spline.Xmax(); xi += xs)
{
*of++ = spline.evaluate (xi);
}
}
@endverbatim
*
* In the usual usage, the BSplineBase can compute a reasonable number of
* nodes for the spline, balancing between a few desirable factors. There
* needs to be at least 2 nodes per cutoff wavelength (preferably 4 or
* more) for the derivative constraint to reliably approximate a lo-pass
* filter. There should be at least 1 and preferably about 2 data points
* per node (measured just by their number and not by any check of the
* density of points across the domain). Lastly, of course, the fewer the
* nodes then the faster the computation of the spline. The computation of
* the number of nodes happens in the Setup() method during BSplineBase
* construction and when setDomain() is called. If the setup fails to find
* a desirable number of nodes, then the BSplineBase object will return
* false from ok().
*
* The ok() method returns false when a BSplineBase or BSpline could not
* complete any operation successfully. In particular, as mentioned above,
* ok() will return false if some problem was detected with the domain
* values or if no reasonable number of nodes could be found for the given
* cutoff wavelength. Also, ok() on a BSpline object will return false if
* the matrix equation could not be solved, such as after BSpline
* construction or after a call to apply().
*
* If letting Setup() determine the number of nodes is not acceptable, the
* constructors and setDomain() accept the parameter num_nodes. By
* default, num_nodes is passed as zero, forcing Setup() to calculate the
* number of nodes. However, if num_nodes is passed as 2 or greater, then
* Setup() will bypass its own algorithm and accept the given number of
* nodes instead. Obviously, it's up to the programmer to understand the
* affects of the number of nodes on the representation of the data and on
* the solution (or non-solution) of the spline. Remember to check the
* ok() method to detect when the spline solution has failed.
*
* The interface for the BSplineBase and BSpline templates is defined in
* the header file BSpline.h. The implementation is defined in BSpline.cpp.
* Source files which will instantiate the template should include the
* implementation file and @em not the interface. If the implementation
* for a specific type will be linked from elsewhere, such as a
* static library or Windows DLL, source files should only include the
* interface file. On Windows, applications should link with the import
* library BSpline.lib and make sure BSpline.dll is on the path. The DLL
* contains an implementation for BSpline<float> and BSpline<double>.
* For debugging, an application can include the implementation to get its
* own instantiation.
*
* The algorithm is based on the cubic spline described by Katsuyuki Ooyama
* in Montly Weather Review, Vol 115, October 1987. This implementation
* has benefited from comparisons with a previous FORTRAN implementation by
* James L. Franklin, NOAA/Hurricane Research Division. In particular, the
* algorithm in the Setup() method is based mostly on his implementation
* (VICSETUP). The Setup() method finds a suitable default for the number
* of nodes given a domain and cutoff frequency. This implementation
* adopts most of the same constraints, including a constraint that the
* cutoff wavelength not be greater than the span of the domain values: wl
* < max(x) - min(x). If this is not an acceptable constraint, then use the
* num_nodes parameter to specify the number of nodes explicitly.
*
* The cubic b-spline is formulated as the sum of some multiple of the
* basis function centered at each node in the domain. The number of nodes
* is determined by the desired cutoff wavelength and a desirable number of
* x values per node. The basis function is continuous and differentiable
* up to the second degree. A derivative constraint is included in the
* solution to achieve the effect of a low-pass frequency filter with the
* given cutoff wavelength. The derivative constraint can be disabled by
* specifying a wavelength value of zero, which reduces the analysis to a
* least squares fit to a cubic b-spline. The domain nodes, boundary
* constraints, and wavelength determine a linear system of equations,
* Qa=b, where a is the vector of basis function coefficients at each node.
* The coefficient vector is solved by first LU factoring along the
* diagonally banded matrix Q in BSplineBase. The BSpline object then
* computes the B vector for a set of y values and solves for the
* coefficient vector with the LU matrix. Only the diagonal bands are
* stored in memory and calculated during LU factoring and back
* substitution, and the basis function is evaluated as few times as
* possible in computing the diagonal matrix and B vector.
*
* @author Gary Granger (http://www.eol.ucar.edu/homes/granger)
*
@verbatim
Copyright (c) 1998-2009
University Corporation for Atmospheric Research, UCAR
All rights reserved.
@endverbatim
**/
template <class T>
class BSplineBase
{
public:
// Datum type
typedef T datum_type;
/// Return a string describing the implementation version.
static const char *ImplVersion();
/// Return a string describing the interface version.
static const char *IfaceVersion();
/**
* Call this class method with a value greater than zero to enable
* debug messages, or with zero to disable messages. Calling with
* no arguments returns true if debugging enabled, else false.
*/
static bool Debug (int on = -1);
/**
* Boundary condition types.
*/
enum BoundaryConditionTypes
{
/// Set the endpoints of the spline to zero.
BC_ZERO_ENDPOINTS = 0,
/// Set the first derivative of the spline to zero at the endpoints.
BC_ZERO_FIRST = 1,
/// Set the second derivative to zero.
BC_ZERO_SECOND = 2
};
public:
/**
* Construct a spline domain for the given set of x values, cutoff
* wavelength, and boundary condition type. The parameters are the
* same as for setDomain(). Call ok() to check whether domain
* setup succeeded after construction.
*/
BSplineBase (const T *x, int nx,
double wl, int bc_type = BC_ZERO_SECOND,
int num_nodes = 0);
/// Copy constructor
BSplineBase (const BSplineBase &);
/**
* Change the domain of this base. [If this is part of a BSpline
* object, this method {\em will not} change the existing curve or
* re-apply the smoothing to any set of y values.]
*
* The x values can be in any order, but they must be of sufficient
* density to support the requested cutoff wavelength. The setup of
* the domain may fail because of either inconsistency between the x
* density and the cutoff wavelength, or because the resulting matrix
* could not be factored. If setup fails, the method returns false.
*
* @param x The array of x values in the domain.
* @param nx The number of values in the @p x array.
* @param wl The cutoff wavelength, in the same units as the
* @p x values. A wavelength of zero disables
* the derivative constraint.
* @param bc_type The enumerated boundary condition type. If
* omitted it defaults to BC_ZERO_SECOND.
* @param num_nodes The number of nodes to use for the cubic b-spline.
* If less than 2 a reasonable number will be
* calculated automatically, if possible, taking
* into account the given cutoff wavelength.
*
* @see ok().
*/
bool setDomain (const T *x, int nx, double wl,
int bc_type = BC_ZERO_SECOND,
int num_nodes = 0);
/**
* Create a BSpline smoothed curve for the given set of NX y values.
* The returned object will need to be deleted by the caller.
* @param y The array of y values corresponding to each of the nX()
* x values in the domain.
* @see ok()
*/
BSpline<T> *apply (const T *y);
/**
* Return array of the node coordinates. Returns 0 if not ok(). The
* array of nodes returned by nodes() belongs to the object and should
* not be deleted; it will also be invalid if the object is destroyed.
*/
const T *nodes (int *nnodes);
/**
* Return the number of nodes (one more than the number of intervals).
*/
int nNodes () { return M+1; }
/**
* Number of original x values.
*/
int nX () { return NX; }
/// Minimum x value found.
T Xmin () { return xmin; }
/// Maximum x value found.
T Xmax () { return xmin + (M * DX); }
/**
* Return the Alpha value for a given wavelength. Note that this
* depends on the current node interval length (DX).
*/
double Alpha (double wavelength);
/**
* Return alpha currently in use by this domain.
*/
double Alpha () { return alpha; }
/**
* Return the current state of the object, either ok or not ok.
* Use this method to test for valid state after construction or after
* a call to setDomain(). ok() will return false if either fail, such
* as when an appropriate number of nodes and node interval cannot be
* found for a given wavelength, or when the linear equation for the
* coefficients cannot be solved.
*/
bool ok () { return OK; }
virtual ~BSplineBase();
protected:
typedef BSplineBaseP<T> Base;
// Provided
double waveLength; // Cutoff wavelength (l sub c)
int NX;
int K; // Degree of derivative constraint (currently fixed at 2)
int BC; // Boundary conditions type (0,1,2)
// Derived
T xmax;
T xmin;
int M; // Number of intervals (M+1 nodes)
double DX; // Interval length in same units as X
double alpha;
bool OK;
Base *base; // Hide more complicated state members
// from the public interface.
bool Setup (int num_nodes = 0);
void calculateQ ();
double qDelta (int m1, int m2);
double Beta (int m);
void addP ();
bool factor ();
double Basis (int m, T x);
double DBasis (int m, T x);
static const double BoundaryConditions[3][4];
static const double BS_PI;
double Ratiod (int&, double &, double &);
};
/*
* Original BSpline.h start here
*/
template <class T> struct BSplineP;
/**
* Used to evaluate a BSpline.
* Inherits the BSplineBase domain information and interface and adds
* smoothing. See the BSplineBase documentation for a summary of the
* BSpline interface.
*/
template <class T>
class BSpline : public BSplineBase<T>
{
public:
/**
* Create a single spline with the parameters required to set up
* the domain and subsequently smooth the given set of y values.
* The y values must correspond to each of the values in the x array.
* If either the domain setup fails or the spline cannot be solved,
* the state will be set to not ok.
*
* @see ok().
*
* @param x The array of x values in the domain.
* @param nx The number of values in the @p x array.
* @param y The array of y values corresponding to each of the
* nX() x values in the domain.
* @param wl The cutoff wavelength, in the same units as the
* @p x values. A wavelength of zero disables
* the derivative constraint.
* @param bc_type The enumerated boundary condition type. If
* omitted it defaults to BC_ZERO_SECOND.
* @param num_nodes The number of nodes to use for the cubic b-spline.
* If less than 2 a "reasonable" number will be
* calculated automatically, taking into account
* the given cutoff wavelength.
*/
BSpline (const T *x, int nx, /* independent variable */
const T *y, /* dependent values @ ea X */
double wl, /* cutoff wavelength */
int bc_type = BSplineBase<T>::BC_ZERO_SECOND,
int num_nodes = 0);
/**
* A BSpline curve can be derived from a separate @p base and a set
* of data points @p y over that base.
*/
BSpline (BSplineBase<T> &base, const T *y);
/**
* Solve the spline curve for a new set of y values. Returns false
* if the solution fails.
*
* @param y The array of y values corresponding to each of the nX()
* x values in the domain.
*/
bool solve (const T *y);
/**
* Return the evaluation of the smoothed curve
* at a particular @p x value. If current state is not ok(), returns 0.
*/
T evaluate (T x);
/**
* Return the first derivative of the spline curve at the given @p x.
* Returns zero if the current state is not ok().
*/
T slope (T x);
/**
* Return the @p n-th basis coefficient, from 0 to M. If the current
* state is not ok(), or @p n is out of range, the method returns zero.
*/
T coefficient (int n);
virtual ~BSpline();
using BSplineBase<T>::Debug;
using BSplineBase<T>::Basis;
using BSplineBase<T>::DBasis;
protected:
using BSplineBase<T>::OK;
using BSplineBase<T>::M;
using BSplineBase<T>::NX;
using BSplineBase<T>::DX;
using BSplineBase<T>::base;
using BSplineBase<T>::xmin;
using BSplineBase<T>::xmax;
// Our hidden state structure
BSplineP<T> *s;
T mean; // Fit without mean and add it in later
};
#endif

View File

@ -0,0 +1,375 @@
/* -*- 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/
*
*************************************************************************/
/**
* Template for a diagonally banded matrix.
**/
#ifndef _BANDEDMATRIX_ID
#define _BANDEDMATRIX_ID "$Id$"
#include <vector>
#include <algorithm>
#include <iostream>
template <class T> class BandedMatrixRow;
template <class T> class BandedMatrix
{
public:
typedef unsigned int size_type;
typedef T element_type;
// Create a banded matrix with the same number of bands above and below
// the diagonal.
BandedMatrix (int N_ = 1, int nbands_off_diagonal = 0) : bands(0)
{
if (! setup (N_, nbands_off_diagonal))
setup ();
}
// Create a banded matrix by naming the first and last non-zero bands,
// where the diagonal is at zero, and bands below the diagonal are
// negative, bands above the diagonal are positive.
BandedMatrix (int N_, int first, int last) : bands(0)
{
if (! setup (N_, first, last))
setup ();
}
// Copy constructor
BandedMatrix (const BandedMatrix &b) : bands(0)
{
Copy (*this, b);
}
inline bool setup (int N_ = 1, int noff = 0)
{
return setup (N_, -noff, noff);
}
bool setup (int N_, int first, int last)
{
// Check our limits first and make sure they make sense.
// Don't change anything until we know it will work.
if (first > last || N_ <= 0)
return false;
// Need at least as many N_ as columns and as rows in the bands.
if (N_ < abs(first) || N_ < abs(last))
return false;
top = last;
bot = first;
N = N_;
out_of_bounds = T();
// Finally setup the diagonal vectors
nbands = last - first + 1;
if (bands) delete[] bands;
bands = new std::vector<T>[nbands];
int i;
for (i = 0; i < nbands; ++i)
{
// The length of each array varies with its distance from the
// diagonal
int len = N - (abs(bot + i));
bands[i].clear ();
bands[i].resize (len);
}
return true;
}
BandedMatrix<T> & operator= (const BandedMatrix<T> &b)
{
return Copy (*this, b);
}
BandedMatrix<T> & operator= (const T &e)
{
int i;
for (i = 0; i < nbands; ++i)
{
std::fill_n (bands[i].begin(), bands[i].size(), e);
}
out_of_bounds = e;
return (*this);
}
~BandedMatrix ()
{
if (bands)
delete[] bands;
}
private:
// Return false if coordinates are out of bounds
inline bool check_bounds (int i, int j, int &v, int &e) const
{
v = (j - i) - bot;
e = (i >= j) ? j : i;
return !(v < 0 || v >= nbands ||
e < 0 || (unsigned int)e >= bands[v].size());
}
static BandedMatrix & Copy (BandedMatrix &a, const BandedMatrix &b)
{
if (a.bands) delete[] a.bands;
a.top = b.top;
a.bot = b.bot;
a.N = b.N;
a.out_of_bounds = b.out_of_bounds;
a.nbands = a.top - a.bot + 1;
a.bands = new std::vector<T>[a.nbands];
int i;
for (i = 0; i < a.nbands; ++i)
{
a.bands[i] = b.bands[i];
}
return a;
}
public:
T &element (int i, int j)
{
int v, e;
if (check_bounds(i, j, v, e))
return (bands[v][e]);
else
return out_of_bounds;
}
const T &element (int i, int j) const
{
int v, e;
if (check_bounds(i, j, v, e))
return (bands[v][e]);
else
return out_of_bounds;
}
inline T & operator() (int i, int j)
{
return element (i-1,j-1);
}
inline const T & operator() (int i, int j) const
{
return element (i-1,j-1);
}
size_type num_rows() const { return N; }
size_type num_cols() const { return N; }
const BandedMatrixRow<T> operator[] (int row) const
{
return BandedMatrixRow<T>(*this, row);
}
BandedMatrixRow<T> operator[] (int row)
{
return BandedMatrixRow<T>(*this, row);
}
private:
int top;
int bot;
int nbands;
std::vector<T> *bands;
int N;
T out_of_bounds;
};
template <class T>
std::ostream &operator<< (std::ostream &out, const BandedMatrix<T> &m)
{
unsigned int i, j;
for (i = 0; i < m.num_rows(); ++i)
{
for (j = 0; j < m.num_cols(); ++j)
{
out << m.element (i, j) << " ";
}
out << std::endl;
}
return out;
}
/*
* Helper class for the intermediate in the [][] operation.
*/
template <class T> class BandedMatrixRow
{
public:
BandedMatrixRow (const BandedMatrix<T> &_m, int _row) : bm(_m), i(_row)
{ }
BandedMatrixRow (BandedMatrix<T> &_m, int _row) : bm(_m), i(_row)
{ }
~BandedMatrixRow () {}
typename BandedMatrix<T>::element_type & operator[] (int j)
{
return const_cast<BandedMatrix<T> &>(bm).element (i, j);
}
const typename BandedMatrix<T>::element_type & operator[] (int j) const
{
return bm.element (i, j);
}
private:
const BandedMatrix<T> &bm;
int i;
};
/*
* Vector multiplication
*/
template <class Vector, class Matrix>
Vector operator* (const Matrix &m, const Vector &v)
{
typename Matrix::size_type M = m.num_rows();
typename Matrix::size_type N = m.num_cols();
assert (N <= v.size());
//if (M > v.size())
// return Vector();
Vector r(N);
for (unsigned int i = 0; i < M; ++i)
{
typename Matrix::element_type sum = 0;
for (unsigned int j = 0; j < N; ++j)
{
sum += m[i][j] * v[j];
}
r[i] = sum;
}
return r;
}
/*
* LU factor a diagonally banded matrix using Crout's algorithm, but
* limiting the trailing sub-matrix multiplication to the non-zero
* elements in the diagonal bands. Return nonzero if a problem occurs.
*/
template <class MT>
int LU_factor_banded (MT &A, unsigned int bands)
{
typename MT::size_type M = A.num_rows();
typename MT::size_type N = A.num_cols();
if (M != N)
return 1;
typename MT::size_type i,j,k;
typename MT::element_type sum;
for (j = 1; j <= N; ++j)
{
// Check for zero pivot
if ( A(j,j) == 0 )
return 1;
// Calculate rows above and on diagonal. A(1,j) remains as A(1,j).
for (i = (j > bands) ? j-bands : 1; i <= j; ++i)
{
sum = 0;
for (k = (j > bands) ? j-bands : 1; k < i; ++k)
{
sum += A(i,k)*A(k,j);
}
A(i,j) -= sum;
}
// Calculate rows below the diagonal.
for (i = j+1; (i <= M) && (i <= j+bands); ++i)
{
sum = 0;
for (k = (i > bands) ? i-bands : 1; k < j; ++k)
{
sum += A(i,k)*A(k,j);
}
A(i,j) = (A(i,j) - sum) / A(j,j);
}
}
return 0;
}
/*
* Solving (LU)x = B. First forward substitute to solve for y, Ly = B.
* Then backwards substitute to find x, Ux = y. Return nonzero if a
* problem occurs. Limit the substitution sums to the elements on the
* bands above and below the diagonal.
*/
template <class MT, class Vector>
int LU_solve_banded(const MT &A, Vector &b, unsigned int bands)
{
typename MT::size_type i,j;
typename MT::size_type M = A.num_rows();
typename MT::size_type N = A.num_cols();
typename MT::element_type sum;
if (M != N || M == 0)
return 1;
// Forward substitution to find y. The diagonals of the lower
// triangular matrix are taken to be 1.
for (i = 2; i <= M; ++i)
{
sum = b[i-1];
for (j = (i > bands) ? i-bands : 1; j < i; ++j)
{
sum -= A(i,j)*b[j-1];
}
b[i-1] = sum;
}
// Now for the backward substitution
b[M-1] /= A(M,M);
for (i = M-1; i >= 1; --i)
{
if (A(i,i) == 0) // oops!
return 1;
sum = b[i-1];
for (j = i+1; (j <= N) && (j <= i+bands); ++j)
{
sum -= A(i,j)*b[j-1];
}
b[i-1] = sum / A(i,i);
}
return 0;
}
#endif /* _BANDEDMATRIX_ID */

44
xs/src/BSpline/COPYRIGHT Normal file
View File

@ -0,0 +1,44 @@
Copyright (c) 1998-2009,2015
University Corporation for Atmospheric Research, UCAR
All rights reserved.
This software is licensed with the standard BSD license:
http://www.opensource.org/licenses/bsd-license.html
When citing this software, here is a suggested reference:
This software is written by Gary Granger of the National Center for
Atmospheric Research (NCAR), sponsored by the National Science Foundation
(NSF). The algorithm is based on the cubic spline described by Katsuyuki
Ooyama in Montly Weather Review, Vol 115, October 1987. This
implementation has benefited from comparisons with a previous FORTRAN
implementation by James L. Franklin, NOAA/Hurricane Research Division.
The text of the license is reproduced below:
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the UCAR nor the names of its contributors may be
used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,123 @@
/* portable_endian.h
* from https://gist.github.com/panzi/6856583
* "License": Public Domain
* I, Mathias Panzenböck, place this file hereby into the public domain. Use it
* at your own risk for whatever you like. In case there are jurisdictions
* that don't support putting things in the public domain you can also consider
* it to be "dual licensed" under the BSD, MIT and Apache licenses, if you want
* to. This code is trivial anyway. Consider it an example on how to get the
* endian conversion functions on different platforms.
*/
#ifndef PORTABLE_ENDIAN_H__
#define PORTABLE_ENDIAN_H__
#if (defined(_WIN16) || defined(_WIN32) || defined(_WIN64)) && !defined(__WINDOWS__)
# define __WINDOWS__
#endif
#if defined(__linux__) || defined(__CYGWIN__)
# include <endian.h>
#elif defined(__APPLE__)
# include <libkern/OSByteOrder.h>
# define htobe16(x) OSSwapHostToBigInt16(x)
# define htole16(x) OSSwapHostToLittleInt16(x)
# define be16toh(x) OSSwapBigToHostInt16(x)
# define le16toh(x) OSSwapLittleToHostInt16(x)
# define htobe32(x) OSSwapHostToBigInt32(x)
# define htole32(x) OSSwapHostToLittleInt32(x)
# define be32toh(x) OSSwapBigToHostInt32(x)
# define le32toh(x) OSSwapLittleToHostInt32(x)
# define htobe64(x) OSSwapHostToBigInt64(x)
# define htole64(x) OSSwapHostToLittleInt64(x)
# define be64toh(x) OSSwapBigToHostInt64(x)
# define le64toh(x) OSSwapLittleToHostInt64(x)
# define __BYTE_ORDER BYTE_ORDER
# define __BIG_ENDIAN BIG_ENDIAN
# define __LITTLE_ENDIAN LITTLE_ENDIAN
# define __PDP_ENDIAN PDP_ENDIAN
#elif defined(__OpenBSD__) || defined(__FreeBSD__)
# include <sys/endian.h>
#elif defined(__NetBSD__) || defined(__DragonFly__)
# include <sys/endian.h>
# define be16toh(x) betoh16(x)
# define le16toh(x) letoh16(x)
# define be32toh(x) betoh32(x)
# define le32toh(x) letoh32(x)
# define be64toh(x) betoh64(x)
# define le64toh(x) letoh64(x)
#elif defined(__WINDOWS__)
/* # include <winsock2.h> */
# include <sys/param.h>
# if BYTE_ORDER == LITTLE_ENDIAN
# define htobe16(x) htons(x)
# define htole16(x) (x)
# define be16toh(x) ntohs(x)
# define le16toh(x) (x)
# define htobe32(x) htonl(x)
# define htole32(x) (x)
# define be32toh(x) ntohl(x)
# define le32toh(x) (x)
# define htobe64(x) htonll(x)
# define htole64(x) (x)
# define be64toh(x) ntohll(x)
# define le64toh(x) (x)
# elif BYTE_ORDER == BIG_ENDIAN
/* that would be xbox 360 */
# define htobe16(x) (x)
# define htole16(x) __builtin_bswap16(x)
# define be16toh(x) (x)
# define le16toh(x) __builtin_bswap16(x)
# define htobe32(x) (x)
# define htole32(x) __builtin_bswap32(x)
# define be32toh(x) (x)
# define le32toh(x) __builtin_bswap32(x)
# define htobe64(x) (x)
# define htole64(x) __builtin_bswap64(x)
# define be64toh(x) (x)
# define le64toh(x) __builtin_bswap64(x)
# else
# error byte order not supported
# endif
# define __BYTE_ORDER BYTE_ORDER
# define __BIG_ENDIAN BIG_ENDIAN
# define __LITTLE_ENDIAN LITTLE_ENDIAN
# define __PDP_ENDIAN PDP_ENDIAN
#else
# error platform not supported
#endif
#endif

View File

@ -21,11 +21,13 @@
*/
#include <float.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "portable_endian.h"
#include "stl.h"
#ifndef SEEK_SET
@ -68,7 +70,7 @@ stl_initialize(stl_file *stl) {
void
stl_count_facets(stl_file *stl, const ADMESH_CHAR *file) {
long file_size;
int header_num_facets;
uint32_t header_num_facets;
int num_facets;
int i;
size_t s;
@ -123,7 +125,7 @@ stl_count_facets(stl_file *stl, const ADMESH_CHAR *file) {
}
/* Read the int following the header. This should contain # of facets */
if((!fread(&header_num_facets, sizeof(int), 1, stl->fp)) || (num_facets != header_num_facets)) {
if((!fread(&header_num_facets, sizeof(uint32_t), 1, stl->fp)) || (uint32_t)num_facets != le32toh(header_num_facets)) {
fprintf(stderr,
"Warning: File size doesn't match number of facets in the header\n");
@ -260,7 +262,24 @@ stl_reallocate(stl_file *stl) {
void
stl_read(stl_file *stl, int first_facet, int first) {
stl_facet facet;
int i;
int i, j;
const int facet_float_length = 12;
float *facet_floats[12];
char facet_buffer[12 * sizeof(float)];
uint32_t endianswap_buffer; /* for byteswapping operations */
facet_floats[0] = &facet.normal.x;
facet_floats[1] = &facet.normal.y;
facet_floats[2] = &facet.normal.z;
facet_floats[3] = &facet.vertex[0].x;
facet_floats[4] = &facet.vertex[0].y;
facet_floats[5] = &facet.vertex[0].z;
facet_floats[6] = &facet.vertex[1].x;
facet_floats[7] = &facet.vertex[1].y;
facet_floats[8] = &facet.vertex[1].z;
facet_floats[9] = &facet.vertex[2].x;
facet_floats[10] = &facet.vertex[2].y;
facet_floats[11] = &facet.vertex[2].z;
if (stl->error) return;
@ -274,11 +293,19 @@ stl_read(stl_file *stl, int first_facet, int first) {
if(stl->stats.type == binary)
/* Read a single facet from a binary .STL file */
{
/* we assume little-endian architecture! */
if (fread(&facet, 1, SIZEOF_STL_FACET, stl->fp) != SIZEOF_STL_FACET) {
if(fread(facet_buffer, sizeof(facet_buffer), 1, stl->fp)
+ fread(&facet.extra, sizeof(char), 2, stl->fp) != 3) {
perror("Cannot read facet");
stl->error = 1;
return;
}
for(j = 0; j < facet_float_length; j++) {
/* convert LE float to host byte order */
memcpy(&endianswap_buffer, facet_buffer + j * sizeof(float), 4);
endianswap_buffer = le32toh(endianswap_buffer);
memcpy(facet_floats[j], &endianswap_buffer, 4);
}
} else
/* Read a single facet from an ASCII .STL file */
{

View File

@ -99,9 +99,11 @@ namespace nowide {
///
inline int putenv(char *string)
{
if (string == nullptr) return -1;
char const *key = string;
char const *key_end = string;
while(*key_end!='=' && key_end!='\0')
while(*key_end!='=' && *key_end!='\0')
key_end++;
if(*key_end == '\0')
return -1;

38195
xs/src/exprtk/exprtk.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -254,9 +254,9 @@ BridgeDetector::coverage(double angle) const
*/
}
/* This method returns the bridge edges (as polylines) that are not supported
but would allow the entire bridge area to be bridged with detected angle
if supported too */
/// This method returns the bridge edges (as polylines) that are not supported
/// but would allow the entire bridge area to be bridged with detected angle
/// if supported too
Polylines
BridgeDetector::unsupported_edges(double angle) const
{

View File

@ -25,6 +25,9 @@ public:
bool detect_angle();
Polygons coverage() const;
Polygons coverage(double angle) const;
/// Return the bridge edges that are not currently supported but would permit use of the supplied
/// bridge angle if it was supported.
Polylines unsupported_edges(double angle = -1) const;
private:

View File

@ -0,0 +1,150 @@
#include <string>
#include <sstream>
#include <exprtk/exprtk.hpp>
#include "ConditionalGCode.hpp"
namespace Slic3r {
// trim from start (in place)
static inline void ltrim(std::string &s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) {
return !std::isspace(ch);
}));
}
// trim from end (in place)
static inline void rtrim(std::string &s) {
s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) {
return !std::isspace(ch);
}).base(), s.end());
}
// trim from both ends (in place)
static inline void trim(std::string &s) {
ltrim(s);
rtrim(s);
}
static inline void replace_substr(std::string &str, const std::string& from, const std::string& to)
{
size_t start_pos = 0;
while((start_pos = str.find(from, start_pos)) != std::string::npos) {
str.replace(start_pos, from.length(), to);
start_pos += to.length(); // Handles case where 'to' is a substring of 'from'
}
}
/// Start of recursive function to parse gcode file.
std::string apply_math(const std::string& input) {
std::string temp_string(input);
replace_substr(temp_string, std::string("\\{"), std::string("\x80"));
replace_substr(temp_string, std::string("\\}"), std::string("\x81"));
temp_string = expression(temp_string);
replace_substr(temp_string, std::string("\x80"), std::string("{"));
replace_substr(temp_string, std::string("\x81"), std::string("}"));
return temp_string;
}
/// Evaluate expressions with exprtk
/// Everything must resolve to a number.
std::string evaluate(const std::string& expression_string) {
std::stringstream result;
#if SLIC3R_DEBUG
std::cerr << __FILE__ << ":" << __LINE__ << " "<< "Evaluating expression: " << expression_string << std::endl;
#endif
double num_result = double(0);
if ( exprtk::compute(expression_string, num_result)) {
result << num_result;
} else {
#if SLIC3R_DEBUG
std::cerr << __FILE__ << ":" << __LINE__ << " "<< "Failed to parse: " << expression_string.c_str() << std::endl;
#endif
result << "\x80" << expression_string << "\x81";
}
std::string output = result.str();
trim(output);
return output;
}
/// Parse an expression and return a string. We assume that PlaceholderParser has expanded all variables.
std::string expression(const std::string& input, const int depth) {
// check for subexpressions first.
std::string buffer(input);
std::stringstream tmp;
bool is_conditional = false;
auto open_bracket = std::count(buffer.begin(), buffer.end(), '{');
auto close_bracket = std::count(buffer.begin(), buffer.end(), '}');
if (open_bracket != close_bracket) return buffer;
auto i = 0;
#ifdef SLIC3R_DEBUG
std::cerr << __FILE__ << ":" << __LINE__ << " " << "depth " << depth << " input str: " << input << std::endl;
#endif
if (open_bracket == 0 && depth > 0) { // no subexpressions, resolve operators.
return evaluate(buffer);
}
while (open_bracket > 0) {
// a subexpression has been found, find the end of it.
// find the last open bracket, then the first open bracket after it.
size_t pos_if = buffer.rfind("{if");
size_t pos = buffer.rfind("{");
size_t shift_if = ( pos_if >= pos && pos_if < buffer.size() ? 3 : 1 );
is_conditional = (shift_if == 3); // conditional statement
pos_if = (pos_if > buffer.size() ? pos : pos_if);
pos = (pos_if > pos ? pos_if : pos);
#ifdef SLIC3R_DEBUG
std::cerr << __FILE__ << ":" << __LINE__ << " "<< "depth " << depth << " loop " << i << " pos: " << pos << std::endl;
#endif
// find the first bracket after the position
size_t end_pos = buffer.find("}", pos);
if (end_pos > buffer.size()) return buffer; // error!
if (pos > 0)
tmp << buffer.substr(0, pos);
std::string retval = expression(buffer.substr(pos+shift_if, ( end_pos - ( pos+shift_if))), depth+1);
#ifdef SLIC3R_DEBUG
if (is_conditional) {
std::cerr << __FILE__ << ":" << __LINE__ << " "<< "depth " << depth << " loop " << i << " return: '" << retval << "'" << std::endl;
}
#endif
if (is_conditional && retval == "0") {
is_conditional = false;
end_pos = buffer.find('\n', pos); // drop everything to the next line
} else if (!is_conditional) {
tmp << retval;
} // in either case, don't print the output from {if}
if (end_pos < buffer.size()-1) // special case, last } is final char
tmp << buffer.substr(end_pos+1, buffer.size() - (end_pos));
buffer = tmp.str();
// flush the internal string.
tmp.str(std::string());
#ifdef SLIC3R_DEBUG
std::cerr << __FILE__ << ":" << __LINE__ << " "<< "depth: " << depth <<" Result from loop " << i << ": " << buffer << std::endl;
#endif
open_bracket = std::count(buffer.begin(), buffer.end(), '{');
close_bracket = std::count(buffer.begin(), buffer.end(), '}');
i++;
}
// {if that resolves to false/0 signifies dropping everything up to the next newline from the input buffer.
// Also remove the result of the {if itself.
return buffer;
}
}

View File

@ -0,0 +1,38 @@
/**
* https://github.com/alexrj/Slic3r/wiki/Conditional-Gcode-Syntax-Spec
*
*/
#ifndef slic3r_ConditionalGcode_hpp_
#define slic3r_ConditionalGcode_hpp_
#include <iostream>
#include <string>
#include <sstream>
// Valid start tokens
// {, {if
//
// Valid end tokens
// }
//
// Special case:
//
// {if is special, it indicates that the rest of the line is dropped (ignored) if
// it evaluates to False/0.
namespace Slic3r {
/// Recursive expression parser. Offloads mathematics to exprtk.
/// Precondition: All strings inside {} are able to be understood by exprtk (and thus parsed to a number).
/// Starts from the end of the string and works from the inside out.
/// Any statements that resolve to {if0} will remove everything on the same line.
std::string expression(const std::string& input, const int depth = 0);
/// External access function to begin replac
std::string apply_math(const std::string& input);
}
#endif

View File

@ -32,6 +32,9 @@ std::string escape_string_cstyle(const std::string &str)
if (c == '\n' || c == '\r') {
(*outptr ++) = '\\';
(*outptr ++) = 'n';
} else if (c == '\\'){
(*outptr ++) = '\\';
(*outptr ++) = '\\';
} else
(*outptr ++) = c;
}
@ -256,7 +259,7 @@ ConfigBase::apply_only(const ConfigBase &other, const t_config_option_keys &opt_
for (const t_config_option_key &opt_key : opt_keys) {
ConfigOption* my_opt = this->option(opt_key, true);
if (my_opt == NULL) {
if (ignore_nonexistent == false) throw "Attempt to apply non-existent option";
if (ignore_nonexistent == false) throw UnknownOptionException();
continue;
}
@ -339,7 +342,7 @@ ConfigBase::get_abs_value(const t_config_option_key &opt_key) const {
} else if (const ConfigOptionFloat* optv = dynamic_cast<const ConfigOptionFloat*>(opt)) {
return optv->value;
} else {
throw "Not a valid option type for get_abs_value()";
throw std::runtime_error("Not a valid option type for get_abs_value()");
}
}
@ -487,7 +490,7 @@ DynamicConfig::optptr(const t_config_option_key &opt_key, bool create) {
optv->keys_map = &optdef->enum_keys_map;
opt = static_cast<ConfigOption*>(optv);
} else {
throw "Unknown option type";
throw std::runtime_error("Unknown option type");
}
this->options[opt_key] = opt;
return opt;

View File

@ -185,7 +185,7 @@ ExPolygon::simplify(double tolerance, ExPolygons* expolygons) const
}
void
ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polylines) const
ExPolygon::medial_axis(const ExPolygon &bounds, double max_width, double min_width, ThickPolylines* polylines) const
{
// init helper object
Slic3r::Geometry::MedialAxis ma(max_width, min_width, this);
@ -195,41 +195,145 @@ ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polyl
ThickPolylines pp;
ma.build(&pp);
/*
/* // Commented out debug code
SVG svg("medial_axis.svg");
svg.draw(*this);
svg.draw(pp);
svg.Close();
*/
/* Find the maximum width returned; we're going to use this for validating and
filtering the output segments. */
// Find the maximum width returned; we're going to use this for validating and
// filtering the output segments.
double max_w = 0;
for (ThickPolylines::const_iterator it = pp.begin(); it != pp.end(); ++it)
max_w = fmaxf(max_w, *std::max_element(it->width.begin(), it->width.end()));
/* Loop through all returned polylines in order to extend their endpoints to the
expolygon boundaries */
// Aligned fusion: Fusion the bits at the end of lines by "increasing thickness"
// For that, we have to find other lines,
// and with a next point no more distant than the max width.
// Then, we can merge the bit from the first point to the second by following the mean.
bool changes = true;
while (changes) {
changes = false;
for (size_t i = 0; i < pp.size(); ++i) {
ThickPolyline& polyline = pp[i];
ThickPolyline* best_candidate = nullptr;
float best_dot = -1;
int best_idx = 0;
// find another polyline starting here
for (size_t j = i + 1; j < pp.size(); ++j) {
ThickPolyline& other = pp[j];
if (polyline.last_point().coincides_with(other.last_point())) {
polyline.reverse();
other.reverse();
}
else if (polyline.first_point().coincides_with(other.last_point())) {
other.reverse();
}
else if (polyline.first_point().coincides_with(other.first_point())) {
}
else if (polyline.last_point().coincides_with(other.first_point())) {
polyline.reverse();
} else {
continue;
}
//only consider the other if the next point is near us
if (polyline.points.size() < 2 && other.points.size() < 2) continue;
if (!polyline.endpoints.second || !other.endpoints.second) continue;
if (polyline.points.back().distance_to(other.points.back()) > max_width) continue;
if (polyline.points.size() != other.points.size()) continue;
Pointf v_poly(polyline.lines().front().vector().x, polyline.lines().front().vector().y);
v_poly.scale(1 / std::sqrt(v_poly.x*v_poly.x + v_poly.y*v_poly.y));
Pointf v_other(other.lines().front().vector().x, other.lines().front().vector().y);
v_other.scale(1 / std::sqrt(v_other.x*v_other.x + v_other.y*v_other.y));
float other_dot = v_poly.x*v_other.x + v_poly.y*v_other.y;
if (other_dot > best_dot) {
best_candidate = &other;
best_idx = j;
best_dot = other_dot;
}
}
if (best_candidate != nullptr) {
//assert polyline.size == best_candidate->size (see selection loop, an 'if' takes care of that)
//iterate the points
// as voronoi should create symetric thing, we can iterate synchonously
unsigned int idx_point = 1;
while (idx_point < polyline.points.size() && polyline.points[idx_point].distance_to(best_candidate->points[idx_point]) < max_width) {
//fusion
polyline.points[idx_point].x += best_candidate->points[idx_point].x;
polyline.points[idx_point].x /= 2;
polyline.points[idx_point].y += best_candidate->points[idx_point].y;
polyline.points[idx_point].y /= 2;
polyline.width[idx_point] += best_candidate->width[idx_point];
++idx_point;
}
//select if an end occur
polyline.endpoints.second &= best_candidate->endpoints.second;
//remove points that are the same or too close each other, ie simplify
for (unsigned int idx_point = 1; idx_point < polyline.points.size(); ++idx_point) {
//distance of 1 is on the sclaed coordinates, so it correspond to SCALE_FACTOR, so it's very small
if (polyline.points[idx_point - 1].distance_to(polyline.points[idx_point]) < 1) {
if (idx_point < polyline.points.size() -1) {
polyline.points.erase(polyline.points.begin() + idx_point);
} else {
polyline.points.erase(polyline.points.begin() + idx_point -1);
}
--idx_point;
}
}
//remove points that are outside of the geometry
for (unsigned int idx_point = 0; idx_point < polyline.points.size(); ++idx_point) {
//distance of 1 is on the sclaed coordinates, so it correspond to SCALE_FACTOR, so it's very small
if (!bounds.contains_b(polyline.points[idx_point])) {
polyline.points.erase(polyline.points.begin() + idx_point);
--idx_point;
}
}
if (polyline.points.size() < 2) {
//remove self
pp.erase(pp.begin() + i);
--i;
--best_idx;
}
pp.erase(pp.begin() + best_idx);
changes = true;
}
}
}
// Loop through all returned polylines in order to extend their endpoints to the
// expolygon boundaries
bool removed = false;
for (size_t i = 0; i < pp.size(); ++i) {
ThickPolyline& polyline = pp[i];
// extend initial and final segments of each polyline if they're actual endpoints
/* We assign new endpoints to temporary variables because in case of a single-line
polyline, after we extend the start point it will be caught by the intersection()
call, so we keep the inner point until we perform the second intersection() as well */
// Assign new endpoints to temporary variables because in case of a single-line
// polyline. After the start point is extended it will be caught by the intersection()
// call, so keep the inner point until the second intersection() is performed.
Point new_front = polyline.points.front();
Point new_back = polyline.points.back();
if (polyline.endpoints.first && !this->has_boundary_point(new_front)) {
if (polyline.endpoints.first && !bounds.has_boundary_point(new_front)) {
Line line(polyline.points.front(), polyline.points[1]);
// prevent the line from touching on the other side, otherwise intersection() might return that solution
if (polyline.points.size() == 2) line.b = line.midpoint();
line.extend_start(max_width);
(void)this->contour.intersection(line, &new_front);
(void)bounds.contour.intersection(line, &new_front);
}
if (polyline.endpoints.second && !this->has_boundary_point(new_back)) {
if (polyline.endpoints.second && !bounds.has_boundary_point(new_back)) {
Line line(
*(polyline.points.end() - 2),
polyline.points.back()
@ -239,60 +343,83 @@ ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polyl
if (polyline.points.size() == 2) line.a = line.midpoint();
line.extend_end(max_width);
(void)this->contour.intersection(line, &new_back);
(void)bounds.contour.intersection(line, &new_back);
}
polyline.points.front() = new_front;
polyline.points.back() = new_back;
}
// If we removed any short polylines, we now try to connect consecutive polylines
// in order to allow loop detection. Note that this algorithm is greedier than
// MedialAxis::process_edge_neighbors(), as it will connect random pairs of
// polylines even when more than two start from the same point. This has no
// drawbacks since we optimize later using nearest-neighbor which would do the
// same, but should we use a more sophisticated optimization algorithm.
// We should not connect polylines when more than two meet.
// Optimisation of the old algorithm : Select the most "straight line" choice
// when we merge with an other line at a point with more than two meet.
/* remove too short polylines
(we can't do this check before endpoints extension and clipping because we don't
know how long will the endpoints be extended since it depends on polygon thickness
which is variable - extension will be <= max_width/2 on each side) */
for (size_t i = 0; i < pp.size(); ++i) {
ThickPolyline& polyline = pp[i];
if (polyline.endpoints.first && polyline.endpoints.second) continue; // optimization
ThickPolyline* best_candidate = nullptr;
float best_dot = -1;
int best_idx = 0;
// find another polyline starting here
for (size_t j = i+1; j < pp.size(); ++j) {
ThickPolyline& other = pp[j];
if (polyline.last_point().coincides_with(other.last_point())) {
other.reverse();
} else if (polyline.first_point().coincides_with(other.last_point())) {
polyline.reverse();
other.reverse();
} else if (polyline.first_point().coincides_with(other.first_point())) {
polyline.reverse();
} else if (!polyline.last_point().coincides_with(other.first_point())) {
continue;
}
Pointf v_poly(polyline.lines().back().vector().x, polyline.lines().back().vector().y);
v_poly.scale(1 / std::sqrt(v_poly.x*v_poly.x + v_poly.y*v_poly.y));
Pointf v_other(other.lines().front().vector().x, other.lines().front().vector().y);
v_other.scale(1 / std::sqrt(v_other.x*v_other.x + v_other.y*v_other.y));
float other_dot = v_poly.x*v_other.x + v_poly.y*v_other.y;
if (other_dot > best_dot) {
best_candidate = &other;
best_idx = j;
best_dot = other_dot;
}
}
if (best_candidate != nullptr) {
polyline.points.insert(polyline.points.end(), best_candidate->points.begin() + 1, best_candidate->points.end());
polyline.width.insert(polyline.width.end(), best_candidate->width.begin(), best_candidate->width.end());
polyline.endpoints.second = best_candidate->endpoints.second;
assert(polyline.width.size() == polyline.points.size()*2 - 2);
pp.erase(pp.begin() + best_idx);
}
}
for (size_t i = 0; i < pp.size(); ++i) {
ThickPolyline& polyline = pp[i];
// remove too short polylines
// (we can't do this check before endpoints extension and clipping because we don't
// know how long will the endpoints be extended since it depends on polygon thickness
// which is variable - extension will be <= max_width/2 on each side)
if ((polyline.endpoints.first || polyline.endpoints.second)
&& polyline.length() < max_w*2) {
&& polyline.length() < max_w * 2) {
pp.erase(pp.begin() + i);
--i;
removed = true;
continue;
}
}
/* If we removed any short polylines we now try to connect consecutive polylines
in order to allow loop detection. Note that this algorithm is greedier than
MedialAxis::process_edge_neighbors() as it will connect random pairs of
polylines even when more than two start from the same point. This has no
drawbacks since we optimize later using nearest-neighbor which would do the
same, but should we use a more sophisticated optimization algorithm we should
not connect polylines when more than two meet. */
if (removed) {
for (size_t i = 0; i < pp.size(); ++i) {
ThickPolyline& polyline = pp[i];
if (polyline.endpoints.first && polyline.endpoints.second) continue; // optimization
// find another polyline starting here
for (size_t j = i+1; j < pp.size(); ++j) {
ThickPolyline& other = pp[j];
if (polyline.last_point().coincides_with(other.last_point())) {
other.reverse();
} else if (polyline.first_point().coincides_with(other.last_point())) {
polyline.reverse();
other.reverse();
} else if (polyline.first_point().coincides_with(other.first_point())) {
polyline.reverse();
} else if (!polyline.last_point().coincides_with(other.first_point())) {
continue;
}
polyline.points.insert(polyline.points.end(), other.points.begin() + 1, other.points.end());
polyline.width.insert(polyline.width.end(), other.width.begin(), other.width.end());
polyline.endpoints.second = other.endpoints.second;
assert(polyline.width.size() == polyline.points.size()*2 - 2);
pp.erase(pp.begin() + j);
j = i; // restart search from i+1
}
}
}
polylines->insert(polylines->end(), pp.begin(), pp.end());
}
@ -301,7 +428,7 @@ void
ExPolygon::medial_axis(double max_width, double min_width, Polylines* polylines) const
{
ThickPolylines tp;
this->medial_axis(max_width, min_width, &tp);
this->medial_axis(*this, max_width, min_width, &tp);
polylines->insert(polylines->end(), tp.begin(), tp.end());
}

View File

@ -39,7 +39,7 @@ class ExPolygon
Polygons simplify_p(double tolerance) const;
ExPolygons simplify(double tolerance) const;
void simplify(double tolerance, ExPolygons* expolygons) const;
void medial_axis(double max_width, double min_width, ThickPolylines* polylines) const;
void medial_axis(const ExPolygon &bounds, double max_width, double min_width, ThickPolylines* polylines) const;
void medial_axis(double max_width, double min_width, Polylines* polylines) const;
void get_trapezoids(Polygons* polygons) const;
void get_trapezoids(Polygons* polygons, double angle) const;

View File

@ -11,6 +11,7 @@
#include "FillConcentric.hpp"
#include "FillHoneycomb.hpp"
#include "Fill3DHoneycomb.hpp"
#include "FillGyroid.hpp"
#include "FillPlanePath.hpp"
#include "FillRectilinear.hpp"
@ -23,6 +24,7 @@ Fill::new_from_type(const InfillPattern type)
case ipConcentric: return new FillConcentric();
case ipHoneycomb: return new FillHoneycomb();
case ip3DHoneycomb: return new Fill3DHoneycomb();
case ipGyroid: return new FillGyroid();
case ipRectilinear: return new FillRectilinear();
case ipAlignedRectilinear: return new FillAlignedRectilinear();

View File

@ -88,29 +88,25 @@ zip(const std::vector<coordf_t> &x, const std::vector<coordf_t> &y)
static std::vector<Pointfs>
makeNormalisedGrid(coordf_t z, size_t gridWidth, size_t gridHeight, size_t curveType)
{
// offset required to create a regular octagram
coordf_t octagramGap = coordf_t(0.5);
// sawtooth wave function for range f($z) = [-$octagramGap .. $octagramGap]
// sawtooth wave function
coordf_t a = std::sqrt(coordf_t(2.)); // period
coordf_t wave = fabs(fmod(z, a) - a/2.)/a*4. - 1.;
coordf_t offset = wave * octagramGap;
coordf_t offset = fabs(fmod(z, a) - a/2.)/a*2. - 0.5;
bool printHoriz = (fabs(fmod(z, a)) / a*2. < 1);
std::vector<Pointfs> points;
if ((curveType & 1) != 0) {
if (printHoriz) {
for (size_t x = 0; x <= gridWidth; ++x) {
points.push_back(Pointfs());
Pointfs &newPoints = points.back();
newPoints = zip(
perpendPoints(offset, x, gridHeight),
perpendPoints(offset, x, gridHeight),
colinearPoints(offset, 0, gridHeight));
// trim points to grid edges
trim(newPoints, coordf_t(0.), coordf_t(0.), coordf_t(gridWidth), coordf_t(gridHeight));
if (x & 1)
std::reverse(newPoints.begin(), newPoints.end());
}
}
if ((curveType & 2) != 0) {
} else {
for (size_t y = 0; y <= gridHeight; ++y) {
points.push_back(Pointfs());
Pointfs &newPoints = points.back();
@ -150,8 +146,8 @@ makeGrid(coord_t z, coord_t gridSize, size_t gridWidth, size_t gridHeight, size_
void
Fill3DHoneycomb::_fill_surface_single(
unsigned int thickness_layers,
const direction_t &direction,
ExPolygon &expolygon,
const direction_t &direction,
ExPolygon &expolygon,
Polylines* polylines_out)
{
// no rotation is supported for this infill pattern
@ -159,10 +155,10 @@ Fill3DHoneycomb::_fill_surface_single(
const coord_t distance = coord_t(scale_(this->min_spacing) / this->density);
// align bounding box to a multiple of our honeycomb grid module
// (a module is 2*$distance since one $distance half-module is
// (a module is 2*$distance since one $distance half-module is
// growing while the other $distance half-module is shrinking)
bb.min.align_to_grid(Point(2*distance, 2*distance));
// generate pattern
Polylines polylines = makeGrid(
scale_(this->z),
@ -171,7 +167,7 @@ Fill3DHoneycomb::_fill_surface_single(
ceil(bb.size().y / distance) + 1,
((this->layer_id/thickness_layers) % 2) + 1
);
// move pattern in place
for (Polylines::iterator it = polylines.begin(); it != polylines.end(); ++ it)
it->translate(bb.min.x, bb.min.y);

View File

@ -0,0 +1,199 @@
#include "../ClipperUtils.hpp"
#include "../PolylineCollection.hpp"
#include "../Surface.hpp"
#include <cmath>
#include <algorithm>
#include <iostream>
#include "FillGyroid.hpp"
namespace Slic3r {
static inline double f(double x, double z_sin, double z_cos, bool vertical, bool flip)
{
if (vertical) {
double phase_offset = (z_cos < 0 ? M_PI : 0) + M_PI;
double a = sin(x + phase_offset);
double b = - z_cos;
double res = z_sin * cos(x + phase_offset + (flip ? M_PI : 0.));
double r = sqrt(a*a + b*b);
return asin(a/r) + asin(res/r) + M_PI;
}
else {
double phase_offset = z_sin < 0 ? M_PI : 0.;
double a = cos(x + phase_offset);
double b = - z_sin;
double res = z_cos * sin(x + phase_offset + (flip ? 0 : M_PI));
double r = sqrt(a*a + b*b);
return (asin(a/r) + asin(res/r) + 0.5 * M_PI);
}
}
static inline Polyline make_wave(
const std::vector<Pointf>& one_period, double width, double height, double offset, double scaleFactor,
double z_cos, double z_sin, bool vertical)
{
std::vector<Pointf> points = one_period;
double period = points.back().x;
points.pop_back();
int n = points.size();
do {
points.emplace_back(Pointf(points[points.size()-n].x + period, points[points.size()-n].y));
} while (points.back().x < width);
points.back().x = width;
// and construct the final polyline to return:
Polyline polyline;
for (Pointf& point : points) {
point.y += offset;
point.y = std::max(0., std::min(height, point.y));
if (vertical)
std::swap(point.x, point.y);
polyline.points.emplace_back(Point(coord_t(point.x * scaleFactor), coord_t(point.y * scaleFactor)));
}
return polyline;
}
static bool sortPointf (Pointf& lfs,Pointf& rhs) { return lfs.x < rhs.x || (lfs.x == rhs.x && lfs.y < rhs.y); }
static std::vector<Pointf> make_one_period(double width, double scaleFactor, double z_cos, double z_sin, bool vertical, bool flip)
{
std::vector<Pointf> points;
double dx = M_PI_4; // very coarse spacing to begin with
double limit = std::min(2*M_PI, width);
for (double x = 0.; x < limit + EPSILON; x += dx) { // so the last point is there too
x = std::min(x, limit);
points.emplace_back(Pointf(x,f(x, z_sin,z_cos, vertical, flip)));
}
// now we will check all internal points and in case some are too far from the line connecting its neighbours,
// we will add one more point on each side:
const double tolerance = .1;
for (unsigned int i=1;i<points.size()-1;++i) {
Pointf& lp = points[i-1]; // left point
Pointf& tp = points[i]; // this point
Pointf& rp = points[i+1]; // right point
// calculate distance of the point to the line:
double dist_mm = unscale(scaleFactor * std::abs( (rp.y - lp.y)*tp.x + (lp.x - rp.x)*tp.y + (rp.x*lp.y - rp.y*lp.x) ) / std::hypot((rp.y - lp.y),(lp.x - rp.x)));
if (dist_mm > tolerance) { // if the difference from straight line is more than this
double x = 0.5f * (points[i-1].x + points[i].x);
points.emplace_back(Pointf(x, f(x, z_sin, z_cos, vertical, flip)));
x = 0.5f * (points[i+1].x + points[i].x);
points.emplace_back(Pointf(x, f(x, z_sin, z_cos, vertical, flip)));
std::sort(points.begin(), points.end(), sortPointf); // we added the points to the end, but need them all in order
--i; // decrement i so we also check the first newly added point
}
}
return points;
}
static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double line_spacing, double width, double height)
{
const double scaleFactor = scale_(line_spacing) / density_adjusted;
//scale factor for 5% : 8 712 388
// 1z = 10^-6 mm ?
const double z = gridZ / scaleFactor;
const double z_sin = sin(z);
const double z_cos = cos(z);
bool vertical = (std::abs(z_sin) <= std::abs(z_cos));
double lower_bound = 0.;
double upper_bound = height;
bool flip = true;
if (vertical) {
flip = false;
lower_bound = -M_PI;
upper_bound = width - M_PI_2;
std::swap(width,height);
}
std::vector<Pointf> one_period = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip); // creates one period of the waves, so it doesn't have to be recalculated all the time
Polylines result;
for (double y0 = lower_bound; y0 < upper_bound+EPSILON; y0 += 2*M_PI) // creates odd polylines
result.emplace_back(make_wave(one_period, width, height, y0, scaleFactor, z_cos, z_sin, vertical));
flip = !flip; // even polylines are a bit shifted
one_period = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip); // updates the one period sample
for (double y0 = lower_bound + M_PI; y0 < upper_bound+EPSILON; y0 += 2*M_PI) // creates even polylines
result.emplace_back(make_wave(one_period, width, height, y0, scaleFactor, z_cos, z_sin, vertical));
return result;
}
void FillGyroid::_fill_surface_single(
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon &expolygon,
Polylines *polylines_out)
{
// no rotation is supported for this infill pattern (yet)
BoundingBox bb = expolygon.contour.bounding_box();
// Density adjusted to have a good %of weight.
double density_adjusted = std::max(0., density * 2.);
// Distance between the gyroid waves in scaled coordinates.
coord_t distance = coord_t(scale_(this->spacing()) / density_adjusted);
// align bounding box to a multiple of our grid module
bb.min.align_to_grid(Point(2*M_PI*distance, 2*M_PI*distance));
// generate pattern
Polylines polylines = make_gyroid_waves(
scale_(this->z),
density_adjusted,
this->spacing(),
ceil(bb.size().x / distance) + 1.,
ceil(bb.size().y / distance) + 1.);
// move pattern in place
for (Polyline &polyline : polylines)
polyline.translate(bb.min.x, bb.min.y);
// clip pattern to boundaries
polylines = intersection_pl(polylines, (Polygons)expolygon);
// connect lines
if (! dont_connect && ! polylines.empty()) { // prevent calling leftmost_point() on empty collections
ExPolygon expolygon_off;
{
ExPolygons expolygons_off = offset_ex(expolygon, (float)SCALED_EPSILON);
if (! expolygons_off.empty()) {
// When expanding a polygon, the number of islands could only shrink. Therefore the offset_ex shall generate exactly one expanded island for one input island.
assert(expolygons_off.size() == 1);
std::swap(expolygon_off, expolygons_off.front());
}
}
Polylines chained = PolylineCollection::chained_path_from(
std::move(polylines),
PolylineCollection::leftmost_point(polylines), false); // reverse allowed
bool first = true;
for (Polyline &polyline : chained) {
if (! first) {
// Try to connect the lines.
Points &pts_end = polylines_out->back().points;
const Point &first_point = polyline.points.front();
const Point &last_point = pts_end.back();
// TODO: we should also check that both points are on a fill_boundary to avoid
// connecting paths on the boundaries of internal regions
// TODO: avoid crossing current infill path
if (first_point.distance_to(last_point) <= 5 * distance &&
expolygon_off.contains(Line(last_point, first_point))) {
// Append the polyline.
pts_end.insert(pts_end.end(), polyline.points.begin(), polyline.points.end());
continue;
}
}
// The lines cannot be connected.
polylines_out->emplace_back(std::move(polyline));
first = false;
}
}
}
} // namespace Slic3r

View File

@ -0,0 +1,34 @@
#ifndef slic3r_FillGyroid_hpp_
#define slic3r_FillGyroid_hpp_
#include "../libslic3r.h"
#include "Fill.hpp"
namespace Slic3r {
class FillGyroid : public Fill
{
public:
FillGyroid(){}
virtual Fill* clone() const { return new FillGyroid(*this); };
virtual ~FillGyroid() {}
/// require bridge flow since most of this pattern hangs in air
// but it's not useful as most of it is on the previous layer.
// it's just slowing it down => set it to false!
virtual bool use_bridge_flow() const { return false; }
protected:
virtual void _fill_surface_single(
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon &expolygon,
Polylines *polylines_out);
};
} // namespace Slic3r
#endif // slic3r_FillGyroid_hpp_

View File

@ -96,12 +96,12 @@ Flow::_auto_width(FlowRole role, float nozzle_diameter, float height) {
float width = ((nozzle_diameter*nozzle_diameter) * PI + (height*height) * (4.0 - PI)) / (4.0 * height);
float min = nozzle_diameter * 1.05;
float max = nozzle_diameter * 3; // cap width to 3x nozzle diameter
float max = nozzle_diameter * 1.25; // cap width to 1.25x nozzle diameter
if (role == frExternalPerimeter || role == frSupportMaterial || role == frSupportMaterialInterface) {
min = max = nozzle_diameter;
min = max = nozzle_diameter*1.1;
} else if (role != frInfill) {
// do not limit width for sparse infill so that we use full native flow for it
max = nozzle_diameter * 1.7;
// limit width a bit for sparse infill to avoid unwanted overextrusion.
max = nozzle_diameter * 1.4;
}
if (width > max) width = max;
if (width < min) width = min;

View File

@ -69,6 +69,7 @@ class Flow
static float _bridge_width(float nozzle_diameter, float bridge_flow_ratio);
/// Calculate a relatively sane extrusion width, based on height and nozzle diameter.
/// Algorithm used does not play nice with layer heights < 0.1mm.
/// To avoid extra headaches, min and max are capped at 105% and 125% of nozzle diameter.
static float _auto_width(FlowRole role, float nozzle_diameter, float height);
static float _width_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge);
};

View File

@ -262,6 +262,12 @@ GCode::set_origin(const Pointf &pointf)
this->origin = pointf;
}
std::string
GCode::notes()
{
return this->writer.notes();
}
std::string
GCode::preamble()
{
@ -420,7 +426,10 @@ GCode::extrude(ExtrusionLoop loop, std::string description, double speed)
&& loop.length() <= SMALL_PERIMETER_LENGTH
&& speed == -1)
speed = this->config.get_abs_value("small_perimeter_speed");
description = "small perimeter";
if (paths.front().role == erExternalPerimeter)
description = std::string("external ") + description;
// extrude along the path
std::string gcode;
for (ExtrusionPaths::const_iterator path = paths.begin(); path != paths.end(); ++path)
@ -497,8 +506,8 @@ std::string
GCode::_extrude(ExtrusionPath path, std::string description, double speed)
{
path.simplify(SCALED_RESOLUTION);
std::string gcode;
description = path.is_bridge() ? description + " (bridge)" : description;
// go to first point of extrusion path
if (!this->_last_pos_defined || !this->_last_pos.coincides_with(path.first_point())) {
@ -749,7 +758,9 @@ GCode::set_extruder(unsigned int extruder_id)
PlaceholderParser pp = *this->placeholder_parser;
pp.set("previous_extruder", this->writer.extruder()->id);
pp.set("next_extruder", extruder_id);
gcode += pp.process(this->config.toolchange_gcode.value) + '\n';
pp.set("previous_retraction", this->writer.extruder()->retracted);
pp.set("next_retraction", this->writer.extruders.find(extruder_id)->second.retracted);
gcode += Slic3r::apply_math(pp.process(this->config.toolchange_gcode.value)) + '\n';
}
// if ooze prevention is enabled, park current extruder in the nearest

View File

@ -10,6 +10,7 @@
#include "PlaceholderParser.hpp"
#include "Print.hpp"
#include "PrintConfig.hpp"
#include "ConditionalGCode.hpp"
#include <string>
namespace Slic3r {
@ -100,6 +101,7 @@ class GCode {
void set_extruders(const std::vector<unsigned int> &extruder_ids);
void set_origin(const Pointf &pointf);
std::string preamble();
std::string notes();
std::string change_layer(const Layer &layer);
std::string extrude(const ExtrusionEntity &entity, std::string description = "", double speed = -1);
std::string extrude(ExtrusionLoop loop, std::string description = "", double speed = -1);

View File

@ -135,8 +135,8 @@ GCodeSender::set_baud_rate(unsigned int baud_rate)
ios.c_ispeed = ios.c_ospeed = baud_rate;
ios.c_cflag &= ~CBAUD;
ios.c_cflag |= BOTHER | CLOCAL | CREAD;
ios.c_cc[VMIN] = 1; // Minimum of characters to read, prevents eof errors when 0 bytes are read
ios.c_cc[VTIME] = 1;
ios.c_cc[VMIN] = 1; // Minimum of characters to read, prevents eof errors when 0 bytes are read
ios.c_cc[VTIME] = 1;
if (ioctl(handle, TCSETS2, &ios))
printf("Error in TCSETS2: %s\n", strerror(errno));
@ -359,7 +359,7 @@ GCodeSender::on_read(const boost::system::error_code& error,
boost::algorithm::trim_left_if(line, !boost::algorithm::is_digit());
size_t toresend = boost::lexical_cast<size_t>(line.substr(0, line.find_first_not_of("0123456789")));
toresend++; // N is 0-based
if (toresend >= this->sent - this->last_sent.size()) {
if (toresend >= this->sent - this->last_sent.size() && toresend < this->last_sent.size()) {
{
boost::lock_guard<boost::mutex> l(this->queue_mutex);

View File

@ -66,12 +66,8 @@ GCodeTimeEstimator::_accelerated_move(double length, double v, double accelerati
if (half_length >= dx_init) {
half_length -= (0.5*v*t_init);
t += t_init;
t += (half_length / v); // rest of time is at constant speed.
} else {
// If too much displacement for the expected final velocity, we don't hit the max, so reduce
// the average velocity to fit the displacement we actually are looking for.
t += std::sqrt(std::abs(length) * 2.0 * acceleration) / acceleration;
}
t += (half_length / v); // constant speed for rest of the time and too short displacements
return 2.0*t; // cut in half before, so double to get full time spent.
}

View File

@ -1,4 +1,5 @@
#include "GCodeWriter.hpp"
#include "utils.hpp"
#include <algorithm>
#include <iomanip>
#include <iostream>
@ -32,11 +33,51 @@ GCodeWriter::set_extruders(const std::vector<unsigned int> &extruder_ids)
this->multiple_extruders = (*std::max_element(extruder_ids.begin(), extruder_ids.end())) > 0;
}
std::string
GCodeWriter::notes()
{
std::ostringstream gcode;
// Write the contents of the three notes sections
// a semicolon at the beginning of each line.
if (this->config.notes.getString().size() > 0) {
gcode << "; Print Config Notes: \n";
std::vector<std::string> temp_line = split_at_regex(this->config.notes.getString(),"\n");
for (auto j = temp_line.cbegin(); j != temp_line.cend(); j++) {
gcode << "; " << *j << "\n";
}
gcode << "; \n";
}
for (auto i = this->config.filament_notes.values.cbegin(); i != this->config.filament_notes.values.cend(); i++) {
if (i->size() > 0) {
gcode << "; Filament notes: \n";
std::vector<std::string> temp_line = split_at_regex(*i,"\n");
for (auto j = temp_line.cbegin(); j != temp_line.cend(); j++) {
gcode << "; " << *j << "\n";
}
gcode << "; \n";
}
}
if (this->config.printer_notes.getString().size() > 0) {
gcode << "; Printer Config Notes: \n";
std::vector<std::string> temp_line = split_at_regex(this->config.printer_notes.getString(),"\n");
for (auto j = temp_line.cbegin(); j != temp_line.cend(); j++) {
gcode << "; " << *j << "\n";
}
gcode << "; \n";
}
return gcode.str();
}
std::string
GCodeWriter::preamble()
{
std::ostringstream gcode;
if (FLAVOR_IS_NOT(gcfMakerWare)) {
gcode << "G21 ; set units to millimeters\n";
gcode << "G90 ; use absolute coordinates\n";
@ -49,7 +90,8 @@ GCodeWriter::preamble()
}
gcode << this->reset_e(true);
}
return gcode.str();
}
@ -65,7 +107,7 @@ GCodeWriter::postamble() const
std::string
GCodeWriter::set_temperature(unsigned int temperature, bool wait, int tool) const
{
wait = this->config.use_set_and_wait_extruder ? true : wait;
std::string code, comment;
if (wait && FLAVOR_IS_NOT(gcfTeacup) && FLAVOR_IS_NOT(gcfMakerWare) && FLAVOR_IS_NOT(gcfSailfish)) {
code = "M109";
@ -101,6 +143,7 @@ std::string
GCodeWriter::set_bed_temperature(unsigned int temperature, bool wait) const
{
std::string code, comment;
wait = this->config.use_set_and_wait_bed ? true : wait;
if (wait && FLAVOR_IS_NOT(gcfTeacup)) {
if (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) {
code = "M109";
@ -173,11 +216,15 @@ GCodeWriter::set_acceleration(unsigned int acceleration)
this->_last_acceleration = acceleration;
std::ostringstream gcode;
if (FLAVOR_IS(gcfRepetier)) {
if (FLAVOR_IS(gcfRepetier) || (FLAVOR_IS(gcfRepRap))) {
gcode << "M201 X" << acceleration << " Y" << acceleration;
if (this->config.gcode_comments) gcode << " ; adjust acceleration";
gcode << "\n";
}
if (FLAVOR_IS(gcfRepetier)) {
gcode << "M202 X" << acceleration << " Y" << acceleration;
} else if (FLAVOR_IS(gcfRepRap)) {
gcode << "M204 P" << acceleration << " T" << acceleration;
} else {
gcode << "M204 S" << acceleration;
}
@ -244,12 +291,16 @@ GCodeWriter::set_extruder(unsigned int extruder_id)
std::string
GCodeWriter::toolchange(unsigned int extruder_id)
{
std::ostringstream gcode;
// set the new extruder
this->_extruder = &this->extruders.find(extruder_id)->second;
//first thing to do : reset E (because a new item is now printed or with a new extruder)
gcode << this->reset_e(true);
// return the toolchange command
// if we are running a single-extruder setup, just set the extruder and return nothing
std::ostringstream gcode;
if (this->multiple_extruders) {
if (FLAVOR_IS(gcfMakerWare)) {
gcode << "M135 T";
@ -261,8 +312,6 @@ GCodeWriter::toolchange(unsigned int extruder_id)
gcode << extruder_id;
if (this->config.gcode_comments) gcode << " ; change extruder";
gcode << "\n";
gcode << this->reset_e(true);
}
return gcode.str();
}
@ -415,14 +464,18 @@ GCodeWriter::retract_for_toolchange()
return this->_retract(
this->_extruder->retract_length_toolchange(),
this->_extruder->retract_restart_extra_toolchange(),
"retract for toolchange"
"retract for toolchange",
true
);
}
std::string
GCodeWriter::_retract(double length, double restart_extra, const std::string &comment)
GCodeWriter::_retract(double length, double restart_extra, const std::string &comment, bool long_retract)
{
std::ostringstream gcode;
std::ostringstream outcomment;
outcomment << comment;
/* If firmware retraction is enabled, we use a fake value of 1
since we ignore the actual configured retract_length which
@ -439,17 +492,20 @@ GCodeWriter::_retract(double length, double restart_extra, const std::string &co
double dE = this->_extruder->retract(length, restart_extra);
if (dE != 0) {
outcomment << " extruder " << this->_extruder->id;
if (this->config.use_firmware_retraction) {
if (FLAVOR_IS(gcfMachinekit))
gcode << "G22 ; retract\n";
gcode << "G22";
else if ((FLAVOR_IS(gcfRepRap) || FLAVOR_IS(gcfRepetier)) && long_retract)
gcode << "G10 S1";
else
gcode << "G10 ; retract\n";
gcode << "G10";
} else {
gcode << "G1 " << this->_extrusion_axis << E_NUM(this->_extruder->E)
<< " F" << this->_extruder->retract_speed_mm_min;
COMMENT(comment);
gcode << "\n";
}
COMMENT(outcomment.str());
gcode << "\n";
}
if (FLAVOR_IS(gcfMakerWare))
@ -470,15 +526,17 @@ GCodeWriter::unretract()
if (dE != 0) {
if (this->config.use_firmware_retraction) {
if (FLAVOR_IS(gcfMachinekit))
gcode << "G23 ; unretract\n";
gcode << "G23";
else
gcode << "G11 ; unretract\n";
gcode << "G11";
if (this->config.gcode_comments) gcode << " ; unretract extruder " << this->_extruder->id;
gcode << "\n";
gcode << this->reset_e();
} else {
// use G1 instead of G0 because G0 will blend the restart with the previous travel move
gcode << "G1 " << this->_extrusion_axis << E_NUM(this->_extruder->E)
<< " F" << this->_extruder->retract_speed_mm_min;
if (this->config.gcode_comments) gcode << " ; unretract";
if (this->config.gcode_comments) gcode << " ; unretract extruder " << this->_extruder->id;
gcode << "\n";
}
}

View File

@ -23,6 +23,10 @@ public:
std::string extrusion_axis() const { return this->_extrusion_axis; }
void apply_print_config(const PrintConfig &print_config);
void set_extruders(const std::vector<unsigned int> &extruder_ids);
/// Write any notes provided by the user as comments in the gcode header.
std::string notes();
/// Actually write the preamble information.
std::string preamble();
std::string postamble() const;
std::string set_temperature(unsigned int temperature, bool wait = false, int tool = -1) const;
@ -56,7 +60,7 @@ private:
Pointf3 _pos;
std::string _travel_to_z(double z, const std::string &comment);
std::string _retract(double length, double restart_extra, const std::string &comment);
std::string _retract(double length, double restart_extra, const std::string &comment, bool long_retract = false);
};
} /* namespace Slic3r */

View File

@ -39,6 +39,13 @@ class POV
static bool write(TriangleMesh& mesh, std::string output_file);
};
class TMF
{
public:
static bool read(std::string input_file, Model* model);
static bool write(Model& model, std::string output_file);
};
} }
#endif

891
xs/src/libslic3r/IO/TMF.cpp Normal file
View File

@ -0,0 +1,891 @@
#include "TMF.hpp"
namespace Slic3r { namespace IO {
bool
TMFEditor::write_types()
{
// Create a new .[Content_Types].xml file to add to the zip file later.
boost::nowide::ofstream fout(".[Content_Types].xml", std::ios::out | std::ios::trunc);
if(!fout.is_open())
return false;
// Write 3MF Types.
fout << "<?xml version=\"1.0\" encoding=\"UTF-8\"?> \n";
fout << "<Types xmlns=\"" << namespaces.at("content_types") << "\">\n";
fout << "<Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/>\n";
fout << "<Default Extension=\"model\" ContentType=\"application/vnd.ms-package.3dmanufacturing-3dmodel+xml\"/>\n";
fout << "</Types>\n";
fout.close();
// Create [Content_Types].xml in the zip archive.
if(!zip_archive->add_entry("[Content_Types].xml", ".[Content_Types].xml"))
return false;
// Remove the created .[Content_Types].xml file.
if (remove(".[Content_Types].xml") != 0)
return false;
return true;
}
bool
TMFEditor::write_relationships()
{
// Create a new .rels file to add to the zip file later.
boost::nowide::ofstream fout(".rels", std::ios::out | std::ios::trunc);
if(!fout.is_open())
return false;
// Write the primary 3dmodel relationship.
fout << "<?xml version=\"1.0\" encoding=\"UTF-8\"?> \n"
<< "<Relationships xmlns=\"" << namespaces.at("relationships") <<
"\">\n<Relationship Id=\"rel0\" Target=\"/3D/3dmodel.model\" Type=\"http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel\" /></Relationships>\n";
fout.close();
// Create .rels in "_rels" folder in the zip archive.
if(!zip_archive->add_entry("_rels/.rels", ".rels"))
return false;
// Remove the created .rels file.
if (remove(".rels") != 0)
return false;
return true;
}
bool
TMFEditor::write_model()
{
// Create a new .3dmodel.model file to add to the zip file later.
boost::nowide::ofstream fout(".3dmodel.model", std::ios::out | std::ios::trunc);
if(!fout.is_open())
return false;
// Add the XML document header.
fout << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
// Write the model element. Append any necessary namespaces.
fout << "<model unit=\"millimeter\" xml:lang=\"en-US\"";
fout << " xmlns=\"" << namespaces.at("3mf") << "\"";
fout << " xmlns:slic3r=\"" << namespaces.at("slic3r") << "\"> \n";
// Write metadata.
write_metadata(fout);
// Write resources.
fout << " <resources> \n";
// Write Object
int object_index = 0;
for(const auto object : model->objects)
write_object(fout, object, object_index++);
// Close resources
fout << " </resources> \n";
// Write build element.
write_build(fout);
// Close the model element.
fout << "</model>\n";
fout.close();
// Create .3dmodel.model in "3D" folder in the zip archive.
if(!zip_archive->add_entry("3D/3dmodel.model", ".3dmodel.model"))
return false;
// Remove the created .rels file.
if (remove(".3dmodel.model") != 0)
return false;
return true;
}
bool
TMFEditor::write_metadata(boost::nowide::ofstream& fout)
{
// Write the model metadata.
for (const auto metadata : model->metadata){
fout << " <metadata name=\"" << metadata.first << "\">" << metadata.second << "</metadata>\n";
}
// Write Slic3r metadata carrying the version number.
fout << " <slic3r:metadata version=\"" << SLIC3R_VERSION << "\"/>\n";
return true;
}
bool
TMFEditor::write_object(boost::nowide::ofstream& fout, const ModelObject* object, int index)
{
// Create the new object element.
fout << " <object id=\"" << (index + object_id) << "\" type=\"model\"";
// Add part number if found.
if (object->part_number != -1)
fout << " partnumber=\"" << (object->part_number) << "\"";
fout << ">\n";
// Write Slic3r custom configs.
for (const auto &key : object->config.keys()){
fout << " <slic3r:object type=\"" << key
<< "\" config=\"" << object->config.serialize(key) << "\"" << "/>\n";
}
// Create mesh element which contains the vertices and the volumes.
fout << " <mesh>\n";
// Create vertices element.
fout << " <vertices>\n";
// Save the start offset of each volume vertices in the object.
std::vector<int> vertices_offsets;
int num_vertices = 0;
for (const auto volume : object->volumes){
// Require mesh vertices.
volume->mesh.require_shared_vertices();
vertices_offsets.push_back(num_vertices);
const auto &stl = volume->mesh.stl;
for (int i = 0; i < stl.stats.shared_vertices; ++i)
{
// Subtract origin_translation in order to restore the coordinates of the parts
// before they were imported. Otherwise, when this 3MF file is reimported parts
// will be placed in the platter correctly, but we will have lost origin_translation
// thus any additional part added will not align with the others.
// In order to do this we compensate for this translation in the instance placement
// below.
fout << " <vertex";
fout << " x=\"" << (stl.v_shared[i].x - object->origin_translation.x) << "\"";
fout << " y=\"" << (stl.v_shared[i].y - object->origin_translation.y) << "\"";
fout << " z=\"" << (stl.v_shared[i].z - object->origin_translation.z) << "\"/>\n";
}
num_vertices += stl.stats.shared_vertices;
}
// Close the vertices element.
fout << " </vertices>\n";
// Append volumes in triangles element.
fout << " <triangles>\n";
// Save the start offset (triangle offset) of each volume (To be saved for writing Slic3r custom configs).
std::vector<int> triangles_offsets;
int num_triangles = 0;
int i_volume = 0;
for (const auto volume : object->volumes) {
int vertices_offset = vertices_offsets[i_volume];
triangles_offsets.push_back(num_triangles);
// Add the volume triangles to the triangles list.
for (int i = 0; i < volume->mesh.stl.stats.number_of_facets; ++i){
fout << " <triangle";
for (int j = 0; j < 3; j++){
fout << " v" << (j+1) << "=\"" << (volume->mesh.stl.v_indices[i].vertex[j] + vertices_offset) << "\"";
}
fout << "/>\n";
num_triangles++;
}
i_volume++;
}
triangles_offsets.push_back(num_triangles);
// Close the triangles element
fout << " </triangles>\n";
// Add Slic3r volumes group.
fout << " <slic3r:volumes>\n";
// Add each volume as <slic3r:volume> element containing Slic3r custom configs.
// Each volume has the following attributes:
// ts : "start triangle index", te : "end triangle index".
i_volume = 0;
for (const auto volume : object->volumes) {
fout << " <slic3r:volume ts=\"" << (triangles_offsets[i_volume]) << "\""
<< " te=\"" << (triangles_offsets[i_volume+1] - 1) << "\""
<< (volume->modifier ? " modifier=\"1\" " : " modifier=\"0\" ")
<< ">\n";
for (const std::string &key : volume->config.keys()){
fout << " <slic3r:metadata type=\"" << key
<< "\" config=\"" << volume->config.serialize(key) << "\"/>\n";
}
// Close Slic3r volume
fout << " </slic3r:volume>\n";
i_volume++;
}
// Close Slic3r volumes group.
fout << " </slic3r:volumes>\n";
// Close the mesh element.
fout << " </mesh>\n";
// Close the object element.
fout << " </object>\n";
return true;
}
bool
TMFEditor::write_build(boost::nowide::ofstream& fout)
{
// Create build element.
fout << " <build> \n";
// Write ModelInstances for each ModelObject.
int object_id = 0;
for(const auto object : model->objects){
for (const auto instance : object->instances){
fout << " <item objectid=\"" << (object_id + 1) << "\"";
// Get the rotation about x, y &z, translations and the scale vector.
double sc = instance->scaling_factor,
cosine_rz = cos(instance->rotation),
sine_rz = sin(instance->rotation),
cosine_ry = cos(instance->y_rotation),
sine_ry = sin(instance->y_rotation),
cosine_rx = cos(instance->x_rotation),
sine_rx = sin(instance->x_rotation),
tx = instance->offset.x + object->origin_translation.x ,
ty = instance->offset.y + object->origin_translation.y,
tz = instance->z_translation;
// Add the transform
fout << " transform=\""
<< (cosine_ry * cosine_rz * sc * instance->scaling_vector.x)
<< " "
<< (cosine_ry * sine_rz * sc) << " "
<< (-1 * sine_ry * sc) << " "
<< ((sine_rx * sine_ry * cosine_rz -1 * cosine_rx * sine_rz) * sc) << " "
<< ((sine_rx * sine_ry * sine_rz + cosine_rx *cosine_rz) * sc * instance->scaling_vector.y) << " "
<< (sine_rx * cosine_ry * sc) << " "
<< ((cosine_rx * sine_ry * cosine_rz + sine_rx * sine_rz) * sc) << " "
<< ((sine_rx * sine_ry * sine_rz - sine_rx * cosine_rz) * sc) << " "
<< (cosine_rx * cosine_ry * sc * instance->scaling_vector.z) << " "
<< (tx) << " "
<< (ty) << " "
<< (tz)
<< "\"/>\n";
}
object_id++;
}
fout << " </build> \n";
return true;
}
bool
TMFEditor::read_model()
{
// Extract 3dmodel.model entry.
if(!zip_archive->extract_entry("3D/3dmodel.model", "3dmodel.model"))
return false;
// Read 3D/3dmodel.model file.
XML_Parser parser = XML_ParserCreate(NULL);
if (! parser) {
std::cout << ("Couldn't allocate memory for parser\n");
return false;
}
boost::nowide::ifstream fin("3dmodel.model", std::ios::in);
if (!fin.is_open()) {
boost::nowide::cerr << "Cannot open file: " << "3dmodel.model" << std::endl;
return false;
}
// Create model parser.
TMFParserContext ctx(parser, model);
XML_SetUserData(parser, (void*)&ctx);
XML_SetElementHandler(parser, TMFParserContext::startElement, TMFParserContext::endElement);
XML_SetCharacterDataHandler(parser, TMFParserContext::characters);
char buff[8192];
bool result = false;
while (!fin.eof()) {
fin.read(buff, sizeof(buff));
if (fin.bad()) {
printf("3MF model parser: Read error\n");
break;
}
if (XML_Parse(parser, buff, fin.gcount(), fin.eof()) == XML_STATUS_ERROR) {
printf("3MF model parser: Parse error at line %lu:\n%s\n",
XML_GetCurrentLineNumber(parser),
XML_ErrorString(XML_GetErrorCode(parser)));
break;
}
if (fin.eof()) {
result = true;
break;
}
}
// Free the parser and close the file.
XML_ParserFree(parser);
fin.close();
// Remove the extracted 3dmodel.model file.
if (remove("3dmodel.model") != 0)
return false;
if (result)
ctx.endDocument();
return result;
}
bool
TMFEditor::produce_TMF()
{
// Create a new zip archive object.
zip_archive = new ZipArchive(this->zip_name, 'W');
// Check it's successfully initialized.
if(zip_archive->z_stats() == 0) return false;
// Prepare the 3MF Zip archive by writing the relationships.
if(!write_relationships())
return false;
// Prepare the 3MF Zip archive by writing the types.
if(!write_types())
return false;
// Write the model.
if(!write_model()) return false;
// Finalize the archive and end writing.
zip_archive->finalize();
return true;
}
bool
TMFEditor::consume_TMF()
{
// Open the 3MF package.
zip_archive = new ZipArchive(this->zip_name, 'R');
// Check it's successfully initialized.
if(zip_archive->z_stats() == 0) return false;
// Read model.
if(!read_model())
return false;
// Close zip archive.
zip_archive->finalize();
return true;
}
TMFEditor::~TMFEditor(){
delete zip_archive;
}
bool
TMF::write(Model& model, std::string output_file)
{
TMFEditor tmf_writer(std::move(output_file), &model);
return tmf_writer.produce_TMF();
}
bool
TMF::read(std::string input_file, Model* model)
{
if(!model) return false;
TMFEditor tmf_reader(std::move(input_file), model);
return tmf_reader.consume_TMF();
}
TMFParserContext::TMFParserContext(XML_Parser parser, Model *model):
m_parser(parser),
m_path(std::vector<TMFNodeType>()),
m_model(*model),
m_object(nullptr),
m_objects_indices(std::map<std::string, int>()),
m_output_objects(std::vector<bool>()),
m_object_vertices(std::vector<float>()),
m_volume(nullptr),
m_volume_facets(std::vector<int>())
{
m_path.reserve(9);
m_value[0] = m_value[1] = m_value[2] = "";
}
void XMLCALL
TMFParserContext::startElement(void *userData, const char *name, const char **atts){
TMFParserContext *ctx = (TMFParserContext*) userData;
ctx->startElement(name, atts);
}
void XMLCALL
TMFParserContext::endElement(void *userData, const char *name)
{
TMFParserContext *ctx = (TMFParserContext*)userData;
ctx->endElement();
}
void XMLCALL
TMFParserContext::characters(void *userData, const XML_Char *s, int len)
{
TMFParserContext *ctx = (TMFParserContext*)userData;
ctx->characters(s, len);
}
const char*
TMFParserContext::get_attribute(const char **atts, const char *id) {
if (atts == NULL)
return NULL;
while (*atts != NULL) {
if (strcmp(*(atts ++), id) == 0)
return *atts;
++ atts;
}
return NULL;
}
void
TMFParserContext::startElement(const char *name, const char **atts)
{
TMFNodeType node_type_new = NODE_TYPE_UNKNOWN;
switch (m_path.size()){
case 0:
// Must be <model> tag.
if (strcmp(name, "model") != 0)
this->stop();
node_type_new = NODE_TYPE_MODEL;
break;
case 1:
if (strcmp(name, "metadata") == 0) {
const char* metadata_name = this->get_attribute(atts, "name");
// Name is required if it's not found stop parsing.
if (!metadata_name)
this->stop();
m_value[0] = metadata_name;
node_type_new = NODE_TYPE_METADATA;
} else if (strcmp(name, "resources") == 0) {
node_type_new = NODE_TYPE_RESOURCES;
} else if (strcmp(name, "build") == 0) {
node_type_new = NODE_TYPE_BUILD;
}
break;
case 2:
if (strcmp(name, "object") == 0){
const char* object_id = get_attribute(atts, "id");
if (!object_id)
this->stop();
if(!m_object_vertices.empty())
this->stop();
// Create a new object in the model. This object should be included in another object if
// it's a component in another object.
m_object = m_model.add_object();
m_objects_indices[object_id] = int(m_model.objects.size()) - 1;
m_output_objects.push_back(1); // default value 1 means: it's must not be an output.
// Add part number.
const char* part_number = get_attribute(atts, "partnumber");
m_object->part_number = (!part_number) ? -1 : atoi(part_number);
// Add object name.
const char* object_name = get_attribute(atts, "name");
m_object->name = (!object_name) ? "" : object_name;
node_type_new = NODE_TYPE_OBJECT;
} else if (strcmp(name, "item") == 0){
// Get object id.
const char* object_id = get_attribute(atts, "objectid");
if(!object_id)
this->stop();
// Mark object as output.
m_output_objects[m_objects_indices[object_id]] = 0;
// Add instance.
ModelInstance* instance = m_model.objects[m_objects_indices[object_id]]->add_instance();
// Apply transformation if supplied.
const char* transformation_matrix = get_attribute(atts, "transform");
if(transformation_matrix){
// Decompose the affine matrix.
std::vector<double> transformations;
if(!get_transformations(transformation_matrix, transformations))
this->stop();
if(transformations.size() != 9)
this->stop();
apply_transformation(instance, transformations);
}
node_type_new = NODE_TYPE_ITEM;
}
break;
case 3:
if (strcmp(name, "mesh") == 0){
// Create a new model volume.
if(m_volume)
this->stop();
node_type_new = NODE_TYPE_MESH;
} else if (strcmp(name, "components") == 0) {
node_type_new = NODE_TYPE_COMPONENTS;
} else if (strcmp(name, "slic3r:object") == 0) {
// Create a config option.
DynamicPrintConfig *config = nullptr;
if(m_path.back() == NODE_TYPE_OBJECT && m_object)
config = &m_object->config;
// Get the config key type.
const char *key = get_attribute(atts, "type");
if (config && (print_config_def.options.find(key) != print_config_def.options.end())) {
// Get the key config string.
const char *config_value = get_attribute(atts, "config");
config->set_deserialize(key, config_value);
}
node_type_new = NODE_TYPE_SLIC3R_OBJECT_CONFIG;
}
break;
case 4:
if (strcmp(name, "vertices") == 0) {
node_type_new = NODE_TYPE_VERTICES;
} else if (strcmp(name, "triangles") == 0) {
node_type_new = NODE_TYPE_TRIANGLES;
} else if (strcmp(name, "component") == 0) {
// Read the object id.
const char* object_id = get_attribute(atts, "objectid");
if(!object_id)
this->stop();
ModelObject* component_object = m_model.objects[m_objects_indices[object_id]];
// Append it to the parent (current m_object) as a mesh since Slic3r doesn't support an object inside another.
// after applying 3d matrix transformation if found.
TriangleMesh component_mesh;
const char* transformation_matrix = get_attribute(atts, "transform");
if(transformation_matrix){
// Decompose the affine matrix.
std::vector<double> transformations;
if(!get_transformations(transformation_matrix, transformations))
this->stop();
if( transformations.size() != 9)
this->stop();
// Create a copy of the current object.
ModelObject* object_copy = m_model.add_object(*component_object, true);
apply_transformation(object_copy, transformations);
// Get the mesh of this instance object.
component_mesh = object_copy->raw_mesh();
// Delete the copy of the object.
m_model.delete_object(m_model.objects.size() - 1);
} else {
component_mesh = component_object->raw_mesh();
}
ModelVolume* volume = m_object->add_volume(component_mesh);
if(!volume)
this->stop();
node_type_new =NODE_TYPE_COMPONENT;
} else if (strcmp(name, "slic3r:volumes") == 0) {
node_type_new = NODE_TYPE_SLIC3R_VOLUMES;
}
break;
case 5:
if (strcmp(name, "vertex") == 0) {
const char* x = get_attribute(atts, "x");
const char* y = get_attribute(atts, "y");
const char* z = get_attribute(atts, "z");
if ( !x || !y || !z)
this->stop();
m_object_vertices.push_back(atof(x));
m_object_vertices.push_back(atof(y));
m_object_vertices.push_back(atof(z));
node_type_new = NODE_TYPE_VERTEX;
} else if (strcmp(name, "triangle") == 0) {
const char* v1 = get_attribute(atts, "v1");
const char* v2 = get_attribute(atts, "v2");
const char* v3 = get_attribute(atts, "v3");
if (!v1 || !v2 || !v3)
this->stop();
// Add it to the volume facets.
m_volume_facets.push_back(atoi(v1));
m_volume_facets.push_back(atoi(v2));
m_volume_facets.push_back(atoi(v3));
node_type_new = NODE_TYPE_TRIANGLE;
} else if (strcmp(name, "slic3r:volume") == 0) {
// Read start offset of the triangles.
m_value[0] = get_attribute(atts, "ts");
m_value[1] = get_attribute(atts, "te");
m_value[2] = get_attribute(atts, "modifier");
if( m_value[0].empty() || m_value[1].empty() || m_value[2].empty())
this->stop();
// Add a new volume to the current object.
if(!m_object)
this->stop();
m_volume = add_volume(stoi(m_value[0])*3, stoi(m_value[1]) * 3 + 2, static_cast<bool>(stoi(m_value[2])));
if(!m_volume)
this->stop();
node_type_new = NODE_TYPE_SLIC3R_VOLUME;
}
break;
case 6:
if( strcmp(name, "slic3r:metadata") == 0){
// Create a config option.
DynamicPrintConfig *config = nullptr;
if(!m_volume)
this->stop();
config = &m_volume->config;
const char *key = get_attribute(atts, "type");
if( config && (print_config_def.options.find(key) != print_config_def.options.end())){
const char *config_value = get_attribute(atts, "config");
config->set_deserialize(key, config_value);
}
node_type_new = NODE_TYPE_SLIC3R_METADATA;
}
default:
break;
}
m_path.push_back(node_type_new);
}
void
TMFParserContext::endElement()
{
switch (m_path.back()){
case NODE_TYPE_METADATA:
if( m_path.size() == 2) {
m_model.metadata[m_value[0]] = m_value[1];
m_value[1].clear();
}
break;
case NODE_TYPE_MESH:
// Add the object volume if no there are no added volumes in slic3r:volumes.
if(m_object->volumes.size() == 0) {
if(!m_object)
this->stop();
m_volume = add_volume(0, int(m_volume_facets.size()) - 1, 0);
if (!m_volume)
this->stop();
m_volume = nullptr;
}
break;
case NODE_TYPE_OBJECT:
if(!m_object)
this->stop();
m_object_vertices.clear();
m_volume_facets.clear();
m_object = nullptr;
break;
case NODE_TYPE_MODEL:
{
size_t deleted_objects_count = 0;
// According to 3MF spec. we must output objects found in item.
for (size_t i = 0; i < m_output_objects.size(); i++) {
if (m_output_objects[i]) {
m_model.delete_object(i - deleted_objects_count);
deleted_objects_count++;
}
}
}
break;
case NODE_TYPE_SLIC3R_VOLUME:
m_volume = nullptr;
m_value[0].clear();
m_value[1].clear();
m_value[2].clear();
break;
default:
break;
}
m_path.pop_back();
}
void
TMFParserContext::characters(const XML_Char *s, int len)
{
switch (m_path.back()) {
case NODE_TYPE_METADATA:
if(m_path.size() == 2)
m_value[1].append(s, len);
break;
default:
break;
}
}
void
TMFParserContext::endDocument()
{
}
void
TMFParserContext::stop()
{
XML_StopParser(m_parser, 0);
}
bool
TMFParserContext::get_transformations(std::string matrix, std::vector<double> &transformations)
{
// Get the values.
double m[12];
int k = 0;
std::string tmp = "";
for (size_t i= 0; i < matrix.size(); i++)
if ((matrix[i] == ' ' && !tmp.empty()) || (i == matrix.size() - 1 && !tmp.empty())) {
m[k++] = std::stof(tmp);
tmp = "";
}else
tmp += matrix[i];
if(tmp != "")
m[k++] = std::stof(tmp);
if(k != 12)
return false;
// Get the translation (x,y,z) value. Remember the matrix in 3mf is a row major not a column major.
transformations.push_back(m[9]);
transformations.push_back(m[10]);
transformations.push_back(m[11]);
// Get the scale values.
double sx = sqrt( m[0] * m[0] + m[1] * m[1] + m[2] * m[2]),
sy = sqrt( m[3] * m[3] + m[4] * m[4] + m[5] * m[5]),
sz = sqrt( m[6] * m[6] + m[7] * m[7] + m[8] * m[8]);
transformations.push_back(sx);
transformations.push_back(sy);
transformations.push_back(sz);
// Get the rotation values.
// Normalize scale from the rotation matrix.
m[0] /= sx; m[1] /= sy; m[2] /= sz;
m[3] /= sx; m[4] /= sy; m[5] /= sz;
m[6] /= sx; m[7] /= sy; m[8] /= sz;
// Get quaternion values
double q_w = sqrt(std::max(0.0, 1.0 + m[0] + m[4] + m[8])) / 2,
q_x = sqrt(std::max(0.0, 1.0 + m[0] - m[4] - m[8])) / 2,
q_y = sqrt(std::max(0.0, 1.0 - m[0] + m[4] - m[8])) / 2,
q_z = sqrt(std::max(0.0, 1.0 - m[0] - m[4] + m[8])) / 2;
q_x *= ((q_x * (m[5] - m[7])) <= 0 ? -1 : 1);
q_y *= ((q_y * (m[6] - m[2])) <= 0 ? -1 : 1);
q_z *= ((q_z * (m[1] - m[3])) <= 0 ? -1 : 1);
// Normalize quaternion values.
double q_magnitude = sqrt(q_w * q_w + q_x * q_x + q_y * q_y + q_z * q_z);
q_w /= q_magnitude;
q_x /= q_magnitude;
q_y /= q_magnitude;
q_z /= q_magnitude;
double test = q_x * q_y + q_z * q_w;
double result_x, result_y, result_z;
// singularity at north pole
if (test > 0.499)
{
result_x = 0;
result_y = 2 * atan2(q_x, q_w);
result_z = PI / 2;
}
// singularity at south pole
else if (test < -0.499)
{
result_x = 0;
result_y = -2 * atan2(q_x, q_w);
result_z = -PI / 2;
}
else
{
result_x = atan2(2 * q_x * q_w - 2 * q_y * q_z, 1 - 2 * q_x * q_x - 2 * q_z * q_z);
result_y = atan2(2 * q_y * q_w - 2 * q_x * q_z, 1 - 2 * q_y * q_y - 2 * q_z * q_z);
result_z = asin(2 * q_x * q_y + 2 * q_z * q_w);
if (result_x < 0) result_x += 2 * PI;
if (result_y < 0) result_y += 2 * PI;
if (result_z < 0) result_z += 2 * PI;
}
transformations.push_back(result_x);
transformations.push_back(result_y);
transformations.push_back(result_z);
return true;
}
void
TMFParserContext::apply_transformation(ModelObject *object, std::vector<double> &transformations)
{
// Apply scale.
Pointf3 vec(transformations[3], transformations[4], transformations[5]);
object->scale(vec);
// Apply x, y & z rotation.
object->rotate(transformations[6], X);
object->rotate(transformations[7], Y);
object->rotate(transformations[8], Z);
// Apply translation.
object->translate(transformations[0], transformations[1], transformations[2]);
return;
}
void
TMFParserContext::apply_transformation(ModelInstance *instance, std::vector<double> &transformations)
{
// Apply scale.
instance->scaling_vector = Pointf3(transformations[3], transformations[4], transformations[5]);;
// Apply x, y & z rotation.
instance->rotation = transformations[8];
instance->x_rotation = transformations[6];
instance->y_rotation = transformations[7];
// Apply translation.
instance->offset.x = transformations[0];
instance->offset.y = transformations[1];
instance->z_translation = transformations[2];
return;
}
ModelVolume*
TMFParserContext::add_volume(int start_offset, int end_offset, bool modifier)
{
ModelVolume* m_volume = nullptr;
// Add a new volume.
m_volume = m_object->add_volume(TriangleMesh());
if(!m_volume || (end_offset < start_offset)) return nullptr;
// Add the triangles.
stl_file &stl = m_volume->mesh.stl;
stl.stats.type = inmemory;
stl.stats.number_of_facets = (1 + end_offset - start_offset) / 3;
stl.stats.original_num_facets = stl.stats.number_of_facets;
stl_allocate(&stl);
int i_facet = 0;
for (int i = start_offset; i <= end_offset ;) {
stl_facet &facet = stl.facet_start[i_facet / 3];
for (unsigned int v = 0; v < 3; ++v) {
memcpy(&facet.vertex[v].x, &m_object_vertices[m_volume_facets[i++] * 3], 3 * sizeof(float));
i_facet++;
}
}
stl_get_size(&stl);
m_volume->mesh.repair();
m_volume->modifier = modifier;
return m_volume;
}
} }

Some files were not shown because too many files have changed in this diff Show More